使用Crypto实现c++和c#之间加密的互操作性

interoperability of encryption between C++ and C# with Crypto

本文关键字:之间 加密 互操作性 Crypto 实现 c++ 使用      更新时间:2023-10-16

我正试图在Qt项目中使用Crypto++ lib加密c++中的字符串,并在web应用程序中解密c#中的字符串。这是我的代码。

c++代码,使用Crypto++ lib

std::string Crypter::encrypt(const std::string& str_in, const std::string& key, const std::string& iv)
{
    std::string str_out;
    CryptoPP::CFB_Mode<CryptoPP::AES>::Encryption encryption((byte*)key.c_str(), key.length(), (byte*)iv.c_str());
    qDebug() << encryption.DefaultKeyLength();
    qDebug() << encryption.DefaultIVLength();

    CryptoPP::StringSource encryptor(str_in, true,
        new CryptoPP::StreamTransformationFilter(encryption,
            new CryptoPP::Base64Encoder(
                new CryptoPP::StringSink(str_out),
                false // do not append a newline
            )
        )
    );
    return str_out;
}

在这里调用函数

std::string str = "123456789012345";
std::string key = "01234567891234560123456789123456"; // 32 bytes
std::string iv  = "0123456789123456"; // 16 bytes
std::string str_encrypted = c->encrypt(str, key, iv);
std::string str_decrypted = c->decrypt(str_encrypted, key, iv);
std::cout << "str_encrypted: " << str_encrypted << std::endl;
std::cout << "str_decrypted: " << str_decrypted << std::endl;

这段代码产生如下结果

Plain text: "123456789012345"
Encrypted value (base64): 3Qo/6hWctRiID3txA9nC

我在这里用c#写的相同的代码

    private void button1_Click(object sender, EventArgs e)
    {
        string strOutput = Encrypt("123456789012345");
        Debug.WriteLine("Encrypted value is: " + strOutput);
    }
   private string Encrypt(string clearText)
    {
        byte[] clearBytes = Encoding.ASCII.GetBytes(clearText + "");
        using (Aes encryptor =  Aes.Create("AES"))
        {
            encryptor.BlockSize = 128;
            encryptor.KeySize = 128;
            encryptor.Mode = CipherMode.CFB;
            encryptor.Key = Encoding.ASCII.GetBytes("01234567891234560123456789123456");
            encryptor.IV = Encoding.ASCII.GetBytes("0123456789123456");
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(clearBytes, 0, clearBytes.Length);
                    cs.Close();
                }
                byte[] bt = ms.ToArray();
                clearText = Convert.ToBase64String(bt);
            }
        }
        return clearText;
    }

产生如下结果

Encrypted value is: 3YklwM2vG20ZmkOT029jTTL7FlSZHrh0RfvaT1FFa2k=

谁能告诉我我错过了什么?从两种语言中获得相似输出的正确方法是什么?

我的目标是在c++中加密一个值,并在c#中解密。

编辑

我做了一些改变。

将Hello world替换为123456789012345将编码从utf更改为Ascii在c#字符串的末尾增加了一个空字节将模式更改为CFB

我还用新结果编辑了原始结果

不幸的是,这样做之后,两个字符串不匹配。

我已经确保两个输入是相同的

您的c++代码是根据std::string。这很可能包含在ANSI代码页下编码的文本。当你把它传递到CryptoPP::StringSource时,我希望它直接处理该文本的字节,而不将其转换为任何其他编码。

c#正在传递Encoding.Unicode.GetBytes的结果。这意味着加密工作在UTF-16编码数据的字节上。

由于编码不同,字节表示也不同。那么由于字节数不同,加密结果也不同。

你需要让这两段代码在相同的方案下工作。

如果ANSI(或甚至只是ASCII)字符都是你想要处理的(这可能是你的c++代码的情况),那么你可以修改c#代码使用Encoding.Default.GetBytes(或可能Encoding.ASCII.GetBytes)来获得clearText的字节。


编辑

进一步看,你的c++代码使用CryptoPP::CFB_Mode,而你的c#代码使用encryptor.Mode = CipherMode.CBC;。这些模式需要匹配,否则算法将以不同的方式应用。

您可能需要检查其他属性,例如padding,以确保它们在相同的方案下工作。

似乎有两个潜在的问题。下面的代码将产生与CryptoCC库(3Qo/6hWctRiID3txA9nC)相同的输出:

byte[] clearBytes = Encoding.ASCII.GetBytes(clearText);
using (var encryptor = RijndaelManaged.Create())
{
    encryptor.KeySize = 128;
    encryptor.Padding = PaddingMode.Zeros;
    encryptor.Mode = CipherMode.CFB;
    encryptor.Key = Encoding.ASCII.GetBytes("01234567891234560123456789123456");
    encryptor.IV = Encoding.ASCII.GetBytes("0123456789123456");
    using (MemoryStream ms = new MemoryStream())
    {
       using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
        {
            cs.Write(clearBytes, 0, clearBytes.Length);
            cs.Close();
        }
        Array.Copy(ms.ToArray(), clearBytes, clearBytes.Length);
        clearText = Convert.ToBase64String(clearBytes);
     }
 }
 return clearText;

同样,下面的Crypto++实现将提供在示例(3YklwM2vG20ZmkOT029j)中返回的值。net。

std::string encrypt(const std::string& str_in, const std::string& key, const std::string& iv)
{
std::string str_out;
CryptoPP::AES::Encryption e1((byte*)key.c_str(), key.length());
    // use feedback size of 1 byte.
CryptoPP::CFB_Mode_ExternalCipher::Encryption encryption(e1, (byte*)iv.c_str(), 1);

    CryptoPP::StringSource encryptor(str_in, true,
     new CryptoPP::StreamTransformationFilter(encryption,
        new CryptoPP::Base64Encoder(
            new CryptoPP::StringSink(str_out),
            false // do not append a newline
        )
     )
    );
    return str_out;
}

注意事项:

  1. 不需要在字符串后面添加零

  2. Crypto++实现不允许在密码反馈(CFB)模式下填充。.NET实现需要填充;但是,可以手动截断多余的数据(如上面的. net示例所做的那样)。(见http://social.msdn.microsoft.com/Forums/vstudio/en-US/a1be5f49-5f0f-4f5f-b01c-af46fdc71915/des-encryption-cfb-mode)。

  3. 请参阅这篇关于使用AES代替Rijndael作为CSP的含义的文章。以下警告特别适用于CFB模式:

本质上,如果您想使用RijndaelManaged作为AES,您需要确保:

  1. 块大小设置为128位
  2. 你没有使用CFB模式,或者如果你是反馈大小也是128位

在这种情况下,使用CFB模式会引入额外的并发症。注意,这是使用CFB的结果;如果您使用密码块链(CBC)模式,则AesRijndael对于给定的密钥和值(IwffxivpwdSuS9BV0KeyCg==)返回与Crypto++相同的结果。