Decrypting Large files with RSA in pycrypto? - python

I have been using pycrypto module for encryption and decryption with RSA key pair and algorithm. The problem is when I try encrypting large files (10kB of text file) I take the block size of 32 byte when reading the file and encrypting it
>>> f = open('10kb','rb')
>>> p = open('enc','wb')
>>> while True:
data = f.read(32)
if not data:
break
enc_data = public_key.encrypt(data,32)
p.write(enc_data[0])
p.close()
f.close()
It gives the output:
128
128
.......and the many 128 blocks it is writing
When I try to decrypt the encrypted file, I need to read it with 128 byte block so as to give back 32 byte blocks,
>>> f = open('enc','rb')
>>> p = open('dec','wb')
>>> while True:
data = f.read(128)
if not data:
break
dec_data = private_key.decrypt(data)
p.write(dec_data)
p.close()
f.close()
It is giving the output:
32
32
.....so many 32 byte blocks it is decrypting, then
128
128
128
128
Traceback (most recent call last):
File "<pyshell#251>", line 5, in <module>
enc_data = private_key.decrypt(data)
File "/usr/lib/python3/dist-packages/Crypto/PublicKey/RSA.py", line 174, in decrypt
return pubkey.pubkey.decrypt(self, ciphertext)
File "/usr/lib/python3/dist-packages/Crypto/PublicKey/pubkey.py", line 93, in decrypt
plaintext=self._decrypt(ciphertext)
File "/usr/lib/python3/dist-packages/Crypto/PublicKey/RSA.py", line 237, in _decrypt
cp = self.key._blind(ciphertext, r)
ValueError: Message too large
To the point where it is outputting the block size of 32, it is decrypting right, but where it starts with 128, its messing up. Why it is saying Message size too large ? Is there any better and fast way to decrypt large text files using pycrypto module ?

Partial answer coming along ...
RSA works on numbers. You only get bytes out of it when you serialize those long integers. Since those numbers don't have a fixed size, they are serialized with as much bytes as are necessary, but not more.
An RSA encryption c = me mod n can result in ciphertexts, which are so much smaller than n, that not all the bytes are filled, because leading zeros of the the number don't have to be serialized.
Sometimes (depending on modulus and plaintext) it may happen that you're writing a 127 byte chunk instead of a 128 byte chunk during encryption, but you're always reading a 128 byte chunk during decryption. That means, you're taking away one byte from the next chunk. When the alignment breaks, you can run into various random behaviors such as a chunk being larger than the modulus and therefore not a valid ciphertext.
There are two ways to solve that:
Always write the length of the ciphertext chunk before it.
Encryption:
data = f.read(readsize)
if not data:
break
i += 1
enc_data = public_key.encrypt(data, 32)[0]
p.write(chr(len(enc_data)))
p.write(enc_data)
Decryption:
length = f.read(1)
if not length:
break
data = f.read(ord(length))
print(length, len(data))
j += 1
dec_data = private_key.decrypt(data)
p.write(dec_data[:readsize])
At the end you have to reduce the ciphertext to the original plaintext size, because you're working without PKCS#1 v1.5 padding or OAEP.
Pad the zero bytes that are missing during encryption.
Encryption:
data = f.read(readsize)
if not data:
break
i += 1
enc_data = public_key.encrypt(data, 32)[0]
while len(enc_data) < writesize:
enc_data = "\x00" + enc_data
p.write(enc_data)
Decryption:
data = f.read(writesize)
if not data:
break
j += 1
dec_data = private_key.decrypt(data)
p.write(dec_data[:readsize])
Note that readsize = 127 and writesize = 128. Here are the full source codes for both variants.
Now, this is a partial answer, because this still leads to corrupt files, which are also too short, but at least it fixes the OP's error.

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.

How do you encrypt an image using CTR encryption?

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.

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')

binascii.Error: Incorrect padding, even when string length is multiple of 4

I am trying to convert base64 string to image by python code, but I am getting binascii.Error: Incorrect padding I have gone through with my solution but they only suggest check string length is divisible 4, if not make it divisible by 4 by adding '=' characters at the end of base64 encoded sting.
Please help in this.
PYTHON CODE: (please check code from drive for more visibility)
import base64
strOne= 'data:image/png;base64,iVBORw0KGgoAAAANSU...string has 200000 character thats why I couldn t paste'
print 'strOne Length',len(strOne)
print 'StrOne Length is completely divisible by 4 (len%4),(len/4):', len(strOne)%4,len(strOne)/4
with open("imageToSave.png", "wb") as fh:
fh.write(strOne.strip().decode('base64'))
output:
strOne Length 200000
StrOne Length is completely divisible by 4 (len%4),(len/4): 0 50000
Traceback (most recent call last):
File "/tests.py", line 13, in <module>
fh.write(strOne.strip().decode('base64'))
File "/usr/lib/python2.7/encodings/base64_codec.py", line 42, in base64_decode
output = base64.decodestring(input)
File "/usr/lib/python2.7/base64.py", line 328, in decodestring
return binascii.a2b_base64(s)
binascii.Error: Incorrect padding
by checking your link, your string has 200000 bytes all right, but it contains the header:
strOne = b"data:image/png;base64,iVBORw0KGgoAAAANSU...
This is part of MIME message or something. You have to strip this first.
strOne = strOne.partition(",")[2]
then pad (if needed)
pad = len(strOne)%4
strOne += b"="*pad
then decode using codecs (python 3 compliant)
codecs.decode(strOne.strip(),'base64')
=> "we believe in team work" :)

Python code that decrypts RSA encrypted file in chunks of 64 bit using a private exponent and modulus

I have an encrypted file that I am trying to decode as part of an experiment. After working hard and long, I have been able to extract the private exponent from the public key since the modulus was small:
openssl rsa -pubin -inform PEM -text -noout < public_key.pem
Public-Key: (64 bit)
Modulus: 16513720463601767803 (0xe52c8544a915157b)
Exponent: 65537
Now, I have:
Factors: 3917781347 x 4215069449
Private exponent: 9440767265896423601
Now, to derive the plaintext, I need to raise each 64-bit block of ciphertext to the private exponent mod the modulus. I am trying to write a Python script that will do this for me in hex data form.
This is the code I have so far:
#!/usr/bin/python
file = open('encrypted.hex', 'r')
c = file.readline()
d = 0x830457cf1ae460b1
N = 0xe52c8544a915157b
m = hex(pow(c, d, N)).rstrip("L")
f = open('new.try', 'w')
f.write(m)
f.close()
I have used xxd to extract the hex data from the ciphertext file:
xxd -p > encrypted.hex
This created a hex dump of the file called 'encrypted.hex'. The contents of this file begin like this:
7d4554292d7b9f980ed049cea0f968cf438b6fc312cf2028ce5ce2fe9f38
387b72a01bf6564f25884a2cacd187c2eeccd0cf78c2a74785f18d5e72b5
270ac3e45b6f7505347b38ec7684b1af206d73ea4a84cd59b50be56d7abf
74a569868406ab2b17846c9e448fe1392b21dac0b10fbb733536c99e598b
683be7400a1ad55c42faa171becd803b8b8f4a1fa512a33222ec042486c5
672f6200d4f00e2994b6d247a44edb6ce90795bde7ccda4433cf6fca8362
f87c68f9df6418c4f0b8fb9da39a1d173fea2b1466e646f01e2dc7fb0499
311d35ec75c15c5910b2d3e0c662de0b3b1716bab44faa2a36538bb44f6a
3c3abd37692cf95fa075b58485ad983533782d7bf51e10c0e3b18ccec972
...and so on. The tool 'xxd' created the hexdump and inserted line breaks I think.
So, 'c' is the ciphertext, 'd' is the private exponent, 'N' is the modulus and 'm' is supposed to contain the plaintext hex bytes.
However, the script gives me this error:
Traceback (most recent call last):
File "final.py", line 8, in <module>
m = hex(pow(c, d, N)).rstrip("L")
TypeError: unsupported operand type(s) for pow(): 'str', 'long', 'long'
Could anyone correct my Python script so that it is able to decipher the ciphertext as desired?
Assuming that c is a hex string with a multiple of 16 characters, you can split it every 16 characters (64-bit) and then convert this hex chunk into an int to run your textbook RSA on it. Since your hex file has line breaks, you first need to remove those line breaks. You can read the whole file like with file.read() instead of file.readline().
Final code
n = 16
c = c.replace('\n', '').replace('\r', '')
m = [hex(pow(int(c[i:i+n], 16), d, N)).rstrip("L") for i in range(0, len(c), n)]
f = open('new.try', 'w')
f.write(''.join(m))
f.close()

Categories