从C#调用时,从CFB模式截断输出

Truncated output from CFB mode when calling from C#

本文关键字:输出 模式 调用 CFB      更新时间:2023-10-16

我有一个非常烦人的问题,我两天都无法解决。我有一个encrypt()方法,它利用了用C++编写的Crypto++库。方法实现如下:

string CRijndaelHelper::Encrypt(string text)
{
    CFB_Mode< AES >::Encryption e;
    e.SetKeyWithIV(_key, sizeof(_key), _iv);
    string cipher, encoded;
    // CFB mode must not use padding. Specifying
    //  a scheme will result in an exception
    StringSource ss(text, true,
        new StreamTransformationFilter(e,
            new StringSink(cipher)
        ));     
    return cipher;
};

当我在本机环境中调用此方法时,它工作得很好,可以毫无问题地加密整个26Kb的xml字符串。

最终,我不得不在C#代码中实现加密,为此,我在本机dll中编写了以下包装器代码,以便稍后与PInvoke一起使用:

extern "C"
API_EXPORT char* _cdecl encrypt(char* contents)
{   
    string cont(contents);
    CRijndaelHelper rij;
    string transformedText = rij.Encrypt(cont);     
    return marshalReturn(transformedText);  
}

而PInvoke部分看起来如下:

[DllImport("EncryptDecrypt.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string encrypt([MarshalAs(UnmanagedType.LPStr)] string contents);

一切看起来都很完美,除了我在transformedText变量中只加密了前540个字节,仅此而已。

请告知。

您的根本问题是使用了错误的类型。加密适用于字节数组,而不适用于文本。您需要停止使用stringchar*等。在非托管代码中需要使用unsigned char*,在托管代码中使用byte[]

你可能很难接受这个建议,但我还是建议你的。除了零字节被视为空终止符的问题(截断的原因(之外,您当前的方法只是忽略了文本编码的问题。你不能那样做。

您可能有充分的理由希望将一个字符串转换为另一个字符串。这很好,但转换的加密部分需要对字节数组进行操作。处理文本到文本加密的方法是使用转换链。加密时的转换运行方式如下:

  1. 使用定义良好的编码将字符串转换为字节数组。例如,UTF-8
  2. 加密字节数组,这将导致输出另一个字节数组
  3. 使用例如base64将此加密字节数组编码为文本

这尊重加密在字节数组上操作的事实,以及对文本编码的明确性。

在相反的方向上,您颠倒步骤:

  1. 将base64解码为加密的字节数组
  2. 解密此字节数组
  3. 使用UTF-8对解密后的字节数组进行解码,以获得原始字符串

返回从本机代码调用时正确加密的字符串

问题不在于C++encrypt,它使用std::string。问题是将它编组回作为char*的托管代码。

CRijndaelHelper::Encrypt更改为以下内容以删除将以1/255的概率大量散布的嵌入NULL

#include <cryptopp/base64.h>
using CryptoPP::Base64Encoder;
...
string CRijndaelHelper::Encrypt(string text)
{
    CFB_Mode< AES >::Encryption e;
    e.SetKeyWithIV(_key, sizeof(_key), _iv);
    string cipher, encoded;
    // CFB mode must not use padding. Specifying
    //  a scheme will result in an exception
    StringSource ss(text, true,
        new StreamTransformationFilter(e,
            new Base64Encoder(
                new StringSink(cipher)
        )));     
    return cipher;
};

对于Base64编码,作为char*的封送处理将而不是截断遇到的第一个NULL字节的加密结果。

然后,解密:

StringSource ss(cipher, true,
    new Base64Decoder(
        new StreamTransformationFilter(d,
            new StringSink(text)
    )));