【问题标题】:.NET triple des to Windows API.NET 三重 des 到 Windows API
【发布时间】:2012-05-02 18:02:28
【问题描述】:

我有一些 C# 加密代码,我必须用 C++ 重写 我在这里看到了几个类似的问题,但不知何故我仍然无法弄清楚。 用相同的密码编码相同的字符串会产生不同的结果。

C#代码

    byte[] TestEncrypt(string data)
    {
        byte[] plainText  = System.Text.Encoding.ASCII.GetBytes(data);
        TripleDES des3 = new     System.Security.Cryptography.TripleDESCryptoServiceProvider();
        des3.Mode = CipherMode.CBC;
        des3.Key = System.Text.Encoding.ASCII.GetBytes("12656b2e4ba2f22e");
        des3.IV = System.Text.Encoding.ASCII.GetBytes("d566gdbc");
        ICryptoTransform transform = des3.CreateEncryptor();
        MemoryStream memStreamEncryptedData = new MemoryStream();
        CryptoStream encStream = new CryptoStream(memStreamEncryptedData,
            transform, CryptoStreamMode.Write);
        encStream.Write(plainText, 0, plainText.Length);
        encStream.FlushFinalBlock();
        encStream.Close();
        byte[] cipherText = memStreamEncryptedData.ToArray();
        return cipherText;
    }

结果 255,142,22,151,93,255,156,10,174,10,250,92,144,0,60,142 已编辑:添加了新的 C++ 版本

    string Test3DES()
    {
        string key = "12656b2e4ba2f22e";
        HCRYPTPROV hCryptProv = NULL;
        HCRYPTHASH hHash = NULL;
        HCRYPTKEY hCryptKey = NULL;
        char pIV[] = "d566gdbc";  //simple test IV for 3DES
        CryptAcquireContext(&hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL,CRYPT_VERIFYCONTEXT);
        PlainTextKeyBlob keyBlob ={0};
        keyBlob.hdr.bType = PLAINTEXTKEYBLOB;
        keyBlob.hdr.bVersion = CUR_BLOB_VERSION;
        keyBlob.hdr.reserved = 0;
        keyBlob.hdr.aiKeyAlg = CALG_3DES_112;
        keyBlob.cbKeySize = key.size();
        memcpy(keyBlob.key, key.c_str(), key.size());
        DWORD dwSizeBlob = sizeof(BLOBHEADER)+sizeof(DWORD)+key.size();
        ret = CryptImportKey( hCryptProv, (const BYTE*)&keyBlob, dwSizeBlob, 0, CRYPT_EXPORTABLE, &hCryptKey );
        DWORD dwMode = CRYPT_MODE_CBC;
        CryptSetKeyParam(hCryptKey, KP_MODE, (BYTE*)&dwMode, 0);
        CryptSetKeyParam(hCryptKey, KP_IV,(const BYTE*) pIV, 0) ; 
        DWORD dwFilled = 0;
        BOOL ret = CryptEncrypt( hCryptKey, NULL, TRUE, 0, (LPBYTE)cipherText.c_str(), &dwFilled, (DWORD)str.size());
        cipherText.resize(dwFilled);
        if( hCryptKey ) CryptDestroyKey( hCryptKey );
        if( hHash ) CryptDestroyHash( hHash );
        if( hCryptProv ) CryptReleaseContext( hCryptProv, 0 );
        return cipherText;
    }

结果 167,177,201,56,123,240,169,174

旧 C++ 版本

C++

  string Test3DES()
    {
        string key = "12656b2e4ba2f22e";
        HCRYPTPROV hCryptProv = NULL;
        HCRYPTHASH hHash = NULL;
        HCRYPTKEY hCryptKey = NULL;
        char pIV[] = "d566gdbc";  //simple test IV for 3DES
        CryptAcquireContext(&hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
        CryptCreateHash( hCryptProv, CALG_MD5, NULL, 0, &hHash );
        CryptHashData( hHash, (LPBYTE)key.c_str(), (DWORD)key.size(), 0 ); 
        DWORD dwMode = CRYPT_MODE_CBC;
        CryptDeriveKey(hCryptProv, CALG_3DES, hHash, 0, &hCryptKey);
        CryptSetKeyParam(hCryptKey, KP_MODE, (BYTE*)&dwMode, 0);
        CryptSetKeyParam(hCryptKey, KP_IV,(const BYTE*) pIV, 0) ; 
        DWORD dwFilled = 0;
        BOOL ret = CryptEncrypt( hCryptKey, NULL, TRUE, 0, (LPBYTE)cipherText.c_str(), &dwFilled, (DWORD)str.size());
        cipherText.resize(dwFilled);
        if( hCryptKey ) CryptDestroyKey( hCryptKey );
        if( hHash ) CryptDestroyHash( hHash );
        if( hCryptProv ) CryptReleaseContext( hCryptProv, 0 );
        return cipherText;
    }

【问题讨论】:

  • 您的问题/问题到底是什么?
  • 您的密钥太短(120 位),最小为 128 位。 MSDN:该算法支持从 128 位到 192 位的密钥长度,以 64 位为增量
  • C# 代码的输出是什么? Base64 或 Hex 对该输出进行编码并将其添加到您的问题中。为了在 C++ 中准确地复制该输出,看看它在 C# 中的样子会有所帮助。

标签: c# c++ winapi encryption


【解决方案1】:

我从您的代码开始设置了一些示例项目。你没有包括所有东西,所以我不得不添加一些东西。当我编译和测试时,我在 C++ 和 C# 中都得到了相同的答案。我怀疑问题可能出在您指定 cipherText 缓冲区的方式上?这是我所有的测试代码,所以你应该很容易设置一些示例项目,看看你是否也得到了相同的结果,那么也许你可以从那里弄清楚:

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace _3dtest
{
    class Program
    {
        static byte[] TestEncrypt(string data)
        {
            byte[] plainText = System.Text.Encoding.ASCII.GetBytes(data);
            TripleDES des3 = new System.Security.Cryptography.TripleDESCryptoServiceProvider();
            des3.Mode = CipherMode.CBC;
            des3.Key = System.Text.Encoding.ASCII.GetBytes("12656b2e4ba2f22e");
            des3.IV = System.Text.Encoding.ASCII.GetBytes("d566gdbc");
            ICryptoTransform transform = des3.CreateEncryptor();
            MemoryStream memStreamEncryptedData = new MemoryStream();
            CryptoStream encStream = new CryptoStream(memStreamEncryptedData,
                transform, CryptoStreamMode.Write);
            encStream.Write(plainText, 0, plainText.Length);
            encStream.FlushFinalBlock();
            encStream.Close();
            byte[] cipherText = memStreamEncryptedData.ToArray();
            return cipherText;
        }

        static void Main(string[] args)
        {
            var info = TestEncrypt("password");
            foreach (byte b in info)
            {
                Console.Write(b.ToString());
                Console.Write(", ");
            }
            Console.WriteLine();
        }
    }
}

C++

#include "stdafx.h"
#include <Windows.h>
#include <WinCrypt.h>
#include <cassert>
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>

using namespace std;

struct PlainTextKeyBlob {
        BLOBHEADER hdr;
        DWORD cbKeySize;
        BYTE key[16];
};

std::wstring LastError(DWORD lasterr)
{
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );
    return (wchar_t*)lpMsgBuf; // Leaking, don't care
}

std::vector<BYTE> Test3DES(const std::string& passwd)
{
        string key = "12656b2e4ba2f22e";
        unsigned char pIV[] = "d566gdbc";  //simple test IV for 3DES
        HCRYPTPROV hCryptProv = NULL;
        HCRYPTHASH hHash = NULL;
        HCRYPTKEY hCryptKey = NULL;
        DWORD ret = CryptAcquireContext(&hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL,CRYPT_VERIFYCONTEXT);
        if( ret == 0 ) std::wcout << LastError(GetLastError()) << std::endl;

        PlainTextKeyBlob keyBlob ={0};
        keyBlob.hdr.bType = PLAINTEXTKEYBLOB;
        keyBlob.hdr.bVersion = CUR_BLOB_VERSION;
        keyBlob.hdr.reserved = 0;
        keyBlob.hdr.aiKeyAlg = CALG_3DES_112;
        keyBlob.cbKeySize =  key.size();
        memcpy(keyBlob.key, key.c_str(), key.size());

        DWORD dwSizeBlob = sizeof(BLOBHEADER)+sizeof(DWORD)+key.size();
        ret = CryptImportKey( hCryptProv, (const BYTE*)&keyBlob, dwSizeBlob, 0, CRYPT_EXPORTABLE, &hCryptKey );
        if( ret == 0 ) std::wcout << LastError(GetLastError()) << std::endl;

        DWORD dwMode = CRYPT_MODE_CBC;
        CryptSetKeyParam(hCryptKey, KP_MODE, (BYTE*)&dwMode, 0);
        CryptSetKeyParam(hCryptKey, KP_IV,(const BYTE*) pIV, 0) ; 

        std::vector< BYTE > buffer( 1024 );
        memcpy( &buffer[0], passwd.c_str(), passwd.size() );
        DWORD dwFilled = passwd.size();
        ret = CryptEncrypt( hCryptKey, NULL, TRUE, 0, (LPBYTE)&buffer[0], &dwFilled, (DWORD)buffer.size());
        if( ret == 0 ) std::wcout << LastError(GetLastError()) << std::endl;
        buffer.resize(dwFilled);
        if( hCryptKey ) CryptDestroyKey( hCryptKey );
        if( hHash ) CryptDestroyHash( hHash );
        if( hCryptProv ) CryptReleaseContext( hCryptProv, 0 );
        return buffer;
}

int _tmain(int argc, _TCHAR* argv[])
{
    auto result = Test3DES("password");
    std::for_each( begin(result), end(result), [](BYTE b) {
        cout << to_string( (_ULonglong)b ) << " ";
    });
    cout << std::endl;

    return 0;
}

【讨论】:

  • 这个样本效果很好。不知道我做错了什么,但现在可以正常工作了
  • 是的,我也看不到问题所在。我不认为问题出在您发布的代码中,或者我只是无法眯着眼睛看到它。
  • @Skrymsli 当我将 IV 设置为 24 字节时,我收到“指定的初始化向量 (IV) 与此算法的块大小不匹配”错误消息。这和 CipherMode 有关系吗?
  • 3DES 的块大小为 64 位。 IV 必须与此匹配,因此 24 字节对 3DES 无效。你在使用不同的算法吗?如果没有看到en.wikipedia.org/wiki/Triple_DES
【解决方案2】:

所以您的密钥 System.Text.Encoding.ASCII.GetBytes("5656b2e4ba2f22e") 是 15 个字节,对于您想要 16 个字节或 24 个字节的三重 DES,这不是有效的密钥长度。我的猜测是,这两种实现正在以不同的方式补偿他们想要更大密钥的事实。

附言

将 ascii 常量字符串的原始字节值用于 Key 和 IV 真的很奇怪。在您的示例中, Key 和 IV 是字符串中的十六进制数字。看起来它们应该是您想要的字节长度的两倍,以便每两个字符代表一个完整的可能字节范围,然后使用十六进制转换器进行转换, How can I convert a hex string to a byte array?, C++ convert string to hexadecimal and vice versa

始终拥有一个十六进制表示的密钥(您只能获得算法的 ASCII 字节)的安全含义是,您正在将可能的密钥空间缩减一个平方根!

【讨论】:

  • 我修复了关键问题。现在是 16 个字节。使用 .NET 和 C++ 方法加密的数据仍然不同
【解决方案3】:

您的 C++ 是使用宽/Unicode 字符串编译的 - 因此您使用与字符串 "5656b2e4ba2f22e" 对应的宽/Unicode 字节作为键(可能是 30 个字节),而在 C# 中,您正在使用将相同的字符串转换为字节ASCII 编码,因此您可以获得相同字符串的 ASCII 字节(15 个字节)。

尝试在 C# 中使用 Unicode 编码,或者更好地将键声明为字节数组而不是字符串。

编辑

错误答案 - string 始终是每个字符 8 位,因此问题不可能是由于编码不同造成的。

【讨论】:

  • C++ 是 UNICODE,但 char 和 string 并没有像我知道的那样受到影响
  • 如果字符串是 Unicode,(LPBYTE)cipherText.c_str()0x35 0x00 0x36 0x00 . . .,而 Encoding.ASCII.GetBytes("5656b2e4ba2f22e");0x35 0x36 . . .
  • 你是对的 - UNICODE 将是 wstring - 确保我检查了 (LPBYTE)cipherText.c_str() 并且它是 ASCII 码的序列。
【解决方案4】:

请记住,DES 密钥是自校验密钥。这意味着每个字节的一位用于检查您的密钥是否正确。也许你有这些错误?您应该能够使用 DES 维基百科页面找到它。

此外,您需要至少两个完整的(64 位)密钥才能使 TDES 工作。

【讨论】:

  • 你的意思是我使用的随机序列可能不是有效的密钥吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-22
  • 2012-03-30
  • 2010-12-24
  • 2016-12-10
  • 1970-01-01
  • 1970-01-01
  • 2013-12-07
相关资源
最近更新 更多