I searched for how to use openssl_encrypt correctly, and found several stackoverflow questions and answers. However, I'm afraid I just can't get it to work.
My current code looks like this:
$encryption_key = openssl_random_pseudo_bytes(32);
$iv = openssl_random_pseudo_bytes(32);
$encrypted = openssl_encrypt($data, 'AES-256-CTR', $encryption_key, 0, $iv);
$error = openssl_error_string();
var_dump($encrypted, $error);
The var_dump just gives me bool(false) bool(false). It doesn't work and I don't get any error.
Anyone can help me?
EDIT: I don't know what exactly the problem was, but apparently using AES-256-CTR did not work on the system. Using AES-256-CBC with above code works just fine...
It doesn't give an error, because you forgot to enable error reporting in PHP. If you did, you would have seen:
E_WARNING : type 2 -- openssl_encrypt(): IV passed is 32 bytes long which is longer than the 16 expected by selected cipher, truncating -- at line 6
AES is a block cipher with a fixed block size of 128 bit or 16 byte. The nonce for CTR mode (here called IV for initialization vector) must be at most as long as the block size. For optimal security, it is a good practice to use a nonce that is 96 bit or 12 byte long. The remaining 32 bit or 4 byte can be filled with zeros:
$iv = openssl_random_pseudo_bytes(12) . "\0\0\0\0";
If you use CBC mode, then you need to use:
$iv = openssl_random_pseudo_bytes(16);
Related
I have a decryption function that just started failing for unknown reasons. The error is "openssl_decrypt(): IV passed is only 6 bytes long, cipher expects an IV of precisely 16 bytes". I have found multiple suggestions to "add base64 encoding", which I already had.
My function is
function decryptThis($data) {
$encryptionKey = "encryptionKey";
$encryption_key = base64_decode($encryptionKey);
list($encrypted_data, $iv) = array_pad(explode('::', base64_decode($data), 2),2,null);
return openssl_decrypt($encrypted_data, 'AES-128-CTR', $encryption_key, 0, $iv);
}
Previously a lot of the mcrypt and OpenSSL functions simply right padded the IV with zeros. However nowadays we try and keep to the specifications for the cipher / mode of operation. If you want to return to the previous situation then you'll have to right pad yourself.
It seems that the fix was a bit too generic or that the author was a bit overzealous as CTR really doesn't take an IV but a nonce. In that sense it would be perfectly reasonable to right-pad a nonce with zeros, as long as the key / nonce combination remains unique.
However, many CTR (counter mode) implementations assume that you provide an entire block consisting of the nonce + starting counter value.
Note that OpenSSL is a C library, so just providing an IV value of 6 bytes would result in a buffer overrun, possibly leading to broken - or worse: irreversible - encryption.
I need help for decryption algorithm in php.
I got error when I pass wrong encrypt data in my decryption function. so i need to handle error using exception. can it possible?
I tried to this code but no luck
function decrypt($encrypt_data) {
$key = ENC_KEY;
$encryption_key = base64_decode($key);
list($encrypted_data, $iv) = explode('::', base64_decode($encrypt_data), 2);
try {
if(openssl_decrypt($encrypted_data, 'aes-256-cbc', $encryption_key, 0, $iv)) {
throw new customException($encrypted_data);
}
}
catch (customException $e) {
echo $e->errorMessage();
}
}
But this function gives me error:-
Warning: openssl_decrypt(): IV passed is only 7 bytes long, cipher
expects an IV of precisely 16 bytes, padding with \0 in abc.php
As Magnus indicates, the key and IV should be specific sizes for AES in CBC mode. The key size should be 32 bytes for AES-256 and the IV is identical to the block size: 16 bytes.
Generally any language will validate the size of the key and IV before using it, but PHP has had the nasty habit (for both the OpenSSL and mcrypt API's) of padding it with zero's if it is too small or cutting part of it when it is too large. This is probably because the underlying C-libraries do not validate the size either as the key and IV are passed as pointers rather than arrays or objects.
To fix this you should make sure that the key is exactly 32 randomized bytes (apparently it is because there is no warning) and that the IV consists of exactly 16 randomized bytes.
Note that it is a security requirement for CBC mode that the IV consists of 16 bytes that are fully unpredictable to an adversary. Just fixing the error by padding the IV with zeros yourself is therefore not recommended. Although the use of 7 bytes as IV is unlikely to directly lead to exploits you should fix the protocol rather than to rely on workarounds in the code.
As indicated, other cryptographic API's (for different languages) will strictly enforce to use keys and IV's of the correct size; if you keep using a 7 byte IV then all those runtimes may need workarounds as well.
I am working on a project that requires the AES encryption of a soap envelope using the requirements below.
Encryption key: myKey-1234567abcdef
AES-256 encryption
128 block size
PKCS7 padding
16 bit vector (vector is attached before encrypted message)
Cipher Block Chaining (CBC)
This is what I tried:
$key = 'myKey-1234567abcdef';
$encryptionMethod = "AES-256-CBC";
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = substr(mcrypt_create_iv($iv_size, MCRYPT_RAND), 0, 16);
$xml = openssl_encrypt($xml,$encryptionMethod, $key, 0, $iv);
I am currently getting a 400 bad request error and there seems to be lots of options for argument constants mcrypt functions, was wondering if my implementation satisfies the needs for padding, block size and vector?
I appreciate any suggestions, thanks in advance!
First: Your key isn't an appropriate length for AES-256. I realize the key you're using here is an example, but make sure that the one you've been provided is 32 characters (256 bits) long. If it isn't, ask the recipient for clarification.
Second: You're mixing the mcrypt and openssl extensions inappropriately here. You shouldn't be using mcrypt anyway, as it's unmaintained, sometimes broken, and will be removed entirely in PHP 7.2. Instead, hard-code the IV size as 16 and use openssl_random_pseudo_bytes to generate it:
$iv = openssl_random_pseudo_bytes(16);
$xml = openssl_encrypt($xml, $encryptionMethod, $key, 0, $iv);
Third: By default, openssl_encrypt() encodes its output as Base64. The recipient may not be expecting this. Pass OPENSSL_RAW_DATA as the fourth argument to openssl_encrypt() (replacing 0) to get unencoded output.
I have a string encrypted with AES 128 CBC, which I need to decrypt. I have the key which seems to work fine. The problem is with the initialization vector (IV).
The IV is 16 bytes long,
B409678003171307B8B8B8B8B8B8B8B8
but when I add it to my script, OpenSSL truncates it saying it's 32 long like so:
openssl_decrypt(): IV passed is 32 bytes long which is longer than the 16 expected by selected cipher, truncating
I guess it means it is 32 characters long - but how do I make it understand it's just 16 bytes?
UPDATE: using hex2bin on the IV solved the truncating - but my openssl_decrypt yields nothing. Also did the hex2bin on the key, still no output. Simplified the code to make it easier to find the problem:
<?php
$str = "7F53B967F1BF7C9EC26B0C405E453ABD";
$k = "F71D4590A6E6E219EBBE8BFE9D3DC21A";
$intv = "B409678003171307B8B8B8B8B8B8B8B8";
$key = hex2bin($k);
$iv = hex2bin($intv);
$plaintext = openssl_decrypt($str, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
print_r($plaintext);
?>
So, is the hex2bin the wrong way to go? Or is there something wrong in how I use the openssl_decrypt? There are NO errors in the PHP error_log.
Thanks in advance!
OK this appears to achieve the same results as the web-based service linked by the OP. The key steps are
a) in addition to $k and $intv, make sure you also convert the encrypted $str to binary from its hex representation
b) supply the extra flag OPENSSL_ZERO_PADDING
c) when you echo or var_dump or print_r the output, make sure you do a conversion back to hex so the output is readable
$encrypted = "7F53B967F1BF7C9EC26B0C405E453ABD";
$k = "F71D4590A6E6E219EBBE8BFE9D3DC21A";
$intv = "B409678003171307B8B8B8B8B8B8B8B8";
$str = hex2bin($encrypted);
$key = hex2bin($k);
$iv = hex2bin($intv);
$decrypted = openssl_decrypt($str, 'AES-128-CBC', $key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv);
$str_decrypted = bin2hex($decrypted);
var_dump($str_decrypted);
output:
string(32) "2f2f0c1335000000046d372b27230f15"
NOTE: I can't be sure that this is in fact the decrypted form of the originally encrypted data. It just matches the web-based service. I'm assuming the value you linked is in fact the correct value. Simply adding the OPENSSL_ZERO_PADDING flag to your original code can get rid of the errors but the output will be different. Maybe try some experimenting.
I suppose I should deal with my first statement first. Is there an inconsistency in the way PHP does AES-128-CBC encryption when you use the Open SSL library?
I am asking this because if you look through RFC3602 for AES then you'll find some test vectors in section 4. The first one I tried was:
Case #1: Encrypting 16 bytes (1 block) using AES-CBC with 128-bit key
Key : 0x06a9214036b8a15b512e03d534120006
IV : 0x3dafba429d9eb430b422da802c9fac41
Paintext : "Single block msg"
Cphertext: 0xe353779c1079aeb82708942dbe77181a
Using the open SSL library with the following code:
echo bin2hex(openssl_encrypt($str, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $iv));
You actually get back:
e353779c1079aeb82708942dbe77181ab97c825e1c785146542d396941bce55d
Now this is all well and good and decrypts back just fine, and the first 32 bytes are the expected cipher text "e353779c1079aeb82708942dbe77181a". However, there appears to be another 32 bytes at the end of the cipher text returned from the Open SSL library. Why is this? This question becomes relevant next.
As part of something I am doing for fun I am trying to implement AES CBC mode encryption sort of from scratch. The code I currently have for doing this is (based on the RFC previously referenced and on CBC diagrams at the "Block cipher mode of operation" Wikipedia page):
public function cbcEncrypt($str, $iv, $key) {
$bl = 16;
$bs = str_split($str,$bl);
$pv = null;
$r = '';
foreach($bs as $b) {
if($pv === null) $pv = $iv;
if(strlen($b)<$bl) $b = $this->pkcs7Pad($b, $bl);
$pv = openssl_encrypt($b^$pv, 'aes-128-ecb', $key, OPENSSL_RAW_DATA);
$r .= $pv;
}
return $r;
}
At the moment I'm pretty sure this code wont work on strings longer than the current test 16 byte string anyway, however, I ran this code side by side with the Open SSL implementation to compare. The return value from by CBC implementation is:
e353779c1079aeb82708942dbe77181a8b1ccc6f8cd525ffe22d6327d891a063
When called via:
$crypto = new CryptoTools();
$enc = $crypto->cbcEncrypt('Single block msg',hex2bin("3dafba429d9eb430b422da802c9fac41"),hex2bin("06a9214036b8a15b512e03d534120006"));
Again, the first 32 bytes are correct, but the second 32 bytes are again added but completely different from the Open SSL implementation of CBC. Any clues as to why?
Also, the Open SSL implementation of ECB mode returns a 32 byte string, which means CBC encrypting strings currently greater than 16 bytes in length the new value of $pv will be 32 bytes in length and then it will be XOR'd against the second plain text block when in reality shouldn't $pv always be 16 bytes in length? Is it usually just accepted that you truncate $pv from 32 to 16 bytes?
Just in case it helps, the pkcs7Pad method in the code above looks like:
public function pkcs7Pad($str, $b = 8) {
if(trim($str) == '') return false;
if((int)$b < 1) return false;
$p = $b - (strlen($str) % $b);
return $str . str_repeat(chr($p),$p);
}
Any help would be greatly appreciated. Needless to say there's not much documentation on this as people aren't usually stupid enough to try and reinvent the wheel, certainly in PHP...
This has to do with padding. The test vectors don't use any padding at all, while the openssl_encrypt function and your function apply PKCS#7 padding.
PKCS#7 padding is specified so that padding is added in all cases. When the plaintext length is a multiple of the block size, a full block of padding is added, which is why the ciphertext is 32 bytes long when the plaintext is 16 bytes.
Can't immediately see anything wrong with your function (though I'm not familiar with PHP), but you could try padding $str before splitting it instead of checking whether each block requires padding.
Ah ha! Got it! Thanks to ntoskrnl's answer and this Stackoverflow question I managed to come up with a solution! If you do:
echo bin2hex(base64_decode(openssl_encrypt($str, 'aes-128-cbc', $key, OPENSSL_ZERO_PADDING, $iv)));
You will match the test case provided by the RFC! You just need to remember that because you're now not doing OPENSSL_RAW_DATA you will get back a base64 encoded string and so will need to base64 decode it before converting it to hex to match the test case!
TLDR: it was a padding issue, just not an obvious one!