Я просматриваю некоторые резервные копии Apple Keychain и дошел до момента, когда мне нужно расшифровать некоторые данные с помощью AES-GCM
.
У меня есть ключ дешифрования, тег аутентификации GCM и зашифрованный текст.
У меня нет IV
.
Глядя на код, написанный на Go (https://github.com/dunhamsteve/ios/blob/master/cmd/irestore/irestore.go):
...
// Create a gcm cipher
c, err := aes.NewCipher(key)
if err != nil {
log.Panic(err)
}
gcm, err := gcm.NewGCM(c)
if err != nil {
log.Panic(err)
}
plain, err := gcm.Open(nil, nil, edata, nil)
...
Похоже, что вызов gcm.Open
передается null
для IV
, что, по моему мнению, недопустимо для AES-GCM
.
Помещение данных в CyberChef (https://gchq.gibhub.io) работает, если я предоставляю ключ, зашифрованный текст и тег аутентификации. Однако мне пришлось оставить поле IV пустым, и, насколько я могу судить, оно просто использует байты пустой строки.
По сути, мне нужно, чтобы это работало в C#
, но что бы я ни пытался, в конечном итоге я получаю исключения при проверке MAC.
Я пробовал предоставить пустой байт[12] для IV, это не сработало.
Мой текущий подход использует org.bouncycastle
со следующим кодом:
var keyParam = ParameterUtilities.CreateKeyParameter("AES", unwrappedKey);
var parameters = new ParametersWithIV(keyParam, new byte[12]);
var cipher = CipherUtilities.GetCipher("AES/GCM/NoPadding");
cipher.Init(false, parameters);
var decrypted = cipher.DoFinal(encrypted);
Насколько я могу судить, невозможно создать дешифратор AES-GCM
без IV
.
Возможно, я недостаточно хорошо понимаю AES-GCM
, встроен ли IV
в зашифрованный текст, или вы можете расшифровать его с помощью нуля IV
Я надеюсь, что это имеет смысл, и любой вклад или совет будут очень признательны.
===Дополнительная информация===
Похоже, что расшифровка GCM здесь должна быть специальной реализацией, поскольку Apple использует нулевой IV для информации о цепочке ключей.
Похоже, это относится к проблеме, поэтому мне придется реализовать что-то подобное на С#.
С помощью Cyberchef можно расшифровать следующее:
Ключ: DD24A01473D859BA6E27640C982BE5CA9CA41F0928CEBA4BA404DE4FAD5F7FD3
Тег аутентификации: 89EA2B7232F5B053BB8EEB32534C52DC
Шифрованный текст: 4A13C29326CF3CC34F9218BF1DAFE602AED65D2F81386769EE87086DAAF10884A18D8618DE185599DC23355E02DC55F635F3AB5CC14066FF67438628B2AE589C5BFB0946F51866CCDA7AA81FC2860CBAB84A8F9B057CAA77D0BC82896171527E6DDC22D16B72A7F904DE13D3C8452A6B75893D069D5D2561C4D5B604AA927EDC0A22198338E86263698FA42BB7D5C777718D9C66F8148C444F0DDA08590BA629D1CE34CFA7ECBAE8C592A3026084F7AFBF331EAFA411EC138BAF06B19E6962A531EFCD983059567DB683B8CBE5121B2ECB86165DB5D3C143AFAF6A3AB2E317B424C670481C625C8199421E143C70E195A56B8A30DB46FB1463DE5319409C2B4C0C9C9516F3394FE58DDBE7370DAE7EE21A9172AC9C7A48C8F27C42DBC9BED0CD092A9BA7E4C8B695A295C6DD03E30A69B152F5F43E06C2989F09CC98FA6F767EE93D66F63606416A940A4C5988B8688EEFFF331CD36E7AA7811B9B778B37C72A3EEC58A639F9F605B31226E08947
🤔 А знаете ли вы, что...
C# имеет обширную библиотеку классов .NET, что упрощает разработку приложений.
tl;dr — Тестовые данные можно расшифровать в режиме CTR. Это можно сделать в .NET Framework 4.8 с помощью Portable.BouncyCastle v1.9.0 следующим образом:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using System;
...
byte[] key = Hex.Decode("DD24A01473D859BA6E27640C982BE5CA9CA41F0928CEBA4BA404DE4FAD5F7FD3");
byte[] iv = Hex.Decode("00000000000000000000000000000001");
byte[] ct = Hex.Decode("4A13C29326CF3CC34F9218BF1DAFE602AED65D2F81386769EE87086DAAF10884A18D8618DE185599DC23355E02DC55F635F3AB5CC14066FF67438628B2AE589C5BFB0946F51866CCDA7AA81FC2860CBAB84A8F9B057CAA77D0BC82896171527E6DDC22D16B72A7F904DE13D3C8452A6B75893D069D5D2561C4D5B604AA927EDC0A22198338E86263698FA42BB7D5C777718D9C66F8148C444F0DDA08590BA629D1CE34CFA7ECBAE8C592A3026084F7AFBF331EAFA411EC138BAF06B19E6962A531EFCD983059567DB683B8CBE5121B2ECB86165DB5D3C143AFAF6A3AB2E317B424C670481C625C8199421E143C70E195A56B8A30DB46FB1463DE5319409C2B4C0C9C9516F3394FE58DDBE7370DAE7EE21A9172AC9C7A48C8F27C42DBC9BED0CD092A9BA7E4C8B695A295C6DD03E30A69B152F5F43E06C2989F09CC98FA6F767EE93D66F63606416A940A4C5988B8688EEFFF331CD36E7AA7811B9B778B37C72A3EEC58A639F9F605B31226E08947");
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");
cipher.Init(false, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", key), iv));
byte[] decryptedBytes = cipher.DoFinal(ct);
Console.WriteLine(Hex.ToHexString(decryptedBytes)); // 3182016a30080c046d757372040030090c0473796e6302010030090c04746f6d62020100300a0c0470646d6e0c02646b30150c04737663650c0d57694669416e616c797469637330160c04616772700c0e77696669616e616c797469637364301c0c0473686131041471e6960f30245dff29bc9083c0dae34f755f09aa301e0c0463646174181632303233303431383036333631352e3837383332335a301e0c046d646174181632303233303431383036333631352e3837383332335a301e0c0a706572736973747265660410321cc548fc8f467b9d7e8bdcb94a9ae4302c0c04555549440c2431333042463330352d343634312d343542372d414432322d304536393731303337343541302e0c06765f44617461042442373333364442432d304343462d343631432d383345392d43423031313646334433383630310c04616363740c29636f6d2e6170706c652e776966692e616e616c79746963732e746f6b656e53746f72652e7769666964
Расшифрованные данные закодированы ASN.1/DER и могут быть проанализированы в анализаторе ASN.1/DER, например. вот .
Обратите внимание: в отличие от GCM данные не аутентифицируются (именно поэтому не используется тег аутентификации). Для аутентификации пришлось бы воссоздать сложную логику GCM, что было бы намного сложнее (по крайней мере, я не знаю простого способа).
Более подробно: режим GCM использует внутренний счетчик, который увеличивается с каждым блоком. Реализация Go Open()
применяет значение 0x00000000000000000000000000000000
для nil
nonce в качестве счетчика 0.
Такое значение для счетчика 0 невозможно сгенерировать в реализации, совместимой с GCM (см. также здесь):
0x000000000000000000000000
используется в качестве nonce, алгоритм GCM расширяет его внутренне, добавляя 0x00000001
к значению для счетчика 0 из 0x00000000000000000000000000000001
.0x00000000000000000000000000000000
используется непосредственно как одноразовый номер, алгоритм GCM не воспринимает его как счетчик 0, но фактическое значение счетчика 0 получается из него с помощью алгоритма GHASH (как и в случае со всеми размерами одноразового номера, не равными 12 байтам). .Обратите внимание, что счетчик 0 зарезервирован для тега, а счетчик 1 и последующие используются для шифрования.
Помимо аутентификации, режим GCM основан на режиме CTR, где счетчик 1 используется для шифрования. По этой причине зашифрованный текст можно расшифровать в режиме CTR с помощью 0x00000000000000000000000000000001
в качестве счетчика 1, как в приведенном выше фрагменте кода.
Как ни странно, расшифровка с помощью CyberChef возможна в режиме GCM, если используется пустой nonce. Однако GCM запрещает пустой номер nonce (см. NIST SP 800-38d, раздел 5.2.1.1), так что CyberChef на данном этапе не соответствует требованиям GCM.
Очевидно, CyberChef внутренне преобразовал пустой nonce (по какой-то причине) в значение 0x00000000000000000000000000000000
для счетчика 0.
Изменить. Другой подход: настройка реализации GCM BouncyCastle с отражением:
Альтернативой, которая обеспечивает расшифровку с помощью реализации GCM Bouncycastle и, следовательно, аутентификацию данных, является повторная инициализация значения счетчика 0 (и зависимых значений) посредством отражения. Реализация GCM BouncyCastle может затем использоваться как обычно, за исключением повторной инициализации (однако обратите внимание, что измененная логика, как правило, больше не соответствует фактической спецификации GCM).
Счетчик 0 определяется в Init() , значение сохраняется в J0
. Это значение должно быть повторно инициализировано значением 0x00000000000000000000000000000000
после вызова Init()
с отражением. Значения, зависящие от J0
, также инициализируются в Init() , counter
и counter32
. Их также необходимо последовательно повторно инициализировать после вызова Init()
. Это можно сделать с помощью Reset().
Возможная реализация:
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.Reflection;
...
byte[] key = Hex.Decode("DD24A01473D859BA6E27640C982BE5CA9CA41F0928CEBA4BA404DE4FAD5F7FD3");
byte[] ct = Hex.Decode("4A13C29326CF3CC34F9218BF1DAFE602AED65D2F81386769EE87086DAAF10884A18D8618DE185599DC23355E02DC55F635F3AB5CC14066FF67438628B2AE589C5BFB0946F51866CCDA7AA81FC2860CBAB84A8F9B057CAA77D0BC82896171527E6DDC22D16B72A7F904DE13D3C8452A6B75893D069D5D2561C4D5B604AA927EDC0A22198338E86263698FA42BB7D5C777718D9C66F8148C444F0DDA08590BA629D1CE34CFA7ECBAE8C592A3026084F7AFBF331EAFA411EC138BAF06B19E6962A531EFCD983059567DB683B8CBE5121B2ECB86165DB5D3C143AFAF6A3AB2E317B424C670481C625C8199421E143C70E195A56B8A30DB46FB1463DE5319409C2B4C0C9C9516F3394FE58DDBE7370DAE7EE21A9172AC9C7A48C8F27C42DBC9BED0CD092A9BA7E4C8B695A295C6DD03E30A69B152F5F43E06C2989F09CC98FA6F767EE93D66F63606416A940A4C5988B8688EEFFF331CD36E7AA7811B9B778B37C72A3EEC58A639F9F605B31226E08947");
byte[] tag = Hex.Decode("89EA2B7232F5B053BB8EEB32534C52DC");
byte[] ctTag = Arrays.Concatenate(ct, tag);
AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, new byte[12], null);
GcmBlockCipher cipher = new GcmBlockCipher(new AesEngine());
cipher.Init(false, parameters);
// Change J0 via reflection
cipher.GetType().GetField("J0", System.Reflection.BindingFlags.NonPublic | BindingFlags.Instance).SetValue(cipher, new byte[16]);
// Reinitialize values depending on J0 (counter, counter32)
cipher.Reset();
byte[] plaintext = new byte[cipher.GetOutputSize(ctTag.Length)];
int len = cipher.ProcessBytes(ctTag, 0, ctTag.Length, plaintext, 0);
cipher.DoFinal(plaintext, len);
Console.WriteLine(Hex.ToHexString(plaintext)); // 3182016a30080c046d757372040030090c0473796e6302010030090c04746f6d62020100300a0c0470646d6e0c02646b30150c04737663650c0d57694669416e616c797469637330160c04616772700c0e77696669616e616c797469637364301c0c0473686131041471e6960f30245dff29bc9083c0dae34f755f09aa301e0c0463646174181632303233303431383036333631352e3837383332335a301e0c046d646174181632303233303431383036333631352e3837383332335a301e0c0a706572736973747265660410321cc548fc8f467b9d7e8bdcb94a9ae4302c0c04555549440c2431333042463330352d343634312d343542372d414432322d304536393731303337343541302e0c06765f44617461042442373333364442432d304343462d343631432d383345392d43423031313646334433383630310c04616363740c29636f6d2e6170706c652e776966692e616e616c79746963732e746f6b656e53746f72652e7769666964
Этот код обеспечивает аутентифицированную расшифровку тестовых данных.
Однако недостатком этого решения является то, что использование отражения (и доступа частных членов) нарушает принцип инкапсуляции. Вполне возможно, что приватная часть класса может измениться, особенно в разных версиях, что может привести к сбою этого решения в будущем и необходимости адаптации.