I managed to make a code to encrypt and decrypt data in AES-CCM in PHP language. This code retrieves data, sends it to a server and adds it to a database via post URL. But if someone has access to the URL and sends it several times to the server, it will add data to the database each time.
Is there a simple way to prohibit this URL after one adding data?
Edit : I think I will add date and hour in the encryption part. Then when I will receive data, I will check date and hour with +-1min. And check if I have not already receive this message.
What do you think about this solution?! Is that easy is to crack it?!
This is my code (Fisrt part) :
$algo = 'aes-128-ccm';
$iv = random_bytes(openssl_cipher_iv_length($algo));
$key = "cd9344040aa9f9217871d46ee871c59c";
$data = '00000000010-3b57af';
$ciphertext = openssl_encrypt(
$data,
$algo,
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
$ciphertext = bin2hex($ciphertext);
$iv = bin2hex($iv);
$tag = bin2hex($tag);
echo'"decrypte"';
?>
The second part :
$algo = 'aes-128-ccm';
$key = "cd9344040aa9f9217871d46ee871c59c";
$ciphertext = hex2bin($_GET['data']);
$iv = hex2bin($_GET['iv']);
$tag = hex2bin($_GET['tag']);
$decrypt = openssl_decrypt(
$ciphertext,
$algo,
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
if (false === $decrypt) {
throw new Exception(sprintf(
"OpenSSL error: %s", openssl_error_string()
));
}
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'm using the openssl_encrypt / decrypt method in my website but i'm having some troubles with the $tag option
openssl_encrypt ( $data, $method, $key, $options, $iv, $tag )
openssl_decrypt ( $data, $method, $key, $options, $iv, $tag )
from http://php.net/manual/en/function.openssl-encrypt.php, the definition of tag is: The authentication tag passed by reference when using AEAD cipher mode (GCM or CCM). But i didn't understand it.
I tried it in my codes
$data = "text to be encrypted";
$cipher = "aes-128-gcm";
$key = "0123456789abcdefghijklmnob123456";
$option = 0;
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
if (in_array($cipher, openssl_get_cipher_methods())){
$encryptedData = openssl_encrypt($data,$cipher,$key,$option,$iv,$tag);
echo $encryptedData;
$decryptedData = openssl_decrypt($encryptedData,$cipher,$key,$option,$iv,$tag);
echo $decryptedData;
}
i got this result:
encrypted text: Vlx/yKkPhg0DpD0YKvnFKRiCh/I=
decrypted text: text to be encrypted
which is correct. but if i directly decrypt the encrypted text this way:
$data = "text to be encrypted";
$cipher = "aes-128-gcm";
$key = "0123456789abcdefghijklmnob123456";
$option = 0;
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
if (in_array($cipher, openssl_get_cipher_methods())){
$encryptedData = "Vlx/yKkPhg0DpD0YKvnFKRiCh/I=";
$decryptedData = openssl_decrypt($encryptedData,$cipher,$key,$option,$iv,$tag);
echo $decryptedData;
}
i'm getting:
Notice: Undefined variable: tag
if someone could explain to me why this is happening and what should be the value of $tags. thanks
The tag that PHP is complaining about is an essential aspect of AES when using GCM mode of operation. In this mode, not only does the AES block cipher get applied, but an authentication tag gets calculated as well. It is an array of bytes that represents a MAC (Message Authentication Code) that can be used to verify the integrity of the data and wen decrypting. That same tag needs to be provided to do that verification. See the Wikipedia page about Galois/Counter Mode for more details.
So in order to successfully decrypt that ciphertext, you need to capture the $tag variable resulting from the openssl_encrypt() invocation and feed it into the openssl_decrypt() invocation. You did not do that, hence the complaint about the missing tag. Note that the tag (typically) contains non-readable characters so it is more convenient to store it in a base64 encoded format.
In addition to the $tag variable, you should also provide the same value for the $iv variable to the openssl_decrypt() method as you used in the openssl_encrypt() invocation. Again, base64 encoding makes that easier.
A quick test below demonstrates all this, where I first modified your script to print more stuff and then used the provided script to decrypt:
$ php test1.php
iv base64-ed: vBKbi8c6vCyvWonV
plaintext: text to be encrypted
ciphertext base64-ed: z28spOd3UEDmj+3a8n/WK11ls7w=
GCM tag base64-ed: OIAggQCGUbPgmPN6lFjQ8g==
$ php test2.php
decrypted ciphertext: text to be encrypted
where the code for test2.php is the following:
$cipher = "aes-128-gcm";
$key = "0123456789abcdefghijklmnob123456";
$option = 0;
$iv = base64_decode("vBKbi8c6vCyvWonV");
if (in_array($cipher, openssl_get_cipher_methods())){
$encryptedData = "z28spOd3UEDmj+3a8n/WK11ls7w=";
$tag = base64_decode("OIAggQCGUbPgmPN6lFjQ8g==");
$decryptedData = openssl_decrypt($encryptedData,$cipher,$key,$option,$iv,$tag);
echo("decrypted ciphertext: ".$decryptedData."\n");
}
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.
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.
According to the documentation of OpenSSL ( https://www.openssl.org/docs/apps/enc.html#OPTIONS ) they expect a hex-digit value for key and iv; does that mean only numbers? or will a md5 hash do? (Because a md5 doesn't seem reversible)
Note i'm mentioning key and iv because $password in the PHP function openssl_encrypt is actually the key.
(Almost) straight from PHP comments ( http://php.net/manual/en/function.openssl-encrypt.php )
function strtohex($x)
{
$s='';
foreach (str_split($x) as $c) $s.=sprintf("%02X",ord($c));
return($s);
}
$source = 'It works !';
$iv = substr( md5( "123sdfsdf4567812345678" ), 0, 16 );
$pass = '1234567812345678';
$method = 'aes-256-cbc';
echo "\niv in hex to use: ".$iv;
echo "\nkey in hex to use: ".strtohex($pass);
echo "\n";
file_put_contents ('./file.encrypted',openssl_encrypt ($source, $method, $pass, true, $iv));
$exec = "openssl enc -".$method." -d -in file.encrypted -nosalt -nopad -K ".strtohex($pass)." -iv ".$iv;
echo 'executing: '.$exec."\n\n";
echo exec ($exec);
echo "\n";
Your first link is about the command-line tools, not the PHP functions. You'd have a hard time throwing binary data in a terminal, hence why the key there has to be hex-encoded.
In PHP however, openssl_encrypt() and openssl_decrypt() expect a raw binary string.
The documentation is also misleading in that it mentions a 'password' instead of 'key'. You've noticed that, but an encryption key is not something that you should just type in via your keyboard and md5()-ing anything is also never the answer for an encryption key.
The key has to be randomly generated via openssl_random_pseudo_bytes() (or at least that's the most convenient way for your case):
$key = openssl_random_pseudo_bytes(32);
(the same goes for IVs as well)
If you need to hex-encode the resulting $key, just pass it to bin2hex(), but the example that you gave is a bit broken ... you're doing double encryption. Encrypting the file contents via PHP is enough, you don't need to deal with the command line.
Please note that my answer is far from the whole story about doing encryption. You should also add authentication, proper padding, think carefully of how to manage & store your keys, etc.
If you want to learn about it, here's a fairly short, but still descriptive blog post that gives the right answers to key points that you should cover: http://timoh6.github.io/2014/06/16/PHP-data-encryption-cheatsheet.html
If what you need is to simply get the job done - use a popular encryption library, don't write your own.
It took me some time to work with the openssl documentation. Finally I had the solution to return encoded and decoded as 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;
}