Это краткое введение в криптографию под .NET для чайников, как и следует из заголовка. Здесь будут простые вещи и никаких углубленных знаний.
Немного про хэши
Хэширование — это процесс преобразования некоторых входных данных любой длины в массив байтов фиксированной длины.
Хэш представляет собой одностороннюю функцию преобразования, результат работы которой нельзя реверсировать для получения исходных входных данных. Очень часто используется для хранения паролей. Даже если злоумышленник получит хэш, то получить сам пароль не получится. Длина хэша определяется алгоритмом хэширования.
В .NET можно найти следующие алгоритмы хэширования (наследующие абстрактный класс HashAlgorithm):
- MD5 — длина 128 битов
- SHA (Secure Hash Algorithm) — такого класса нет, но есть SHA1 (160 битов), SHA256, SHA384, SHA512
- KeydHashAlgorithm (известные как Message Authentication Code). Представителями является класс алгоритмов HMAC и MACTripleDES
В .NET, если не нужен хэш, основанный на ключе, то, при прочих равных условиях на сегодняшний день, лучше всего использовать алгоритм SHA512. MD5 несколько устарел и может быть скомпрометирован. SHA512 работает очень быстро (уступает только SHA1). Вообще-то всё семейство SHA работает быстро. Самый медленный MACTripleDES. Семейство HMAC работает примерно в два-четыре раза медленнее, чем SHA.
Хэши на основе ключа могут использоваться для защиты данных от модификаций. Запросы посылаемые на сервер с клиента могут проверяться на предмет модификаций. Если данные, передаваемые на сервер были модифицированы, то хэши не совпадут. При защите строковых запросов нельзя использовать простой хэш, без ключа. Потому что, тогда злоумышленник просто переберёт все возможные алгоритмы хэширования, найдёт нужный, модифицирует аргументы в строке запроса и подставит нужный хэш и тогда, серверу ничего не останется, как отвечать на корректно сформированный запрос. Для такой защиты вполне может подойти HMACSHA1 в качестве алгоритма хэширования, основанного на ключе. Разумеется, в полный рост встаёт и проблема хранения секретного ключа.
public static byte[] ComputeHmacsha1(byte[] data, byte[] key)
{
using (var hmac = new HMACSHA1(key))
{
return hmac.ComputeHash(data);
}
}
Чтобы у злоумышленника оставалось меньше возможностей на сбор входных данных и соответствующих им хэшей для последующего перебора с целью поиска ключа, можно добавить соль.
Соль — это некий рандомизированный дополнительный «ключ», который добавляет энтропии к шифрованию. Соль можно передавать в открытом виде. Соль можно сгенерировать и ассоциировать с текущей сессией пользователя. Таким образом, соль будет меняться каждую сессию и результаты хэширования для одних и тех же запросов будут разные. Соль также добавляют при хэшировании паролей, чтобы усложнить перебор по словарю, особенно если, например, у злоумышленника есть доступ к значениям хэшей. Для генерации соли лучше использовать криптоустойчивый генератор типа RNGCryptoServiceProvider.
Соль можно сгенерировать следующим образом:
string salt = GenSalt(32);
string GenSalt(int length)
{
RNGCryptoServiceProvider p = new RNGCryptoServiceProvider();
var salt = new byte[length];
p.GetBytes(salt);
return Convert.ToBase64String(salt);
}
Немного про симметричные алгоритмы шифрования
В .NET представлено несколько алгоритмов, наследующих базовый класс SymmetricAlgorithm:
- Rijndael
- DES
- TripleDES
- RC2
В настоящее время рекомендуемым симметричным алгоритмом в .NET является Rijndael. Также хороши Mars, RC6, Serpent, TwoFish, но в .NET они не реализованы. Если только в сторонних библиотеках. Rijndael представляет собой блочный алгоритм шифрования.
Блочный значит, что входные данные будут нарезаны на блоки и алгоритм шифрования будет применён последовательно к каждому из этих блоков.
Паддинг (Padding) означает процесс «добивания» результатов блочного шифрования для того, чтобы в результате получить требуемый размер.
Rijndael поддерживает несколько режимов паддинга — добивания нулями, добивание случайными значениям и ещё пару режимов. Пожалуй, наиболее надёжный способ, добавляющий энтропии это добивание случайными значениями — это режим ISO10126.
Rijndael также поддерживает несколько способов работы над блоками. По умолчанию, лучше выбрать режим CBC, который означает, что на вход шифрованию следующего блока будут подаваться также результаты шифрования предыдущего блока, что опять же увеличивает энтропию.
IV — initialization vector (вектор инициализации). Нужен для подачи на вход шифрования первого блока. При использовании Rijndael генерируется автоматически. Для дешифрования вектор инициализации знать необходимо и посылать его можно в открытом виде.
Маленький пример:
string Encrypt()
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.KeySize = 256;
cipher.BlockSize = 256; // используйте 128 для совместимости с AES
cipher.Padding = PaddingMode.ISO10126;
cipher.Mode = CipherMode.CBC;
cipher.Key = "some_super_secret_key";
ICryptoTransform t = cipher.CreateEncryptor();
string text = "some_text_to_encrypt";
byte[] textInBytes = Encoding.UTF8.GetBytes(text);
byte[] result = t.TransformFinalBlock(textInBytes, 0, textInBytes.Length);
return Convert.ToBase64String(result);
}
В .NET также есть интересный класс CryptoStream. Он предназначен для интеграции шифрования с потоками типа FileStream и так далее. Например, в один CryptoStream, который производит шифрование, можно вложить другой CryptoStream, который содержит FileStream. Затем в первый CryptoStream сделать Write, передав текст, и в результате получить шифрованный текст, записанный в некий файл.
Немного про асимметричные алгоритмы шифрования
В случае с асимметричными алгоритмами используется публичный ключ для шифрования и приватный для расшифровки. Если шифрованные сообщения расшифровываются только одним из участников, то закрытый ключ можно хранить только в одном месте. В случае с симметричным шифрованием закрытый ключ приходится делать известным всем участникам обмена сообщениями.
Асимметричные алгоритмы шифрования приблизительно в 100-1000 раз медленнее симметричных. Поэтому большие объёмы данных ими шифровать может оказаться накладно. Асимметричное шифрование может быть использовано вместе с симметричным. С помощью асимметричного шифрования генерируется сессионный ключ, который используется для шифрования данных.
В .NET реализованы следующие основные алгоритмы:
- RSA
- DSA (только для цифровых подписей)
- ECDiffieHellman
Обычно для RSA используют ключи длиной 1024, 2048, 4096 битов. Доказано, что RSA на ключе длиной 1024 — ненадёжен, так что следует использовать более длинные ключи.
Пример использования RSA:
public class RsaWithCspKey
{
const string ContainerName = "MyContainer";
public void AssignNewKey()
{
CspParameters cspParams = new CspParameters(1);
cspParams.KeyContainerName = ContainerName;
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
var rsa = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = true };
}
public void DeleteKeyInCsp()
{
var cspParams = new CspParameters { KeyContainerName = ContainerName };
var rsa = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = false };
rsa.Clear();
}
public byte[] EncryptData(byte[] dataToEncrypt)
{
byte[] cipherbytes;
var cspParams = new CspParameters { KeyContainerName = ContainerName };
using (var rsa = new RSACryptoServiceProvider(2048, cspParams))
{
cipherbytes = rsa.Encrypt(dataToEncrypt, false);
}
return cipherbytes;
}
public byte[] DecryptData(byte[] dataToDecrypt)
{
byte[] plain;
var cspParams = new CspParameters { KeyContainerName = ContainerName };
using (var rsa = new RSACryptoServiceProvider(2048, cspParams))
{
plain = rsa.Decrypt(dataToDecrypt, false);
}
return plain;
}
}
Цифровые подписи основаны на асимметричном шифровании. Цифровая подпись обеспечивает приватность сообщения и доказывает принадлежность сообщения тому или иному автору. Обычно это работает следующим образом:
1. Алиса шифрует своё сообщение.
2. Алиса берёт хэш своего сообщения.
3. Алиса подписывает сообщение своим приватным ключом для подписывания.
4. Алиса посылает зашифрованные данные, хэш и подпись Бобу.
5. Боб вычисляет хэш зашифрованных данных.
6. Боб проверяет подпись, используя публичный ключ.
Пример использования подписи:
public class DigitalSignature
{
private RSAParameters _publicKey;
private RSAParameters _privateKey;
public void AssignNewKey()
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
rsa.PersistKeyInCsp = false;
_publicKey = rsa.ExportParameters(false);
_privateKey = rsa.ExportParameters(true);
}
}
public byte[] SignData(byte[] hashOfDataToSign)
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
rsa.PersistKeyInCsp = false;
rsa.ImportParameters(_privateKey);
var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa);
rsaFormatter.SetHashAlgorithm("SHA256");
return rsaFormatter.CreateSignature(hashOfDataToSign);
}
}
public bool VerifySignature(byte[] hashOfDataToSign, byte[] signature)
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
rsa.ImportParameters(_publicKey);
var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
rsaDeformatter.SetHashAlgorithm("SHA256");
return rsaDeformatter.VerifySignature(hashOfDataToSign, signature);
}
}
}
Статья представляет собой очень краткий конспект двух курсов с Pluralsight: «Introduction to Cryptography in .NET» и «Practical Cryptography in .NET».
Перевод здесь.