AES Decryption in Python when IV and Value provided separately - python

I've got a encrypt/decrypt class setup based on this SO answer. I've tested it and it works fine. It's not working for a new API I'm pulling information from. The new API is built with PHP and is using the following package to encrypt information: https://laravel.com/docs/8.x/encryption using Laravel Crypt() command. All encrypted values are encrypted using OpenSSL and the AES-256-CBC cipher.
The enc value after the first line of the decrypt method
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
looks like this (Don't worry, this is just non-sensitive demo data):
b"{'iv': 'Ld2pjRJqoKcWnW1P3tiO9Q==', 'value': 'M9QeHtbybeUxAVuIRcQ3bA==', 'mac': 'xxxxxx......'}"
, which basically looks like a byte-string JSON. The testing encryption key is base64:69GnnXWsW1qnW+soLXOVd8Mi4AdXKBFfkw88/7F2kSg=.
I know I can turn it into a dictionary like this
import json
d = json.loads(enc)
How should I manipulate values from this dictionary to prepare it to be decrypted like other encrypted text this class can successfully decrypt?
Update:
Based on comments I've tried to modify the method to look like this:
def decrypt(self, encrypted):
enc = base64.b64decode(encrypted)
if b'{"iv":' == enc[:6]:
d = json.loads(enc)
iv = base64.b64decode(d['iv'])
val = base64.b64decode(d['value'])
else:
iv = enc[:AES.block_size]
val = enc[AES.block_size:]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(val)).decode('utf-8')
This still does not work. It doesn't crash, but I'm getting a blank string back ('') and I know that's not what was encrypted. The answer should be 'Demo'

The code in the "Update" section of the question will work without any changes. You just need to make sure to remove the "base64:" prefix in the encryption key provided. Once that is removed, it will work as expected.

Related

Struggling with AES in python

To keep it short: I am sending an encrypted message (AES with CBC) to another service, and it returns me an encrypted response, but I can't decrypt it because they are not using padding? (to be honest, I don't know much about encryption and its mechanisms).
This is my implementation (based on the documentation) of a class used to encrypt and decrypt messages.
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
class AESCipher:
def __init__(self, key, iv):
self.key = bytes.fromhex(key)
self.iv = bytes.fromhex(iv)
def encrypt(self, msg):
msg = pad(msg.encode(), AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return cipher.encrypt(msg)
def decrypt(self, msg):
cipher = AES.new(self.key, AES.MODE_CFB, self.iv)
return unpad(cipher.decrypt(msg), AES.block_size)
I can easily encrypt (and even decrypt my own message) using this implementation, but the actual response from the host always fails to decrypt. First I thought it was a problem with the host, but when I use an AES online tool, it kinda works (I used this one)
To reproduce the issue, I send a malformed message and receive an encrypted error:
msg = b"X\xb4\xc6\xc9j\x92\x8f\xe5\x84\xe5\\N7\x8bv\xb8\x02\x0e\xed*\xe7\x92\xdd/\xf1\xff\xdfj 5\x00\x91\xb5;\xb6Q\x08\xc8\xf1PFF\x1aw\x93\xa7\xbe\xa7\xafD\xe7:=\x8b\x1d\x86i\xa8\x95\x107\xf2\xbcF1\x80D\x8c\x98\x1f\xfc\x80\xc3\xd6\x81'\xf3\xd98\x93\x8bv\xf7P\xc9\xb1L,\x8aJ\x05\xd8\xd0P\x10\rQ\xba\xf5&4\x0e\xf0\x97\xf5\xa5B\xb7\xbda_?\xcbk~\xe6\xfe\xf6\x8f\x92\x1b;#\xd2\x87\xc6^\n"
The key and iv are:
key = AEC273769C9C4E9830D5FA3929BE1F5115E4BF085BCBA6ACCBAEF63E654D8AE3
iv = ACE499278E5FDC6849DDF23A8966D7CF
I get this error:
File "/home/richter/Code/Test_encryption/encryptation.py", line 22, in decrypt
return unpad(uncrypt, AES.block_size)
File "/home/richter/Code/Test_encryption/.venv/lib64/python3.10/site-packages/Crypto/Util/Padding.py", line 92, in unpad
raise ValueError("Padding is incorrect.")
ValueError: Padding is incorrect.
I also tried a different library, one written entirely in python (and boy, it's slow) but got the same issue with the size of the package.

Python AES CTR Mode only encrypts first two bytes

I want to achieve the same result in Dart and in Python in encryption using AES.
My problem is that my Python version only outputs the first two bytes successfully and nothing else.
In doing so, I (must) start in Dart.
As seen in the code, I generate an IV and a key, which is passed as a parameter.
List<String> aesEncryption(List<int> key, String message) {
final iv =
enc.IV.fromLength(16); // IV generation
final aesKey = enc.Key(Uint8List.fromList(
key)); // transform the key into the right format
enc.Encrypter encrypter =
enc.Encrypter(enc.AES(aesKey, mode: enc.AESMode.ctr));
final encryptedMessage = encrypter.encrypt(message, iv: iv);
// Here I send my encrypted message and IV to my Flask app, to use the same IV and to
// compare the results with eacht other
return [encryptedMessage.base64, iv.base64];
}
Then, eventually, I just encrypt the message "test" with the following key:
The sha256 Hash of the string "low;high"
I then send this tuple to my Flask app and receive the data as follows:
#app.route("/test", methods=["POST"])
def test():
values = request.get_json()
iv = base64.b64decode(values["iv"]) # my IV which was sent of the Dart App
sig = values["signature"] # just the AES encryption value of the Dart App
key = values["key"] # the string "low;high"
aes_key = hashlib.sha256(key.encode('utf-8')).digest() # as mentioned before, I have
# to hash the string "low;high" first and I will use this as my aes key
# I tested if the computed hash values are the same in python and dart, and yes it is so
# I create a Counter with my IV, I do not completely understand this part, maybe here is my mistake somewhere.
counter = Counter.new(128, initial_value=int.from_bytes(iv, byteorder='big'))
cipher = AES.new(aes_key, AES.MODE_CTR, counter=counter)
test = "test".encode('utf-8')
# The output of this print is: b'\xd52\xf8m\x0b\xcb"\xa7\x0e\xf7\xab\x96\x0f\xfc\x9f\n'
print(base64.b64decode(sig))
encrypted = cipher.encrypt(test)
# The output of this print is: b'\xd52\xf8m'
print(encrypted)
return "", 200
As you can see only the first two bytes are output of the python AES encryption and unfortunately, I don't understand why. I hope someone can help me with this because this implementation is essential for me.
Thank you in advance for every helpful answer!
Kind regards,
Bagheera
I was able to find the error again pretty quickly.
The problem is that automatic PKCS7 padding is used in Dart and not in Python.
So the solution (just for the matter that the encryption values are the same) is the following in Dart:
enc.Encrypter encrypter =
enc.Encrypter(enc.AES(aesKey, mode: enc.AESMode.ctr, padding: null));

AES won't decrypt properly

So I am using pycryptodome to encrypt a message using a secret key with AES. I want to then, as a test, decrypt the encrypted message using AES with the same secret key. I have done that here, but the result of the decrypted message is not the same as the encrypted message. Maybe I am misunderstanding how AES works, but I would assume that I should be able to decrypt a message encrypted with AES if I have the same secret key, but it would appear that I'm wrong. How do I make this work properly?
finalCipher = AES.new(sKey, AES.MODE_CFB)
message = input()
#Encrypt the message using the cipher
enMessage = message.encode('utf-8')
encMessage = finalCipher.encrypt(enMessage)
print(encMessage)
#Create a new cipher to decrypt the encrypted message, using the same key
otherCipher = AES.new(sKey, AES.MODE_CFB)
print(otherCipher.decrypt(encMessage))
I realized that I need more than just the original secret key to create a cipher that can decrypt messages encrypted using the original cipher. The original cipher I created has an attribute "iv" that I need to use in the constructor of the new cipher in order to be able to use it to decrypt properly, by doing this instead:
finalCipher = AES.new(sKey, AES.MODE_CFB)
message = input()
#Encrypt the message using the cipher
enMessage = message.encode('utf-8')
encMessage = finalCipher.encrypt(enMessage)
print(encMessage)
#Create a new cipher to decrypt the encrypted message, using the same key
otherCipher = AES.new(sKey, AES.MODE_CFB, finalCipher.iv)
print(otherCipher.decrypt(encMessage))

How to check Python's AES decrypt error?

I'm using python to encrypt and decrypt files. When file encrypted, then try to decrypt like this:
from Crypto.Cipher import AES
from Crypto import Random
def decrypt(in_file, out_file, pwd, key_len=32):
bs = AES.block_size
salt = in_file.read(bs)[len('Salted__'):]
key, iv = derive_keyiv(pwd, salt, key_len, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = ''
finished = False
try:
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024*bs))
if len(next_chunk) == 0:
padding_len = ord(chunk[-1])
chunk = chunk[:-padding_len]
finished = True
out_file.write(chunk)
return True, None
except Exception as e:
return False, e
But if the password input error, the decrypt still decrypt in_file and write to out_file and no exception throw.
How to check the password error during decrypt?
AES by itself cannot check if the key is "correct". It is simply a pure function that transforms some bytes to some other bytes.
To achieve what you want, you need to implement it yourself. One way to do is to add a fixed header (like 16 bytes of zero) to the plaintext before encryption. Upon decryption, check and discard the said header if it matches or raise an error if the header mismatches.
A side note: you are doing encryption without any authentication, which is probably insecure.
Edit
First of all, you should add authentication. Encryption without authentication easily leads to many security flaws, many not obvious to the untrained. Especially since you are using AES in CBC mode, you may open yourself to padding oracle attacks without authentication.
When you do authenticated encryption the right way (encrypt-then-mac), you will get an authentication error if the user input the wrong password. If you want to further distinguish a wrong password from tampered data, you have to devise your own method, like prepending a ciphertext of magic number.

Decrypting ActiveSupport::MessageEncryptor encrypted values in python

I have a rails project that has sensitive string type values stored on a remote Postgresql database. I encrypted these strings using the ActiveSupport::MessageEncryptor (http://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html) functions. I have the key I used to encrypt them and trying to find a way to retrieve them from the database and decrypt them in a python script.
I'am open for any suggestions on how to achieve this in any other way using rails and python. And much appreciated for any advice on how to decrypt these values in python.
Thanks,
So we managed to solve this with a lot of hit and trial and lot of help from some outdated or similar codes on internet.
Versions used of different libraries:
Rails version(from which messages were being encrypted): 5.2.x
Python version we are using: 3.8
We are also using Django rest framework(3.12.2).
Versions of libraries used in the script(this bit us hard, because some libraries' new versions were not working as expected, didn't dig in much detail as to why):
pycryptodomex: 3.9.7
cryptography: 3.3.1
rubymarshal: 1.2.7
Actual encryptor/decryptor
# pylint: disable=missing-module-docstring,too-few-public-methods
import base64
import hashlib
import os
from Cryptodome.Cipher import AES
from cryptography.hazmat.primitives.ciphers import Cipher
from rubymarshal.reader import loads
from rest_framework.response import Response
from rest_framework import status
from rubymarshal.writer import writes
from dotenv import load_dotenv
load_dotenv()
class MyRailsEncryptor():
"""
This is a class for providing encryption/decryption functionality.
"""
#classmethod
def get_encrypted_data(cls, data):
"""
This method handles encryption algorithm takes in data and return encrypted data
"""
key = cls.get_key()
iv = os.urandom(16)
auth_tag = os.urandom(16)
cipher = AES.new(key, AES.MODE_GCM, iv)
ciphertext = cipher.encrypt(writes(data))
ciphertext = base64.b64encode(ciphertext)
iv = base64.b64encode(iv)
auth_tag = base64.b64encode(auth_tag)
blob = f'{ciphertext.decode("utf-8")}--{iv.decode("utf-8")}--{auth_tag.decode("utf-8")}'
return blob
#classmethod
def get_decrypted_data(cls, data):
"""
This method handles decryption algorithm takes in encrypted_data and return decrypted plain text
"""
key = cls.get_key()
ciphertext, iv, auth_tag = data.split("--")
ciphertext = base64.b64decode(ciphertext)
iv = base64.b64decode(iv)
cipher = AES.new(key, AES.MODE_GCM, iv)
try:
decrypted_data = cipher.decrypt(ciphertext)
except AssertionError as err:
return Response({"Assertion Error": err.message_dict}, status=status.HTTP_400_BAD_REQUEST)
plaintext = loads(decrypted_data)
return plaintext
#classmethod
def get_key(cls):
"""
Returns key generated by Encryption key and Encryption secret using hashlib on rails methodology
"""
return hashlib.pbkdf2_hmac('sha1', os.getenv("ENCRYPTION_KEY").encode(),
os.getenv("ENCRYPTION_SECRET").encode(), 65536, 32)
Keys will obviously be synced/provided by the encryption party, this contains a method for encryption as well, though we only need decryption.

Categories

Resources