问题
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!.
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;
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;
}
}
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.
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).
Thanks!
Omri
回答1:
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.
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.
回答2:
Just posting the correct C# code for future reference, note the changes setting the IV & CBC mode :
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.CBC;
prov3des.Padding = PaddingMode.PKCS7;
prov3des.IV = new byte[]{0,0,0,0,0,0,0,0}; //8 bytes, zero-ed
using (PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, null)) //No slat needed here
{
prov3des.Key = pdb.CryptDeriveKey("TripleDES", "SHA1", prov3des.KeySize, prov3des.IV);
}
ICryptoTransform cTransform = prov3des.CreateEncryptor();
resultArray = cTransform.TransformFinalBlock(streamToEncrypt, 0, streamToEncrypt.Length);
}
return resultArray;
}
}
Thanks a lot 'pstrjds'!!!! The encryption\decryption results are now identical => the methods are equivalent :-)
Omri
来源:https://stackoverflow.com/questions/6253363/translating-win32-crypto-api-calls-to-c-sharp-with-system-security-cryptography