A dependency-free Java 17 implementation of the Fernet specification. It provides authenticated symmetric encryption for arbitrary byte arrays, optional token expiry, authenticated timestamp extraction, and key rotation.
- Java 17 or newer
- No runtime dependencies
<dependency>
<groupId>dev.ercan</groupId>
<artifactId>fernet</artifactId>
<version>1.0.0</version>
</dependency>Load the key from persistent secret storage during normal application startup:
String key = secretManager.get("fernet-key");Encrypt and decrypt UTF-8 text:
import dev.ercan.fernet.Fernet;
Fernet fernet = Fernet.of(key);
String token = fernet.encryptUtf8("secret message");
String message = fernet.decryptUtf8(token);For binary data, use encrypt(byte[]) and decrypt(String).
Create a key once during provisioning, store its encoded value, and reuse that same value on every application start:
import dev.ercan.fernet.FernetKey;
FernetKey generatedKey = FernetKey.generate();
String valueToPersist = generatedKey.encoded();FernetKey.generate() does not persist the key. Do not call it on each startup: replacing or losing the key makes all
tokens created with the previous key permanently undecryptable.
Pass a Duration when decrypting to enforce a maximum token age:
import java.time.Duration;
byte[] plaintext = fernet.decrypt(token, Duration.ofMinutes(15));An expired token, an invalid signature, malformed input, invalid padding, and an unsupported token version all produce
the same InvalidTokenException. This intentionally avoids exposing which validation stage failed. Fernet timestamps have
one-second precision; a token exactly as old as its TTL is accepted. When a TTL is supplied, tokens more than 60 seconds
in the future are rejected to account for clock skew.
The authenticated creation time can be read with:
Instant createdAt = fernet.extractTimestamp(token);Place the newest key first and retain older keys only for the required migration window:
import dev.ercan.fernet.MultiFernet;
MultiFernet keyRing = MultiFernet.of(currentKey, previousKey);
String newToken = keyRing.encryptUtf8("uses the current key");
String plaintext = keyRing.decryptUtf8(tokenFromEitherKey);
String rotated = keyRing.rotate(tokenFromPreviousKey);rotate authenticates and decrypts with the first matching key, then creates a token with the primary key and a fresh IV.
It preserves the original creation timestamp.
- Fernet keys are encryption keys, not passwords. Generate them once with
FernetKey.generate()during provisioning and store the encoded value in a secret manager; do not derive keys by padding or truncating user passwords. - Anyone holding a key can both create and read tokens. Fernet does not provide asymmetric signatures or authorization.
- The creation timestamp is authenticated but visible in plaintext as part of the Fernet format.
- Fernet is not a password-hashing scheme. Use a dedicated password hashing algorithm for stored passwords.
- Prefer short, explicit TTLs where replay risk matters, and remove fallback keys after the rotation window.
See SECURITY.md for reporting security issues.
mvn verifyThe test suite covers the official Fernet generate.json, verify.json, and invalid.json acceptance vectors, binary and
UTF-8 round trips, TTL/clock-skew boundaries, authenticated-field mutations, concurrent use, and MultiFernet rotation.
Contributions are welcome! Feel free to fork the repo, submit pull requests or open issues.
Released under the MIT License.