Class SecureEnvelope
The one encrypted-blob format this package writes, and the only one it reads.
An envelope is self-describing: it carries the format version, the cipher suite, the key derivation parameters, the salt, the nonce, the identity and version of the key that sealed it, and the ciphertext with its tag. Everything except the ciphertext is authenticated but not encrypted, so a reader can decide what to do before it has a key, and an attacker who edits any of it fails the tag rather than steering the decryption.
Wire format, version 1
offset size field
0 4 magic, the ASCII bytes CN1V
4 1 format version, 1
5 1 cipher suite, 1 = AES-256-GCM with a 12 byte nonce and a 128 bit tag
6 1 KDF id, 0 = none, 1 = PBKDF2-HMAC-SHA256
7 4 KDF iterations, 0 when the KDF id is 0
11 1 salt length, 0 to 64
12 n salt
1 nonce length, exactly 12 for suite 1
n nonce
1 key id length, 0 to 64
n key id, UTF-8
4 key version
4 ciphertext length
n ciphertext, with the 16 byte GCM tag appended
All integers are big-endian. The associated data fed to AES-GCM is the whole header above,
followed by the caller's AssociatedData bytes -- so the version, the suite, the iteration
count and the key id are covered by the tag. That is what makes a downgrade attempt fail
rather than succeed: an attacker who rewrites the iteration count to 1000 has changed the
authenticated bytes, and the decrypt returns VaultError.AUTHENTICATION_FAILED.
What parsing rejects
A blob that is not this format, is truncated, declares a length that does not fit in the
bytes present, declares a suite or version this build does not implement, or exceeds
MAX_CIPHERTEXT is refused before any key is touched. No field is clamped into range on
read, and there is no fallback to an earlier format: an envelope from the future is
VaultError.UNSUPPORTED_FORMAT and not something to guess at.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intCiphertext larger than this is refused on parse.static final intGCM nonce length in bytes.static final intAES-256-GCM, 12 byte nonce, 128 bit tag.static final intThe GCM authentication tag length in bytes, appended to the ciphertext.static final intThe only format version this build writes. -
Method Summary
Modifier and TypeMethodDescriptiongetKdf()The derivation profile the envelope declares.getKeyId()Which key sealed this, so a reader holding several can pick without trying each.intThe key's rotation counter at the time of sealing.byte[]getSalt()The salt, for a caller that derives the key itself rather than throughopenWithPassword(char[], AssociatedData).intgetSuite()The cipher suite, alwaysSUITE_AES_256_GCMfor anything this build parses.intThe format version, alwaysVERSION_1for anything this build parses.byte[]open(byte[] key, byte[] associatedData) The same, against associated data the caller has already serialized.byte[]open(byte[] key, AssociatedData aad) Opens an envelope with a key the caller already has.byte[]openWithPassword(char[] password, AssociatedData aad) Opens an envelope sealed withsealWithPassword(char[], KdfProfile, String, int, AssociatedData, byte[]).static SecureEnvelopeparse(byte[] sealed) Parses an envelope without opening it.static byte[]The same, against associated data the caller has already serialized.static byte[]seal(byte[] key, String keyId, int keyVersion, AssociatedData aad, byte[] plaintext) Seals plaintext under a key the caller already has.static byte[]sealWithPassword(char[] password, KdfProfile profile, String keyId, int keyVersion, AssociatedData aad, byte[] plaintext) Seals plaintext under a key derived from a password.
-
Field Details
-
VERSION_1
public static final int VERSION_1The only format version this build writes.- See Also:
-
SUITE_AES_256_GCM
public static final int SUITE_AES_256_GCMAES-256-GCM, 12 byte nonce, 128 bit tag. The only suite version 1 defines.- See Also:
-
NONCE_LENGTH
public static final int NONCE_LENGTHGCM nonce length in bytes. Twelve, which is the size AES-GCM is defined for; any other length goes through an extra derivation step that not every platform implements alike.- See Also:
-
TAG_LENGTH
public static final int TAG_LENGTHThe GCM authentication tag length in bytes, appended to the ciphertext.- See Also:
-
MAX_CIPHERTEXT
public static final int MAX_CIPHERTEXTCiphertext larger than this is refused on parse. Sixty-four megabytes is far past what a vault record should be, and the bound exists so a corrupt or hostile length field cannot make a reader allocate until it dies.- See Also:
-
-
Method Details
-
seal
public static byte[] seal(byte[] key, String keyId, int keyVersion, AssociatedData aad, byte[] plaintext) Seals plaintext under a key the caller already has.
Parameters
-
key: 32 bytes of AES-256 key material -
keyId: which key this is, so a reader with several can pick. At most 64 UTF-8 bytes -
keyVersion: the key's rotation counter -
aad: the binding this envelope may only be opened against -
plaintext: the bytes to protect
Returns
the envelope bytes, safe to store or to send to a server that must not read them
-
-
seal
public static byte[] seal(byte[] key, String keyId, int keyVersion, byte[] associatedData, byte[] plaintext) The same, against associated data the caller has already serialized.
AssociatedDatais the spelling application code should use; this overload exists for the two callers that hold bytes rather than fields -- the device-protection SPI, whose associated data is the port's own, and an implementation of this format in another language checking itself against test vectors.Parameters
-
key: 32 bytes of AES-256 key material -
keyId: which key this is, at most 64 UTF-8 bytes -
keyVersion: the key's rotation counter -
associatedData: authenticated, not encrypted; may be null for none -
plaintext: the bytes to protect
Returns
the envelope bytes
-
-
sealWithPassword
public static byte[] sealWithPassword(char[] password, KdfProfile profile, String keyId, int keyVersion, AssociatedData aad, byte[] plaintext) Seals plaintext under a key derived from a password.
A fresh salt and nonce are generated for every call. Reusing either is the classic way to destroy AES-GCM, and there is no overload that lets a caller supply them.
Parameters
-
password: the password, cleared by the caller afterwards -
profile: the derivation profile, normallyKdfProfile.current() -
keyId: which key this is -
keyVersion: the key's rotation counter -
aad: the binding this envelope may only be opened against -
plaintext: the bytes to protect
Returns
the envelope bytes
-
-
parse
Parses an envelope without opening it.
Useful before a key is available: the key id and version say which key is needed, and
getKdf()says how long deriving one will take, which is worth showing a user before a six hundred thousand iteration derivation begins.Parameters
sealed: the envelope bytes
Returns
the parsed envelope
Throws
VaultException:VaultError.CORRUPTfor a malformed blob,VaultError.UNSUPPORTED_FORMATfor a version, suite or KDF this build does not implement
-
open
Opens an envelope with a key the caller already has.
Parameters
-
key: 32 bytes of AES-256 key material -
aad: the binding the envelope was sealed against. A different binding fails the tag, which is the point of it
Returns
the plaintext
Throws
VaultException:VaultError.AUTHENTICATION_FAILEDwhen the tag does not verify -- wrong key, altered ciphertext, or the wrong binding. No plaintext is returned in that case, not even a partial one
-
-
open
public byte[] open(byte[] key, byte[] associatedData) The same, against associated data the caller has already serialized. See [#seal(byte[], String, int, byte[], byte[])] for why this overload exists.
Parameters
-
key: 32 bytes of AES-256 key material -
associatedData: the exact bytes the envelope was sealed against
Returns
the plaintext
-
-
openWithPassword
Opens an envelope sealed with
sealWithPassword(char[], KdfProfile, String, int, AssociatedData, byte[]).Parameters
-
password: the password, cleared by the caller afterwards -
aad: the binding the envelope was sealed against
Returns
the plaintext
Throws
VaultException:VaultError.AUTHENTICATION_FAILEDfor a wrong password
-
-
getVersion
public int getVersion()The format version, alwaysVERSION_1for anything this build parses. -
getSuite
public int getSuite()The cipher suite, alwaysSUITE_AES_256_GCMfor anything this build parses. -
getKdf
The derivation profile the envelope declares.KdfProfile.needsUpgrade()on the result says whether it is worth rewrapping. -
getKeyId
Which key sealed this, so a reader holding several can pick without trying each. -
getKeyVersion
public int getKeyVersion()The key's rotation counter at the time of sealing. -
getSalt
public byte[] getSalt()The salt, for a caller that derives the key itself rather than throughopenWithPassword(char[], AssociatedData).
-