Подводные камни System.Security.Cryptography

Preview:

Citation preview

Подводные камни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

Вопросы?

Владимир Кочетков

vkochetkov@ptsecurity.com

@kochetkov_v

Compilable Applications Analyzers Development / Team LeadPositive Technologies

Recommended