Подводные камни system.security.cryptography
TRANSCRIPT
Подводные камниSystem.Security.Cryptography;
Владимир КочетковCompilable Applications Analyzers Development / Team Lead
Positive Technologies
Когда и какую криптографию использовать?
3
2 правила самодельной криптографии
4
Никаких самодельных алгоритмов!
Никаких самодельных реализаций!
Задача: сравнить два байтовых массива
5
1. public static bool Equals(byte[] a1, byte[] a2)
2. {if (a1.Length != a2.Length)
3. {
4. return false;
5. }
for (var i=0; i < first.Length; i++)
6. {
7. if (a1[i] != a2[i])
8. {
9. return false;
10. }
11. }
12. return true;
13.}
Очевидный подход
6
1. public static bool Equals(byte[] a1, byte[] a2)
2. {if (a1.Length != a2.Length)
3. {
4. return false;
5. }
for (var i=0; i < first.Length; i++)
6. {
7. if (a1[i] != a2[i])
8. {
9. return false;
10. }
11. }
12. return true;
13.}
Очевидный подход
Если длины массивов не совпадут, метод завершится быстрее
Время выполнения метода линейно возрастает с ростом длины общего префикса у a1 и a2
7
― LAN: разница в 200 наносекунд за 1000 измерений;
― Internet: разница в 30 микросекунд за 1000 измерений;
― side-channel amplification: всегда есть возможность усилить канал.
http://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf
Добавление случайных временных задержек –не работает!
https://events.ccc.de/congress/2012/Fahrplan/attachments/2235_29c3-schinzel.pdf
Timing-атаки
8
1. public static bool Equals(byte[] a1, byte[] a2)
2. {
3. var result = true;
4. for (var i = 0; i < a1.Length; ++i)
5. {
6. if (a1[i] != a2[i])
7. {
8. result = false;
9. }
10. }
11. return result;
12. }
Попытка #2 (.NET BCL-alike)
9
1. public static bool Equals(byte[] a1, byte[] a2)
2. {
3. var result = true;
4. for (var i = 0; i < a1.Length; ++i)
5. {
6. if (a1[i] != a2[i])
7. {
8. result = false;
9. }
10. }
11. return result;
12. }
Попытка #2 (.NET BCL-alike)
Нет гарантии вмешательства в поток выполнения оптимизаторов C# или JIT компиляторов
10
1. public static bool Equals(byte[] a1, byte[] a2)
2. {
3. var diff = (uint)a.Length ^ (uint)b.Length;
4. for (var i = 0; i < a1.Length && i < a2.Length; i++)
5. {
6. diff |= (uint)(a1[i] ^ a2[i]);
7. }
8. return diff == 0;
9. }
Правильный подход
11
Как насчет выполнения ещедесятка других правил?
https://cryptocoding.net/index.php/Coding_rules
12
13
Годный план
Необходимо использовать:
― TLS для шифрования каналов передачи информации;
― существующие высокоуровневые библиотеки для всего остального:
• inferno (http://securitydriven.net/inferno/);
• libsodium-net (https://github.com/adamcaudill/libsodium-net);
• keyczar-dotnet (https://github.com/jbtule/keyczar-dotnet).
И только если это невозможно, следует использовать примитивы System.Security.Cryptography
14
Схема именования классов
― …Managed – полностью управляемая реализация;
― …CryptoServiceProvider – обертка над CryptoAPI;
― …Cng – обертка над CryptoAPI New Generation.
15
Схема именования классов
― …Managed – полностью управляемая реализация;
― …CryptoServiceProvider – обертка над CryptoAPI;
― …Cng – обертка над CryptoAPI New Generation.
== различные реализации одних и тех же проблем16
Нельзя просто так взять и использовать примитивы System.Security.Cryptography
17
Задача: обеспечить целостность передаваемого шифротекста
18
Решение: рассчитывать подпись по формуле signature = HASH(secret + ciphertext) и передавать ее вместе с шифротекстом
19
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}20
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}21
Жестко заданный в коде секрет
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}22
Уязвимость к атакам удлинением сообщения
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}23
Кто сказал, что data будет в ASCII?
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}24
Использование уязвимой функции хэширования
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}25
Несбалансированное сравнение строк
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}26
AES-CBC-PKCS7 → уязвимость к атакам на оракул дополнения
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}27
Жестко заданный в коде ключ
Низкоуровневая криптография1. private static bool IsValidSignature(string data, string signature)
2. {
3. var bytes = Encoding.ASCII.GetBytes("eCTR4rhYQVNwn78j" + data);
4. var hash = MD5.Create().ComputeHash(bytes);
5. return BitConverter.ToString(hash) == signature;
6. }
7. ...
8. if (IsValidSignature(Request["data"], Request["signature"]))
9. {
10. var decryptor = Aes.Create()
11. {
12. BlockSize = 128;
13. Key = Encoding.ASCII.GetBytes("YtGDn6mvAHbp5X7C");
14. IV = Encoding.ASCII.GetBytes("mHMUYSjiVxo4wp9R");
15. }.CreateDecryptor();
16.}28
Жестко заданный в коде вектор инициализации
Высокоуровневая криптография (почувствуйте разницу)
29
Высокоуровневая криптография1. // inferno
2. decryptedData = Decrypt(key, Request["data"], nonce);
30
Высокоуровневая криптография1. // inferno
2. decryptedData = Decrypt(key, Request["data"], nonce);
3. // keyczar-dotnet
4. using(var crypter = new Crypter(keySet))
5. {
6. decryptedData = crypter.Decrypt(Request["data"]);
7. }
31
Высокоуровневая криптография1. // inferno
2. decryptedData = Decrypt(key, Request["data"], nonce);
3. // keyczar-dotnet
4. using(var crypter = new Crypter(keySet))
5. {
6. decryptedData = crypter.Decrypt(Request["data"]);
7. }
8. // libsodium-net
9. decryptedData = SecretBox.Open(Request["data"], nonce, key);
32
Высокоуровневая криптография1. // inferno
2. decryptedData = Decrypt(key, Request["data"], nonce);
3. // keyczar-dotnet
4. using(var crypter = new Crypter(keySet))
5. {
6. decryptedData = crypter.Decrypt(Request["data"]);
7. }
8. // libsodium-net
9. decryptedData = SecretBox.Open(Request["data"], nonce, key);
33
Генерация случайных чисел
34
Типичные сценарии использования PRNG
• Генерация:
—параметров криптографических функций (векторов инициализации, оказий, значений соли, больших простых чисел и т.п.);
—идентификаторов (фрагментов файловых путей, URL);
—примитивов аутентификации (сессионных маркеров, временных паролей).
• Выбор ветви в рабочем потоке приложения.
• Специфика логики предметной области (моделирование, численный анализ).
35
А нужен ли вообще криптографический PRNG?
36
Что, если атакующий воспроизведет предыдущие илипоследующие значения, случайной последовательности?
37
System.Guid не предназначен для генерации случайных чисел
38
System.Guid
• «Черный ящик» для генерации уникальных идентификаторов → не гарантирует случайность генерируемых GUID;
• обертка над CoCreateGuid (ole32.dll) → вызов UuidCreate (rpcrt4.dll), основанный на RC4 и уязвимый к атакам на угадывание очередного значения (https://rsdn.ru/article/Crypto/UuidCrypto.xml) в ряде сценариев.
39
Константное маскирование бит снижает энтропию
Использование System.Randomможет обернуться серьезной уязвимостью
40
System.Random
• Значение seed по умолчанию Environment.TickCount → гонки за seed;
• cубтрактивный LFG(Xn = (Xn-55 - Xn-24) mod (231 - 1), n ≥ 0) → для синхронизации с генератором достаточно получить последовательность из 55 случайных чисел;
• ошибка в реализации во всех версиях .NET вплоть до текущей → неполный цикл случайной последовательности.
41
G(x) = x55 + x21 + 1 вместо x55 + x24 + 1
Seed-гонка
- состояние приложения, при котором PRNG в различных потоках получают одно и то же значение seed.
Эксперимент Neohapsis (http://labs.neohapsis.com/tag/seed-racing-attack-vector):
• 67 тысяч запросов на восстановление пароля;
• временный пароль генерировался с помощью System.Random;
• получено 208 ответов с уникальными паролями;
• 322 ответа содержали одинаковые пароли.42
Уникальные Одинаковые
Для генерации случайных чисел следует использовать наследниковRandomNumberGenerator
43
RandomNumberGenerator
• Абстрактный класс, в .NET BCL всего один наследник;
• RNGCryptoServiceProvider – обертка над CryptGetRandom (advapi32.dll) → FIPS-186-2 G=SHA1 (до Windows Vista SP1) и AES-CTR (все последующие версии);
• Не требуется задавать seed для генератора Crypto API;
• Опасный метод GetNonZeroBytes снижает энтропию последовательности (после 14 нулевых бит подряд всегда будет идти единичный бит) и не должен использоваться без необходимости;
44
Хеширование
45
HashAlgorithm
― KeyedHashAlgorithm:
• наследники HMAC?: OK (с учетом безопасности базовых хэш-функций);
• MACTripleDES: только для обратной совместимости.
― наследники MD5: не должны использоваться!
― наследники RIPEMD160: только для обратной совместимости
― наследники SHA-1: не должны использоваться!
― наследники SHA256/384/512: OK (пока)
46
Задача: обеспечить целостность передаваемых данных
47
Решение: рассчитывать подпись по формуле signature = HASH(secret + data) и передавать ее вместе с данными
48
Проблема: при таком решении атакующий сможет подделывать подпись data || attacker-data для большинства хэш-функций из .NET BCL
49
Атака удлинением сообщения
― Подвержен ряд хэш-функций, основанных на структуре Меркла-Дамгарда:
• MD5;
• RIPEMD-160;
• SHA-1
• SHA-256;
• SHA-512.
― Не подверждены:
• SHA-384;
• HMAC-?;
• MACT-ripleDES.
50
Атака удлинением сообщения
51
Изначальное хэшируемое сообщение:
m' = m || padding
Атакующему достаточно рассчитать хэш для:
m'' = m || padding || new-data || new-padding
Используй HMAC, Люк!
52
Атака удлинением сообщения
Хранение паролей
53
Хранение паролей1. public static string GetPasswordHash(string password)
2. {
3. Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, 32);
4. rfc2898DeriveBytes.IterationCount = 16384;
5. byte[] hash = rfc2898DeriveBytes.GetBytes(32);
6. byte[] salt = rfc2898DeriveBytes.Salt;
7. return Convert.ToBase64String(salt) + "|" + Convert.ToBase64String(hash);
8. }
Криптографические функции хэширования не подходят для хэшированияпаролей. Для этого следует использовать PBKDF2 (класс Rfc2898DeriveBytes).
Длина соли должна быть достаточной для обеспечения энтропии E=log2(nm) ≥
128 бит для любого пароля, допускаемого приложением.
54
Блочные шифры
55
SymmetricAlgorithm
― Aes: (блок 128 бит, ключ 128/192/256 бит)
― DES: не должен использоваться!
― RC2: только для обратной совместимости
― Rijndael: (блок 128/192/256 бит, ключ 128/192/256 бит)
― TripleDES: только для обратной совместимости
Чтобы использовать Rijndael как Aes, необходимо установить размер блока 128 бит и не использовать режим CFB (либо использовать feedback == 128 бит).
56
Критичные параметры блочных шифров
― IV – вектор инициализации:
• должен генерироваться случайным образом:
—SymmetricAlgorithm.GenerateKey();
• не должен использоваться повторно;
• может не являться секретом.
― Key – ключ шифрования:
• должен генерироваться случайным образом:
—SymmetricAlgorithm.GenerateKey();
—Rfc2898DeriveBytes (password, salt).CryptDeriveKey(…).
57
Критичные параметры блочных шифров
― CipherMode - режим шифрования
• CipherMode.ECB:только в качестве базы для реализации неподдерживаемых режимов!
• CipherMode.CBC:
OK, при условии ручной реализации EtA.
• CipherMode.CFB:
• CipherMode.OFB:
• CipherMode.CTS:
обеспечивают конфиденциальность шифротекста, основаны на IV, имитируют потоковое шифрование, допускают (хотя и не требуют) дополнение.
58
не поддерживаются ни одним блочным шифром
CipherMode.ECB
― CipherMode - режим шифрования
• CipherMode.ECB:только в качестве базы для реализации неподдерживаемых режимов!
• CipherMode.CBC:
OK, при условии ручной реализации EtA.
• CipherMode.CFB:
• CipherMode.OFB:
• CipherMode.CTS:
обеспечивают конфиденциальность шифротекста, основаны на IV, имитируют потоковое шифрование, допускают (хотя и не требуют) дополнение.
59
не поддерживаются ни одним блочным шифром
Критичные параметры блочных шифров
― CipherMode - режим шифрования
• CipherMode.ECB:только в качестве базы для реализации неподдерживаемых режимов!
• CipherMode.CBC:
OK, при условии ручной реализации EtA.
• CipherMode.CFB:
• CipherMode.OFB:
• CipherMode.CTS:
обеспечивают конфиденциальность шифротекста, основаны на IV, имитируют потоковое шифрование, допускают (хотя и не требуют) дополнение.
60
не поддерживаются ни одним блочным шифром
Критичные параметры блочных шрифтов
― PaddingMode - режим дополнения блоков:
Для данных [FF FF FF FF FF FF FF FF FF]:
• PaddingMode.ANSIX923: [FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07]
• PaddingMode.ISO10126: [FF FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07]
• PaddingMode.None: [FF FF FF FF FF FF FF FF FF]
• PaddingMode.PKCS7: [FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07]
• PaddingMode.Zeros: [FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00]61
По умолчанию все блочные шифры используют CBC-PKCS7
62
Проблема: если атакующий CBC-PKCS7 сможет манипулировать шифротекстом, отслеживая ошибки дополнения…
63
…то он сможет расшифроватьсообщение или зашифроватьпроизвольный текст, не владея ключом*
64
*за исключением первого блока
65
{DEMO}
Уязвим не только AES-CBC-PKCS7!!!
66
Атаки на оракул дополнения
― Уязвим любой блочный шифр.
― Уязвимы все доступные режимы дополнения, кроме PaddingMode.ISO10126 и PaddingMode.None.
― {Частично} уязвимы все доступные режимы конфиденциального шифрования, допускающие дополнение и устанавливающие обратную связь по шифротекстуили ключевому потоку между всеми блоками (CBC, OFB, CFB, CTR, …).
― Перехват исключений и добавление временных задержек не устраняют уязвимость.
67
Подход EtA (encrypt-then-authenticate)
1. Сообщение шифруется обычным образом (например, AES-CBC).
2. IV добавляется к шифротексту.
3. Для полученного на шаге 2 считается MAC с помощью одного из наследников HMAC (например, HMACSHA512) и также добавляется к нему.
4. …
5. Только PROFIT!!! и никаких оракулов.
68
EtA всегда необходимо реализовывать для любого блочного шифра System.Security.Cryptography
69
Вопросы?
Владимир Кочетков
@kochetkov_v
Compilable Applications Analyzers Development / Team LeadPositive Technologies