views:

370

answers:

2

I need medium to strong encryption on serverside, so I thought I would use mcrypt with PHP. If I use the functions below the beginning of my original string turns into binary garbage after decryption. (This is not the usual problem of getting appended additional garbage, instead my string is altered.) According to the documentation, mcrypt_encrypt() should have padded enough characters to match the block size of the selected algorithm but I suspect it does not work.

However, if I pad it manually to the block size of 128 bit (16 bytes) of Rijndael, it doesn't work either. The only way I can get this to work is by prepending some string long enough to (likely) cover the garbaged block and add a known prefix like "DATA#" between that string and my data. After decryption that block has been partially mangled but my prefix and all data after that has been correctly decrypted.

$GLOBALS['encryptionmarker'] = 'DATA#';

function encrypt($plain, $key) {
    /*
    // workaround because beginning of decrypted string is being mangled
    // so we simply prefix with some text plus marker
    $prefix = str_pad('', 128, '#', STR_PAD_RIGHT).$GLOBALS['encryptionmarker'];
    $plain = $prefix.$plain;
    */

    $encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $plain, MCRYPT_MODE_CFB,
        mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CFB),
        MCRYPT_DEV_URANDOM));

    return $encrypted;
}

function decrypt($encrypted, $key) {
    $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CFB,
        mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CFB),
        MCRYPT_DEV_URANDOM));

    /*
    // workaround: remove garbage
    $pos = strpos($decrypted, $GLOBALS['encryptionmarker']);
    $decrypted = trim(substr($decrypted, $pos + strlen($GLOBALS['encryptionmarker'])));
    */

    return $decrypted;
}

What's wrong with my functions? Why do I have to prefix my data like that (I consider it a dirty workaround, so I would like to fix it)?

Storing the encrypted data is not the problem; decrypting it immediately after encryption without storing it to a database results in the same errors.

+3  A: 

Your problem is that you are generating a new, different, random IV on the receiving side. This doesn't work, as you've seen.

The receiver needs to know the IV that the sender used; so you have to send it along with the encrypted data and pass it to mcrypt_decrypt().

Note that you must also use mhash() with a key (a different key to the encryption key) to generate an HMAC over the message, and check it on the receiving side. If you do not, a man-in-the-middle can trivially modify parts of your message without you detecting it.

caf
Thanks, not sharing the IV really was the full problem. But I've got a small bonus question: Why am I able to decode everything after the first ~30 bytes correctly? The key is only 32 byte long since I can't use any longer key (PHP will throw an error); is a part of my message unprotected from decryption? I think I don't need mhash since I only need encryption to protect (critical) temporary data in a database? (deleted after 10 minutes; only read access matters, manipulations without read access don't)
Energiequant
That's because of the way CFB works - the IV is only needed to decrypt the first block. To decrypt the second block, the ciphertext of the first block is what's needed (and you were transmitting that, and all subsequent blocks, fine). This is why an HMAC is a good idea - the attacker can flip *any* bits of your message at will, undetectably - so if he can guess what you were saying (eg, "admin=0"), he can make you say whatever he wants (eg, "admin=1").
caf
Okay, so the IV is only important to protect the first block (should be 16 bytes for Rijndael 256?) so one can safely store a hash there by adding the IV as some kind of salt to check for manipulations? But if I don't need that extra protection I would be fine to use an empty IV as well (or no IV at all if that's possible) and would still not sacrifice any security of all blocks except the first one because everything after the first block will always be decryptable with whatever random IV I choose?
Energiequant
No - the IV is required to *decrypt* the first block. A HMAC to give integrity protection is another seperate value. If you use a fixed IV, then you are removing all the whitening from the entire message - it will be no better than ECB mode! You were on the right track to begin with - generate a new IV with `mcrypt_create_iv` for each message and send it along with the encrypted message.
caf
Sounds like I should read something about cryptography before using mcrypt... I will implement my functions as suggested and will read about that algorithm I use, maybe it helps. ;) Thanks so far!
Energiequant
Of course Wikipedia has a nice diagram and text explaining everything: http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 Having read that now it's clear that I have to use an IV to simply provide some initial input for the feedback algorithm because else it would be easy to break the encryption. The feedback is also causing a valid decryption on later blocks because the algorithm uses previous encrypted (not decrypted) blocks, so it "stabilizes" decryption when knowing the key.
Energiequant
+2  A: 

Use the same IV in en- and decryption. The IV is not a shared secret, but has to be shared. You may consult Wikipedia: IV

$IV = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CFB),
      MCRYPT_DEV_URANDOM));

The IV must be transferred ONCE. You may want to increment the value of IV for each packet. But this can be done on both sides independently.

tuergeist
Ahm, yes. caf was faster than me :|
tuergeist