Search code examples
phpiosencryptionaesrncryptor

PHP AES256 Encryption => RNCryptor RNDecryptor returns blank / HMAC Mismatch


Using PHP 5.4 (mcrypt), RNCryptor 2, iOS 6.

PHP function creates base64 with all headers as referenced from https://github.com/rnapier/RNCryptor/wiki/Data-Format.

PHP decrypt function which can decrypt base64 string from both RNEncryptor and the PHP Encrypt function below return data as expected.

When using RNDecryptor with base64 from PHP Encrypt function below, no data is returned as shown in XCode output below.

PHP Function:

function encrypt($data, $key)
{
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

    $salt = '12345678';

    $_key = $this->pbkdf2('SHA1', $key, $salt, 10000, 32, true);

    $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $_key, $data, MCRYPT_MODE_CBC, $iv);

    $hmac = $this->pbkdf2('SHA1', $key, $salt, 10000, 32, true);

    $data = mb_convert_encoding(chr(1).chr(0).$salt.$salt.$iv.$ciphertext.$hmac, "BASE64", "UTF-8");

    return $data;
}

PHP Function Call:

encrypt('My Data', 'mykey');

iOS:

NSError * error;
NSData *decryptedData = [RNDecryptor decryptData:[NSString base64DataFromString:@"AQBpcGhvbmU2MmlwaG9uZTYyrYk2rJnaoywktnx6TZ4X3YKgYuEHCL1EHv+/MqIvQMq5BmZOyMJr QSRs9P4uxShsOJOg67VYniUGhHbFNTSl1Q=="]
                                    withPassword:@"mykey"
                                           error:&error];

NSLog(@"data = %@, %@", decryptedData, error);

XCode output:

data = <>, (null)

This is done when I comment out HMAC verification in RNDecryptor -finish, once these section is uncommented I receive a HMAC Mismatch error

data = (null), Error Domain=net.robnapier.RNCryptManager Code=1 "HMAC Mismatch" UserInfo=0x1e564280 {NSLocalizedDescription=HMAC Mismatch}

if (self.hasHMAC) {
  NSMutableData *HMACData = [NSMutableData dataWithLength:self.HMACLength];
  CCHmacFinal(&_HMACContext, [HMACData mutableBytes]);

  if (![HMACData isEqualToData:self.inData]) {
    [self cleanupAndNotifyWithError:[NSError errorWithDomain:kRNCryptorErrorDomain
                                                        code:kRNCryptorHMACMismatch
                                                    userInfo:[NSDictionary dictionaryWithObject:@"HMAC Mismatch"
                                                                                         forKey:NSLocalizedDescriptionKey]]];
    return;
  }
}

Solution

  • The problem was due to both an incorrect HMAC (was passing the HMAC Key) and PHP encryption needing PKCS7 Padding on the data to be encrypted (not the IV too).

    Final PHP function...

    function AES256Encrypt($data, $key)
    {
        $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $pad = $block - (strlen($data) % $block);
        $data .= str_repeat(chr($pad), $pad);
    
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    
        $keySalt = '12345678';
        $hmacSalt = '12345678';
    
        $_key = $this->pbkdf2('SHA1', $key, $keySalt, 10000, 32, true);
        $_hmacKey = $this->pbkdf2('SHA1', $key, $hmacSalt, 10000, 32, true);
    
        $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $_key, $data, MCRYPT_MODE_CBC, $iv);
    
        $data = base64_encode(chr(1).chr(0).$keySalt.$hmacSalt.$iv.$ciphertext.hash_hmac('SHA256',$ciphertext,$_hmacKey, true));
        return $data;
    }