I am trying to get a php equivalent of this node function to be able to encrypt some data in php before decrypting it in node
/**
* Encrypts an object with aes-256-cbc to use as a token
* #param {any} data An object to encrypt
* #param {string} secret The secret to encrypt the data with
* #returns {string}
*/
static encrypt(data, secret) {
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-cbc', secret, iv);
return `${cipher.update(JSON.stringify(data), 'utf8', 'base64') + cipher.final('base64')}.${iv.toString('base64')}`;
}
This is the php function I have come up with, along with the necessary helper functions
/**
* Encrypts an object with aes-256-cbc to use as a token
* #param $data An object to encrypt
* #param $secret The secret to encrypt data with
* #return \Illuminate\Http\Response
*/
public function encrypt($data, $secret)
{
$method = 'AES-256-CBC';
$data = static::getPaddedText($data);
$iv = static::generateIv();
$ciphertext = openssl_encrypt(json_encode($data), $method, $secret, OPENSSL_RAW_DATA, $iv);
$ciphertext_64 = base64_encode(utf8_encode($ciphertext));
$iv_64 = base64_encode($iv);
return "$ciphertext_64.$iv_64";
}
private static function getPaddedText(string $plainText): string
{
$blocksize = 8;
$textLength = strlen($plainText);
if ($textLength % $blocksize) {
$plainText = str_pad($plainText, $textLength + $blocksize - $textLength % $blocksize, "\0");
}
return $plainText;
}
public static function generateIv(): string
{
$ivLength = 16;
$success = false;
$random = openssl_random_pseudo_bytes($ivLength, $success);
if (!$success) {
$random = random_bytes($ivLength);
}
return $random;
}
But I dont think it is exactly the same as when decrypting it with this function
/**
* Decrypts an object with aes-256-cbc to use as a token
* #param {string} token An data to decrypt
* #param {string} secret The secret to decrypt the data with
* #returns {any}
*/
static decrypt(token, secret) {
const [data, iv] = token.split('.');
const decipher = createDecipheriv('aes-256-cbc', secret, Buffer.from(iv, 'base64'));
return JSON.parse(decipher.update(data, 'base64', 'utf8') + decipher.final('utf8'));
}
I get this error EVP_DecryptFinal_ex:wrong final block length
Looks like you aren't padding your text with nulls to ensure you have block sizes that are exactly eight characters long. In pother words, your string needs to be a multiple of eight in length. Padding your string with nulls will accomplish that for you without changing its value.
class AES256Encryption
{
public const BLOCK_SIZE = 8;
public const IV_LENGTH = 16;
public const CIPHER = 'AES-256-CBC';
public static function generateIv(): string
{
$success = false;
$random = openssl_random_pseudo_bytes(static::IV_LENGTH, $success);
if (!$success) {
$random = random_bytes(static::IV_LENGTH);
}
return $random;
}
private static function getPaddedText(string $plainText): string
{
$textLength = strlen($plainText);
if ($textLength % static::BLOCK_SIZE) {
$plainText = str_pad($plainText, $textLength + static::BLOCK_SIZE - $textLength % static::BLOCK_SIZE, "\0");
}
return $plainText;
}
public static function encrypt(string $plainText, string $key, string $iv): string
{
$plainText = static::getPaddedText($plainText);
return base64_encode(openssl_encrypt($plainText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv));
}
public static function decrypt(string $encryptedText, string $key, string $iv): string
{
return openssl_decrypt(base64_decode($encryptedText), static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
}
}
Usage:
$key = 'secretkey';
$iv = AES256Encryption::generateIv();
$text = 'The quick brown fox jumps over the lazy dog';
$encryptedText = AES256Encryption::encrypt($text, $key, $iv);
$decryptedText = AES256Encryption::decrypt($encryptedText, $key, $iv);
printf('Original Text: %s%s', $text, PHP_EOL);
printf('Encrypted : %s%s', $encryptedText, PHP_EOL);
printf('Decrypted : %s%s', $decryptedText, PHP_EOL);
Output:
Original Text: The quick brown fox jumps over the lazy dog
Encrypted : 1J+0tGHzn67WA7kJGjp9malmv0w+tcl0t8YnC+F9Y9IZWMDeBH4+k9TyuznF77cWe1SGUWa6Pb6r/i0xmH+ajg==
Decrypted : The quick brown fox jumps over the lazy dog
Your usage:
// Your encoding usage:
$key = 'secret';
$iv = AES256Encryption::generateIv();
$text = 'The quick brown fox jumps over the lazy dog';
$encryptedText = AES256Encryption::encrypt($text, $key, $iv);
$finalString = $encryptedText . bin2hex($iv);
// Your decoding usage:
$iv = hex2bin(substr($finalString, -32, 32));
$decryptedText = AES256Encryption::decrypt($encryptedText, $key, $iv);
printf('Original Text : %s%s', $text, PHP_EOL);
printf('Encrypted : %s%s', $encryptedText, PHP_EOL);
printf('Encrypted w/Iv: %s%s', $finalString, PHP_EOL);
printf('Decrypted : %s%s', $decryptedText, PHP_EOL);
Output:
Original Text : The quick brown fox jumps over the lazy dog
Encrypted : +JsjXFPdyTycYrc2fOb1edLKNksEUHzEGxwRewBlA8yxBWZZV2y/EVIwk0g9g17dtxAaTGZvYaf2y9+oTCvLWg==
Encrypted w/Iv: +JsjXFPdyTycYrc2fOb1edLKNksEUHzEGxwRewBlA8yxBWZZV2y/EVIwk0g9g17dtxAaTGZvYaf2y9+oTCvLWg==54993615360ff641a952a553e1500568
Decrypted : The quick brown fox jumps over the lazy dog
Generating the IV can be more robust as random_bytes() can throw an exception.
Related
I use the following functions to encrypt/decrypt with NodeJS, they work fine. But I was unable to decrypt the data with PHP to use in some part of the same project.
NodeJS:
function encrypt(text){
var cipher = crypto.createCipher('aes-256-cbc','strong-key')
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher('aes-256-cbc','strong-key')
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
What I tried with PHP is:
openssl_decrypt('fdf32748aa4ce37fc600bbe7be14bfc7', 'AES-256-CBC', "strong-key");
But it keeps returning false/empty. I appreciate healping me to know what I am doing wrong.
Edit:
For example, decrypting 28e1dfdedac467a015a9c8720d0a6451 with PHP should return "Hello World", using the same key as above.
Make sure that your incoming data is the correct format (ie doesn't have any extra layers of encoding). It looks like hex but it's not what openssl_decrypt necessarily expects.
Here's a back and forth PHP example (using an IV which you should too):
$data = 'hello this is some data';
$key = 'this is a cool and secret password';
$iv = random_bytes(16);
$encrypted = openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv);
echo 'iv (as hex): ', bin2hex($iv), PHP_EOL;
echo 'encrypted: ', $encrypted, PHP_EOL; // note this is not hex
$decrypted = openssl_decrypt($encrypted, 'aes-256-cbc', $key, 0, $iv);
echo 'decrypted: ', $decrypted, PHP_EOL;
$ php test.php
iv (as hex): 02c00788438518f241cb86dc90237102
encrypted: oRZAXMjNle6hkJ9rTHTeUl5VoHQol+020Q/iFnbgbeU=
decrypted: hello this is some data
Edit, even more specific example, highlighting the importance of knowing your encodings:
// test.js
const crypto = require('crypto');
let data = 'hello this is some data';
const key = crypto.scryptSync('Password used to generate key', '', 32); // 256 / 8 = 32
const iv = crypto.randomBytes(16); // Initialization vector.
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let crypted = cipher.update(data,'utf8','hex')
crypted += cipher.final('hex');
console.log('data: ', data);
console.log('key: ', key.toString('hex')); // key: 9266bc531befd01b6a55c232fa0efeb35625079e7024758b2e65d0dd72fe59df
console.log('crypted (as hex): ', crypted); // crypted (as hex): b571d864da0680d77e4880d0071b49e456a1eead4b1cbfa42a9337965a466362
console.log('iv (as hex): ', iv.toString('hex')); // iv (as hex): 788ac1dcee25824b713b5201d07cc133
Here we know all our outputs are hex, so we can re-format them to binary data on the PHP side:
// test.php
$iv = hex2bin( '788ac1dcee25824b713b5201d07cc133' );
$encrypted = hex2bin( 'b571d864da0680d77e4880d0071b49e456a1eead4b1cbfa42a9337965a466362' );
$key = hex2bin('9266bc531befd01b6a55c232fa0efeb35625079e7024758b2e65d0dd72fe59df');
$decrypted = openssl_decrypt($encrypted, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
echo 'decrypted: ', $decrypted, PHP_EOL; // hello this is some data
Final Edit: Now with working key derivation implementation. This successfully decrypts your 'Hello world':
$password = 'strong-key';
// derrive key and IV using function from SO, which implements same method node uses
$ar = deriveKeyAndIV($password, 1, 'aes-256-cbc', 'md5');
$key = $ar['key'];
$iv = $ar['iv'];
$decrypted = openssl_decrypt(hex2bin('28e1dfdedac467a015a9c8720d0a6451'), 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
echo 'decrypted: ', $decrypted, PHP_EOL; // Hello world
function deriveKeyAndIV($data,$count,$cipher,$digest) {
$ivlen = openssl_cipher_iv_length($cipher);
$keylen = 32;
$hash = "";
$hdata = "";
while(strlen($hash) < $keylen+$ivlen) {
$hdata .= $data;
$md_buf = openssl_digest($hdata, $digest);
//
for ($i = 1; $i < $count; $i++) {
$md_buf = openssl_digest ( hex2bin($md_buf),$digest);
}
$hdata = hex2bin($md_buf);
$hash .= $hdata;
}
//
$key = substr($hash,0,$keylen);
$iv = substr($hash,$keylen,$ivlen);
//
return array('key' => $key, 'iv' => $iv);
}
I am trying to decode a simple JSON string and the format to make it look correct. I actually copied the string and decode is with the same algorithm but without all the extra code and it worked fine.
print_r(json_decode('{"user_id":1,"issused":"2016-02-24 04:40:17","expire":"2016-03-02 04:40:17"}'));
That worked. But when I do
$hash = Hash::salt(32);
$issused = date('Y-m-d H:i:s');
$expire = date('Y-m-d H:i:s', strtotime('+1 week'));
$data = array('user_id' => 1, 'issused' => $issused, 'expire' => $expire);
$encrypt = Cipher::encrypt(json_encode($data), $hash);
$decrypt = Cipher::decrypt($encrypt, $hash);
echo $encrypt;
echo "<br><br>";
echo $decrypt;
echo "<br><br>";
print_r(json_decode($decrypt));
Where $decrypted is the valid formated JSON that I posted above. When I used:
echo json_last_erro();
It gave me an output of 3 which is JSON_ERROR_CTRL_CHAR
Any idea why this isn't being decoded correctly?
EDIT
Here is how I am encrypting data.
class Cipher {
public static function encrypt($string, $hash) {
$size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
$encrypted = mcrypt_encrypt(MCRYPT_BLOWFISH, $hash, utf8_encode($string), MCRYPT_MODE_ECB, $iv);
//$encoded = urlencode($encrypted);
$encoded = base64_encode($encrypted);
return $encoded;
}
public static function decrypt($string, $hash) {
//$decoded = urldecode($string);
$decoded = base64_decode($string);
$size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
$decrypted = mcrypt_decrypt(MCRYPT_BLOWFISH, $hash, $decoded, MCRYPT_MODE_ECB, $iv);
return $decrypted;
}
}
Here how I am creating the salt.
public static function salt($length) {
return mcrypt_create_iv($length); //base64_encode(openssl_random_pseudo_bytes($length));
}
The extra control characters (\0) are due to the cypher block padding. From the mcrypt_decrypt docs
data
The data that will be decrypted with the given cipher and mode. If
the size of the data is not n * blocksize, the data will be padded
with '\0'.
You can pad the input for the block size yourself in the encrypt and then remove the extra padding in decrypt() or you can trim the trailing zero bytes from the decoded message doing the below.
$decrypt = trim($decrypt, "\0");
Here are my encrypt and decrypt functions
public function encrypt($text){
$key = hash("md5", KEY);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_TWOFISH, MCRYPT_MODE_CBC), MCRYPT_RAND);
$result = base64_encode(mcrypt_encrypt(MCRYPT_TWOFISH, $key, $text, MCRYPT_MODE_CBC, $iv));
return $result;
}
public function decrypt($text){
$key = hash("md5", KEY);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_TWOFISH, MCRYPT_MODE_CBC), MCRYPT_RAND);
$result = trim(mcrypt_decrypt(MCRYPT_TWOFISH, $key, base64_decode($text), MCRYPT_MODE_CBC, $iv));
return $result;
}
When encryption is run on a JSON string to be stored as a text file and then retrieved and decrypted the front section of the resulting string has replacement and/or incorrect characters:
Expected:
{"players":[{"label":"...
Actual:
�Ӹ�!G#${�W�Rՙ�bel":"...
If it makes any difference the actual placement/incorrect chars are different each time I refresh the page on the same file.
In case anyone comes across this...
The IV needs to be prepended to the file before encoding, like so:
public function encrypt($text){
$key = hash("md5", KEY);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_TWOFISH, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);
$result = base64_encode($iv.mcrypt_encrypt(MCRYPT_TWOFISH, $key, $text, MCRYPT_MODE_CBC, $iv));
return $result;
}
Then when decrypting take the IV from the decoded string and use it to decrypt, like so:
public function decrypt($text){
$key = hash("md5", KEY);
$decode = base64_decode($text);
$iv = substr($decode, 0, 16);
$decrypt = substr($decode, 16);
$result = mcrypt_decrypt(MCRYPT_TWOFISH, $key, $decrypt, MCRYPT_MODE_CBC, $iv);
return $result;
}
Hi and good day to all members, admin and to everyone. I would like to ask a question that has a connection from my previous post which can be seen here entitled Crypto-Js different output from mcrypt Upon chage of data to encrypt. Now my question is I made another php function that will eventually call this function stated in the link. See below the basic php function I created.
function login($word,$word2)
{
$word = mcrypts_encrypt($word);
$word2 = mcrypts_encrypt($word2);
return $word;
return $word2;
}
Now my question is this, I have tried placing the $word and the $word 2 with real data such as CROW and Blader but It only echoes the encrypted word of CROW ($word) and not Blader ($w0rd2).
For reference purpose I will also include the script for the encrypt.
MCRYPT_ENCRYPT
function mcrypts_encrypt($encrypted)
{
//Padding 6/25/2014
$pad = 16 - (strlen($encrypted) % 16);
$encrypted = $encrypted . str_repeat(chr($pad), $pad);
//Encrypt//Decode
$iv = base64_decode('AAAAAAAAAAAAAAAAAAAAAA==');
$key = base64_decode('ITU2NjNhI0tOc2FmZExOTQ==');
$plaintext = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv );
//Return encrypted Data
return base64_encode($plaintext);
}
Thanks for the help in advance.
You can only call return from a function once, at that point, the flow of code is returned back to the caller.
To pass multiple values back to the caller, return an array containing both of the values, e.g.
function login($word,$word2)
{
$word = mcrypts_encrypt($word);
$word2 = mcrypts_encrypt($word2);
return array($word, $word2);
}
and use as this;
$encrypted = login('first-word', 'second-word');
echo $encrypted[0]; // the first word, encrypted
echo $encrypted[1]; // the second word, encrypted
function login($word,$word2)
{
$word = mcrypts_encrypt($word);
$word2 = mcrypts_encrypt($word2);
$returnArray["user"] = $word;
$returnArray["pass"] = $word2;
return $returnArray;
}
function call
$loginValues = login('CROW','Blader');
extract($loginValues);
print $user; // prints $word
print $pass; // prints $word2
This works
<?php
function login($word,$word2)
{
$word = mcrypts_encrypt($word);
$word2 = mcrypts_encrypt($word2);
return array($word, $word2);
}
function mcrypts_encrypt($encrypted)
{
//Padding 6/25/2014
$pad = 16 - (strlen($encrypted) % 16);
$encrypted = $encrypted . str_repeat(chr($pad), $pad);
//Encrypt//Decode
$iv = base64_decode('AAAAAAAAAAAAAAAAAAAAAA==');
$key = base64_decode('ITU2NjNhI0tOc2FmZExOTQ==');
$plaintext = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv );
//Return encrypted Data
return base64_encode($plaintext);
}
var_dump(login("test1", "test2"));
?>
outputs:
array(2) {
[0]=>
string(24) "eeyZfxyUnMykJ23fMamEBQ=="
[1]=>
string(24) "0egb4dfuXbgFg7GzuuBZcQ=="
}
I have the following c# code that generates keys:
public static byte[] Encrypt(byte[] plainData, string salt)
{
DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
DES.Key = ASCIIEncoding.ASCII.GetBytes(salt);
DES.IV = ASCIIEncoding.ASCII.GetBytes(salt);
ICryptoTransform desencrypt = DES.CreateEncryptor();
byte[] encryptedData = desencrypt.TransformFinalBlock(plainData, 0, plainData.Length);
return encryptedData;
}
private string GetEncryptedKey(string key)
{
return BitConverter.ToString(KeyGeneratorForm.Encrypt(ASCIIEncoding.ASCII.GetBytes(key), "abcdefgh")).Replace("-", "");
}
I'm trying to perform the same thing in PHP:
function get_encrypted_key($key){
$salt = "abcdefgh";
return bin2hex(mcrypt_encrypt(MCRYPT_DES, $salt, $key, MCRYPT_MODE_CBC, $salt));
}
However, there is a small discrepency in the results, as the last 16 chars are always different:
With key "Benjamin Franklin":
C# : 0B3C6E5DF5D747FB3C50DE952FECE3999768F35B890BC391
PHP: 0B3C6E5DF5D747FB3C50DE952FECE3993A881F9AF348C64D
With key "President Franklin D Roosevelt":
C# : C119B50A5A7F8C905A86A43F5694B4D7DD1E8D0577F1CEB32A86FABCEA5711E1
PHP: C119B50A5A7F8C905A86A43F5694B4D7DD1E8D0577F1CEB37ACBE60BB1D21F3F
I've also tried to perform the padding transform to my key using the following code:
function get_encrypted_key($key){
$salt = "abcdefgh";
$extra = 8 - (strlen($key) % 8);
if($extra > 0) {
for($i = 0; $i < $extra; $i++) {
$key.= "\0";
}
}
return bin2hex(mcrypt_encrypt(MCRYPT_DES, $salt, $key, MCRYPT_MODE_CBC, $salt));
}
But I end up with the same results as without padding.
If you have any clue as to what's going on, I'd be glad to hear about it! :)
Thanks
You mentioned trying a "classic" padding snippet. The following quick adaptation of the snippet posted on the mcrypt_encrypt documentation gives the same results you were getting from C#.
PKCS #7 (the default padding scheme used by C#'s SymmetricAlgorithm) pads with bytes where each padding byte's value is the same as the number of bytes of padding, not with zero bytes.
function get_encrypted_key($key)
{
$salt = 'abcdefgh';
$block = mcrypt_get_block_size('des', 'cbc');
$pad = $block - (strlen($key) % $block);
$key .= str_repeat(chr($pad), $pad);
return bin2hex(mcrypt_encrypt(MCRYPT_DES, $salt, $key, MCRYPT_MODE_CBC, $salt));
}
Test output:
php > echo get_encrypted_key('Benjamin Franklin');
0b3c6e5df5d747fb3c50de952fece3999768f35b890bc391
php > echo get_encrypted_key('President Franklin D Roosevelt');
c119b50a5a7f8c905a86a43f5694b4d7dd1e8d0577f1ceb32a86fabcea5711e1