Figuring out the exact key created by PHP's mcrypt
Asked Answered
P

2

12

A PHP application I'm maintaining uses Rijndael_256 with EBC_MODE encryption with mcrypt. Fun has it that the key isn't 256 bits long, but only 160. According to the mcrypt_encrypt documentation the key is padded with \0 to get the required size if it's too small.

The key with which the data will be encrypted. If it's smaller than the required keysize, it is padded with '\0'. It is better not to use ASCII strings for keys.

This seem to happen at around the start of line 1186 in mcrypt.c and modifying the key at line 1213.

So lets say we've got $key = 'abcdefghijkm'; which is too short, but PHP's implementation of mcrypt makes sure it's extended to 32 characters (or 256 bit) when using RIJNDAEL_256. What will the final key look like?

I'm asking this because another application is being built that uses the same encrypted data, but is in another language. Perl to be exact and I'm using Crypto::Rijndael. For the given example key, what is the exact key I would have to feed to Crypto::Rijndael (or any other for that matter) to be able to decrypt the data again?

Update

With Perl I can generate a key that's \0 padded doing pack('a32', 'my secret key'); (or Z32), length() will report 32 and the Crypt::Rijndael module accepts the key. Looking at the source of PHP's mcrypt this should be the key that's being generated (\0 padded), but it simply won't take it.

In theory in PHP pack('a32', 'my secret key'); should result in the same \0 padded key that PHP's mcrypt generates, but this isn't the case.

I'm very close to just encrypt everything again but with a new key. This is taking too much time.

Pleo answered 18/7, 2012 at 12:3 Comment(6)
Have you tried just padding it with 0s?Atlantes
Yes, haven't had any luck with that though.Pleo
Remember that the results might not actually be printable characters, so don't rely on console/page output to determine what the key actually looks like. But if you write the resulting key out to a binary file, you should be able to pull it into your perl script and use it successfully.Barometer
I'm trying it with plain text that has been encrypted with PHP's mcrypt_encrypt and tried to decrypt with Perl's Crypt::Rijndael. All I need to know is what the actual key looks like when PHP's mcrypt_encrypt is done with it :-).Pleo
Excuse me for not answering the question directly, but I think this is a bad idea. If your key is a password, stretch your value first using a KDF (e.g PBKDF2, bcrypt, scrypt) and then use the result to encrypt the data.Transcendentalism
Could you explain what KDF stands for? The kind of data that's being encrypted here doesn't matter. The fact is I've got to deal with data that's being encrypted this way and I need to be able to decrypt it using another language, in this case Perl.Pleo
R
12

The issue isn't the key's padding, it's that you're using two different block sizes. In PHP, using MCRYPT_RIJNDAEL_256 uses a block size of... 256 bits. However, in perl using Crypt::Rijndael, they note:

blocksize
The blocksize for Rijndael is 16 bytes (128 bits), although the algorithm actually supports any blocksize that is any multiple of our bytes. 128 bits, is however, the AES-specified block size, so this is all we support.

So there's no key that will allow for conversion between those different algorithms. You can either switch to 128 bits in PHP:

<?
$key = "abcdefghijklmnopqrstuvwxyz";
$data = "Meet me at 11 o'clock behind the monument.";
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB, nil);
echo bin2hex($crypttext) . "\n";
// prints c613d1804f52f535cb4740242270b1bcbf85151ce4c874848fd1fc2add06e0cc2d26b6403feef4a8df18f7dd7f8ac67d
?>

Which Perl can decrypt without a problem using Crypt::Rijndael:

use Crypt::Rijndael;
$key = "abcdefghijklmnopqrstuvwxyz\0\0\0\0\0\0";
$crypttext = "c613d1804f52f535cb4740242270b1bcbf85151ce4c874848fd1fc2add06e0cc2d26b6403feef4a8df18f7dd7f8ac67d";
$cipher = Crypt::Rijndael->new($key, Crypt::Rijndael::MODE_ECB());
print $cipher->decrypt(pack('H*', $crypttext));
# prints "Meet me at 11 o'clock behind the monument."

Or you can switch to a different Perl module that supports more block sizes, e.g., Crypt::Rijndael_PP:

# Same PHP code except using MCRYPT_RIJNDAEL_256
# prints f38469ec9deaadbbf49bb25fd7fc8b76462ebfbcf149a667306c8d1c033232322ee5b83fa87d49e4e927437647dbf7193e6d734242d583157b492347a2b1514c

Perl:

use Crypt::Rijndael_PP ':all';
$key = "abcdefghijklmnopqrstuvwxyz\0\0\0\0\0\0";
$crypttext = "f38469ec9deaadbbf49bb25fd7fc8b76462ebfbcf149a667306c8d1c033232322ee5b83fa87d49e4e927437647dbf7193e6d734242d583157b492347a2b1514c";
print rijndael_decrypt(unpack('H*', $key), MODE_ECB, pack('H*', $crypttext), 256, 256);
# prints "Meet me at 11 o'clock behind the monument."
Rebuff answered 26/7, 2012 at 23:24 Comment(4)
Thanks, I was already starting to think the blocksize had something to do with it but didn't had the time yet to check it out. I guess I'll be going with Crypt::Rijndael_PP then.Pleo
Hmm, whilst $key is actually 256bits long Crypt::Rijndael_PP still appends 0's to it. Looks to me like there's a bug in it?Pleo
@Pleo Crypt::Rijndael_PP is not as mature a module as Crypt::Rijndael and has some oddities. Notably, it doesn't want the raw key, but the hex string representation. See my example using it: you have to unpack the raw key before passing it in.Rebuff
Ah I see, that's indeed a bit strange. Will give it a go.Pleo
P
4

'\0' means NULL, the hex value of it is 00 ! So i tested 3 codes, and they returned all the same :)

  1. Let mcrypt_encrypt do the '\0' padding
  2. Added with PHP NULL values
  3. Added with PHP converted 0 hex values

Code:

function encryptThis($text,$key){
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv);
    return ($crypttext);
}

echo bin2hex(encryptThis("Meet me at 11 o'clock behind the monument.", "abcdefghijklmnopqrstuvwxyz"))."<br/>";

echo bin2hex(encryptThis("Meet me at 11 o'clock behind the monument.", "abcdefghijklmnopqrstuvwxyz" . NULL . NULL . NULL . NULL . NULL . NULL))."<br/>";

echo bin2hex(encryptThis("Meet me at 11 o'clock behind the monument.", "abcdefghijklmnopqrstuvwxyz" . hex2bin(0) . hex2bin(0) . hex2bin(0) . hex2bin(0) . hex2bin(0) . hex2bin(0)))."<br/>";

?>
Pycnidium answered 18/7, 2012 at 13:36 Comment(9)
I'm happy with letting mcrypt_encrypt do the \0 padding. I need the exact key so I can use it in another programming language. Thanks for the 0x00 mention, had forgotten about that one. Wonder if perl's undef will be the same (doubt it).Pleo
Well it must be 32 characters. In this example the alphabet is 26, 32-26 = 6, so you must add 6 Nulls or 0x00 :)Pycnidium
The formatting is messed up now. Actually, $key1 and $key2 are exactly the same length, so mcrypt_encrypt is still taking care of the padding. The same goes for $key3. So it's no wonder they give the same result, as to PHP they all containt "abcdefghijklmnopqrstuvwxyz".Pleo
hence the final key is "abcdefghijklmnopqrstuvwxyz" . NULL . NULL . NULL . NULL . NULL . NULL; ...Pycnidium
Also Warning: hex2bin(): Hexadecimal input string must have an even length. All these 3 examples do exactly the same thing, they let mcrypt_encrypt take care of the padding. You're feeding it a key with the length of 26 bytes.Pleo
That warning is because my code is messed up xD ... Also, i'm not feeding it with a key of 26bytes. 1 NULL = 1 byte, so if you add 6 NULLs to the key it will be 32bytes wich what PHP mcrypt_encrypt exactly do, i'm not a familiar with PERL, but if you add those NULL's it should be the same encryption method :)Pycnidium
Try doing a strlen() on the generated keys. It'll report 26 for all of them, which shouldn't be correct according to what you're saying.Pleo
this is indeed frustrating, '\0' means end of a string, it would be naturaly that strlen() doesn't count that as a string ...Pycnidium
Since strlen() calculates the length of the string in bytes I'd expect it to count a \0 as well, since that's a byte as well. Since we can't be sure yet what PHP does with the string before mcrypt modifies the key string it's still not certain what the string is supposed to end up like.Pleo

© 2022 - 2024 — McMap. All rights reserved.