Как добавить / удалить pkcs7 padding из зашифрованной строки AES?



Я пытаюсь зашифровать / расшифровать строку, используя 128-битное шифрование AES (ECB). Что я хочу знать, так это как я могу добавить / удалить pkcs7 padding к нему. Кажется, что расширение Mcrypt может позаботиться о шифровании / дешифровании, но заполнение должно быть добавлено/удалено вручную.



Есть идеи?

871   3  

3 ответов:

Давайте посмотрим. PKCS #7 описан в RFC 5652 (синтаксис криптографического сообщения).

Сама схема заполнения приведена в разделе 6.3. Процесс шифрования содержимого . Он по существу говорит: добавьте столько байтов, сколько необходимо для заполнения данного размера блока (но по крайней мере один), и каждый из них должен иметь длину заполнения в качестве значения.

Таким образом, глядя на последний расшифрованный байт, мы знаем, сколько байтов нужно удалить. (Можно также проверить, что все они имеют одинаковый значение.)

Теперь я мог бы дать вам пару функций PHP для этого, но мой PHP немного заржавел. Так что либо сделайте это сами (затем не стесняйтесь редактировать мой ответ, чтобы добавить его), либо посмотрите на заметки , внесенные пользователем в документацию mcrypt-довольно многие из них посвящены заполнению и обеспечивают реализацию PKCS #7 padding.


Итак, давайте посмотрим на первую заметку там подробно:

<?php

function encrypt($str, $key)
 {
     $block = mcrypt_get_block_size('des', 'ecb');

Это возвращает размер блока используемого алгоритм. В вашем случае вы бы использовали aes или rijndael_128 вместо des, я полагаю (я не проверял это). (Вместо этого вы можете просто взять 16 здесь для AES, вместо вызова функции.)

     $pad = $block - (strlen($str) % $block);

При этом вычисляется размер отступа. strlen($str) это длина ваших данных (в байтах), % $block дает остаток по модулю $block, то есть количество байтов данных в последнем блоке. $block - ... таким образом, дает число байт, необходимых для заполнения этого последнего блока (теперь это число между 1 и $block, включительно).

     $str .= str_repeat(chr($pad), $pad);

str_repeat производит строку, состоящую из повторения той же строки, здесь повторение символа , заданного $pad, $pad times, то есть строка длины $pad, заполненная $pad. $str .= ... добавляет эту строку заполнения к исходным данным.

     return mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);

Вот само шифрование. Использование MCRYPT_RIJNDAEL_128 вместо MCRYPT_DES.

 }

Теперь другое направление:

 function decrypt($str, $key)
 {   
     $str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);

Расшифровка. (Вы, конечно, измените алгоритм, как описано выше). $str теперь является расшифрованной строкой, включая заполнение.

     $block = mcrypt_get_block_size('des', 'ecb');

Это снова размер блока. (Смотреть выше.)

     $pad = ord($str[($len = strlen($str)) - 1]);

Это выглядит немного странно. Лучше написать его в несколько шагов:

    $len = strlen($str);
    $pad = ord($str[$len-1]);

$len теперь это длина дополненной строки, а $str[$len - 1] - последний символ этой строки. ord преобразует его в число. Таким образом, $pad - это число, которое мы ранее использовали в качестве значения заполнения для обивка, и это длина обивки.

     return substr($str, 0, strlen($str) - $pad);

Итак, теперь мы отсекаем последние $pad байты из строки. (Вместо strlen($str) мы могли бы также написать $len здесь: substr($str, 0, $len - $pad).).

 }

?>
Обратите внимание, что вместо использования substr($str, $len - $pad) можно также написать substr($str, -$pad), так как функция substr в PHP имеет специальную обработку для отрицательных операндов/аргументов, чтобы считать с конца строки. (Я не знаю, является ли это более или менее эффективным, чем сначала получить длину и вычислить индекс вручную.)

Как было сказано ранее и отмечено в комментарии rossum, вместо того, чтобы просто снимать заполнение, как это сделано здесь, вы должны проверить, что оно правильно - т. е. посмотрите на substr($str, $len - $pad) и проверьте, что все его байты являются chr($pad). Это служит небольшой проверкой на коррупцию (хотя эта проверка более эффективна, если вы используете режим цепочки вместо ЕЦБ и не заменяете реальный MAC).


(и все же скажите своему клиенту, что он должен подумать о переходе на более безопасный режим, чем ЕЦБ.)

Я создал два метода для выполнения заполнения и распаковки. Эти функции задокументированы с помощью phpdoc и требуют PHP 5. Как вы заметите, функция unpad содержит много обработки исключений, генерируя не менее 4 различных сообщений для каждой возможной ошибки.

Чтобы получить размер блока для PHP mcrypt, вы можете использовать mcrypt_get_block_size, что также определяет размер блока в байтах вместо битов.

/**
 * Right-pads the data string with 1 to n bytes according to PKCS#7,
 * where n is the block size.
 * The size of the result is x times n, where x is at least 1.
 * 
 * The version of PKCS#7 padding used is the one defined in RFC 5652 chapter 6.3.
 * This padding is identical to PKCS#5 padding for 8 byte block ciphers such as DES.
 *
 * @param string $plaintext the plaintext encoded as a string containing bytes
 * @param integer $blocksize the block size of the cipher in bytes
 * @return string the padded plaintext
 */
function pkcs7pad($plaintext, $blocksize)
{
    $padsize = $blocksize - (strlen($plaintext) % $blocksize);
    return $plaintext . str_repeat(chr($padsize), $padsize);
}

/**
 * Validates and unpads the padded plaintext according to PKCS#7.
 * The resulting plaintext will be 1 to n bytes smaller depending on the amount of padding,
 * where n is the block size.
 *
 * The user is required to make sure that plaintext and padding oracles do not apply,
 * for instance by providing integrity and authenticity to the IV and ciphertext using a HMAC.
 *
 * Note that errors during uppadding may occur if the integrity of the ciphertext
 * is not validated or if the key is incorrect. A wrong key, IV or ciphertext may all
 * lead to errors within this method.
 *
 * The version of PKCS#7 padding used is the one defined in RFC 5652 chapter 6.3.
 * This padding is identical to PKCS#5 padding for 8 byte block ciphers such as DES.
 *
 * @param string padded the padded plaintext encoded as a string containing bytes
 * @param integer $blocksize the block size of the cipher in bytes
 * @return string the unpadded plaintext
 * @throws Exception if the unpadding failed
 */
function pkcs7unpad($padded, $blocksize)
{
    $l = strlen($padded);

    if ($l % $blocksize != 0) 
    {
        throw new Exception("Padded plaintext cannot be divided by the block size");
    }

    $padsize = ord($padded[$l - 1]);

    if ($padsize === 0)
    {
        throw new Exception("Zero padding found instead of PKCS#7 padding");
    }    

    if ($padsize > $blocksize)
    {
        throw new Exception("Incorrect amount of PKCS#7 padding for blocksize");
    }

    // check the correctness of the padding bytes by counting the occurance
    $padding = substr($padded, -1 * $padsize);
    if (substr_count($padding, chr($padsize)) != $padsize)
    {
        throw new Exception("Invalid PKCS#7 padding encountered");
    }

    return substr($padded, 0, $l - $padsize);
}

Это не делает недействительным ответ Павло Эбермана в в любом случае, это в основном тот же ответ в code & phpdoc вместо описания.


Обратите внимание, что возврат ошибки заполнения, злоумышленник может привести к обивка атаки оракула, которая полностью ломает общий анализ крови (CBC используется, когда вместо ЕЦБ или безопасную проверку подлинности шифра).

Просто вызовите следующую функцию после расшифровки данных

function removePadding($decryptedText){
    $strPad = ord($decryptedText[strlen($decryptedText)-1]);
    $decryptedText= substr($decryptedText, 0, -$strPad);
    return $decryptedText;
}

Comments

    Ничего не найдено.