3

I have been trying to implement Ciphertext Stealing(CTS) in PHP for CBC.

Referring below two links

How can I encrypt/decrypt data using AES CBC+CTS (ciphertext stealing) mode in PHP?

and

http://en.wikipedia.org/wiki/Ciphertext_stealing

I am confused and stuck on the last and simplest step of XOR. I know this is silly but having tried all the combinations, i don't know what am i missing. Code follows.

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

2 回答 2

5

我发现具体的用例对理解算法非常有帮助。这里有 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 个字节的数组。最左边的字节是数组的 Least Signficant(最低地址)字节的呈现,最右边的字节是最重要的字节。这些字节是用十六进制写的,例如,如果一个人放...

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,可以通过将 TCodec 组件的密码 ('UTF8Password') 属性设置为...

    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. 现在让我们通过密文窃取来进行解密。

    • 简历:= 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 是我们的进位向量。链接模式定义了这如何从一个块到另一个块变化。
    • 但是对于倒数第二个块(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 
    

    应用于用例一:

    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] 的长度)。申请用例一...

    • 发射 m[1]

      m[1] = [16] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65
      
    • 发出 m[0] 的前 6 个字节

      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
      

    这是 'Leeeeeeeeeeroy Jenkins!' 的 UTF-8 编码

用例二

在这个用例中,消息正好是 2 个块长。这称为圆形外壳。在圆形情况下,没有要量化的部分块,因此它就像正常的 CBC 一样进行。密码、密钥和 IV 与用例一中的相同。要解密的密文消息(包括前置 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]。申请用例二...

    • 发射 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 编码

  5. 要考虑的边缘案例。有两个边缘案例,这里提供的两个用例没有说明。

    • 短消息。短消息是一条消息,其长度以字节为单位:

      • 不为零;和
      • 不到一个街区;
    • 零长度消息。

在短消息的情况下,技术上仍然可以通过使用IV作为密文的先验块来实现密文窃取。然而,恕我直言,这种使用密文窃取的方式,由于缺乏对密码强度影响的研究,更不用说增加的实现复杂性,是不合理的。在 TurboPower LockBox 3 中,当消息是短消息,并且链模式不是密钥流时,链模式被视为 CFB-8bit。CFB-8 位是一种密钥流模式。

在零长度消息的情况下,它真的很简单。零长度明文消息一对一映射到零长度密文消息。不需要、生成或预先添加 IV。此映射独立于链接模式和密码(在块模式密码的情况下)。

PHP实现注意事项

警告

我不是 PHP 程序员。我不知道PHP。我在这里说的任何事情都应该持保留态度。

字节数组

看起来您正在使用 PHP 字符串来存储字节数组。这对我来说看起来很危险。如果其中一个字节值为零怎么办?那会缩短字符串吗?strlen() 在这种情况下会如何表现?如果 PHP 有一个本地数据类型,它是一个字节数组,那么这可能会更安全。但我真的不知道。我只是提请您注意这一点,如果您还没有意识到的话。可能这不是一个真正的问题。

mcrypt_decrypt 库

我不熟悉这个库。它本身是否支持密文窃取?我假设不是。因此,您有两种可能的策略。

  1. 使用 CBC 模式为除最后两个块之外的所有块调用库的解密。按照我向您描述的方式处理最后两个块。但这需要访问 CV。API会公开这个吗?如果不是,则此策略对您来说不是一个可行的选择。

  2. 使用 ECB 模式为除最后两个块之外的所有块调用库的解密,并滚动您的 CBC 链接。相当容易实施和定义,您可以访问 CV。

如何在 PHP 中进行异或运算

其他人发布了对此问题的答案,但目前已将其撤回。但他是对的。它看起来像是在 PHP 中对字节数组进行 XOR,逐个遍历字符,然后进行字节级 XOR。此处显示了该技术。

于 2012-09-08T11:09:01.713 回答
0

我正在为 perl 寻找类似的答案。Perl 的库仅限于 CBC 模式。以下是我使用 AES 256 CBC 模式和 CTS 方法 3 让 CTS 工作的方法。我认为这对 PHP 也可能有帮助。

这是实际的 NIST 文档。文档 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;
}
于 2016-06-15T13:37:39.257 回答