翻译Win32加密API调用到C#与System.Security.CryptographyAPI、System、Cryptography、Security

2023-09-04 09:12:15 作者:心照不宣

我一直在考虑转让砸我们的产品的DLL之一,它与一个纯C#更换。旧的DLL是一个.NET 2.0托管C ++(C ++ \ CLI)它包装调用Win32本地加密API。新的DLL应公开使用相同的名称和放一个新的对象;的方法,但应写入的C#(.NET 4.0)。当然,新的DLL应加密同样的方式(和解密)为旧的 - 否则,在持久性存储中的所有加密保存的密码就像在一个数据库或文件 - !就不会被解决。

I have been given an assignment to drop one of our product's dll and replace it with a pure C# one. The old DLL is a .NET 2.0 Managed C++ (C++\CLI) which wraps calls to the Win32 native Crypto API. The new DLL should expose a new object with the same name & methods, but should be written with C# (.NET 4.0). Of course, the new DLL should encrypt that same way (and decrypt) as the old one - otherwise, all saved encrypted passwords in a persistent storage like in a DB or in a File - will not be resolved!.

这是(伪)$ C $原生的C(Win32的)API调用(注意输入常是统一code连接codeD):

This is the (pseudo) code of the native (Win32) API calls (note the the input is alway Unicode encoded):

//buffer_to_encrypt - Is the input to the following procedure and is the buffer
// to be encrypted using 3DES and the below password to generate a valid 3DES key
// The buffer is Unicode encoded!!!


HCRYPTPROV m_provider = NULL;
HCRYPTHASH m_hash = NULL;
HCRYPTKEY m_key = NULL;

static const unsigned char password[] = { 
                                                0xF1, 0x49, 0x4C, 0xD0, 0xC1, 
                                                0xE2, 0x1A, 0xEA, 0xFB, 0x34, 
                                                0x25, 0x5A, 0x63, 0xA5, 0x29, 
                                                0x09, 0x8E, 0xB6, 0x7B, 0x75 
                                            }; //20 BYTES password


CryptAcquireContextW( &m_provider, NULL, NULL, PROV_DH_SCHANNEL, CRYPT_MACHINE_KEYSET | CRYPT_VERIFYCONTEXT);

CryptCreateHash( m_provider, CALG_SHA1, NULL, 0, &m_hash );

CryptHashData( m_hash, password, (DWORD)20, 0 ); //password is a 20Bytes buffer 

CryptDeriveKey(m_provider, CALG_3DES, m_hash, CRYPT_EXPORTABLE, &m_key);

CryptEncrypt( m_key.handle(), NULL, TRUE, 0, buffer_to_encrypt, &dwFilled, (DWORD)total );

return buffer_to_encrypt;

现在,我想用C#(System.Security.Cryptography命名空间),与.NET API提供的新的加密对象写相同的过程:

Now, I'm trying to write the same procedure using C# (System.Security.Cryptography namespace) with the new Crypto objects exposed by the .NET API:

class Encryptor
{

         private static byte[] password = { 
                                            0xF1, 0x49, 0x4C, 0xD0, 0xC1, 
                                            0xE2, 0x1A, 0xEA, 0xFB, 0x34, 
                                            0x25, 0x5A, 0x63, 0xA5, 0x29, 
                                            0x09, 0x8E, 0xB6, 0x7B, 0x75 
                                        }; //20 BYTES password, same as the above native code




        private static byte[] EncryptInternal(string source)
        {
            byte[] resultArray = null;
            byte[] streamToEncrypt = Encoding.Unicode.GetBytes(source);

            using (TripleDESCryptoServiceProvider prov3des = new TripleDESCryptoServiceProvider())
            {

                prov3des.Mode = CipherMode.ECB;
                prov3des.Padding = PaddingMode.PKCS7;

                using (PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, null)) //No slat needed here
                {
                    prov3des.Key = pdb.CryptDeriveKey("TripleDES", "SHA1", prov3des.KeySize, ZeroIV);
                }

                ICryptoTransform cTransform = prov3des.CreateEncryptor();

                resultArray = cTransform.TransformFinalBlock(streamToEncrypt, 0, streamToEncrypt.Length);

            }

            return resultArray;
        }
}

在这里,我面临着一个恼人的问题 - 的加密阵列(导致加密缓冲区)是不一样的使用这两种方法!每个阵列的前8个字节(64位)是相同的,但下一个字节不是。这会导致短字符串(最大3个字符)相同使用这两种方法来进行加密,但更长字符串导致不同加密数据。

Here I'm facing an annoying problem - The encrypted array (result encrypted buffer) is not the same using both methods! The first 8 bytes (64 bits) of each array are identical, but the next bytes are not. This causes short strings (max 3 characters) to be encrypted identically using both methods, but longer strings are resulted in different encrypted data.

我如何可以强制这两种方法是等价的?那就是 - 要加密和放大器;同样的方式进行解密,以便输出会是一样的吗? 我在想什么吗?是否有默认值的变化\的.NET与放大器之间的行为;原生的(Win32)的API? (我想在Win32加密API默认的3DES暗号方式是EBC在使用C#默认为CBC - 请纠正我,如果我错了)

How can I force the two methods to be equivalent? That is - To encrypt & decrypt that same way so that the output would be the same? What am I missing here? Is there a change of default values\behavior between the .NET & Native (Win32) APIs? (I think the default 3DES cypher mode in Win32 Crypto API is EBC while the default using C# is CBC - please correct me if I'm wrong).

谢谢!

欧米 -

推荐答案

根据MSDN的页面的 CryptDeriveKey 似乎默认的暗号模式3DES不是EBC而是CBC - 当对称分组密码生成密钥,默认情况下,关键是建立在密码块链接( CBC)模式的零初始化向量。这种加密模式提供了批量加密数据的好默认方法。要改变这些参数,使用CryptSetKeyParam功能。默认方式为净TripleDes的供应商也CBC。尝试删除,你将它设置为EBC行了,看看有没有什么帮助了。

According to the MSDN page for CryptDeriveKey it appears that the default cypher mode for 3DES is not EBC but rather CBC - "When keys are generated for symmetric block ciphers, the key by default is set up in cipher block chaining (CBC) mode with an initialization vector of zero. This cipher mode provides a good default method for bulk-encrypting data. To change these parameters, use the CryptSetKeyParam function." The default mode for the .Net TripleDES provider is also CBC. Try removing the line where you are setting it to EBC and see if that helps out.

在这里重要注意事项,你需要知道初始化向量才能够成功解密。该CryptDeriveKey函数将使用零IV在默认情况下,这意味着为了有一个匹配的纯C#code,你需要确保你使用的是零IV为好。

Important note here, you will need to know the initialization vector to be able to decrypt successfully. The CryptDeriveKey function will use a zero IV by default, which means in order to have a match in your pure C# code you will need to ensure you are using a zero IV as well.