Cryptopals: 1-2 in python

Challenge 1-2 Fixed XOR

Description

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:

1
1c0111001f010100061a024b53535009181c

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

1
686974207468652062756c6c277320657965

… should produce:

1
746865206b696420646f6e277420706c6179

Solution

I didn’t want to rewrite functions over and over again or copy/paste in between challenges so i wrote a quick lib to import functions I’ll need in various challenges. Then i started at this one. Something tricky i found that I may or may not be doing correctly is the hex conversion. I noticed whenever I convert back to Hex if there is a leading 0 it gets dropped.

Example:

1
2
3
09->9  // bad
0a->a  // bad
1f->1f // ok

Anyway, this was also straight forward. I wrote a few helper functions to convert hex to decimal and to xor a string against another string of equal length. Note that i did not add in error checking for if the lengths do not match since i know the exact inputs.

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
# cryptopals.py
import sys

def hex_digit_to_dec_digit(d):
    if '0' <= d <= '9':
        return ord(d) - ord('0')
    if 'a' <= d.lower() <= 'f':
        return ord(d.lower()) - ord('a') + 10

def hex_to_dec(h):
    if len(h) == 1:
        # missing leading 0
        h = "0" + h
    return hex_digit_to_dec_digit(h[0]) << 4 | hex_digit_to_dec_digit(h[1])

def xor_strings(string_1, string_2):
    hex_string = ""
    for i in range(0, len(string_1), 2):
        d1 = hex_to_dec(string_1[i:i+2])
        d2 = hex_to_dec(string_2[i:i+2])
        new_byte = d1 ^ d2
        h_char = hex(new_byte)[2:]
        if len(h_char) == 1:
            # missing leading 0
            h_char = "0" + h_char
        hex_string += h_char
    return hex_string

def xor_string_with_key(string_1, key):
    hex_string = ""
    key = hex_to_dec(key)
    for i in range(0, len(string_1), 2):
        c = string_1[i:i+2]
        d1 = hex_to_dec(c) # debugging
        # d1 = hex_to_dec(string_1[i:i+2])
        new_byte = d1 ^ key
        h_char = hex(new_byte)[2:]
        if len(h_char) == 1:
            # missing leading 0
            h_char = "0" + h_char
        hex_string += h_char
    return hex_string
 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
# convert.py
import sys

sys.path.append('lib') # folder i keep cryptopals.py in

from cryptopals import xor_strings

original_string = "1c0111001f010100061a024b53535009181c" # "KSSP	"
xor_string      = "686974207468652062756c6c277320657965" # "hit the bull's eye"
expected_string = "746865206b696420646f6e277420706c6179" # "the kid don't play"

def main():
    print("original: {}").format(original_string)
    print("xor w/: {}").format(xor_string)
    print("should yield: {}").format(expected_string)

    actual_string = xor_strings(original_string, xor_string)

    print("")
    print("{} = {}").format(expected_string, actual_string)

    if expected_string == actual_string:
        print("SUCCESS!")
    else:
        print("FAILURE!")

if __name__ == "__main__":
    main()

Which yields the following output:

1
2
3
4
5
6
7
$ python convert.py
original: 1c0111001f010100061a024b53535009181c
xor w/: 686974207468652062756c6c277320657965
should yield: 746865206b696420646f6e277420706c6179

746865206b696420646f6e277420706c6179 = 746865206b696420646f6e277420706c6179
SUCCESS!
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy