AES 256 encryption PHP with Padding - php

I got the following requirements for the encryption for the API i am currently trying to access:
PKCS7 padding method
CBC encryption mode
AES key size 256, block size 128
Everytime i submit to the API with the encryption, there seems to be something wrong with the API (unfortunately no error is produced).
$Data = "GOOD!";
$aesKey = "1234567812345678";
$EncryptedData = encrypt($aesKey,$Data);
$DecryptedData = decrypt($aesKey,$EncryptedData);
echo "Orignal Data : ". $Data;
echo "<br/>";
echo "After encryption = ". $EncryptedData;
echo "<br/>";
echo "After decryption = " .$DecryptedData;
function encrypt($aesKey, $dataToEncrypt) {
$output = false;
$iv = '{{{{{{{{{{{{{{{{';
$output = openssl_encrypt($dataToEncrypt, 'AES-128-CBC', $aesKey,
OPENSSL_RAW_DATA, $iv);
$output = base64_encode($output);
return $output;
}
function decrypt($aesKey, $dataTodecrypt) {
$output = false;
$iv = '{{{{{{{{{{{{{{{{';
$dataTodecrypt = base64_decode ($dataTodecrypt);
$dataTodecrypt = $output = openssl_decrypt($dataTodecrypt, 'AES-128-CBC',
$aesKey, OPENSSL_RAW_DATA, $iv);
return $output;
}
Questions:
What exactly is PKCS7 padding method and can be implemented with php?
AES 256 is fine but what exactly does block size mean?
What exactly does IV do?

AES 256 is fine but what exactly does block size mean?
AES has a fixed block size of 128 bit. A block cipher only works on one block of a specific size. A mode operation extends a block cipher with the ability to work on multiple blocks and a padding enables it to work on plaintexts that are not a multiple of the block size.
AES-128-CBC means AES with key size of 128 bit and the CBC mode of operation. If you want to use AES-256, then you need to tell OpenSSL that: AES-256-CBC. Additionally, you need to use a key that is actually 256 bit long. Your current key is only 128 bit long.
What exactly is PKCS7 padding method and can be implemented with php?
openssl_encrypt() already does PKCS#7 padding for you and openssl_decrypt() removes it for you.
What exactly does IV do?
A random IV randomizes the ciphertext which means that encrypting the same plaintext with the same key, but a different IV produces a different ciphertext which is indistinguishable from random noise or other the same encryption with a different IV. Wikipedia has a good description what this actually does.
Keep in mind that an IV must be randomly generated for each iteration. Otherwise, an attacker who observes only the ciphertext may discover that you encrypted the same plaintext multiple times.
Keep in mind that an AES key is supposed to be quite noisy with high entropy. "12345..." looks more like a password. If you want to use passwords, then you need to derive a key from that password. PBKDF2 is a good idea with a random salt and a lot of iterations.

What exactly is PKCS7 padding method and can be implemented with php?
I am not really certain 'padding' is the phrase you mean here. While the PKCS#7 format does rely on padding, the example you provide has absolutely nothing to do with asymmetric encryption and the ASN.1 format for the PKCS#7 messaging syntax mentioned.
AES 256 is fine but what exactly does block size mean?
Block size is the bit size an encryption cipher, like AES-256, operates on per permutation.
What exactly does IV do?
An IV is short for initialization vector or for some symmetric encryption cipher implementations it can also be referred to as an nonce.
Both are used to help strengthen the resulting cipher text. You can think of them as being similar to a salt for a non-reversible hashing algorithm.
You should avoid re-using the same IV.
In regards to your example; the documentation for openssl_encrypt() states the following function usage:
string openssl_encrypt ( string $data , string $method , string $password [, int $options = 0 [, string $iv = "" ]] )
Your encryption of the plain text looks accurate (while I would choose AES-256-GCM vs. AES-128-CBC as the algorithm, blocksize & mode):
$output = openssl_encrypt($dataToEncrypt, 'AES-128-CBC', $aesKey,
OPENSSL_RAW_DATA, $iv);
Without testing I am assuming you are getting a valid base64 encoded value.
The manual for the openssl_decrypt() method states the following usage:
string openssl_decrypt ( string $data , string $method , string $password [, int $options = 0 [, string $iv = "" ]] )
While technically your implementation is correct; I would suggest the following (note the double assignment to $dataTodecrypt = $output = openssl_decrypt()):
$output = openssl_decrypt(base64_decode($dataTodecrypt), 'AES-128-CBC', $aesKey, OPENSSL_RAW_DATA, $iv);

Related

Encrypting SOAP envelope using AES

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.

Decrypt mcrypt with openssl

Since mcrypt is considered obsolete, my task is upgrading the current code to use openssl. Sounds simple, but ... after a few days of try and failure I feel like going insane.
My question to you is: Is there any way you can decrypt with openssl data previously encrypted with mcrypt? I've read so many posts on this matter and most of them say that a previous manual padding of the data was/is necessary before running mcrypt on it.
The issue is that the mcrypt-ed data is already encrypted (with the automatic null padding mcrypt provides) and resides in a database, so modification of that is not possible and/or desired.
Mentions:
the algorithm used is rijndael-128 cbc with a 32-byte key (so I'm using aes-256-cbc for openssl).
I'm using an openssl wrapper for php (php-crypto).
I've managed to make the inverse operation work (decode openssl with mcrypt) by simply stripping the end decoded characters if they were non alpha-numerical.
Manually padding the data before mcrypt-ing and then decrypting it using openssl works like a charm, but that's not the problem here.
Some code snippets:
// Simple mcrypt encrypt, decrypt with php-crypto example
// This doesn't work and produces a "Finalizing of cipher failed" error
$data = "This is a text";
$strMcryptData=mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
$algorithm = 'aes-256-cbc';
$cipher = new Cipher($algorithm);
$sim_text = $cipher->decrypt($strMcryptData, $key, $iv);
// Simple mcrypt encrypt with padding, decrypt with php-crypto
// Works and produces the correct text on decryption
$pad = $blocksize - (strlen($data) % $blocksize);
$text = $data;
$text .= str_repeat(chr($pad), $pad);
$strPaddedData=mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
$sim_text = $cipher->decrypt($strPaddedData, $key, $iv);
Slightly old, but you can solve this with a bit of work. You can tell PHP's OpenSSL that the encrypted string is not padded, and tell it to give you the raw output (So you don't have to base64 decode it, either). You can then strip nulls from the end of the resulting string if the length of the string happens to be perfectly divisible by the IV (This is a sanity check, as if the resulting string isn't divisible by the IV then it wasn't padded at all).
Be aware, this code has two major limitations:
If, at any point, you encrypted a legitimate string that ended in two or more NULL bytes then this code will not give you the same output.
If the padding of the string needed only one null byte, then this code won't strip it.
You can solve both of these if you know for a FACT that you didn't encrypt anything that ends in null bytes, you can alter the code that strips the nulls to just do a preg_replace; just make sure you anchor the regex to the end of the string so it only strips from the end.
<?php
$message = 'test';
$key = openssl_random_pseudo_bytes(16);
$iv = openssl_random_pseudo_bytes(16);
$cipher = mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
$key,
$message,
MCRYPT_MODE_CBC,
$iv
);
$plain = openssl_decrypt(
$cipher,
'aes-128-cbc',
$key,
OPENSSL_RAW_DATA | OPENSSL_NO_PADDING,
$iv
);
//try to detect null padding
if (mb_strlen($iv, '8bit') % mb_strlen($plain, '8bit') == 0) {
preg_match_all('#([\0]+)$#', $plain, $matches);
if (mb_strlen($matches[1][0], '8bit') > 1) {
$plain = rtrim($plain, "\0");
trigger_error('Detected and stripped null padding. Please double-check results!');
}
}
var_dump(
$message,
bin2hex($cipher),
$plain,
mb_strlen($message, '8bit'),
mb_strlen($plain, '8bit'),
$message === $plain
);
http://3v4l.org/kYAXn
Obviously this code comes with no major disclaimer and please test it in your use case, but someone might hopefully find this useful.
If you encrypt in mcrypt without adding PKCS7 manually, mcrypt will happily pad your plaintext with NUL bytes.
OpenSSL will do PKCS7 padding for you whenever using aes-X-cbc. The unfortunate consequence of this is that if you have AES-CBC(NULL_PADDED(plaintext)) and try to decrypt it, openssl_decrypt will attempt to remove the padding and fail.
Compare http://3v4l.org/bdQe9 vs http://3v4l.org/jr68f and http://3v4l.org/K6ZEU
The OpenSSL extension does not currently offer you a way to say "This string is not padded, please don't strip the padding for me" and then remove the NUL bytes on your own. You must encrypt with PKCS7 padding in order for decryption to succeed.
Although this is a limitation of OpenSSL, it bears emphasizing that the only reason you're running into it is because mcrypt is terrible.
There shouldn't be any major differences except for the padding. You should be able to call EVP_CIPHER_CTX_set_padding if you use the higher level OpenSSL (EVP) constructs directly. I presume that the padding argument should be zero, although it is not documented. You need a preconfigured encryption/decryption context for this.
Afterwards you will have your plaintext of the same length as the ciphertext. Zero to fifteen bytes at the end will be set to zero. You need to remove these bytes manually. If the plaintext happens to end with zero bytes then those will also be removed; that's however never the case if the plaintext is a printable string (that uses 8 bit encoding). You may want to ensure that you don't remove more than 15 bytes.
If you get completely random plaintext then your key or ciphertext is incorrect. If you get readable plaintext but for the first 16 bytes then your IV handling is incorrect.

PHP Encryption Method Comparison

I was recently asked to create a method for encryption / decryption of a URL string. I quickly produced a 1 liner and shot it out.
I was then provided with the code from another developer and asked my opinion. I looked at his to find a much more complex function.
My questions:
What are the specific differences here?
Are there shortfalls found in the short solution?
We are encrypting a json encoded array and passing it via query string URL.
Long solution:
public function Encrypt($message, $key = 'defaultkey') {
//Create an instance of the mcrypt resource
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
//Create a random intialization vector and initialize
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
// Create a Timestamp and add it.
$T = new \DateTime('NOW');
$message = $T->format("YmdHis") . $message;
// PKCS7 Padding
//get the block size of the cipher
$b = mcrypt_get_block_size('tripledes', 'ecb');
//What is the purpose?
$dataPad = $b-(strlen($message)%$b);
$message .= str_repeat(chr($dataPad), $dataPad);
//convert to hexidec string
$encrypted_data = bin2hex(mcrypt_generic($td, $message));
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $encrypted_data;
}
Short Solution:
public function Encrypt($message, $key = 'defaultkey') {
$T = new \DateTime('NOW');
return bin2hex(mcrypt_encrypt(MCRYPT_3DES, $key, $T->format("YmdHis").$message, 'ecb'));
}
The only real difference is the padding. Triple DES is a symmetric block cipher and such only operates on a single full block (8 byte). A mode of operation like ECB enables it to encrypt many full blocks. When your data is not a multiple of the block size, it has to be padded for it to be encrypted.
MCrypt uses zero padding by default. It will fill up the plaintext with 0x00 bytes until a multiple of the block size is reached. Those additional padding bytes have to be removed during decryption (usually done with rtrim()). This means that if the plaintext ends with 0x00 bytes, those will also be removed which might break your plaintext.
PKCS#5/PKCS#7 padding on the other hand pads with a byte that represents the number of padding bytes. If the plaintext is already a multiple of the block size, it will add a full block of padding. Doing it this way enables it to only remove the padding and not additional plaintext bytes during decryption.
Whether mcrypt_generic_init() or mcrypt_encrypt() was used doesn't really make a difference.
You should never use ECB mode. It is not semantically secure. It means that the same plaintext block will always result in the same ciphertext block. Since you're encrypting URLs the first couple of blocks will stay the same for similar URLs after observing many ciphertexts. An attacker might get additional information out of this.
Use at least CBC mode with a random IV. The IV doesn't need to be hidden, so it can be easily prepended to the ciphertext and sliced off during decryption.
It is also best to have the ciphertext authenticated to detect manipulation. You can use a message authentication code such as HMAC-SHA256 with a different key. A better way would be to simply use an authenticated mode such as GCM or EAX.

Rijndael-256 Encryption Decryption in PHP

I have a situation where I have to encrypt and decrypt SOAP xml in PHP. I am trying to encrypt the Envelope of a soap xml. Encryption is working, but decryption is not. For encryption and decryption I use the code specified in this article:
http://blog.djekldevelopments.co.uk/?p=334
If the input string contains any special characters(eg:'<>'), then decryption is not working. How can I solve this?
function decrypt($string = "")
{
$keyfile = "./AES.keyz";
$keyfile = file($keyfile);
$key = base64_decode($keyfile[0]);
$iv = base64_decode($keyfile[1]);
$string = base64_decode($string);
return strippadding(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $string,MCRYPT_MODE_CBC, $iv));
}
MCRYPT_RIJNDAEL_256 ia not AES with 256 bit key , its RIJNDAEL with a 256 bit block size.
To decrypt 256 bit AES , you need to use MCRYPT_RIJNDAEL_128 with a 256 bit key.

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.

Categories