Cryptopals: Set 1 – challenge 5

Breaking encryption is fun. Building an encryption cipher is even more so! Let's continue our Cryptopals journey with XOR.

Breaking cryptography is fun, but implementing it is even more so. This time around, rather than try to break a particular cipher we want to build one.

Implement repeating-key XOR

Here is the opening stanza of an important work of the English language:
Burning 'em, if you ain't quick and nimble
I go crazy when I hear a cymbal

Encrypt it, under the key “ICE”, using repeating-key XOR.

In repeating-key XOR, you’ll sequentially apply each byte of the key; the first byte of plaintext will be XOR’d against I, the next C, the next E, then I again for the 4th byte, and so on.

It should come out to: 0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272 a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f

Encrypt a bunch of stuff using your repeating-key XOR function. Encrypt your mail. Encrypt your password file. Your .sig file. Get a feel for it. I promise, we aren’t wasting your time with this.

We already have a Bytes object that will work for this message. The problem is that we can only XOR our object against a key of the same length. In order to make this work, we need to pad our key such that we can create a key of equal length.

For example, if we want to encrypt the plaintext “Bob”, the key “ICE” is adequate. If we instead want to encrypt “Alice” we’ll need to use the key “ICEIC”.

Padding a key

There are two things you need to understand here.

First, XOR with a sufficiently random and never reused key is incredibly secure. Second, padding a key with itself in this way almost entirely negates any security this cipher provides.

Now that we know what we’re getting into, let’s spin up our Bytes objects and encrypt things. We’ll want to create a new function to wrap everything that pads the key automatically. It will return a Bytes object itself, which we can encode however we want for output:

$plaintext = new Bytes("Burning 'em, if you ain't quick and nimble\nI go crazy when I hear a cymbal");

function paddedXor(Bytes $plaintext, string $key): Bytes
{
  $realKey = new Bytes(str_pad('', $plaintext->length(), $key));

  return $plaintext->xor($realKey);
}

$ciphertext = paddedXor($plaintext, 'ICE');

Controlling the output

Now here’s where things can get fun. We encoded the line break in our plaintext as a literal newline (the \n character). The resulting ciphertext will encrypt this as well, meaning we won’t get easy-to-read line breaks in our output.

To remedy this, let’s expand our Bytes object with a __toString() function that automatically selects an encoding and inserts these line breaks to print things clearing to the screen.

I’m a fan of hex-encoded strings, as their output length is easier to understand than base 64.

class Bytes
{
  // ...

  public function __toString(): string
  {
    return chunk_split($this->asHex(), 75);
  }

  // ...
}

Now, when we attempt to echo or otherwise print a Bytes object to the terminal, PHP will understand exactly how to split things up and insert newlines.

In our case, this means we can directly print the result of our encryption now with:

echo $ciphertext;

And we’ll see the properly hex-encoded, encrypted output of:

0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272
a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f