openssl_seal() in Python - python

To connect a server, I've found that, using PHP, I need to use openssl_seal(). That's OK, but I want to use Python. I'm not able to convert openssl_seal() in an equivalent function.
Can you help me?
This is what openssl_seal() does:
Description
int openssl_seal ( string $data , string &$sealed_data , array &$env_keys ,
array $pub_key_ids )
openssl_seal() seals (encrypts) data by using RC4 with a randomly generated
secret key. The key is encrypted with each of the public keys associated
with the identifiers in pub_key_ids and each encrypted key is returned in
env_keys. This means that one can send sealed data to multiple recipients
(provided one has obtained their public keys). Each recipient must receive
both the sealed data and the envelope key that was encrypted with the
recipient's public key.

this blogpost has a very detailed description of what's going on inside openssl_seal(). It also has an implementation in java.
From this, I would think it should be relatively straightforward ("the proof left as an exercise to the reader" kind of straightforward) to do an equivalent implementation in python using pyopenssl, which includes RC4, or the newer, but for these purposes more focused tlslite.

What openssl_seal does is:
Extract the public_key from the certificate
Generate a 128 bits (16 bytes) long random_key (this will be used to encrypt the message using a symmetrical algorithm, since it's faster)
Encrypt the random_key using PKCS #1
Encrypt the message using ARC4 a secure cipher method and the random_key (Note that ARC4 is no longer considered secure and that PHP strongly recommends to explicitly specify a secure cipher method using the cipher_algo param)
Output the encrypted_random_key and the encrypted_message
The receiving party can then decrypt the encrypted_random_key using their private_key and then decrypt the encrypted_message using the random_key.
Since there's no way of doing this in Python via the standard library, I'm just gonna' throw out the 3 approaches that I've tried out:
# pyca/cryptography (cryptography.io) version
# pip install cryptography
import os
import cryptography
from cryptography import x509
message = 'Super secret secret message'
message = message.encode('utf-8')
certificate_data = open('/path/to/certificate.cer', 'r').read()
certificate_data = certificate_data.encode('utf-8')
certificate = cryptography.x509.load_pem_x509_certificate(data=certificate_data, backend=cryptography.hazmat.backends.default_backend())
public_key = certificate.public_key()
random_key = os.urandom(16)
encrypted_random_key = public_key.encrypt(plaintext=random_key, padding=cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15())
print(encrypted_random_key)
algorithm = cryptography.hazmat.primitives.ciphers.algorithms.AES(random_key)
cipher = cryptography.hazmat.primitives.ciphers.Cipher(algorithm=algorithm, mode=None, backend=cryptography.hazmat.backends.default_backend())
encryptor = cipher.encryptor()
encrypted_message = encryptor.update(message)
print(encrypted_message)
.
# M2Crypto version
# pip install pip install git+https://gitlab.com/m2crypto/m2crypto#python3
import M2Crypto
message = 'Super secret secret message'
message = message.encode('utf-8')
certificate = M2Crypto.X509.load_cert('/path/to/certificate.cer')
public_key = certificate.get_pubkey()
rsa_pub = public_key.get_rsa()
random_key = M2Crypto.Rand.rand_bytes(16)
encrypted_random_key = rsa_pub.public_encrypt(random_key, M2Crypto.RSA.pkcs1_padding)
print(encrypted_random_key)
cipher = M2Crypto.EVP.Cipher(alg='aes_128_cbc', key=random_key, iv=b'', op=M2Crypto.encrypt)
encrypted_message = cipher.update(message)
encrypted_message += cipher.final()
print(encrypted_message)
.
# PyCrypto version
# Update: PyCrypto 2.x is unmaintained, obsolete, and contains security vulnerabilities!!!
# pip install pycrypto
# Please bear in mind that PyCrypto cannot handle x509 certificates.
# You will have to extract the public_key to a pem file:
# openssl x509 -inform pem -in certificate.cer -pubkey -noout > public_key.pem
from Crypto import Random
from Crypto.Cipher import ARC4
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
message = 'Super secret secret message'
message = message.encode('utf-8')
public_key_data = open('/path/to/public_key.pem', 'r').read()
public_key = RSA.importKey(public_key_data)
random_key = Random.new().read(16)
cipher = PKCS1_v1_5.new(public_key)
encrypted_random_key = cipher.encrypt(random_key)
print(encrypted_random_key)
cipher = ARC4.new(random_key)
encrypted_message = cipher.encrypt(message)
print(encrypted_message)
You can check out my post at => http://helpfulsheep.com/2017-09-01-openssl-seal-in-python/

Since I can't post comments yet, I need to add to Gabi Nagy's answer, that while their answer describes a correct algorithm, it is not the same as using openssl_seal() function.
OpenSSL doesn't let unencrypted key to get outside of OpenSSL structures. It generates key somewhere inside and keeps it there, giving you only encrypted key. The crucial difference is that when OpenSSL cleans it's structures, it should dispose of unencrypted key in a safe way.

Related

ValueError: Ciphertext with incorrect length (not 256 bytes) using Python

I want to decode RSA key in python, see below my code :
def decode():
encoded_password = request.args['password']
rsa_key = RSA.importKey(open('Python/auth/private.txt', "rb").read())
cipher = PKCS1_v1_5.new(rsa_key)
raw_cipher_data = b64decode(encoded_password)
# Decrypt the data
decrypted_password = cipher.decrypt(raw_cipher_data, "ERROR")
return jsonify({'status': 'success', 'message': decrypted_password.decode('utf-8')})
My RSA Private key file looks like this :
-----BEGIN RSA PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3r6bHdnLyOlnA
3SL9Q1emrIfq/GUz1L9nlUEraWAF/ZSjwAjzxISNW2sP7Utoz6rUCxGPhwUAYaqx
D5J4iNZ4Uh4v7q8/XIyzfj0kT2/+sEznFLLTMbfIMV/gn8dbzAWDfMjA4itv3FCp
...
sEcPKDP+Cfu+9qjCGZ8+cTa7ilT9r/yvUus5bwW+DpfeMAVMgLahPB0sHPR2jkAK
v/dTPZz+XPN8k4kbO/wQlY1NCavHjwOIiwd/Z1d0Fv+K9Pmrwi2xO3icegEj93dG
F7c3bSZfWisoMQm+K2VCMZY=
-----END RSA PRIVATE KEY-----
I managed to import the private RSA key, but I get an error when I try to decode.
ValueError: Ciphertext with incorrect length (not 256 bytes)
I can't find answers to my problem.
Maybe your encryption part is not coherent with the decryption one :
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_v1_5
from base64 import b64encode
from base64 import b64decode
def decode():
rsa_key = RSA.importKey(open('Python/auth/private.txt', "rb").read())
cipher = PKCS1_v1_5.new(rsa_key)
global encoded_password
# Decrypt the data
decrypted_password = cipher.decrypt(encoded_password, "ERROR")
password = b64decode(decrypted_password)
return jsonify({'status': 'success', 'message': decrypted_password.decode('utf-8')})
def encode():
password = b"stackoverflow"
rsa_key=RSA.generate(2048)
with open('Python/auth/private.txt', "wb") as file:
file.write(rsa_key.export_key('PEM'))
cipher = PKCS1_v1_5.new(rsa_key)
raw_cipher_data = b64encode(password)
print(raw_cipher_data)
# Decrypt the data
global encoded_password
encoded_password = cipher.encrypt(raw_cipher_data)
return jsonify({'status': 'success', 'message': encoded_password.decode('utf-8')})
encode()
decode()
The RSA key looks like :
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAmnDiQmYr3oHiVaEtRcD2wARtFS2FeHCX6CMreiyXy7bIAtMY
KvO2EMBv4jjqpZQ3IT8qFKYxSANgC/t6/PX8ZshjERhngTs/C39logjgf7NOabWh
pfjKVJ+XMGBCR943YpTR2YBCqyztbZUGTO1EL4dFrEYYEPBIlvy2AXXOYwWj78Bv
VrgQlhqZk7WeEmZ1t6ezGvsw18R72ITU1wzSekLiMCJhNttELIHbuWQZw48wVlXd
5B/803zHKPivtKsVaqrfBwv+UONozlXhpBlz6FYjA/tnC2xuk5WpSBc2jQfyeNho
pRtA1WK2YMp2IMbp1nVeajlPENCEE3w7joIyGwIDAQABAoIBABWVgNbvyYzDe+V6
F5t/4DGuIgKg2zOduhUxfnIziOlpu6KYFvDAdmmj7gm16PvyxBEA72QgZT3KaaId
vNldQC+rLMaRsbwqvWEMJnY9UFwjK+pz0x92LuzMZbMB7kbLWdPiDEFIqXNGiE4q
0s0YfHw5/F+bEjeieo/4ydpYDlvh5nG0y9Sdl6FxufAhx/JXuNNq+qO5qEEMQwYU
hnf31Sw7twEJGvOaKsCAcPG9F64JGo/QUx/VJIA97EoHvWl5USRxqUiOkyWpeeHs
iSYBOA4zAF68uEakax7jD2xc/GxCwBJGjrI9K3A6+NI9wfu6uJxIL8unfR+KwCSn
0k6446UCgYEAvZnOj7eY5t3q94TxxH3HHc61jBDLn7Mq/N0YdxocJp84zXLFtH6x
bgBVoqqllF3rp77f2HLnVHzacW+Tvv8GVxhZ1o48v8WK/5Ej3Sgh+6dsa99e24SQ
z7sGpYinATwqhWnwzd/G0ZHDTNX3vScOq/rNfnyRi6qu9+TIEbqCqC0CgYEA0Ibo
IJDxoBP2387jYB/Z8gqv6xq0kqyrdOa0yv2c7JFS8yCXXZ1cTy/x/YJrZR2eCeQs
YQfN95u12j3lUlCVWVb3ZmM6CAyEpUYqPZ4sCtJdKChLM0+I0yfIbfXiEIIJ0yjM
Al56IAYNEYydSPE9jLW/P4lHRrssQbKr3Py/qGcCgYB0XpKJYwZVrJ8qjE5Xa1tq
0BRdg3F282DPEmSRtVTR36fdcTQnNBtyiIIG9PXrujmJG34IO34APSFVvkXQVHZv
vmJlbaebjINjmJGKi7dP3dKN4us1kIfQ99l8gAMAnwz6FavWsCI3Pl/AKROE5RP8
OlMl7w7lyjzZqXGib/cBgQKBgEw8hjEhzLTRl7hLUyWZf3zWG2rA4LOfHTAoCIEO
J4j1uHXavHwlQ9JPnREp1UmqglTrbq4qxEp6Swn3BxgJDhETkm+EZ3r52KTz+g18
/m0Wa6h60sN3mHZaXRSWiIewgxcIG03ibJO4op5/4iEA0ZfX+ouoDL73Pz7lq5+n
aAqRAoGAFbytz4N8vCftiznwzTED5l2qqO3PASU1YctcZWVh7zDK8bQ6sXI9Abmz
8Q8kXPNFFLs+LeMaiId8KK6KtCMv8nu1GvjC1OuzMTwbASTDyy6FFdxOTB+JfEX9
XCftkh3k7F7OtIKarGVnSXbwH+NIPbV25DBRvW2tqCyztH8j4Bc=
-----END RSA PRIVATE KEY-----
So it is bigger than yours.
NB : I strongly recommend you to use PKCS OAEP #1 due to security reasons (https://pycryptodome.readthedocs.io/en/latest/src/cipher/pkcs1_v1_5.html).
As the error message says, the ciphertext you are trying to decrypt is of the incorrect length. Looking at the section for RSA decryption in RFC 3447, the ciphertext must be "an octet string of length k, where k is the length in octets of the RSA modulus n". In your case, it must be 256 bytes.
As #JoelCrypto says, this might be due to a discrepancy between your encryption and decryption code. Or it could be an extra byte or two that you added to the ciphertext by mistake. (Maybe an extra newline crept in somewhere?) Also, I totally agree with Joel about using PKCS#1 OAEP instead of PKCS#1 v1.5, as the latter is vulnerable to timing attacks.

Decrypt a file encrypted in Python using only OpenSSL

I have a file encrypted in Python and pycryptodome like this:
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP
key = RSA.generate(2048)
secret_key = key.exportKey(passphrase='letmein', pkcs=8, protection='scryptAndAES128-CBC')
public_key = key.publickey().exportKey()
rsa_key = RSA.importKey(public_key)
session_key = get_random_bytes(16)
cipher_rsa = PKCS1_OAEP.new(rsa_key)
cipher_aes = AES.new(session_key, AES.MODE_EAX)
ciphertext, tag = cipher_aes.encrypt_and_digest(data)
dst.write(cipher_rsa.encrypt(session_key))
dst.write(cipher_aes.nonce)
dst.write(tag)
dst.write(ciphertext)
And I am able to decode it like this:
rsa_key = RSA.importKey(secret_key, 'letmein')
enc_session_key, nonce, tag, ciphertext = [
src.read(x) for x in (rsa_key.size_in_bytes(), 16, 16, -1)
]
cipher_rsa = PKCS1_OAEP.new(rsa_key)
session_key = cipher_rsa.decrypt(enc_session_key)
cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
decoded = cipher_aes.decrypt_and_verify(ciphertext, tag)
Is there a way to decrypt the file using command line with openssl? Or how should I modify the code so that it would be possible?
You could base 64 encode the separate components and then splitting them using a separator. Command line is mainly text based, so it would be easier to program that in e.g. Bash.
EAX mode is not directly supported so trying CBC mode would make it easier. OpenSSL command line doesn't seem to support any AEAD cipher for now so you would lose the authenticity it may have offered.
Finally, the combination of OAEP and a cipher doesn't seem supported, so you may have to handle the binary result and convert it to a symmetric cipher, e.g. in hexadecimals.

How to decrypt RSA encrypted file (via PHP and OpenSSL) with pyopenssl?

Simple n00b question: I am trying to replicate the behavior of the openssl_private_decrypt function in PHP to decrypt a file that a vendor is sending me which was encrypted via the openssl_public_encrypt function. I am using python 3.4 and thus the only library I can see available is pyopenssl, but it's sufficiently low-level that I cannot easily find out how to do what I want to do. It's probably very simple, but does anyone have an exemple of what I want to do?
With the Cryptography module, which you can install with:
$ pip install cryptography
Assuming you have the private key stored in a file called "path/to/key.pem", first you load the private key:
from cryptography.hazmat.primitives import serialization
with open("path/to/key.pem", "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
backend=default_backend()
)
And then you decrypt with:
plaintext = private_key.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
Thanks to #mnistic it got to work, with a couple of modifications though. Here is the final working code (you have to keep in mind the defaults of openssl_private_decrypt):
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
# It's critical that the file be opened in mode "rb"!
with open("private.key", 'rb') as key_file:
private_key = serialization.load_pem_private_key(key_file.read(), password=None, backend=default_backend())
with open('encrypted_file', 'rb') as encrypted_file:
ciphertext = encrypted_file.read()
plaintext = private_key.decrypt(ciphertext, padding.PKCS1v15())
Please note that ciphertext needs to be shorter than the maximum chunk size of the key (which is the number of bits in the key divided by 8 for RSA). Hope that helps future Googlers!

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.

AES: how to detect that a bad password has been entered?

A text s has been encrypted with:
s2 = iv + Crypto.Cipher.AES.new(Crypto.Hash.SHA256.new(pwd).digest(),
Crypto.Cipher.AES.MODE_CFB,
iv).encrypt(s.encode())
Then, later, a user inputs the password pwd2 and we decrypt it with:
iv, cipher = s2[:Crypto.Cipher.AES.block_size], s2[Crypto.Cipher.AES.block_size:]
s3 = Crypto.Cipher.AES.new(Crypto.Hash.SHA256.new(pwd2).digest(),
Crypto.Cipher.AES.MODE_CFB,
iv).decrypt(cipher)
Problem: the last line works even if the entered password pw2 is wrong. Of course the decrypted text will be random chars, but no error is triggered.
Question: how to make Crypto.Cipher.AES.new(...).decrypt(cipher) fail if the password pw2 is incorrect? Or at least how to detect a wrong password?
Here is a linked question: Making AES decryption fail if invalid password
and here a discussion about the cryptographic part (less programming) of the question: AES, is this method to say “The password you entered is wrong” secure?
.
AES provides confidentiality but not integrity out of the box - to get integrity too, you have a few options. The easiest and arguably least prone to "shooting yourself in the foot" is to just use AES-GCM - see this Python example or this one.
You could also use an HMAC, but this generally requires managing two distinct keys and has a few more moving parts. I would recommend the first option if it is available to you.
A side note, SHA-256 isn't a very good KDF to use when converting a user created password to an encryption key. Popular password hashing algorithms are better at this - have a look at Argon2, bcrypt or PBKDF2.
Edit: The reason SHA-256 is a bad KDF is the same reason it makes a bad password hash function - it's just too fast. A user created password of, say, 128 bits will usually contain far less entropy than a random sequence of 128 bits - people like to pick words, meaningful sequences etc. Hashing this once with SHA-256 doesn't really alleviate this issue. But hashing it with a construct like Argon2 that is designed to be slow makes a brute-force attack far less viable.
The best way is to use authenticated encryption, and a modern memory-hard entropy-stretching key derivation function such a scrypt to turn the password into a key. The cipher's nounce can be used as salt for the key derivation. With PyCryptodome that could be:
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import scrypt
# initialize an AES-128-GCM cipher from password (derived using scrypt) and nonce
def cipherAES(pwd, nonce):
# note: the p parameter should allow use of several processors, but did not for me
# note: changing 16 to 24 or 32 should select AES-192 or AES-256 (not tested)
return AES.new(scrypt(pwd, nonce, 16, N=2**21, r=8, p=1), AES.MODE_GCM, nonce=nonce)
# encryption
nonce = get_random_bytes(16)
print("deriving key from password and nonce, then encrypting..")
ciphertext, tag = cipherAES(b'pwdHklot2',nonce).encrypt_and_digest(b'bonjour')
print("done")
# decryption of nonce, ciphertext, tag
print("deriving key from password and nonce, then decrypting..")
try:
plaintext = cipherAES(b'pwdHklot2', nonce).decrypt_and_verify(ciphertext, tag)
print("The message was: " + plaintext.decode())
except ValueError:
print("Wrong password or altered nonce, ciphertext, tag")
print("done")
Note: Code is here to illustrate the principle. In particular, the scrypt parameters should not be fixed, but rather be included in a header before nonce, ciphertext, and tag; and that must be somewhat grouped for sending, and parsed for decryption.
Caveat: nothing in this post should be construed as an endorsement of PyCryptodome's security.
Addition (per request):
We need scrypt or some other form of entropy stretching only because we use a password. We could use a random 128-bit key directly.
PBKDF2-HMAC-SHAn with 100000 iterations (as in the OP's second code fragment there) is only barely passable to resist Hashcat with a few GPUs. It would would be almost negligible compared to other hurdles for an ASIC-assisted attack: a state of the art Bitcoin mining ASIC does more than 2*1010 SHA-256 per Joule, 1 kWh of electricity costing less than $0.15 is 36*105 J. Crunching these numbers, testing the (62(8+1)-1)/(62-1) = 221919451578091 passwords of up to 8 characters restricted to letters and digits cost less than $47 for energy dedicated to the hashing part.
scrypt is much more secure for equal time spent by legitimate users because it requires a lot of memory and accesses thereof, slowing down the attacker, and most importantly making the investment cost for massively parallel attack skyrocket.
Doesn't use the Crypto package, but this should suit your needs:
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
def derive_password(password: bytes, salt: bytes):
"""
Adjust the N parameter depending on how long you want the derivation to take.
The scrypt paper suggests a minimum value of n=2**14 for interactive logins (t < 100ms),
or n=2**20 for more sensitive files (t < 5s).
"""
kdf = Scrypt(salt=salt, length=32, n=2**16, r=8, p=1, backend=default_backend())
key = kdf.derive(password)
return base64.urlsafe_b64encode(key)
salt = os.urandom(16)
password = b'legorooj'
bad_password = b'legorooj2'
# Derive the password
key = derive_password(password, salt)
key2 = derive_password(bad_password, salt) # Shouldn't re-use salt but this is only for example purposes
# Create the Fernet Object
f = Fernet(key)
msg = b'This is a test message'
ciphertext = f.encrypt(msg)
print(msg, flush=True) # Flushing pushes it strait to stdout, so the error that will come
print(ciphertext, flush=True)
# Fernet can only be used once, so we need to reinitialize
f = Fernet(key)
plaintext = f.decrypt(ciphertext)
print(plaintext, flush=True)
# Bad Key
f = Fernet(key2)
f.decrypt(ciphertext)
"""
This will raise InvalidToken and InvalidSignature, which means it wasn't decrypted properly.
"""
See my comment for links to the documentation.
For future reference, here is a working solution following the AES GCM mode (recommended by #LukeJoshuaPark in his answer):
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
# Encryption
data = b"secret"
key = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(data)
nonce = cipher.nonce
# Decryption
key2 = get_random_bytes(16) # wrong key
#key2 = key # correct key
try:
cipher = AES.new(key2, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
print("The message was: " + plaintext.decode())
except ValueError:
print("Wrong key")
It does fail with an exception when the password is wrong indeed, as desired.
The following code uses a real password derivation function:
import Crypto.Random, Crypto.Protocol.KDF, Crypto.Cipher.AES
def cipherAES(pwd, nonce):
return Crypto.Cipher.AES.new(Crypto.Protocol.KDF.PBKDF2(pwd, nonce, count=100000), Crypto.Cipher.AES.MODE_GCM, nonce=nonce)
# encryption
nonce = Crypto.Random.new().read(16)
cipher = cipherAES(b'pwd1', nonce)
ciphertext, tag = cipher.encrypt_and_digest(b'bonjour')
# decryption
try:
cipher = cipherAES(b'pwd1', nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
print("The message was: " + plaintext.decode())
except ValueError:
print("Wrong password")
#fgrieu's answer is probably better because it uses scrypt as KDF.

Categories

Resources