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.
A | B | A ^ B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
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:

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.
