I am encrypting data successfully in python and sending the url safe data to PHP. When I try to decrypt the data with the openssl_decrypt
PHP method it returns false
. I have been reading for 2 days know searching for a solution with none of them solving my problem [there are 2 similar questions on this forum, but they did not help, Q1, Q2 ].
The encryption uses python3.8 and package PyCryptodome:
from Crypto.Cipher import AES
import base64
import hashlib
import os
import secrets
BLOCK = 16
PADDING = '~'
iv = secrets.token_urlsafe(16)
secretKey = secrets.token_urlsafe(32) # 32 bytes is used as AES 256 expects 256 bits
encoding = 'latin-1'
s = 'very secret data' # secret to be encrypted
k = hashlib.sha256(secretKey.encode(encoding)).hexdigest()[:32].encode(encoding) # key hashed and encoded
pad = s (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
cipher = AES.new(key=k, mode=AES.MODE_CBC, IV=hashlib.sha256(iv.encode(encoding)).hexdigest()[:16].encode(encoding)) # cipher object
encrypted_secret = base64.urlsafe_b64encode(cipher.encrypt(pad.encode(encoding)))
print("Key: ", secretKey, "\n", "IV:",iv, "\n", "Encrypted:", encrypted_secret.decode(encoding))
Outputs[0]:
Key: cdLTCHW95FjHF6ESu2Rkm-90AUbzDBv71HrdshsEx3k
IV: b16ezD5O05EDNovsqLExUg
Encrypted: RJqA3-n6d7hmKgj7biiwUKD0SjRjm__4f42P06R-qO8=
*I decode the encrypted secret result because it is sent as a json encoded object and json`s encoder complains about byte objects in its midst.
Decrypting in PHP is less code (for tests I copy key and iv over, but the iv is newly created every time and the secret key is stored on different server):
$key = 'cdLTCHW95FjHF6ESu2Rkm-90AUbzDBv71HrdshsEx3k';
$iv = 'b16ezD5O05EDNovsqLExUg';
$method = "AES-256-CBC";
$blocksize = 16;
$padwith = '~';
$decoded_secret = 'RJqA3-n6d7hmKgj7biiwUKD0SjRjm__4f42P06R-qO8='; // secret is sent as string
$hashKey = substr(hash('sha256', $key), 0, 32);
$iv_len = openssl_cipher_iv_length($method);
$iv_hash = substr(hash('sha256', $iv), 0, $iv_len); // cipher expects an IV of precisely 16 bytes, padding with \\0
$decrypted_secret = openssl_decrypt($decoded_secret, $method, $hashKey, OPENSSL_RAW_DATA, $iv_hash);
$what = rtrim($decrypted_secret, $padwith);
var_dump($decrypted_secret, $what);
Outputs[1]: bool(false) string(0) ""
I have tried setting utf-8
as encoding in python, decoding the encrypted string with base64
in php, changing block size to 32, not using hashlib
on python side, but none of the changes gave the expected decrypted result.
Can someone please give me a clue to solve this problem?
CodePudding user response:
You actually need two clues, so let's call it a holiday bonus :-)
your data is in urlsafe-base64, but your php doesn't decode it, so it tries to decrypt entirely wrong data. php doesn't (AFAICS) directly support urlsafe, so you need to convert it; you can then decode it and pass to
openssl_decrypt
, or simply callopenssl_decrypt
withoutOPENSSL_RAW_DATA
because it defaults to (traditional aka MIME or PEM or PGP or XML) base64.your encryption doesn't use PKCS5/7 padding, but OpenSSL uses that by default, so your decryption fails. You need to use
OPENSSL_ZERO_PADDING
which becomes in this case a no-op and then (as you already did) do the unpad yourself.
With either the uncommented or commented line in this code, it works:
<?php
$key = 'cdLTCHW95FjHF6ESu2Rkm-90AUbzDBv71HrdshsEx3k';
$iv = 'b16ezD5O05EDNovsqLExUg';
$method = "AES-256-CBC";
$blocksize = 16; // not used
$padwith = '~';
$secret = 'RJqA3-n6d7hmKgj7biiwUKD0SjRjm__4f42P06R-qO8='; // secret is sent as string
$fixed_secret = str_replace(array('-','_'),array(' ','/'),$secret);
$decoded_secret = base64_decode($fixed_secret); // maybe used
$hashKey = substr(hash('sha256', $key), 0, 32);
$iv_len = openssl_cipher_iv_length($method);
$iv_hash = substr(hash('sha256', $iv), 0, $iv_len);
$decrypted_secret = openssl_decrypt($fixed_secret, $method, $hashKey, OPENSSL_ZERO_PADDING, $iv_hash);
//$decrypted_secret = openssl_decrypt($decoded_secret, $method, $hashKey, OPENSSL_RAW_DATA OPENSSL_ZERO_PADDING, $iv_hash);
$what = rtrim($decrypted_secret, $padwith);
var_dump($decrypted_secret, $what);
?>
->
string(32) "very secret data~~~~~~~~~~~~~~~~"
string(16) "very secret data"