Table of Contents

Class KeyEnvelope

Namespace
FishMMO.Auth.Implementation
Assembly
FishMMO-AuthShared.dll

AEAD envelope (AES-256-GCM) for wrapping symmetric key material at rest.

public static class KeyEnvelope
Inheritance
KeyEnvelope
Inherited Members

Remarks

Sealed blob layout:

[0..3]    magic         (4 bytes, identifies a wrapped blob)
[4]       version       (1 byte, currently 0x01)
[5..16]   nonce         (12 bytes, GCM nonce)
[17..N]   ciphertext    (= plaintext length bytes)
[N..N+15] tag           (16 bytes, GCM auth tag)

The magic prefix lets readers distinguish a wrapped blob from a legacy raw 32-byte HMAC key on the same column without an additional schema flag. The false-positive probability for a uniformly-random 32-byte key beginning with the 4-byte magic is 2-32 ≈ negligible; in practice all newly written keys are produced via Wrap(byte[], byte[], byte[]) so the discriminator is unambiguous.

The KEK is supplied per call (e.g. loaded once at startup from a deployment-level secret distinct from the database). A leaked DB dump without the KEK does not disclose any signing-key material.

Fields

HeaderSize

Header length (magic + version + nonce) preceding ciphertext.

public const int HeaderSize = 17

Field Value

int

Magic

Magic prefix identifying a sealed blob produced by Wrap(byte[], byte[], byte[]).

public static readonly byte[] Magic

Field Value

byte[]

MinWrappedLength

Minimum length of any sealed blob produced by Wrap(byte[], byte[], byte[]).

public const int MinWrappedLength = 33

Field Value

int

NonceSize

AES-GCM nonce length in bytes.

public const int NonceSize = 12

Field Value

int

TagSize

AES-GCM authentication tag length in bytes.

public const int TagSize = 16

Field Value

int

Version1

Current sealed-blob version. Bump on incompatible layout changes.

public const byte Version1 = 1

Field Value

byte

Methods

LooksWrapped(byte[])

Returns true when blob begins with the wrapped-blob magic prefix and is structurally large enough to be a valid envelope. Does not validate the GCM tag — callers must still call Unwrap(byte[], byte[], byte[]) for that.

public static bool LooksWrapped(byte[] blob)

Parameters

blob byte[]

Returns

bool

Unwrap(byte[], byte[], byte[])

Decrypts a sealed blob produced by Wrap(byte[], byte[], byte[]) under kek, validating aad. Returns the plaintext on success or null on any structural, version, or authentication failure (the caller MUST treat null as a hard failure and fail closed; do not retry with a different KEK).

public static byte[]? Unwrap(byte[] kek, byte[] wrappedBlob, byte[] aad)

Parameters

kek byte[]
wrappedBlob byte[]
aad byte[]

Returns

byte[]

Wrap(byte[], byte[], byte[])

Encrypts plaintext under kek with AES-256-GCM, authenticating aad. Returns the sealed blob.

public static byte[] Wrap(byte[] kek, byte[] plaintext, byte[] aad)

Parameters

kek byte[]

32-byte key-encryption key (AES-256).

plaintext byte[]

Key material to wrap. Caller retains ownership and is responsible for zeroing it after the call returns.

aad byte[]

Additional authenticated data bound to the ciphertext (e.g. the owning row's stable identifier). May be empty but should be non-null and stable across Wrap(byte[], byte[], byte[])/Unwrap(byte[], byte[], byte[]) cycles.

Returns

byte[]

The sealed blob.