Я пытаюсь зашифровать / расшифровать строку с использованием 128-битного шифрования AES (ECB). То, что я хочу знать, — как я могу добавить / удалить заполнение PKCS7 к нему. Кажется, что расширение Mcrypt может позаботиться о шифровании / дешифровании, но заполнение должно быть добавлено / удалено вручную.
Есть идеи?
Посмотрим. PKCS # 7 описан в RFC 5652 (Синтаксис криптографических сообщений).
Сама схема заполнения дана в разделе 6.3. Процесс шифрования контента. По сути, это говорит: добавьте столько байтов, сколько необходимо для заполнения заданного размера блока (но не менее одного), и каждый из них должен иметь длину заполнения в качестве значения.
Таким образом, глядя на последний расшифрованный байт, мы знаем, сколько байтов нужно удалить. (Можно также проверить, что все они имеют одинаковое значение.)
Теперь я мог бы дать вам пару функций PHP для этого, но мой PHP немного ржавый. Так что либо делайте это самостоятельно (затем смело редактируйте мой ответ, чтобы добавить его), либо посмотрите на добавленные пользователем заметки в документации по mcrypt — некоторые из них касаются заполнения и реализации реализации дополнения PKCS # 7.
Итак, давайте посмотрим на первая заметка там в деталях:
<?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
раз, то есть строка длины $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 имеет специальную обработку для отрицательных операндов / аргументов, чтобы отсчитывать от конца строки. (Я не знаю, является ли это более или менее эффективным, чем сначала получить длину и вычислить индекс вручную.)
Как было сказано ранее и отмечено в комментарии Россумом, вместо того, чтобы просто снять отступы, как здесь сделано, вы должны проверить, что это правильно — то есть посмотрите на substr($str, $len - $pad)
и убедитесь, что все его байты chr($pad)
, Это служит небольшой проверкой на предмет повреждения (хотя эта проверка более эффективна, если вы используете режим цепочки вместо ECB, и не является заменой для реального MAC).
(И все же, скажите вашему клиенту, что он должен подумать о переходе на более безопасный режим, чем ECB.)
Я создал два метода, чтобы выполнить padding и unpadding. Функции документированы с использованием 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);
}
Это никоим образом не лишает законной силы ответ Пауло Эбермана, это в основном тот же самый ответ в коде & phpdoc вместо описания.
Обратите внимание, что возвращение злоумышленнику ошибки заполнения может привести к набивка оракула который полностью нарушает CBC (когда CBC используется вместо ECB или шифра с проверкой подлинности).
Просто вызовите следующую функцию после расшифровки данных
function removePadding($decryptedText){
$strPad = ord($decryptedText[strlen($decryptedText)-1]);
$decryptedText= substr($decryptedText, 0, -$strPad);
return $decryptedText;
}