Convert openssl AES in Php to Python AES - php

I have a php file which is as follow:
$encryption_encoded_key = 'c7e1wJFz+PBwQix80D1MbIwwOmOceZOzFGoidzDkF5g=';
function my_encrypt($data, $key) {
$encryption_key = base64_decode($key);
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cfb'));
$encrypted = openssl_encrypt($data, 'aes-256-cfb', $encryption_key, 1, $iv);
// The $iv is just as important as the key for decrypting, so save it with encrypted data using a unique separator (::)
return base64_encode($encrypted . '::' . $iv);
}
function my_decrypt($data, $key) {
// Remove the base64 encoding from key
$encryption_key = base64_decode($key);
// To decrypt, split the encrypted data from IV - unique separator used was "::"
list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
return openssl_decrypt($encrypted_data, 'aes-256-cfb', $encryption_key, 1, $iv);
}
$data = 'USER_ID||NAME||EMAIL||MOBILE';
$data_encrypted = my_encrypt($data, $encryption_encoded_key);
echo $data_encrypted;
$data_decrypted = my_decrypt($data_encrypted, $encryption_encoded_key);
echo "Decrypted string: ". $data_decrypted;
This works fine, and is able to encrypt/decrypt with the encryption key, now i also have a python file:
import hashlib
import base64
from Crypto.Cipher import AES
from Crypto import Random
encryption_encoded_key = 'c7e1wJFz+PBwQix80D1MbIwwOmOceZOzFGoidzDkF5g='
def my_encrypt(data, key):
#Remove the base64 encoding from key
encryption_key = base64.b64decode(key)
#Generate an initialization vector
bs = AES.block_size
iv = Random.new().read(bs)
cipher = AES.new(encryption_key, AES.MODE_CFB, iv)
#Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
encrypted = cipher.encrypt(data)
#The iv is just as important as the key for decrypting, so save it with encrypted data using a unique separator (::)
return base64.b64encode(encrypted + '::' + iv)
def my_decrypt(data, key):
#Remove the base64 encoding from key
encryption_key = base64.b64decode(key)
#To decrypt, split the encrypted data from IV - unique separator used was "::"
encrypted_data, iv = base64.b64decode(data).split('::')
cipher = AES.new(encryption_key, AES.MODE_CFB, iv)
return cipher.decrypt(encrypted_data)
data = 'USER_ID||NAME||EMAIL||MOBILE'
print "Actual string: %s" %(data)
data_encrypted = my_encrypt(data, encryption_encoded_key)
print data_encrypted
data_decrypted = my_decrypt(data_encrypted, encryption_encoded_key)
print "Decrypted string: %s" %(data_decrypted)
This also works fine when i try to use this from python, it is able to encrypt/decrypt input string,
I want to encrypt using php file and decrypt the output in python, both should use AES 256 Encryption using CFB mode, what am i doing wrong ?

To use CFB mode you need to specify a segment size for it. OpenSSL has aes-256-cfb (which is 128 bit), aes-256-cfb1 (i.e. 1-bit) and aes-256-cfb8 (8 bit) (and similar modes for AES-128 and 192). So you are using 128 bit cfb in your php code.
The Python library accepts a segment_size argument to AES.new, but the default is 8, so you are using different modes in the two versions.
To get the Python code to decrypt the output of the PHP code, add a segment size of 128 to the cipher object:
cipher = AES.new(encryption_key, AES.MODE_CFB, iv, segment_size=128)
(N.B. this is using the newer PyCryptodome fork of PyCrypto. PyCrypto has a bug here and won’t work.)
Alternatively, you can get the PHP code to use CFB-8 by setting the cipher (don’t change both, obviously):
$encrypted = openssl_encrypt($data, 'aes-256-cfb8', $encryption_key, 1, $iv);

Related

How to create openssl encryption and decryption equivalent of php code in nodejs application

I have an application running on php which have some values encrypted using openssl encrption by using the code below
<?php
define('OSSLENCKEY','14E2E2D1582A36172AE401CB826003C1');
define('OSSLIVKEY', '747E314D23DBC624E971EE59A0BA6D28');
function encryptString($data) {
$encrypt_method = "AES-256-CBC";
$key = hash('sha256', OSSLENCKEY);
$iv = substr(hash('sha256', OSSLIVKEY), 0, 16);
$output = openssl_encrypt($data, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
return $output;
}
function decryptString($data){
$encrypt_method = "AES-256-CBC";
$key = hash('sha256', OSSLENCKEY);
$iv = substr(hash('sha256', OSSLIVKEY), 0, 16);
$output = openssl_decrypt(base64_decode($data), $encrypt_method, $key, 0, $iv);
return $output;
}
echo encryptString("Hello World");
echo "<br>";
echo decryptString("MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09");
?>
I have another endpoint which runs on nodejs where I need to decrypt and encrypt values based on the above php encrypt/decrypt rule.
I have searched but could'nt find a solution for this.
I tried with the library crypto But ends up with errors Reference
My nodejs code which I have tried is given below
message = 'MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09';
const cypher = Buffer.from(message, "base64");
const key = crypto.createHash('sha256').update('14E2E2D1582A36172AE401CB826003C1');//.digest('hex');
// $iv = substr(hash('sha256', '747E314D23DBC624E971EE59A0BA6D28'), 0, 16); from php returns '0ed9c2aa27a31693' need nodejs equivalent
const iv = '0ed9c2aa27a31693';
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
console.log( decipher.update(contents) + decipher.final());
Someone please help me to find a nodejs code for openssl encryption and decyption
Thanks in advance
There are the following problems in the code:
The key is returned hex encoded in the PHP code, so in the NodeJS code for AES-256 only the first 32 bytes must be considered for the key (PHP does this automatically).
The PHP code Base64 encodes the ciphertext implicitly, so because of the explicit Base64 encoding the ciphertext is Base64 encoded twice (which is unnecessary). Therefore, a double Base64 encoding is necessary in the NodeJS code as well.
Also, note that using a static IV is insecure (but you are probably only doing this for testing purposes).
The following NodeJS code produces the same ciphertext as the PHP code:
const crypto = require('crypto');
const plain = 'Hello World';
const hashKey = crypto.createHash('sha256');
hashKey.update('14E2E2D1582A36172AE401CB826003C1');
const key = hashKey.digest('hex').substring(0, 32);
const hashIv = crypto.createHash('sha256');
hashIv.update('747E314D23DBC624E971EE59A0BA6D28');
const iv = hashIv.digest('hex').substring(0, 16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
var encrypted = cipher.update(plain, 'utf-8', 'base64');
encrypted += cipher.final('base64');
encrypted = Buffer.from(encrypted, 'utf-8').toString('base64');
console.log(encrypted); // MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09
encrypted = Buffer.from(encrypted, 'base64').toString('utf-8');
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
var decrypted = decipher.update(encrypted, 'base64', 'utf-8');
decrypted += decipher.final('utf-8');
console.log(decrypted); // Hello World
EDIT:
As mentioned in the comments, PHP's hash() method returns the hash as a hexadecimal string by default (unless the third parameter is explicitly set to true, which is not the case in the reference code). This doubles the length, because in this encoding each byte of the hash is represented by two hex digits (hexits), i.e. 2 bytes.
Therefore it is necessary to shorten the key in the NodeJS code (see the first point of my original answer). This shortening is not necessary in the PHP code, since PHP does this implicitly (which is actually a design flaw, since this way the user does not notice a possible issue with the key).
The use of the hex string has two disadvantages:
With a hex encoded string, each byte consists of 16 possible values (0-15), as opposed to 256 possible values of a byte (0-255). This reduces the security from 256 bit to 128 bit (which is arithmetically equivalent to AES-128), see here.
Depending on the platform, the hexits a-f can be represented as lowercase or uppercase letters, which can result in different keys and IVs (without explicit agreement on one of the two cases).
For these reasons it is more secure and robust to use the raw binary data of the hash instead of the hex encoded strings. If you want to do this, then the following changes are necessary.
In the PHP code:
$key = hash('sha256', OSSLENCKEY, true);
$iv = substr(hash('sha256', OSSLIVKEY, true), 0, 16);
in the NodeJS code:
const key = hashKey.digest();
const iv = hashIv.digest().slice(0, 16)
Note, however, that this version is not compatible with the old one, i.e. encryptions before this change cannot be decrypted after the change. So the old data would have to be migrated.

PHP openssl MCRYPT_RIJNDAEL_128 equivalent

I have an encrypted value, which I know has been encrypted via the following obsolete php function:
$encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, trim($encryptedValue), MCRYPT_MODE_CBC, $iv);
I'm trying to decrypt this value using openssl_decrypt with this function :
$decryptedValue = openssl_decrypt("QTu07uBvWSJHmN7gqGIaJg==", 'aes-256-cbc', $key, $options = 0, $iv);
I know that the encryptedValue should return the value '1000' but the function don't work (return false)
What I did wrong ? Is the AES mode incorrect or something like that ?
I also tried this :
$encryptedValue = "QTu07uBvWSJHmN7gqGIaJg=="; // = "1000"
if (strlen($encryptedValue) % 8) {
$encryptedValue = str_pad($encryptedValue, strlen($encryptedValue) + 8 - strlen($encryptedValue) % 8, "\0");
}
$decryptedValue = openssl_decrypt($encryptedValue, 'aes-256-cbc', $key, $options = 0, $iv);
dd($decryptedValue);
But this function still return false with the dump.
I hope you've found a better solution in the months past, as this seems outdated, but for the sake of answering the question:
The correct cipher to use with OpenSSL depends on the keysize from your original code using mcrypt. Both AES-128 and AES-256 are variants of Rijndael-128, they just differ in key size. If you have a 128-bit (16-byte) key, then you have AES-128; if it's larger than that (and ideally exactly 256 bits), then you have AES-256.
Then, seeing that your cipherText is Base64-encoded, you need to either base64_decode() it before passing to openssl_decrypt() OR don't use OPENSSL_RAW_DATA - the only thing this flag does is to tell the function to not perform Base64 decoding itself.
And finally, yes, mcrypt will apply zero-padding, but that extra step you tried is just unnecessarily adding it again, just use OPENSSL_ZERO_PADDING while decrypting. So, you end up with something like this:
$cipher = (mb_strlen($key, '8bit') <= 8) ? 'aes-128-cbc' : 'aes-256-cbc';
$plainText = openssl_decrypt($encryptedValue, $cipher, $key, OPENSSL_ZERO_PADDING, $iv);
There are other possible variables, like the key also being encoded or not, the IV being prepended or appended to the cipherText already, etc, but with the information that you provided, this should be all you need to recover the data.

decrypt string in AES using php

I am currently working on encryption and decryption. I have encrypted my api key using https://medium.com/#amitasaurus/encrypting-decrypting-a-string-with-aes-js-1d9efa4d66d7 like below
var api_key = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
var d = new Date();
var n = d.getTime();
var final_key = api_key+'/'+n;
var encrypted = CryptoJS.AES.encrypt('encryption', final_key);
var encrypted_key = encrypted.toString();
and passed the encrypted key to the server side. I used
<?php
$key = pack("H*", "0123456789abcdef0123456789abcdef");
$iv = pack("H*", "abcdef9876543210abcdef9876543210");
$encrypted = base64_decode('U2FsdGVkX19gHSzwsrc5H9K6rqDYr2E8oYoVNSp8INU=');
$decrypt_string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv);
echo $decrypt_string;
?>
for decrypting the encrypted string. When i print decrypted string , it is like this ���9Һ��دa<��5*| աT�;��놻��V�[�}��ID-�}��硵�
Any suggestions to print as decoded string?
mcryptdefaults to zero padding. That means that, no matter what kind of ciphertext and key combination you are using, that the unpadding will not fail. Instead, it just returns invalid, randomized plaintext.
CryptoJS by default uses OpenSSL key derivation from a given password. Your decryption will return randomized plaintext as long as you cannot mimic the final AES key value that is generated by CryptoJS.
Modern modes such as GCM include an authentication tag with the ciphertext so that the validity of the ciphertext / key combination is ensured, or a verification error will be generated. Note that CBC mode is absolutely not secure when directly used for transport mode security.

PHP encrpyt, first 16 of string gone after decrypt with python

After encrypting with PHP 7, whenever I decrypt using Python 2, first 16 characters are gone..
IE:
before encryption:
THIS IS A TEXT THAT NEED TO BE ENCRYPTED AND ALSO NEED TO BE DECRYPT USING PYTHON (OR OTHER LANGAUGE).
after decryption:
HAT NEED TO BE ENCRYPTED AND ALSO NEED TO BE DECRYPT USING PYTHON (OR OTHER LANGAUGE)
PHP to encrypt
$method = 'AES-256-CBC';
$key = hash('sha256', 'password', true);
$ivlen = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivlen);
$enc = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
$encrypted = base64_encode($enc);
Python to decrypt
import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
class SomeClass(object):
def __init__(self, key):
self.bs = 32
self.key = hashlib.sha256(key.encode()).digest()
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
#staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]
so if I encrypt this in PHP (decrypting it on PHP works fine)
THIS IS A TEXT THAT NEED TO BE ENCRYPTED AND ALSO NEED TO BE DECRYPT USING PYTHON (OR OTHER LANGAUGE).
but i decrpyt it on Python, the result is missing first 16 parts of string
HAT NEED TO BE ENCRYPTED AND ALSO NEED TO BE DECRYPT USING PYTHON (OR OTHER LANGAUGE)

How to convert mcrypt_generic to openssl_encrypt?

this is my php code in PHP7.0.30
$key = strtoupper(md5('799ae002c7e940ef8a890b3a428f8f458e3f7c39d1cc2bf24390f0c46cf932c8'));
$text ='name=王星星&mobile=15212345678&idNumber=620402198709215456&bankName=招商银行&bankNum=6214830100799652';
$plaintext = $text;
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
//PKCS5Padding
$padding = $size - strlen($plaintext) % $size;
$plaintext .= str_repeat(chr($padding), $padding);
$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$iv = str_repeat("\0", $size);
/* Intialize encryption */
mcrypt_generic_init($module, base64_decode($key), $iv);
/* Encrypt data */
$encrypted = mcrypt_generic($module, $plaintext);
/* Terminate encryption handler */
mcrypt_generic_deinit($module);
mcrypt_module_close($module);
var_dump(base64_encode($encrypted));
/* openssl_encrypt */
$encrypted = openssl_encrypt($plaintext, 'AES-256-CBC',base64_decode($key), OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,$iv);
var_dump(base64_encode($encrypted));
mcrypt_generic output: 8LZZEXRhAfeeQOxF1iI9GpBcA2hSCelrUq2OimhSgZly6RfRonzGiE32vHh/JkdK+X5N5hFBMKz+iOmWAbgL9BIu2GIAxBIXCOusxFU4eDJ/5uy7F9vR9EE5NqOAiHBZhTP3pzMtEc0fLAzg8Tsngg==
but openssl_encrypt output:
g/YBzu+SGy9jfR+DIVY2S0iGM2O0QEs+J3IEv7bNAoz7+3iX9FboJZT0h+OH6uUeQBoSsD+eAga69U5C86Ibcp5Q2ay1FzfDFG/eGBtUaAJxRBwhxiNeBxPw2jBar2fR42wZUZOGTjlT5Ujgz+s/Iw==
I don't know how to convert mcrypt_generic to openssl_encrypt , thanks!
You need a 256 bit key. Your current "key" is 32 bytes which is 256 bits.
You problem is that your are decoding the key. When you decode the key you are reducing the key's size to 24 bytes or 192 bits.
So you have two options.
Increase the key size by another 8 bytes before you base64_encode() it.
OR
Just remove the base64_decode() functions.
Secondly, I hope you are using the md5 for testing purposes and not for your application. The md5 hash is not suited to give you a hash that is designed for modern cryptography.
You should use something like $key = openssl_random_pseudo_bytes($size); to generate a key with.
I would also like to point you in the direction of the Libsodium library. It is now native on the latest version of PHP.
UPDATED
If your goal is to have AES encryption with a 256 bit key like your code suggest, then you will have to do what I stated above. If that is not one of your requirements then the only thing you have to do is change the AES-256-CBC to AES-192-CBC in the openssl_encrypt() function like so.
$encrypted = openssl_encrypt($plaintext, 'AES-192-CBC', base64_decode($key), OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
One thing to point out is that the "128" in the name MCRYPT_RIJNDAEL_128 refers to the block size not the key size, where as the "256" in AES-256-CBC actually refers to the key size. Which is why the AES-192-CBC will work for your current key size of 192 bits. AES encryption uses a standard of a 128 bit block size, so that is compatible the MCRYPT_RIJNDAEL_128 block size.
Cheers

Categories