CBC mode AES encryption problem with Pycrypto - python-3.x

I have the following code running like a charm
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
key = b'Sixteen byte key'
data = 'some text to encrypt'.encode("UTF-8")
data = pad(data, AES.block_size)
encryptor = AES.new(key, AES.MODE_CBC)
iv = encryptor.IV
decryptor = AES.new(key, AES.MODE_CBC, IV=iv)
ciphertext = encryptor.encrypt(data)
print(ciphertext)
plaintext = decryptor.decrypt(ciphertext)
print(unpad(plaintext, 16))
But when I try to convert to a function I got an padding error. My adapted code is
def cbc(msg, op):
key = b'Sixteen byte key'
encryptor = AES.new(key, AES.MODE_CBC)
iv = encryptor.IV
decryptor = AES.new(key, AES.MODE_CBC, IV=iv)
if op == 1:
data = msg.encode("UTF-8")
data = pad(data, AES.block_size)
ciphertext = encryptor.encrypt(data)
print(ciphertext)
else:
plaintext = decryptor.decrypt(msg)
print(unpad(plaintext, 16))
My log is
Traceback (most recent call last):
File "D:/Google Drive/max/AES.py", line 48, in <module>
cbc(b'*\xd3\xc1Y\xc2f;\xf0\xc0#\xd9E\xc5x\x11\xb4', 2)
File "D:/Google Drive/max/AES.py", line 19, in cbc
print(unpad(plaintext, 16))
File "C:\Users\Evilmaax\AppData\Local\Programs\Python\Python36\lib\site-packages\Crypto\Util\Padding.py", line 90, in unpad
raise ValueError("Padding is incorrect.")
ValueError: Padding is incorrect.
Error occurs in else statement when I try to decrypt a byte message like *\xd3\xc1Y\xc2f;\xf0\xc0#\xd9E\xc5x\x11\xb4. Important: This message was generated by the if statement of same function.
Does anyone know why this happens?
__________________________________________________________
EDIT: Using Rob Napier's tip (thank you so much) I solved the problem. If you experiencing the same issue, this is a functional version:
def cbc(key, data, op):
if op == 1:
cipher = AES.new(key, AES.MODE_CBC, iv)
data = key.encrypt(data)
print(f"Coded text: {data}")
else:
decipher = AES.new(key, AES.MODE_CBC, IV=iv)
print(f'Plaintext: {unpad(decipher.decrypt(data), BLOCK_SIZE).decode("utf-8")}')

You're generating a random IV, which is good, but you then throw it away, which means you can't decrypt the data.
This line creates an encryptor with a random IV:
encryptor = AES.new(key, AES.MODE_CBC)
This line creates a decryptor with the same IV:
decryptor = AES.new(key, AES.MODE_CBC, IV=iv)
That's fine in your initial code, but you don't save the IV anywhere, so when you put it in a function which you call twice, you're trying to decrypt with a different IV than you encrypted with.
You need to save the IV as part of the cipher text, and use it during decryption. If you want an example of such a format, see the RNCryptor spec, and if you want to see an example in Python, see RNCryptor-python.

Related

Decrypt AES ECB using python

I have some data i want to decrypt which is encrypted in AES ECB with PKCS5 padding using java. but i am having hard time decryptiing it. i tried many tutorials and stack overflow ans. but nothing worked for me. help me with this
i am trying like this
BLOCK_SIZE = 16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
def decrypt(key,enc):
enc = b64decode(enc)
cipher = AES.new(key.encode("utf8"), AES.MODE_ECB)
return unpad(cipher.decrypt(enc)).decode('utf8')
key = '0vlqRTgxr0C]X29C(}{M\\&TZErb$1!f{'
enc = 'T3cPMizpZj63+iVwXvlFUnD8Hld5XN4h3v3Ncd8YuIk='
but i am getting only empty string back
It looks like you are using the PyCrypto/PyCryptoDome package for encryption. Instead of writing your own padding function, it's easier to use the built-in one. Here's the example from the docs:
from Crypto.Util.Padding import pad, unpad
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
data = b'Unaligned' # 9 bytes
key = get_random_bytes(32)
iv = get_random_bytes(16)
cipher1 = AES.new(key, AES.MODE_CBC, iv)
ct = cipher1.encrypt(pad(data, 16))
cipher2 = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher2.decrypt(ct), 16)
assert(data == pt)
So in your case, you can get rid of the padding functions and simply rewrite the encrypt and decrypt functions to use PyCryptoDome's padding utils. I've done that for you here with two functions, encrypt and decrypt.
from Crypto.Util.Padding import pad, unpad
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from base64 import b64encode,b64decode
key = get_random_bytes(32)
iv = get_random_bytes(16)
def encrypt(plaintext,key):
cipher = AES.new(key,AES.MODE_CBC,iv)
return b64encode(cipher.encrypt(pad(plaintext.encode(),16))).decode()
def decrypt(ciphertext,key):
cipher = AES.new(key,AES.MODE_CBC,iv)
return unpad(cipher.decrypt(b64decode(ciphertext.encode())),16).decode()
And testing it:
>>> encrypt("hello",key)
'aekD8rXrimLT12hFWg22ww=='
>>> decrypt('aekD8rXrimLT12hFWg22ww==',key)
'hello'
Note that I used CBC mode because ECB mode is insecure and shouldn't ever be used. Even CBC has weaknesses, and I would recommend you use CTR mode and an HMAC, or GCM mode which will take care of everything for you.

Getting incorrect size (in bytes) of AES encrypted data using Python3

I am doing the encryption of text using AES in python.
Size of input message is 16 bytes (128 bits).
But when I am checking the size of encrypted data, it is showing 32 bytes.
Size of encrypted data should be a 16 bytes.
What is wrong in my code ?
Please help me.
Below is the code:
import binascii
from os import urandom
from Crypto.Cipher import AES
def get_text_size(txt):
return len(txt.encode('utf-8'))
def convert_byte_to_str(bt):
return bt.decode("utf-8")
secret_key = urandom(16)
iv = urandom(16)
obj = AES.new(secret_key, AES.MODE_CBC, iv)
message = 'Sample test text'
print('Original message is: ', message)
print('Size of original message is: ', get_text_size(message))
encrypted_text = obj.encrypt(b'Sample test text')
print('The encrypted text', encrypted_text)
encrypted_text_size = get_text_size(convert_byte_to_str(binascii.hexlify(encrypted_text)))
print('Size of encrypted text is: ', encrypted_text_size)
Output of the code:
Original message is: Sample test text
Size of original message is: 16
The encrypted text b'\xf1\x9f\xee\xc9\x88\xa0eg\xd1\xf3Nz\xe4\xf7-\xc7'
Size of encrypted text is: 32

I am trying to read a string encrypt it save it to a text file then read the text file read the encrypted string decrypt it in python

I am trying to read a string encrypt it save it to a text file then read the text file read the encrypted string decrypt it in python, I am using cryptography library, I think the error is because of python lists adding ["string"] at the beginning and at the end,i also tried converting the read list into binary but it takes it as b["b'key'"],any ideas
def write_key(): #creating a key
key = Fernet.generate_key()
with open("key.key", "wb") as key_file:
key_file.write(key)
def load_key(): #reading the generated key
return open("key.key", "rb").read()
data = input()
data = bytes(data,'utf-8') #you cannot encrypt str do converting it to bytes
write_key() #creating the key
key = load_key() #loading the key
f = Fernet(key)
encrypted = f.encrypt(data) #encrypting the data
print(encrypted)
file = open("encrypted.txt", "w") #writing the encrypted the data
file.write("%s\n" %(encrypted))
file.close()
with open("encrypted.txt", "r") as f: #reading the encrypted the data
rdata = [line.rstrip('\n') for line in f] #removing \n as it is added while saving the txt file
print(rdata)
key = load_key()
f = Fernet(key)
decrypted_encrypted = f.decrypt(rdata)
print(decrypted_encrypted)
Output i get:
Enter the secret message: YOUR MESSAGE
Printing the encrypted message after encryption b'gAAAAABfWdNG64dvT-tpQA1EA-zYC8lsC4hL9EoZ0e008BMIWikfafT_FOmLyjWJh2dinGG8oi6VI16XCpwB1H4AZE2sk-ZgJQ=='
Printing the encrypted message after reading it from the txt file ["b'gAAAAABfWdNG64dvT-tpQA1EA-zYC8lsC4hL9EoZ0e008BMIWikfafT_FOmLyjWJh2dinGG8oi6VI16XCpwB1H4AZE2sk-ZgJQ=='"]
Traceback (most recent call last):
File "C:\Users\Documents\Projects\temp.py", line 31, in <module>
decrypted_encrypted = f.decrypt(rdata)
File "C:\Users\anaconda3\lib\site-packages\cryptography\fernet.py", line 74, in decrypt
timestamp, data = Fernet._get_unverified_token_data(token)
File "C:\Users\anaconda3\lib\site-packages\cryptography\fernet.py", line 85, in _get_unverified_token_data
utils._check_bytes("token", token)
File "C:\Users\anaconda3\lib\site-packages\cryptography\utils.py", line 31, in _check_bytes
raise TypeError("{} must be bytes".format(name))
TypeError: token must be bytes
Data passed to decrypt should be bytes not strings. Hence this issue.
While reading and writing from encrypted.txt use respective binary mode as below.
def write_key(): #creating a key
key = Fernet.generate_key()
with open("key.key", "wb") as key_file:
key_file.write(key)
def load_key(): #reading the generated key
return open("key.key", "rb").read()
data = input()
data = bytes(data,'utf-8') #you cannot encrypt str do converting it to bytes
write_key() #creating the key
key = load_key() #loading the key
f = Fernet(key)
encrypted = f.encrypt(data) #encrypting the data
file = open("encrypted.txt", "wb") #writing the encrypted the data
file.write(encrypted)
file.close()
with open("encrypted.txt", "rb") as f: #reading the encrypted the data
rdata =f.read()
key = load_key()
f = Fernet(key)
decrypted_encrypted = f.decrypt(rdata)
print(decrypted_encrypted)

I can encrypt but not decrypt with pycrypto in python 3

I created this software in python3. The problem is that my software don't write the result if I want to decrypt a file.
I am using easygui for gui as a gui. Finally I don't have any error message. The software just close as usually but the file stay encrypted.
def decrypt(key, filename):
chunksize = 64 * 1024
outputFile = filename[11:]
with open(filename, 'rb') as infile:
filesize = int(infile.read(16))
IV = infile.read(16)
decryptor = AES.new(key, AES.MODE_CBC, IV)
with open(outputFile, 'wb') as outfile:
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
outfile.write(decryptor.decrypt(chunk))
outfile.truncate(filesize)
def getKey(password):
hasher = SHA256.new(password.encode('utf-8'))
return hasher.digest()
def Main():
image = "./images/encryption.gif"
msg = "Do you want to encrypt or decrypt a file ?"
choices = ["Encrypt", "Decrypt", "Exit"]
reply = buttonbox(msg, image=image, choices=choices)
if reply == 'Encrypt':
filename = fileopenbox(msg="Select the file to Encrypt",title="Select the file to Encrypt", default='*', filetypes=None, multiple=False)
password = passwordbox(msg="Enter a password",title="Enter a password",default="")
encrypt(getKey(password),filename)
elif reply == 'Decrypt':
filename = fileopenbox(msg="Select the file to Decrypt", title="Select the file to Decrypt", default='*', filetypes=None, multiple=False)
password = passwordbox(msg="Enter a password",title="Enter a password",default="")
decrypt(getKey(password),filename)
else:
sys.exit(0)
if __name__ == '__main__':
Main()
You have forgotten to take the requirements of AESCipher.encrypt and AESCipher.decrypt into consideration: you need to perform the required padding yourself:
For MODE_ECB, MODE_CBC, and MODE_OFB, plaintext length (in bytes) must be a multiple of block_size.
Probably you will miss part of the ciphertext if you do not provide a message (file contents) that is exactly a multiple of 16 bytes as some part of the message stays in cache. So zero to 15 bytes may be stripped off of the input file each time you perform encryption.
Obviously for very small files up to 15 bytes nothing will be left.

Skipping elif statement?

Am trying to create a simple encryption/decryption using pycryptodome but keeping getting the following error:
ValueError: Error 3 while encrypting in CBC mode
after some digging I saw that you get this error if there is not enough data to encrypt, as in there is no padding in effect. The thing is that I've added a padding function. After debugging it seems as if my code literally skips the padding part completely and causes this error. What am I doing wrong?
import os, random
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
def encrypt(key, filename):
chunksize = 64*1024
outputfile = filename + "(encrypted)"
filesize = str(os.path.getsize(filename)).zfill(16)
IV =''
for i in range(16):
IV += chr(random.randint(0, 0xFF))
encryptor = AES.new(key, AES.MODE_CBC, IV.encode("latin-1"))
with open(filename, 'rb') as infile:
with open(outputfile, 'wb') as outfile:
outfile.write(filesize.encode("latin-1"))
outfile.write(IV.encode("latin-1"))
while True:
chunk = infile.read(chunksize)
print(len(chunk))
if len(chunk) == 0:
break
elif len(chunk) % 16 != 0:
chunk += ' ' * (16 - (len(chunk) % 16))
outfile.write(encryptor.encrypt(chunk))
def decrypt(key, filename):
chunksize = 64 *1024
outputfile = filename[:11]
with open(filename, 'rb') as infile:
filesize = int(infile.read(16))
IV = infile.read(16)
decryptor = AES.new(key, AES.MODE_CBC, IV.encode("latin-1"))
with open(outputfile, 'wb') as outfile:
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
outfile.write(decryptor.decrypt(chunk))
outfile.truncate(filesize)
def getkey (password):
hasher = SHA256.new(password.encode("latin-1"))
return hasher.digest()
def main():
choice = input ("do you want to [E]ncrypt of [D]ecrypt?")
if choice == 'E':
filename = input("File to encrypt >")
password = input("Password >")
encrypt(getkey(password), filename)
print("Encryption done!")
elif choice == 'D':
filename = input("File to Decrypt >")
password = input("Password >")
decrypt(getkey(password), filename)
print("Decryption done!")
else:
print("No option selected")
if __name__ == '__main__':
main()
*I am using python 3.6
EDIT:
Here are the full console output when I run the code:
C:\Users\itayg\AppData\Local\Programs\Python\Python36\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.2\helpers\pydev\pydevd.py" --multiproc --qt-support --client 127.0.0.1 --port 21111 --file C:/Users/itayg/PycharmProjects/PyCrypto/encrypt.py
Connected to pydev debugger (build 171.4249.47)
pydev debugger: process 12876 is connecting
do you want to [E]ncrypt of [D]ecrypt?E
File to encrypt >grades.jpg
Password >123
65536
49373
Traceback (most recent call last):
File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.2\helpers\pydev\pydevd.py", line 1585, in <module>
globals = debugger.run(setup['file'], None, None, is_module)
File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.2\helpers\pydev\pydevd.py", line 1015, in run
pydev_imports.execfile(file, globals, locals) # execute the script
File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.2\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "C:/Users/itayg/PycharmProjects/PyCrypto/encrypt.py", line 66, in <module>
main()
File "C:/Users/itayg/PycharmProjects/PyCrypto/encrypt.py", line 55, in main
encrypt(getkey(password), filename)
File "C:/Users/itayg/PycharmProjects/PyCrypto/encrypt.py", line 29, in encrypt
outfile.write(encryptor.encrypt(chunk))
File "C:\Users\itayg\AppData\Local\Programs\Python\Python36\lib\site-packages\pycryptodome-3.4.6-py3.6-win-amd64.egg\Crypto\Cipher\_mode_cbc.py", line 167, in encrypt
raise ValueError("Error %d while encrypting in CBC mode" % result)
ValueError: Error 3 while encrypting in CBC mode
Ok, let's fix a few things that are wrong with your code. First the most obvious one - your padding would break on Python 3.5+ (and your user 'menu' would break on 2.x) because infile.read() would give you bytes array so trying to add a string formed by chunk += ' ' * (16 - (len(chunk) % 16)) would result in an error. You would need to convert your whitespace pad to bytes array first: chunk += b' ' * (16 - (len(chunk) % 16))
But whitespace padding like this is a bad idea - when you're later decrypting your file how will you know how much, if any, padding you've added? You need to store this somewhere - and you do in the 'header' via the filesize value, telling a potential attacker how exactly big is your file and how much padding was added opening you to a padding oracle attack (which is possible with the bellow code so do not use it for passing messages without adding a proper MAC to it).
There are plenty of robust padding schemes that you can use - I personally prefer PKCS#7 which is simply padding your uneven block or adding a whole new block with n number of bytes with the value of n - that way, after decryption, you can pick the last byte from your block and know exactly how many bytes were padded so you can strip them. So, replace your encryption portion with:
def encrypt(key, filename):
outputfile = filename + "(encrypted)"
chunksize = 1024 * AES.block_size # use the cipher's defined block size as a multiplier
IV = bytes([random.randint(0, 0xFF) for _ in range(AES.block_size)]) # bytes immediately
encryptor = AES.new(key, AES.MODE_CBC, IV)
with open(filename, 'rb') as infile:
with open(outputfile, 'wb') as outfile:
outfile.write(IV) # write the IV
padded = False
while not padded: # loop until the last block is padded
chunk = infile.read(chunksize)
chunk_len = len(chunk)
# if no more data or the data is shorter than the required block size
if chunk_len == 0 or chunk_len % AES.block_size != 0:
padding = AES.block_size - (chunk_len % AES.block_size)
chunk += bytes([padding]) * padding
# on Python 2.x replace with: chunk += chr(padding_len) * padding_len
padded = True
outfile.write(encryptor.encrypt(chunk))
I've also changed your chunksize to match the block size you're using (multiples of AES.block_size) - it just happens that 64 is a multiple of 16 but you should pay attention to those things.
Now that we have the encryption sorted out, the decryption is all this but in reversal - decrypt all blocks, read the last byte of the last block and remove n amount of bytes from behind matching the value of the last byte:
def decrypt(key, filename):
outputfile = filename[:-11] + "(decrypted)"
chunksize = 1024 * AES.block_size # use the cipher's defined block size as a multiplier
with open(filename, 'rb') as infile:
IV = infile.read(AES.block_size)
decryptor = AES.new(key, AES.MODE_CBC, IV)
with open(outputfile, 'wb') as outfile:
old_chunk = b'' # stores last chunk, needed for reading data with a delay
stripped = False
while not stripped: # delayed loop until the last block is stripped
chunk = decryptor.decrypt(infile.read(chunksize)) # decrypt as we read
if len(chunk) == 0: # no more data
padding = old_chunk[-1] # pick the padding value from the last byte
if old_chunk[-padding:] != bytes([padding]) * padding:
raise ValueError("Invalid padding...")
old_chunk = old_chunk[:-padding] # strip the padding
stripped = True
outfile.write(old_chunk) # write down the 'last' chunk
old_chunk = chunk # set the new chunk for checking in the next loop

Resources