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.
Related
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!
Based on the Golang documentation on CFB decryption I wrote a minimal working example to decrypt a string that was encrypted with AES CFB and then base 64 encoded in python3.
The golang decryption works fine when the message was encrypted within Golang (with the encryption function from the Golang doc example).
However when I encrypt the message in a python script using the python crypto package, I am unable to decrypt it in the golang script successfully. I don't get the right bytes back.
$ python3 stack.py
Going to encrypt and base64 "This is not encrypted" result:
b'jf9A5LCxKWPuNb1XiH+G3APAgR//'
Now going to call the Golang script:
b'Hello from Golang, going to decrypt: jf9A5LCxKWPuNb1XiH+G3APAgR//
Result: Tl!\xca/\xf1\xc0\xb2\xd01Y\x02V\xec\xdf\xecy\xd38&\xd9\n'
Blocksize is 16 by default for both AES implementations.
So the question: What is going wrong?
Golang script:
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"os"
)
func main() {
key := []byte("TfvY7I358yospfWKcoviZizOShpm5hyH")
iv := []byte("mb13KcoviZizvYhp")
payload_python := os.Args[1]
fmt.Println("Hello from Golang, going to decrypt: "+payload_python+" Result: "+string(decrypt(key, payload_python, iv)))
}
func decrypt(key []byte, cryptoText string, iv []byte) []byte {
ciphertext, _ := base64.StdEncoding.DecodeString(cryptoText) //decode base64 coding
//prepare decryption based on key and iv
block, _ := aes.NewCipher(key)
stream := cipher.NewCFBDecrypter(block, iv)
//decrypt
stream.XORKeyStream(ciphertext, ciphertext)
return ciphertext
}
Python script:
#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
from subprocess import check_output
original_message = 'This is not encrypted'
key = 'TfvY7I358yospfWKcoviZizOShpm5hyH'
iv = 'mb13KcoviZizvYhp'
#prepare encryption
cfb_cipher_encrypt = AES.new(key, AES.MODE_CFB, iv)
#encrypt and base64 encode
encryptedpayload = base64.b64encode(cfb_cipher_encrypt.encrypt(original_message))
print('Going to encrypt and base64 "{}" result:\n{}\n'.format(original_message,encryptedpayload))
print('Now going to call the Golang script:')
print(check_output('go run stack.go {}'.format(encryptedpayload.decode()),shell=True))
Try encrypting from Python like this.
The result can then be unencrypted from Go successfully.
#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
MODE = AES.MODE_CFB
BLOCK_SIZE = 16
SEGMENT_SIZE = 128
def _pad_string(value):
length = len(value)
pad_size = BLOCK_SIZE - (length % BLOCK_SIZE)
return value.ljust(length + pad_size, '\x00')
def encrypt(key, iv, plaintext):
aes = AES.new(key, MODE, iv, segment_size=SEGMENT_SIZE)
plaintext = _pad_string(plaintext)
encrypted_text = aes.encrypt(plaintext)
return encrypted_text
key = 'TfvY7I358yospfWKcoviZizOShpm5hyH'
iv = 'mb13KcoviZizvYhp'
original_message = 'This is not encrypted'
encryptedpayload = base64.b64encode(encrypt(key, iv, original_message))
print('Going to encrypt and base64 "{}" result:\n{}\n'.format(original_message,encryptedpayload))
Source: http://chase-seibert.github.io/blog/2016/01/29/cryptojs-pycrypto-ios-aes256.html
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.
I am implementing a Django website in which uploaded files are encrypted with a user provided key before they are saved on the server (/media). When users wish to view them, they are prompted for the key, the encrypted file is decrypted, and then displayed for them. Here's my encrypt/decrypt file code:
from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
def encryption_pad(string):
pad = b"\0" * (AES.block_size - len(string) % AES.block_size)
padded_string = string + pad
return padded_string
def encrypt_file(key, file):
with open(file, 'rb') as out:
byte_output = out.read()
hash = SHA256.new()
hash.update(key)
byte_output = encryption_pad(byte_output)
initialization_vector = Random.new().read(AES.block_size)
cipher = AES.new(hash.digest(), AES.MODE_CBC, initialization_vector)
encrypted_output = initialization_vector + cipher.encrypt(byte_output)
with open(file + ".enc", 'wb') as out:
out.write(encrypted_output)
def decrypt_file(file, key):
with open(file, 'rb') as input:
ciphertext = input.read()
hash = SHA256.new()
hash.update(key)
initialization_vector = ciphertext[:AES.block_size]
cipher = AES.new(hash.digest(), AES.MODE_CBC, initialization_vector)
decrypted_output = cipher.decrypt(ciphertext[AES.block_size:])
decrypted_output = decrypted_output.rstrip(b"\0")
with open(file[:-4], 'wb') as output:
output.write(decrypted_output)
I am relatively new to security, so my question is: For this setup the keys must exist in the server's memory for some duration of time, so what is the proper way for my views.py function to pass them to this module and then properly dispose of them after?
There are some existing questions on how to securely handle (or not) in-memory objects in Python: see here and here.
If security is that important, though, you might want to consider an even more secure option: doing the encryption and decryption on the client, in Javascript. That way the key never gets sent over the wire, and never exists on the server. That's how LastPass works, for example.
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.