Skip to main content
  1. Articles/

CryptoPals: Set 1 Challenge 2 - Fixed XOR

Cryptopals Go Cryptography Xor
Author
nronzel
Cryptopals Set 1 - This article is part of a series.
Part 2: This Article

This is a walkthrough of Set 1 - Challenge 2 from CryptoPals. In this challenge we focus on the task of creating a function that takes two equal-length buffers and XOR’s them, returning the result.

The full rules of the challenge state the following:

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

Steps to Solve
#

Decode Hex
#

Initially, we need to decode the Hex ecoded strings. We can use the function made in Challenge 1.

func decodeHex(encoded []byte) ([]byte, error) {
    decoded := make([]byte, hex.DecodedLen(len(encoded)))
    _, err := hex.Decode(decoded, encoded)
    if err != nil {
        return nil, err
    }
    return decoded, nil
}

Fixed XOR
#

Now, we need to decode both of the Hex encoded buffers, and XOR the bytes.

XOR, or “exclusive or”, compares two bits yielding true if either bit is true, as illustrated below.

ABA ^ B
000
011
101
110

Let’s write a function that will take our two inputs, decode them, and return the XORed result.

func fixedXOR(input1, input2 []byte) ([]byte, error) {
    // return early if the buffers aren't the same length
    if len(input1) != len(input2) {
        return []byte{}, errors.New("buffers are not equal length")
    }

    d1, err := decodeHex(input1)
    if err != nil {
        return []byte{}, err
    }

    d2, err := decodeHex(input2)
    if err != nil {
        return []byte{}, err
    }


    // XOR
    result := make([]byte, len(d1))
    for i := 0; i < len(d1); i++ {
        result[i] = d1[i] ^ d2[i]
    }

    return hexEncoded, nil
}

Putting it Together
#

In the main() function, let’s add our input strings as variables and print the result of running them through our fixedXOR() function. The challenge is looking for a Hex encoded answer, so we can encode the result and print the encoded string.

func main() {
    hex1 := []byte("1c0111001f010100061a024b53535009181c")
    hex2 := []byte("686974207468652062756c6c277320657965")

    result, err := fixedXOR(hex1, hex2)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println("Result: ", hex.EncodeToString(result))
}

Final Code
#

package main

import (
	"encoding/hex"
	"errors"
	"fmt"
)

func decodeHEX(input []byte) ([]byte, error) {
	db := make([]byte, hex.DecodedLen(len(input)))
	_, err := hex.Decode(db, input)
	if err != nil {
		return nil, err
	}
	return db, nil
}

func fixedXOR(input1, input2 []byte) ([]byte, error) {
	if len(input1) != len(input2) {
		return []byte{}, errors.New("buffers are not of equal length")
	}

	d1, err := decodeHEX(input1)
	if err != nil {
		return []byte{}, err
	}
	d2, err := decodeHEX(input2)
	if err != nil {
		return []byte{}, err
	}

	// XOR the bytes
	result := make([]byte, len(d1))
	for i := 0; i < len(d1); i++ {
		result[i] = d1[i] ^ d2[i]
	}

	return result, nil
}

func main() {
	hex1 := []byte("1c0111001f010100061a024b53535009181c")
	hex2 := []byte("686974207468652062756c6c277320657965")

	result, err := fixedXOR(hex1, hex2)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println("Result: ", hex.EncodeToString(result))
}

Now if we run this with go run main.go we should see the following result:

result

Comparing this to what the challenge expected the output to be, we can see they are the same!

746865206b696420646f6e277420706c6179

Challenge Complete

Bonus (Testing)
#

Let’s write some tests to ensure to ensure the reliability of our solution. These tests are pretty minimal; feel free to add more test cases to make them more comprehensive.

package main

import (
	"encoding/hex"
	"testing"
)

func TestFixedXOR(t *testing.T) {
	tests := []struct {
		name     string
		hex1     string
		hex2     string
		expected string
		wantErr  bool
	}{
		{
			name:     "valid input",
			hex1:     "1c0111001f010100061a024b53535009181c",
			hex2:     "686974207468652062756c6c277320657965",
			expected: "746865206b696420646f6e277420706c6179",
			wantErr:  false,
		},
		{
			name:    "invalid input",
			hex1:    "ABC",
			hex2:    "XYZ",
			wantErr: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := fixedXOR([]byte(tc.hex1), []byte(tc.hex2))
			if err != nil && !tc.wantErr {
				t.Errorf("unexpected error: %v", err)
				return
			}
			if err == nil && tc.wantErr {
				t.Error("expected error, got none")
				return
			}

            // hex encode what we got
            hexGot := make([]byte, hex.EncodedLen(len(got)))
            hex.Encode(hexGot, got)

			if !tc.wantErr && string(hexGot) != tc.expected {
				t.Errorf("expected %s, got %s", tc.expected, hexGot)
			}
		})
	}
}

Run the tests with go test -v and make sure the tests are passing.

test-output

Cryptopals Set 1 - This article is part of a series.
Part 2: This Article