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 BM // 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 $??? ^ $???; 

    Я считаю, что конкретные варианты использования очень полезны при понимании алгоритмов. Вот два варианта использования и шаг за шагом.

    Исходная точка для обоих случаев использования.

    Эти случаи использования предполагают, что вы расшифровываете сообщения, используя AES-256 с режимом цепочки CBC и кражи шифрованного текста для квантования блоков. Чтобы сгенерировать эти случаи использования, я использовал компилятор Delphi 2010 и библиотеку LockBox3 TurboPower (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 

    … это означает, что LSB равен 3, а MSB – 16.

    Использовать первый случай

    1. Пусть сжатый ключ 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.' 
    2. Сообщение открытого текста, которое будет отправлено, будет

       Message = (UTF-8) 'Leeeeeeeeeroy Jenkins!' 

      Зашифровано это 22 байта. AES-256 имеет размер блока по 16 байтов, поэтому это где-то между 1 и 2 блоками.

    3. Пусть IV равно 1. (Помимо этого: на стороне Delphi это может быть достигнуто установкой

       TRandomStream.Instance.Seed := 1; 

      непосредственно перед шифрованием). Таким образом, шифрованное текстовое сообщение, которое должно быть расшифровано PHP, будет (с 8-байтовым IV добавлено la 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 
    4. Теперь давайте пройдем дешифрование с кражи шифрованного текста.

      • CV: = IV

         CV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
      • В общем случае для n-го блока (за исключением последних двух блоков) наш обычный алгоритм CBC …

         m[n] := Decrypt( c[n]) XOR CV; CV[n+1] := c[n] 

        где:

        • m – выходной текстовый блок;
        • Расшифровка () означает дешифрование ЕЕС-256 ЕЦБ на этом блоке;
        • CV – наш Carry-Vector. Режим цепочки определяет, как это изменяется от блока к блоку.
      • но для второго последнего блока (N-1) (N = 2 в примере использования) преобразование изменяется на … ( Это исключение сделано из-за выбора шифрования зашифрованного текста )

         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 
    5. Теперь обработать последний блок. Это частичный, длиной 6 байтов. В общем, обработка последнего блока происходит так …

       y := c[N-1] | LastBytes( m[N-2], BlockSize-Length(c[N-1])); m[N-1] := Decrypt( y) XOR CV 

      Применение к случаю 1:

       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 
    6. Последним шагом в процессе дешифрования является эмиссия последних двух блоков. Сначала мы отменяем порядок, испуская m [N-1], а затем излучаем первую часть m [N-2] (длина которой равна длине c [N-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 такие же, как в примере использования One. Зашифрованное текстовое сообщение, которое должно быть расшифровано (включая предварительный 8-байтовый IV), …

    1. Настроить

       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 
    2. Общий и второй последний блок

       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 
    3. Последний блок:

      Наш последний блок круглый в этом случае.

       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 
    4. Последним шагом в процессе дешифрования является эмиссия последних двух блоков. В круглом случае мы не отменяем порядок. Сначала мы испускаем m [N-2], а затем m [N-1]. Применяется к использованию второго варианта …

      • Emit m [0]

         m[0] = [16] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72 
      • Извлеките все m 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 «Dance», тогда, когда вы можете быть '

    5. Рассмотрение краевых дел. Есть два крайних случая, не проиллюстрированные двумя приведенными здесь примерами использования.

      • Короткие сообщения. Короткое сообщение – это сообщение, длина которого в байтах:

        • Не равен нулю; а также
        • Менее одного блока;
      • Сообщения с нулевой длиной.

    В случае коротких сообщений технически все еще можно было внедрить кражу шифрованного текста, используя IV в качестве предварительного блока зашифрованного текста. Тем не менее, IMHO, это использование шифрования зашифрованного текста, таким образом, не оправдано отсутствием исследований влияния на криптографическую мощь, не говоря уже о добавленной сложности реализации. В TurboPower LockBox 3, когда сообщение является коротким сообщением, а режим цепочки не является потоковым потоком, тогда режим цепочки рассматривается как CFB-8 бит. CFB-8 бит – это режим потоковой передачи.

    В случае сообщений с нулевой длиной это очень просто. Сообщение с незаполненным текстом нулевой длины отображает взаимно однозначные сообщения с шифротекстом нулевой длины. Никакой IV не требуется, не генерируется и не добавляется. Это отображение не зависит от режима цепочки и шифрования (в случае шифров блочного режима).

    Замечания по реализации PHP

    Предостережение

    Я не программист PHP. Я не знаю PHP. Любая вещь, которую я говорю здесь, должна быть взята с солью.

    Массивы байтов

    Похоже, вы используете строки PHP для хранения массивов байтов. Это выглядит опасно для меня. Что, если одно из байтовых значений было равно нулю? Будет ли это сокращать строку? Как будет выглядеть strlen () в этом случае? Если PHP имеет собственный тип данных, который был массивом байтов, то это, вероятно, было бы более безопасным. Но я действительно не знаю. Я просто привожу этот момент к вашему вниманию, если вы еще не знаете об этом. Возможно, это не проблема.

    Библиотека mcrypt_decrypt

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

    1. Позвоните в расшифровку библиотеки для всех, кроме двух последних блоков с режимом CBC. Обработайте последние два блока, как я описал вам. Но для этого требуется доступ к CV. Означает ли API это? Если нет, эта стратегия не является жизнеспособным вариантом для вас.

    2. Позвоните в расшифровку библиотеки для всех, кроме двух последних блоков с режимом ECB, и переместите цепочку CBC. Довольно легко реализовать и определить, у вас есть доступ к CV.

    Как сделать XOR в PHP

    Кто-то еще отправил ответ на этот вопрос, но в настоящее время его отозвал. Но он был прав. Похоже, сделать XOR в PHP на массив байтов, перебирать символы, один за другим и делать XOT уровня байта. Техника показана здесь .

    Я искал аналогичный ответ для perl. Библиотеки Perl были ограничены режимом CBC. Вот как я получил CTS для работы с использованием режима AES 256 CBC и метода CTS 3. Я думал, что это может быть полезно и для PHP.

    Вот фактическая документация NIST. Doc ID: 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; }