2011-06-06 14 views
7

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

+0

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

+0

¿Ha comprobado si la contraseña (bytes derivados de la contraseña) que recibe es la misma para ambas implementaciones? – pstrjds

Respuesta

6

De acuerdo con la página de MSDN para CryptDeriveKey parece que el modo de cifrado por defecto para 3DES no es EBC sino CBC - "Cuando las claves se generan para cifrado por bloques simétricos, la clave por defecto está configurado en modo de encadenamiento de bloques de cifrado (CBC) con un vector de inicialización de cero. Este modo de cifrado proporciona un buen método predeterminado para el cifrado masivo de datos. Para cambiar estos parámetros, utilice la función CryptSetKeyParam ". El modo predeterminado para el proveedor .Net TripleDES también es CBC. Intente eliminar la línea donde la está configurando para EBC y vea si eso ayuda.

Nota importante aquí, necesitará saber el vector de inicialización para poder descifrar correctamente. La función CryptDeriveKey usará un IV cero por defecto, lo que significa que para tener una coincidencia en su código C# puro, necesitará asegurarse de que también está utilizando un IV cero.

+0

¡GUAU! No puedo creer que me haya perdido esa (muy) importante oración en la documentación ... Supongamos que mi ego era lo suficientemente grande como para admitir que lo he leído todo ;-) – OmriSela

0

Sólo publicar el código C# correcta para referencia futura, tenga en cuenta los cambios en la configuración del modo CBC IV &:

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; 
    } 

}

Muchas gracias '' pstrjds !!!! Los resultados de cifrado \ descifrado son ahora idénticos => los métodos son :-) equivalente

Omri

Cuestiones relacionadas