Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Part II: How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together

This question is a continuation of my last one, regarding How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together. I've got that working now, but I'm still struggling to go the other direction. The PHP generated cryptogram appears to have all the information that was provided, but I cannot get the Ruby code to decrypt it without error.

Here's the PHP code I'm using to generate the cryptogram:

$cleartext = "Who's the clever boy?";
$key = base64_decode("6sEwMG/aKdBk5Fa2rR6vVw==\n");
$iv = base64_decode("vCkaypm5tPmtP3TF7aWrug==");
$cryptogram = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $cleartext, MCRYPT_MODE_CBC, $iv);
$result = base64_encode($cryptogram);
print "\n'$result'\n";

RESULT
'JM0OxMINPTnF1vwXdI3XdKI0KlVx210CvpJllFja+GM='

Then here's the attempt to decrypt in Ruby:

>> cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
>> cipher.key = Base64.decode64("6sEwMG/aKdBk5Fa2rR6vVw==\n")
>> cipher.iv = Base64.decode64("vCkaypm5tPmtP3TF7aWrug==")
>> cryptogram = Base64.decode64('JM0OxMINPTnF1vwXdI3XdKI0KlVx210CvpJllFja+GM=')
>> cleartext = cipher.update(cryptogram)
=> "Who's the clever"
>> cleartext << cipher.final
OpenSSL::Cipher::CipherError: bad decrypt
 from (irb):100:in `final'
 from (irb):100

What's really frustrating about this is that it's possible to get the entire cleartext out of that encrypted string. Repeating the above, but adding a nonsense pad to the cryptogram:

  >> cleartext = cipher.update(cryptogram + 'pad')
  => "Who's the clever boy?\000\000\000\000\000\000\000\000\000\000\000"
  >> cleartext << cipher.final
  OpenSSL::Cipher::CipherError: bad decrypt
   from (irb):119:in `final'
   from (irb):119

In my actual use case the cleartext is structured (a JSON string, since you ask), so I feel comfortable a this point that I could tell use this scheme and detect poorly encrypted input without performing the cipher.final. However, I can't tolerate this sort of kludge in my code, so I'd like to understand how to make the ruby code handle the final block gracefully.

like image 551
dondo Avatar asked Dec 08 '09 04:12

dondo


People also ask

Can AES 256 CBC be cracked?

AES 256 is virtually impenetrable using brute-force methods. While a 56-bit DES key can be cracked in less than a day, AES would take billions of years to break using current computing technology. Hackers would be foolish to even attempt this type of attack. Nevertheless, no encryption system is entirely secure.

How does AES work with CBC?

CBC (short for cipher-block chaining) is a AES block cipher mode that trumps the ECB mode in hiding away patterns in the plaintext. CBC mode achieves this by XOR-ing the first plaintext block (B1) with an initialization vector before encrypting it.

How strong is AES 256 CBC?

Brute force attacks AES-256 is 340 billion-billion-billion-billion times harder to brute force than AES-128. To put this into perspective, the universe is 14 billion years old. It is therefore safe to say that even at its lower bit sizes, AES is highly resistant to brute force attacks from conventional computers.

What is the difference between AES 256 CBC and AES 256 GCM?

AES-GCM is a more secure cipher than AES-CBC, because AES-CBC, operates by XOR'ing (eXclusive OR) each block with the previous block and cannot be written in parallel. This affects performance due to the complex mathematics involved requiring serial encryption.


1 Answers

The problem is that mcrypt isn't padding the last block, whereas Ruby's OpenSSL binding uses the default OpenSSL padding method, which is PKCS padding. I can't really improve on the description from the OpenSSL documentation:

PKCS padding works by adding n padding bytes of value n to make the total length of the data a multiple of the block size. Padding is always added so if the data is already a multiple of the block size n will equal the block size. For example if the block size is 8 and 11 bytes are to be encrypted then 5 padding bytes of value 5 will be added.

You'll need to manually add proper padding to the end of the cleartext in PHP before encrypting. To do that, pass your $cleartext through this pkcs5_pad function on the PHP side before you encrypt it (passing 16 as the blocksize).

function pkcs5_pad ($text, $blocksize)
{
    $pad = $blocksize - (strlen($text) % $blocksize);
    return $text . str_repeat(chr($pad), $pad);
}

If you also go the other way (encrypt in Ruby and decrypt with mcrypt), you'll have to strip off the padding bytes after decrypting.

Side note: The reason you have to add padding even if the cleartext is already a multiple of the blocksize (a whole block of padding), is so that when you are decrypting you know that the last byte of the last block is always the amount of padding added. Otherwise, you couldn't tell the difference between cleartext with a single padding byte and a cleartext with no padding bytes that just happened to end in the value 0x01.

like image 53
caf Avatar answered Sep 27 '22 20:09

caf