I am doing AES encryption on ios End, and i the base64 encode that string and send over to php end. On the php end, i have following code:
<?php
$key = 'a16byteslongkey!';
$data = base64_decode('LsCH4nvvGPKN67v94Ig9BweQgOk9rtDdK7ZugeJkTS8=');
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = substr($data, 0, $iv_size);
function aes256_cbc_decrypt($key, $data, $iv) {
if(32 !== strlen($key)) $key = hash('SHA256', $key, true);
if(16 !== strlen($iv)) $iv = hash('MD5', $iv, true);
$data = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB, $iv);
$padding = ord($data[strlen($data) - 1]);
return substr($data, 0, -$padding);
}
$result = aes256_cbc_decrypt($key,$data,$iv);
var_dump($result);
?>
But when i run this code i get this "anil.mnd#gmail.cA���u�" . I should have got anil.mnd#gmail.com. I get only first 16 characters correct.
I am new to encryption so not have much idea what is wrong.
I am new to encryption so not have much idea what is wrong.
If you're new and want something that "just works", use defuse/php-encryption instead of trying to write it yourself.
If you're up for the challenge, however, keep reading:
Your code is unreadable. Let's add some whitespace.
$key = 'a16byteslongkey!';
$data = base64_decode('LsCH4nvvGPKN67v94Ig9BweQgOk9rtDdK7ZugeJkTS8=');
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = substr($data, 0, $iv_size);
function aes256_cbc_decrypt($key, $data, $iv)
{
if (32 !== strlen($key)) {
$key = hash('SHA256', $key, true);
}
if (16 !== strlen($iv)) {
$iv = hash('MD5', $iv, true);
}
$data = mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
$key,
$data,
MCRYPT_MODE_ECB,
$iv
);
$padding = ord($data[strlen($data) - 1]);
return substr($data, 0, -$padding);
}
$result = aes256_cbc_decrypt($key,$data,$iv);
var_dump($result);
Specific problems:
You're using MCRYPT_MODE_ECB for a function named aes256_cbc (have you seen the penguin?)
When I switch that out, I get invalid data.
Your encryption method is also probably broken, since changing your IV to "\x00\x00"... makes it decrypt.
Specific recommendations:
Please, please, PLEASE consider using well-studied cryptography code instead of writing it yourself.
strlen() and substr() are brittle. See: function overloading.
Use a real key derivation function, not a hash function.
Your IV (and keys, for that matter) should be generated from a cryptographically secure pseudo-random number generator, such as random_bytes().
Use authenticated encryption.
Related
I have a PHP page that loops through a CSV file and encrypts the 'email' column using the following function:
function my_encrypt($data, $key)
{
// Remove the base64 encoding from our key
$encryption_key = base64_decode($key);
// Generate an initialization vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
// Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
$encrypted = openssl_encrypt($data, 'aes-256-cbc', $encryption_key, 0, $iv);
// The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
return base64_encode($encrypted . '::' . $iv);
}
In another part of the app, I decrypt the returned value using:
function my_decrypt($data, $key)
{
// Remove the base64 encoding from our key
$encryption_key = base64_decode($key);
// To decrypt, split the encrypted data from our IV - our unique separator used was "::"
list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
return openssl_decrypt($encrypted_data, 'aes-256-cbc', $encryption_key, 0, $iv);
}
This all works smoothly for the most part, but every now and then, the decrypted value comes back with a few weird characters in it.
For example: rsmi3�6CTΣ%mecompany.com was returned instead of rsmith#somecompany.com.
I'm not sure if it's the input or the output that is bad, but I'm guessing it has something to do with the uploaded CSV file... encoding issue? What do those characters mean and under what conditions are they produced?
UPDATE
Here's the code I'm using to add the encrypted value to the CSV:
$file = fopen(get_stylesheet_directory() . "/emma_members.csv", "r"); //Open the old file for reading
$newFile = fopen(get_stylesheet_directory() . "/emma_members_new.csv", "w"); //Create a new file for writing
if (!$file) error_log('ERROR opening file');
if (!$newFile) error_log('ERROR creating file');
$columns = ['email', 'member_id', 'member_since', 'plaintext_preferred', 'bounce_count', 'status_name', 'last_modified_at', 'city', 'first_name', 'last_name', 'request-demo', 'job-function', 'title', 'country', 'current-ams', 'opt-in', 'address-2', 'unique-identifier', 'state', 'postal_code', 'web-address', 'address', 'phone-number', 'company', 'area-of-specialization', 'work-phone'];
while (($data = fgetcsv($file)) !== FALSE) {
$row = array_combine($columns, $data);
$email = "{$row['email']}";
$uid = my_encrypt($email, ENCRYPT_KEY_1);
$row['unique-identifier'] = $uid;
$ret = fputcsv($newFile, array_values($row));
}
UPDATE 2
So after much testing with thousands of emails, it seems the my_encrypt function returns some bad values, depending on the input of course. It didn't happen with EVERY email address, but even 1 is too many for my use case.
I even tried getting rid of the :: between the data and the iv, but that didn't work either (although it's possible I did it wrong).
Anyway, I ended up using the following function in its place, and all is well:
function encrypt_decrypt($action, $string) {
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = PHRASE_1;
$secret_iv = PHRASE_2;
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
if ( $action == 'encrypt' ) {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
} else if( $action == 'decrypt' ) {
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return $output;
}
I tested your encrypt and decrypt functions and they are working as expected, so the reason for the behaviour seems to be a different file encoding on your device.
Especially when reading a CSV-file sometimes a (windows) device changes the encoding and you get some curious characters like those you have shown. My recommendation is to read the files with another encoding as the default one (ISO...).
I setup a live example that "proves" the correctness on a simple string en- and decryption: https://paiza.io/projects/e/Y-1gy9Y3b-VAlXAMG4odng
The result is simple:
plaintext: rsmith#somecompany.com
ciphertext: Y0RrMWRwR1pWeGtGbFdic3dIVmFzVmp4VUFYemJGdUhzMStRSll6akIwWT06Orf+twLGopVa4083RckEw44=
decryptedtext: rsmith#somecompany.com
Here is the code:
<?php
function my_encrypt($data, $key)
{
// Remove the base64 encoding from our key
$encryption_key = base64_decode($key);
// Generate an initialization vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
// Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
$encrypted = openssl_encrypt($data, 'aes-256-cbc', $encryption_key, 0, $iv);
// The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
return base64_encode($encrypted . '::' . $iv);
}
function my_decrypt($data, $key)
{
// Remove the base64 encoding from our key
$encryption_key = base64_decode($key);
// To decrypt, split the encrypted data from our IV - our unique separator used was "::"
list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
return openssl_decrypt($encrypted_data, 'aes-256-cbc', $encryption_key, 0, $iv);
}
$plaintext = 'rsmith#somecompany.com';
echo 'plaintext: ' . $plaintext . PHP_EOL;
$encryptionKey = base64_encode(32);
$ciphertext = my_encrypt($plaintext, $encryptionKey);
echo 'ciphertext: ' . $ciphertext . PHP_EOL;
$decryptedtext = my_decrypt($ciphertext, $encryptionKey);
echo 'decryptedtext: ' . $decryptedtext . PHP_EOL;
?>
I am doing encryption which is working good but with same method I am doing decryption I am getting blank string not getting decryption string. I am using method AES-256-ECB and key is hexadecimal so I pass as
$key = pack('H*','xxxxxxxxxxxx');
Encryption is going correct but decryption is not working. Please help me what I am doing wrong.
function encrypt(string $data, string $key, string $method): string
{
$ivSize = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivSize);
$encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
$encrypted = strtoupper(implode(null, unpack('H*', $encrypted)));
return $encrypted;
}
function decrypt(string $data, string $key, string $method): string
{
$data = pack('H*', $data);
$ivSize = openssl_cipher_iv_length($method);
$iv = $iv = openssl_random_pseudo_bytes($ivSize);
$decrypted = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
return trim($decrypted);
}
Your functions are working perfectly for me with the following code:
$key = pack('H*','aaaaaaaaaaaaa');
$method = 'aes-256-ecb';
$encrypted = encrypt('test string', $key, $method);
$decrypted = decrypt($encrypted, $key.'a', $method);
echo $decrypted; // Output: 'test string'
Since you're getting an empty string for decryption, this means that you've either got the wrong key or cipher text when decrypting. Make sure the key you are using for decryption is exactly the same as the key you're using for encryption, including any manipulations done on it, such as the pack() function you've done here. Even one byte difference and you're not going to be able to decrypt.
Also make sure neither the key nor the cipher text are being truncated when they are stored. If using a database and the column type is too small for what you are trying to store, it will truncate the values.
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 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.
Ok, I have tried to create my own encryption/decryption methods using PHP mcrypt, and when I posted them a while back some called them "trash". They were mentioning things about "Initialization Vectors" and such. Basically, how can I make these cryptography methods better:
function encrypt($key, $data){
$encrypted_data = mcrypt_cbc(MCRYPT_RIJNDAEL_192, $key, $data, MCRYPT_ENCRYPT);
return base64_encode($encrypted_data);
}
function decrypt($key, $encryptedData){
$dec = base64_decode($encryptedData);
$decrypt = mcrypt_cbc(MCRYPT_RIJNDAEL_192, $key, $dec, MCRYPT_DECRYPT);
return trim($decrypt);
}
I want these to work the best they can except I am a duck in a brand new world when it comes to mcrypt, any suggestions are welcome, thanks!
Here is a snippet of the mcrypt functions I use. They use mcrypt_generic and mdecrypt_generic, which should be used according to the PHP manual.
function encrypt($key, $data){
$b = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$enc = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($enc), MCRYPT_DEV_URANDOM);
mcrypt_generic_init($enc, md5($key), $iv);
// PKCS7 Padding from: https://gist.github.com/1077723
$dataPad = $b-(strlen($data)%$b);
$data .= str_repeat(chr($dataPad), $dataPad);
$encrypted_data = mcrypt_generic($enc, $data);
mcrypt_generic_deinit($enc);
mcrypt_module_close($enc);
return array(
'data' => base64_encode($encrypted_data),
'iv' => base64_encode($iv)
);
}
function decrypt($key, $iv, $encryptedData){
$iv = base64_decode($iv);
$enc = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($enc, md5($key), $iv);
$encryptedData = base64_decode($encryptedData);
$data = mdecrypt_generic($enc, $encryptedData);
mcrypt_generic_deinit($enc);
mcrypt_module_close($enc);
// PKCS7 Padding from: https://gist.github.com/1077723
$dataPad = ord($data[strlen($data)-1]);
return substr($data, 0, -$dataPad);
}
I don't know much about mcrypt either, so I just kinda hacked these together. I md5 the key so it's always 32 characters (the max key length), and I randomly calculate an "Initialization Vector".
Using PKCS7 Padding is better because you can have strings that end in white space (as trim would remove that), also the encryption is more efficient when the string is a certain length.
I'm using AES 256 (MCRYPT_RIJNDAEL_256) here, but AES 192 (MCRYPT_RIJNDAEL_192) would work too.
Demo: http://ideone.com/WA5Tk
You can create an iv with mcrypt_create_iv(), using the appropriate size for your encryption mode.
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_192, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
Then pass it to mcrypt_cbc() as the optional 5th parameter. The only changes I've made here to your original functions are to pass in $iv:
function encrypt($key, $data, $iv){
$encrypted_data = mcrypt_cbc(MCRYPT_RIJNDAEL_192, $key, $data, MCRYPT_ENCRYPT, $iv);
return base64_encode($encrypted_data);
}
function decrypt($key, $encryptedData, $iv){
$dec = base64_decode($encryptedData);
$decrypt = mcrypt_cbc(MCRYPT_RIJNDAEL_192, $key, $dec, MCRYPT_DECRYPT, $iv);
return trim($decrypt);
}