Cryptopals 25 - Break "random access read/write" AES CTR

TL;DR

Challenge 25 in Cryptopals.

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!


Comments? Octodon, , GitHub, Reddit, or drop me a line!