How do you encrypt an image using CTR encryption? - python

Good afternoon, I am trying to write a program to read a .bmp file and encrypt it using the given initial value using 𝑐𝑡𝑟 and the one-time pad.
The first 36 bytes form the header of the image and are not encrypted, but just copied to the new file
The image data beginning at 0x36 to the end are grouped into four-byte words and each word is encrypted using 𝑐𝑡𝑟.
To avoid changing the size of the image, do not include 𝑐0=𝑐𝑡𝑟 in the encrypted image.
As of now, this is what I have:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util import Counter
filename = "Image11.bmp"
filename_out = "Image11Encrypted.bmp"
key = 0xe0984dd3
bkey = key.to_bytes(32, 'big')
cipher = AES.new(bkey, AES.MODE_CTR, initial_value= 0xff128eff)
def encrypt(filename, filename_out, key):
with open(filename, "rb") as f:
clear = f.read()
clear_trimmed = clear[64:-2]
ciphertext = clear_trimmed
ciphertext = cipher.encrypt(pad(clear_trimmed, 16))
ciphertext = clear[0:64] + ciphertext + clear[-2:]
with open(filename_out, "wb") as f:
f.write(ciphertext)
encrypt(filename, filename_out, key)
print("Encrypted using AES in CTR mode and saved to \"" + filename_out + "\"")
However, I keep running into this Error:
.
Any help would be great, not sure where to go from here

The configuration of the CTR mode with PyCryptodome is described here. There are two ways to specify the counter block: By setting a nonce (parameter nonce) and a start value (parameter initial_value). If no nonce is specified, a random nonce of half the block size is generated implicitly.
The other way is to define a counter block object (parameter counter), which can be used to specify the components of the counter block in detail (prefix, counter, suffix).
If only the start value is to be specified, the parameter initial_value must be used instead of counter:
cipher = AES.new(bkey, AES.MODE_CTR, initial_value=0xff128eff)
As mentioned above, this implicitly creates a random nonce with half the block size, which can be determined with cipher.nonce.
Please note: The code lacks the determination of the 16 byte IV, which is needed for decryption. The IV consists of nonce and counter and is usually placed on byte level before the ciphertext.Furthermore, according to the question the first 36 bytes should not be encrypted, a little later it is stated that the data starts at 0x36 (=54) and in the code 64 is used as the beginning of the data. This seems to be inconsistent.

Related

AES Python - Different output than expected

I try to make a AES ecryption script for a HEX file in python, which should then be decrypted on a microcontroller. At the moment I want to encrypt a test array (hex, 16-byte), which I already did successfully on the microcontroller, but phyton seems to do something different.
I expected the 'expected' output when encrypted, but it gives me a much larger output, but the AES block size is 16 byte, so it should work. When I have a look at the size of the iv or password after unhexlify, it states 49, that seems totally wrong. What am I doing wrong here?
from base64 import b64encode, b64decode
from binascii import unhexlify
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
iv = "000102030405060708090A0B0C0D0E0F"
password = "2b7e151628aed2a6abf7158809cf4f3c"
msg = "6bc1bee22e409f96e93d7e117393172a"
expected = "7649abac8119b246cee98e9b12e9197d"
print(f"IV: {iv}")
print(f"PWD: {password}")
print(f"MSG: {msg}")
# Convert Hex String to Binary
iv = unhexlify(iv)
password = unhexlify(password)
# Pad to AES Block Size
msg = pad(msg.encode(), AES.block_size)
print(f"IV SIZE: {iv.__sizeof__()}")
print(f"PSW SIZE: {password.__sizeof__()}")
print(f"MSG SIZE: {msg.__sizeof__()}")
# Encipher Text
cipher = AES.new(password, AES.MODE_CBC, iv)
cipher_text = cipher.encrypt(msg)
print(cipher_text)
# Encode Cipher_text as Base 64 and decode to String
out = b64encode(cipher_text).decode('utf-8')
print(f"OUT: {out}")
# Decipher cipher text
decipher = AES.new(password, AES.MODE_CBC, iv)
# UnPad Based on AES Block Size
plaintext = unpad(decipher.decrypt(b64decode(out)), AES.block_size).decode('utf-8')
print(f'PT: {plaintext}')
Edit: When I use len(IV) instead of size, it gives the correct length. The problem is still, that the message length is somehow 48-bytes, although the AES.block_size is 16 bytes
The expected value for the ciphertext is produced when 1st the plaintext is not padded (the padding is not necessary because the length of the plaintext satisfies the length criterion, according to which the length must be an integer multiple of the blocksize, 16 bytes for AES), 2nd the message is hex decoded and 3rd the ciphertext is hex encoded.
I.e. you have to replace
msg = pad(msg.encode(), AES.block_size)
with
msg = unhexlify(msg)
and hex encode the ciphertext for the output (to get the expected value):
print(hexlify(cipher_text).decode('utf-8'))
Similarly, no unpadding may be performed during decryption and the message must be hex encoded and not UTF-8 decoded.
I.e. you have to replace
plaintext = unpad(decipher.decrypt(b64decode(out)), AES.block_size).decode('utf-8')
with
plaintext = hexlify(decipher.decrypt(b64decode(out))).decode('utf-8')
Regarding the length, you have already recognized that __sizeof__() is the wrong function and that len() should be used.

Python 3 XChaCha20 test vectors work for encryption but decryption stage fails

I use the following vectors to test XChaCha20 encryption with AEAD by Poly1305 in python:
Vectors:
https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03#appendix-A.3
pycryptodome:
https://pycryptodome.readthedocs.io/en/latest/src/cipher/chacha20_poly1305.html
The drafts use HEX for the test vectors, if you really need to check me convert using this service:
https://www.asciitohex.com/
import json
from base64 import b64encode
from base64 import b64decode
from Crypto.Cipher import ChaCha20_Poly1305
from Crypto.Random import get_random_bytes
#nonce_xchacha20 = get_random_bytes(24)
nonce_xchacha20 = b64decode("QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZX")
#header = b"header"
header = b64decode("UFFSU8DBwsPExcbH")
plaintext = b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."
#key = get_random_bytes(32)
key = b64decode("gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp8=")
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce_xchacha20)
cipher.update(header)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
jk = [ 'nonce', 'header', 'ciphertext', 'tag' ]
jv = [ b64encode(x).decode('utf-8') for x in (cipher.nonce, header, ciphertext, tag) ]
result = json.dumps(dict(zip(jk, jv)))
print(result)
# We assume that the key was securely shared beforehand
try:
b64 = json.loads(result)
jk = [ 'nonce', 'header', 'ciphertext', 'tag' ]
jv = {k:b64decode(b64[k]) for k in jk}
cipher = ChaCha20_Poly1305.new(key=key, nonce=jv['nonce'])
cipher.update(jv['header'])
plaintext = cipher.decrypt_and_verify(jv['ciphertext'], jv['tag'])
print("The message was: " + plaintext)
except (ValueError, KeyError):
print("Incorrect decryption")
print("sanity check if key values are the same: ")
print(b64encode(jv['nonce']))
print(b64encode(jv['header']))
print(b64encode(jv['ciphertext']))
print(b64encode(jv['tag']))
Why does my decryption stage fail if the test vectors encrypt correctly according to IETF-draft?
{"nonce": "AAAAAFBRUlNUVVZX", "header": "UFFSU8DBwsPExcbH", "ciphertext": "vW0XnT6D1DuVdleUk8DpOVcqFwAlK/rMvtKQLCE5bLtzHH8bC0qmRAvzqC9O2n45rmTGcIxUwhbLlrcuEhO0Ui+Mm6QNtdlFsRtpuYLBu54/P6wrw2lIj3ayODVl0//5IflmTJdjfal2iBL2FcaLE7Uu", "tag": "wIdZJMHHmHlH3q/YeArPSQ=="}
Incorrect decryption
sanity check if key values are the same:
b'AAAAAFBRUlNUVVZX'
b'UFFSU8DBwsPExcbH'
b'vW0XnT6D1DuVdleUk8DpOVcqFwAlK/rMvtKQLCE5bLtzHH8bC0qmRAvzqC9O2n45rmTGcIxUwhbLlrcuEhO0Ui+Mm6QNtdlFsRtpuYLBu54/P6wrw2lIj3ayODVl0//5IflmTJdjfal2iBL2FcaLE7Uu'
b'wIdZJMHHmHlH3q/YeArPSQ=='
When I convert the byte arrays back to base64, they still match the JSON output.
So reading my key values from JSON for decryption was done correctly.
Where is the mistake? I literally use a code example from the site offering pycryptodome and encryption was done correctly. It should decrypt just fine.
The decryption will be done correctly if you replace in the line
jv = [ b64encode(x).decode('utf-8') for x in (cipher.nonce, header, ciphertext, tag) ]
the expression cipher.nonce with nonce_xchacha20. The bug causes an incorrect nonce to be supplied in the JSON.
It seems that cipher.nonce can only be used to determine a randomly generated nonce (a random nonce is generated if no explicit nonce is specified when instantiating the cipher, s. here).
A second (trivial) change is in the line
print("The message was: " + plaintext)
necessary. Here a UTF8 decoding must be performed, i.e. plaintext must be replaced by plaintext.decode('utf8').
In your first post, the AADs were also set incorrectly. But this has been corrected in the meantime.
With these two changes, the code, especially the decryption, works on my machine.

Decrypting Python 256 AES with pycryptodome

I have a serial to TCP device which is receiving data via rs232, encrypting that data, then forwarding to a TCP server. The documentation for the device's encryption methodology is borderline nonexistent, only that its 128, 192, or 256 bit AES encryption(configurable). Judging from some other context clues in the documentation, I'm seeing that it is likely utilizing either CBC or CFB mode of operation(though that could be wrong as well). And no, I have no insight into the nature of the IV in the documentation. Perhaps its prepended to the encrypted data?
For development purposes, I have the same RS232 data going to two different devices: one is configured to encrypt the data, the other is configured to send in plaintext. Below is the same data in both plaintext and encrypted format.
Plaintext:
b'\r\x00\nLocal: \r\x00\nACCESS by user 5 22:49:32 03/07/11\r\x00\n'
Encrypted:
b'$+\xf5Sq\x1aBC\xbe\t\x8f2\x0b\xf9\xdc!\x80By2\xbb\x10k\x03G\xbb\x85\xd5u\x1dM\xeb\xfd\xa8\xf4\xa13GX|\x06\x0e\xa7K\x0f\xbc\xca\x82js)Q\xff\xbc\xbd\xe2\x05mfJ\xe7g\xdc\xd3b\xff_O\xaeNDH\xb4\x8e\xb7\xbf$2\xba\xe6\xd1\x1bu\n\xe2\x05\xae\x1a\xfc\xd7v\x06\xe6/^&v\xd4\x1a-\x0f\x16o\xc7\xeb\xc4\x90h\xe9'
Key is 64 hex characters (obviously I'll change this): 566B59703373367638792F423F4528482B4D6251655468576D5A713474377721
So when I run the following code against it, (attempting both CFB and CBC mode) I don't get anything human-readable.
from Crypto.Cipher import AES
encrypted_data = b"$+\xf5Sq\x1aBC\xbe\t\x8f2\x0b\xf9\xdc!\x80By2\xbb\x10k\x03G\xbb\x85\xd5u\x1dM\xeb\xfd\xa8\xf4\xa13GX|\x06\x0e\xa7K\x0f\xbc\xca\x82js)Q\xff\xbc\xbd\xe2\x05mfJ\xe7g\xdc\xd3b\xff_O\xaeNDH\xb4\x8e\xb7\xbf$2\xba\xe6\xd1\x1bu\n\xe2\x05\xae\x1a\xfc\xd7v\x06\xe6/^&v\xd4\x1a-\x0f\x16o\xc7\xeb\xc4\x90h\xe9"
key = bytes.fromhex("566B59703373367638792F423F4528482B4D6251655468576D5A713474377721")
def decrypt(data, key):
print("Encrypted data: ", data)
#trying CFB mode
cipher = AES.new(key, AES.MODE_CFB)
pt = cipher.decrypt(data)
print("CFB mode outputs: ", pt)
#now trying CBC mode
#padding data per CBC requirements
length = 16 - (len(data) % 16)
data += bytes([length]) * length
cipher = AES.new(key, AES.MODE_CBC)
pt = cipher.decrypt(data)
print("CBC mode outputs: ", pt)
decrypt(encrypted_data, key)
Output:
Encrypted data: b'$+\xf5Sq\x1aBC\xbe\t\x8f2\x0b\xf9\xdc!\x80By2\xbb\x10k\x03G\xbb\x85\xd5u\x1dM\xeb\xfd\xa8\xf4\xa13GX|\x06\x0e\xa7K\x0f\xbc\xca\x82js)Q\xff\xbc\xbd\xe2\x05mfJ\xe7g\xdc\xd3b\xff_O\xaeNDH\xb4\x8e\xb7\xbf$2\xba\xe6\xd1\x1bu\n\xe2\x05\xae\x1a\xfc\xd7v\x06\xe6/^&v\xd4\x1a-\x0f\x16o\xc7\xeb\xc4\x90h\xe9'
CFB mode output: b'\xd3I\x95#[\xbeA\x15"W\xf4|\x7f\x93\x9d]\r\x10\xca\x9e\xc2\x9f\x8b\xfcDp :\x94\xdb\x85tS\xc4\xf4Lc\xe4\xa45\xa1{\x07\x0fOT\xbe\xe7u\x82\x8d\x01\x9a\x91A\xd6\x0f\x83\xe8\xf8\x80HP\x83 pu\xbaG\xae\xeb.\x9cTF\x17!\xa6\x0c) \xa8\xe7\x07\xf6J\'\xa7\xbc\x05\xcf\\\x7f\x1a.\xe83n\xe2<\xb7\xe51\xdcZ\\\x8b\xb2\xf2'
CBC mode output: b'\x8d\x1f\xaf#\x8c\xab\xb7\xf5\x1a}\x05b#\xcf\xd7L\xaa\xcc\x1a6\x82\xd3)\xee\xc2\x03\xc7\xe6k\x04P+\x0b>\xc1\x9d\xf5S\x0c\x17\xaf\xf6\x1dV\xe2\xa4\x0e\x98[\xdd\xcd6\xb6\xde,\x8f\xdfS\xc2\xc3h\xc7x\xee\x10IFM\xb0\x11K\xd87\xec\x86\xc8\xac\xff\xdb\t\x19i9\xa1\xbe\xf5\x153\xfdv)\x8d\x0b\x1e\x0e\xa7I!vb\xe4\x87X6\x14\xb0\x87D\xdc\x10\x7f\x98'
How do I get this code to output something human-readable?
Any advice would be helpful! Thank you!
-B
CFB is a stream cipher mode and does not require padding. There are different CFB variants, e.g. CFB1, CFB8, CFB128. CFB128 is also often referred to as CFB.
The numerical value describes the number of plaintext bits encrypted per encryption step.
The posted ciphertext can be successfully decrypted with CFB128.
In PyCryptodome the numerical value is specified with the parameter segment_size, here
In addition, the encrypted data contains the 16 bytes IV placed in front (as you already assumed), so that the IV and the actual ciphertext must be separated first.
The following code decrypts the ciphertext:
from Crypto.Cipher import AES
encrypted_data = b"$+\xf5Sq\x1aBC\xbe\t\x8f2\x0b\xf9\xdc!\x80By2\xbb\x10k\x03G\xbb\x85\xd5u\x1dM\xeb\xfd\xa8\xf4\xa13GX|\x06\x0e\xa7K\x0f\xbc\xca\x82js)Q\xff\xbc\xbd\xe2\x05mfJ\xe7g\xdc\xd3b\xff_O\xaeNDH\xb4\x8e\xb7\xbf$2\xba\xe6\xd1\x1bu\n\xe2\x05\xae\x1a\xfc\xd7v\x06\xe6/^&v\xd4\x1a-\x0f\x16o\xc7\xeb\xc4\x90h\xe9"
key = bytes.fromhex("566B59703373367638792F423F4528482B4D6251655468576D5A713474377721")
def decrypt(data, key):
#print("Encrypted data: ", data)
iv = data[:16] # Separate IV
ciphertext = data[16:] # and actual ciphertext
#trying CFB mode
cipher = AES.new(key, AES.MODE_CFB,iv=iv,segment_size=128) # Specify CFB128
pt = cipher.decrypt(ciphertext)
print("CFB mode outputs: ", pt)
decrypt(encrypted_data, key)
with the output:
CFB mode outputs: b'\r\nLocal: \r\nACCESS by user 5 22:49:32 03/07/11\r\n'

How to decrypt a .txt.gz.enc file with a python program without knowing the key?

For this problem I have been given an encrypted text file and have been asked to find the key and then decrypt the file into a .txt.gz file.
So far, I know that the cipher I should be using is a type of substitution cipher. I was given the code that was used to encrypt the message and I know that I will need an XOR Rotation in order to find the key and decipher the message.
This is code that I had developed when I was given a key
import sys
import gzip
with open("juliaplaintext.txt.gz.enc", "rb") as f:
data = f.read()
k = data.decode("utf-8")
i =0
key = "IbSeMGjyepOr" * 10000
rotated = b""
s = open("juliaplaintext.txt.gz", "wb")
for ch0, ch1 in zip(k, key):
eb = chr(ord(ch0) ^ ord(ch1))
rotated += bytes(ord(eb) >> 7 & 0xff | ord(eb) << 7)
s.write(rotated)
s.close()
I am very new to python and am unsure how to go about creating a decoding program when I am not given the key. Any help is very appreciated.

LSB Encryption in BMP file for Image Stenography in Python

I am looking to encrypt a secret message into a BMP file in Python. I have a program that decrypts and works properly (tested using a provided image). However, the encryption is stumping me.
I've written the following code, which should be taking the least significant bits of the pixel data in a BMP file and modifying it to match the bits in the secret message.
I think there is something going on when I save the file (maybe based on the OS or Hardware) that saves the BMP differently than it would on Windows. Or I have written something incorrectly as the message is not appearing when decrypted.
Is there something I am missing here?
from PIL import Image
from bitstring import BitArray
import binascii
import os
import io
#Create a secret message
message = "This is my secret message"
#Convert the secret message to binary
message = ' '.join(format(ord(x), 'b') for x in message)
# Open the Image of choice, Dump all binary of BMP file
with open("barbara_gray.bmp", "rb") as imageFile:
f = imageFile.read()
b = bytearray(f)
# BMP Header size (start of pixel data)
BMP_Header_End = 54
# Count to keep track of where we are in the secret message
count = 0
# Variable to store bit data
bit = ""
# For the total length of the secret message
for i in range(BMP_Header_End, BMP_Header_End + len(message)):
print(count)
# Get the LSB of the image file byte
bit = str(b[i] & 1)
# If the LSB is not equal to the bit of the message
if bit != message[count]:
# Change the byte value by 1 (effectively changing the LSB by 1)
if b[i] == 255:
b[i] = b[i] - 1
else:
b[i] = b[i] + 1
# Move to the next character in the message
count += 1
print(b[54:])
# Write the binary to an image file
image = Image.open(io.BytesIO(b))
image.save('my_image.bmp', 'bmp')

Categories