Cryptopals: 1-1 in python

I have been meaning to get better at python. I really like it but i find that it’s so long between each time I use it that i essentially have to re-learn it over and over again. This hasn’t really been a roadblock for me because python is super easy, but i’ve decided I want it to be a regular tool in my toolbox.

That being said, I’ve decided to do all the Cryptopals challenges in python!

I have been meaning to do these for quite some time, however, life just kept getting in the way. I still have limited time, but I’m dedicating a few hours a week to working on them.

I’m not trying to write the most elegant or “pythonic” code. My intentions while doing these challenges is to write code that is easy for me to reason about. It’s likely going to be verbose as well since i like to log things often at various steps so the way my code is written will typically reflect that.

Challenge 1-1 Convert hex to base64

The string:

1
49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d

Should produce:

1
SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t

So go ahead and make that happen. You’ll need to use this code for the rest of the exercises.

Cryptopals Rule

Always operate on raw bytes, never on encoded strings. Only use hex and base64 for pretty-printing.

Solution

Challenge 1-1 doesn’t really need much explanation. We’re just base 64 encoding a string and comparing it’s value against the expected value.

Full code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import sys

base64_table = [
"A","B","C","D","E","F","G","H",
"I","J","K","L","M","N","O","P",
"Q","R","S","T","U","V","W","X",
"Y","Z","a","b","c","d","e","f",
"g","h","i","j","k","l","m","n",
"o","p","q","r","s","t","u","v",
"w","x","y","z","0","1","2","3",
"4","5","6","7","8","9","+","/"
]

hex_original = "49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d"
hex_expected = "SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t"

def get_base_64_value(i):
    if i > 63:
        print("Bad! {0}", i)
        raise ValueError("Base64 value out of bounds")
    return base64_table[i]

def base64_encode(hex_s):
    base64_s = ""
    binary_s = ""
    for i in range(0, len(hex_s), 2):
        h = hex_s[i:i+2]
        binary_s += bin(int(h, 16))[2:].zfill(8)
    while binary_s:
        b = int(binary_s[:6], 2) # get 6 bits
        binary_s = binary_s[6:] # chop those 6 off original string
        base64_s += get_base_64_value(b)
    return base64_s

def main():
    print("hex_original: {}").format(hex_original)
    print("hex_expected: {}").format(hex_expected)
    hex_actual = base64_encode(hex_original)
    print("hex_actual: {}").format(hex_actual)

    if hex_expected == hex_actual:
        print("SUCCESS!")
    else:
        print("FAILURE!")

if __name__ == "__main__":
    main()

Which yields the following output:

1
2
3
4
5
$ python better.py
hex_original: 49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d
hex_expected: SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t
hex_actual: SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t
SUCCESS!
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy