I am using the following .NET code to generate a key from a password and salt:
static byte[] GenerateKey(string password, string salt, int size)
{
var saltBytes = Encoding.Unicode.GetBytes(salt);
var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes, iterations);
var key = derivedBytes.GetBytes(size);
Console.WriteLine(string.Format("Key: {0}", Convert.ToBase64String(key)));
return key;
}
// Console.Writeline() shows
// Key: tb6yBBYGdZhyFWrpWQ5cm5A1bAI5UF0KnDdom7BhVz0=
// for password="password" and salt="salt"
I need to decode the encoded message using python, a language I am only slightly familiar with, using the same password and salt. Thanks to #Topaco I now know that there is the PBKDF2 equivalent:
def decrypt_file(filename, password, salt):
key = PBKDF2(password, salt, 32, count=12345, hmac_hash_module=SHA1)
print(f"Key: {base64.b64encode(key).decode('utf-8')}");
# more lines redacted
# print() shows
# Key: 3ohW9ctQIXoNvGnvLaKmoQTG8/jJzoFThviHXqgM9Co=
# for password="password" and salt="salt"
I'm having some trouble getting the same key from both implementations. I am not well-versed in python's encoding and decoding; it's entirely likely possible that I am generating the same key but the base64.b64encode(key).decode('utf-8') line is showing me a different translation.
What am I doing wrong here?
You have to encode the salt with UTF-16LE since Encoding.Unicode corresponds to UTF-16LE. The rest is fine:
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA1
import base64
password = b'password'
salt = 'salt'.encode('utf-16le')
key = PBKDF2(password, salt, 32, count=12345, hmac_hash_module=SHA1)
print(base64.b64encode(key).decode('utf-8')) # tb6yBBYGdZhyFWrpWQ5cm5A1bAI5UF0KnDdom7BhVz0=
For completeness: If the password contains non-ASCII characters, the password in the Python code must be encoded with the more specific '...'.encode('utf-8'), since the Rfc2898DeriveBytes overload used in the C# code encodes the password string with UTF-8.
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 kafka message that was encrypted on java with the following code:
private String decryptedMessage(String key, String encryptedMessage) throws NoSuchAlgorithmException, InvalidKeySpecException, ParseException, JOSEException {
PrivateKey privateKey = <some way to generate a private key from key>;
JWEDecrypter decrypter = new RSADecrypter(privateKey);
JWEObject decryptedJweObj = JWEObject.parse(encryptedMessage);
decryptedJweObj.decrypt(decrypter);
return decryptedJweObj.getPayload().toJSONObject().toJSONString();
}
Now I'm trying to decode it using python on a decoded message where I already know the private key.
I tried using jwcrypto (since I'm using python3 and jeso is only for 2.x) using this code like in their documents, but it didn't work:
enc = '<encrypted message>'
private = '<private key>'
jwetoken = jwe.JWE()
jwetoken.deserialize(enc, key=private_key)
payload = jwetoken.payload
And I get this error code: jwcrypto.jwe.InvalidJWEData: No recipient matched the provided key["Failed: [ValueError('key is not a JWK object',)]"] I tried looking for a way to make the private key a JWK object but couldn't find one.
I know my message is JWE since it's split by 4 dots and when I base64 decode the first part I get this json: b'{"alg":"RSA-OAEP-256","enc":"A256GCM","kid":"<some key id>"}'
So I'm kind of stuck on how to decode my message.
Decrypted string: {"value":"Object Encryption"}
Encrypted string:
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiYjFhOWVmNzAtYjQ4Yy00YzdiLWI0ZTQtODU2YzQyNGIyYzZlIn0.XzLgQTzESD7mg-DtiwFaOQQIfJjQOox5Efbq3Cn8n4H0OZUNvNFWuLr2gPH4WqhWZFYvYh6Mx3--bKiYA_kGplPaJUdPfuYx3OgOug9fuYMrZesE-9stJFd4TnQOJcrTfehJkI_QKPqfWgbEgh1Zn8r7DuIBbABmNK4OHa0edwUA0Lu4mUxzRW6UPaNdWtfEGL9ZVR4lzUx6QX6nweKdbi8tkDnQrSNcQ4eZKIn8mVl5rL3s-qa2VC1Zvo4R-eA4jTKs6WQrkPChJkfoECcYcLx7SIHdxP6VB9DAhW-TwRizr5OZUVLLLH3UEOF77Rtc6MLL4Al5mo29sE-E1faywQ.R1QE-zY29Ed7yfqX.-soXsfltkJL0AXk_q5tPn9hagCBG_1c03VKdh2A.-oteTYv0SHzE4yBmZlterg
Decryption key (need to decode with base64):
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChyho54VxOGYDxrN2pjM8/pA94kcqlMNZ0NSIoSHhasAm72X66XN9GI2IapQETy7+gNBKBWszWn4JsNuAbLLNp5zeTlmzHp09ee4L+g8s/NIKMwA5Mgx9wGu2Hi0foh0pErAMKQV6CLBJUfB5JUu9PX2MED7Z2XGG0RYClSMEO8L7iHXm1ooCr83rU/U5xXwpuTrp4L9nYD9eB6EptCgOWEi98lq5oeQXNyWfti3/gckathUi8WINlI+5/fPv7ZWd9Z60VXAVfb5u861/erIhEIJRkqbDXc/ULAQsAeEKVYzHpStM9In30mQdp6EdY48imZiHj1GNgPvY+MD3wPmdTAgMBAAECggEAE3IcJtrMYmK0WdfiKI/RFSAd7+ruBV7SV9NPELJtLNE9ykNA9RtWhrKYBwXQFFYH6TR5CO0l86HmZiVOWFXOFquAxY9t8a1NX9jOjNLAag9gpZQr48xayfmilQkLkoo4Rfq6vs/OkSzE4zyr0zpRoyOe3g0ZbC58W9OCu9r7wVTDV+KKE8ChU39Ae8HLfMEQXWSIUqxbrpw+mLeFX1qh5ILNBDA9M6vD+JuoiuIZltW4djnlU4hxYPVwTyuPBCQ3AwJsRGcddfxWTI+kIm2/6k8HzhdpB1ajBrZX/XVeXAp7VlvyHzBZ5ri1NDpr/Cyh6o2ysdB8qGCnlDcsNHLl+QKBgQDTORonO1FrIAo2VnsQS0Lq6EdxRzzP9q9mHttJNJK5eEXP7sNSdTDrudk4tpr3zvXtK4b+4SiCDriA5VTJHhdGF3wwqhR76XT3gLoXZLGYlx+4RvMbuIFDGkUee+39T2//MztEZgt3TM3LcBFEjTRV8gzpFJsj8wea3E4B8lOEZQKBgQDEFkfdgIBNu/3wH+z2uq40kYlkMRb4wQq8CjmwXYxGu1WR3SYn2zNQTsZR6BtiHFzx/37W279dO87u9rAbNuY5V9VYQKjxZD2lidYQ/0w07kO1PhNuISLpqn2AbiLczlLluX8dHpLpb5UG+JWlqih5VBCDFktmVUlMVteHPa21VwKBgCkpEHqiqYwJk1PhaFvVfrXOC9X8PtJ7zNRGoQ7T6t+vm1MYwQE5iw30imrt0qcFspDEEatrbvxhJ/0eM3Z5oalr/CxziEhZRwzQDfNvENieYnUDhm5Zdv7/iIaXOdpJ95YwgpUimYtm8Rd6wDKunYs9/twQwuavfkTkN2NTuIitAoGBAJu0NYylpTwUsyghscCZrAsCJd7xPBR69VMrq3NoVSM1TlVtDgdIAA8c/k27yUK20vc2sjladTJLc549NMnnZhjSrg5OCdjkiC8SrHECyDifmhQpHrSsi1SQlOeOjRBYpWrVSSKOTIogmG3YprvNyiXNou70nRq9Tl7X9nzldTIxAoGBAJ3qo/epO7xAs6Vhp+8na0Tv93Ji2kCvA14iOE+P08saldspB4NEVitGahuvrTx8CKDyL1PDH5X1MTabvI/LYw6k/gnzOhSX3MTP3qMepNEq1A5chvMJHhMS72/tAPKIXux+AgDkSvq8+G02DFWDpF9bcBoINL/89wOUcEfOQty0
The posted encrypted token can be decrypted with the posted RSA key and the Python library JWCrypto. Maybe it doesn't work for you because of a key import bug.
The posted RSA key is a DER encoded PKCS#8 key (Base64 encoded). JWCrypto does not support this encoding directly, but only the PEM encoding, which is not too bad, because the conversion from DER to PEM is trivial: The Base64 string must be formatted (line break after every 64 characters) and the header (-----BEGIN PRIVATE KEY-----) and footer (-----END PRIVATE KEY-----) must be added each in a separate line.
Alternatively, the key can be converted to a JWK and imported in this format.
The following Python code shows these two variants based on the posted data. For this the JWK was derived with online tools from the posted PKCS#8 key:
pkcs8pem = b'''-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChyho54VxOGYDx
rN2pjM8/pA94kcqlMNZ0NSIoSHhasAm72X66XN9GI2IapQETy7+gNBKBWszWn4Js
NuAbLLNp5zeTlmzHp09ee4L+g8s/NIKMwA5Mgx9wGu2Hi0foh0pErAMKQV6CLBJU
fB5JUu9PX2MED7Z2XGG0RYClSMEO8L7iHXm1ooCr83rU/U5xXwpuTrp4L9nYD9eB
6EptCgOWEi98lq5oeQXNyWfti3/gckathUi8WINlI+5/fPv7ZWd9Z60VXAVfb5u8
61/erIhEIJRkqbDXc/ULAQsAeEKVYzHpStM9In30mQdp6EdY48imZiHj1GNgPvY+
MD3wPmdTAgMBAAECggEAE3IcJtrMYmK0WdfiKI/RFSAd7+ruBV7SV9NPELJtLNE9
ykNA9RtWhrKYBwXQFFYH6TR5CO0l86HmZiVOWFXOFquAxY9t8a1NX9jOjNLAag9g
pZQr48xayfmilQkLkoo4Rfq6vs/OkSzE4zyr0zpRoyOe3g0ZbC58W9OCu9r7wVTD
V+KKE8ChU39Ae8HLfMEQXWSIUqxbrpw+mLeFX1qh5ILNBDA9M6vD+JuoiuIZltW4
djnlU4hxYPVwTyuPBCQ3AwJsRGcddfxWTI+kIm2/6k8HzhdpB1ajBrZX/XVeXAp7
VlvyHzBZ5ri1NDpr/Cyh6o2ysdB8qGCnlDcsNHLl+QKBgQDTORonO1FrIAo2VnsQ
S0Lq6EdxRzzP9q9mHttJNJK5eEXP7sNSdTDrudk4tpr3zvXtK4b+4SiCDriA5VTJ
HhdGF3wwqhR76XT3gLoXZLGYlx+4RvMbuIFDGkUee+39T2//MztEZgt3TM3LcBFE
jTRV8gzpFJsj8wea3E4B8lOEZQKBgQDEFkfdgIBNu/3wH+z2uq40kYlkMRb4wQq8
CjmwXYxGu1WR3SYn2zNQTsZR6BtiHFzx/37W279dO87u9rAbNuY5V9VYQKjxZD2l
idYQ/0w07kO1PhNuISLpqn2AbiLczlLluX8dHpLpb5UG+JWlqih5VBCDFktmVUlM
VteHPa21VwKBgCkpEHqiqYwJk1PhaFvVfrXOC9X8PtJ7zNRGoQ7T6t+vm1MYwQE5
iw30imrt0qcFspDEEatrbvxhJ/0eM3Z5oalr/CxziEhZRwzQDfNvENieYnUDhm5Z
dv7/iIaXOdpJ95YwgpUimYtm8Rd6wDKunYs9/twQwuavfkTkN2NTuIitAoGBAJu0
NYylpTwUsyghscCZrAsCJd7xPBR69VMrq3NoVSM1TlVtDgdIAA8c/k27yUK20vc2
sjladTJLc549NMnnZhjSrg5OCdjkiC8SrHECyDifmhQpHrSsi1SQlOeOjRBYpWrV
SSKOTIogmG3YprvNyiXNou70nRq9Tl7X9nzldTIxAoGBAJ3qo/epO7xAs6Vhp+8n
a0Tv93Ji2kCvA14iOE+P08saldspB4NEVitGahuvrTx8CKDyL1PDH5X1MTabvI/L
Yw6k/gnzOhSX3MTP3qMepNEq1A5chvMJHhMS72/tAPKIXux+AgDkSvq8+G02DFWD
pF9bcBoINL/89wOUcEfOQty0
-----END PRIVATE KEY-----'''
jwkey = {"p":"0zkaJztRayAKNlZ7EEtC6uhHcUc8z_avZh7bSTSSuXhFz-7DUnUw67nZOLaa98717SuG_uEogg64gOVUyR4XRhd8MKoUe-l094C6F2SxmJcfuEbzG7iBQxpFHnvt_U9v_zM7RGYLd0zNy3ARRI00VfIM6RSbI_MHmtxOAfJThGU","kty":"RSA","q":"xBZH3YCATbv98B_s9rquNJGJZDEW-MEKvAo5sF2MRrtVkd0mJ9szUE7GUegbYhxc8f9-1tu_XTvO7vawGzbmOVfVWECo8WQ9pYnWEP9MNO5DtT4TbiEi6ap9gG4i3M5S5bl_HR6S6W-VBviVpaooeVQQgxZLZlVJTFbXhz2ttVc","d":"E3IcJtrMYmK0WdfiKI_RFSAd7-ruBV7SV9NPELJtLNE9ykNA9RtWhrKYBwXQFFYH6TR5CO0l86HmZiVOWFXOFquAxY9t8a1NX9jOjNLAag9gpZQr48xayfmilQkLkoo4Rfq6vs_OkSzE4zyr0zpRoyOe3g0ZbC58W9OCu9r7wVTDV-KKE8ChU39Ae8HLfMEQXWSIUqxbrpw-mLeFX1qh5ILNBDA9M6vD-JuoiuIZltW4djnlU4hxYPVwTyuPBCQ3AwJsRGcddfxWTI-kIm2_6k8HzhdpB1ajBrZX_XVeXAp7VlvyHzBZ5ri1NDpr_Cyh6o2ysdB8qGCnlDcsNHLl-Q","e":"AQAB","kid":"79635991-092f-4576-a23a-4cbab618e8a8","qi":"neqj96k7vECzpWGn7ydrRO_3cmLaQK8DXiI4T4_TyxqV2ykHg0RWK0ZqG6-tPHwIoPIvU8MflfUxNpu8j8tjDqT-CfM6FJfcxM_eox6k0SrUDlyG8wkeExLvb-0A8ohe7H4CAORK-rz4bTYMVYOkX1twGgg0v_z3A5RwR85C3LQ","dp":"KSkQeqKpjAmTU-FoW9V-tc4L1fw-0nvM1EahDtPq36-bUxjBATmLDfSKau3SpwWykMQRq2tu_GEn_R4zdnmhqWv8LHOISFlHDNAN828Q2J5idQOGbll2_v-Ihpc52kn3ljCClSKZi2bxF3rAMq6diz3-3BDC5q9-ROQ3Y1O4iK0","dq":"m7Q1jKWlPBSzKCGxwJmsCwIl3vE8FHr1Uyurc2hVIzVOVW0OB0gADxz-TbvJQrbS9zayOVp1Mktznj00yedmGNKuDk4J2OSILxKscQLIOJ-aFCketKyLVJCU546NEFilatVJIo5MiiCYbdimu83KJc2i7vSdGr1OXtf2fOV1MjE","n":"ocoaOeFcThmA8azdqYzPP6QPeJHKpTDWdDUiKEh4WrAJu9l-ulzfRiNiGqUBE8u_oDQSgVrM1p-CbDbgGyyzaec3k5Zsx6dPXnuC_oPLPzSCjMAOTIMfcBrth4tH6IdKRKwDCkFegiwSVHweSVLvT19jBA-2dlxhtEWApUjBDvC-4h15taKAq_N61P1OcV8Kbk66eC_Z2A_XgehKbQoDlhIvfJauaHkFzcln7Yt_4HJGrYVIvFiDZSPuf3z7-2VnfWetFVwFX2-bvOtf3qyIRCCUZKmw13P1CwELAHhClWMx6UrTPSJ99JkHaehHWOPIpmYh49RjYD72PjA98D5nUw"}
enc = 'eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiYjFhOWVmNzAtYjQ4Yy00YzdiLWI0ZTQtODU2YzQyNGIyYzZlIn0.XzLgQTzESD7mg-DtiwFaOQQIfJjQOox5Efbq3Cn8n4H0OZUNvNFWuLr2gPH4WqhWZFYvYh6Mx3--bKiYA_kGplPaJUdPfuYx3OgOug9fuYMrZesE-9stJFd4TnQOJcrTfehJkI_QKPqfWgbEgh1Zn8r7DuIBbABmNK4OHa0edwUA0Lu4mUxzRW6UPaNdWtfEGL9ZVR4lzUx6QX6nweKdbi8tkDnQrSNcQ4eZKIn8mVl5rL3s-qa2VC1Zvo4R-eA4jTKs6WQrkPChJkfoECcYcLx7SIHdxP6VB9DAhW-TwRizr5OZUVLLLH3UEOF77Rtc6MLL4Al5mo29sE-E1faywQ.R1QE-zY29Ed7yfqX.-soXsfltkJL0AXk_q5tPn9hagCBG_1c03VKdh2A.-oteTYv0SHzE4yBmZlterg'
from jwcrypto import jwk, jwe
# Import of a PEM encoded PKCS#8 key
private_key = jwk.JWK.from_pem(pkcs8pem)
jwetoken = jwe.JWE()
jwetoken.deserialize(enc, key=private_key)
payload = jwetoken.payload
print(payload.decode('utf-8'))
# Import of a JWK
private_key = jwk.JWK(**jwkey)
jwetoken = jwe.JWE()
jwetoken.deserialize(enc, key=private_key)
payload = jwetoken.payload
print(payload.decode('utf-8'))
with the output:
{"value":"Object Encryption"}
{"value":"Object Encryption"}
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 am using RSA with private/public key. I am trying to encrypt a string, save it in a database (sqlite) and then retrieve it again and encrypt.
I can't decrypt the data again when it is coming out of the sqlite. The string is identical and I am a bit lost.
#!/usr/bin/env python3
from Crypto.PublicKey import RSA
from Crypto import Random
import sqlite3
import base64
# database layout
#
# CREATE TABLE secrets ( id INT, secret TEXT );
# INSERT INTO secrets (id,secret) VALUES (1,"");
# database
conn = sqlite3.connect('database.db')
c = conn.cursor()
# generate keys
private_key = RSA.generate(1024, Random.new().read)
public_key = private_key.publickey()
# save keys
f = open('public.pem', 'wb+')
f.write(public_key.exportKey('PEM'))
f.close()
f = open('private.pem', 'wb+')
f.write(private_key.exportKey('PEM'))
f.close()
# crypt
f = open('public.pem','rb')
encrypt_public_key = RSA.importKey(f.read())
secret = "123456"
enc_secret = encrypt_public_key.encrypt(secret.encode("utf-8"), 32)[0]
enc_secret_encoded = base64.b64encode(enc_secret)
print("Base64: " + str(enc_secret_encoded))
# save in db
c.execute('UPDATE secrets SET secret="%s" WHERE id=1' % (enc_secret_encoded))
conn.commit()
print("--------------- DECRYPTION ------------------------")
# decrypt
p = open('private.pem','rb')
decrypt_private_key = RSA.importKey(p.read())
c.execute('SELECT secret FROM secrets WHERE id=1')
result = c.fetchone()
encoded_secret = result[0]
print("Base64: " + encoded_secret)
decoded_secret = base64.b64decode(encoded_secret)
enc_secret = decrypt_private_key.decrypt(decoded_secret)
print("Decrypted: " + str(enc_secret))
Output:
$ ./stuck.py
Base64: b'bfAERXPFvrDRdr5Pcexu8JgHlKfDaUhkqJrSWZJbLwlKLWY8XHtIlBwrRfP7eMX9PTKo4t2CtpdXS6Fam4B+jR3/bYPxji0rHt1Aed64sLH4xAnxgh5B/qWidcYT5cPmvwMekGbCaMSgGjvNB4Js/yDRrW4+N8dqx3IoUAl8zgA='
--------------- DECRYPTION ------------------------
Base64: b'bfAERXPFvrDRdr5Pcexu8JgHlKfDaUhkqJrSWZJbLwlKLWY8XHtIlBwrRfP7eMX9PTKo4t2CtpdXS6Fam4B+jR3/bYPxji0rHt1Aed64sLH4xAnxgh5B/qWidcYT5cPmvwMekGbCaMSgGjvNB4Js/yDRrW4+N8dqx3IoUAl8zgA='
Decrypted: b'\x90\x07\xa2}\x96w\xda\xd3h\xf1\xd4\xc6z\xa5\xf3\x85\x97\xeb\xcfL\x0e\x1f;\x18\xd5\x98\xb3\xb2\xd0\x93.\xc9z\x1c\xc8\xac\xe4x\xbfT\xe4{\x1b\x19\xda\xfb/?A\xda_\xceHc\xd14X\x94\x8a\x94\xfc\x12\xc4\x86\xc9\x16\xc9b\xbf\xdaJ\xcf\xff\xe1J\x95\x03&\xda\x98\x9f\x10\xb1\tzW\xea\x9b\xd2\x13\xc1\x8d\x19\xe97\xd6\xeay\xf3\x83\xb7\xcf\xd3v\\`~\x07\xcea(\x81\xe1c\x08\x0b\x8c\xee\xc2\x87\xed\xc8\x08D\x8e\xe5\x83\xf4'
When you run my example, you will see that the same encrypted string gets into the sqlite and out again, but why can't I decrypt it again and get the same result as secret?
UPDATE: When I remove the sqlite database then it works as expected. So the problem must be somewhere in storing or retrieving the data.
Any hint appreciated.
The Python base 64 library returns a bytes object rather than a string when encoding (which is a bit odd since the whole point of base 64 encoding is to create a printable string).
This means that when you convert the result to a string to save it in sqlite it is in the form b'XXX...XX', i.e. it is saved as a string starting with a b with quotes around the actual base64 encoded data.
When decoding, the default is to discard any non base64 characters. So this removes the quotes but not the initial b. This means the data you are decoding has an extra b at the front so you end up trying to decrypt the wrong cipher text.
You can see this by adding validate=True to the call to decode the base 64 data to force it to validate the input. This will cause a binascii.Error because of the ' character.
enc_secret = base64.b64decode(enc_secret_b64, validate=True)
The fix is to decode the bytes object from base 64 encoding into an ASCII string before saving to sqlite. Then only the “real” base 64 characters will saved to the database:
enc_secret_encoded = base64.b64encode(enc_secret).decode("ASCII")
you got the wrong order of base64/RSA while decoding. this works:
#!/usr/bin/env python3
from Crypto.PublicKey import RSA
from Crypto import Random
import base64
key_pair = RSA.generate(1024, Random.new().read(1024 // 8))
public_key = key_pair.publickey()
secret = "123456"
enc_secret = public_key.encrypt(secret.encode("utf-8"), 32)[0]
enc_secret_b64 = base64.b64encode(enc_secret)
print(enc_secret_b64)
enc_secret = base64.b64decode(enc_secret_b64)
secret = key_pair.decrypt(enc_secret)
print(secret.decode("utf-8"))
# 123456
also note that you need to call the .read method of Random.
apart from that: that is not what RSA is meant for. if you want to encrypt data using RSA you should use RSA for key encapsulation only and encrypt the data using a symmetric crypto system (e.g. AES).
I want to make RSA encryption with private key (not normal signing), but PyCryptodome seems not be able to do it.
The reason I need to do it with private key is, I need to get the same results as from a Java program not written by me, which wrongly uses javax.crypto.Cipher and private key to sign messages...
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import javax.crypto.Cipher;
...
String deviceKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASC...";
PKCS8EncodedKeySpec localPKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(deviceKey).getBytes("UTF-8"));
PrivateKey localPrivateKey = KeyFactory.getInstance("RSA").generatePrivate(localPKCS8EncodedKeySpec);
byte[] hash = MessageDigest.getInstance("SHA-256").digest("test".getBytes());
Cipher localCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
localCipher.init(Cipher.ENCRYPT_MODE, localPrivateKey);
String sign = new String(Base64.encode(localCipher.doFinal(hash)));
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Hash import SHA256
...
deviceKey = 'MIIEvgIBADANBgkqhkiG9w0BAQEFAASC...'
privateKey = RSA.importKey(deviceKey)
hash = SHA256.new('test'.encode()).digest()
signer = PKCS1_v1_5.new(privateKey)
sign = b64encode(signer.encrypt(hash))
Result of the Java program:
Hash: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
Sign: k8y6zMfl0KVuQWWOmRxieXF1aH0dpVUX......(always the same)
Result of my Python script:
Hash: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
Sign: GfLVqZDnu5aLHHbi0tM5OtCBEVKKRcjW......(it changes every time)
You don't use private keys to encrypt data.
A private key can sign data, which can be verified by the matching public key.
A public key can encrypt data, which can be decrypted by the matching private key.
If what you really want is to sign your hash, instead of using the encrypt function, you should use the sign function.
So, instead of
from Crypto.Cipher import PKCS1_v1_5
PKCS1_v1_5.new(privateKey).encrypt(hash)
you might want to try
from Crypto.Signature import pkcs1_15
pkcs1_15.new(privateKey).sign(hash)
I wrote a little blog post about signing/verifying using pycryptodome, if you want to take a look.