I made the following observation with an AES encrypt/decrypt example which is very counter intuitive for me.
I tried to encrypt and the decrypt a simple payload with AES in CBC mode. My understanding is/was that the initialization vector does not have to be secret, according to this answer: https://security.stackexchange.com/a/17046. And in most examples that I have seen the initialization vector is a non random part of the encrypted payload.
But by changing the initialization vector I was able to change the message during encryption.
See for example this python example which I copied and adapted from https://stackoverflow.com/a/21928790/669561.
I set a hardcoded iv for encrypt and I slightly adapted the iv for decrypt.
With this change I could change the message from "hello world" to "hello!world".
import base64
import hashlib
from Crypto.Cipher import AES
class AESCipher(object):
def __init__(self, key):
self.bs = AES.block_size
self.key = hashlib.sha256(key.encode()).digest()
def encrypt(self, raw):
raw = self._pad(raw)
#iv = Random.new().read(AES.block_size)
# | here is the difference to the iv from decrypt
iv = b'\xe2\xe0l3H\xc42*N\xb0\x152\x98\x9cBh'
cipher = AES.new(self.key, AES.MODE_CBC, iv)
code = cipher.encrypt((raw.encode()))
return base64.b64encode(iv + code)
def decrypt(self, enc):
enc = base64.b64decode(enc)
#iv = enc[:AES.block_size]
# | here is the difference to the iv from encrypt
iv = b'\xe2\xe0l3H\xc52*N\xb0\x152\x98\x9cBh'
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
@staticmethod
def _unpad(s):
return s[:-ord(s[len(s) - 1:])]
if __name__ == '__main__':
text = "hello world"
print(text) # -> "hello world"
aes = AESCipher("F56hnXWaUWMh6ThQZ5l3mBg9zHFx6vQg")
payload = aes.encrypt(text)
print(aes.decrypt(payload)) # -> "hello!world"
The result of this simple example is completly counter-intuitive for me.
It seems that someone in the middle can take the payload, change the iv slightly and by doing so change the decrypted message without even knowing the secret key!
In my understanding it should not be that easy to change the content of the encrypted message by just changing the initialization vector. Changing the initialization vector should result in a completly different result!
Is there something wrong with my thinking?
Could you help me clarify my misunderstanding?
AES, and block ciphers in general, usually only provide "secrecy" - they make no guarantees about integrity.
Your observations are correct - changing the IV does change the resulting plaintext after decrypting. You'll also note that, in my cases, changing the bytes of the ciphertext itself can still allow a successful decryption (albeit, a different plaintext) under AES-CBC.
What you want is a way to verify that the IV and ciphertext have not been modified since the initial encryption operation took place.
The two most common ways to achieve this are:
You might find this example of AES-GCM encryption in Python useful. I've included it below: