PHP's mcrypt_encrypt and openssl command line output different cipher - php

I am new to encryption. I want to encode a string with AES 128bit encryption. I can do this in PHP:
$key = 'Hello';
$plain = 'Hello Hello Hello Hello';
$cipher = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plain, MCRYPT_MODE_CBC);
echo base64_encode($cipher);
This outputs:
bzXdTNochlsQwpR9hzSSS6ihG+MYIZIDZZlF85pIXlQ=
I tried the same with openssl command line:
openssl enc -aes-128-cbc -a -nosalt -in plain.txt -out encrypted.enc -pass pass:Hello
And the string saved in encrypted.enc is:
5apwiN8MdAuJ9nEW82XMyR0H3VKpI/vWc7xV2iVjCTE=
Why is it different?
The reason why I am trying to get the same output with both PHP and command line openssl is because I will have two separate web services communicating together. One service will have PHP available so I can use that but the other one will not be using PHP so I will probably have to use openssl in command line.

You mixed up password and key. Add a -p to your openssl command line to see the actual key used and observe http://php.net/manual/en/function.mcrypt-encrypt.php string mcrypt_encrypt ( string $cipher , string $key <= key! Not password.
Edit:
You also have problems with padding. Now making your plain text 48 chars (3*128 bit=3*16 bytes) long:
$plain = 'Hello Hello Hello Hellox';
$plain .= $plain;
function hexstr($hexstr) {
// return pack('H*', $hexstr); also works but it's much harder to understand.
$return = '';
for ($i = 0; $i < strlen($hexstr); $i+=2) {
$return .= chr(hexdec($hexstr[$i] . $hexstr[$i+1]));
}
return $return;
}
$cipher = #mcrypt_encrypt(MCRYPT_RIJNDAEL_128, hexstr('25c506a9e4a0b3100d2d86b49b83cf9a'), $plain, MCRYPT_MODE_CBC, hexstr('00000000000000000000000000000000'));
echo base64_encode($cipher);
echo "\n";
And
openssl enc -aes-128-cbc -a -iv 0 -nosalt -in plain.txt -K 25c506a9e4a0b3100d2d86b49b83cf9a -nopad
results the same:
EZjBup0sfRAkIZ2/IQ3bKHWXHG4qBVv4uyW0PnxJJWvWHanNgE1QyBHMpWoZqejR

Related

openssl_decrypt() can't decrypt text encrypted on the commandline

For testing purposes, I wrote encrypt.bash and decrypt.bash, to prove that the encrypted data saved to encrypted.txt can successfully be decrypted.
Here are the bash files:
encrypt.bash
#!/bin/bash
message="This is my message, I hope you can see it. It's very long now."
key="sup3r_s3cr3t_p455w0rd"
echo "$message" | openssl enc \
-aes-256-ctr \
-e \
-k "$key" \
-iv "504914019097319c9731fc639abaa6ec" \
-out encrypted.txt
decrypt.bash
#!/bin/bash
key="sup3r_s3cr3t_p455w0rd"
decrypted=$(openssl enc \
-aes-256-ctr \
-d \
-k "$key" \
-iv "504914019097319c9731fc639abaa6ec" \
-in encrypted.txt)
echo "Decrypted message: $decrypted"
Running bash decrypt.bash outputs the following:
Decrypted message: This is my message, I hope you can see it. It's very long now.
Where I'm struggling is reading the encrypted.txt file with PHP and decrypting it with openssl_decrypt. As far as I can tell, I'm using all the same settings, and working with binary data correctly, but obviously I'm doing something wrong.
decrypt.php
<?php
$key = "sup3r_s3cr3t_p455w0rd";
$encrypted = file_get_contents("encrypted.txt");
$iv = hex2bin("504914019097319c9731fc639abaa6ec");
$decrypted = openssl_decrypt(
$encrypted,
"aes-256-ctr",
$key,
0,
$iv,
);
echo "Decrypted message: $decrypted";
Running php decrypt.php outputs the following:
Decrypted message: ��c�������Pb�j��
It seems so simple when boiled down like this, but I am struggling to see where the bug exists in my code.
The -k option does not specify a key, but a password. From this password, together with a randomly generated 8 bytes salt, the key is derived using the derivation function EVP_BytesToKey(). The encrypted data is returned in OpenSSL format, which consists of the ASCII encoding of Salted__, followed by the 8 bytes salt and the actual ciphertext.
A simplified PHP implementation of this key derivation function that is sufficient here is (since the IV is explicitly specified here with -iv, it is not derived along with the key):
// from: https://gist.github.com/ezimuel/67fa19030c75052b0dde278a383eda1b
function EVP_BytesToKey($salt, $password) {
$bytes = '';
$last = '';
// 32 bytes key
while(strlen($bytes) < 32) {
$last = hash('sha256', $last . $password . $salt, true); // md5 before v1.1.0
$bytes.= $last;
}
return $bytes;
}
Extracting the salt and actual ciphertext is:
$password = "sup3r_s3cr3t_p455w0rd";
$encrypted = file_get_contents("<path to enc file>");
$salt = substr($encrypted, 8, 8);
$key = EVP_BytesToKey($salt, $password);
$ciphertext = substr($encrypted, 16);
In addition, since the raw data is passed, the corresponding OPENSSL_RAW_DATA flag must be set:
$iv = hex2bin("504914019097319c9731fc639abaa6ec");
$decrypted = openssl_decrypt($ciphertext, "aes-256-ctr", $key, OPENSSL_RAW_DATA, $iv);
Note that as of OpenSSL v1.1.0 the default digest is SHA256, before MD5. The digests used in EVP_BytesToKey() must be identical for compatibility. Also be aware that EVP_BytesToKey() is considered insecure nowadays.

OpenSSL [des-ede3-cbc] Decryption php

i had to receive some data encrypted with 3DES with shared keys.
I'm using php7 and openssl_decrypt function, but I'm not able to recreate the result of the example of the documentation sent to me.
The OpenSSL command that create the data sent to me is the following:
openssl enc -des-ede3-cbc -base64 -K 17839778773fadde0066e4578710928988398877bb123789 -iv 00000000 -in D:/in.txt
Example:
string_encoded: 123456
data_to_decrypt: Ja79hWTRfBE=
I tried to decode "Ja79hWTRfBE=" with an online tool and I successfully obtain "123456".
(I used this tool: http://tripledes.online-domain-tools.com/ with input text (hex) "25aefd8564d17c11", function: 3DES, mode: CBC, key (hex) 17839778773fadde0066e4578710928988398877bb123789, iv: 00000000 )
Below my php code:
$key = "17839778773fadde0066e4578710928988398877bb123789";
$decData = openssl_decrypt(base64_decode('Ja79hWTRfBE='), 'DES-EDE3-CBC', $key, 0, "00000000");
var_dump($decData);
var_dump return me bool(false).
What am i doing wrong?
i can reproduce your goal with the following code:
<?php
$data = "123456";
$method = "DES-EDE3";
$key = "17839778773fadde0066e4578710928988398877bb123789";
$options = 0;
// transform the key from hex to string
$key = pack("H*", $key);
// encrypt
$enc = openssl_encrypt($data, $method, $key, $options);
// decrypt
$dec = openssl_decrypt($enc, $method, $key, $options);
echo "plain: ".$data." encrypted: ".$enc." decrypted: ".$dec;
set data without base64
use DES-EDE3 method
transform your key (from hex to string)

How to decrypt PHP Openssl encryption with BASH command

I am encrypting a password in PHP, and want to decrypt it on a different box. I am having no luck and I would prefer to be able to decrypt it right from bash and echo it. Below is a snippet of a test in PHP.
$textToEncrypt = "My super secret information.";
$encryptionMethod = "AES-256-CBC";
$secretHash = "Testkey";
//To encrypt
$encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secretHash);
//To Decrypt
$decryptedMessage = openssl_decrypt($encryptedMessage, $encryptionMethod, $secretHash);
//Result
echo "Encrypted: $encryptedMessage <br>Decrypted: $decryptedMessage";
I have tried numerous methods to decrypt it on Ubuntu, even storing the data to a file and outputting it to a file. Command tried was:
openssl aes-256-cbc -a -d -k Testkey -in foo.txt -out secrets.txt
Where foo.txt is the value returned from the PHP encryption, and secrets.txt is the output. How can I do this?
It bears repeating, as in the comments, that encryption without an IV is dangerous. In fact, the current version of PHP will issue a warning about it. IVs can be randomly generated using the openssl_random_pseudo_bytes() function, and transmitted in the clear along with the encrypted text. They don't have to be secret, the important thing is not to reuse the same key and IV combination, and have a random IV.
So, with that out of the way, if you take a look at the source for the function, it's not passing the password argument as a passphrase, but rather as the key. So for using openssl on the command line, it needs to be in hex and passed to the -K option, not the -k option. But then, you'll get an error back saying "iv undefined" so your PHP needs to be adjusted to include one:
<?php
$textToEncrypt = "My super secret information.\n";
$encryptionMethod = "AES-256-CBC";
$key = "Testkey";
$iv = openssl_random_pseudo_bytes(
openssl_cipher_iv_length($encryptionMethod)
);
$keyHex = bin2hex($key);
$ivHex = bin2hex($iv);
//To encrypt
$encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $key, 0, $iv);
//To Decrypt
$decryptedMessage = openssl_decrypt($encryptedMessage, $encryptionMethod, $key, 0, $iv);
//Result
printf(
"Decrypted message: %s\n\nkeyHex=%s\nivHex=%s\nencryptedMessage=%s\n",
$decryptedMessage,
escapeshellarg($keyHex),
escapeshellarg($ivHex),
escapeshellarg($encryptedMessage)
);
Once you have these details, you can decrypt from command line (re-using PHP variable names here):
echo -n "$encryptedMessage" | openssl aes-256-cbc -d -a -A -K "$keyHex" -iv "$ivHex"
The other way around
#!/bin/bash
# create in bash keys
echo "generating private key"
openssl genrsa -out privkey.pem 2048
echo "signing private key"
openssl req -new -key privkey.pem -out certreq.csr -subj "/C=RO/ST=AB L=AB/O=None/OU=Department/CN=someweb.com"
echo "create a sign request"
openssl x509 -req -in certreq.csr -signkey privkey.pem -out newcert.pem
# end-of-bash-script
cp ./privkey.pem /path/to/apache/root/<some>
Encrypt some json file
openssl smime -encrypt -aes256 -in ./json.txt -binary -outform DER -out ./json.xxx newcert.pem
# test decrypt here in bash
# openssl smime -decrypt -in json.xxx -inform DER -inkey privkey.pem -out json.dec
Post it as binary to php
curl --request POST --data-binary #./json.xxx http://localhost/<some/>json.php
Then json.php script # apache root
<?php
$rkey = file_get_contents("/var/www/html/privkey.pem");
$pkey = file_get_contents("/var/www/html/newcert.pem");
$data = file_get_contents("php://input");
$fenc = tempnam("", "enc");
$fdec = tempnam("", "dec");
file_put_contents($fenc,$data);
// openssl_pkcs7_decrypt ($fenc , $fdec , $pkey, $rkey ); unable to coerce parameter 3 to x509 cert
system("openssl smime -decrypt -in ${fenc} -inform DER -inkey privkey.pem -out ${fdec}");
echo file_get_contents($fdec);
?>

What is the openssl command line to decrypt text encrypted by this php snippet?

What is the openssl command line for decrypting text encrypted using the following php code?
function encrypt($text, $key) {
return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));
}
This is how far I got:
"blah blah" encrypted with "password" becomes:
whSklKP6+wJdbGpiaCavFp8BiaZ6B1LZmJ0d7Nqx2nc=
I try and decrypt this with:
openssl base64 -d -in test.b64 > test.bin
openssl enc -d -aes-256-ecb -pass pass:password -in test.bin -out test.txt
And I get:
bad magic number

How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together

I'm generating data to send from a Ruby stack to a PHP stack. I'm using the OpenSSL::Cipher library on the Ruby side and the 'mcrypt' library in PHP. When I encrypt using 'aes-256-cbc' (256-bit block size) in Ruby I need to use MCRYPT_RIJNDAEL_128 (128-bit block size) in PHP to decrypt it. I suspect the Ruby code that is broken, because the cipher.iv_len is 16; I believe it should be 32:
>> cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
=> #<OpenSSL::Cipher::Cipher:0x3067c5c>
>> cipher.key_len
=> 16
>> cipher.iv_len
=> 16
>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
=> #<OpenSSL::Cipher::Cipher:0x306de18>
>> cipher.key_len
=> 32
>> cipher.iv_len
=> 16
So here's my test. On the Ruby side, first I generate the key and iv:
>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
>> cipher.encrypt
>> iv = cipher.random_iv
>> iv64 = [iv].pack("m").strip
=> "vCkaypm5tPmtP3TF7aWrug=="
>> key = cipher.random_key
>> key64 = [key].pack("m").strip
=> "RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0="
Then I use those keys to do the encryption:
>> plain_data = "Hi, Don, this is a string."
>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
>> cipher.encrypt
>> cipher.key = Base64.decode64(key64)
>> cipher.iv = Base64.decode64(iv64)
>> encrypted_data = cipher.update(plain_data)
>> encrypted_data << cipher.final
>> crypt64 = [encrypted_data].pack("m").strip
=> "5gfC/kJcnAV2fJI0haxnLcdraIKWgtu54UoznVxf8K0="
Here's the PHP decryption:
$ruby_crypt = "5gfC/kJcnAV2fJI0haxnLcdraIKWgtu54UoznVxf8K0=";
$encrypted_data = base64_decode($ruby_crypt);
$key = base64_decode("RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0=");
$iv = base64_decode("vCkaypm5tPmtP3TF7aWrug==");
$result = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted_data, MCRYPT_MODE_CBC, $iv);
$unencrypt = rtrim($result, "\x00..\x1F");
print "\nUnencrypted token:\n'$unencrypt'\n";
RESULT:
Unencrypted token:
'Hi, Don, this is a string.'
I'd prefer to use the longer block size. Clearly I'm misunderstanding the APIs. Help?
I wrote an example that somebody else may find explanatory of the discussion above:
$ cat publisher.rb
#!/usr/bin/env ruby
require 'openssl'
require 'base64'
key = '7fc4d85e2e4193b842bb0541de51a497'
cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
cipher.encrypt()
iv = cipher.random_iv
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.encrypt()
cipher.key = key
cipher.iv = iv
crypt = cipher.update('This is my text')
crypt << cipher.final()
puts [Base64.encode64(crypt).strip(), Base64.encode64(iv).strip()].join('|')
$ cat consumer.php
$key256 = '7fc4d85e2e4193b842bb0541de51a497';
$fd = fopen("php://stdin", "r");
$tokens = '';
while (!feof($fd))
$tokens .= fread($fd, 1024);
fclose($fd);
$tokens = explode('|', trim($tokens));
$crypt = $tokens[0];
$iv = $tokens[1];
$crypttext = base64_decode($crypt);
$iv = base64_decode($iv);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key256, $crypttext, MCRYPT_MODE_CBC, $iv);
print $decrypted ."\n";
To test it, from command line try:
$ ruby publisher.rb | php consumer.php
This is my text
I don't know PHP, but reading through related questions on the sidebar, I see Converting Ruby AES256 decrypt function to PHP. This includes a reference to this page, pointing out that the 128 in MCRYPT_RIJNDAEL_128 refers to the block size of the encryption, not the key size. You'll notice that the key size that you've passed between ruby and PHP is 256 bits in both cases. In other words, this seems to be the expected behavior, and you are using the larger key already.
#!/usr/bin/ruby
require 'base64'
puts((Base64.decode64("RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0=").length * 8).to_s)
HTH
Let me show you some code.
PHP code:
$privateKey = "1234567890123456"; # the size is 16.
$data = "hello";
$iv = "0123456789012345";
$result = mcrypt_encrypt(
MCRYPT_RIJNDAEL_128, $privateKey, $data, MCRYPT_MODE_CBC, $iv
)
$base64str = base64_encode($result);
$base64str = str_replace("+", "-", $base64str);
$base64str = str_replace("/","_", $base64str);
# => f-WffBXnf122NcVBUZ6Rlg==
Ruby code:
require 'base64'
require 'openssl'
private_key = "1234567890123456"
data = "hello"
iv = "0123456789012345"
cipher = OpenSSL::Cipher::AES.new(128, :CBC)
cipher.encrypt
cipher.padding = 0 # we must disable padding in ruby.
cipher.key = private_key
cipher.iv = iv
block_size = cipher.block_size
# Add padding by yourself.
data = data + "\0" * (block_size - data.bytesize % block_size)
result = cipher.update(data) + cipher.final
Base64.urlsafe_encode64(result)
# ==> f-WffBXnf122NcVBUZ6Rlg==
As you can see I am using AES-128 in ruby because the size of private_key is 16.
So you have to use AES-256 if the size of your private_key is 32.
Formula: size_of_private_key * 8.
I had troubles because the PHP was using a password smaller than 8 characters. In this case one needs to add the 0, to make it compatible with PHP:
mcrypt-encrypt manual page
"key
The key with which the data will be encrypted. If it's smaller than the required keysize, it is padded with '\0'. It is better not to use ASCII strings for keys.
http://php.net/manual/en/function.mcrypt-encrypt.php
It is recommended to use the mhash functions to create a key from a string."
require 'openssl'
cipher = OpenSSL::Cipher.new('DES-ECB')
cipher.encrypt
key = 'passwrd'[0...7].ljust(8, 0.chr) #Pad the key smaller than 8 chars
cipher.key = key
encrypted = cipher.update('33')
encrypted << cipher.final
dec = Base64.encode64(encrypted).strip()

Categories