ASCII-Armouring
I’m going to take a quick detour and talk about ASCII armouring. If
you’ve played with the crypto functions above, you’ll notice they
produce an annoying dump of binary data that can be a hassle to
deal with. One common technique for making the data a little bit
easier to deal with is to encode it with base64. There are a
few ways to incorporate this into python:
{Absolute Base64 Encoding}The easiest way is to just base64 encode
everything in the encrypt function. Everything that goes into the
decrypt function should be in base64 - if it’s not, the base64
module will throw an error: you could catch this and then try to
decode it as binary data.
A Simple Header
A slightly more complex option, and the one I adopt in this
article, is to use a \x00 as the first byte of the ciphertext for
binary data, and to use \x41 (an ASCII “A”) for ASCII encoded
data. This will increase the complexity of the encryption and
decryption functions slightly. We’ll also pack the initialisation
vector at the beginning of the file as well. Given now that the
iv argument might be None in the decrypt function, I will have
to rearrange the arguments a bit; for consistency, I will move it
in both functions. My modified functions look like this now:
1 def encrypt(data, key, armour=False):
2 """
3 Encrypt data using AES in CBC mode. The IV is prepended to the
4 ciphertext.
5 """
6 data = pad_data(data)
7 ivec = generate_nonce()
8 aes = AES.new(key[:__AES_KEYLEN], AES.MODE_CBC, ivec)
9 ctxt = aes.encrypt(data)
10 tag = new_tag(ivec+ctxt, key[__AES_KEYLEN:])
11 if armour:
12 return '\x41' + (ivec + ctxt + tag).encode('base64')
13 else:
14 return '\x00' + ivec + ctxt + tag
15
16 def decrypt(ciphertext, key):
17 """
18 Decrypt a ciphertext encrypted with AES in CBC mode; assumes the IV
19 has been prepended to the ciphertext.
20 """
21 if ciphertext[0] == '\x41':
22 ciphertext = ciphertext[1:].decode('base64')
23 else:
24 ciphertext = ciphertext[1:]
25 if len(ciphertext) <= AES.block_size:
26 return None, False
27 tag_start = len(ciphertext) - __TAG_LEN
28 ivec = ciphertext[:AES.block_size]
29 data = ciphertext[AES.block_size:tag_start]
30 if not verify_tag(ciphertext, key[__AES_KEYLEN:]):
31 return None, False
32 aes = AES.new(key[:__AES_KEYLEN], AES.MODE_CBC, ivec)
33 data = aes.decrypt(data)
34 return unpad_data(data), True
A More Complex Container
There are more complex ways to do it (and you’ll see it with the public keys in the next section) that involve putting the base64 into a container of sorts that contains additional information about the key.