Retrieve IV from RNCryptor AES 256 header in PHP

荒凉一梦 提交于 2019-12-14 01:28:47

问题


Using the latest source of RNCryptor and attempting to send the encrypted data to a PHP script.

RNCryptor packages the IV into the header section which is prepended onto the actual encrypted data.

- (NSData *)header
{
  uint8_t header[2] = {kRNCryptorFileVersion, self.options};
  NSMutableData *headerData = [NSMutableData dataWithBytes:header length:sizeof(header)];
  if (self.options & kRNCryptorOptionHasPassword) {
    [headerData appendData:self.encryptionSalt]; // 8 bytes
    [headerData appendData:self.HMACSalt]; // 8 bytes
  }
  [headerData appendData:self.IV]; // BlockSizeAES128
  return headerData;
}

I am new to working with binary data in PHP, am I correct with using the following unpack function?

<?
$baseEncodedString = "...";
$data = mb_convert_encoding($baseEncodedString, "UTF-8", "BASE64" );
$array = unpack("Cversion/Coptions/C8salt/C8hmac/C16iv/C*aes", $data);
print_r($array);
?>

NOTE: The encrypted data is Base64 encoded from cocoa before transmission.

The above PHP script returns data such as...

Array ( [version] => 1 [options] => 1 [salt1] => 109 [salt2] => 195 [salt3] => 185 [salt4] => 71 [salt5] => 130 [salt6] => 209 [salt7] => 230 [salt8] => 25 [hmac1] => 8 [hmac2] => 152 [hmac3] => 188 [hmac4] => 135 [hmac5] => 117 [hmac6] => 169 [hmac7] => 25 [hmac8] => 228 [iv1] => 43 [iv2] => 220 [iv3] => 80 [iv4] => 102 [iv5] => 142 [iv6] => 144 [iv7] => 172 [iv8] => 104 [iv9] => 216 [iv10] => 45 [iv11] => 155 [iv12] => 117 [iv13] => 188 [iv14] => 67 [iv15] => 24 [iv16] => 191 [aes1] => 122 [aes2] => 227 [aes3] => 45 [aes4] => 194 [aes5] => 57 [aes6] => 123 [aes7] => 28 [aes8] => 130 [aes9] => 110 [aes10] => 122 [aes11] => 97 [aes12] => 118 [aes13] => 214 [aes14] => 117 [aes15] => 56 [aes16] => 168 [aes17] => 54 [aes18] => 198 [aes19] => 113 [aes20] => 120 [aes21] => 138 [aes22] => 67 [aes23] => 223 [aes24] => 200 [aes25] => 11 [aes26] => 109 [aes27] => 177 [aes28] => 167 [aes29] => 103 [aes30] => 139 [aes31] => 243 [aes32] => 199 [aes33] => 214 [aes34] => 214 [aes35] => 241 [aes36] => 199 [aes37] => 173 [aes38] => 219 [aes39] => 71 [aes40] => 97 [aes41] => 32 [aes42] => 27 [aes43] => 248 [aes44] => 175 [aes45] => 203 [aes46] => 123 [aes47] => 21 )

How am I able to use this in the PHP MCrypt functions?

Thanks.


EDIT

In response to drew010's answer I have updated my PHP script to the following...

<?
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        die('PBKDF2 ERROR: Invalid hash algorithm.');
    if($count <= 0 || $key_length <= 0)
        die('PBKDF2 ERROR: Invalid parameters.');

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 1; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}

$base = $_GET['base'];
$data = mb_convert_encoding($base, "UTF-8", "BASE64" );
//$data = base64_decode($base);

$header = array();
$header['ver'] = substr($data, 0, 1);
$header['options'] = substr($data, 1, 1);
$header['salt'] = substr($data, 2, 8);
$header['hmac'] = substr($data, 10, 8);
$header['iv'] = substr($data, 18, 16);
$data = substr($data, 34);

$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($td, pbkdf2('SHA256', 'password', $header['salt'], 10000, 16), $header['iv']);

//$decrypted = mcrypt_decrypt('rijndael-256','password',$data,'',$header['iv']);
$decrypted = mdecrypt_generic($td, $data);
echo $decrypted;
?>

How ever I am still getting scrambled text.

U¸¦uÀ­ˆÆ&bŸ8:f`ôShŽºÃ~:¾ÉöÁß=Ç®nqäà€•Æ‹ò

I looked back at RNCryptor and used the following values for the PHP script

static const RNCryptorSettings kRNCryptorAES256Settings = {
    .algorithm = kCCAlgorithmAES128,
    .blockSize = kCCBlockSizeAES128,
    .IVSize = kCCBlockSizeAES128,
    .options = kCCOptionPKCS7Padding,
    .HMACAlgorithm = kCCHmacAlgSHA256,
    .HMACLength = CC_SHA256_DIGEST_LENGTH,

    .keySettings = {
        .keySize = kCCKeySizeAES256,
        .saltSize = 8,
        .PBKDFAlgorithm = kCCPBKDF2,
        .PRF = kCCPRFHmacAlgSHA1,
        .rounds = 10000
    },

    .HMACKeySettings = {
        .keySize = kCCKeySizeAES256,
        .saltSize = 8,
        .PBKDFAlgorithm = kCCPBKDF2,
        .PRF = kCCPRFHmacAlgSHA1,
        .rounds = 10000
    }
};

This function I believe produces the key?

+ (NSData *)keyForPassword:(NSString *)password salt:(NSData *)salt settings:(RNCryptorKeyDerivationSettings)keySettings
{
  NSMutableData *derivedKey = [NSMutableData dataWithLength:keySettings.keySize];

  int result = CCKeyDerivationPBKDF(keySettings.PBKDFAlgorithm,         // algorithm
                                    password.UTF8String,                // password
                                    password.length,                    // passwordLength
                                    salt.bytes,                         // salt
                                    salt.length,                        // saltLen
                                    keySettings.PRF,                    // PRF
                                    keySettings.rounds,                 // rounds
                                    derivedKey.mutableBytes,            // derivedKey
                                    derivedKey.length);                 // derivedKeyLen

  // Do not log password here
  // TODO: Is is safe to assert here? We read salt from a file (but salt.length is internal).
  NSAssert(result == kCCSuccess, @"Unable to create AES key for password: %d", result);

  return derivedKey;
}

Thanks again.

Is MCRYPT_RIJNDAEL_128 correct? Even though RNCryptor settings suggest its using 256 the actually algorithm is 128 and the IV size relates to the 128 block size. I have read somewhere to force PHP to use the 16 byte IV you must use MCRYPT_RIJNDAEL_128 then to have 256 feed it a 32 byte key.


回答1:


This works for me with the latest RNCryptor in iOS

$b64_data: base64-encoded encrypted data
$pwd: password

// back to binary
$bin_data = mb_convert_encoding($b64_data, "UTF-8", "BASE64");
// 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);
// extract HMAC
$hmac = substr($bin_data, strlen($bin_data) - 32);

// make HMAC key
$hmac_key = $this->pbkdf2('SHA1', $password, $hmac_salt, 10000, 32, true);
// make HMAC hash
$hmac_hash = hash_hmac('sha256', $data , $hmac_key, true);
// check if HMAC hash matches HMAC
if($hmac_hash != $hmac) return false;

// make data key
$key = $this->pbkdf2('SHA1', $password, $salt, 10000, 32, true);
// decrypt
$ret = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
return trim(preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\xFF]/u', '', $ret));

pbkdf2 is the same as in the question above, from https://defuse.ca/php-pbkdf2.htm.




回答2:


You shouldn't need to use unpack for this.

Once you receive the full base64 encoded string, decode it, and now you should have a binary string which has the IV at the beginning of the string.

You can then use substr() to grab each piece you require from the data.

For example:

$base = $_GET['base'];
$data = base64_decode($base);

$iv   = substr($data, 0, 32);  // get 32 byte IV
$data = substr($data, 32);     // set data to begin after the IV now

If you have other fields prepended to the ciphertext, make sure to do the same as above with the other data in the correct sequence.

Once you have those pieces of data, you can pass $data to mcrypt along with the IV and your key.




回答3:


Is MCRYPT_RIJNDAEL_128 correct? Even though RNCryptor settings suggest its using 256 the actually algorithm is 128 and the IV size relates to the 128 block size. I have read somewhere to force PHP to use the 16 byte IV you must use MCRYPT_RIJNDAEL_128 then to have 256 feed it a 32 byte key.

The "128" in MCRYPT_RIJNDAEL_128 refers to the block size, not the key size. The Rijndael algorithm can handle several block sizes, but AES can only handle 128 bit blocks. This is independent of the key-size. The CBC IV should always be the block size, which again is always 16 bytes in AES. (Rijndael and AES are very similar, but not identical. Rijndael is more flexible than AES.)

In your pbkdf2() function, you should pass a key-length of 32 bytes (256 bits), not 16 bytes. I believe that the PHP mcrypt module will automatically switch to 256-bit AES if passed a 256-bit key (based on the comments on Understanding PHP AES Encryption; I'm not particularly familiar with mcrypt). I am assuming you're implementing PBKDF2 correctly; I haven't studied your code there.

Note that RNCryptor appends a 32-byte HMAC at the end. I believe your current code will try to decrypt that, leading to 32 bytes of garbage at the end. Typically you should strip this HMAC off and verify it to ensure that the data was not modified in transit and that the password is correct.



来源:https://stackoverflow.com/questions/13000843/retrieve-iv-from-rncryptor-aes-256-header-in-php

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!