Как добиться совместимости между C# и шифрованием SQL2k8 AES?

У меня есть шифрование AES для двух столбцов: один из этих столбцов хранится в базе данных SQL Server 2000; другой хранится в базе данных SQL Server 2008.

Поскольку база данных первого столбца (2000 г.) не имеет собственных функций для шифрования/дешифрования, мы решили выполнить логику шифрования на уровне приложения с классами .NET для обоих.

Но поскольку база данных второго столбца (2008 г.) допускает такую ​​функциональность, мы хотели бы сделать миграцию данных с использованием функций базы данных более быстрой, поскольку миграция данных в SQL 2k намного меньше, чем в этой секунде, и она будет длиться дольше. чем 50 часов из-за того, что они сделаны на уровне приложений.

Моя проблема началась в этот момент: используя тот же ключ, я не получил ни того же результата при шифровании значения, ни того же размера результата.

Ниже у нас есть полная логика в обе стороны. Ключ я, конечно, не показываю, но в остальном все то же самое:

private byte[] RijndaelEncrypt(byte[] clearData, byte[] Key)
{
    var memoryStream = new MemoryStream();

    Rijndael algorithm = Rijndael.Create();

    algorithm.Key = Key;
    algorithm.IV = InitializationVector;

    var criptoStream = new CryptoStream(memoryStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write);
    criptoStream.Write(clearData, 0, clearData.Length);
    criptoStream.Close();

    byte[] encryptedData = memoryStream.ToArray();
    return encryptedData;
}

private byte[] RijndaelDecrypt(byte[] cipherData, byte[] Key)
{
    var memoryStream = new MemoryStream();

    Rijndael algorithm = Rijndael.Create();

    algorithm.Key = Key;
    algorithm.IV = InitializationVector;

    var criptoStream = new CryptoStream(memoryStream, algorithm.CreateDecryptor(), CryptoStreamMode.Write);

    criptoStream.Write(cipherData, 0, cipherData.Length);

    criptoStream.Close();

    byte[] decryptedData = memoryStream.ToArray();

    return decryptedData;
}

Это пример кода SQL:

open symmetric key columnKey decryption by password = N'{pwd!!i_ll_not_show_it_here}'

declare @enc varchar(max)

set @enc = dbo.VarBinarytoBase64(EncryptByKey(Key_GUID('columnKey'), 'blablabla'))

select LEN(@enc), @enc

Этот varbinaryToBase64 — это проверенная функция sql, которую мы используем для преобразования varbinary в тот же формат, который мы используем для хранения строк в приложении .net.

Результат в C#: eg0wgTeR3noWYgvdmpzTKijkdtTsdvnvKzh+uhyN3Lo=

Тот же результат в SQL2k8: AI0zI7D77EmqgTQrdgMBHAEAAACyACXb+P3HvctA0yBduAuwPS4Ah3AB4Dbdj2KBGC1Dk4b8GEbtXs5fINzvusp8FRBknF15Br2xI1CqP0Qb/M4w

Я просто еще не понял, что я делаю неправильно.

У тебя есть идеи?

EDIT: Один момент, который я считаю решающим: у меня есть один вектор инициализации в моем коде C #, 16 байтов. Этот IV не установлен в симметричном ключе SQL, могу ли я это сделать?

Но даже не заполняя IV на C#, я получаю совсем другие результаты, как по содержанию, так и по длине.


person Victor Rodrigues    schedule 12.05.2010    source источник
comment
Есть ли в документации что-нибудь, утверждающее, что два алгоритма шифрования на самом деле одинаковы?   -  person Joe    schedule 13.05.2010
comment
они говорят, что они оба AES, верно? Я думал, что этого будет достаточно, чтобы обеспечить некоторую совместимость.   -  person Victor Rodrigues    schedule 13.05.2010


Ответы (2)


Есть несколько вещей, на которые я бы посмотрел:

  1. Убедитесь, что открытый текст идентичен по содержанию и кодировке. IIRC, потоки по умолчанию используют UTF-8, тогда как если ваша функция VarBinaryToBase64 принимает параметр nvarchar, это будет Unicode.

  2. Убедитесь, что оба алгоритма шифрования используют одинаковый размер блока. В SQL вы определяете алгоритм при вызове CREATE SYMMETRIC KEY. Если вы не укажете алгоритм, он использует AES256. Я считаю, что в .NET с использованием RijndaelManaged размер блока по умолчанию равен 128, но вы можете установить его на 256 (вы не можете, если используете класс Aes).

  3. Последнее, что я бы искал, это то, как SQL Server работает с векторами инициализации, как вы упомянули в своем исправленном сообщении. Я хочу сказать, что он использует для этого параметр authenticator, но это дикая догадка.

ИЗМЕНИТЬ

Я был далеко. Учитывая то, что я обнаружил, вы не можете использовать класс .NET для расшифровки текста, зашифрованного встроенным шифрованием SQL Server, потому что SQL Server добавляет кучу липкости к тому, что зашифровывается, включая случайный вектор инициализации. Из книги Майкла Коула "Руководство программиста Pro T-SQL 2005" (хотя 2008 год делает то же самое):

Когда SQL Server шифрует с помощью симметричного ключа, он добавляет метаданные к зашифрованному результату, а также заполнение, делая зашифрованный результат больше (иногда значительно больше), чем незашифрованный обычный текст. Формат зашифрованного результата с метаданными соответствует следующему формату:

  • Первые 16 байт зашифрованного результата представляют собой GUID симметричного ключа, используемого для шифрования данных.
  • Следующие 4 байта представляют собой номер версии, в настоящее время жестко запрограммированный как «01000000».
  • Следующие 8 байтов для шифрования DES (16 байтов для шифрования AES) представляют случайно сгенерированный вектор инициализации.
  • Следующие 8 байтов — это информация заголовка, представляющая параметры, используемые для шифрования данных. Если используется опция аутентификатора, эта информация заголовка включает 20-байтовый хэш SHA1 аутентификатора, что делает информацию заголовка длиной 28 байтов.
  • Последняя часть зашифрованных данных — это сами данные и заполнение. Для алгоритмов DES длина этих зашифрованных данных будет кратна 8 байтам. Для алгоритмов AES длина будет кратна 16 байтам.
person Thomas    schedule 12.05.2010
comment
Судя по длине вывода (84 байта до кодировки base64), я не думаю, что SQL Server использует AES256. Хотя это тоже не кратно 16. Довольно странно. - person Thorarin; 13.05.2010
comment
@Thorarin - Поверьте, Коулз просто говорит, что данные + заполнение будут кратны 16, а не весь шифр. Таким образом, 84 меньше 36-байтового заголовка равно 48 байтам, что кратно 16. - person Thomas; 13.05.2010
comment
Ах, ваше редактирование многое проясняет, по крайней мере, оно объясняет, почему первые 18 символов base64 были одинаковыми, а остальные продолжали меняться. Однако теперь, когда вы знаете макет заголовка, расшифровка с помощью .NET должна быть возможной. Шифрование, в котором я не уверен, зависит от хэша SHA1, я думаю. - person Thorarin; 13.05.2010
comment
Спасибо, Томас, ты очень помог. Я написал сборку, выполняющую шифрование C#, которое используется SQL Server вместо родного AES;) - person Victor Rodrigues; 19.05.2010
comment
@Victor Rodrigues - Точно. Рад, что вы нашли решение. - person Thomas; 20.05.2010
comment
Официальная информация о формате находится по адресу blogs.msdn.com/b/sqlsecurity/archive/2009/03/29/ - person zzzeek; 26.09.2013

Не совсем ответ, но слишком большой для комментария. Возможно, это поможет кому-то еще разобраться :)
Похоже, что SQL Server делает много дополнений. Когда я попробовал ваш пример, я продолжал получать разные результаты, хотя первые 18 символов каждый раз кажутся одинаковыми. Однако для меня это пока не имеет смысла...

Вы не показываете, как вызывается RijndaelEncrypt, но с разной длиной данных я думал о различиях кодовых страниц Unicode и ANSI. Однако вы, похоже, не используете unicode в SQL Server, поэтому разница в длине будет наоборот, если что.

person Thorarin    schedule 12.05.2010