Me han asignado la tarea de colocar una de las dll de nuestro producto y reemplazarla por una C# pura. La DLL anterior es una .NET 2.0 Managed C++ (C++ \ CLI) que envuelve las llamadas a la API nativa de Win32 Crypto. La nueva DLL debe exponer un nuevo objeto con el mismo nombre & métodos, pero debe escribirse con C# (.NET 4.0). Por supuesto, la nueva DLL debe encriptar de la misma manera (y descifrar) que la anterior, de lo contrario, todas las contraseñas guardadas en un almacenamiento persistente como en un DB o en un archivo no se resolverán.Traducción de las llamadas a Win32 Crypto API a C# con System.Security.Cryptography
Esta es la (pseudo) código de la nativa (Win32) llamadas a la API (nótese el que la entrada está todos los días codificados con Unicode):
//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;
Ahora, estoy tratando de escribir el mismo procedimiento utilizando C# (System.Security.Cryptography espacio de nombres) con los nuevos objetos Crypto expuestos por la API .NET:
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;
}
}
Aquí estoy frente a un problema molesto - la matriz cifrada (encriptada resultar búfer) no es lo mismo usando ambos métodos! Los primeros 8 bytes (64 bits) de cada matriz son idénticos, pero los siguientes bytes no lo son. Esto hace que las cadenas cortas (máximo 3 caracteres) se cifren de manera idéntica con ambos métodos, pero las cadenas más largas tienen como resultado diferentes datos cifrados.
¿Cómo puedo forzar que los dos métodos sean equivalentes? Es decir - ¿Encriptar & descifrar de la misma manera para que la salida sea la misma? ¿Qué me estoy perdiendo aquí? ¿Hay algún cambio en el comportamiento de los valores predeterminados entre las API .NET & Native (Win32)? (Creo que el modo de cifrado 3DES predeterminado en Win32 Crypto API es EBC, mientras que el valor predeterminado con C# es CBC; corrígeme si me equivoco).
Gracias!
Omri
olvidó añadir - El API Crypto (Win32 C \ C++.) Procedimiento no referencia y sal o IV (Vector de Inicialización), así que asumí que son ignorados cuando escribo el procedimiento .NET. * La variable 'ZeroIV' en el método C# es una matriz con valor cero de 8 bytes. – OmriSela
¿Ha comprobado si la contraseña (bytes derivados de la contraseña) que recibe es la misma para ambas implementaciones? – pstrjds