Перевод вызовов Win32 Crypto API в C # с помощью System.Security.Cryptography - PullRequest
7 голосов
/ 06 июня 2011

Мне дали задание удалить один из dll нашего продукта и заменить его на чистый C #. Старая DLL - это .NET 2.0 Управляемый C ++ (C ++ \ CLI), который оборачивает вызовы в Win32 собственный Crypto API. Новая DLL должна предоставлять новый объект с тем же именем и методами, но должна быть написана на C # (.NET 4.0). Конечно, новая DLL должна шифроваться таким же образом (и расшифровываться), как и старая, в противном случае все сохраненные зашифрованные пароли в постоянном хранилище, например в БД или в файле, не будут разрешены!.

Это (псевдо) код собственных вызовов API (Win32) (обратите внимание, что вход всегда кодируется в Юникоде):

//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) с новыми объектами Crypto, предоставляемыми .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 символов) шифруются одинаково с использованием обоих методов, но более длинные строки приводят к различным зашифрованным данным.

Как я могу заставить эти два метода быть эквивалентными? То есть - шифровать и дешифровать таким же образом, чтобы выходные данные были одинаковыми? Что мне здесь не хватает? Есть ли изменение значений \ поведения по умолчанию между API .NET и Native (Win32)? (Я думаю, что режим шифрования 3DES по умолчанию в Win32 Crypto API - EBC, в то время как C # по умолчанию - CBC - исправьте меня, если я ошибаюсь).

Спасибо!

Омри

Ответы [ 2 ]

6 голосов
/ 06 июня 2011

В соответствии со страницей MSDN для CryptDeriveKey представляется, что режим шифрования по умолчанию для 3DES - это не EBC, а CBC - «Когда ключи генерируются для симметричных блочных шифров, ключ по умолчанию устанавливается врежим цепочки блоков шифрования (CBC) с вектором инициализации, равным нулю. Этот режим шифрования обеспечивает хороший метод по умолчанию для массового шифрования данных. Чтобы изменить эти параметры, используйте функцию CryptSetKeyParam. "Режимом по умолчанию для провайдера .Net TripleDES также является CBC.Попробуйте удалить строку, в которой вы устанавливаете его как EBC, и посмотрите, поможет ли это.

Важное замечание: для успешной расшифровки вам потребуется знать вектор инициализации.Функция CryptDeriveKey будет использовать нулевой IV по умолчанию, что означает, что для совпадения в вашем чистом коде C # вам необходимо убедиться, что вы также используете нулевой IV.

0 голосов
/ 07 июня 2011

Просто отправив правильный код C # для дальнейшего использования, обратите внимание на изменения, устанавливающие режим IV и CBC:

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

}

Большое спасибо 'pstrjds' !!!!Результаты шифрования \ дешифрования теперь идентичны => методы эквивалентны: -)

Omri

...