.NET 11 AES Key Wrap adds the unpadded RFC 3394 algorithm directly to System.Security.Cryptography.Aes. Use it when an interoperable protocol expects AES-KW for a fixed-size key, not when you need to encrypt arbitrary application data. The safe pattern is to validate the wrapped key size, keep the key-encryption key outside application code, verify unwrap integrity, and treat rotation as a versioned operation.
The essential distinction is simple: EncryptKeyWrap implements RFC 3394 and accepts plaintext of at least 16 bytes in 8-byte increments. EncryptKeyWrapPadded implements AES-KWP for lengths that do not meet that contract. They are related algorithms, but their ciphertext formats are not interchangeable.
Table of Contents
Choose AES-KW only for the right contract
AES-KW wraps key material under a key-encryption key, often abbreviated KEK. A common envelope-encryption design generates a data-encryption key for one object or message, encrypts the data with an authenticated encryption mode, and stores the wrapped data key beside the ciphertext. The KEK stays in a more tightly controlled key-management boundary.
That is different from calling AES-KW on a document, token, connection string, or arbitrary byte payload. RFC 3394 has strict block-length rules and is designed for key data. It also does not carry the application metadata that a complete envelope format normally needs, such as the KEK identifier, content-encryption algorithm, nonce, authentication tag, and format version.
- Use AES-KW when the protocol names RFC 3394 or an identifier such as
A128KW,A192KW, orA256KW, and the wrapped key length is a valid multiple of 8 bytes. - Use AES-KWP when the protocol explicitly requires the padded algorithm or the key material has a length that RFC 3394 cannot accept.
- Use authenticated encryption such as AES-GCM for ordinary application data. Key wrapping is not a replacement for an application-data encryption design.
Do not choose between AES-KW and AES-KWP by trial and error. Record the algorithm identifier in the envelope and reject a record whose identifier does not match the unwrap path. Silently trying both algorithms turns corrupt or misconfigured data into ambiguous behavior.
Wrap a fixed-size key with the array API
The array-returning overload is the clearest starting point. The following method accepts a KEK and a fixed-size content-encryption key, rejects an invalid RFC 3394 length before entering the cryptographic provider, and returns the wrapped value.
using System.Security.Cryptography;
static byte[] WrapKey(ReadOnlySpan<byte> kek, ReadOnlySpan<byte> keyToWrap)
{
if (keyToWrap.Length < 16 || keyToWrap.Length % 8 != 0)
{
throw new ArgumentException(
"RFC 3394 requires at least 16 bytes in 8-byte increments.",
nameof(keyToWrap));
}
using Aes aes = Aes.Create();
aes.SetKey(kek);
return aes.EncryptKeyWrap(keyToWrap);
}
The KEK can be 128, 192, or 256 bits because it is an AES key. The value being wrapped has its own independent size contract. For example, a 16-byte key produces a 24-byte wrapped value because RFC 3394 adds an 8-byte integrity register. Aes.GetKeyWrapLength performs that calculation and rejects invalid plaintext sizes.
Do not confuse the KEK size with the wrapped key size. A 256-bit KEK does not require the content-encryption key to be 256 bits, and choosing a longer KEK does not repair an invalid payload length. Both values must follow the external protocol and the algorithms they protect.
Use span overloads without weakening validation
Services that wrap keys frequently can write into caller-owned memory. The destination for wrapping must be exactly the length returned by GetKeyWrapLength; the implementation rejects both shorter and longer buffers. Source and destination must not overlap.
static int WrapKey(
ReadOnlySpan<byte> kek,
ReadOnlySpan<byte> keyToWrap,
Span<byte> destination)
{
int required = Aes.GetKeyWrapLength(keyToWrap.Length);
if (destination.Length != required)
{
throw new ArgumentException(
$"Destination must be exactly {required} bytes.",
nameof(destination));
}
using Aes aes = Aes.Create();
aes.SetKey(kek);
aes.EncryptKeyWrap(keyToWrap, destination);
return required;
}
Span overloads reduce transient array allocation, but they do not remove secret-lifetime responsibilities. Clear temporary plaintext key buffers with CryptographicOperations.ZeroMemory when ownership permits it. Do not clear memory owned by an upstream key provider unless its contract explicitly transfers ownership to the caller.
Avoid returning pooled buffers that still contain unwrapped key material. When a pool is necessary, clear the complete rented region before returning it, including bytes beyond the logical key length.
Verify the RFC 3394 known-answer vector
A .NET 11 AES Key Wrap known-answer test proves interoperability more directly than a wrap-then-unwrap round trip. A round trip can pass when both sides make the same mistake. RFC 3394 section 4.1 defines a public 128-bit KEK, a public 128-bit plaintext key, and the exact 192-bit wrapped result.
using System.Security.Cryptography;
byte[] kek = Convert.FromHexString(
"000102030405060708090A0B0C0D0E0F");
byte[] plaintextKey = Convert.FromHexString(
"00112233445566778899AABBCCDDEEFF");
byte[] expectedWrapped = Convert.FromHexString(
"1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5");
using Aes aes = Aes.Create();
aes.SetKey(kek);
byte[] actualWrapped = aes.EncryptKeyWrap(plaintextKey);
if (!CryptographicOperations.FixedTimeEquals(
actualWrapped,
expectedWrapped))
{
throw new CryptographicException("RFC 3394 vector mismatch.");
}
byte[] actualPlaintext = aes.DecryptKeyWrap(actualWrapped);
if (!CryptographicOperations.FixedTimeEquals(
actualPlaintext,
plaintextKey))
{
throw new CryptographicException("AES-KW unwrap mismatch.");
}
CryptographicOperations.ZeroMemory(actualPlaintext);
The .NET runtime test suite uses this same RFC vector and the other vectors from sections 4.2 through 4.6. Keep at least one external known-answer vector in your integration tests, then add a vector from the real peer system or protocol library when interoperability crosses a service boundary.
Reject tampered or malformed wrapped keys
An RFC 3394 ciphertext must be at least 24 bytes and a multiple of 8 bytes. A valid unwrap also has to recover the algorithm’s expected integrity value. Invalid length is a contract error; an integrity failure is a cryptographic failure. Neither should fall back to another key, algorithm, or plaintext value automatically.
static bool TryUnwrapKey(
ReadOnlySpan<byte> kek,
ReadOnlySpan<byte> wrappedKey,
Span<byte> destination,
out int bytesWritten)
{
bytesWritten = 0;
if (wrappedKey.Length < 24 || wrappedKey.Length % 8 != 0)
{
return false;
}
int plaintextLength = wrappedKey.Length - 8;
if (destination.Length < plaintextLength)
{
return false;
}
try
{
using Aes aes = Aes.Create();
aes.SetKey(kek);
return aes.TryDecryptKeyWrap(
wrappedKey,
destination,
out bytesWritten);
}
catch (CryptographicException)
{
CryptographicOperations.ZeroMemory(
destination[..plaintextLength]);
return false;
}
}
TryDecryptKeyWrap returns false when the destination is too small. It still throws CryptographicException for integrity failure, overlapping buffers, or a provider error. Keep those cases distinct in internal telemetry, but do not return detailed cryptographic failure reasons to an untrusted caller.
The runtime implementation clears the destination portion when unwrap fails after writing sensitive material. Application code should still clear any longer-lived buffer it owns and avoid logging the KEK, plaintext key, wrapped key, or test vector substitutions from production.
Design key rotation before deployment
AES-KW protects a key with the KEK supplied to the Aes instance. It does not discover, version, rotate, authorize, or audit that KEK. Those are application and key-management responsibilities. A production envelope should include a non-secret key identifier and format version so the reader can select the correct KEK without trying every historical key.
public sealed record WrappedKeyEnvelope(
int FormatVersion,
string Algorithm,
string KekId,
byte[] WrappedKey);
// Example metadata only. Do not place the KEK itself in the envelope.
var envelope = new WrappedKeyEnvelope(
FormatVersion: 1,
Algorithm: "A256KW",
KekId: "orders-kek/2026-09",
WrappedKey: wrappedKey);
- Create the new KEK version and grant only the wrapping service the required operation.
- Start writing new envelopes with the new
KekId. - Keep the previous KEK readable during a bounded migration window.
- Rewrap stored data keys without decrypting the protected application data when the architecture permits it.
- Count records by KEK version and prove that no active record references the old version.
- Disable the old KEK, observe failures, and retain a documented rollback window before final destruction.
Do not store a raw KEK in source control, appsettings.json, a container image, CI variables with broad read access, or application logs. If a managed key service performs wrapping for you, prefer its wrap/unwrap operation rather than exporting the KEK into the process. The new Aes APIs are most appropriate when the application is intentionally responsible for the KEK bytes.
Check provider and platform boundaries
Use Aes.Create() for the platform implementation. The .NET 11 work includes provider paths for Windows, OpenSSL-based platforms, and supported Apple platforms. The legacy AesCryptoServiceProvider does not implement the key-wrap methods and throws NotSupportedException.
- Browser: the runtime’s AES-KW tests exclude the browser platform. Do not assume a Blazor WebAssembly client can use the server-side provider path.
- Legacy provider: replace direct
AesCryptoServiceProviderconstruction withAes.Create()before adopting AES-KW. - Platform baseline: run the known-answer test on every deployment image and operating-system family you support.
- FIPS policy: validate the actual provider and organizational policy. An AES algorithm name alone does not prove that a deployment satisfies a particular compliance profile.
- Protocol compatibility: confirm algorithm identifier, KEK size, wrapped-key size, and encoding with the peer implementation.
Keep the first production release able to read the old envelope format. A rollout that can write the new format but cannot roll back its reader creates an avoidable data-availability risk.
Roll out .NET 11 AES Key Wrap safely
- Inventory: identify the exact protocol and determine whether it requires RFC 3394 AES-KW or padded AES-KWP.
- Pin: use the intended .NET 11 SDK and runtime version in build and deployment.
- Verify: run an RFC known-answer vector and a peer-system vector on every supported platform.
- Validate: reject invalid plaintext and ciphertext lengths before storage or network calls.
- Version: persist the algorithm, envelope version, and KEK identifier beside the wrapped key.
- Protect: keep raw KEKs outside application configuration and minimize plaintext-key lifetime.
- Observe: count wrap failures, unwrap integrity failures, unknown KEK identifiers, and legacy-envelope reads without logging key material.
- Canary: enable new writes for a small workload slice while every reader still understands both formats.
- Roll back: stop new-format writes first; keep dual-read support until the last new envelope has been rewrapped or expired.
The useful outcome is not merely replacing custom crypto code with a new method call. It is a reviewable contract: the protocol chooses AES-KW, length rules are explicit, an external vector proves interoperability, integrity failures remain closed, and KEK rotation can happen without making stored data unreadable.
References
- .NET libraries in .NET 11 RC1 — AES Key Wrap support
- dotnet/runtime #132349 — implement RFC 3394 AES Key Wrap APIs and tests
- dotnet/runtime #132477 — .NET 11 RC1 backport of AES-KW
- RFC 3394 — Advanced Encryption Standard Key Wrap Algorithm
Found this useful? Support more practical developer content.