public final class KdfProfile
- Object
- KdfProfile
The portable password key derivation this package uses, as a versioned profile rather than a set of numbers each caller picks.
Why PBKDF2 and not something better
Argon2id and scrypt are better password KDFs, and neither exists in a browser. Web Crypto
implements exactly one – PBKDF2 – and a password vault whose whole point is that Android,
iOS and the browser derive the same key from the same password cannot use a KDF that one of
the three has to emulate in application code. A JavaScript Argon2 would be orders of magnitude
slower than the native one the phone uses, which in practice means the parameters get lowered
until the browser is usable and every platform is then weaker than PBKDF2 would have been.
So: PBKDF2-HMAC-SHA256, with the iteration count carried in the envelope so it can be raised
without breaking anything already written. current() is what new material is derived with;
needsUpgrade() tells a caller that an envelope it just opened was written under a weaker
profile and should be rewrapped.
Bounds
The iteration count is clamped into MIN_ITERATIONS to MAX_ITERATIONS on the way in and
rejected rather than clamped on the way out of a parsed envelope. The asymmetry is the
point: an attacker who can edit stored bytes would otherwise set the count to 1 and turn a
password check into a guessable one, or set it to two billion and make the application hang on
open. A count outside the range in a stored envelope is VaultError.UNSUPPORTED_FORMAT.
Where the work happens
The derivation is one native call on every port that has one (Util.pbkdf2), and a pure Java
loop over Hmac where there is none. The pure Java path produces identical bytes – it is
RFC 8018 with no latitude in it – but 600000 iterations of software HMAC is slow enough to be
a problem, so a port without the hook should add one rather than rely on it.
Fields
public static final int PBKDF2_HMAC_SHA256 = 1 | PBKDF2 with HMAC-SHA-256, the only KDF identifier version 1 of the envelope defines. |
public static final int DIRECT = 0 | No derivation: the envelope is sealed directly under a key the caller already has. |
public static final int MIN_ITERATIONS = 100000 | Below this an iteration count is not a password KDF, it is a formality. |
public static final int MAX_ITERATIONS = 10000000 | Above this the derivation is a denial of service against the user’s own device. |
public static final int DEFAULT_ITERATIONS = 600000 | What new material is derived with today. |
public static final int SALT_LENGTH = 16 | The salt length this package generates. |
Methods
public static KdfProfile current() | The profile new material should be derived with. |
public static KdfProfile pbkdf2(int iterations) | A profile with an explicit iteration count, clamped into the supported range. |
public int getKdfId() | The envelope’s KDF identifier: PBKDF2_HMAC_SHA256 or DIRECT. |
public int getIterations() | The iteration count, or zero for DIRECT. |
public boolean needsUpgrade() | Whether material written under this profile should be rewrapped under current(). |
public byte[] derive(char[] password, byte[] salt, int length) | Derives length bytes from a password and salt. |
public String toString() | Returns a string representation of the object. |
public boolean equals(Object other) | Indicates whether some other object is “equal to” this one. |
public int hashCode() | Returns a hash code value for the object. |
Inherited methods
Field details
PBKDF2_HMAC_SHA256
public static final int PBKDF2_HMAC_SHA256 = 1DIRECT
public static final int DIRECT = 0MIN_ITERATIONS
public static final int MIN_ITERATIONS = 100000needsUpgrade() reports it.MAX_ITERATIONS
public static final int MAX_ITERATIONS = 10000000DEFAULT_ITERATIONS
public static final int DEFAULT_ITERATIONS = 600000SALT_LENGTH
public static final int SALT_LENGTH = 16Method details
current
public static KdfProfile current()pbkdf2
public static KdfProfile pbkdf2(int iterations)A profile with an explicit iteration count, clamped into the supported range.
Clamping rather than throwing because this is the caller-facing constructor and a caller
asking for 1000 iterations has made a mistake worth correcting silently upward. The
parsing path does not clamp – see forStored(int, int).
Parameters
iterationsint- the requested count
getKdfId
public int getKdfId()PBKDF2_HMAC_SHA256 or DIRECT.getIterations
public int getIterations()DIRECT.needsUpgrade
public boolean needsUpgrade()Whether material written under this profile should be rewrapped under current().
True for anything weaker than today’s default. An application that opens a vault, sees this, and has the password in hand should re-derive and rewrite; one that does not can carry on, because the envelope still opens.
derive
public byte[] derive(char[] password, byte[] salt, int length)length bytes from a password and salt.Parameters
passwordchar[]- the password, as characters so the caller can clear them. Encoded with this package’s UTF-8 and not normalized – see [Bytes#utf8(char[])].
saltbyte[]- fresh per envelope, at least
SALT_LENGTHbytes for new material lengthint- how many bytes to produce, normally 32 for an AES-256 key
Returns
toString
public String toString()equals
public boolean equals(Object other)hashCode
public int hashCode()