用于Blowfish引擎的Bouncy Castle CTS模式未按预期工作

EPr*_*und 17 c# delphi encryption bouncycastle

也许我的期望是错的.我不是加密专家,我只是一个简单的用户.到目前为止,我已经详尽地试图使这项工作取得成功.

背景资料:

我正在尝试从Delphi Encryption Compendium移植Legacy Encryption,它使用Blowfish Engine(TCipher_Blowfish_)和CTS操作模式(cmCTS).私钥由RipeMD256(THash_RipeMD256)进行哈希处理.

问题:

输入纯文本字节数组的大小必须相同CIPHER_BLOCK.据我所知,它不应该.

来自维基百科:

在密码学中,密文窃取(CTS)是使用分组密码操作模式的一般方法,其允许处理不可均分成块的消息而不会导致密文的任何扩展,代价是稍微增加了复杂性.

输出与旧例程不同:

我正在使用:

  • 相同的IV
  • 相同的密码
  • 相同的输入纯文本

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

然后由RipeMD256对私有密码字节进行散列,我检查了输出字节,它们是相同的.

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

这是我使用两个代码的草图Blowfish.csBouncy Castle:

德尔福加密纲要

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

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

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

充气城堡

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
Run Code Online (Sandbox Code Playgroud)

正如我所说,我将CBC与CTS进行比较,假设给定8字节输入,输出将是相同的.如果使用相同的输入输出不相同,我无法使用Bouncy Castle转发实现.

我不知道:

  • 如果Delphi Encryption Compendium中使用的CTS模式使用CBC和CTS.我无法在任何地方找到记录.
  • 在Bouncy Castle中调用DoFinal()和ProcessBytes()然后调用DoFinal()之间的区别,我想当输入块大于引擎块大小时需要它,在这种情况下它们是相同的大小.
  • 如果Delphi Encryption Compendium是正确/错误或者如果Bouncy Castle是正确/错误的.我没有足够的密码学知识来理解实现,否则我不会在这里问一个问题(我需要指导).

Cod*_*ler 14

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

不,这是一个错误的陈述.

以下是维基百科的引用:

用于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));
}
Run Code Online (Sandbox Code Playgroud)

所以这基本上是你主要问题的答案.对于8字节输入,您不应期望CTS和CBC具有相同的输出.

以下是您对其他问题的答案(我希望如此):

如果Delphi Encryption Compendium中使用的CTS模式使用CBC和CTS.我无法在任何地方找到记录.

我还没有在Delphi Encryption Compendium中找到任何CTS模式的文档,但是在源代码中有这样的注释:

cmCTSx =双CBC,具有截断的最终块的CFS8填充

模式cmCTSx,cmCFSx,cmCFS8是我开发的专有模式.这些模式的工作原理如cmCBCx,cmCFBx,cmCFB8,但输入流的双XOR输入反馈寄存器.

因此,似乎CTS模式是在Delphi Encryption Compendium中以自定义方式实现的,这与Bouncy Castle的标准实现不兼容.

在Bouncy Castle中调用DoFinal()和ProcessBytes()然后调用DoFinal()之间的区别,我想当输入块大于引擎块大小时需要它,在这种情况下它们是相同的大小.

如果按顺序加密数据,则需要调用pair ProcessBytes()/ DoFinal().例如,如果流式传输大量数据,则可能需要它.但是,如果你有一个例程,它需要整个字节数组进行加密,你可以调用DoFinal()一次方便的重载:

var encryptedData = inCipher.DoFinal(plainText);
Run Code Online (Sandbox Code Playgroud)

这种DoFinal()过载将计算的输出缓冲区的大小,使所需的呼叫ProcessBytes()DoFinal() 引擎盖下.

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

我们在这里总结一下:

  1. 对于8字节输入,您不应期望CTS和CBC具有相同的输出.
  2. 似乎Delphi Encryption Compendium使用自定义算法进行CTS.由于Bouncy Castle是根据标准实施的,因此这些库将产生不同的结果.如果您的新应用程序不需要支持使用旧版Delphi应用程序生成的加密数据,那么您可以使用Bouncy Castle并且可以.在其他情况下,您应该使用Delphi Encryption Compendium使用的相同的自定义CTS算法,遗憾的是,它需要将其源代码端口转移到C#.

UPDATE

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

这是来自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;
Run Code Online (Sandbox Code Playgroud)

在这里,我们看到DEC文档中提到的双XOR'ing.基本上这段代码实现了以下算法:

C[i] = Encrypt( P[i] xor F[i-1] )
F[i] = F[i-1] xor C[i]
F[0] = IV
Run Code Online (Sandbox Code Playgroud)

而标准算法将是:

C[i] = Encrypt( P[i] xor C[i-1] )
C[0] = IV
Run Code Online (Sandbox Code Playgroud)

该步骤F[i] = F[i-1] xor C[i]是DEC作者发明并使加密结果不同.对CTS模式至关重要的最后两个块的处理也不是通过标准实现的.

以下是DEC v 3.0 ReadMe.txt的评论,该评论描述了作者添加此类修改的原因:

cmCTS模式,XOR是加密之前和之后的数据.使用InitVector时,这具有更好的Securityeffect,当使用错误的InitVector时输出是安全的,ca 1%速度损失

当安全库的作者试图通过这种天真的修改使底层算法"更安全"时,这是一个非常常见的错误.在许多情况下,这种变化具有相反的效果并降低保护强度.当然,另一个缺点是加密数据无法通过根据标准实现的其他库解密,就像您的情况一样.