Question

I've been using RNCryptor in an iOS app of mine. Due to some issues with the app, I need to decrypt some of the data server-side, which runs PHP 5.4. The RNCryptor package includes PHP code, but it only works with the 2.x branch of RNCryptor. My data was encrypted with the 1.x branch. Unfortunately, RNCryptor 2.x is not backwards-compatible with 1.x.

As far as I can tell, the only difference between these branches is that 1.x uses AES CTR mode to encrypt the text, whereas 2.x now uses AES CBC. But I'm at a loss figuring out how to adapt the PHP code to use CTR. I'm also not sure if there are any other changes between 1.x and 2.x. I'm finding very little useful information about 1.x.

Here's the RNCryptor code for decrypting data from the 2.x line:

/**
 * @param string $b64_data Data encrypted by RNCryptor 2.x
 * @return string|false Decrypted plaintext string, or false if decryption fails
 */
function decrypt_data($b64_data) {
    global $gPassword; // the password string that was used to encrypt the data

    // kRNCryptorAES256Settings 
    $algorithm = MCRYPT_RIJNDAEL_128;
    $key_size = 32;
    $mode = MCRYPT_MODE_CBC;
    $pbkdf2_iterations = 10000;
    $pbkdf2_prf = 'sha1';
    $hmac_algorithm = 'sha256';

    // back to binary              
    $bin_data = base64_decode($b64_data);
    // extract salt
    $salt = substr($bin_data, 2, 8);
    // extract HMAC salt
    $hmac_salt = substr($bin_data, 10, 8);
    // extract IV
    $iv = substr($bin_data, 18, 16);
    // extract data
    $data = substr($bin_data, 34, strlen($bin_data) - 34 - 32);
    $dataWithoutHMAC = chr(2).chr(1).$salt.$hmac_salt.$iv.$data;
    // extract HMAC
    $hmac = substr($bin_data, strlen($bin_data) - 32);
    // make HMAC key
    $hmac_key = hash_pbkdf2($pbkdf2_prf, $gPassword, $hmac_salt, $pbkdf2_iterations, $key_size, true);
    // make HMAC hash
    $hmac_hash = hash_hmac($hmac_algorithm, $dataWithoutHMAC , $hmac_key, true);
    // check if HMAC hash matches HMAC  
    if($hmac_hash != $hmac) {
        echo "HMAC mismatch".$nl.$nl.$nl;
        return false;
    }
    // make data key
    $key = hash_pbkdf2($pbkdf2_prf, $gPassword, $salt, $pbkdf2_iterations, $key_size, true);

    // decrypt
    $cypher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
    // initialize encryption handle
    if (mcrypt_generic_init($cypher, $key, $iv) != -1) {
        // decrypt
        $decrypted = mdecrypt_generic($cypher, $data);

        // http://www.php.net/manual/en/function.mdecrypt-generic.php
        // We found that sometimes the resulting padding is not null characters "\0" but rather one of several control characters.
        // If you know your data is not supposed to have any trailing control characters "as we did" you can strip them like so.
        $decrypted = preg_replace( "/\p{Cc}*$/u", "", $decrypted );

        // clean up
        mcrypt_generic_deinit($cypher);
        mcrypt_module_close($cypher);

        return trim($decrypted);
    }
    return false;
}

I really need a function like the above which works with data encrypted by the 1.x line of RNCryptor. Short of that, anyone know how I can adapt the above function myself? Thanks!

Was it helpful?

Solution

I realized my problem was slightly different than what I thought. I actually was using RNCryptor 2.0 unknowingly, but it turns out that the underlying problem was still there, that the PHP implementation RNCryptor includes wasn't decrypting my data.

Here's what I've learned after many hours of research and testing today: RNCryptor doesn't just encrypt the data by itself. It also adds custom headers and an HMAC digital signature. Unfortunately, this schema for laying out the headers and the HMAC has changed a couple of times since RNCryptor 1.0 was released.

RNCryptor 1.0 and 1.1 use schema version 0. I banged my head against this for longer than I care to admit, and I just can't get AES CTR Little-Endian en/decryption to work in PHP. (Anyone know?)

RNCryptor 2.0 uses schema version 1, and RNCryptor 2.1 uses schema 2. The only difference is that in schema 1, the HMAC is generated solely from the payload, but not the headers. Schema 2 correctly generates the HMAC from the payload plus the headers (and thus is way more secure.) More on this can be found in this post on Rob Napier's blog.

I decided to overhaul the PHP implementation included in RNCryptor in order to document this issue and prevent others (you!) from running into problems. I also wanted to modernize the implementation and make it Object-Oriented. See my forked RNCryptor on Github. I've submitted a pull request as well, so hopefully it'll end up in rnapier/RNCryptor too.

Cheers!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top