从PHP中的RNCryptor AES 256头检索IV

时间:2012-10-21 18:32:38

标签: php cocoa-touch cocoa aes unpack

使用最新的RNCryptor源并尝试将加密数据发送到PHP脚本。

RNCryptor将IV打包到标题部分,该标题部分预先添加到实际的加密数据中。

- (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;
}

我是新手使用PHP中的二进制数据,我使用以下解包函数是否正确?

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

注意:加密数据是在传输之前从cocoa编码的Base64。

上述PHP脚本返回诸如......

之类的数据
  

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

我如何在PHP MCrypt函数中使用它?

感谢。


修改

为了回应drew010的回答,我已将PHP脚本更新为以下内容......

<?
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;
?>

我怎么还得到乱乱的文字。

  

U¸|uÀÆ&安培; BY8:f`ôShŽºÃ〜:¾ÉöÁß=Ç®nqäà€•Æ<ò

我回顾了RNCryptor并使用以下值来编写PHP脚本

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
    }
};

我认为这个功能能产生关键吗?

+ (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;
}

再次感谢。

MCRYPT_RIJNDAEL_128是否正确?即使RNCryptor设置建议使用256,实际算法为128,IV大小与128块大小相关。我已经读过某个地方强制PHP使用16字节IV你必须使用MCRYPT_RIJNDAEL_128然后让256为它提供一个32字节密钥。

3 个答案:

答案 0 :(得分:4)

这适用于iOS中最新的RNCryptor

$b64_data:base64编码的加密数据
$pwd:密码

// 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与上面的问题相同,来自https://defuse.ca/php-pbkdf2.htm

答案 1 :(得分:1)

你不需要为此解压缩。

一旦你收到完整的base64编码字符串,解码它,现在你应该有一个二进制字符串,其字符串的开头有一个IV。

然后,您可以使用substr()从数据中获取所需的每件作品。

例如:

$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

如果密文前面有其他字段,请确保按照正确的顺序执行与上述相同的其他字段。

获得这些数据后,您可以将$data与IV和密钥一起传递给mcrypt。

答案 2 :(得分:0)

  

MCRYPT_RIJNDAEL_128是否正确?即使RNCryptor设置建议使用256,实际算法为128,IV大小与128块大小相关。我已经读过某个地方强制PHP使用16字节IV你必须使用MCRYPT_RIJNDAEL_128然后让256为它提供一个32字节密钥。

MCRYPT_RIJNDAEL_128中的“128”表示块大小,而不是密钥大小。 Rijndael算法可以处理多个块大小,但AES只能处理128位块。这与密钥大小无关。 CBC IV应始终为块大小,在AES中也始终为16个字节。 (Rijndael和AES非常相似,但不完全相同.Rijndael比AES更灵活。)

pbkdf2()函数中,应传递32字节(256位)的密钥长度,而不是16字节。我相信如果传递256位密钥,PHP mcrypt模块将自动切换到256位AES(基于对Understanding PHP AES Encryption的评论;我对mcrypt并不是特别熟悉)。我假设你正确地实施了PBKDF2;我没有在那里研究你的代码。

请注意,RNCryptor会在末尾附加一个32字节的HMAC。我相信你当前的代码会尝试解密它,最后导致32字节的垃圾。通常,您应该关闭此HMAC并验证它以确保数据在传输过程中未被修改且密码正确。