PHP, реализующий Ciphertext Stealing (CTS) с CBC
Я пытался реализовать Ciphertext Stealing(CTS) в PHP для CBC.
Ссылаясь ниже на две ссылки
Как я могу зашифровать / расшифровать данные, используя режим AES CBC+CTS (шифрование текста) в PHP?
а также
http://en.wikipedia.org/wiki/Ciphertext_stealing
Я запутался и застрял на последнем и простейшем шаге XOR. Я знаю, что это глупо, но перепробовав все комбинации, я не знаю, что мне не хватает. Кодекс следует.
// 1. Decrypt the second to last ciphertext block, using zeros as IV.
$second_to_last_cipher_block = substr($cipher_text, strlen($cipher_text) - 32, 16);
$second_to_last_plain = @mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $second_to_last_cipher_block, MCRYPT_MODE_CBC);
// 2. Pad the ciphertext to the nearest multiple of the block size using the last B-M
// bits of block cipher decryption of the second-to-last ciphertext block.
$n = 16 - (strlen($cipher_text) % 16);
$cipher_text .= substr($second_to_last_plain, -$n);
// 3. Swap the last two ciphertext blocks.
$cipher_block_last = substr($cipher_text, -16);
$cipher_block_second_last = substr($cipher_text, -32, 16);
$cipher_text = substr($cipher_text, 0, -32) . $cipher_block_last . $cipher_block_second_last;
// 4. Decrypt the ciphertext using the standard CBC mode up to the last block.
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($cipher, $key, $iv);
$plain_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $cipher_text, MCRYPT_MODE_CBC , $iv);
// 5. Exclusive-OR the last ciphertext (was already decrypted in step 1) with the second last ciphertext.
// ???
// echo $??? ^ $???;
2 ответа
Я считаю, что конкретные варианты использования очень полезны для понимания алгоритмов. Вот 2 варианта использования и пошаговое описание.
Отправная точка для обоих вариантов использования.
В этих сценариях использования предполагается, что для расшифровки сообщений используется AES-256 с режимом цепочки CBC и кражей зашифрованного текста для квантования блоков. Для генерации этих вариантов использования я использовал компилятор Delphi 2010 и библиотеку TurboPower LockBox3 (версия SVN 243). В дальнейшем я использую обозначение, как так...
IV := [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
... чтобы обозначить, что некоторой переменной с именем 'IV' присвоено значение, равное массиву из 16 байтов. Самый левый байт - это рендеринг байта наименее значимого (самого низкого адреса) массива, а самого правого байта - самого значимого. Эти байты записаны в шестнадцатеричном формате, например, если поставить
X := [2] 03 10
... это означает, что младший бит равен 3, а старший - 16.
Вариант использования один
Пусть сжатый ключ AES-256 32 байта (как определено в стандарте AES) будет...
key = [32] 0D EE 8F 9F 8B 0B D4 A1 17 59 FA 05 FA 2B 65 4F 23 00 29 26 0D EE 8F 9F 8B 0B D4 A1 17 59 FA 05
С помощью TurboPower LockBox 3 этого можно достичь, установив для свойства пароля (UTF8Password) компонента TCodec значение...
password = (UTF-8) 'Your lips are smoother than vasoline.'
Текстовое сообщение для отправки будет
Message = (UTF-8) 'Leeeeeeeeeroy Jenkins!'
Закодировано это длиной 22 байта. AES-256 имеет размер блока 16 байт, так что это где-то между 1 и 2 блоками.
Пусть IV будет 1. (В стороне: на стороне Delphi это может быть достигнуто путем установки
TRandomStream.Instance.Seed := 1;
как раз перед шифрованием). Таким образом, зашифрованное текстовое сообщение, которое будет расшифровано PHP, будет (с 8-байтовым IV, добавленным в виде LockBox3) ...
ciphertext = [30] 01 00 00 00 00 00 00 00 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5 1D 66 DB 97 2E 2C (base64 equivalent ='AQAAAAAAAAAXXMCX/+9jWoiDbABiv4flHWbbly4s')
Разбивая это на IV, первый блок зашифрованного текста (c[0]) и последний (частичный) блок зашифрованного текста (c[ 1])...
IV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c[0] = [16] 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5 c[1] = [6] 1D 66 DB 97 2E 2C
Теперь давайте разберемся с расшифровкой зашифрованного текста.
CV: = IV
CV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
В общем, для n-го блока (кроме последних 2-х блоков) наш обычный алгоритм CBC...
m[n] := Decrypt( c[n]) XOR CV; CV[n+1] := c[n]
где:
- m - выходной текстовый блок;
- Decrypt () означает расшифровку AES-256 ECB в этом блоке;
- CV - это наш Carry-Vector. Режим цепочки определяет, как это меняется от блока к блоку.
но для второго последнего блока (N-1) (N=2 в варианте использования 1) преобразование изменяется на... (Это исключение сделано из-за выбора кражи зашифрованного текста)
m[n] := Decrypt( c[n]) XOR CV; CV[n+1] := CV[n] // Unchanged!
Применение к нашему варианту использования:
CV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c[0] = [16] 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5 Decrypt(c[0]) = [16] 6F 6B 69 6E 73 21 F0 7B 79 F2 AF 27 B1 52 D6 0B m[0] := Decrypt(c[0]) XOR CV = [16] 6E 6B 69 6E 73 21 F0 7B 79 F2 AF 27 B1 52 D6 0B
Теперь обработать последний блок. Он частичный, длиной 6 байт. В общем, обработка последнего блока идет так...
y := c[N-1] | LastBytes( m[N-2], BlockSize-Length(c[N-1])); m[N-1] := Decrypt( y) XOR CV
Применение к первому варианту использования:
c[1] = [6] 1D 66 DB 97 2E 2C y := c[1] | LastBytes( m[0], 10) y = [16] 1D 66 DB 97 2E 2C F0 7B 79 F2 AF 27 B1 52 D6 0B Decrypt( y) = [16]= 4D 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65 m[1] := Decrypt(y) XOR CV m[1] = [16] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65
Последним этапом процесса дешифрования является излучение последних двух блоков. Мы изменяем порядок, сначала испуская m[N-1], а затем испускаем первую часть m[N-2] (длина которой равна длине c[N-1]). Применение к варианту использования 1...
Emit m [1]
m[1] = [16] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65
Извлечь первые 6 байтов из m[0]
FirstBytes( m[0], 6) = 6E 6B 69 6E 73 21
В целом, мы получаем восстановленный текст...
[22] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65 6E 6B 69 6E 73 21
которая является кодировкой UTF-8 'Leeeeeeeeeroy Jenkins!'
Вариант использования второй
В этом случае использование сообщения составляет ровно 2 блока. Это называется круглый случай. В круглых случаях нет частичного блока для количественной оценки, поэтому он действует так, как если бы это был обычный CBC. Пароль, ключ и IV такие же, как в первом случае использования. Зашифрованное текстовое сообщение, которое необходимо расшифровать (включая 8-байтовый IV с добавлением),...
Настроить
Ciphertext = [40] 01 00 00 00 00 00 00 00 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83 which is encoded base64 as 'AQAAAAAAAABwdhJYTjgc4ZLKNPuaN8UKdfILRqHfVmDUXHZLUhnagw=='
Это разбивается на IV, первый блок и второй блок, вот так...
IV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c[0] = [16] 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A c[1] = [16] 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83
Генеральный и второй последний блок
Decrypt(c[0]) = [16] 45 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72 m[0] := Decrypt(c[0]) XOR CV = [16] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72 Next CV := c[0] = [16] 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A
Последний блок:
Наш последний блок является круглым в этом случае использования.
Decrypt(c[1]) = [16] 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83 m[1] := Decrypt(c[1]) XOR CV = [16] 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65
Последним этапом процесса дешифрования является излучение последних двух блоков. В круглом случае мы не меняем порядок. Сначала мы испускаем m [N-2], а затем m[N-1]. Применение к варианту использования два...
Испускать m[0]
m[0] = [16] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72
Выбрось всего м 1
m[1] = [16] 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65
В целом, мы получаем восстановленный текст...
[32] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65
кодировка UTF-8 "Танцуй тогда, где бы ты ни был"
Краевые случаи для рассмотрения. Существует два крайних случая, которые не показаны в двух описанных здесь случаях использования.
Короткие сообщения. Короткое сообщение - это сообщение, длина которого в байтах:
- Не ноль; а также
- Менее одного блока;
Сообщения нулевой длины.
В случае коротких сообщений технически все еще можно реализовать кражу зашифрованного текста, используя IV в качестве предыдущего блока зашифрованного текста. Однако, ИМХО, такое использование кражи зашифрованного текста, таким образом, не оправдывается отсутствием исследований влияния на криптографическую стойкость, не говоря уже о дополнительной сложности реализации. В TurboPower LockBox 3, когда сообщение является коротким сообщением, а режим цепочки не является потоковым режимом, режим цепочки обрабатывается как CFB-8bit. Бит CFB-8 - это режим потоковой передачи ключей.
В случае сообщений нулевой длины это действительно просто. Текстовое сообщение нулевой длины преобразует один за другим в зашифрованные текстовые сообщения нулевой длины. Никакая IV не нужна, произведена, или подготовлена. Это отображение не зависит от режима цепочки и шифра (в случае шифров блочного режима).
Примечания по реализации PHP
Предостережение
Я не программист PHP. Я не знаю PHP. Любая вещь, которую я здесь говорю, должна быть взята с крошкой соли.
Массивы байтов
Похоже, вы используете строки PHP для хранения массивов байтов. Это выглядит опасно для меня. Что если одно из значений байтов было равно нулю? Будет ли это сократить строку? Как будет вести себя strlen() в этом случае? Если PHP имеет собственный тип данных, который был массивом байтов, то это, вероятно, будет безопаснее. Но я действительно не знаю. Я просто довожу этот вопрос до вашего сведения, если вы еще не знаете об этом. Возможно, это не совсем проблема.
библиотека mcrypt_decrypt
Я не знаком с этой библиотекой. Поддерживает ли он изначально кражу шифротекста? Я предполагаю, что нет. Таким образом, есть две возможные стратегии для вас.
Вызовите расшифровку библиотеки для всех, кроме двух последних блоков, в режиме CBC. Обработайте последние два блока, как я вам описал. Но для этого нужен доступ к резюме. API выставляет это? Если нет, эта стратегия не подходит для вас.
Вызовите расшифровку библиотеки для всех, кроме двух последних блоков в режиме ECB, и разверните цепочку CBC. Достаточно прост в реализации, и, по определению, у вас есть доступ к резюме.
Как сделать XOR в PHP
Кто-то еще опубликовал ответ на этот вопрос, но в настоящее время снял его. Но он был прав. Похоже, сделать XOR в PHP на массив байтов, перебрать символы, один за другим, и сделать XOR уровня байтов. Техника показана здесь.
Я искал похожий ответ для Perl. Библиотеки Perl были ограничены режимом CBC. Вот как я заставил CTS работать в режиме AES 256 CBC и методе CTS 3. Я подумал, что это может быть полезно и для PHP.
Вот фактическая документация NIST. Идентификатор документа: NIST800-38A CBC-CS3 Название: Рекомендация режимов работы блочного шифра; Три варианта кражи зашифрованного текста для режима CBC Источник: http://csrc.nist.gov/publications/nistpubs/800-38a/addendum-to-nist_sp800-38A.pdf
Вот код...
use Crypt::CBC;
use Crypt::Cipher::AES;
my $key = pack("H*","0000000000000000000000000000000000000000000000000000000000000000");
my $iv = pack("H*","00000000000000000000000000000000");
my $pt = pack("H*","0000000000000000000000000000000000");
my $ct = aes256_cbc_cts_decrypt( $key, $iv, $pt );
#AES 256 CBC with CTS
sub aes256_cbc_cts_decrypt {
my ($key, $iv, $in) = @_;
my $len_in_bytes = length(unpack("H*", $in)) / 2;
my $in_idx = 0;
my $null_iv = pack( "H32", "00000000000000000000000000000000");
my $cipher = Crypt::CBC->new(
-key => $key,
-iv => $null_iv,
-literal_key => '1',
-keysize => 32,
-blocksize => 16,
-header => 'none',
-cipher => 'Crypt::Cipher::AES');
my $out;
while ( $len_in_bytes >= 16 )
{
my $tmp = substr($in, $in_idx, 16);
my $outblock = $cipher->decrypt($tmp);
if ( ( ($len_in_bytes % 16) eq 0 ) || ( $len_in_bytes > 32 ) )
{
$outblock = $outblock ^ $iv;
$iv = $tmp;
}
$out .= $outblock;
$in_idx += 16;
$len_in_bytes -= 16;
}
if ($len_in_bytes) {
my $tmp = substr($in,$in_idx,$len_in_bytes);
my $out_idx = $in_idx - 16;
$tmp .= substr($out,$out_idx + $len_in_bytes, 16 - $len_in_bytes);
$out .= substr($out, $out_idx, $len_in_bytes) ^ substr($tmp, 0, $len_in_bytes);
substr($out,$out_idx,16) = $iv ^ $cipher->decrypt($tmp);
}
return $out;
}