I'm working on a small program that has to encrypt a basic sentence and echo the result. Then I want to be able to copy paste the result and be able to decode the text in the same way I encoded it. It doesn't need to be too safe, so therefore I opted to use Mcrypt.
When I try to decrypt, it gives weird (ASCII?) letters and I don't understand where they come from. Tried to solve it with a base64 encode/decode, but that didn't help either. What do I need to change to get it to work properly?
<?php
// Define Mcrypt variables
$enc = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$key = 'SanderDitIsNodigVoor16bi';
$iv = mcrypt_create_iv(mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM);
// Check if 'submit' is set
if (isset($_POST['submit'])) {
// Check if 'text' is set
if (!empty($_POST['text'])) {
// Check if 'crypt' is set
if (isset($_POST['crypt'])) {
// Retrieve 'text'
$input = $_POST['text'];
// Check for encrypt/decrypt
switch ($_POST['crypt']) {
case 'encrypt':
$result = encrypt();
break;
case 'decrypt':
$result = decrypt();
break;
}
echo $result;
}
// If 'crypt' is not set
else {
echo 'Please select encrypt or decrypt.';
}
}
// If 'text' is not set
else {
echo 'Please fill in some text.';
}
}
function encrypt() {
global $enc, $key, $input, $mode, $iv;
$encrypted = mcrypt_encrypt($enc, $key, $input, $mode, $iv);
$output = base64_encode($encrypted);
return $output;
}
function decrypt() {
global $enc, $key, $input, $mode, $iv;
$decrypted = base64_decode($input);
$output = mcrypt_decrypt($enc, $key, $decrypted, $mode, $iv);
return $output;
}
?>
To be clear, if I include the mcrypt_decrypt in the encryption to make sure it's not something I messed up in the function itself, it does decrypt it properly. But when I try to separate the two, it doesn't. I'm stumped.
you are sending to both encrypt and decrypt the same input , which is :
$input = $_POST['text'];
encrypt will encrypt successfully , but you are always trying to decrypt 'decrypted' phrase !
you must pass the encrypted phrase to decrypt function
and don't forget about the important note that mcrypt_* extension has been deprecated :
This extension rely in libmcrypt which is dead, unmaintained since
2007.
Please don't rely on it, consider switching to well maintained
alternatives (openssl, crypt, password hashing functions, phpseclib,
password_compat...)
and , try to stop using global variables , it isn't recommended .
Related
From time to time I come around the task of creating functions for encrypting/decrypting strings and files in PHP.
I decided to finally nail those functions and did some searching but I couldn't find enough resources to confirm the security of these functions.
Please note that I don't want to use another full-blown library unless necessary and I don't see why PHP provides OpenSSL & mcrypt functions but nobody really implements them.
I was able to find these functions but they are not commented and some steps were unclear (also they do not generate a key but use a predefined one).
Following these functions I also found this stackoverflow question but the first answer uses another library while the second one uses ECB.
edit: I updated the code sample which previously utilized mcrypt to using only OpenSSL as suggested in the comments:
function generate_key($cipher = 'AES-256-CBC')
{
return base64_encode(openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher))); // Generate a random key - currently using the function for the vector length
}
function encrypt($data, $key, $cipher = 'AES-256-CBC')
{
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher)); // Generate a random initialization vector
return base64_encode($iv) . '$' . openssl_encrypt($data, $cipher, base64_decode($key), false, $iv); // Return a base64 encoded string containing the iv and the encrypted data
}
function decrypt($data, $key, $cipher = 'AES-256-CBC')
{
$data = explode('$', $data); // Explode the previously encoded string
if(count($data) == 2)
return openssl_decrypt($data[1], $cipher, base64_decode($key), false, base64_decode($data[0])); // Decrypt the data given key and the iv
else
return false;
}
I tested encryption and decryption using these function like this:
$input = 'Hello world!';
echo 'Original data: ' . $input . '<br /><br />';
$key = generate_key();
$encrypted = encrypt($input, $key);
echo 'Key used for encryption: ' . $key . '<br />';
echo 'Encrypted data: ' . $encrypted . '<br /><br />';
$decrypted = decrypt($encrypted, $key);
echo 'Decrypted data: ' . $decrypted . '<br />';
The question: Is OpenSSL properly implemented as shown above? Can they be used for files too?
These are the old functions using mcrypt. Don't use them anymore.
function generate_key($cipher = MCRYPT_RIJNDAEL_256)
{
return bin2hex(openssl_random_pseudo_bytes(mcrypt_get_key_size($cipher, MCRYPT_MODE_CBC))); // Generate a random key using OpenSSL with size given from mcrypt depending on cipher
}
function encrypt($data, $key, $cipher = MCRYPT_RIJNDAEL_256)
{
$iv = mcrypt_create_iv(mcrypt_get_iv_size($cipher, MCRYPT_MODE_CBC)); // Generate random initialization vector with size given from mcrypt depending on cipher
return bin2hex($iv) . '$' . bin2hex(mcrypt_encrypt($cipher, pack('H*', $key), $data, MCRYPT_MODE_CBC, $iv)); // Return the initialization vector and encrypted data as ASCII string
}
function decrypt($data, $key, $cipher = MCRYPT_RIJNDAEL_256)
{
$data = explode('$', $data); // Split the input data by $ to retrieve the initialization vector and the encrypted data
if(count($data) == 2) // Check if there are 2 parts after splitting by $
return mcrypt_decrypt($cipher, pack('H*', $key), pack('H*', $data[1]), MCRYPT_MODE_CBC, pack('H*', $data[0])); // Return the decrypted string
else
return false; // Return false if the given data was not properly formatted (no $)
}
Current best practise is to avoid using mcrypt in favor of openssl.
And the speed benchmark, where openssl is pretty much faster.
Using PHP, I've created the following set of functions witch ultimately takes a string (password) and applies a bcrypt encryption to it. Furthermore, it generates a key to use with mcrypt then applies that to the bcrypt string (along with base64 to simplify the string) to then insert into a database for storage.
From this when decoding I decrypt the mcrypt encryption applied to the hash and then use password_verify() to then validate it.
However, I am not able to get password_verify() to validate the hash if it has been run through the mcrypt process, even though after it has been decoded the two strings (one from the encode function and one from the decode) are IDENTICAL.
The encode function looks like this:
function passwordEncode($string) {
$hash = password_hash($string, PASSWORD_BCRYPT, ['cost' => 12]);
$key = generateKey();
$encrypt = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key."******", $hash, MCRYPT_MODE_ECB));
return [$encrypt, $key, $hash];
}
This would return:
[0] ENCRYPT: lTzVGcAY1jkuawebFG/9ZI4O5f/+4hjZHRewstOBAAJwQlYydLJ+B+2QHg9A16qjCUe7FHfTacPzmvH+xnT4rQ==
[1] KEY: 122593420654793b0ee4efc932
[2] HASH: $2y$10$k/4gM1jMIMxnmfBMgrML6enMgqIvnZp2EzPU.G64P3Bb3MDrwJj8e
The HASH index is only for debugging purposes to provide an output hash that has not been run through the mcrypt process
The decode function looks like this:
function passwordDecode($string, $key) {
$decrypt = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key."******", base64_decode($string), MCRYPT_MODE_ECB);
return $decrypt;
}
This would return:
DECRYPT: $2y$10$k/4gM1jMIMxnmfBMgrML6enMgqIvnZp2EzPU.G64P3Bb3MDrwJj8e
Using the raw hash that hasn't been run through mcrypt returns Verified
$encode = passwordEncode("password");
if(password_verify("password", $encode[2])) {
echo 'Verified';
} else {
echo 'Not verified';
}
However using the hash run through mcrypt encryption and decryption returns Not verified
$encode = passwordEncode("password");
if(password_verify("password", passwordDecode($encode[0], $encode[1]))) {
echo 'Verified';
} else {
echo 'Not verified';
}
After spending hours essentially grinding my forehead against a cheese grater, I still haven't been able to figure out what mcrypt is doing to the string to unverify it. I've made an attempt at searching for invisible characters (keyword attempt) but other than that I'm out of ideas as to what the cause is.
Edit: also, this returns not verified
$encode = passwordEncode("password");
if($encode[2]==passwordDecode($encode[0], $encode[1])) {
echo 'Verified';
} else {
echo 'Not verified';
}
So something's being done to the string...I just don't know what
For some stupid reason PHP includes the \0 valued characters that are used as zero padding by mcrypt in the decrypted plaintext. Even more stupid, those seem to be included in the base64 decoding performed by password_verify as well, which make it fail without explicit reason. This kind of stupidity makes PHP one of the worst environments to use for security related functions.
So without further ado, the rewritten functions that perform rtrim, in a piece of code that can be run on it's own. Requires either PHP 5.5 or password_compat :
<?php
# uncomment for PHP 5.3/5.4
# require "lib/password.php";
function generateKey() {
$fp = #fopen('/dev/urandom','rb');
if ($fp !== FALSE) {
$key = #fread($fp, 16);
#fclose($fp);
return $key;
}
return null;
}
function hashPassword($password) {
$hash = password_hash($password, PASSWORD_BCRYPT, array('cost' => 12));
return $hash;
}
function encryptHash($key, $hash) {
# encrypt using unsafe ECB mode and without AES
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $hash, MCRYPT_MODE_ECB);
$encoded = base64_encode($encrypted);
return $encoded;
}
function decryptHash($key, $ciphertext) {
$decoded = base64_decode($ciphertext);
$decryptedHash = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $decoded, MCRYPT_MODE_ECB);
# remove stupid zero padding
$decryptedHash = rtrim($decryptedHash, "\0");
return $decryptedHash;
}
$hash = hashPassword("password");
if(password_verify("password", $hash)) {
echo 'Verified' . PHP_EOL;
} else {
echo 'Not verified' . PHP_EOL;
}
$key = generateKey();
$encrypted = encryptHash($key, $hash);
$decrypted = decryptHash($key, $encrypted);
if(password_verify('password', $decrypted)) {
echo 'Verified' . PHP_EOL;
} else {
echo 'Not verified' . PHP_EOL;
}
?>
Usually, I use openssl_encrypt to encrypt simple string with AES in PHP, and it works pretty well.
Now I need to encrypt files with AES-256-CTR mode, but the only way to do this is to file_get_contents the entire content of the file and then send it to the openssl_encrypt function to encrypt the actual file data. The problem is this method is very "poor" because of the critical waste of memory.
1) Is there a way to work with chunked data with PHP OpenSSL ?
For example:
<?php
// ...
$f = fopen('large.iso','r');
while(feof($f)){
$chunk = fread($f,16);
$cipher = openssl_encrypt(...$chunk...);
// ... code ...
}
// ... more code ...
?>
2) openssl_encrypt official documentation is not published yet. Does someone could clarify the meaning of the parameters of the function for use with AES-CTR mode? Does the counter is handled automatically? Is it necessary to apply a manual XOR the data returned by the function?
Note: It is a professional project so I don't want to use phpseclib or others' "anonymous" libraries, nor do I don't want to use the command line as well.
Looks like for php it's not possible to use aes-256-ctr without temporary file.
But for next chiper types:
OPENSSL_CIPHER_RC2_40
OPENSSL_CIPHER_RC2_128
OPENSSL_CIPHER_RC2_64
OPENSSL_CIPHER_DES
OPENSSL_CIPHER_3DES
OPENSSL_CIPHER_AES_128_CBC
OPENSSL_CIPHER_AES_192_CBC
OPENSSL_CIPHER_AES_256_CBC
you can use generating key on the fly:
$res = openssl_pkey_new('chiper args here');
openssl_pkey_export($res, $private_key);
$public_key = openssl_pkey_get_details($res);
$public_key = $public_key["key"];
Then encrypt:
$crypted_text = openssl_get_privatekey($private_key,'your data');
And decrypt:
openssl_public_decrypt($crypted_text,$decrypted_text,$public_key);
So if you don't want to use files, may be switching to OPENSSL_CIPHER_AES_256_CBC will help you?
1) It should be something like this:
function strtohex($x) {
$s = '';
foreach (str_split($x) as $c){
$s.=sprintf("%02X", ord($c));
}
return($s);
}
$method = "aes-256-ctr"; //aes-256-cbc
echo "Selected method: ".$method."<br /><br />";
$textToEncrypt = "My chunk of data";
$iv = "1234567890123456";
$pass = 'some_pass';
$dec_iv = strtohex($iv);
$key = strtohex($pass);
$enc_data = openssl_encrypt($textToEncrypt, $method, $pass, true, $iv);
echo "Encrypted message (openssl): ".$enc_data."<br />";
$dec_data = openssl_decrypt($enc_data, $method, $pass, OPENSSL_RAW_DATA, $iv);
echo "Decrypted message (openssl): ".$dec_data."<br />";
For CTR $iv should be unique for each chunk or your data can be broken.
2) I know only abot difference betwen CBC and CTR:
For CBC, the IV must be random, but not unique. It also must not be known.
For CTR, the IV must be unique and not known, but does not need to be random.
I am new to AES but from what I have found there are several modes (ECB,CBC, etc.) and different modes need different initialization vector requirements, blocks, and encodings. I am trying to decode the following
Xrb9YtT7cHUdpHYIvEWeJIAbkxWUtCNcjdzOMgyxJzU/vW9xHivdEDFKeszC93B6MMkhctR35e+YkmYI5ejMf5ofNxaiQcZbf3OBBsngfWUZxfvnrE2u1lD5+R6cn88vk4+mwEs3WoAht1CAkjr7P+fRIaCTckWLaF9ZAgo1/rvYA8EGDc+uXgWv9KvYpDDsCd1JStrD96IACN3DNuO28lVOsKrhcEWhDjAx+yh72wM=
using php and the (text) key "043j9fmd38jrr4dnej3FD11111111111" with mode CBC and an IV of all zeros. I am able to get it to work with this tool but can't get it in php. Here is the code I am using:
function decrypt_data($data, $iv, $key) {
$data = base64_decode($data);
$cypher = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
// initialize encryption handle
if (mcrypt_generic_init($cypher, $key, $iv) != -1) {
// decrypt
$decrypted = mdecrypt_generic($cypher, $data);
// clean up
mcrypt_generic_deinit($cypher);
mcrypt_module_close($cypher);
return $decrypted;
}
return false;
}
I think I may be missing something relating to base 64 encoding or turning the key into binary first. I have tried decoding many things and all I can produce is gibberish. Any help would be very appreciated.
Well the tool itself does not say how exactly it's encrypted. And you can't set the IV either so it's hard to get the parameters right (because they have to be equal).
After some guesswork I found out the following:
The IV is prepended to the ciphertext
The ciphertext is encrypted with aes-128-cbc
So you have to modify the code:
function decrypt_data($data, $iv, $key) {
$cypher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
if(is_null($iv)) {
$ivlen = mcrypt_enc_get_iv_size($cypher);
$iv = substr($data, 0, $ivlen);
$data = substr($data, $ivlen);
}
// initialize encryption handle
if (mcrypt_generic_init($cypher, $key, $iv) != -1) {
// decrypt
$decrypted = mdecrypt_generic($cypher, $data);
// clean up
mcrypt_generic_deinit($cypher);
mcrypt_module_close($cypher);
return $decrypted;
}
return false;
}
$ctext = "Xrb9YtT7cHUdpHYIvEWeJIAbkxWUtCNcjdzOMgyxJzU/vW9x" .
"HivdEDFKeszC93B6MMkhctR35e+YkmYI5ejMf5ofNxaiQcZb" .
"f3OBBsngfWUZxfvnrE2u1lD5+R6cn88vk4+mwEs3WoAht1CA" .
"kjr7P+fRIaCTckWLaF9ZAgo1/rvYA8EGDc+uXgWv9KvYpDDs" .
"Cd1JStrD96IACN3DNuO28lVOsKrhcEWhDjAx+yh72wM=";
$key = "043j9fmd38jrr4dnej3FD11111111111";
$res = decrypt_data(base64_decode($ctext), null, $key);
I'm not sure why the key length is not used to encrypt it with aes-256-cbc - I've checked out the source of that as3crypto-library and it kind of supported it, but I would have to debug it to really verify it.
I am coding a Drupal payment method module and within this I need to generate a hash to send to a bank. Bank asks me to code certain strings into the DES/ECB hash. They also provide test environment and here comes my problem. With the string B7DC02D5D6F2689E and key 7465737465703031 I should get result hash 3627C7356B25922B (after bin2hex, of course). This is by the bank test page and I have also checked this on this page: http://www.riscure.com/tech-corner/online-crypto-tools/des.html (encryption java applet).
My problem is that whatever I do I cant get my PHP code to provide the correct result. This is a simple function I am trying to use:
function encrypt($hash, $key)
{
$hash = strtoupper(substr(sha1($hash), 0, 16));
$key = strtoupper(bin2hex($key));
$block = mcrypt_get_block_size('des', 'ecb');
if (($pad = $block - (strlen($hash) % $block)) < $block) {
$hash .= str_repeat(chr($pad), $pad);
}
$sig = strtoupper(bin2hex(mcrypt_encrypt(MCRYPT_DES, $key, $hash, MCRYPT_MODE_ECB)));
return $sig;
}
and I have been trying sth like this as well:
function encrypt( $value, $key) {
$hash = strtoupper(substr(sha1($value), 0, 16));
$key = strtoupper(substr(bin2hex($key), 0, 16));
// encrypt hash with key
if (function_exists('mcrypt_module_open')) { // We have mcrypt 2.4.x
$td = mcrypt_module_open(MCRYPT_DES, "", MCRYPT_MODE_ECB, "");
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
$signature = strtoupper(bin2hex(mcrypt_generic ($td, $hash)));
mcrypt_generic_end ($td);
}
else
{ // We have 2.2.x only
$signature = strtoupper(bin2hex(mcrypt_ecb (MCRYPT_3DES, $key, $hash, MCRYPT_ENCRYPT)));
}
return $signature;
}
None of these gave the correct signature. Any idea what's wrong? For now I am dealing with this issue more than 3 hrs, so I appreciate any help. I am not very familiar with this encryption stuff. Thanks a lot.
Btw.: Those $hash and $key mentioned above are after the strtoupper, substr and bin2hex functions at the beginning of my code snippets.
Simple solution:
function encrypt($hash, $key) {
return mcrypt_encrypt("des", pack("H*", $key), pack("H*", $hash), "ecb");
}
print bin2hex(encrypt("B7DC02D5D6F2689E", "7465737465703031"));
This prints 3627c7356b25922b for me, so it looks like that's working.
You were on the right track with bin2hex(), but that was converting in the wrong direction. (There's unfortunately no hex2bin() function, so you have to use pack() instead.)
You also don't need an IV for a single-block encryption like this.
You plaintext, B7DC02D5D6F2689E, is 8 bytes = 64 bits. This is an exact block for DES so you don't need any padding in ECB mode. I suggest that you remove the padding code entirely. All DEC-ECB needs in this case is the block to encrypt and the key; no padding and no IV.