-1

I have this implementation of a reversible encoding:

# coding=utf-8

from Crypto.Cipher import AES
from Crypto import Random
import uuid
import unittest
import random


key = r'Sixteen byte key'  # Keep this real secret
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CFB, iv)


def encode(role, plaintext):
    '''Encode the message, prefix with the role specifier'''
    msg = iv + cipher.encrypt(plaintext)
    msg = msg.encode('hex')
    msg = role + '-' + msg
    return msg


def decode(msg):
    '''Decode message, return role and plaintext'''
    role, msg = msg.split('-', 1)
    plaintext = cipher.decrypt(msg.decode('hex'))[len(iv):]
    return role, plaintext


class TestMe(unittest.TestCase):

    def test_whole(self):
        ROLES = ['sales', 'vendor', 'designer']
        for _ in xrange(100):
            role = random.choice(ROLES)
            txt = uuid.uuid4().hex
            msg = encode(role, txt)
            drole, dtxt = decode(msg)
            self.assertEqual(role, drole)
            self.assertEqual(txt, dtxt)
            print 'ok'


if __name__ == '__main__':
    unittest.main()

But this is failing, always on the second test round. I am doing something obviously wrong, but I do not know what.

Note

You need to:

pip install pycrypto

To run that code

The code fails with:

» python test.py 
ok
F
======================================================================
FAIL: test_whole (__main__.TestMe)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 40, in test_whole
    self.assertEqual(txt, dtxt)
AssertionError: 'b2e7894dd6254b259ae06350f199e6a2' != '\xa7\xcd\t\xde~\x15\xce\x9d\xcfU\x8f\xb2\xfa\x08\x98\x1c9ae06350f199e6a2'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
Artjom B.
  • 58,311
  • 24
  • 111
  • 196
blueFast
  • 33,335
  • 48
  • 165
  • 292

1 Answers1

2

The error message provides vital clues as to what is going on. As you can see, the first 16 bytes of the decrypted message are different, but the next 16 bytes are the same. This happens when the key is correct, but the IV isn't.

The problem seems to be that pyCrypto doesn't reset the state of the cipher after the encryption/decryption and the IV is some other value.

Either way, you shouldn't be setting the IV once and reusing it multiple times. The IV is there to provide randomization of the ciphertexts so that attackers who observe the ciphertexts cannot determine whether the plaintext that is encrypted has repeated.

Moving AES object creation into the function, solves this issue:

key = r'Sixteen byte key'  # Keep this real secret

def encode(role, plaintext):
    '''Encode the message, prefix with the role specifier'''
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(key, AES.MODE_CFB, iv)
    msg = iv + cipher.encrypt(plaintext)
    msg = msg.encode('hex')
    msg = role + '-' + msg
    return msg


def decode(msg):
    '''Decode message, return role and plaintext'''
    role, msg = msg.split('-', 1)
    msg = msg.decode('hex')
    iv = msg[:AES.block_size]
    cipher = AES.new(key, AES.MODE_CFB, iv)
    plaintext = cipher.decrypt(msg[AES.block_size:])
    return role, plaintext

You should check out the 2.7-alpha release of pyCrypto which includes authenticated modes such as GCM, EAX, SIV. Ciphertext authentication is important, because it might be possible to use a padding oracle attack in your system to decrypt any ciphertext.

Artjom B.
  • 58,311
  • 24
  • 111
  • 196
  • Thanks, I'll try this. I didn't know I can obtain the iv from the encrypted message. I thought it should be a fixed value both for encryption and decryption - which is probably stupid. Is it so that it iv can be anything when decripting, as long as it has the right length? Or do I really have to get it from the encrypted message? – blueFast Oct 30 '15 at 12:17
  • You have to get it off from the received ciphertext before instantiating the AES object. The IV itself doesn't have to be secret, but it has to be unpredictable, which is why it must be randomly generated during each encryption operation. – Artjom B. Oct 30 '15 at 12:19