Reputation: 3638
I'm taking a look at some Apple Keychain backup stuff, and I've reached a point where I need to decrypt some data with AES-GCM
.
I have the decryption key, GCM auth tag, and ciphertext.
I do not have an IV
.
Looking at some code written in 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)
...
It appears that the call to gcm.Open
is passing in null
for the IV
, which I didn't think was a valid scenario for AES-GCM
Putting the data into CyberChef (https://gchq.gibhub.io) works, if I provide the key, ciphertext, and auth tag. However, I have to leave the IV field empty, and as far as I can tell, it's just using the bytes of an empty string.
Essentially I need to make this work in C#
, but no matter what I try, I end up with MAC check exceptions.
I've tried providing an empty byte[12] for the IV, this doesn't work.
My current approach is using org.bouncycastle
, with the following code:
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);
As far as I can tell, there isn't any way to create a AES-GCM
decryptor without an IV
.
Perhaps I'm not understanding AES-GCM
well enough, is the IV
embedded in the ciphertext, or can you decrypt with a null IV
I hope that makes sense, and any input or advice would be greatly appreciated.
===Additional information===
It appears that the GCM decryption here needs to be a custom implementation, as Apple use a null IV for keychain information.
This appears to relate to the issue, so it would appear I'll have to implement something similar in C#
The following can be decrypted by Cyberchef:
Key: DD24A01473D859BA6E27640C982BE5CA9CA41F0928CEBA4BA404DE4FAD5F7FD3
Auth tag: 89EA2B7232F5B053BB8EEB32534C52DC
Cipher text: 4A13C29326CF3CC34F9218BF1DAFE602AED65D2F81386769EE87086DAAF10884A18D8618DE185599DC23355E02DC55F635F3AB5CC14066FF67438628B2AE589C5BFB0946F51866CCDA7AA81FC2860CBAB84A8F9B057CAA77D0BC82896171527E6DDC22D16B72A7F904DE13D3C8452A6B75893D069D5D2561C4D5B604AA927EDC0A22198338E86263698FA42BB7D5C777718D9C66F8148C444F0DDA08590BA629D1CE34CFA7ECBAE8C592A3026084F7AFBF331EAFA411EC138BAF06B19E6962A531EFCD983059567DB683B8CBE5121B2ECB86165DB5D3C143AFAF6A3AB2E317B424C670481C625C8199421E143C70E195A56B8A30DB46FB1463DE5319409C2B4C0C9C9516F3394FE58DDBE7370DAE7EE21A9172AC9C7A48C8F27C42DBC9BED0CD092A9BA7E4C8B695A295C6DD03E30A69B152F5F43E06C2989F09CC98FA6F767EE93D66F63606416A940A4C5988B8688EEFFF331CD36E7AA7811B9B778B37C72A3EEC58A639F9F605B31226E08947
Upvotes: 2
Views: 214
Reputation: 49251
tl;dr - The test data can be decrypted using the CTR mode. This can be done on .NET Framework 4.8 with Portable.BouncyCastle v1.9.0 as follows:
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
The decrypted data is ASN.1/DER encoded and can be analyzed in an ASN.1/DER parser, e.g. here.
Please note: In contrast to GCM, the data is not authenticated (which is also why the authentication tag is not used). For authentication, the complex GCM logic would have to be recreated, which would be much more complex (at least I don't know of a simple way).
More in-depth: The GCM mode uses an internal counter that is incremented with each block. The Go Open()
implementation applies the value 0x00000000000000000000000000000000
for a nil
nonce as Counter 0.
Such a value for Counter 0 cannot be generated in a GCM-compatible implementation (s. also here):
0x000000000000000000000000
is used as the nonce, the GCM algorithm expands this internally by appending 0x00000001
to a value for Counter 0 of 0x00000000000000000000000000000001
.0x00000000000000000000000000000000
is used directly as nonce, this is not taken by the GCM algorithm as Counter 0, but the actual value for Counter 0 is derived from it using the GHASH algorithm (as is the case for all nonce sizes not equal to 12 bytes).Please note that Counter 0 is reserved for the tag, while Counter 1 and following are used for the encryption.
Apart from authentication, the GCM mode is based on the CTR mode, where Counter 1 is used for encryption. For this reason, the ciphertext can be decrypted in CTR mode with 0x00000000000000000000000000000001
as Counter 1, like in the code snippet above.
Oddly enough, decryption with CyberChef is possible in GCM mode if an empty nonce is used. However, GCM prohibits an empty nonce (see NIST SP 800-38d, section 5.2.1.1), so that CyberChef is not GCM-compliant at this point.
Obviously, CyberChef converted the empty nonce internally (for whatever reason) into the value 0x00000000000000000000000000000000
for Counter 0.
Edit - Another approach: Customization of BouncyCastle's GCM implementation with reflection:
An alternative that enables decryption with Bouncycastle's GCM implementation and thus also the authentication of the data is the reinitialization of the value for Counter 0 (and dependent values) via reflection. BouncyCastle's GCM implementation can then be used as usual apart from the reinitialization (note, however, that the changed logic is generally no longer compliant with the actual GCM specification).
Counter 0 is determined in Init()
, the value is saved in J0
. This value must be reinitialized to the value 0x00000000000000000000000000000000
after the Init()
call with reflection. The values dependent on J0
are initialized in Init()
as well, counter
and counter32
. These must also be consistently reinitialized after the Init()
call. This can be done with Reset()
.
A possible implementation is:
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
This code enables authenticated decryption of the test data.
However, a downside of this solution is that the use of reflection (and the access of private members) breaks the encapsulation principle. It is quite possible that the private part of a class can change, especially across versions, which can lead to a future failure of this solution and the need for adaptation.
Upvotes: 3