I am trying to apply PKCS7 padding to my PHP code. I derived my code from this gist
https://gist.github.com/Halama/5956871
The blocksize is expected to be 16 bytes.
The data is "password" with a length of 8 bytes.
After getting the pad, it will append it at the end of the data to be encrypted.
$blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, $thisMCRYPT_MODE_CBCmode);
$pad = $blockSize - (strlen($data) % $blockSize);
$data = $data . str_repeat(chr($pad), $pad);
The problem is there are (a lot) of instances that where the data fails to decrypt it.
Provided below are base64 encoded sample data. The first 16 bytes of the decoded sample represents the IV
working: cjg1RYWxlc8bDH2de43t0bv1ug36i8ayjWDQTela938= (pad length: 8)
not working: 9wWI+MyYj5ZVj2sC4xr7EgOsgNSoeTZW1yM8ddmqg18= (pad length:
122)
The pad length mentioned above is retrieved using this
$pad = ord($data[strlen($data) - 1]);
I am using mcrypt_enrypt to encrypt the string "password". The key I am using for mcrypt is
lGbsVE+qVO1P2ue0iCjrTPMU5hKX9aHE7r1aUUeqFag=
The padding/unpadding routine looks correct. What it does not provide is a safeguard against padding values higher than the block size.
If a ciphertext is decrypted using a wrong key, or if the ciphertext is corrupted (and for short sized ciphertext, even if the IV is incorrect), the result will be a (padded) plaintext that has seemingly random data. So the last byte can have any random value during the unpadding of the incorrect result.
To protect against such failures, use a MAC value over the ciphertext, preferably using a different key. For now, the issue is not likely to be the (un)padding routine.
solved it. the "+" signs in the base64 encoded data is being converted to spaces when transported through http thus resulting into different values.
What I did is the client encoded the binary data to base64 and passed it through urlencode() function. The PHP side handled the data by using rawurldecode so it will ignore the "+" signs.
Related
I am using AES 256 cbc method to encrypt my files.
The column which I am encrypting is called 'Name'.
previously before encrypting I had set the varchar length in phpmyadmin for 'Name' to be 20. when I was trying to encrypt , I saw it was short and the entire encrypted string was not getting inserted in the database.
So I changed the size of varchar to 50 but still the length is small.
I have to do this for other column as well. How do I determine efficient length for 'Name' column.
I am using randomized IV in the encryption as can be seen from the below example.
$encryptionMethod = "AES-256-CBC";
$secretHash = "25c6c7ff35b9979b151f2136cd13b0ff";
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod));
//To encrypt
$encrypted = openssl_encrypt($textToEncrypt, $encryptionMethod, $secretHash,false,$iv);
$encryptedMessage = $encrypted . ':' .base64_encode($iv);
during decryption I use
$parts = explode(':', $encryptedMessage);
// Decrypt the data
$decryptedMessage = openssl_decrypt($parts[0], $encryptionMethod, $secretHash, 0, base64_decode($parts[1]));
echo $decryptedMessage;
since the IV is appended to the encrypted string , how would I be able to calculate the length needed to be defined in the database for the column 'Name'.
The block size of AES is 16 bytes, so you you'll need
the size of your input, rounded up to the closest multiple of 16
plus, if the input is already a multiple of 16, one block size for the PKCS#5 padding
plus 16 bytes for the IV
Note that this doesn't necessarily apply to other cipher modes1.
So for 20 bytes of input you'll need a total of 48 bytes. However, you are also base64 encoding the result, which requires at least 33% more space (i.e. you should be storing the raw bytes if you care about space).
You should always concatenate before encoding, otherwise you often waste space with multiple padding byte sequences. If your input is 20 bytes long, encoding the 32 byte ciphertext by itself produces 44 bytes, and encoding the IV produces 24 bytes (both need padding). Concatenating before encoding produces only 64 bytes.
Concatenating before encoding also doesn't require the delimiter, because the length of the IV is known.
1 AEAD ciphers, such as GCM, are generally preferable over CBC, but require more space for storing the authentication hash, obviously.
I am trying to encrypt hexadecimal data with openssl aes-256-cbc in php language. My data length is 32 byte and key length is 32 byte and iv is 16 byte 0.
I used openssl_encrypt function to encrypt the hexadecimal data but function output so long I expected. I don't know where is my fault.
Here is the code below:
$plainData = "00A40800063D103D202F2400";
$encryptionMethod = "aes-256-cbc";
$encryptionKey = "395f426c0e5bd914375837483b791d80854dd9a19dd86fd189e94ccade60c5b8";
$iv = "0000000000000000";
$encryptedData = openssl_encrypt($plainData, $encryptionMethod, $encryptionKey, $iv);
Output: 6e6469763877536e534a4f677168716f67692f684a4166315767534951764f645a575044554f6f4162763333332f6c516d6a397635723566713259444f6e79586137586e366e4f476e7a46765a4b45302b4b4855676961786757556361373932766869584453385749726f3d
Expected output length is 32 byte but above output is 216 character string.
Where is the problem above code block?
The length is different in size because of so called padding. The padding used by default is PKCS#7 compatible padding. This padding is always added; this way the padding can be distinguished from possible (binary) plaintext.
If you already know that the ciphertext is always a multiple of the block size then you can use OPENSSL_NO_PADDING as option both during encryption and decryption; you will then be responsible for making sure that the plaintext is a multiple of the block size during encryption.
It is possible to use AES-CBC with an all zero IV, but remember that this leaks information about the plaintext: repeated plaintext blocks at the start of the plaintext message can be identified. The only way to reliably use an all zero IV is to make sure that the first plaintext block is never identical or to change the key on every call.
Also note that CBC mode is malleable - if the ciphertext or plaintext can be altered by an attacker the simple encryption mode you're describing is bound to have security issues.
Basic Facts:
$algorithm = MCRYPT_BLOWFISH;
$mode = MCRYPT_MODE_CBC;
$randSource = MCRYPT_DEV_URANDOM;
Note
This is not a strict coding question.
Context:
CentOS 7, Apache 2.4.12, & PHP 5.6.20.
I am making an HTML email with a "verify your email address" link that allows a registration process to complete. Everything on my virtual private server is UTF-8, and all form and query string input is processed with multi-byte (mb) funcions.
Background
As an experiment (I know about the age and state of the mcrypt library), I am attempting to decrypt Blowfish encrypted query string parameters. Assume that on the way up, the encryption sequence works perfectly and I am receiving email with the link.
On the way down, the hmac_hash() signing (SHA-512, just for this experiment) is working and I am able to separate each independent message (32 characters) from its hash checksum (128 characters). Base64 decoding of the separated message portion is working. For each parameter, I am left with the composite cipher text, where composite cipher text equals the IV + base cipher text. Assume I use a version of substr() to obtain the IV and the base cipher text independently (which is par for the course).
Problem
PHP: Warning mcrypt_generic_init(): Iv size is incorrect; supplied length: 12, needed: 8
Assume I have combed the PHP manual and Stackoverflow. Assume I have looked at other questions similar, but not exactly like this one. Assume I have searched the Internet to no avail. Assume I have enough experience to setup mb_string properly. Assume that I will take care of mcrypt padding when I get past this current problem.
Could multi-byte issues be interfering with decryption?
Could base64 encoding the IV + base cipher text corrupt the IV?
Could base64 padding be an issue?
Should I be specifying a more specific MCRYPT_BLOWFISH_*?
Why does the blowfish IV size report 8 bytes, but rarely produces an 8 byte IV?
Which substr() should I use, substr() or mb_substr(), for a setup that leans towards making everything UTF-8 and processes all other input as multi-byte UTF-8. I know that is an odd question, but all of the PHP Manual mycrypt decryption sequence examples use substr(), and none use mb_substr(). Everything on my site works with mb_functions when possible, and I would not mind using substr() if it solved my problem, but it does not solve it. When I use mb_substr(), I get the following warning.
PHP: Warning mcrypt_generic_init(): Iv size is incorrect; supplied length: 11, needed: 8
Does anyone have any experience with this exact issue? Constructive answers will be rewarded!
Latest
Above is an example Blowfish hash that I am trying to reconstruct from an array, received via a SHA512 HMACed, symmetricly Blowfish encrypted (CBC), url safe Base64 encoded, urlencoded, query string (phew!).
Below, is what the strings for the query string (having chopped up the blowfish hash above) look like after encrypting, signing, and base64 encoding, but before being urlencoded. Each one is 128 characters long (each string gets longer as you do more stuff).
Above is the Base64 decoded and Blowfish decrypted array derived from the query string (Obviously, there are security steps in between this result, but I am just trying to show the latest state of things.) Something is not right. Encryption appears to work without any errors. Decryption does not produce any errors either. The plain text is just wrong. If I join/implode these elements, they will not be like the Blowfish hash above.
I would guess that the issue will hide somewhere with the UTF-8 encoding, as you use it in incorrect contexts. It could also be that your framework does some magic for all use-cases. This could be too much and generally end up in security hole or just bugs like that, as you don't do what really needs to be done when it really needs to be done.
Strings in PHP are just collections of bytes. You can store text there, in encoding of your choosing, or you could just store binary data there, like images. PHP knows neither what kind of data is in what string nor what encoding is used there. This is up to developer to track this information.
When working with encryption, you get binary data when generating random strings or encrypting some payloads. It's saved in strings, but it does not have UTF-8 encoding, as it's just bytes. I wouldn't even say that it's encoding is ISO-8859-1, as this would mean that byte 77 (0x4D) stands for letter "M". But for real, it's just numbers - 77 does not stand for any letter at all.
One more thing to add - for ASCII symbols (Latin letters, digits etc. - 0-127 byte values) it takes one byte to represent that symbol in UTF-8 encoding (same as in ISO-8859). So as far as you pass base64_encoded data, you shouldn't worry too much about it. mb_substr will also work in the same way as substr. But! for the binary data, you cannot use mb_* functions, as it works with characters. For example, if encrypted data is two bytes 0xC5 0xA1, it's only single symbol in UTF-8. Encryption works with bytes (up until the final result, which could be anything - even binary files), not characters.
As you've not provided any code, I've put some for you - I hope it will help with your issue (if it's still relevant at all).
To show passing parameters in URL, there are two files: encrypt.php and decrypt.php. Save to a directory, run php -S localhost:8000 in it and go to http://localhost:8000/encrypt.php
encrypt.php:
<?php
// mcrypt_enc_get_key_size($td) gives 56, so it's longest that this key can be
$key = 'LedsoilgarvEwAbDavVenpirabUfjaiktavKekjeajUmshamEsyenvoa';
$data = 'This is very important data, with some š UTF-8 ĘĖ symbols';
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
// create random IV - it's just random 8 bytes. You should use random_bytes() instead if available
$ivSize = mcrypt_enc_get_iv_size($td);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $data);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
// payload that you want to send - binary. It's neither UTF-8 nor ISO-8859-1 - it's just bytes
$payload = $iv . $encrypted;
// base64 to pass safely
$base64EncodedPayload = base64_encode($payload);
// URL encode for URL. No need to do both URL-safe base64 *and* base64 + urlencode
$link = 'http://localhost:8000/decrypt.php?encryptedBase64=' . urlencode($base64EncodedPayload);
// in fact, just for the reference, you don't even need base64_encode - urlencode also works at byte level
// base64_encode takes about 1.33 more space, but urlencode takes 3 times more than original for non-safe symbols, so base_64 will probably be shorter
$link2 = 'http://localhost:8000/decrypt.php?encrypted=' . urlencode($payload);
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<pre><?php
var_dump('Data:', $data);
var_dump('Data size in bytes:', strlen($data));
var_dump('Data size in characters - smaller, as 3 of the characters take 2 bytes:', mb_strlen($data, 'UTF-8'));
var_dump('Encrypted data size in bytes - same as original:', strlen($encrypted));
var_dump('Encrypted data size in characters - will be pseudo-random each time:', mb_strlen($encrypted, 'UTF-8'));
var_dump('IV base64 encoded:', base64_encode($iv));
var_dump('Encrypted string base64 encoded:', base64_encode($encrypted));
?></pre>
<!-- Link will not contain any special characters, so htmlentities should not make any difference -->
<!-- In any case, I would still recommend to use right encoding at the right context to avoid any issues if something changes -->
Link to decrypt<br/>
Link to decrypt2
</body>
</html>
decrypt.php:
<?php
$key = 'LedsoilgarvEwAbDavVenpirabUfjaiktavKekjeajUmshamEsyenvoa';
if (isset($_GET['encryptedBase64'])) {
// just get base64_encoded symbols (will be ASCII - same in UTF-8 or other encodings)
$base64EncodedPayload = $_GET['encryptedBase64'];
$payload = base64_decode($base64EncodedPayload);
} else {
// just get binary string from URL
$payload = $_GET['encrypted'];
}
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
$ivSize = mcrypt_enc_get_iv_size($td);
$iv = substr($payload, 0, $ivSize);
$encrypted = substr($payload, $ivSize);
mcrypt_generic_init($td, $key, $iv);
/* Decrypt encrypted string */
$decrypted = mdecrypt_generic($td, $encrypted);
/* Terminate decryption handle and close module */
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<pre><?php
var_dump('IV base64 encoded:', base64_encode($iv));
var_dump('Encrypted string base64 encoded:', base64_encode($encrypted));
var_dump('Result:', $decrypted);
?></pre>
</body>
</html>
I used Rijndael algorithm to encrypt and decrypt my database password. I kept encoded password in another file. Here I reduced the code to get relevant :
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, PASSWORD_SALT, 'mypassword', MCRYPT_MODE_ECB);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, PASSWORD_SALT, $encrypted, MCRYPT_MODE_ECB);
// !! The value of $decrypted is "mypassword " i.e. "mypasswordNULLNULLNULLNULLNULL"
'mypassword' is converted to 'mypassword' + 6xNULL. The decrypted code is containing null.
I wrote this code 1 year ago and everything was working fine. But now, when version of all technologies have changed, I am having problem.
It was always so.
According to documentation:
The data that will be decrypted with the given cipher and mode. If the size of the data is not n * blocksize, the data will be padded with '\0'.
So either you trim your data with \0, or you have to store the original length anywhere and then cut the padded 0 off.
Using the Rijndael-128 algorithm mcrypt_encrypt() will always return multiples of 16 bytes. If your plain text ist not an exact multiple of 16 bytes the data will be padded with zero bytes so it will be a multiple of 16.
Those zero Bytes will appear in the decrypted text as well. You have to remove those by using:
$decrypted = rtrim($decrypted, "\0");
Note 1: Rijndael is a block encryption algorithm which operates on block of a fixed size. This is why padding may be necessary.
Note 2: Encryption is only suitable for encoded input that never ends with value 00h (because of default zero padding). Taken from example code at http://php.net/manual/en/function.mcrypt-encrypt.php
I am in need of symmetric encryption and decryption in a pair of PHP scripts. I am using mcrypt_encrypt and crypt_decrypt. To test this, I have the following code:
$encrypted_token = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $ENCRYPTION_SECRET, $refresh_token, MCRYPT_MODE_ECB);
$encrypted_encoded_token=base64_encode($encrypted_token);
echo "\nEncrypted Token: " . $encrypted_encoded_token . "\n";
To test this, in the same PHP script I do the following:
$decoded_refresh_token = base64_decode($encrypted_encoded_token);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $ENCRYPTION_SECRET, $decoded_refresh_token, MCRYPT_MODE_ECB);
echo "\nDecrypted Token After decrypt: " . $decrypted . "\n";
My input $refresh_token is a long string of characters that looks something like this (only longer):
AQCNKTZhaWTmUl3WvHOtlkv2Vc-Ybkl24D5Zp1lZPLBraTxrr-YQhErXrI2IWWEIWk5lnBc1k
The Decrypted Token After decrypt looks something like this:
AQCNKTZhaWTmUl3WvHOtlkv2Vc-Ybkl24D5Zp1lZPLBraTxrr-YQhErXrI2IWWEIWk5lnBc1k�������������������
My $ENCRYPTION_SECRET is 32 characters long. The base64_encode and decode are because I am json_encoding and decoding the token in a Post.
What am I doing wrong?
Those extra characters are indeed bytes valued zero. PHP's mcrypt uses 0..n - 1 bytes of zero padding, where n is the blocksize. In other words, if the plaintext is already a multiple of the blocksize then it doesn't pad. Otherwise it pads up to the block size.
Now you are using MCRYPT_RIJNDAEL_256 which is not AES, but Rijndael with a block size of 256 bits. So the number of bytes added are 0..31 bytes. If you view those as a string they get converted to question marks or removed, depending on what you view the string with. In the C-library of mcrypt that probably made more sense as zero terminates a null-terminated string.
Nowadays the ad-hoc standard is PKCS#7 padding, which adds 1..blocksize of padding bytes. If x is the number of padding bytes then x is also the value of the bytes added. PKCS#7 padding is deterministic, i.e. you can always unpad, no matter the value of the plaintext. Zero padding behaves largely the same way, unless the plaintext contains zero characters at the end. This is however never the case for printable strings (in ASCII, latin or UTF-8).
Finally, to remove the padding, simply perform rtrim(plaintext, "\0") and you'll get the original string.