RFC 3394 and Key Wrapping in C#

--

The protection of encryption keys is important, and where they often have to be protected. This is especially important for a symmetric key or for a private key of a public key pair. For this, we can use key wrapping and make sure the key cannot be used, unless we have a secret master key. One standard for this is [RFC 3394]. With RFC 3394, the length of the key to be wrapped needs to be a multiple of 64 bits, but with RFC 5649 we overcome this weakness.

With AES-KW, we use an AES key-encryption key (KEK) with a length of 128, 192, or 256 bits, and where we will get 64-bit blocks as an output. If we can either generate this randomly or use a key derivation function (such as HKDF) to generate a key of a given size.

The protection of the keys by the KEK means that the wrapped keys could then be stored within a Cloud-based system (the red key in Figure 1), but where the KEK will then be protected from access. When the symmetric keys are required to be unwrapped, the KEK can be revealed within a trusted environment and then produce the actual encryption key. Thus the actual encryption keys are never stored anywhere in the core form.

Figure 1: Key wrapping and unwrapping with KEK (key-encryption key)

Key wrapping and the HSM

Within the Cloud, AWS CloudHSM (hardware security module) supports AES key wrapping with the default initialization vector — 0xA6A6A6A6A6A6A6A6- or a user-defined value. This provides a FIPS 140–2 Level 3 environment and where the keys are handled within a trusted cloud instance. The wrapped keys can then exist outside this but only converted into their actual form within the CloudHSM. A key generated within the CloudHSM can then be wrapped for export from the environment, or imported from an external wrapped key. The AWS CLI is on the form which defines a key handle (with -k) and the wrapping key handle (with -w):

> wrapKey -k 7 -w 14 -out mykey.key -m 5Key Wrapped.Wrapped Key written to file "mykey.key: length 612Cfm2WrapKey returned: 0x00 : HSM Return: SUCCESS

The modes for the -m option are AES_KEY_WRAP_PAD_PKCS5 (4) NIST_AES_WRAP_NO_PAD (5) NIST_AES_WRAP_PAD ( 6) RSA_AES (7) RSA_OAEP (8) NIST_TDEA_WRAP (9), AES_GCM (10) and CLOUDHSM_AES_GCM (11). A -iv option supports the additional of a specific initialisation vector.

Key wrapping and C#

We can generate a random KEK with:

// Random key generation
CipherKeyGenerator myKey = new CipherKeyGenerator();
myKey.Init(new KeyGenerationParameters(new SecureRandom(), Convert.ToInt32(size)));
var keyParam = myKey.GenerateKeyParameter();

We can then wrap our key (inputData) with:

// Key wrap
var symmetricBlockCipher = new AesEngine();
Rfc3394WrapEngine wrapEngine = new Rfc3394WrapEngine(symmetricBlockCipher);
wrapEngine.Init(true, keyParam);
var wrappedData= wrapEngine.Wrap(inputData,0,inputData.Length);

And then unwrap with:

// Key unwrap
wrapEngine.Init(false, keyParam);
var unwrap= wrapEngine.Unwrap(wrappedData,0,wrappedData.Length);

The following is the full code [here]:



namespace PB_WRAP02
{

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Security;
using System.Text.Json;


class Program
{

static void Main(string[] args)
{



var keystr="0001020304050607";
var size ="128";


if (args.Length >0) keystr=args[0];
if (args.Length >1) size=args[1];


try {
var inputData=System.Convert.FromHexString(keystr);

// Random key generation
CipherKeyGenerator myKey = new CipherKeyGenerator();
myKey.Init(new KeyGenerationParameters(new SecureRandom(), Convert.ToInt32(size)));
var keyParam = myKey.GenerateKeyParameter();


// Key wrap
var symmetricBlockCipher = new AesEngine();
Rfc3394WrapEngine wrapEngine = new Rfc3394WrapEngine(symmetricBlockCipher);
wrapEngine.Init(true, keyParam);
var wrappedData= wrapEngine.Wrap(inputData,0,inputData.Length);

// Key unwrap
wrapEngine.Init(false, keyParam);
var unwrap= wrapEngine.Unwrap(wrappedData,0,wrappedData.Length);

Console.WriteLine("== RFC 3394 Key Wrapping (with AES) ==");
Console.WriteLine("\nKey to protect: {0}",Convert.ToHexString(inputData));
Console.WriteLine("KEK: {0}",Convert.ToHexString(keyParam.GetKey()));
Console.WriteLine("Key size: {0}",keyParam.GetKey().Length);
Console.WriteLine("\nWrapped: {0}",Convert.ToHexString(wrappedData));
Console.WriteLine("\nUnwrapped: {0}",Convert.ToHexString(unwrap));


} catch (Exception e) {
Console.WriteLine("Error: {0}",e.Message);
}
}
}
}

A sample run with a key of 0x0001020304050607 is [here]:

== RFC 3394 Key Wrapping (with AES) ==

Key to protect: 0001020304050607
KEK: 852125975C51CA0EA74C35A5ACD4EB6A
Key size: 16

Wrapped: 40CD2F82477B79065BF68B3BBD6AB2A2

Unwrapped: 0001020304050607

Conclusions

Key wrapping is so important for backing up keys and also in moving them from one system to another. While RFC 3394 supports only multiples of 64 bits (8 bytes), RFC 5649 supports any size of key.

Here is some more on key wrapping:

https://asecuritysite.com/wrap/

--

--

Prof Bill Buchanan OBE FRSE
ASecuritySite: When Bob Met Alice

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.