PHP通过CBC实现密文窃取(CTS)

Mad*_*hur 3 php cryptography aes lockbox-3 cbc-mode

我一直在尝试在PHP中为CBC实现Ciphertext Stealing(CTS)。

在下面引用两个链接

如何在PHP中使用AES CBC + CTS(密文窃取)模式加密/解密数据?

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 $??? ^ $???;
Run Code Online (Sandbox Code Playgroud)

Sea*_*kin 5

我发现具体的用例对理解算法非常有帮助。这里有2个用例,以及分步演练。

两种用例的起点。

这些用例假定您要解密的消息使用具有CBC链接模式和密文窃取的AES-256进行块量化。为了生成这些用例,我使用了Delphi 2010编译器和TurboPower LockBox3库(SVN修订版243)。在下面的内容中,我使用这样的表示法...

IV := [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Run Code Online (Sandbox Code Playgroud)

...表示将名为“ IV”的某些变量分配为等于16个字节的数组。最左边的字节是数组的最低符号(最低地址)字节的呈现,而最右边的字节是最高有效字节的呈现。这些字节以十六进制写入,因此例如,如果放置...

X := [2] 03 10
Run Code Online (Sandbox Code Playgroud)

...这表示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
    
    Run Code Online (Sandbox Code Playgroud)

    使用TurboPower LockBox 3,可以通过将TCodec组件的密码('UTF8Password')属性设置为...来实现。

    password = (UTF-8) 'Your lips are smoother than vasoline.'
    
    Run Code Online (Sandbox Code Playgroud)
  2. 要发送的纯文本消息将是

    Message = (UTF-8) 'Leeeeeeeeeroy Jenkins!'
    
    Run Code Online (Sandbox Code Playgroud)

    编码为22个字节长。AES-256的块大小为16字节,因此长度在1到2个块之间。

  3. 令IV为1。(此外:在Delphi端,这可以通过设置

    TRandomStream.Instance.Seed := 1;
    
    Run Code Online (Sandbox Code Playgroud)

    加密之前)。因此,将由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')
    
    Run Code Online (Sandbox Code Playgroud)

    将其分解为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
    
    Run Code Online (Sandbox Code Playgroud)
  4. 现在,让我们逐步了解密文窃取的解密过程。

    • 简历:= IV

      CV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      
      Run Code Online (Sandbox Code Playgroud)
    • 通常,对于第n个块(最后两个块除外),我们的常规CBC算法是...

      m[n]    := Decrypt( c[n]) XOR CV;
      CV[n+1] := c[n]
      
      Run Code Online (Sandbox Code Playgroud)

      哪里:

      • m是输出的纯文本块;
      • Decrypt()表示该块上的AES-256 ECB解密;
      • 简历是我们的载体。链接模式定义了它如何在块之间变化。
    • 但是对于倒数第二个块(N-1)(用例1中的N = 2),转换更改为...(此例外是由于选择了密文窃取而造成的

      m[n]    := Decrypt( c[n]) XOR CV;
      CV[n+1] := CV[n] // Unchanged!
      
      Run Code Online (Sandbox Code Playgroud)
    • 适用于我们的用例:

      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
      
      Run Code Online (Sandbox Code Playgroud)
  5. 现在处理最后一个块。它是一部分,长度为6个字节。通常,最后一个块的处理如下:

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

    应用于用例一:

    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
    
    Run Code Online (Sandbox Code Playgroud)
  6. 解密过程的最后一步是最后两个块的发射。我们颠倒顺序,先发射m [N-1],然后发射m [N-2]的第一部分(其长度等于c [N-1]的长度)。申请用例一...

    这是“ Leeeeeeeeeroy 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=='
    
    Run Code Online (Sandbox Code Playgroud)

    可以分为四段,第一段和第二段,就像这样...

    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
    
    Run Code Online (Sandbox Code Playgroud)
  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
    
    Run Code Online (Sandbox Code Playgroud)
  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
    
    Run Code Online (Sandbox Code Playgroud)
  4. 解密过程的最后一步是最后两个块的发射。在这种情况下,我们不会撤销订单。我们先发射m [N-2],然后发射m [N-1]。应用于用例二...

    这是“随处跳舞”的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中进行XOR

有人发布了该问题的答案,但目前已撤回。但是他是对的。似乎要在PHP中对字节数组进行XOR,一个接一个地遍历字符,然后对字节进行XOR。该技术在此处显示。