Exception handling for openssl decrypt In php - php

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.

Related

openssl_decrypt fails. cipher expects an IV of precisely 16 bytes

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.

Decrypt from AES-ECB ciphertext created with php/mcrypt using node.js

I have an encrypted message using MCRYPT_RIJNDAEL_128, ECB mode, and Base64, I've tried decrypting using crypto(https://www.npmjs.com/package/crypto) with the following code:
var text = '';
var decipher = crypto.createDecipheriv("aes-128-ecb","SOMEKEYHEREWITHLENGTH32ooooooooo", '');
text += decipher.update(data, "base64");
text += decipher.final();
The error I've been getting is: Invalid key length 32.
What should the length of key be? When I attempt to use 16 instead the error thrown is:
'TypeError: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt'
If client requires to use ECB with key of length 32, how can I make this case valid?
AES is a subset of Rijndael with a fixed block size 128 bit whereas Rijndael supports block sizes of 128, 192 and 256 bit. The 128 in MCRYPT_RIJNDAEL_128 means the block size. The 128 in aes-128-ecb on the other hand means the key size. Both AES and Rijndael support key sizes of 128, 192 and 256 bit.
If you have a 32 character key and this key is not hex-encoded to reach those 32 characters then it is possible that you actually want aes-256-ecb.
Keep in mind that the key encoding might be different than the one in PHP so might need to parse the key into a Buffer with a specific encoding.
Also, please don't use ECB mode. It is insecure. You should use at least CBC mode with a random IV. Since the IV does not need to be secret, you can simply send it along with the ciphertext as a prefix for example. You would then need to slice it off before decryption.

AES encryption inconsistency in PHP using the Open SSL library and coding CBC mode from scratch

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!

PHP (mcrypt_encrypt) Ruby (AES-256-CBC) Encryption Different Results

I have been trying to encrypt a string in PHP and Ruby using the same key and iv but I always got different results.
Below is the PHP Code
$data = "This string needs to be encrypted";
$key = "1234567887654321abcdefghabcdefgh";
$iv = "1234567887654321abcdefghabcdefgh";
echo $encrypted_data = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
Below is the Ruby Code
data = "This string needs to be encrypted"
key = "1234567887654321abcdefghabcdefgh"
iv = "1234567887654321abcdefghabcdefgh"
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
aes.encrypt
aes.key = key
aes.iv = iv
encrypted_data = aes.update(data) + aes.final
Could somebody please help me get the same encrypted data in PHP and Ruby? I encrypted some data in PHP and then decrypted in Ruby but didn't get the data back. So I think the issue is PHP and Ruby encryption and decryption mechanism work differently. Please correct me if I am wrong. Thanks
Don't hard code IV's , it is insecure. IVs must be random but can be public , so just use
mcrypt_create_iv and prepend it to the front of the ciphtertext and then extract it before
decrypting
You likely have three problems
MCRYPT_RIJNDAEL_256 is nott AES. AES is a specific version RIJNDAEL that was standardized with a 128 bit block size and either 128 or 256 bit keys. MCRYPT_RIJNDAEL_256 is RIJNDAEL with a 256 bit block size. You want to use MCRYPT_RIJNDAEL_128 which is actually AES. For php, key length is just determined by the length of the key. So just give it a 256 bit ( 32 character) key and you will be fine. Note block size does not effect security really, so don't worry about the deference and just use AES with a 256 bit key: its good enough for the NSA and top secret data.
Padding. AES only takes fixed 128 bit chunks, so you must pad out the text to be a multiple of that size. PHP doesn't really bad and i believe SSL uses pkcs7 padding. Note that even with different padding schemes, for most of them the start of the cipher text should the the same there just may be garbage at the end.
String encoding. AES is defined with bit inputs, in c typically this is a byte array. Ruby and PHP use strings. I'd be willing to bet your string encodings are different.

Picking encryption cipher for mcrypt

I have few questions about this code:
<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$text = file_get_contents('path/to/your/file');
echo strlen($text) . "\n";
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv);
file_put_contents('path/to/your/file', $crypttext);
?>
It encrypts the file just fine, however it adds additional nulls at the end, so if I encrypt:
a test string is this one
and here is a new line
once decrypted becomes:
a test string is this one
and here is a new line 000000000000000
What's going on?
Second, is MCRYPT_RIJNDAEL_256 compatible with AES-128?
Finally, how would I let another party decrypt a file I've encrypted? They would need to know which encryption was used and I am not sure what to tell them.
Much of what I'm about to explain can be gleaned from #ircmaxwell's excellent slidedeck: Cryptography For The Average Developer which you should check out immediately. I'll reiterate one of his main points: Avoid writing code that deals with encryption/decryption. Unless you understand all the factors you will probably flub it.
MCRYPT_RIJNDAEL_128 closely matches AES
The Advanced Encryption Standard was created by The National Institute of Standards and Technology. NIST chose the Rijandael cipher from a competitive pool of cryptographic experts.
Common confusion: the numbers in AES and Mycrpt refer to different things
AES-128 refers to 128 bit key size
AES-256 refers to 256 bit key size
MCRYPT_RIJNDAEL_128 refers to a 128 bit cipher block size
MCRYPT_RIJNDAEL_256 refers to a 256 bit cipher block size
Key size and block size
Key Size refers to the length of the secret used to encrypt/decrypt. A 256 bit key is 32 bytes, roughly 32 characters.
Block Size is a property of block ciphers (i.e. all AES candidates) which chunk out the data to a specific size during the cipher process.
AES specifies a 128 bit cipher block
Mcrypt's Rijndaels all accept 256 bit keys
based on key/block size MCRYPT_RIJNDAEL_128 seems to conform closest to AES-256
there are other factors to consider — like how many transformation rounds occur? — it's hard to verify this aspect without digging into the source code
Mode of Operation
The mode is an important element.
Both ECB and CBC mode pad your plaintext into the block size. If you're encrypting 1 byte of data with a 128 bit block size you will get 15 null bytes. Padding opens up the possibility for a padding oracle attack.
Beyond padding ECB does not make use of an Initialization Vector (IV) which leaves you open to a potential vulnerability: prefer CBC or CFB mode.
You can avoid both problems by using CFB mode which does not need padding and therefore does not require post-decryption trimming.
Initialization Vector (IV)
When creating your IV you need a crypto-strong random source: MCRYPT_RAND is not random enough — MCRYPT_DEV_RANDOM or MCRYPT_DEV_URANDOM are preferred.
$iv_size = mcrypt_get_iv_size(
MCRYPT_RIJNDAEL_128,
MCRYPT_MODE_CFB
);
$iv = mcrypt_create_iv(
$iv_size,
MCRYPT_DEV_URANDOM
);
Finally, for your friends
In order to decrypt your friend will need to know:
the cipher — MCRYPT_RIJNDAEL_128
the block mode — MCRYPT_MODE_CFB
the IV — each element you encrypt should get a unique IV, so you'll want to append the IV to the encrypted data
the key — this is secret, and should stay the same for all encryptions; if your key gets compromised rotate it out
Only the key needs to stay secret. Depending on your transport method you should consider implementing a data integrity check to ensure the ciphertext data wasn't tampered with. Again, see #ircmaxwell's excellent slidedeck: Cryptography For The Average Developer for an example of creating an HMAC fingerprint using hash_hmac().
It should be obvious that the maintenance all of these moving parts is rife with complexity. Tread carefully.
Other ciphers
Mcrypt gives you other cipher options. Some, like DES, are not recommended. Others were candidates for AES, like Blowfish, TwoFish, and Serpent. Rijandael is a vetted, proven cipher, and is recommended.
MCRYPT_RIJNDAEL_128 is AES-128, MCRYPT_RIJNDAEL_256 is AES-256 - just another name:
[...]The standard comprises three block
ciphers, AES-128, AES-192 and AES-256,
adopted from a larger collection
originally published as Rijndael.originally published as Rijndael.[...]
[...]The Rijndael cipher was developed by
two Belgian cryptographers, Joan
Daemen and Vincent Rijmen, and
submitted by them to the AES selection
process. Rijndael (pronounced "Rhine
dall") is a wordplay with the names of
the two inventors.[...]
The \x00 characters you encounter at the end of the decrypted string are the padding required for some block ciphers (with ECB being such a block cipher). Mcyrpt uses NULL-padding internally if the input data needs to be padded to the required block length. There are other padding modes available (which have to be user-coded when using Mcyrpt), namely PKCS7, ANSI X.923 or ISO 10126. NULL-padding is problematic when encrypting binary data that may end with one or more \x00 characters because you can't detect where the data ends and the padding starts - the other padding modes mentioned solve this kind of problem. If you're encrypting character data (strings) you can easily trim off the trailing \x00 by using $data = trim($data, "\x00");.
To decrypt the data you sent to a consumer, the consumer would need to know the IV (initialization vector) ($iv), the algorithm used (MCRYPT_RIJNDAEL_256/AES-256), the encryption mode (ECB), the secret encryption key ($key) and the padding mode used (NULL-padding). The IV can be transmitted with the encrypted data as it does not need to be kept secret:
The IV must be known to the recipient
of the encrypted information to be
able to decrypt it. This can be
ensured in a number of ways: by
transmitting the IV along with the
ciphertext, by agreeing on it
beforehand during the key exchange or
the handshake, by calculating it
(usually incrementally), or by
measuring such parameters as current
time (used in hardware authentication
tokens such as RSA SecurID, VASCO
Digipass, etc.), IDs such as sender's
and/or recipient's address or ID, file
ID, the packet, sector or cluster
number, etc. A number of variables can
be combined or hashed together,
depending on the protocol.depending on the protocol.
MCRYPT_RIJNDAEL_128 is equivalent to AES-128 in mcrypt. This is documented under MCRYPT_RIJNDAEL_128 here: http://mcrypt.sourceforge.net/

Categories