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!
Related
I have made a signature signing app in python. I have little experience with signatures in general, but when I run it, I would like to convert the data to text, (or maybe it is supposed to be bytes? as I said, little experience), However, I have tried to (and looked up about how to convert bytes to string/text) and it either throws an error or doesn't convert the data at all.
Code:
# Generate a private key
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# Sign a message using the key
def sign_string(input_string):
message = input_string
signature = private_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return signature
I see you answered your own question. The alternative is to Base64 encode it with base64.b64encode(). The advantage is you’ll get a shorter string than if you represent it as hex.
I am using the following code to create hash for a message:
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_PSS
msg = b'\x15\x00\xea'
hash = SHA256.new(msg)
...
signer = PKCS1_PSS.new(privKey)
signature = signer.sign(hash)
However, I need that the hash digest value i.e hash.digest() to be in big-endian format.
Please note that I need to pass the whole hash to the signer. I can get the hash.digest() and convert it to big-endian, but then I need to construct a variable of type hash to pass to the signer.sig() function.
How can I do that?
As mentioned in my comment, I'm not sure if it is really required to invert the hash.
But if so, you need an implementation that does not process the raw data, but the already hashed data (and does not hash anymore).
PyCryptodome does not support this, but the Cryptography library with the Prehashed class does, s. here, 2nd snippet.
The following code performs a signing with PKCS#1 v1.5 and:
with PyCryptodome (implicit hashing)
with Cryptography (implicit hashing)
with Cryptography (using the Prehashed class and explicit hashing)
with Cryptography (using the Prehashed class and explicit hashing) and using the inverted hash
# Short key only for testing purposes!
pkcs8 = '''-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----'''
message = b'The quick brown fox jumps over the lazy dog'
# 1. PyCryptodome (implicit hashing)
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
privateKey = RSA.import_key(pkcs8)
digest = SHA256.new(message)
signature = pkcs1_15.new(privateKey).sign(digest)
print(signature.hex())
# 2. Cryptography (implicit hashing)
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
privateKey = serialization.load_pem_private_key(
pkcs8.encode('utf8'),
password=None,
)
digest = hashes.SHA256()
signature = privateKey.sign(
message,
padding.PKCS1v15(),
digest
)
print(signature.hex())
# 3. Cryptography (explicit hashing)
from cryptography.hazmat.primitives.asymmetric import utils
digest = hashes.SHA256()
hasher = hashes.Hash(digest)
hasher.update(message)
hash = hasher.finalize()
signature = privateKey.sign(
hash,
padding.PKCS1v15(),
utils.Prehashed(digest) # The digest must still be specified because the digest ID is included in the signature.
)
print(signature.hex())
# 4. Cryptography (explicit hashing), inverse hash
hashBA = bytearray(hash)
hashBA.reverse()
hashReversed = bytes(hashBA)
signature = privateKey.sign(
hashReversed,
padding.PKCS1v15(),
utils.Prehashed(digest) # The digest must still be specified because the digest ID is included in the signature.
)
print(signature.hex())
with the output:
8c83cad897eda249fec9eba231061d585dafc99177267e3e71bb8a3fce07cc6663bf4df7af2e1c1945d2a6bb42eb25f042228b591fc18cda82d92caae844670c
8c83cad897eda249fec9eba231061d585dafc99177267e3e71bb8a3fce07cc6663bf4df7af2e1c1945d2a6bb42eb25f042228b591fc18cda82d92caae844670c
8c83cad897eda249fec9eba231061d585dafc99177267e3e71bb8a3fce07cc6663bf4df7af2e1c1945d2a6bb42eb25f042228b591fc18cda82d92caae844670c
550ba1cd3c968fa1e24b79a939edb6740b63d2ab021fe87f0639ce978d5127792661e83f4e8fdff8124a12fe208bd70bdca9db2b9c82306f2ed018ab06363c9e
Since the PKCS#1 v1.5 signature is deterministic, cases 1 to 3 produce the same signature.
The SHA-256 standard uses 32 bit words, and those are already specified to be big endian.
If you get 32 bit little endian words then you may need to reorder the bytes within them, but this is unlikely.
i am relatively new to pyhton and the cryptography module, so i'm trying to learn the basics of encrypting and decrypting.
It all works fine when i encrypt a file and decrypt it on the same program, but if i try to just run a decrypt code on a pre-encrypted file (i used the same key, of course) i get an InvalidSignature error, followed by an InvalidToken.
Now, i assumed that for some reason the key didn't match, but they are indeed the same.
Then i thought that for some reason i was passing a string instead of a byte to the functions, or that there are some sort of conversion errors that might alter the encrypted message. But the encrypt-decrypt code works, so i can't figure why the decrypt-only should face errors.
At last, i had a look at the source code for the decrypt function, and tried to figure out if the time stamp had something to do with the error i get, but i couldn't get anything relevant since i'm not too experienced.
This is the encrypt-decrypt code: given a password by the user it encrypts and prints a file, that can decrypt right away.
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
print("Insert password: ")
password_str = input()
password = password_str.encode()
salt = os.urandom(16)
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt,
iterations=100000, backend=default_backend())
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
message = "A really secret message. Not for prying eyes.".encode()
token = f.encrypt(message)
file = open("text_encrypted.txt", "wb")
file.write(token)
file.close()
file = open("text_encrypted.txt", "rb")
data = file.read()
file.close()
token = f.decrypt(data)
file = open("text_decrypted.txt", "wb")
file.write(token)
file.close()
Now, that works just fine and i get the two files that contain the encrypted and decrypted message.
If i delete the:
message = "A really secret message. Not for prying eyes.".encode()
token = f.encrypt(message)
file = open("text_encrypted.txt", "wb")
file.write(token)
file.close()
part, i should be left with just a decryption code, that should work on a previously generated encrypted file, that should be decrypted by the same password.
I'm obviously missing something maybe trivial, since it raises both invalid signature and invalid token.
Thanks for your help
The encryption key you’re using is a result of a PBKDF2. In order for PBKDF2 to return the same encryption key it must get the exact same parameters. That includes salt, which in your example is generated randomly every time.
You need to store the generated salt together with the encrypted file in order to be able to decrypt it later.
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.
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.