Получить IV из заголовка RNCryptor AES 256 в PHP

Использование последнего источника 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);
?>

ПРИМЕЧАНИЕ. Перед передачей зашифрованные данные кодируются в формате Base64 из какао.

Приведенный выше PHP-скрипт возвращает такие данные, как...

Массив ( [версия] => 1 [опции] => 1 [соль1] => 109 [соль2] => 195 [соль3] => 185 [соль4] => 71 [соль5] => 130 [соль6] => 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 [aes 35] => 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 )

Как я могу использовать это в функциях PHP MCrypt?

Спасибо.


ИЗМЕНИТЬ

В ответ на ответ Draw010 я обновил свой 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ÀˆÆ&bŸ8: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-байтовый ключ.


person Leon Storey    schedule 21.10.2012    source источник


Ответы (3)


Это работает для меня с последним RNCryptor в iOS

$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< /а>.

person Ty Kroll    schedule 09.12.2012
comment
Обратите внимание, что это удаляет HMAC. Без HMAC вы не сможете определить, изменил ли кто-то ваше сообщение в пути. Если злоумышленник знает часть открытого текста, он может изменить зашифрованный текст таким образом, чтобы расшифровка читалась по-другому. Например, если Ева знает, что Алиса отправляет Бобу 10 долларов, она может изменить сообщение, чтобы отправить Еве 99 долларов, не зная пароля. Я бы порекомендовал проверить HMAC, так как он предотвращает подобные атаки. (Тем не менее, спасибо за код, @bubba_gump.) - person Rob Napier; 10.01.2013
comment
С удовольствием, Роб. Я вроде как понимаю, о чем вы говорите, хотя я недостаточно искусен, чтобы понять, как злоумышленник мог это сделать. Как я могу использовать HMAC, чтобы закрыть эту дыру? - person Ty Kroll; 10.01.2013
comment
Чтобы проверить HMAC в PHP, используйте hash_hmac. Алгоритм — sha256. Данные $data выше. Ключ генерируется с помощью pbkdf2(), как указано выше, просто используйте соль HMAC, а не соль шифрования. Сравните результат hash_hmac с предоставленным HMAC. Если есть несоответствие, либо пароль был неправильным, либо зашифрованный текст был изменен. - person Rob Napier; 10.01.2013
comment
Обновлен мой ответ, чтобы проверить HMAC. В моих тестах работает. Смотри нормально? - person Ty Kroll; 14.01.2013
comment
Выглядит правильно. Однако грядущие изменения в том, как рассчитывается HMAC. См. robnapier.net/blog/rncryptor-hmac-vulnerability-827. Новый расчет будет по всем данным, кроме самого hmac. - person Rob Napier; 14.01.2013
comment
нужно ли изменить приведенный выше код PHP, чтобы он соответствовал вашим предстоящим изменениям, Роб? У меня возникли проблемы с реализацией вышеизложенного для расшифровки сообщения, полученного от RNCryptor. Каждый раз, когда он пытается расшифровать, либо происходит сбой проверки hmac, либо сообщение отличается от последнего раза (возвращает мусор). Хотя я иногда получаю частичную расшифровку. - person Tiago; 12.02.2013

Вам не нужно использовать unpack для этого.

Как только вы получите полную строку в кодировке 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 в mcrypt вместе с IV и вашим ключом.

person drew010    schedule 21.10.2012
comment
Спасибо за ваш ответ, я все еще получаю зашифрованный текст. Я отредактировал оригинал с исправленным сценарием на основе вашего ответа. - person Leon Storey; 21.10.2012
comment
Просто чтобы быть уверенным, поля заголовка (salt, IV, version, hmac и т. д.) такие же на стороне PHP, как и на стороне C? - person drew010; 22.10.2012

Верен ли MCRYPT_RIJNDAEL_128? Несмотря на то, что настройки RNCryptor предполагают использование 256, на самом деле алгоритм равен 128, а размер IV относится к размеру блока 128. Я где-то читал, чтобы заставить PHP использовать 16-байтовый IV, вы должны использовать MCRYPT_RIJNDAEL_128, а затем 256 передать ему 32-байтовый ключ.

«128» в MCRYPT_RIJNDAEL_128 относится к размеру блока, а не к размеру ключа. Алгоритм Rijndael может обрабатывать блоки нескольких размеров, но AES может обрабатывать только 128-битные блоки. Это не зависит от размера ключа. CBC IV всегда должен быть размером блока, который также всегда равен 16 байтам в AES. (Rijndael и AES очень похожи, но не идентичны. Rijndael более гибок, чем AES.)

В вашей функции pbkdf2() вы должны передать длину ключа 32 байта (256 бит), а не 16 байт. Я считаю, что модуль PHP mcrypt автоматически переключится на 256-битный AES, если будет передан 256-битный ключ (на основе комментариев к Знакомство с шифрованием PHP AES; я не особо знаком с mcrypt). Я предполагаю, что вы правильно реализуете PBKDF2; Я не изучал ваш код там.

Обратите внимание, что RNCryptor добавляет в конце 32-байтовый HMAC. Я полагаю, что ваш текущий код попытается расшифровать это, что приведет к 32 байтам мусора в конце. Как правило, вы должны удалить этот HMAC и проверить его, чтобы убедиться, что данные не были изменены при передаче и что пароль правильный.

person Rob Napier    schedule 23.10.2012