Currently I have some code that signs a byte string with the SHA256 algorithm using the native OpenSSL binary, the code calls an external process, sends the parameters, and receive the result back into the Python code.
The current code is as follows:
signed_digest_proc = subprocess.Popen(
['openssl', 'dgst', '-sha256', '-sign', tmp_path],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
signed_digest_proc.stdin.write(original_string)
signed_digest, _ = signed_digest_proc.communicate()
base64.encodestring(signed_digest).decode().replace('\n', '')
When original_string is too big, I might have problems with the result (from the communication with an external process I think), that's why I'm trying to change it to a Python only solution:
import hmac, hashlib
h = hmac.new(bytes(key_pem(), 'ASCII'), original_string, hashlib.sha256)
result = base64.encodestring(h).decode().replace('\n', '')
This result in a completely different string than the first one.
What would be the way to implement the original code without calling an external process?
The openssl command you used does three things:
Create a hash of the data, using SHA256
If RSA is used, pad out the message to a specific length, using PKCS#1 1.5
Sign the (padded) hash, using the private key you provided. It'll depend on the type of key what algorithm was used.
The hmac module does not serve the same function.
You'll need to install a cryptography package like cryptography to replicate what openssl dgst -sign does. cryptography uses OpenSSL as a backend, so it will produce the same output.
You can then
load the key with the load_pem_private_key() function. This returns the right type of object for the algorithm used.
use the key to sign the message; each key type has a sign() method, and this method will take care of hashing the message for you if you so wish. See for example the Signing section for RSA.
However, you'll need to provide different kinds of config for the different .sign() methods. Only the RSA, DSA and Elliptic Curve keys can be used to create a signed digest.
You'll have to switch between the types to get the signature right:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa, utils
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
# configuration per key type, each lambda takes a hashing algorithm
_signing_configs = (
(dsa.DSAPrivateKey, lambda h: {
'algorithm': h}),
(ec.EllipticCurvePrivateKey, lambda h: {
'signature_algorithm': ec.ECDSA(h)}),
(rsa.RSAPrivateKey, lambda h: {
'padding': padding.PKCS1v15(),
'algorithm': h
}),
)
def _key_singing_config(key, hashing_algorithm):
try:
factory = next(
config
for type_, config in _signing_configs
if isinstance(key, type_)
)
except StopIteration:
raise ValueError('Unsupported key type {!r}'.format(type(key)))
return factory(hashing_algorithm)
def sign(private_key, data, algorithm=hashes.SHA256()):
with open(private_key, 'rb') as private_key:
key = serialization.load_pem_private_key(
private_key.read(), None, default_backend())
return key.sign(data, **_key_singing_config(key, algorithm))
If you need to hash a large amount of data, you can hash the data yourself first, in chunks, before passing in just the digest and the special util.Prehashed() object:
def sign_streaming(private_key, data_iterable, algorithm=hashes.SHA256()):
with open(private_key, 'rb') as private_key:
key = serialization.load_pem_private_key(
private_key.read(), None, default_backend())
hasher = hashes.Hash(algorithm, default_backend())
for chunk in data_iterable:
hasher.update(chunk)
digest = hasher.finalize()
prehashed = utils.Prehashed(algorithm)
return key.sign(digest, **_key_singing_config(key, prehashed))
with open(large_file, 'rb') as large_file:
signature = sign_streaming(private_key_file, iter(lambda: large_file.read(2 ** 16), b''))
This uses the iter() function to read data from a binary file in chunks of 64 kilobytes.
Demo; I'm using an RSA key I generated in /tmp/test_rsa.pem. Using the command-line to produce a signed digest for Hello world!:
$ echo -n 'Hello world!' | openssl dgst -sign /tmp/test_rsa.pem -sha256 | openssl base64
R1bRhzEr+ODNThyYiHbiUackZpx+TCviYR6qPlmiRGd28wpQJZGnOFg9tta0IwkT
HetvITcdggXeiqUqepzzT9rDkIw6CU7mlnDRcRu2g76TA4Uyq+0UzW8Ati8nYCSx
Wyu09YWaKazOQgIQW3no1e1Z4HKdN2LtZfRTvATk7JB9/nReKlXgRjVdwRdE3zl5
x3XSPlaMwnSsCVEhZ8N7Gf1xJf3huV21RKaXZw5zMypHGBIXG5ngyfX0+aznYEve
x1uBrtZQwUGuS7/RuHw67WDIN36aXAK1sRP5Q5CzgeMicD8d9wr8St1w7WtYLXzY
HwzvHWcVy7kPtfIzR4R0vQ==
or using the Python code:
>>> signature = sign(keyfile, b'Hello world!')
>>> import base64
>>> print(base64.encodebytes(signature).decode())
R1bRhzEr+ODNThyYiHbiUackZpx+TCviYR6qPlmiRGd28wpQJZGnOFg9tta0IwkTHetvITcdggXe
iqUqepzzT9rDkIw6CU7mlnDRcRu2g76TA4Uyq+0UzW8Ati8nYCSxWyu09YWaKazOQgIQW3no1e1Z
4HKdN2LtZfRTvATk7JB9/nReKlXgRjVdwRdE3zl5x3XSPlaMwnSsCVEhZ8N7Gf1xJf3huV21RKaX
Zw5zMypHGBIXG5ngyfX0+aznYEvex1uBrtZQwUGuS7/RuHw67WDIN36aXAK1sRP5Q5CzgeMicD8d
9wr8St1w7WtYLXzYHwzvHWcVy7kPtfIzR4R0vQ==
Although the line lengths differ, the base64 data the two output is clearly the same.
Or, using a generated file with random binary data, size 32kb:
$ dd if=/dev/urandom of=/tmp/random_data.bin bs=16k count=2
2+0 records in
2+0 records out
32768 bytes transferred in 0.002227 secs (14713516 bytes/sec)
$ cat /tmp/random_data.bin | openssl dgst -sign /tmp/test_rsa.pem -sha256 | openssl base64
b9sYFdRzpBtJTan7Pnfod0QRon+YfdaQlyhW0aWabia28oTFYKKiC2ksiJq+IhrF
tIMb0Ti60TtBhbdmR3eF5tfRqOfBNHGAzZxSaRMau6BuPf5AWqCIyh8GvqNKpweF
yyzWNaTBYATTt0RF0fkVioE6Q2LdfrOP1q+6zzRvLv4BHC0oW4qg6F6CMPSQqpBy
dU/3P8drJ8XCWiJV/oLhVehPtFeihatMzcZB3IIIDFP6rN0lY1KpFfdBPlXqZlJw
PJQondRBygk3fh+Sd/pGYzjltv7/4mC6CXTKlDQnYUWV+Rqpn6+ojTElGJZXCnn7
Sn0Oh3FidCxIeO/VIhgiuQ==
Processing the same file in Python:
>>> with open('/tmp/random_data.bin', 'rb') as random_data:
... signature = sign_streaming('/tmp/test_rsa.pem', iter(lambda: random_data.read(2 ** 16), b''))
...
>>> print(base64.encodebytes(signature).decode())
b9sYFdRzpBtJTan7Pnfod0QRon+YfdaQlyhW0aWabia28oTFYKKiC2ksiJq+IhrFtIMb0Ti60TtB
hbdmR3eF5tfRqOfBNHGAzZxSaRMau6BuPf5AWqCIyh8GvqNKpweFyyzWNaTBYATTt0RF0fkVioE6
Q2LdfrOP1q+6zzRvLv4BHC0oW4qg6F6CMPSQqpBydU/3P8drJ8XCWiJV/oLhVehPtFeihatMzcZB
3IIIDFP6rN0lY1KpFfdBPlXqZlJwPJQondRBygk3fh+Sd/pGYzjltv7/4mC6CXTKlDQnYUWV+Rqp
n6+ojTElGJZXCnn7Sn0Oh3FidCxIeO/VIhgiuQ==
Related
I'm encrypting a key with a public key with the cryptography library, in Python.
key_path = "key.bin"
key = secrets.token_bytes(32)
with open(key_path, "w") as key_file:
key_file.write(key.hex())
with open(public_key, "rb") as public_key_file:
public_key = serialization.load_pem_public_key(public_key_file.read())
padding_config = padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
enc_path = key_path + ".enc"
with open(enc_path, "wb") as enc_file:
bytes_array = public_key.encrypt(content, padding_config)
enc_file.write(bytes_array)
This works well, afaik, but the code reading this key is in Rust, which is simply a FFI to openssl C calls. There's not many option with openssl. You can't choose an "algorithm", a "mgf" and a "label. Padding is simply an enum, so I picked the obvious one PKCS1_OAEP.
use openssl::{
cipher::Cipher,
cipher_ctx::CipherCtx,
pkey::Private,
rsa::{Padding, Rsa},
};
pub fn decrypt(key_file: File, pk: &str, pass: &str) -> String {
let rsa = Rsa::private_key_from_pem_passphrase(pk.as_bytes(), pass.as_bytes())
.expect("Can't build RSA object from PEM");
let mut encrypted = vec![];
key_file.read_to_end(&mut encrypted).expect("Can't read encrypted key file");
let mut decrypted: Vec<u8> = vec![0; rsa.size() as usize];
rsa.private_decrypt(&encrypted, &mut decrypted, Padding::PKCS1_OAEP).unwrap();
String::from_utf8(decrypted)
}
But I get this error:
ErrorStack([
Error { code: 67571871, library: "rsa routines", function: "RSA_padding_check_PKCS1_type_2", reason: "pkcs decoding error", file: "../crypto/rsa/rsa_pk1.c", line: 251 },
Error { code: 67522674, library: "rsa routines", function: "rsa_ossl_private_decrypt", reason: "padding check failed", file: "../crypto/rsa/rsa_ossl.c", line: 500 }
])
I know that the Rust code is "right" because it was working well (with Padding::PKCS1) in a previous version when I was using subprocess calls to openssl instead of the cryptography library. And anyway there's only the Padding enum to change here.
openssl documentation tells me that
RSA_PKCS1_OAEP_PADDING
EME-OAEP as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode is recommended for all new applications.
but using hashes.SHA1() didn't change anything. How should I setup my padding so that openssl accepts decrypting it?
Presumably, the posted Rust code uses the RFC8017 default values for the OAEP parameters, namely SHA-1 for the content digest and the MGF1 digest, and an empty label.
The posted Python code, on the other hand, applies SHA-256 for both digests, which is why the two codes are incompatible. You have to set both digests to SHA-256 on the Rust side (alternatively you could use SHA-1 on the Python side).
Here you can find a Rust sample implementation for OAEP. The setting of the OAEP padding is done with set_rsa_padding(Padding::PKCS1_OAEP). To avoid using the SHA-1 default value for the content digest, the content digest must be set explicitly with set_rsa_oaep_md(MessageDigest::sha256()). The MGF1 digest is implicitly set with the content digest (since contents and MGF1 digests are identical in the posted example, the MGF1 digest does not need to be explicitly set here).
If a different MGF1 digest is to be used, set_rsa_mgf1_md() must be used. If a label is to be set, set_rsa_oaep_label(). Note, however, that the label is actually always left empty (which should also not be changed for compatibility reasons).
Test:
Using the posted Python code, the base64 encoded 32 bytes key
AAECAwQFBgcICQoLDA0OD/Dx8vP09fb3+Pn6+/z9/v8=
is encrypted as follows (Base64 encoded):
oU1PwQRE0ZEr71OOfoNHLpmjXyTAfdZroeQhX4WLAhXpfTMkEMJ0YptHgFDirJ5fdZ9yRl+y1y6jZSpG7oj5wtJkDa4BeLba++Q1UZcKlne4rfYMEPDrkTCjyHyNskuJuLh3FW+HCp70tRzvgSoBpoIwyxWl3VREYRcJEAdzGwRj0d6JNCO4M3BHX7g59to5urOkTXB7MfcAEz1Ba4AdeNYrZK4XD8GjAqnI95X3Z4F8PoLGMP1eMif0fpNizxZo7hzTfEsfjdlkyfTWLZfxuZ9qZMIpkiQNNEWWMt66FmVgFyi8zngz/Tj5Tk7cNzrWOT5BDaFCxrJF1kHoHobojA==
Note that OAEP is not deterministic, i.e. repeated encryption with the same key and plaintext produces different ciphertexts.
This ciphertext can be decrypted with the following Rust code:
use openssl::encrypt::{Decrypter};
use openssl::rsa::{Rsa, Padding};
use openssl::pkey::PKey;
use openssl::hash::MessageDigest;
use base64::{engine::general_purpose, Engine as _};
fn main() {
// Import private key
let pem_private = "-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAunF5aDa6HCfLMMI/MZLT5hDk304CU+ypFMFiBjowQdUMQKYH
Z+fklB7GpLxCatxYJ/hZ7rjfHH3Klq20/Y1EbYDRopyTSfkrTzPzwsX4Ur/l25Ct
dQldhHCTMgwf/Ev/buBNobfzdZE+Dhdv5lQwKtjI43lDKvAi5kEet2TFwfJcJrBi
RJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1xH9FLojQfyia89/EykiOO7/3UWwd+
MATZ9HLjSx2/Lf3g2jr81eifEmYDlri/OZp4OhZu+0Bo1LXloCTe+vmIQ2YCX7Ea
tUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4iGwIDAQABAoIBAEpSY9BMSKJippgY
RvKvEjNbNrFhuoHUDI7OjBHpbkU/9XNr+/XKtg7pucv/lAZfMfE1Cjyki6Qi7Phl
5IlSoB16xZaqDfjmEAKxthFFs7jg8HM3WD4YbqQV8Ed6F+IONZPwbGH0H2QVcwRR
CXVqyAy8gFBVjZX3RiI9gU4gKMkn8qpmpr5lvG7L3PHKIhpWdvFAvOf16sfcDs43
hqEZAh+z3EzJXPRe0sUrYcF/5NVQV5jTh2vh8xjoDv1t5NSw6iW6tsCo2U1t4lRI
NlaHY8YASzJtDLnkhqCOiLmmUjzMhRDN4BrwtvnAwSNki/2poYk1Gv1Q5U1cPyCm
0rmq/bkCgYEA3e+jND6OS6ofGYUN6G4RapHzuRAV8ux1C9eXMOdZFbcBehn/ydhz
R48LIPTW9HiRE00um27lXfW5/POCaEUvfOp1UxTWeHZ4xICo40PBo383ZKW1MbES
1oiMbjkEqSFGRnTItnLU07bKbzLA7I0UWHWCEAnv0g7HRxk973FAsm8CgYEA1w8+
olZ2POBYeYgw1a0DkeJWKMQi/4pAgyYwustZo0dHlRXQT0OI9XQ0j1PZWoQS28tF
cmoEAg6f5MUDpdM9swS0SOCPI1Lc/f/Slus3u1O3UCezk37pneSPezskDhvV2cCl
JEYH8m/zwDAUlEi4KLIt/H/jgtyDd6pbxxc78RUCgYEAiE6VAxJknM4oeakBiL6J
TdXEReY+RMu7e4F2518/lJmoe5CaTCL3cnzFTgFyQAYIvD0MIgSzNMkl6Ni6QEY1
y1fIpTVIIAZLWAzZLXPA6yTIJbWsmo9xzXdiIJQ+a433NnClkYDne/xpSnB2kxJ2
63mIX0drFq1i8STsqDH7lVsCgYBWpQmzFeqlNC3xKPGj5QTfLbVQz1TaZ7T+IdDY
MT14DyvD4PoANVFksuDKLhoP4c5HR2o/Yn8i5Ql+ffGuSaE+EtMf2XlR3fyzSPJr
Y3Ecw+nDHXu4HRW6S2+TUoyAMq7CAF+Icb6Z6ojmEXj5FSM9Iixs4omjG3zMJZK8
b5vy0QKBgQCz0W7UmxNqT4Lzhyh0xj6+4Cm5rFlWznEAPKxvNNU9/Fv1D3tV7cpz
BI880NvTmlXkML1Dffg8RVyemL8I+ooeFnatdPYuzqNXw5zHMzkDfQynIdnjQB6z
UjjMiwwgZvvsG8hwLEtk9FTvPoMywAb4mZuQBQkFn5HuC2aOZjktdA==
-----END RSA PRIVATE KEY-----";
let private_key = Rsa::private_key_from_pem(pem_private.as_bytes()).unwrap();
let private_key = PKey::from_rsa(private_key).unwrap();
// Import Base64 encoded ciphertext
let data = "oU1PwQRE0ZEr71OOfoNHLpmjXyTAfdZroeQhX4WLAhXpfTMkEMJ0YptHgFDirJ5fdZ9yRl+y1y6jZSpG7oj5wtJkDa4BeLba++Q1UZcKlne4rfYMEPDrkTCjyHyNskuJuLh3FW+HCp70tRzvgSoBpoIwyxWl3VREYRcJEAdzGwRj0d6JNCO4M3BHX7g59to5urOkTXB7MfcAEz1Ba4AdeNYrZK4XD8GjAqnI95X3Z4F8PoLGMP1eMif0fpNizxZo7hzTfEsfjdlkyfTWLZfxuZ9qZMIpkiQNNEWWMt66FmVgFyi8zngz/Tj5Tk7cNzrWOT5BDaFCxrJF1kHoHobojA==";
let encrypted = general_purpose::STANDARD.decode(&data).unwrap();
// Decrypt ciphertext
let mut decrypter = Decrypter::new(&private_key).unwrap();
decrypter.set_rsa_padding(Padding::PKCS1_OAEP).unwrap();
decrypter.set_rsa_oaep_md(MessageDigest::sha256()).unwrap();
//decrypter.set_rsa_mgf1_md(MessageDigest::sha256()).unwrap(); // implicitly set
// Create an output buffer
let buffer_len = decrypter.decrypt_len(&encrypted).unwrap();
let mut decrypted = vec![0; buffer_len];
// Encrypt and truncate buffer
let decrypted_len = decrypter.decrypt(&encrypted, &mut decrypted).unwrap();
decrypted.truncate(decrypted_len);
println!("Decrypted: {}", general_purpose::STANDARD.encode(&decrypted)); // Decrypted: AAECAwQFBgcICQoLDA0OD/Dx8vP09fb3+Pn6+/z9/v8=
}
I have a project written in python. I use cryptography library to encrypt and decrypt data.
I do it how is shown in their tutorial.
Here is my python code:
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
password = b"my password"
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)
data = b"my data..."
token = f.encrypt(data)
Then for decryption I can just use:
f.decrypt(token)
Everything works perfectly in python but now I need to do the same thing in kotlin. I found out about fernet java-8 library but I don't know how to use it in the same way.
The problem is that I have two tools: one is written in python and another I want to write in kotlin. Both tools are meant to do the same thing - the python one is for desktop and the kotlin one is gonna be an android app. So it is really important for their encryption to be the same, so that files encrypted in python (desktop tool) can be decrypted in kotlin (android app) and vice versa.
But I don't know how to write analogous kotlin code.
You see there is a function (or class) called PBKDF2HMAC and there is also base64.urlsafe_b64encode and others. And I don't know what are analogous functions in kotlin or fernet java-8.
So how should I do it? Assuming that in kotlin I have to use password and salt I used in python.
Thanks!
In Java/Kotlin, using fernet-java8, the token generated with the Python code could be decrypted as follows:
import java.security.SecureRandom
import java.util.Base64
import javax.crypto.spec.PBEKeySpec
import javax.crypto.SecretKeyFactory
import com.macasaet.fernet.Key
import com.macasaet.fernet.Token
import com.macasaet.fernet.StringValidator
import com.macasaet.fernet.Validator
import java.time.Duration
import java.time.temporal.TemporalAmount
...
// Data from encryption
val salt = Base64.getUrlDecoder().decode("2Yb8EwpYkMlycHxoKcmHuA==")
val token = Token.fromString("gAAAAABfoAmp7C7IWVgA5urICEIspm_MPAGZ-SyGnPEVUBBNerWQ-K6mpSoYTwRkUt3FobyAFHbYfhNtiGMe_96yyLvUoeLIIg==");
// Derive Fernet key
val key = deriveKey("my password", salt)
val fernetKey = Key(key)
// Decrypt
val validator: Validator<String> = object : StringValidator {
override fun getTimeToLive(): TemporalAmount {
return Duration.ofHours(24)
}
}
val data = token.validateAndDecrypt(fernetKey, validator)
println(data) // my data...
with:
fun deriveKey(password: String, salt: ByteArray): String {
val iterations = 100000
val derivedKeyLength = 256
val spec = PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength)
val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val key = secretKeyFactory.generateSecret(spec).encoded
return Base64.getUrlEncoder().encodeToString(key)
}
Here the Fernet key is derived using the key derivation function PBKDF2. PBKDF2 expects various input parameters, such as a password, a digest, a salt, an iteration count and the desired key length. In the posted example the key is returned Base64url encoded.For decryption the same parameters must be used as for encryption. Since the salt is usually (as in the posted code) randomly generated during encryption, it must be passed to the decryption side along with the ciphertext (note: the salt is not a secret).
The validator sets the time-to-live (by default 60s) to 24h, see here for more details.
In the posted Python code the export of the salt has to be added, e.g. by Base64url encoding it analogous to key and token (and printing it for simplicity). In practice, salt and token could also be concatenated during encryption and separated during decryption.
Update:
The encryption part is analogous:
// Generate salt
val salt = generateSalt()
println(Base64.getUrlEncoder().encodeToString(salt))
// Derive Fernet key
val key = deriveKey("my password", salt)
val fernetKey = Key(key)
// Encrypt
val data = "my data..."
val token = Token.generate(fernetKey, data)
println(token.serialise()) // the Base64url encoded token
with
fun generateSalt(): ByteArray {
val random = SecureRandom()
val salt = ByteArray(16)
random.nextBytes(salt)
return salt
}
I wrote a test demo of signature & verification complete process base on rsa, which helps me to figure out the logic of the process.
# https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
# Preparation phase
# Generate key pairs
# private_key contains the both private key and public key
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# Serilize the keys
from cryptography.hazmat.primitives import serialization
pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b'mypassword')
)
with open('private-key.pem', 'wb') as f:
f.write(pem)
f.close()
public_key = private_key.public_key()
pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
with open('public-key.pem', 'wb') as f:
f.write(pem)
f.close()
# Signer
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import utils
with open('private-key.pem', 'rb') as f:
private_key = serialization.load_pem_private_key(
f.read(),
password=b'mypassword',
backend=default_backend()
)
chosen_hash = hashes.SHA256()
hasher = hashes.Hash(chosen_hash, default_backend())
hasher.update(b"data & ")
hasher.update(b"more data")
digest = hasher.finalize()
signature = private_key.sign(
digest,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
utils.Prehashed(chosen_hash)
)
with open('signature', 'wb') as f:
f.write(signature)
f.close()
# Verifier
chosen_hash = hashes.SHA256()
hasher = hashes.Hash(chosen_hash, default_backend())
hasher.update(b"data & ")
hasher.update(b"more data")
digest = hasher.finalize()
hasher1 = hashes.Hash(chosen_hash, default_backend())
hasher1.update(b"data & more data")
digest1 = hasher1.finalize()
print(digest == digest1)
with open('signature', 'rb') as f:
signature = f.read()
with open('public-key.pem', 'rb') as f:
public_key = serialization.load_pem_public_key(
f.read(),
backend=default_backend()
)
if isinstance(public_key, rsa.RSAPublicKey):
public_key.verify(
signature,
digest,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
utils.Prehashed(chosen_hash)
)
Question:
Does the padding type(eg. PSS) having to be known as input when verification?
But in openssl CLI Generate EC KeyPair from OpenSSL command line
openssl dgst -sha256 -verify public.pem -signature msg.signature.txt msg.digest.txt
Why here didn't mention the padding? I think no matter the key pairs algorithm(ECC or RSA) is different or not, the inputs parameter of (standard?) verification method shall be the same.
Another question, I saw in python having isinstance(public_key, rsa.RSAPublicKey) can find out the algorithm of the keys.
Does the algorithm type also necessary for the verification method?
Like inside the lib may having such ecc_verify rsa_verify methods.
BTW for my understanding, the verify method parameter(same as openssl CLI):
public key
hash type
signature
Does the padding type(eg. PSS) having to be known as input when verification?
Yes, the padding is a required configuration parameter that should be known beforehand.
Why here didn't mention the padding?
ECDSA signature verification doesn't require padding; it is significantly different from RSA signature generation / verification.
There is only one used standard for it, as was the case previously for RSA (PKCS#1 v1.5 padding). PSS was only added later in the RSA PKCS#1 standards when version 2.0 was published.
Does the algorithm type also necessary for the verification method?
Although there is probably not a direct attack, you should assume that the verification algorithm is known in advance, just like with the padding method. RSA as described in PKCS#1 has at least two signature generation functions and there are more in other standards - although those are not common at all.
BTW for my understanding, the verify method parameter(same as openssl CLI):
As you've already seen, RSA PSS uses two hash functions, one for hashing the input data and one for the internal MGF1 function used for padding. So there is not one hash type, but two. The hash types are not necessarily the same and implementations differ on how the MGF1 hash is determined (specifying it explicitly, as you do, is for the best).
This question already has answers here:
How to decrypt OpenSSL AES-encrypted files in Python?
(7 answers)
Closed 4 years ago.
I encrypted a file using openssl using the below command
cat input.txt
hello world
openssl aes-256-cbc -pass pass:'00112233445566778899aabbccddeeff' -iv a2a8a78be66075c94ca5be53c8865251 -nosalt -base64 -in input.txt -out output.txt
cat output.txt
pt7DqtAwtTjPbTlzVApucQ==
How can i decrypt the file using python Crypto package. I tried the below and it didn't work.
>>> from Crypto.Cipher import AES
>>> from base64 import b64decode
>>> with open('output.txt') as f:
... aes = AES.new('00112233445566778899aabbccddeeff', AES.MODE_CBC, IV='a2a8a78be66075c94ca5be53c8865251'.decode('hex'))
... print(aes.decrypt(b64decode(f.read())))
...
L�|�L ��O�*$&9�
I need a way to encrypt a file using openssl aes-256-cbc cipher and decrypt in python
The password is not the key. Openssl uses EVP_BytesToKey to create an appropriate key (& IV, if necessary) from the password and the salt.
As James K Polk mentions in a comment, you can use the -P (or -p) option to tell Openssl to print the key (in hex ), which you can then pass to Crypto.Cipher. Alternatively, you can implement EVP_BytesToKey in Python, as shown below. This is a simplified version of EVP_BytesToKey that uses no salt, and the default value of 1 for the count arg.
As the EVP_BytesToKey docs state, this is a rather weak password derivation function. As the hashlib docs mention, modern password derivation normally performs hundreds of thousands of hashes to make password hashing attacks very slow.
We also need a function to remove the PKCS7 padding from the decrypted data bytes. The unpad function below simply assumes that the padding data is valid. In real software the unpad function must verify that the padded data is valid to prevent padding-based attacks. My unpad function also assumes the data has been encoded as UTF-8 bytes and decodes the unpadded data to text.
from __future__ import print_function
from Crypto.Cipher import AES
from base64 import b64decode
from hashlib import md5
def evp_simple(data):
out = ''
while len(out) < 32:
out += md5(out + data).digest()
return out[:32]
def unpad(s):
offset = ord(s[-1])
return s[:-offset].decode('utf-8')
iv = 'a2a8a78be66075c94ca5be53c8865251'.decode('hex')
passwd = '00112233445566778899aabbccddeeff'
key = evp_simple(passwd)
print('key', key.encode('hex'))
aes = AES.new(key, AES.MODE_CBC, IV=iv)
data = b64decode('pt7DqtAwtTjPbTlzVApucQ==')
raw = aes.decrypt(data)
print(repr(raw), len(raw))
plain = unpad(raw)
print(repr(plain), len(plain))
output
key b4377f7babf2991b7d6983c4d3e19cd4dd37e31af1c9c689ca22e90e365be18b
'hello world\n\x04\x04\x04\x04' 16
u'hello world\n' 12
That code will not run on Python 3, so here's a Python 3 version.
from Crypto.Cipher import AES
from base64 import b64decode
from hashlib import md5
def evp_simple(data):
out = b''
while len(out) < 32:
out += md5(out + data).digest()
return out[:32]
def unpad(s):
offset = s[-1]
return s[:-offset].decode('utf-8')
iv = bytes.fromhex('a2a8a78be66075c94ca5be53c8865251')
passwd = b'00112233445566778899aabbccddeeff'
key = evp_simple(passwd)
aes = AES.new(key, AES.MODE_CBC, IV=iv)
data = b64decode('pt7DqtAwtTjPbTlzVApucQ==')
raw = aes.decrypt(data)
print(repr(raw), len(raw))
plain = unpad(raw)
print(repr(plain), len(plain))
output
b'hello world\n\x04\x04\x04\x04' 16
'hello world\n' 12
I am trying to encrypt a small string using Python and Ruby. I've written code in both these languages that should do exactly the same thing:
In Python:
from Crypto.PublicKey import RSA
from Crypto.Util import asn1
from Crypto import Random
import sys, time, signal, socket, requests, re, base64
pubkey = "9B596422997705A8805F25232C252B72C6B68752446A30BF9117783FE094F8559CA4A7AA5DBECAEC163596E96CD9577BDF232EF2F45DC474458BDA8EC272311924B8A176896E690135323D16800CFB9661352737FEDA9FB8DD44B6025EA8037CBA136DAE2DC0061B2C66A6F564E2C9E9579DAFD9BFF09ACF3B6E39BF363370F4A8AD1F88E3AE9A7F2C1B1C75AC00EAE308F57EC9FBDA244FC8B0D1234677A6BEE228FEE00BF653E8E010D0E59A30D0A1194298E052399A62E6FBD490CF03E16A1166F901E996D53DE776169B4EE8705E1512CCB69F8086C66213667A070A65DA28AF18FC8FC01D37158706A0B952D0963A9A4E4A6451758710C9ADD1245AB057389535AB0FA1D363A29ED8AE797D1EE958352E55D4AD4565C826E9EF12FA53AE443418FD704091039E190690FD55BF32E7E8C7D7668B8F0550C5E650C7D021F63A5055B7C1AEE6A669079494C4B964C6EA7D131FA1662CF5F5C83721D6F218038262E9DDFE236015EE331A8556D934F405B4203359EE055EA42BE831614919539A183C1C6AD8002E7E58E0C2BCA8462ADBF3916C62857F8099E57C45D85042E99A56630DF545D10DD338410D294E968A5640F11C7485651B246C5E7CA028A5368A0A74E040B08DF84C8676E568FC12266D54BA716672B05E0AA4EE40C64B358567C18791FD29ABA19EACA4142E2856C6E1988E2028703B3E283FA12C8E492FDB"
foobar = "foobar"
pubkey_int = long(pubkey,16)
pub_exp = 65537L
pubkey_obj = RSA.construct((pubkey_int, pub_exp))
encypted_data = pubkey_obj.encrypt(foobar, pub_exp)
encypted_data_b64 = base64.b64encode(encypted_data[0])
print encypted_data_b64
In Ruby:
require 'openssl'
require 'base64'
pubkey = "9B596422997705A8805F25232C252B72C6B68752446A30BF9117783FE094F8559CA4A7AA5DBECAEC163596E96CD9577BDF232EF2F45DC474458BDA8EC272311924B8A176896E690135323D16800CFB9661352737FEDA9FB8DD44B6025EA8037CBA136DAE2DC0061B2C66A6F564E2C9E9579DAFD9BFF09ACF3B6E39BF363370F4A8AD1F88E3AE9A7F2C1B1C75AC00EAE308F57EC9FBDA244FC8B0D1234677A6BEE228FEE00BF653E8E010D0E59A30D0A1194298E052399A62E6FBD490CF03E16A1166F901E996D53DE776169B4EE8705E1512CCB69F8086C66213667A070A65DA28AF18FC8FC01D37158706A0B952D0963A9A4E4A6451758710C9ADD1245AB057389535AB0FA1D363A29ED8AE797D1EE958352E55D4AD4565C826E9EF12FA53AE443418FD704091039E190690FD55BF32E7E8C7D7668B8F0550C5E650C7D021F63A5055B7C1AEE6A669079494C4B964C6EA7D131FA1662CF5F5C83721D6F218038262E9DDFE236015EE331A8556D934F405B4203359EE055EA42BE831614919539A183C1C6AD8002E7E58E0C2BCA8462ADBF3916C62857F8099E57C45D85042E99A56630DF545D10DD338410D294E968A5640F11C7485651B246C5E7CA028A5368A0A74E040B08DF84C8676E568FC12266D54BA716672B05E0AA4EE40C64B358567C18791FD29ABA19EACA4142E2856C6E1988E2028703B3E283FA12C8E492FDB"
foobar = "foobar"
asn1_sequence = OpenSSL::ASN1::Sequence.new(
[
OpenSSL::ASN1::Integer.new("0x#{pubkey}".to_i(16)),
OpenSSL::ASN1::Integer.new("0x10001".to_i(16))
]
)
public_key = OpenSSL::PKey::RSA.new(asn1_sequence)
data = Base64.encode64(public_key.public_encrypt(foobar))
puts data
Both these scripts are trying to encrypt the string foobar using the same public key. I expected both of them to output the same results each time, however this is not the case. Furthermore, every time the Ruby Script is executed, it outputs a different result.
Can someone help me identify the difference between these two scripts that is responsible for this behavior?
I am able to solve this issue by reading the documentation for Class _RSAobj (https://www.dlitz.net/software/pycrypto/api/current/Crypto.PublicKey.RSA._RSAobj-class.html#encrypt)
Attention: this function performs the plain, primitive RSA encryption
(textbook). In real applications, you always need to use proper
cryptographic padding, and you should not directly encrypt data with
this method. Failure to do so may lead to security vulnerabilities. It
is recommended to use modules Crypto.Cipher.PKCS1_OAEP or
Crypto.Cipher.PKCS1_v1_5 instead.
Looks like cryptographic padding is not used by default in the RSA module for Python, hence the difference.
Modified Python Script:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import sys, time, signal, socket, requests, re, base64
pubkey = "9B596422997705A8805F25232C252B72C6B68752446A30BF9117783FE094F8559CA4A7AA5DBECAEC163596E96CD9577BDF232EF2F45DC474458BDA8EC272311924B8A176896E690135323D16800CFB9661352737FEDA9FB8DD44B6025EA8037CBA136DAE2DC0061B2C66A6F564E2C9E9579DAFD9BFF09ACF3B6E39BF363370F4A8AD1F88E3AE9A7F2C1B1C75AC00EAE308F57EC9FBDA244FC8B0D1234677A6BEE228FEE00BF653E8E010D0E59A30D0A1194298E052399A62E6FBD490CF03E16A1166F901E996D53DE776169B4EE8705E1512CCB69F8086C66213667A070A65DA28AF18FC8FC01D37158706A0B952D0963A9A4E4A6451758710C9ADD1245AB057389535AB0FA1D363A29ED8AE797D1EE958352E55D4AD4565C826E9EF12FA53AE443418FD704091039E190690FD55BF32E7E8C7D7668B8F0550C5E650C7D021F63A5055B7C1AEE6A669079494C4B964C6EA7D131FA1662CF5F5C83721D6F218038262E9DDFE236015EE331A8556D934F405B4203359EE055EA42BE831614919539A183C1C6AD8002E7E58E0C2BCA8462ADBF3916C62857F8099E57C45D85042E99A56630DF545D10DD338410D294E968A5640F11C7485651B246C5E7CA028A5368A0A74E040B08DF84C8676E568FC12266D54BA716672B05E0AA4EE40C64B358567C18791FD29ABA19EACA4142E2856C6E1988E2028703B3E283FA12C8E492FDB"
foobar = "foobar"
pubkey_int = long(pubkey,16)
pub_exp = 65537L
pubkey_obj = RSA.construct((pubkey_int, pub_exp))
cipher = PKCS1_v1_5.new(pubkey_obj)
encypted_data = cipher.encrypt(foobar)
encypted_data_b64 = base64.b64encode(encypted_data)
print encypted_data_b64