弹性城堡 Blowfish 引擎的 CTS 模式不能按预期工作

16
也许我的期望有误。我不是密码学专家,只是一个普通用户。我已经尽力尝试使其工作,但迄今为止没有成功。
背景信息:
我正在尝试从Delphi Encryption Compendium中移植遗留加密,它使用Blowfish引擎(TCipher_Blowfish_)和CTS操作模式(cmCTS)。私钥由RipeMD256 (THash_RipeMD256)哈希。
问题:
输入的明文字节数组需要与CIPHER_BLOCK的大小相同。据我所知,它不应该这样做。
来自维基百科:
在密码学中,密文窃取(CTS)是一种使用块密码操作模式的通用方法,允许处理不均匀分成块的消息而不会导致密文扩展,代价是稍微增加了复杂性。
输出与旧例程不同:
我正在使用:
- 相同的IV - 相同的密码 - 相同的输入明文

这个遗留应用程序使用 ANSI 字符串,新的应用程序使用 Unicode,因此对于每个输入字符串,我都调用了 Encoding.ASCII.GetBytes("plainText")Encoding.ASCII.GetBytes("privatepassword")

然后,私有密码字节通过 RipeMD256 进行哈希处理,我已经检查了输出字节,它们是相同的。

我可以确认问题特定在 Bouncy Clastle 中(操作模式或缺少配置/步骤),因为我已经下载了第二个库 Blowfish.cs 并使用 8 字节的输入(与密码块大小相同)并使用相同的 IV 的 Encrypt_CBC(bytes[]) 得到了与遗留格式相同的输出。

这是我同时使用 Blowfish.csBouncy Castle 的代码草图:

Delphi 加密手册

var 
  IV: Array [0..7] of Byte (1,2,3,4,5,6,7,8);
  Key: String = '12345678';
with TCipher_Blowfish.Create('', nil) do
begin
  try
    InitKey(Key, @IV); //Key is auto hashed using RIPE256 here;
    Result:= CodeString('12345678', paEncode, -1); //Output bytes is later encoded as MIME64 here, the result is the hash.
  finally
    Free;  
  end;
end;

Blofish.cs

var hashOfPrivateKey = HashValue(Encoding.ASCII.GetBytes("12345678"));
Blowfish b = new BlowFish(hashOfPrivateKey);

b.IV = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8};

var input = Encoding.ASCII.GetBytes("12345678");
var output = b.Encrypt_CBC(input);

我假设如果输入长度为8位,则CTS和CBC的结果始终相同。这只是幸运/巧合还是根本上的真理?

Bouncy Castle

IBufferedCipher inCipher = CipherUtilities.GetCipher("BLOWFISH/CTS");
var hashOfPrivateKey = HashValue(Encoding.ASCII.GetBytes("12345678"));
var key = new KeyParameter(hashOfPrivateKey);
var IV = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8};
var cipherParams = new ParametersWithIV(key, IV); 
inCipher.Init(true, cipherParams);
var input = Encoding.ASCII.GetBytes("12345678");

//try one: direct with DoFinal
var output = inCipher.DoFinal(input);
// output bytes different from expected

inCipher.Reset();

//try two: ProcessBytes then DoFinal
var outBytes = new byte[input.Length];
var res = inCipher.ProcessBytes(input, 0, input.Length, outBytes, 0);
var r = inCipher.DoFinal(outBytes, res);
// outBytes bytes different from expected

如我所说,我正在比较基于假设给定8字节的输入,输出将相同的CBC和CTS。如果即使相同的输入输出也不相同,我就不能使用Bouncy Castle的实现。 我不知道:
  • Delphi Encryption Compendium中使用的CTS模式是否与CBC一起使用。我无法在任何地方找到记录。
  • 调用DoFinal()和ProcessBytes()然后DoFinal()之间的区别,在Bouncy Castle中,我想象这是当输入块大于引擎块大小时需要的,在这种情况下它们具有相同的大小。
  • Delphi Encryption Compendium是否正确/错误或Bouncy Castle是否正确/错误。我没有足够的加密知识来理解实现,否则我不会在这里提问(我需要指导)。

1
如果您对 Blowfish.cs 感到满意,那么这是一个快速而简单的 BC 等效版本,只是使用了与 Blowfish.cs 相同的 CBC,源代码在此:https://pastebin.com/KQ394yWa。我认为 BC 中的 CTS 与 DEC 中的 mCTS 没有任何关系,在此处 https://github.com/evpobr/DEC/blob/master/Part_I/DECCipher.pas 中描述为“Modes cmCTSx ... 是由我开发的禁止模式”(注意:块大小为 8,因此输入必须填充为 8 字节)。另外,我建议您不要使用 ASCII,而是使用默认编码(取决于您的上下文)。 - Simon Mourier
1个回答

15
我假设如果输入长度为8位,则CTS和CBC将始终具有相同的结果。这只是幸运巧合吗?还是基本真理? 不,这是错误的陈述。以下是Wikipedia的引用:“对于CBC模式的密文窃取,并不一定要求明文长于一个块。在明文长度小于或等于一个块的情况下,初始化向量(IV)可以充当前一个块的密文。”因此,即使对于您的8字节输入情况,CTS算法也会起作用并影响输出。基本上,您关于CTS和CBS相等的说法可以颠倒: “CTS和CBC将始终具有相同的结果,直到最后两个块。” 您可以使用以下示例进行验证:
static byte[] EncryptData(byte[] input, string algorithm)
{
    IBufferedCipher inCipher = CipherUtilities.GetCipher(algorithm);
    var hashOfPrivateKey = HashValue(Encoding.ASCII.GetBytes("12345678"));
    var key = new KeyParameter(hashOfPrivateKey);
    var IV = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8 };
    var cipherParams = new ParametersWithIV(key, IV);
    inCipher.Init(true, cipherParams);

    return inCipher.DoFinal(input);
}

static void Main(string[] args)
{
    var data = Encoding.ASCII.GetBytes("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF");
    var ctsResult = EncryptData(data, "BLOWFISH/CTS");
    var cbcResult = EncryptData(data, "BLOWFISH/CBC");
    var equalPartLength = data.Length - 2 * 8;
    var equal = ctsResult.Take(equalPartLength).SequenceEqual(cbcResult.Take(equalPartLength));
}

所以这基本上是你主要问题的答案。你不应该期望在8字节输入上得到相同的CTS和CBC输出。
以下是其他问题的答案(我希望):
如果Delphi Encryption Compendium中使用的CTS模式与CBC一起使用。我找不到任何文档。
我也没有找到Delphi Encryption Compendium中CTS模式的任何文档,但源代码中有这样的注释:
cmCTSx = 双倍CBC,使用截断的最终块的CFS8填充 模式cmCTSx、cmCFSx、cmCFS8是我开发的专有模式。 这些模式的工作方式类似于cmCBCx、cmCFBx、cmCFB8,但将输入流双倍XOR到反馈寄存器中。
因此,在Delphi Encryption Compendium中实现了自定义的CTS模式,它将与Bouncy Castle的标准实现不兼容。
只调用DoFinal()和在Bouncy Castle中调用ProcessBytes()然后DoFinal()之间的区别,我想当输入块大于引擎块大小时,这种情况下它们的大小相同。

如果您需要顺序加密数据,那么调用ProcessBytes() / DoFinal()是必需的。例如,如果有大量数据要流式传输,则可能需要调用这些函数。但是,如果您有一个将整个字节数组用于加密的例程,则只需一次调用以下方便的DoFinal()重载即可:

var encryptedData = inCipher.DoFinal(plainText);

这个 DoFinal() 重载会计算输出缓冲区的大小,并在后台进行所需的ProcessBytes()DoFinal()调用。

如果Delphi Encryption Compendium正确/错误或Bouncy Castle正确/错误。我对密码学没有足够的知识来理解实现,否则我就不会在这里问问题(我需要指导)。

让我们在这里总结一下:

  1. 您不应该期望CTS和CBC针对8字节输入产生相同的输出。
  2. 看起来Delphi Encryption Compendium使用自定义算法进行CTS。由于Bouncy Castle是根据标准实现的,这些库将产生不同的结果。如果您的新应用程序不需要支持使用遗留Delphi应用程序生成的加密数据,则可以只使用Bouncy Castle并且没问题。否则,您应该使用与Delphi Encryption Compendium使用相同的自定义CTS算法,这将需要将其源代码移植到C#中,不幸的是。

更新

(有关Delphi Encryption Compendium 3.0版本的更多详细信息)

这是DEC 3.0版本的CTS编码代码:

S := @Source;
D := @Dest;

// ...

begin
    while DataSize >= FBufSize do
    begin
        XORBuffers(S, FFeedback, FBufSize, D);
        Encode(D);
        XORBuffers(D, FFeedback, FBufSize, FFeedback);
        Inc(S, FBufSize);
        Inc(D, FBufSize);
        Dec(DataSize, FBufSize);
    end;
    if DataSize > 0 then
    begin
        Move(FFeedback^, FBuffer^, FBufSize);
        Encode(FBuffer);
        XORBuffers(S, FBuffer, DataSize, D);
        XORBuffers(FBuffer, FFeedback, FBufSize, FFeedback);
    end;
end;

在这里我们看到了DEC文档中提到的双重异或。基本上,这段代码实现了以下算法:

C[i] = Encrypt( P[i] xor F[i-1] )
F[i] = F[i-1] xor C[i]
F[0] = IV

标准算法通常是:

C[i] = Encrypt( P[i] xor C[i-1] )
C[0] = IV

步骤F[i] = F[i-1] xor C[i]是DEC作者的发明,使加密结果不同。对于CTS模式至关重要的最后两个块的处理也不是按照标准实现的。以下是来自DEC v 3.0 ReadMe.txt的评论,描述了为什么作者添加了这种修改:
cmCTS模式,在加密之前和之后对数据进行异或运算。当使用InitVector时,这样做可以获得更好的安全效果,如果使用错误的InitVector,则输出是安全的,速度损失约为1%。
许多安全库的作者试图通过这种天真的修改来使基础算法“更安全”,这是一个非常常见的错误。在许多情况下,这些变化会产生反向效果并降低保护强度。当然,另一个缺点是,像您这样按照标准实现的其他库无法解密加密数据。

不幸的是,至少在一段时间内(在遗留软件仍然存在时),我需要保持向后兼容性。 我们不会移植这些例程。 我可能会将加密方法发布为dll,并在新应用程序中使用它,直到我们的客户完全升级其版本。 - EProgrammerNotFound
非常抱歉之前没有提供版本链接,但我并没有使用你提供的版本,而是使用了一个旧版本,可以在https://github.com/winkelsdorf/DelphiEncryptionCompendium/releases找到。版本号为3.0。在这些源文件中没有提到自定义CTSx模式。虽然我没有进行源代码比较,但由于这是一个端口,所以可能是相同的,不是吗? - EProgrammerNotFound
1
在DEC的3.0版本中,CTS以相同的非标准方式实现。有关更多详细信息,请检查我的答案更新。 - CodeFuller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接