I'm trying to use PHP's openssl_encrypt() function but my key is coded in hexadecimal and the function is returning an error. When using the hex2bin() function to convert the key to binary, the return value is garbled ASCII text. Then when inserted into openssl_encrypt(). I get an error.
define('TEX_ENCRYPTION_KEY', 'hexadecimalkey...');
define('TEX_ENCRYPTION_IV', 'hexadecimalkey...');
$key = hex2bin(TEX_ENCRYPTION_KEY);
$iv = hex2bin(TEX_ENCRYPTION_IV);
$transData = '<Detail>blah blah blah</Detail>';
$alg = 'aes-256-cbc';
$encryptedData = openssl_encrypt(
$transData,
$alg,
$key,
OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING,$iv
);
This outputs an error:
error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:data
not multiple of block length
Any idea what is going on here?
Though it's not in the official documentation, there's a pretty good explanation of what the OPENSSL_ZERO_PADDING option does in the comments. By default, OpenSSL will pad your plaintext to a multiple of the cipher block size (16 bytes in the case of AES-256-CBC.) However, you've disabled that mechanism and OpenSSL is expecting you to ensure the length of your data is a multiple of 16. It's not, so you get the error message "data not multiple of block length."
Solution: pad your data or remove that option!
<?php
$transData = '<Detail>blah blah blah</Detail>';
$transData = str_pad(
$transData,
strlen($transData) + (16 - (strlen($transData) % 16)),
chr(0)
);
After dancing with the openssl documentation I had the solution to replace depreciated Mcrypt function with openssl (openssl_encrypt and openssl_decrypt functions) and return ASCII text with base64_encode():
//Return encrypted string
public function stringEncrypt ($plainText, $cryptKey = '7R7zX2Urc7qvjhkr') {
$length = 8;
$cstrong = true;
$cipher = 'aes-128-cbc';
if (in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt(
$plainText, $cipher, $cryptKey, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $cryptKey, $as_binary=true);
$encodedText = base64_encode( $iv.$hmac.$ciphertext_raw );
}
return $encodedText;
}
//Return decrypted string
public function stringDecrypt ($encodedText, $cryptKey = '7R7zX2Urc7qvjhkr') {
$c = base64_decode($encodedText);
$cipher = 'aes-128-cbc';
if (in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ivlenSha2len = $ivlen+$sha2len;
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$plainText = openssl_decrypt(
$ciphertext_raw, $cipher, $cryptKey, $options=OPENSSL_RAW_DATA, $iv);
}
return $plainText;
}
Related
I am receiving the following error when on decrypting certain data.
openssl_decrypt(): IV passed is only 12 bytes long, cipher expects an IV of precisely 16 bytes
I am also seeing the warning: hash_equals(): Expected known_string to be a string, boolean given.. perhaps some corrupted data.
I recently decrypted from mcrypt and re-encrypted with openssl (this code). I see I should be passing OPENSSL_RAW_DATA
here is my class:
class AES{
function __construct() {
$this->aes_256_cbc = 'aes-256-cbc';
}
function __destruct() {}
function encrypt($data,$passphrase){
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->aes_256_cbc));
$dataEncrRaw = openssl_encrypt($data, $this->aes_256_cbc, $passphrase, 0, $iv);
$hmac = hash_hmac('sha256', $dataEncrRaw, $passphrase, $as_binary=true);
return base64_encode( $iv.$hmac.$dataEncrRaw );
}
function decrypt($dataEncr,$passphrase){
$data = '';
$dataEncr = base64_decode($dataEncr);
$ivlen = openssl_cipher_iv_length($cipher=$this->aes_256_cbc);
$iv=substr($dataEncr,0,$ivlen);
$hmac = substr($dataEncr, $ivlen, $sha2len=32);
$dataEncrRaw=substr($dataEncr,$ivlen+$sha2len);
$dataPlainText = openssl_decrypt($dataEncrRaw, $this->aes_256_cbc, $passphrase, 0, $iv);
$calcmac = hash_hmac('sha256', $dataEncrRaw, $passphrase, $as_binary=true);
if (hash_equals($hmac, $calcmac)){
$data=$dataPlainText;
}
return $data;
}
}
Thanks,
Drew
I'm using PHP 7.1, studying encryption/decryption topic. I use this functions to enc/dec (based on PHP's official doc):
$key = openssl_random_pseudo_bytes(16);
function encryptName($plaintext) {
global $key;
// $plaintext - string which must be encrypted
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );
return $ciphertext;
}
function decryptName($ciphertext) {
global $key;
// $ciphertext - encrypted string
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key,
$options=OPENSSL_RAW_DATA, $iv); // | OPENSSL_ZERO_PADDING
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
if (hash_equals($hmac, $calcmac)) {
//echo $original_plaintext."\n";
}
echo openssl_error_string();
return $original_plaintext;
}
When I enc/dec strig "MyTestPhrase" both functions work well. But when I encrypt data and then write it in MySQL table decryption fails with this error code:
error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
and my $original_plaintext equals bool(false).
I think in this way. AES works with blocks. Decrypted string must be appropriate to block lengt: size of decrypting data must be multiple to 16. If it is not we have to activate PHP option which fullfills it with 0es.
Guess that problem could be with MySQL data format and with encrypted string length, but can't catch it.
Please help me with question posted above.
So in my example I created a base64_encoded string using the pseudo_bytes. That way your key is constant. You can create your own key but for this ex we will use this one. LoPCPKd8iDxHvb8mATzhhg==
Next we will define the key as a constant. This can be done at the top of your script or in a conf.php file.
Next we will use the constant value anywhere you need the key.
Like so:
define("MYKEY_", base64_decode('LoPCPKd8iDxHvb8mATzhhg=='));
function encryptName($plaintext) {
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, MYKEY_, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, MYKEY_, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );
return $ciphertext;
}
function decryptName($ciphertext) {
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, MYKEY_,
$options=OPENSSL_RAW_DATA, $iv); // | OPENSSL_ZERO_PADDING
$calcmac = hash_hmac('sha256', $ciphertext_raw, MYKEY_, $as_binary=true);
if (hash_equals($hmac, $calcmac)) {
//echo $original_plaintext."\n";
}
echo openssl_error_string();
return $original_plaintext;
}
As you guys probably know, the extension mcrypt will be deprecated on php 7.1.
I use to maintain a "legacy" application that I want to migrate eventually to this version so I ran the tests and verified that I can't get 100% of coverage anymore, since there's a piece of code that use the following code:
$key = 'sA*(DH';
// initialization vector
$iv = md5(md5($key));
$output = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, $iv));
I tried to port this piece of code to openssl_encrypt using this code
$key = md5('sA*(DH');
$iv = md5($key);
echo base64_encode(openssl_encrypt($data, "aes-256-cbc", $key, OPENSSL_RAW_DATA, $iv));
But I have 2 problems with this:
The IV lenght should be 16 chars (and md5 gives me 32), so I get a PHP Warning
The output it's not the same (even if I truncate to 16 chars)
Anyone had similar problems (or know how to fix it?)
BTW: I'm using the dev master version of PHP (supposed to be 7.1.0 alpha 3).
Yet another tested solution taking and returning ANSI text to replace Mcrypt function with the openssl_encrypt() and openssl_decrypt():
//Return encrypted string
public function stringEncrypt ($plainText, $cryptKey = '7R7zX2Urc7qvjhkr') {
$cipher = 'aes-128-cbc';
if (in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt(
$plainText, $cipher, $cryptKey, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $cryptKey, $as_binary=true);
$encodedText = base64_encode( $iv.$hmac.$ciphertext_raw );
}
return $encodedText;
}
//Return decrypted string
public function stringDecrypt ($encodedText, $cryptKey = '7R7zX2Urc7qvjhkr') {
$c = base64_decode($encodedText);
$cipher = 'aes-128-cbc';
if (in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ivlenSha2len = $ivlen+$sha2len;
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$plainText = openssl_decrypt(
$ciphertext_raw, $cipher, $cryptKey, $options=OPENSSL_RAW_DATA, $iv);
}
return $plainText;
}
More read in openssl documentation
You should really get out of the habit of using md5 for anything.
$iv = openssl_random_pseudo_bytes(16);
$key = substr(hash('sha256', 'sA*(DH'), 0, 32)
mcrypt_encrypt and openssl_encrypt will not output the same crypttext given the same plaintext and key.
also, mcrypt is deprecated in PHP 7.1, not removed...so you can update to 7.1 without changing from mcrypt to openssl ... but it is a good idea to remove mcrypt in general.
There are 2 problems :
MCrypt uses zero padding while Openssl uses by default PKCS#7
Openssl needs the input string to be of proper length (multiple of block length)
To solve this problems :
add OPENSSL_ZERO_PADDING flag to openssl_encrypt/openssl_decrypt
if input string length is not multiple of block length then append to the input string zero chars "\0" [aka chr(0)];
That being said this should solve the problem:
// key/iv in ASCII binary data, $str base64
function decrypt_stuff($key, $str, $iv) {
// $plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($str), MCRYPT_MODE_CBC, $iv);
$plaintext_dec = openssl_decrypt(base64_decode($str), "aes-256-cbc", $key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv);
return $plaintext_dec;
}
// key/iv in ascii binary data, $str ascii
function encrypt_stuff($key, $str, $iv) {
// $ciphertext = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_CBC, $iv));
if (($l = (strlen($str) & 15)) > 0) { $str .= str_repeat(chr(0), 16 - $l); }
$ciphertext = base64_encode(openssl_encrypt($str, "aes-256-cbc", $key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv));
return $ciphertext;
}
m using
public function encrypt($plain_str,$key)
{
$str= mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plain_str, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND));
$str = urlencode(base64_encode($str));
return $str ;
}
public function decrypt($cipher_str,$key)
{
$str = urldecode(base64_decode($cipher_str));
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND));
}
on crypting :201433~58~g#fds.com~20140820142427
i get : %2BAihYMLwpwrsmL4lSGGzwFTfonvdCyOb%2BCGEUJ%2F%2BE%2F7ZnvgwFRYFtlazQeSrVjUjyaaGZADK8%2BZyynIGxyt4VQ%3D%3D
on decrypting : %2BAihYMLwpwrsmL4lSGGzwFTfonvdCyOb%2BCGEUJ%2F%2BE%2F7ZnvgwFRYFtlazQeSrVjUjyaaGZADK8%2BZyynIGxyt4VQ%3D%3D
i get :201433~58~g#fds.com~20140820142427 back but
when string is malformed like some character removed
like this : %2BAihYMLwpwrsmL4lSGGzwFTfonvdCyOb%2BCGEUJ%2F%2BE%2F7Z
on decrypting i get : 201433~58~g#fds.com~201408201424O#¿W«Gݽˋ¯ È#'oP´ŸØw\Â⦑
How can i detect this anomoly ?
First of all, I'd like to list some flaws in your code:
Don't use ECB mode.
You are encrypting using MCRYPT_RIJNDAEL_128, but you're getting the IV size for MCRYPT_RIJNDAEL_256. (btw, IV is ignored in ECB mode, which is one of the reasons why not to use it)
You are also using MCRYPT_RAND as your randomness source, which is not secure. You should use MCRYPT_DEV_URANDOM (that is also the new default in PHP 5.6).
You don't have to urlencode() the resulting ciphertext, Base64 encoding is URL-safe.
Now, to answer your question ... this is done via a HMAC. The easiest way to use a HMAC is to prepend the cipher-text with it (which you should do with the IV as well; don't worry, it's not a secret):
public function encrypt($plainText, $encKey, $hmacKey)
{
$ivSize = mcrypt_get_iv_size('rijndael-128', 'ctr');
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
$cipherText = mcrypt_encrypt('rijndael-128', $encKey, $plainText, 'ctr', $iv);
$cipherText = $iv.$cipherText;
$hmac = hash_hmac('sha256', $cipherText, $hmacKey, true);
return base64_encode($hmac.$cipherText);
}
public function decrypt($cipherText, $encKey, $hmacKey)
{
$cipherText = base64_decode($cipherText);
if (strlen($cipherText) <= 32)
{
throw new Exception('Authentication failed!');
}
$recvHmac = substr($cipherText, 0, 32);
$cipherText = substr($cipherText, 32);
$calcHmac = hash_hmac('sha256', $cipherText, $hmacKey, true);
if ( ! hash_equals($recvHmac, $calcHmac))
{
throw new Exception('Authentication failed!');
}
$ivSize = mcrypt_get_iv_size('rijndael-128', 'ctr');
$iv = substr($cipherText, $ivSize);
$cipherText = substr($cipherText, $ivSize);
return mcrypt_decrypt('rijndael-128', $encKey, $cipherText, 'ctr', $iv);
}
Please note that the encryption key and HMAC key are different - they most NOT be the same key. Also, for Rijndael-128, you should create a 128-bit (or 16-byte) random key, it is not something that you can just type in with your keyboard. Here's how to generate one:
$encKey = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
i want to decrypt magento data using the data encrypted and config key to show the data as plan test
i tried alotof ways but no one has done with me is there any way
i mean is there any way as php script to do it
and thanks
i used this code i found here but doesn't show anything
<?php
class Encryption
{
const CIPHER = MCRYPT_RIJNDAEL_128; // Rijndael-128 is AES
const MODE = MCRYPT_MODE_CBC;
/* Cryptographic key of length 16, 24 or 32. NOT a password! */
private $key;
public function __construct($key) {
$this->key = $key;
}
public function encrypt($plaintext) {
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_RANDOM);
$ciphertext = mcrypt_encrypt(self::CIPHER, $this->key, $plaintext, self::MODE, $iv);
return base64_encode($iv.$ciphertext);
}
public function decrypt($ciphertext) {
$ciphertext = base64_decode($ciphertext);
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
if (strlen($ciphertext) < $ivSize) {
throw new Exception('Missing initialization vector');
}
$iv = substr($ciphertext, 0, $ivSize);
$ciphertext = substr($ciphertext, $ivSize);
$plaintext = mcrypt_decrypt(self::CIPHER, $this->key, $ciphertext, self::MODE, $iv);
return rtrim($plaintext, "\0");
}
}
In simplest case, when we use all of standard Magento settings:
Encryptor get from model core/encryption,
Key from setting global/crypt/key
Using Mcrypt
With standard cipher MCRYPT_BLOWFISH and mode MCRYPT_MODE_ECB
(all given for Magento 1.8.1)
$encrypted = 'R4VQyYn6JHs=';
$key = '370ee4d319aebb395b982d72190588d2';
$cipher = MCRYPT_BLOWFISH;
$mode = MCRYPT_MODE_ECB;
$handler = mcrypt_module_open($cipher, '', $mode, '');
$initVector = mcrypt_create_iv (mcrypt_enc_get_iv_size($handler), MCRYPT_RAND);
mcrypt_generic_init($handler, $key, $initVector);
var_dump(str_replace("\x0", '', trim(mdecrypt_generic($handler, base64_decode($encrypted)))));
However, I can't see a point in using this, since you can use Magento, and just call
Magento::helper('core')->decrypt($encrypted);