Class SecureEnvelope

java.lang.Object
com.codename1.security.vault.SecureEnvelope

public final class SecureEnvelope extends Object

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 Details

    • VERSION_1

      public static final int VERSION_1
      The only format version this build writes.
      See Also:
    • SUITE_AES_256_GCM

      public static final int SUITE_AES_256_GCM
      AES-256-GCM, 12 byte nonce, 128 bit tag. The only suite version 1 defines.
      See Also:
    • NONCE_LENGTH

      public static final int NONCE_LENGTH
      GCM 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_LENGTH
      The GCM authentication tag length in bytes, appended to the ciphertext.
      See Also:
    • MAX_CIPHERTEXT

      public static final int MAX_CIPHERTEXT
      Ciphertext 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.

      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
      • 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, normally KdfProfile.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

      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
      • sealed: the envelope bytes
      Returns

      the parsed envelope

      Throws
    • open

      public byte[] open(byte[] key, AssociatedData aad)

      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_FAILED when 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

      public byte[] openWithPassword(char[] password, AssociatedData aad)

      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
    • getVersion

      public int getVersion()
      The format version, always VERSION_1 for anything this build parses.
    • getSuite

      public int getSuite()
      The cipher suite, always SUITE_AES_256_GCM for anything this build parses.
    • getKdf

      public KdfProfile getKdf()
      The derivation profile the envelope declares. KdfProfile.needsUpgrade() on the result says whether it is worth rewrapping.
    • getKeyId

      public String 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 through openWithPassword(char[], AssociatedData).