public final class SecureEnvelope
- Object
- 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.
Fields
public static final int VERSION_1 = 1 | The only format version this build writes. |
public static final int SUITE_AES_256_GCM = 1 | AES-256-GCM, 12 byte nonce, 128 bit tag. |
public static final int NONCE_LENGTH = 12 | GCM nonce length in bytes. |
public static final int TAG_LENGTH = 16 | The GCM authentication tag length in bytes, appended to the ciphertext. |
public static final int MAX_CIPHERTEXT = 67108864 | Ciphertext larger than this is refused on parse. |
Methods
Inherited methods
Field details
VERSION_1
public static final int VERSION_1 = 1SUITE_AES_256_GCM
public static final int SUITE_AES_256_GCM = 1NONCE_LENGTH
public static final int NONCE_LENGTH = 12TAG_LENGTH
public static final int TAG_LENGTH = 16MAX_CIPHERTEXT
public static final int MAX_CIPHERTEXT = 67108864Method details
seal
public static byte[] seal(byte[] key, String keyId, int keyVersion, AssociatedData aad, byte[] plaintext)Parameters
keybyte[]- 32 bytes of AES-256 key material
keyIdString- which key this is, so a reader with several can pick. At most 64 UTF-8 bytes
keyVersionint- the key’s rotation counter
aadAssociatedData- the binding this envelope may only be opened against
plaintextbyte[]- the bytes to protect
Returns
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.
AssociatedData is 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
keybyte[]- 32 bytes of AES-256 key material
keyIdString- which key this is, at most 64 UTF-8 bytes
keyVersionint- the key’s rotation counter
associatedDatabyte[]- authenticated, not encrypted; may be null for none
plaintextbyte[]- the bytes to protect
Returns
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
passwordchar[]- the password, cleared by the caller afterwards
profileKdfProfile- the derivation profile, normally
KdfProfile.current() keyIdString- which key this is
keyVersionint- the key’s rotation counter
aadAssociatedData- the binding this envelope may only be opened against
plaintextbyte[]- the bytes to protect
Returns
parse
public static SecureEnvelope parse(byte[] sealed)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
sealedbyte[]- the envelope bytes
Returns
Throws
VaultExceptionVaultError.CORRUPTfor a malformed blob,VaultError.UNSUPPORTED_FORMATfor a version, suite or KDF this build does not implement
open
public byte[] open(byte[] key, AssociatedData aad)Parameters
keybyte[]- 32 bytes of AES-256 key material
aadAssociatedData- the binding the envelope was sealed against. A different binding fails the tag, which is the point of it
Returns
Throws
VaultExceptionVaultError.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)Parameters
keybyte[]- 32 bytes of AES-256 key material
associatedDatabyte[]- the exact bytes the envelope was sealed against
Returns
openWithPassword
public byte[] openWithPassword(char[] password, AssociatedData aad)sealWithPassword.Parameters
passwordchar[]- the password, cleared by the caller afterwards
aadAssociatedData- the binding the envelope was sealed against
Returns
Throws
VaultExceptionVaultError.AUTHENTICATION_FAILEDfor a wrong password
getVersion
public int getVersion()VERSION_1 for anything this build parses.getSuite
public int getSuite()SUITE_AES_256_GCM for anything this build parses.getKdf
public KdfProfile getKdf()KdfProfile.needsUpgrade() on the result
says whether it is worth rewrapping.getKeyId
public String getKeyId()getKeyVersion
public int getKeyVersion()getSalt
public byte[] getSalt()openWithPassword.