当前位置: 首页 > 知识库问答 >
问题:

使用 CBC 实现密文窃取 (CTS) 的 PHP

贺海
2023-03-14

我一直在尝试在PHP中为CBC实现密文窃取(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 $??? ^ $???;

共有2个答案

金飞翼
2023-03-14

我正在为perl寻找类似的答案。Perl的库仅限于CBC模式。以下是我如何使用AES 256 CBC模式和CTS方法3让CTS工作的。我认为这可能对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;
}
盖弘毅
2023-03-14

我发现具体的用例对理解算法很有帮助。这里有两个用例,以及一个逐步的演示。

这些用例假设您正在使用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

...这意味着LSB为3,MSB为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,这可以通过将TCodec组件的密码('UTF8Password')属性设置为…

    password = (UTF-8) 'Your lips are smoother than vasoline.'
    

    要发送的明文消息将是

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

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

    让 IV 为 1。(题外话:在德尔福方面,这可以通过设置来实现

    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
    

    现在让我们通过密文窃取来完成解密。

    >

  • 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是我们的进位向量。链接模式定义了它如何从一个块到另一个块变化。

    但是对于倒数第二个块 (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
    

    现在处理最后一个块。它是部分一个,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]]的长度)。正在应用于用例一。。。

    > < li>

    发出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
    

    这是“Leeeeeeroy Jenkins!”的UTF-8编码

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

    > < li>

    发出m[0]

    m[0] = [16] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72
    

    发出m1的全部

    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编码

    要考虑的边缘情况。这里提供的两个用例没有说明两种边缘情况。

    >

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

      < li >不为零;和 < li >不到一个街区;

    零长度消息。

    在短消息的情况下,技术上仍然可以通过使用IV作为密文的前一块来实现密文窃取。然而,IMHO,这种使用密文窃取的方式是不合理的,因为缺乏对加密强度影响的研究,更不用说增加了实现的复杂性。在TurboPower LockBox 3中,如果消息是短消息,并且链接模式不是密钥流模式,则链接模式被视为CFB-8bit。CFB-8位是一种密钥流模式。

    对于零长度消息,这非常简单。零长度明文消息一对一地映射到零长度密文消息。不需要、不生成也不预先考虑IV。这种映射独立于链接模式和密码(在块模式密码的情况下)。

    我不是PHP程序员。我不懂PHP。我在这里说的任何事情都不能全信。

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

    我对这个图书馆不熟悉。它本身是否支持密文窃取?我想不会吧。所以有两种可能的策略。

    >

  • 在CBC模式下,调用库的解密来解密除最后两个块之外的所有块。按照我向您描述的方式处理最后两个块。但这需要访问简历。API是否公开了这一点?如果没有,这个策略对你来说不是一个可行的选择。

    用ECB模式调用库的decrypt,除了最后两个块,并滚动CBC链接。相当容易实现,并被定义,你可以访问的简历。

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

  •  类似资料:
    • 我必须在PHP中以AES CTS模式(密文窃取,有时称为AES-XTS)加密和解密数据,以便与用NET平台。英寸NET 4,本机支持这种模式。 对于PHP,我找不到解决方案,根据手册,mcrypt似乎不支持这种模式。 任何人都可以解释一下普通CBC和CBC-CTS之间的区别吗?是否可以使用现有的模块/库使后者在PHP中工作?

    • 几天来,我一直试图用java解密一个用OpenSSL加密的消息。使用以下命令对邮件进行了加密: openssl enc-e-aes-256-cbc-kfile$file.key-in toto-out toto.enc。 文件file.key包含256位的对称密钥。命令中没有指定salt,但文件以salted__开头。下面是我编写的类,试图解密文件,但即使删除文件的16个字符也无法得到任何东西,即

    • 问题内容: Java 256位AES加密 基本上,我正在做的是编写一个程序,该程序将加密通过TCP / IP发送的请求,然后由服务器程序解密。加密将需要是AES,并且进行一些研究后发现我需要使用CBC和PKCS5Padding。所以基本上我也需要一个秘密密钥和一个IV。 我正在开发的应用程序是用于手机的,因此我想使用Java安全包来减小尺寸。我已经完成了设计,但是不确定IV和共享密钥的实现。 这是

    • 我有一个Python应用程序和PHP网站,通过一些特定的网络层发送消息进行通信。我的任务是使用该通道发送所有AES加密和Base64编码的消息。加密密钥是为双方手动预共享的。 在PHP中,我使用以下代码创建名为的最终消息文本: 我在我的Python应用程序中收到这样的消息,去掉了魔力,得到了base64字节的数据。我找不到一个示例来使兼容的AES密码来解码此消息的问题。 Key和“Magic”只是

    • 下面是我到现在为止所尝试的 下面是适用于我的node.js代码。在CryptoJ中,我没有成功地实现类似的功能。根据我的理解,crypto是内置库,其中的节点在其上有自己的包装器。

    • 我使用以下命令加密了一个文件 openssl rand 32>test.key openssl enc-aes-256-cbc-iter 10000-pbkdf2-salt-输入test.txt-输出test.txt.enc-通过文件:test.key 我的代码 我得到的错误 我引用了以下链接 尝试使用时,仍然得到错误