ETOOBUSY 🚀 minimal blogging for the impatient
Cryptopals 25 - Break "random access read/write" AES CTR
TL;DR
This challenge puzzled me a bit. I guess that the point is that CTR mode makes sense if and only if the nonce is changed and not reused. In this case there’s no mentioning of it and it seems like the nonce is always the same, because we should be able to do the editing in-place.
If this is the case, then I might have cheated a bit with the edit
function, because it’s not selective but it re-encrypts everything all
the time.
Why this? Simply because we don’t need to do anything fancy:
- we can provide a ciphertext
- we can provide a substitute for the plaintext at the offset we want
So we’re in a chosen plaintext and ciphertext attack situation here, with encryption done with simple XORing with a very long key which will be kept always the same. Not very secure…
Instead of implementing strange algorithms, we’re going to substitute the whole plaintext with all zeroes, so that the result will be… the key. This is the power of XOR!
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use CryptoPals qw< ctr_mode_encrypt block_encrypter random_octets
slurp_base64 aes_ecb_decrypt
>;
my $ecb_ciphertext = slurp_base64(shift // '25.txt');
my $ecb_key = 'YELLOW SUBMARINE';
my $plaintext = aes_ecb_decrypt($ecb_ciphertext, $ecb_key);
# Here comes the attacker!
my $ciphertext = dencrypt($plaintext);
my $zeroooooos = "\x00" x length $ciphertext;
my $super_key = edit_API($ciphertext, 0, $zeroooooos);
say $ciphertext ^ $super_key;
sub dencrypt ($data) {
state $ctr_key = random_octets(16);
state $encrypter = block_encrypter($ctr_key);
state $ctr_nonce = random_octets(8);
ctr_mode_encrypt($encrypter, $ctr_nonce, $data);
}
sub edit_API ($ciphertext, $offset, $newtext) {
my $plaintext = dencrypt($ciphertext);
substr $plaintext, $offset, length($newtext), $newtext;
return dencrypt($plaintext);
}
I did probably miss the point of the whole challenge.
Stay safe and secure!
Update 2022-09-29 A gentle reader made me realize that the code
had a bug (edit_API
was completely disregarding $plaintext
and
working completely on $ciphertext
instead) but then blasted me with a
fantastic insight: providing the $ciphertext
itself as the new
plaintext will give back… the plaintext we’re after. So we have this:
my $ciphertext = dencrypt($plaintext); # simulate encryption, then...
say edit_API($ciphertext, 0, $ciphertext);
And we’re done!