Challenge 1.1 Convert hex to base64
The first challenge is pretty straightforward, using python’s built-in functions or pwntools, as I use heavily in these challenges.
The functions a2b_hex
, unhexlify
, and ''.decode("hex")
all do the same thing. They take in an ascii string of the form “[0-9a-f]*” and decode it. The string decodes to
I'm killing your brain like a poisonous mushroom
. We can then use binascii.b2a_base64()
to convert the byte string to a base64 representation.
Challenge 1.2 Fixed XOR
This challenge uses iterators to do a fixed-length xor.
The chr()
function takes an ASCII integer code in the range 0-255 and returns a character string.
The ord()
function does the inverse, taking a character and returning its integer. We want to xor these integers.
Thus, ''.join([chr(ord(i)^ord(j)) for i, j in zip(s1, s2)])
will first xor the integers of each character of the input strings with ord()
. Then it will convert back into a character string with chr()
.
Challenge 1.3 Single-byte XOR cipher
Extending the last challenge, we can create a variable-length input xor function. Or, we can use pwntools xor()
.
I iterate through all 255 possible single character xor keys, scoring each decrypted word based on character frequency.
1 | for c in result: |
Challenge 1.4 Detect single-character XOR
This challenge asks us to find which of the 60-character strings in the file has been encrypted by single-character XOR.
Using the find_singlechar_key_xor
utility function created in the last challenge, I can iterate through all the file strings and find the possible single-character xor key and word score of each line.
The line turns out to decode to ‘Now that the party is jumping\n’, xored against the byte 5.
Challenge 1.5 Implement repeating-key XOR
This is a cop-out. Pwntools implements repeating-key xor in its utils.fiddling
library.
We can use python itertools for this as well,
1 | return ''.join([chr(ord(i)^ord(j)) for i, j in it.izip(s1, it.cycle(s2))]) |
Challenge 1.6 Break repeating-key XOR
This challenge asks us to solve repeating-key xor, aka the Vigenere cipher.
There are several steps to doing this.
- First, we need to find the xor key length. the period of the cipher.
- Next, we need to find the key character-by-character, using the single-byte xor cipher we made in Challenge 1.3.
In the first step I calculate the Hamming distance (number of diff bits between two strings) of two consecutive ciphertext blocks (of some guessed length). The correct keylength will create blocks of minimum Hamming distance.
The challenge tells us to try values from 2 to 40 for the guessed key length. I calculate the hamming distance of all combinations of 4 blocks.
1 | pairs = list(it.combinations(blocks, 2)) |
it.starmap
computes the hamming
function using arguments obtained from the iterable pairs
.
I then normalize the Hamming distance for keysize.
1 | normalized = float(reduce(lambda x, y: x+y, hamsum))/keysize |
and find the keylength with the minimum normalized hamming distance is 29.
The second step is to find the key, character-by-character. I split the ciphertext into blocks of len(keysize) and transpose them, so that I have a list of all the 1st, 2nd, etc… chars of each block. Each of these transposed messages has been single-char xored, so I can just find each single-char key and concatenate the chars to get the full xor key.
1 | blocks = [x[i:i+keysize] for i in range(0, len(x), keysize)] |
I need to use izip_longest
because the last block is shorter than the others, and izip_longest
pads the shortest elements of the iterable.
The key made by joining the single-char keys is Terminator X: Bring the noise
.
Challenge 1.7 AES in ECB mode
This challenge introduces the AES block cipher and the ECB mode. The ECB mode is problematic because it is stateless and deterministic — the same block of plaintext will encrypt to the same ciphertext.
I use the cryptography
module in Python because it’s being actively developed, although the PyCrypto library is more popular.
You initialize AES, a symmetric cipher, with
1 | cipher = Cipher(algorithm = algorithms.AES("YELLOW SUBMARINE"), mode = modes.ECB(), backend=default_backend()) |
.
To decrypt text, you use a decryptor
object and the update() and finalize() methods.
1 | d = cipher.decryptor() |
Challenge 1.8 Detect AES in ECB mode
Because ECB is deterministic, I can detect which of the strings in 8.txt is encrypted using ECB. The properties of the plaintext, such as low Hamming distance, will remain in the ciphertext. Thus, I can use minimum Hamming distance to find the correct line, similar to Challenge 6.
I create a scoring function, hamming_score_line(line)
, which will return the hamsum of a line of ciphertext.
1 | min_line_num = min(lines, key=hamming_score_line)[0] |
will be the correct answer, 133.
References
[1] pwntools