Cryptopals Challenge 1.2: Fixed XOR

Second challenge of the first set! This one is about XOR’ing two hex strings together. Sounds straightforward but I ran into a couple of silly mistakes along the way.

Challenge

Alright, let’s move on to the second challenge. This one builds on the first one since we need to hex-decode again, but now we also need to XOR two buffers together.

Description

Here is what the challenge asks us to do:

Write a function that takes two equal-length buffers and produces their XOR combination.

If your function works properly, then when you feed it the string:

1c0111001f010100061a024b53535009181c

… after hex decoding, and when XOR’d against:

686974207468652062756c6c277320657965

… should produce:

746865206b696420646f6e277420706c6179

So basically we need to: take two hex strings, decode them to raw bytes (remember the rule from Challenge 1!), XOR them byte-by-byte, and then encode the result back to hex. Let’s do it.

Solution

My First Attempt (The Wrong Way)

I won’t lie, my first instinct was to just throw an XOR operator between the two strings and call it a day. Here is what I initially wrote:

int main()
{
    std::string intake = "1c0111001f010100061a024b53535009181c";
    std::string xor_key = "686974207468652062756c6c277320657965";
 
    while (sizeof(intake) == sizeof(xor_key)) {
    }
 
    do {
        std::string temp_1;
        StringSource ss1(intake, true,
                         new HexDecoder(new StringSink(temp_1)));
        std::string temp_2;
        StringSource ss2(xor_key, true,
                         new HexDecoder(new StringSink(temp_2)));
                         
        std::string result = temp_1 ^ temp_2;
        std::cout << result;
        return 0;
    }
 
    return -1;
}

Yeah… this had multiple problems. Let me go through them one by one because I think they are good learning moments.

Problem 1: You can’t just XOR two strings

The line std::string result = temp_1 ^ temp_2 does not work because C++ does not have an XOR operator for std::string. XOR (^) works on individual bytes/integers, not on entire string objects. We need to XOR each byte individually in a loop.

Problem 2: sizeof vs .size()

I used sizeof(intake) to compare the lengths of the two strings. This is wrong because sizeof on a std::string gives you the size of the string object itself in memory (which is always the same regardless of content), not the length of the actual text. The correct way is to use .size() which returns the number of characters in the string.

Problem 3: The while loop makes no sense

I wrote while (sizeof(intake) = sizeof(xor_key))= thinking it would act as some kind of guard, but since sizeof always returns the same value for any std::string object, this creates an infinite loop. What I actually wanted was an if statement that checks if the decoded byte lengths are equal.

The Working Solution

After understanding these mistakes, here is the correct approach. We already know the hex-to-raw-bytes pipeline from Challenge 1, so that part is the same:

#include <iostream>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>

using namespace CryptoPP;

First, we define our two hex inputs and decode both of them to raw bytes using the same StringSource pipeline from Challenge 1:

std::string intake = "1c0111001f010100061a024b53535009181c";
std::string xor_key = "686974207468652062756c6c277320657965";

std::string temp_1;
StringSource ss1(intake, true, new HexDecoder(new StringSink(temp_1)));

std::string temp_2;
StringSource ss2(xor_key, true, new HexDecoder(new StringSink(temp_2)));

Then we add a safety check to make sure both decoded byte arrays are the same length. The challenge says “equal-length buffers” so if they are not equal, something went wrong:

if (temp_1.size() != temp_2.size()) {
    std::cerr << "Inputs are not in equal size.\n";
    return -1;
}

Now the actual XOR operation. Since we can’t XOR strings directly, we loop through each byte and XOR them one at a time, appending each result to our output string:

std::string result;
for (size_t i = 0; i < temp_1.size(); i++) {
    result += temp_1[i] ^ temp_2[i];
}

Finally, we need to encode the result back to hex for display. I used HexEncoder with the false parameter which gives us lowercase hex output (the expected output is lowercase):

std::string hex_output;
StringSource ss3(
    result, true,
    new HexEncoder(new StringSink(hex_output),
                   false)); // the last false is for lowercase.
                 
std::cout << hex_output << "\n";

And that’s it! Running this gives us 746865206b696420646f6e277420706c6179 which matches the expected output. If you are curious, that hex string decodes to “the kid don’t play” which is a fun little easter egg from the Cryptopals authors.

You can reach the full code at my GitHub: https://github.com/AydoganArslantash/Cryptopals-Solutions/blob/main/set1/chal2.cpp