views:

89

answers:

2

Hi,

I'm writing a piece of code to encrypt a text using symmetric encryption. But it's not coming back with the right result...

from Crypto.Cipher import AES
import os

crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter = lambda : os.urandom(16))
encrypted = crypto.encrypt("aaaaaaaaaaaaaaaa")
print crypto.decrypt(encrypted)

Here, the decrypted text is different from the original.

I don't really understand much about cryptography so please bear with me. I understand the CTR mode requires a "counter" function to supply a random counter each time, but why does it need it to be 16 bytes when my key is 32 bytes and it insists that my message is in multiples of 16 bytes too? Is this normal?

I'm guessing that it doesn't get back to the original message because the counter changed between encrypt and decrypt. But then, how is it supposed to work theoretically anyway? What am I doing wrong? Anyway, I'm forced to resort back to ECB until I figure this out :(

+1  A: 

The initialization vector ("counter") needs to stay the same, just as the key does, between encryption and decryption. It is used so that you can encode the same text a million times, and get different ciphertext each time (preventing some known plaintext attacks and pattern matching / attacks). You still need to use the same IV when decrypting as when encrypting. Usually when you start decrypting a stream, you initialize the IV to the same value that you started with when you started encrypting that stream.

See http://en.wikipedia.org/wiki/Initialization_vector for info on initialization vectors.

Note that os.urandom(16) is not 'deterministic', which is a requirement for counter functions. I suggest you use the increment function, as that is how CTR mode is designed. The initial counter value should be random, but the successive values should be fully predictable from the initial value (deterministic). The initial value may even be taken care of for you (I don't know the details)

About the key, IV, and input sizes, it sounds like the cipher you chose has a block size of 16 bytes. Everything you describe fits that and seems normal to me.

Slartibartfast
+3  A: 

The counter must return the same on decryption as it did on encryption, as you intuit, so, one 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 isn't going to be particularly secure. 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)
Alex Martelli
awesome. ok, I get it.so essentially, CTR has no advantage over ECB if I just want to encrypt one or very few things?I just want to store some passwords across sessions. Do I even need AES or should I be using something more simple?
Xster
Actually CTR can encrypt any arbitrary amount of text; it converts a block cipher into a key stream generator. There is no actual reason for the restriction that the input be multiples of a block size in this case.
Jack Lloyd
PyCrypto seems to give an error when the input is not a multiple of 16 bytes however
Xster
Yep, so you just need to pad the input (to the next closest multiple of 16) if you want to use pycrypto.
Alex Martelli
but if I use AES to encrypt passwords across sessions, I'd have to save the counter somewhere too in order to decrypt the next time I run it? Doesn't that defeat the security of having random counters?
Xster