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.
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.
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 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.
Working with OAuth and encrypting the keys with the following function with a string which we'll call 'foo' (actually an OAuth token)
public function encrypt( $text )
{
// add end of text delimiter
$data = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $this->key, $text, MCRYPT_MODE_ECB, $this->iv );
return base64_encode( $data );
}
When I decrypt it using the inverse function, I end up with:
Function:
public function decrypt( $text )
{
$text = base64_decode( $text );
return mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $this->key, $text, MCRYPT_MODE_ECB, $this->iv );
}
Result:
foo%00%00%00%00%00%00%00%00%00%00%00%00%00%00
Edit:
Looking at it a little more, I realized that it is actually URL encoding to %00, which means that my strings are somehow being padded by null characters? So I am currently using trim() to get rid of them, but I would like to understand why this is happening.
Rijndael is a block cypher, which means that it operates on chunks of data of a particular length (128 bits in this case). This means that if the length of the input text is not a multiple of the block size, it must be padded out to fit. In this case, the padding is zeros; there are a number of other possible padding schemes that could be used, but if you want them with PHP's mcrypt you'll have to apply them manually.
You can Fix it with this method to get rid of padding characters: In our case we are using Zend.
$filter = new Zend_Filter_Decrypt(array('adapter' => 'mcrypt'));
$filter->setVector($lpt->_seed);
str_replace("\x0", '', trim($filter->filter(base64_decode($textToDecrypt))));
MCRYPT_MODE_ECB means that you are using ECB, a block cipher mode of operation. Block ciphers can be handled either for block cipher modes of operation or for stream cipher modes of operation. Common block cipher modes are ECB and CBC, a common stream cipher mode is CTR, better known as counter mode operation.
MCRYPT_RIJNDAEL_128 is an implementation of AES. AES is the Rijndael cipher with a block size of 128 bits, and three possible key sizes, 128, 192 and 256 bits. So if you use a block cipher mode of encryption then you need to divide up the plain text is sizes of 128 bits - 16 bytes - each. Of course this leaves you with the question what to do when the last block is not 16 bytes.
PHP's mcrypt_encrypt more or less leaves this up to the user. It pads with 00 valued characters if the block is not full up to the block size. This is fine if the input is a string; you can simply trim off the 00 characters from the returned string. If the input is however binary data that ends with a 00 character than that character is lost (+ any other character that is taken from the start and end of the string of course). You can also send the length of the string encrypted together with the plaintext of course.
For a better scheme you only have to look at PKCS#7 padding. Multiple code snippets for implementing the padding can be found in the comments section of mcrypt_encrypt.
mcrypt_encrypt currently does not seem to support stream modes for AES, so that option is out if you want to keep with the PHP mcrypt library.