PyCrypto problem using AES+CTR
The counter
must return the same on decryption as it did on encryption, as you intuit, so, one (NOT SECURE AT ALL) way to do it is:
>>> secret = os.urandom(16)
>>> crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=lambda: secret)
>>> encrypted = crypto.encrypt("aaaaaaaaaaaaaaaa")
>>> print crypto.decrypt(encrypted)
aaaaaaaaaaaaaaaa
CTR is a block cipher, so the "16-at-a-time" constraint that seems to surprise you is a pretty natural one.
Of course, a so-called "counter" returning the same value at each call is grossly insecure. Doesn't take much to do better, e.g....:
import array
class Secret(object):
def __init__(self, secret=None):
if secret is None: secret = os.urandom(16)
self.secret = secret
self.reset()
def counter(self):
for i, c in enumerate(self.current):
self.current[i] = c + 1
if self.current: break
return self.current.tostring()
def reset(self):
self.current = array.array('B', self.secret)
secret = Secret()
crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=secret.counter)
encrypted = crypto.encrypt(16*'a' + 16*'b' + 16*'c')
secret.reset()
print crypto.decrypt(encrypted)
AES is a block cipher: it's an algorithm (more precisely, a pair of algorithms) that takes a key and a message block and either encrypts or decrypts the block. The size of a block is always 16 bytes, regardless of the key size.
CTR is a mode of operation. It's a pair of algorithms that builds on a block cipher to produce a stream cipher, which can encrypt and decrypt messages of arbitrary lengths.
CTR works by combining successive message blocks with the encryption of successive values of a counter. The size of the counter needs to be one block so that it's valid input for the block cipher.
- Functionally, it doesn't matter what the successive values of the counter are, as long as the encryption and decryption side use the same sequence. Usually the counter is treated as a 256-bit number and incremented for each successive block, with an initial value chosen at random. Thus, usually, the incrementation method is baked into the code, but the decryption side needs to know what the initial value is, so encryption side sends or stores the initial counter value at the beginning of the encrypted message.
- For security, it is vital to never repeat the same counter value with a given key. So for a single-use key, it's ok to start with
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
. But if the key is used multiple times then the second message is not allowed to reuse any of the counter values used by the first message, and the easiest way to ensure that is to generate the initial counter value at random (with a 2^128 space, the chances of a collision are acceptably negligible).
By letting the caller pick a counter function, the PyCrypto library gives you plenty of rope to hang yourself. You should use Crypto.Util.Counter
, not just “for better performance” as the documentation puts it, but because it's easier to build something secure than what you're likely to come up with on your own. And even so, take care to use a random initial value, which is not the default.
import binascii
import os
from Crypto.Cipher import AES
from Crypto.Util import Counter
def int_of_string(s):
return int(binascii.hexlify(s), 16)
def encrypt_message(key, plaintext):
iv = os.urandom(16)
ctr = Counter.new(128, initial_value=int_of_string(iv))
aes = AES.new(key, AES.MODE_CTR, counter=ctr)
return iv + aes.encrypt(plaintext)
def decrypt_message(key, ciphertext):
iv = ciphertext[:16]
ctr = Counter.new(128, initial_value=int_of_string(iv))
aes = AES.new(key, AES.MODE_CTR, counter=ctr)
return aes.decrypt(ciphertext[16:])
why does it need it to be 16 bytes when my key is 32 bytes
It has to be the same length as the cipher's block size. CTR mode just encrypts the counter and XORs the plaintext with the encrypted counter block.
Notes:
- the counter value MUST be unique -- if you EVER use the same counter value to encrypt two different plaintexts under the same key, you just gave away your key.
- like an IV, the counter is NOT secret -- just send it along with the ciphertext. If you make the code more complicated by trying to keep it secret, you will probably shoot yourself in the foot.
- the counter value need not be unpredictable -- starting with zero and adding one for each block is perfectly fine. But note that if you encrypt multiple messages, you need to keep track of the counter values that have already been consumed, i.e. you need to keep track of how many blocks have already been encrypted with that key (and you can't use the same key in different instances of your program or on different machines).
- the plain text can be any length -- CTR mode turns a block cipher into a stream cipher.
Standard disclaimer: Crypto is hard. If you don't understand what you are doing, you will get it wrong.
I just want to store some passwords across sessions.
Use scrypt. scrypt includes encrypt
and decrypt
which use AES-CTR with a password-derived key.
$ pip install scrypt
$ python
>>> import scrypt
>>> import getpass
>>> pw = getpass.getpass("enter password:")
enter password:
>>> encrypted = scrypt.encrypt("Guido is a space alien.",pw)
>>> out = scrypt.decrypt(encrypted,pw)
>>> out
'Guido is a space alien.'