3

The following code produces a different ciphertext every time I execute it, which shouldn't happen since the key & data being passed is same for every execution.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from base64 import b64encode, b64decode

key = '/I02fMuSSvnouuu+/vyyD7NuSEVDB/0gte/z50dM0b4='
data = 'hello world!'

cipher = AES.new(b64decode(key), AES.MODE_CBC)
padded_data = pad(data.encode(), cipher.block_size)
print(b64encode(padded_data))
# b'aGVsbG8gd29ybGQhBAQEBA=='
ciphertext = cipher.encrypt(padded_data)
print(b64encode(ciphertext))
# b'rEHH0MWIWCWUldjYBco9TA=='
ciphertext = cipher.encrypt(padded_data)
print(b64encode(ciphertext))
# b'FTpLrkZttDxMlpre3Kq8qQ=='

I am actually trying to replicate a sample PHP code to Python, the PHP code gives the same output and my Python code gives different outputs, none of which match the PHP one.

Python version 3.6.x
PyCryptoDome version 3.4.7

shad0w_wa1k3r
  • 12,955
  • 8
  • 67
  • 90
  • 1
    In CBC mode the output of block *i* depends from the output of block *i-1*... It's normal that you don't get the same result by encrypting again the same data, it's exactly the kind of problem that CBC was designed to solve. – Matteo Italia Dec 27 '17 at 10:53
  • Now, to be able to tell why you get different results in PHP you should post the original PHP code. – Matteo Italia Dec 27 '17 at 10:55
  • I think it has something to do with the `iv` arg that I am not passing in the Python code, but passing in the PHP code. Also, the [source code](https://github.com/Legrandin/pycryptodome/blob/master/lib/Crypto/Cipher/AES.py#L129) tells me that if I don't pass it, a random one is generated each time. Thanks for the inputs @MatteoItalia, I'll play around more and update if necessary. – shad0w_wa1k3r Dec 27 '17 at 10:57
  • 1
    Well yes, of course, in CBC the IV is used to xor the first block instead of using the previous block cyphertext, if you mess it up everything will be different. – Matteo Italia Dec 27 '17 at 11:01

2 Answers2

4

Every time you generate with Pycryptodome an AES cipher object in CBC mode, a random IV is created and used. It can be accessed as the attribute called iv (for instance cipher.iv).

The unique (and unpredictable) IV achieves the goal of randomizing the output even if the same message is getting encrypted multiple times (with the same key), which is a piece of information an attacker can often take advantage of.

You don't show the PHP code, but if its output does NOT change every time, it means that the IV is fixed and the code has a security vulnerability.

  • 2
    The answer is to broad, most AES implementations do not generate an IV but rely on an IV being supplied. Often the IV is prepended to the encrypted data doe use during decryption, it does not need to be secret. – zaph Dec 28 '17 at 18:48
  • @zaph I think it was sufficiently clear I was referring to how Pycryptodome works - because that is what OP is trying to use. You are right that most other libraries require an explicit IV or (even worse) simply fix the IV themselves (thereby requiring one to prepend the random IV itself) but that is also bad library design. – SquareRootOfTwentyThree Dec 29 '17 at 20:34
  • Prepending a random IV is good design, the bad portion is the lack of documentation. One option is to improve the answer with more information, instead you choose to complain about the comment. Note there was no downvote. – zaph Dec 30 '17 at 12:25
  • In most protocols that allow CBC, the IV must be delivered separately from the the ciphertext. One example: JWE where the IV is BASE64-encoded independently of the ciphertext. A library that does not allow you to clearly keep IV and ciphertext separate is not showing good design. – SquareRootOfTwentyThree Jan 01 '18 at 13:45
  • 1
    One can always strip the IV off if necessary. But having a random IV by default delivered as part of the encrypted message sure beats developers here that all to often use fixed IVs. As an example of novice developers seen here an IV is often provided when using ECB mode and often ECB mode is used in order to avoid an IV and that is horrible. The real issue is lazy developers who do not document the implementation defaults. – zaph Jan 01 '18 at 16:22
3

I forgot to pass the iv parameter while creating the cipher object.

It should be something like -

cipher = AES.new(b64decode(key), AES.MODE_CBC, iv=b'0123456789abcdef')

And yeah, as correctly pointed out by Rawing, repeatedly using the same cipher object to encrypt will give different results, but it will always give same output if you reconstruct the cipher object.

cipher = AES.new(b64decode(key), AES.MODE_CBC, iv=b'0123456789abcdef')
padded_data = pad(data.encode(), cipher.block_size)
print(b64encode(padded_data))
# b'aGVsbG8gd29ybGQhBAQEBA=='
ciphertext = cipher.encrypt(padded_data)
print(b64encode(ciphertext))
# b'8G0KL2UiCv7Uo+pKMm9G+A=='
ciphertext = cipher.encrypt(padded_data)
print(b64encode(ciphertext))
# b'tBXcf/Nf6MtxM1ulzNnIlw=='


cipher = AES.new(b64decode(key), AES.MODE_CBC, iv=b'0123456789abcdef')
padded_data = pad(data.encode(), cipher.block_size)
ciphertext = cipher.encrypt(padded_data)
print(b64encode(ciphertext))
# b'8G0KL2UiCv7Uo+pKMm9G+A=='
shad0w_wa1k3r
  • 12,955
  • 8
  • 67
  • 90