Первый
Не делай этого! Написание собственной криптосистемы может легко привести к ошибкам. Лучше всего использовать существующую систему или, если нет, попросить кого-то, кто знает криптографию, сделать это. Если вам нужно сделать это самостоятельно, прочитайте Практическая криптография .
И, пожалуйста, помните: «У нас уже достаточно быстрых, небезопасных систем». (Брюс Шнайер) - исправьте ситуацию и позаботьтесь о производительности позже.
Тем не менее, если вы застряли на использовании AES, чтобы бросить свой собственный, вот несколько указателей.
Вектор инициализации
AES - блочный шифр. Получив ключ и блок открытого текста, он преобразует его в определенный зашифрованный текст. Проблема в том, что одни и те же блоки данных будут каждый раз генерировать один и тот же шифротекст с одним и тем же ключом. Предположим, вы отправляете данные следующим образом:
пользователь = Шифрование (Имя пользователя) и роли = Шифрование (UserRoles)
Это два отдельных блока, и шифрование UserRoles будет каждый раз иметь один и тот же зашифрованный текст независимо от имени. Все, что мне нужно, это зашифрованный текст для администратора, и я могу добавить его прямо со своим именем пользователя. К сожалению.
Итак, существует режим работы шифра . Основная идея заключается в том, что вы возьмете зашифрованный текст одного блока и вставите его в зашифрованный текст следующего блока. Таким образом, мы сделаем Encrypt (UserRoles, Username), и шифр username будет зависеть от UserRoles.
Проблема в том, что первый блок все еще уязвим - просто увидев чей-то зашифрованный текст, я мог бы узнать их роли. Введите вектор инициализации . IV «запускает» шифр и обеспечивает случайные данные для шифрования остальной части потока. Так что теперь зашифрованный текст UserRoles имеет зашифрованный текст случайного IV XOR в. Проблема решена.
Итак, убедитесь, что вы генерируете случайный IV для каждого сообщения. IV не является чувствительным и может быть отправлено в виде открытого текста с зашифрованным текстом. Используйте достаточно большой IV - размер блока должен быть хорошим во многих случаях.
Целостность
AES не обеспечивает функции целостности. Любой может изменить ваш зашифрованный текст, и расшифровка все равно будет работать. Вряд ли это будут действительные данные в целом, но может быть трудно понять, что такое действительные данные. Например, если вы передаете GUID в зашифрованном виде, было бы легко изменить некоторые биты и сгенерировать совершенно другой. Это может привести к ошибкам приложения и т. Д.
Исправление - запуск алгоритма хеширования (используйте SHA256 или SHA512) для открытого текста и включение его в передаваемые вами данные. Поэтому, если мое сообщение (имя пользователя, роли), вы отправите (имя пользователя, роли, хэш (имя пользователя, роли)). Теперь, если кто-то подделает зашифрованный текст, перевернув немного, хеш больше не будет вычисляться, и вы можете отклонить сообщение.
Вывод ключа
Если вам нужно сгенерировать ключ из пароля, используйте встроенный класс: System.Security.Cryptography.PasswordDeriveBytes . Это обеспечивает посолку и итерации, которые могут повысить прочность полученных ключей и уменьшить вероятность обнаружения пароля в случае взлома ключа.
Сроки / воспроизведения
Редактировать: Извините, что не упомянул об этом раньше: P. Вы также должны убедиться, что у вас есть система предотвращения повторного воспроизведения. Если вы просто зашифруете сообщение и передадите его, любой, кто получит сообщение, может просто повторно отправить его. Чтобы избежать этого, вы должны добавить временную метку к сообщению. Если временная метка отличается от определенного порога, отклоните сообщение. Вы также можете включить в него одноразовый идентификатор (это может быть IV) и отклонить сообщения, действительные по времени, которые приходят с других IP-адресов с использованием того же идентификатора.
Важно убедиться, что вы выполняете проверку хэша, когда включаете информацию о времени. В противном случае кто-то может подделать часть зашифрованного текста и, возможно, сгенерировать действительную временную метку, если вы не обнаружите такие попытки перебора.
Пример кода
Поскольку очевидно, что правильное использование IV является спорным для некоторых людей, вот код, который сгенерирует случайные IV и добавит их в ваш вывод для вас. Он также выполнит этап аутентификации, убедившись, что зашифрованные данные не были изменены.
using System;
using System.Security.Cryptography;
using System.Text;
class AesDemo {
const int HASH_SIZE = 32; //SHA256
/// <summary>Performs encryption with random IV (prepended to output), and includes hash of plaintext for verification.</summary>
public static byte[] Encrypt(string password, byte[] passwordSalt, byte[] plainText) {
// Construct message with hash
var msg = new byte[HASH_SIZE + plainText.Length];
var hash = computeHash(plainText, 0, plainText.Length);
Buffer.BlockCopy(hash, 0, msg, 0, HASH_SIZE);
Buffer.BlockCopy(plainText, 0, msg, HASH_SIZE, plainText.Length);
// Encrypt
using (var aes = createAes(password, passwordSalt)) {
aes.GenerateIV();
using (var enc = aes.CreateEncryptor()) {
var encBytes = enc.TransformFinalBlock(msg, 0, msg.Length);
// Prepend IV to result
var res = new byte[aes.IV.Length + encBytes.Length];
Buffer.BlockCopy(aes.IV, 0, res, 0, aes.IV.Length);
Buffer.BlockCopy(encBytes, 0, res, aes.IV.Length, encBytes.Length);
return res;
}
}
}
public static byte[] Decrypt(string password, byte[] passwordSalt, byte[] cipherText) {
using (var aes = createAes(password, passwordSalt)) {
var iv = new byte[aes.IV.Length];
Buffer.BlockCopy(cipherText, 0, iv, 0, iv.Length);
aes.IV = iv; // Probably could copy right to the byte array, but that's not guaranteed
using (var dec = aes.CreateDecryptor()) {
var decBytes = dec.TransformFinalBlock(cipherText, iv.Length, cipherText.Length - iv.Length);
// Verify hash
var hash = computeHash(decBytes, HASH_SIZE, decBytes.Length - HASH_SIZE);
var existingHash = new byte[HASH_SIZE];
Buffer.BlockCopy(decBytes, 0, existingHash, 0, HASH_SIZE);
if (!compareBytes(existingHash, hash)){
throw new CryptographicException("Message hash incorrect.");
}
// Hash is valid, we're done
var res = new byte[decBytes.Length - HASH_SIZE];
Buffer.BlockCopy(decBytes, HASH_SIZE, res, 0, res.Length);
return res;
}
}
}
static bool compareBytes(byte[] a1, byte[] a2) {
if (a1.Length != a2.Length) return false;
for (int i = 0; i < a1.Length; i++) {
if (a1[i] != a2[i]) return false;
}
return true;
}
static Aes createAes(string password, byte[] salt) {
// Salt may not be needed if password is safe
if (password.Length < 8) throw new ArgumentException("Password must be at least 8 characters.", "password");
if (salt.Length < 8) throw new ArgumentException("Salt must be at least 8 bytes.", "salt");
var pdb = new PasswordDeriveBytes(password, salt, "SHA512", 129);
var key = pdb.GetBytes(16);
var aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Key = pdb.GetBytes(aes.KeySize / 8);
return aes;
}
static byte[] computeHash(byte[] data, int offset, int count) {
using (var sha = SHA256.Create()) {
return sha.ComputeHash(data, offset, count);
}
}
public static void Main() {
var password = "1234567890!";
var salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var ct1 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct1"));
Console.WriteLine(Convert.ToBase64String(ct1));
var ct2 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct2"));
Console.WriteLine(Convert.ToBase64String(ct2));
var pt1 = Decrypt(password, salt, ct1);
Console.WriteLine(Encoding.UTF8.GetString(pt1));
var pt2 = Decrypt(password, salt, ct2);
Console.WriteLine(Encoding.UTF8.GetString(pt2));
// Now check tampering
try {
ct1[30]++;
Decrypt(password, salt, ct1);
Console.WriteLine("Error: tamper detection failed.");
} catch (Exception ex) {
Console.WriteLine("Success: tampering detected.");
Console.WriteLine(ex.ToString());
}
}
}
Выход:
* +1058 * JZVaD327sDmCmdzY0PsysnRgHbbC3eHb7YXALb0qxFVlr7Lkj8WaOZWc1ayWCvfhTUz / y0QMz + uv0PwmuG8VBVEQThaNTD02JlhIs1DjJtg =
QQvDujNJ31qTu / foDFUiVMeWTU0jKL / UJJfFAvmFtz361o3KSUlk / ZH + 4701mlFEU4Ce6VuAAuaiP1EENBJ74Wc8mE / QTofkUMHoa65 / 5e4 =
Элис; Боб; Ева ;: PerformAct1 Алиса;
Боб; Ева ;: PerformAct2 Успех:
вскрытие обнаружено.
System.Security.Cryptography.CryptographicException:
Неверный хэш сообщения. в
AesDemo.Decrypt (Строковый пароль,
Byte [] passwordSalt, Byte []
cipherText) в
C: \ Program.cs: линия
46 в AesDemo.Main () в
C: \ Program.cs: линия
100
После удаления случайного IV и хеша, вот тип вывода:
tZfHJSFTXYX8V38AqEfYVXU5Dl / meUVAond70yIKGHY =
tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM =
Обратите внимание, как первый блок, соответствующий "Алиса; Боб; Ева;" та же. «Угловой корпус» действительно.
Пример без хеширования
Вот простой пример передачи 64-битного целого числа. Просто зашифруйте, и вы готовы к атаке. На самом деле, атака легко выполняется, даже с дополнением CBC.
public static void Main() {
var buff = new byte[8];
new Random().NextBytes(buff);
var v = BitConverter.ToUInt64(buff, 0);
Console.WriteLine("Value: " + v.ToString());
Console.WriteLine("Value (bytes): " + BitConverter.ToString(BitConverter.GetBytes(v)));
var aes = Aes.Create();
aes.GenerateIV();
aes.GenerateKey();
var encBytes = aes.CreateEncryptor().TransformFinalBlock(BitConverter.GetBytes(v), 0, 8);
Console.WriteLine("Encrypted: " + BitConverter.ToString(encBytes));
var dec = aes.CreateDecryptor();
Console.WriteLine("Decrypted: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
for (int i = 0; i < 8; i++) {
for (int x = 0; x < 250; x++) {
encBytes[i]++;
try {
Console.WriteLine("Attacked: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
return;
} catch { }
}
}
}
Выход:
Значение: 6598637501946607785 Значение
(байты): A9-38-19-D1-D8-11-93-5B
Encrypted:
31-59-B0-25-FD-C5-13-D7-81-D8-F5-8A-33-2A-57-DD
Расшифровано: 6598637501946607785
Атака: 14174658352338201502
Так что, если вы отправляете такой идентификатор, его можно легко изменить на другое значение. Вам необходимо пройти аутентификацию за пределами вашего сообщения. Иногда, структура сообщения вряд ли встанет на свои места и может выступить в качестве гарантии, но зачем полагаться на то, что может измениться? Вы должны быть в состоянии полагаться на то, что ваш криптограф работает правильно независимо от приложения.