Skip to main content
  1. Articles/

CryptoPals: Set 1 Challenge 1 - Hex to Base64

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

This guide is a walkthrough of Set 1 - Challenge 1 from CryptoPals.

The CryptoPals challenge serves as a way to learn cryptography while also learning the vulnerabilities of various encryption methods and how to attack them. From the home page:

This is a different way to learn about crypto than taking a class or reading a book. We give you problems to solve. They’re derived from weaknesses in real-world systems and modern cryptographic constructions. We give you enough info to learn about the underlying crypto concepts yourself. When you’re finished, you’ll not only have learned a good deal about how cryptosystems are built, but you’ll also understand how they’re attacked.

Steps to Solve
#

They want us to take this Hex string:

49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d

and convert it to Base64 to get the following string:

SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t

The challenge also has a rule, stating that we should always operate on raw bytes, and never on encoded strings. Only use strings for pretty-printing.

Let’s get started.

Decode Hex
#

First, let’s write a function that will decode the hex input and return the decoded bytes. Keeping the rule in mind, let’s make it accept a slice of bytes and return a slice of bytes and an error in case there is an error decoding.

func decodeHex(encoded []byte) ([]byte, error) {
    // Initialize a slice of bytes with a length equal to the decoded length
    // of the encoded bytes.
    decoded := make([]byte, hex.DecodedLen(len(encoded)))

    // Ignore the first return value, which is the number of decoded bytes.
    _, err := hex.Decode(decoded, encoded)
    if err != nil {
        return nil, err
    }

    return decoded, nil
}

Encode to Base64
#

Now we need to write another function that will encode a provided byte slice to Base64. Again, returning the encoded bytes rather than a string.

func encodeBase64(input []byte) []byte {
    // Initialize a slice of bytes with a length equal to the encoded length
    // of the input.
    encoded := make([]byte, base64.StdEncoding.EncodedLen(len(input)))

    // Encode to Base64
    base64.StdEncoding.Encode(encoded, input)

    return encoded
}

Putting it Together
#

Let’s write one more function that will take our Hex input, and return the Base64 encoded string.

func hexToBase64(hex []byte) ([]byte, error) {
    decoded, err := decodeHex(hex)
    if err != nil {
        return nil, err
    }

    base64 := encodeBase64(decoded)

    return base64, nil
}

And now in our main() function we can accept a command line argument with the Hex value, convert that value to Base64, and print it out.

func main() {
    // Ensure the argument is supplied.
    if len(os.Args) < 2 {
        fmt.Println("usage: go run main.go <hex to convert>")
        os.Exit(1)
    }

    encoded := os.Args[1]

    base64, err := hexToBase64(encoded)
    if err != nil {
        fmt.Println("probelm converting to base64: %v", err)
        os.Exit(1)
    }
    fmt.Println(string(base64))
}

Final Code
#

Our final code for this challenge should look like this:

package main

import (
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"os"
)

func decodeHex(encoded []byte) ([]byte, error) {
    // Initialize a slice of bytes with a length equal to the decoded length
    // of the encoded bytes.
    decoded := make([]byte, hex.DecodedLen(len(encoded)))

    // Ignore the first return value, which is the number of decoded bytes.
    _, err := hex.Decode(decoded, encoded)
    if err != nil {
        return nil, err
    }

    return decoded, nil
}

func encodeBase64(input []byte) []byte {
    // Initialize a slice of bytes with a length equal to the encoded length
    // of the input.
    encoded := make([]byte, base64.StdEncoding.EncodedLen(len(input)))

    // Encode to Base64
    base64.StdEncoding.Encode(encoded, input)

    return encoded
}

func hexToBase64(hex []byte) ([]byte, error) {
    decoded, err := decodeHex(hex)
    if err != nil {
        return nil, err
    }

    base64 := encodeBase64(decoded)

    return base64, nil
}

func main() {
    // Ensure the argument is supplied.
    if len(os.Args) < 2 {
        fmt.Println("usage: go run main.go <hex to convert>")
        os.Exit(1)
    }

    encoded := os.Args[1]

    base64, err := hexToBase64(encoded)
    if err != nil {
        fmt.Println("problem converting hex to base64: %v", err)
        os.Exit(1)
    }
    fmt.Println(string(base64))
}

If we now run this program with the supplied hex encoded input from the challenge:

go run main.go 49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d

We should see the result:

output-img

If we look at the expected result the challenge wanted from above, we can see this is the same Base64 string.

SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t

Challenge complete!

Bonus (Testing)
#

As a bonus, let’s write some tests.

package main

import "testing"

func TestHexToBase64(t *testing.T) {
    // Define some test cases
    cases := []struct {
            name     string
            input    string
            want string
            wantErr  bool
        }{
            {
                name:     "empty string",
                input:    "",
                want:     "",
                wantErr:  false,
            },
            {
                name:     "valid hex to base64",
                input:    "48656c6c6f20776f726c64", // "Hello world" in hex
                want:     "SGVsbG8gd29ybGQ=",       // Base64 of "Hello world"
                wantErr:  false,
            },
            {
                name:    "invalid hex characters",
                input:   "XYZ",
                wantErr: true,
            },
        }

    // Loop over each test case and test
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            got, err := hexToBase64([]byte(tc.input))
            gotErr := err != nil
            if gotErr != tc.wantErr {
                t.Errorf("%v - error: %v, wantErr: %v", tc.name, err, tc.wantErr)
            }

            if !tc.wantErr {
                if string(got) != tc.want {
                    t.Errorf("want: %v, got: %v", tc.want, string(got))
                }
            }
        })
    }
}

Run the tests with:

go test -v

The tests should pass.

test-output

Now let’s move on to challenge two.

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