I have a code that goes something like:
$cipher_alg = MCRYPT_RIJNDAEL_128;
$decrypted_string = mcrypt_decrypt($cipher_alg, $key,
$encrypted_string , MCRYPT_MODE
I had to encrypt/decrypt binary data. Unfortunately trim can break the binary data and trim legitimate bits that equate to a null character.
To ensure the size of the binary data is the same before and after encryption, Rocket Hazmat posted a great answer here: How can I decrypt binary data that ended with NUL characters in PHP?
Summary:
// PKCS7 Padding
$blocksize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$pad = $blocksize - (strlen($data) % $blocksize);
$data .= str_repeat(chr($pad), $pad);
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB);
/* Then somewhere else in your code */
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_ECB);
// PKCS7 Padding
$strPad = ord($decrypted[strlen($decrypted)-1]);
$newData = substr($decrypted, 0, -$strPad);
I am not sure about the use of ECB versus CBC...
After 24 hours of research, finally this worked for me:
function removePadding($decryptedText){
$strPad = ord($decryptedText[strlen($decryptedText)-1]);
$decryptedText= substr($decryptedText, 0, -$strPad);
return $decryptedText;
}
In my implementation of TripleDES, I found the decrypted string was padded with \5 or \6 characters. This wasn't the expected \0 or \4 characters mentioned above or in the PHP.net examples. To determine the ASCII value of the padding character use the ord() function. ord() works on a single character so use str_split() to break up a string or access the character directly with array notation - $string[5].
Final trim result - trim($decrypt, "\0..\32");
Final code result -
$key = "encryption key";
$encrypt = base64_decode($encrypt);
$iv_size = mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$decrypt = mcrypt_decrypt(MCRYPT_3DES, $key, $encrypt, MCRYPT_MODE_ECB, $iv);
$final = trim($decrypt, "\0..\32"); // removes potential null padding
Actually both mcrypt_encrypt() and mcrypt_decrypt() as well as the other en-/decryption functons (like mcrypt_generic() or mdecrypt_generic()) do pad the $data
parameter to a length of n * <<blocksize>>
. The padding character is the NUL
character (\x0
or \0
) whereas the <<blocksize>>
depends on the cipher and the block cipher modes used. You should have a look at Block cipher modes of operation and Padding (cryptography).
The following is the output of mcrypt_get_block_size() for each of the available ciphers and modes on my machine. Obviously the function does not take into account that modes such as CFB, OFB and CTR do not require any special measures to handle messages whose lengths are not multiples of the block size, since they all work by XORing the plaintext with the output of the block cipher (quote from Wikipedia). CBC which is used in your example always requires that the final block is padded before encryption.
cast-128
cbc: 8 bytes
cfb: 8 bytes
ctr: 8 bytes
ecb: 8 bytes
ncfb: 8 bytes
nofb: 8 bytes
ofb: 8 bytes
stream: not supported
gost
cbc: 8 bytes
cfb: 8 bytes
ctr: 8 bytes
ecb: 8 bytes
ncfb: 8 bytes
nofb: 8 bytes
ofb: 8 bytes
stream: not supported
rijndael-128
cbc: 16 bytes
cfb: 16 bytes
ctr: 16 bytes
ecb: 16 bytes
ncfb: 16 bytes
nofb: 16 bytes
ofb: 16 bytes
stream: not supported
twofish
cbc: 16 bytes
cfb: 16 bytes
ctr: 16 bytes
ecb: 16 bytes
ncfb: 16 bytes
nofb: 16 bytes
ofb: 16 bytes
stream: not supported
arcfour
cbc: not supported
cfb: not supported
ctr: not supported
ecb: not supported
ncfb: not supported
nofb: not supported
ofb: not supported
stream: 1 bytes
cast-256
cbc: 16 bytes
cfb: 16 bytes
ctr: 16 bytes
ecb: 16 bytes
ncfb: 16 bytes
nofb: 16 bytes
ofb: 16 bytes
stream: not supported
loki97
cbc: 16 bytes
cfb: 16 bytes
ctr: 16 bytes
ecb: 16 bytes
ncfb: 16 bytes
nofb: 16 bytes
ofb: 16 bytes
stream: not supported
rijndael-192
cbc: 24 bytes
cfb: 24 bytes
ctr: 24 bytes
ecb: 24 bytes
ncfb: 24 bytes
nofb: 24 bytes
ofb: 24 bytes
stream: not supported
saferplus
cbc: 16 bytes
cfb: 16 bytes
ctr: 16 bytes
ecb: 16 bytes
ncfb: 16 bytes
nofb: 16 bytes
ofb: 16 bytes
stream: not supported
wake
cbc: not supported
cfb: not supported
ctr: not supported
ecb: not supported
ncfb: not supported
nofb: not supported
ofb: not supported
stream: 1 bytes
blowfish-compat
cbc: 8 bytes
cfb: 8 bytes
ctr: 8 bytes
ecb: 8 bytes
ncfb: 8 bytes
nofb: 8 bytes
ofb: 8 bytes
stream: not supported
des
cbc: 8 bytes
cfb: 8 bytes
ctr: 8 bytes
ecb: 8 bytes
ncfb: 8 bytes
nofb: 8 bytes
ofb: 8 bytes
stream: not supported
rijndael-256
cbc: 32 bytes
cfb: 32 bytes
ctr: 32 bytes
ecb: 32 bytes
ncfb: 32 bytes
nofb: 32 bytes
ofb: 32 bytes
stream: not supported
serpent
cbc: 16 bytes
cfb: 16 bytes
ctr: 16 bytes
ecb: 16 bytes
ncfb: 16 bytes
nofb: 16 bytes
ofb: 16 bytes
stream: not supported
xtea
cbc: 8 bytes
cfb: 8 bytes
ctr: 8 bytes
ecb: 8 bytes
ncfb: 8 bytes
nofb: 8 bytes
ofb: 8 bytes
stream: not supported
blowfish
cbc: 8 bytes
cfb: 8 bytes
ctr: 8 bytes
ecb: 8 bytes
ncfb: 8 bytes
nofb: 8 bytes
ofb: 8 bytes
stream: not supported
enigma
cbc: not supported
cfb: not supported
ctr: not supported
ecb: not supported
ncfb: not supported
nofb: not supported
ofb: not supported
stream: 1 bytes
rc2
cbc: 8 bytes
cfb: 8 bytes
ctr: 8 bytes
ecb: 8 bytes
ncfb: 8 bytes
nofb: 8 bytes
ofb: 8 bytes
stream: not supported
tripledes
cbc: 8 bytes
cfb: 8 bytes
ctr: 8 bytes
ecb: 8 bytes
ncfb: 8 bytes
nofb: 8 bytes
ofb: 8 bytes
stream: not supported
Therefore you have to rtrim() the output of the decryption functions to get the original string if your cipher operates on fixed length blocks:
$output = rtrim($decrypted, "\0");