How To Double Your Money with Python and AES CTR

Don’t you just love making money? Well, if we use the wrong type of encryption, we could end up with the wrong amount of money being…

How To Double Your Money with Python and AES CTR

Don’t you just love making money? Well, if we use the wrong type of encryption, we could end up with the wrong amount of money being allocated to someone.

So, let’s see if we can double Bob’s money. In this case, Bob will give Alice one dollar, and then she will send a ciphertext message to Trent to pay Bob back. But Eve modifies the encrypted ciphertext so that Bob gets two dollars each time. Eve never knows the encryption key that Alice and Trent are using.

And, so, Alice encrypts “Pay Bob 1 Dollar”, with a secret key and a random salt value, and gets a cipher of “39dda42138cc4be3b62161f61ff4fe5ef89dbfd6a2e6c8af7884902558ab4cf0”, and sends this to Trent for payment to Bob.

Eve then sneaks in and changes only one part of the ciphertext to “39dda42138cc4be3b52161f61ff4fe5ef89dbfd6a2e6c8af7884902558ab4cf0”. Whe Trent decrypts the ciphertext, he gets “Pay Bob 2 Dollar”, and pays Bob two dollars. Bob is very happy and keeps giving Alice more dollars, and to keep sending these transactions to Trent. Bob soon becomes a millionaire and shares his earnings with Eve, but Alice has to sell everything — she’s lost everything!

Bit-flipping

You need to beware of bit-flipping in certain modes of AES, including with ECB, CBC and CTR. In the following, we flip the bits of a specific ciphertext, and reveal that we have changed the plaintext [here].

import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import sys

import binascii

def go_encrypt(msg,method,mode):
cipher = Cipher(method, mode)
encryptor = cipher.encryptor()
ct = encryptor.update(msg) + encryptor.finalize()
return (ct)


def go_decrypt(ct,method,mode):
cipher = Cipher(method, mode)
decryptor = cipher.decryptor()
return (decryptor.update(ct) + decryptor.finalize())


def pad(data,size=128):
padder = padding.PKCS7(size).padder()
padded_data = padder.update(data)
padded_data += padder.finalize()
return(padded_data)

def unpad(data,size=128):
padder = padding.PKCS7(size).unpadder()
unpadded_data = padder.update(data)
unpadded_data += padder.finalize()
return(unpadded_data)


key = os.urandom(32)
iv = os.urandom(16)
msg=b"Pay Bob 1 dollar"

if (len(sys.argv)>1):
msg=str(sys.argv[1]).encode()

print ("Message:\t",msg.decode())
print ("Key:\t",binascii.b2a_hex(key))
print ("IV:\t",binascii.b2a_hex(iv))

padded_data=pad(msg)

print ("\n\n=== AES ECB === ")
cipher=go_encrypt(padded_data,algorithms.AES(key), modes.CTR(iv))


plain=go_decrypt(cipher,algorithms.AES(key), modes.CTR(iv))
data=unpad(plain)

print ("Cipher: ",binascii.b2a_hex(cipher))
print (f"Decrypted: {data.decode()}")

Everything works fine:

Message:         Pay Bob 1 dollar
Key: b'927e5f7187779e110ea1d430b9675075d769320101f0ba1360d7e14a5ebe8977'
IV: b'711493f4e0fc8d711682a1c28fb45fc5'


=== AES CBC ===
Cipher: b'0d7a65428c81d83f12969e780bd09a75fb97a360740e34b717b5afa263e8d8e6'
Decrypted: Pay Bob 1 dollar

Now we want to flip the “1” to a “2”, and double our money. For this, a “1” is is 0110001, and a ‘2’ is 0110010, and so we need to invert the last two bits.

Now we can insert:

print("Before bitflip: {cipher}")
ct = bytearray(cipher)
ct[8]=ct[8]^0x03
cipher=bytes(ct)
print("After bitflip: {cipher}")

This will take the 9th byte of “Pay Bob 1 dollar” (the ‘1’) and E-XOR with 0000 0011. This will flip the two least significant bits. The updated code is [here]:



import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import sys

import binascii

def go_encrypt(msg,method,mode):
cipher = Cipher(method, mode,backend=default_backend())
encryptor = cipher.encryptor()
ct = encryptor.update(msg) + encryptor.finalize()
return (ct)


def go_decrypt(ct,method,mode):
cipher = Cipher(method, mode,backend=default_backend())
decryptor = cipher.decryptor()
return (decryptor.update(ct) + decryptor.finalize())


def pad(data,size=128):
padder = padding.PKCS7(size).padder()
padded_data = padder.update(data)
padded_data += padder.finalize()
return(padded_data)

def unpad(data,size=128):
padder = padding.PKCS7(size).unpadder()
unpadded_data = padder.update(data)
unpadded_data += padder.finalize()
return(unpadded_data)

chartoflip=9
key = os.urandom(32)
iv = os.urandom(16)
msg=b"Pay Bob 1 dollar"

if (len(sys.argv)>1):
msg=str(sys.argv[1]).encode()
if (len(sys.argv)>2):
chartoflip=int(sys.argv[2])

if (len(msg)<chartoflip):
print("Not valid")
sys.Exit(0)

print ("Message:\t",msg.decode())
print ("Key:\t",binascii.b2a_hex(key))
print ("IV:\t",binascii.b2a_hex(iv))



print ("\n\n=== AES CTR === ")
cipher=go_encrypt(msg,algorithms.AES(key), modes.CTR(iv))

print(f"Flipping two least significant bits in character {chartoflip}")
print(f"\nBefore bitflip: {cipher.hex()}")
ct = bytearray(cipher)
ct[chartoflip-1]=ct[chartoflip-1]^0x03
cipher=bytes(ct)
print(f"After bitflip: {cipher.hex()}")

plain=go_decrypt(cipher,algorithms.AES(key), modes.CTR(iv))


print ("\nCipher: ",binascii.b2a_hex(cipher))
print (f"Decrypted: {plain.decode()}")

Now when we run the code we get:

Message:	 Pay Eve 1 Dollar
Key: b'3a6d79bedf58cef355929fc9967df3d51c8f6b6e44605fc1b553d40567d575c6'
IV: b'5cd15208d8d13493b782c411d0f5b3e1'
=== AES CTR ===
Flipping two least significant bits in character 9
Before bitflip: 226cb040c0753033 e6f92af9d531ef91
After bitflip: 226cb040c0753033e5f92af9d531ef91
Cipher: b'226cb040c0753033e5 f92af9d531ef91'
Decrypted: Pay Eve 2 Dollar

Notice that “0xe6” (1110 0110) has changed to “0xb5 (1110 0101)”. And, so, we have doubled Bob’s money. We can also flip the first character to change “P” to “S”:

Message:	 Pay Eve 1 Dollar
Key: b'd1c9b21b2d95f831b17645867bec8a285b62342d98c3c30b5f77bb8db01b599b'
IV: b'612c05f73ef330ec6a8a3bee57300018'
=== AES CTR ===
Flipping two least significant bits in character 1
Before bitflip: 9d826ec52a49a57b89cec4684b7b7f76
After bitflip: 9e826ec52a49a57b89cec4684b7b7f76
Cipher: b'9e826ec52a49a57b89cec4684b7b7f76'
Decrypted: Say Eve 1 Dollar

Here is the demo with Python:

https://asecuritysite.com/hazmat/hashnew4a

And a demo using OpenSSL:

https://asecuritysite.com/blogs/chapter04

Conclusions

Use GCM or CCM mode … as they build in integrity checking!