1pyaes 2===== 3 4A pure-Python implementation of the AES block cipher algorithm and the common modes of operation (CBC, CFB, CTR, ECB and OFB). 5 6 7Features 8-------- 9 10* Supports all AES key sizes 11* Supports all AES common modes 12* Pure-Python (no external dependencies) 13* BlockFeeder API allows streams to easily be encrypted and decrypted 14* Python 2.x and 3.x support (make sure you pass in bytes(), not strings for Python 3) 15 16 17API 18--- 19 20All keys may be 128 bits (16 bytes), 192 bits (24 bytes) or 256 bits (32 bytes) long. 21 22To generate a random key use: 23```python 24import os 25 26# 128 bit, 192 bit and 256 bit keys 27key_128 = os.urandom(16) 28key_192 = os.urandom(24) 29key_256 = os.urandom(32) 30``` 31 32To generate keys from simple-to-remember passwords, consider using a _password-based key-derivation function_ such as [scrypt](https://github.com/ricmoo/pyscrypt). 33 34 35### Common Modes of Operation 36 37There are many modes of operations, each with various pros and cons. In general though, the **CBC** and **CTR** modes are recommended. The **ECB is NOT recommended.**, and is included primarily for completeness. 38 39Each of the following examples assumes the following key: 40```python 41import pyaes 42 43# A 256 bit (32 byte) key 44key = "This_key_for_demo_purposes_only!" 45 46# For some modes of operation we need a random initialization vector 47# of 16 bytes 48iv = "InitializationVe" 49``` 50 51 52#### Counter Mode of Operation (recommended) 53 54```python 55aes = pyaes.AESModeOfOperationCTR(key) 56plaintext = "Text may be any length you wish, no padding is required" 57ciphertext = aes.encrypt(plaintext) 58 59# '''\xb6\x99\x10=\xa4\x96\x88\xd1\x89\x1co\xe6\x1d\xef;\x11\x03\xe3\xee 60# \xa9V?wY\xbfe\xcdO\xe3\xdf\x9dV\x19\xe5\x8dk\x9fh\xb87>\xdb\xa3\xd6 61# \x86\xf4\xbd\xb0\x97\xf1\t\x02\xe9 \xed''' 62print repr(ciphertext) 63 64# The counter mode of operation maintains state, so decryption requires 65# a new instance be created 66aes = pyaes.AESModeOfOperationCTR(key) 67decrypted = aes.decrypt(ciphertext) 68 69# True 70print decrypted == plaintext 71 72# To use a custom initial value 73counter = pyaes.Counter(initial_value = 100) 74aes = pyaes.AESModeOfOperationCTR(key, counter = counter) 75ciphertext = aes.encrypt(plaintext) 76 77# '''WZ\x844\x02\xbfoY\x1f\x12\xa6\xce\x03\x82Ei)\xf6\x97mX\x86\xe3\x9d 78# _1\xdd\xbd\x87\xb5\xccEM_4\x01$\xa6\x81\x0b\xd5\x04\xd7Al\x07\xe5 79# \xb2\x0e\\\x0f\x00\x13,\x07''' 80print repr(ciphertext) 81``` 82 83 84#### Cipher-Block Chaining (recommended) 85 86```python 87aes = pyaes.AESModeOfOperationCBC(key, iv = iv) 88plaintext = "TextMustBe16Byte" 89ciphertext = aes.encrypt(plaintext) 90 91# '\xd6:\x18\xe6\xb1\xb3\xc3\xdc\x87\xdf\xa7|\x08{k\xb6' 92print repr(ciphertext) 93 94 95# The cipher-block chaining mode of operation maintains state, so 96# decryption requires a new instance be created 97aes = pyaes.AESModeOfOperationCBC(key, iv = iv) 98decrypted = aes.decrypt(ciphertext) 99 100# True 101print decrypted == plaintext 102``` 103 104 105#### Cipher Feedback 106 107```python 108# Each block into the mode of operation must be a multiple of the segment 109# size. For this example we choose 8 bytes. 110aes = pyaes.AESModeOfOperationCFB(key, iv = iv, segment_size = 8) 111plaintext = "TextMustBeAMultipleOfSegmentSize" 112ciphertext = aes.encrypt(plaintext) 113 114# '''v\xa9\xc1w"\x8aL\x93\xcb\xdf\xa0/\xf8Y\x0b\x8d\x88i\xcb\x85rmp 115# \x85\xfe\xafM\x0c)\xd5\xeb\xaf''' 116print repr(ciphertext) 117 118 119# The cipher-block chaining mode of operation maintains state, so 120# decryption requires a new instance be created 121aes = pyaes.AESModeOfOperationCFB(key, iv = iv, segment_size = 8) 122decrypted = aes.decrypt(ciphertext) 123 124# True 125print decrypted == plaintext 126``` 127 128 129#### Output Feedback Mode of Operation 130 131```python 132aes = pyaes.AESModeOfOperationOFB(key, iv = iv) 133plaintext = "Text may be any length you wish, no padding is required" 134ciphertext = aes.encrypt(plaintext) 135 136# '''v\xa9\xc1wO\x92^\x9e\rR\x1e\xf7\xb1\xa2\x9d"l1\xc7\xe7\x9d\x87(\xc26s 137# \xdd8\xc8@\xb6\xd9!\xf5\x0cM\xaa\x9b\xc4\xedLD\xe4\xb9\xd8\xdf\x9e\xac 138# \xa1\xb8\xea\x0f\x8ev\xb5''' 139print repr(ciphertext) 140 141# The counter mode of operation maintains state, so decryption requires 142# a new instance be created 143aes = pyaes.AESModeOfOperationOFB(key, iv = iv) 144decrypted = aes.decrypt(ciphertext) 145 146# True 147print decrypted == plaintext 148``` 149 150 151#### Electronic Codebook (NOT recommended) 152 153```python 154aes = pyaes.AESModeOfOperationECB(key) 155plaintext = "TextMustBe16Byte" 156ciphertext = aes.encrypt(plaintext) 157 158# 'L6\x95\x85\xe4\xd9\xf1\x8a\xfb\xe5\x94X\x80|\x19\xc3' 159print repr(ciphertext) 160 161# Since there is no state stored in this mode of operation, it 162# is not necessary to create a new aes object for decryption. 163#aes = pyaes.AESModeOfOperationECB(key) 164decrypted = aes.decrypt(ciphertext) 165 166# True 167print decrypted == plaintext 168``` 169 170 171### BlockFeeder 172 173Since most of the modes of operations require data in specific block-sized or segment-sized blocks, it can be difficult when working with large arbitrary streams or strings of data. 174 175The BlockFeeder class is meant to make life easier for you, by buffering bytes across multiple calls and returning bytes as they are available, as well as padding or stripping the output when finished, if necessary. 176 177```python 178import pyaes 179 180# Any mode of operation can be used; for this example CBC 181key = "This_key_for_demo_purposes_only!" 182iv = "InitializationVe" 183 184ciphertext = '' 185 186# We can encrypt one line at a time, regardles of length 187encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv)) 188for line in file('/etc/passwd'): 189 ciphertext += encrypter.feed(line) 190 191# Make a final call to flush any remaining bytes and add paddin 192ciphertext += encrypter.feed() 193 194# We can decrypt the cipher text in chunks (here we split it in half) 195decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv)) 196decrypted = decrypter.feed(ciphertext[:len(ciphertext) / 2]) 197decrypted += decrypter.feed(ciphertext[len(ciphertext) / 2:]) 198 199# Again, make a final call to flush any remaining bytes and strip padding 200decrypted += decrypter.feed() 201 202print file('/etc/passwd').read() == decrypted 203``` 204 205### Stream Feeder 206 207This is meant to make it even easier to encrypt and decrypt streams and large files. 208 209```python 210import pyaes 211 212# Any mode of operation can be used; for this example CTR 213key = "This_key_for_demo_purposes_only!" 214 215# Create the mode of operation to encrypt with 216mode = pyaes.AESModeOfOperationCTR(key) 217 218# The input and output files 219file_in = file('/etc/passwd') 220file_out = file('/tmp/encrypted.bin', 'wb') 221 222# Encrypt the data as a stream, the file is read in 8kb chunks, be default 223pyaes.encrypt_stream(mode, file_in, file_out) 224 225# Close the files 226file_in.close() 227file_out.close() 228``` 229 230Decrypting is identical, except you would use `pyaes.decrypt_stream`, and the encrypted file would be the `file_in` and target for decryption the `file_out`. 231 232### AES block cipher 233 234Generally you should use one of the modes of operation above. This may however be useful for experimenting with a custom mode of operation or dealing with encrypted blocks. 235 236The block cipher requires exactly one block of data to encrypt or decrypt, and each block should be an array with each element an integer representation of a byte. 237 238```python 239import pyaes 240 241# 16 byte block of plain text 242plaintext = "Hello World!!!!!" 243plaintext_bytes = [ ord(c) for c in plaintext ] 244 245# 32 byte key (256 bit) 246key = "This_key_for_demo_purposes_only!" 247 248# Our AES instance 249aes = pyaes.AES(key) 250 251# Encrypt! 252ciphertext = aes.encrypt(plaintext_bytes) 253 254# [55, 250, 182, 25, 185, 208, 186, 95, 206, 115, 50, 115, 108, 58, 174, 115] 255print repr(ciphertext) 256 257# Decrypt! 258decrypted = aes.decrypt(ciphertext) 259 260# True 261print decrypted == plaintext_bytes 262``` 263 264What is a key? 265-------------- 266 267This seems to be a point of confusion for many people new to using encryption. You can think of the key as the *"password"*. However, these algorithms require the *"password"* to be a specific length. 268 269With AES, there are three possible key lengths, 16-bytes, 24-bytes or 32-bytes. When you create an AES object, the key size is automatically detected, so it is important to pass in a key of the correct length. 270 271Often, you wish to provide a password of arbitrary length, for example, something easy to remember or write down. In these cases, you must come up with a way to transform the password into a key, of a specific length. A **Password-Based Key Derivation Function** (PBKDF) is an algorithm designed for this exact purpose. 272 273Here is an example, using the popular (possibly obsolete?) *crypt* PBKDF: 274 275``` 276# See: https://www.dlitz.net/software/python-pbkdf2/ 277import pbkdf2 278 279password = "HelloWorld" 280 281# The crypt PBKDF returns a 48-byte string 282key = pbkdf2.crypt(password) 283 284# A 16-byte, 24-byte and 32-byte key, respectively 285key_16 = key[:16] 286key_24 = key[:24] 287key_32 = key[:32] 288``` 289 290The [scrypt](https://github.com/ricmoo/pyscrypt) PBKDF is intentionally slow, to make it more difficult to brute-force guess a password: 291 292``` 293# See: https://github.com/ricmoo/pyscrypt 294import pyscrypt 295 296password = "HelloWorld" 297 298# Salt is required, and prevents Rainbow Table attacks 299salt = "SeaSalt" 300 301# N, r, and p are parameters to specify how difficult it should be to 302# generate a key; bigger numbers take longer and more memory 303N = 1024 304r = 1 305p = 1 306 307# A 16-byte, 24-byte and 32-byte key, respectively; the scrypt algorithm takes 308# a 6-th parameter, indicating key length 309key_16 = pyscrypt.hash(password, salt, N, r, p, 16) 310key_24 = pyscrypt.hash(password, salt, N, r, p, 24) 311key_32 = pyscrypt.hash(password, salt, N, r, p, 32) 312``` 313 314Another possibility, is to use a hashing function, such as SHA256 to hash the password, but this method may be vulnerable to [Rainbow Attacks](http://en.wikipedia.org/wiki/Rainbow_table), unless you use a [salt](http://en.wikipedia.org/wiki/Salt_(cryptography)). 315 316```python 317import hashlib 318 319password = "HelloWorld" 320 321# The SHA256 hash algorithm returns a 32-byte string 322hashed = hashlib.sha256(password).digest() 323 324# A 16-byte, 24-byte and 32-byte key, respectively 325key_16 = hashed[:16] 326key_24 = hashed[:24] 327key_32 = hashed 328``` 329 330 331 332 333Performance 334----------- 335 336There is a test case provided in _/tests/test-aes.py_ which does some basic performance testing (its primary purpose is moreso as a regression test). 337 338Based on that test, in **CPython**, this library is about 30x slower than [PyCrypto](https://www.dlitz.net/software/pycrypto/) for CBC, ECB and OFB; about 80x slower for CFB; and 300x slower for CTR. 339 340Based on that same test, in **Pypy**, this library is about 4x slower than [PyCrypto](https://www.dlitz.net/software/pycrypto/) for CBC, ECB and OFB; about 12x slower for CFB; and 19x slower for CTR. 341 342The PyCrypto documentation makes reference to the counter call being responsible for the speed problems of the counter (CTR) mode of operation, which is why they use a specially optimized counter. I will investigate this problem further in the future. 343 344 345FAQ 346--- 347 348#### Why do this? 349 350The short answer, *why not?* 351 352The longer answer, is for my [pyscrypt](https://github.com/ricmoo/pyscrypt) library. I required a pure-Python AES implementation that supported 256-bit keys with the counter (CTR) mode of operation. After searching, I found several implementations, but all were missing CTR or only supported 128 bit keys. After all the work of learning AES inside and out to implement the library, it was only a marginal amount of extra work to library-ify a more general solution. So, *why not?* 353 354#### How do I get a question I have added? 355 356E-mail me at pyaes@ricmoo.com with any questions, suggestions, comments, et cetera. 357 358 359#### Can I give you my money? 360 361Umm... Ok? :-) 362 363_Bitcoin_ - `18UDs4qV1shu2CgTS2tKojhCtM69kpnWg9` 364