Wicket encrypts data with one API in org.apache.wicket.core.util.crypt. A scheme decides how data is encrypted, a factory decides which key is used, and SchemeCrypt ties them together. Every scheme Wicket ships is authenticated: altered ciphertext fails to decrypt instead of producing garbage.
This page describes Wicket 11. In Wicket 10,
ICryptandICryptFactorylive inorg.apache.wicket.util.cryptand work on strings. URLs are encrypted by default withSunJceCrypt(PBEWithMD5AndDES) fromKeyInSessionSunJceCryptFactory;AESCryptandTrivialCryptare alternatives. Stored pages use anICrypter(DefaultCrypter, AES-CBC, orGCMSIVCrypter) set withStoreSettings.setCrypter. Neither default is authenticated. Wicket 11 removes all of these in favour of the API below, andKeyInSessionCryptFactoryandApplicationKeyCryptFactoryare new. See the Wicket 10 guide and, for stored pages, Configure the page store.
Where Wicket encrypts
| Use | Class | Key | Encryption | Turned on by |
|---|---|---|---|---|
| Encrypted URLs | CryptoMapper |
From the ICryptFactory in the security settings |
Deterministic | Installing CryptoMapper as root mapper, see Encrypt URLs
|
| Stored pages | CryptingPageStore |
Random key per session, generated by the scheme, kept in the session data | Randomized, bound to the page id |
getStoreSettings().setEncrypted(true), see Configure the page store
|
| Upload tokens |
AbstractFileUploadResource, used by FileUploadToResourceField
|
From the ICryptFactory in the security settings |
Randomized | Always, when the resource is used |
All three use the scheme and the accepted schemes from SecuritySettings. An upload token carries the upload limits (upload id, maximum sizes and file count) in encrypted form. The resource checks it by decrypting it and comparing the limits it contains with its own; a token that was altered fails authentication and the upload is refused. A page that cannot be decrypted, because the session key is gone or the bytes were altered, is treated as no longer stored. An encrypted URL that cannot be decrypted is treated as not mapped.
Settings
All in SecuritySettings, reached with getSecuritySettings(), except where noted.
| Setting | Default | Meaning |
|---|---|---|
setCryptScheme(ICryptScheme) |
AesGcmCryptScheme |
Scheme used for all encryption. Always accepted for decryption. |
setWhitelistedCryptSchemes(Collection<ICryptScheme>) |
only the encryption scheme | Schemes accepted for decryption. Ciphertext of any other scheme is refused. |
setCryptFactory(ICryptFactory) |
KeyInSessionCryptFactory |
Source of the key for URLs and upload tokens |
setRandomSupplier(ISecureRandomSupplier) |
DefaultSecureRandomSupplier |
Randomness for keys and nonces |
StoreSettings.setEncrypted(boolean) |
false |
Wraps the page store in a CryptingPageStore
|
ICrypt
The interface every encrypting component uses. It works on bytes and adds a URL-safe Base64 layer for strings.
| Method | Description |
|---|---|
byte[] encrypt(byte[] plain, byte[] associatedData) |
Randomized: a fresh nonce per call, so the same input gives a different result every time |
byte[] encryptDeterministic(byte[] plain, byte[] associatedData) |
The same input gives the same result for as long as the key lives |
byte[] decrypt(byte[] encrypted, byte[] associatedData) |
Decrypts either kind; returns null on any failure |
encrypt(byte[]), encryptDeterministic(byte[]), decrypt(byte[])
|
The same without associated data |
String encryptUrlSafe(String) |
Randomized, as URL-safe Base64 without padding |
String encryptUrlSafeDeterministic(String) |
Deterministic, as URL-safe Base64; what CryptoMapper uses |
String decryptUrlSafe(String) |
Decrypts either; returns null on any failure, including invalid Base64 |
Associated data is authenticated but not encrypted. Decryption fails unless the same associated data is supplied. CryptingPageStore passes the page id, so a stored page cannot be swapped for another one.
Decryption never throws for bad input: an unknown or refused scheme, a failed authentication and malformed input all return null.
ICrypt implementations
| Class | Description |
|---|---|
SchemeCrypt |
The default. Encrypts with one scheme, decrypts with that scheme or any accepted scheme, using one key. Constructor: SchemeCrypt(SecretKey key, SecureRandom random, ICryptScheme encryptionScheme, Collection<? extends ICryptScheme> whitelistedSchemes). |
NoCrypt |
Returns its input unchanged. Only for environments without a usable security provider; never in production. |
Schemes
An ICryptScheme is one authenticated cipher with a one-byte id. It generates the keys it uses, encrypts randomized and deterministically in the same format, and returns null from decrypt when authentication fails.
| Class | Id | Cipher | Needs |
|---|---|---|---|
AesGcmCryptScheme |
1 | AES-256-GCM (AES/GCM/NoPadding) |
Nothing beyond the JDK. The default. |
AesGcmSivCryptScheme |
2 | AES-256-GCM-SIV (AES/GCM-SIV/NoPadding), resistant to nonce reuse |
Bouncy Castle (org.bouncycastle:bcprov-jdk18on) on the classpath with its provider registered. Slower. |
Both extend AbstractAesGcmCryptScheme: 256-bit AES keys, a 96-bit nonce and a 128-bit authentication tag. Deterministic encryption derives the nonce from the plaintext with HMAC-SHA-256 under a key derived from the encryption key.
Wicket reserves ids 1 to 31. A custom scheme uses an id of 32 or higher, must be thread-safe, and must return null from decrypt on authentication failure; a scheme that returns unverified plaintext removes the tamper detection everything else relies on.
Use AES-GCM-SIV
WicketApplication.java
@Override
protected void init() {
super.init();
Security.addProvider(new BouncyCastleProvider());
getSecuritySettings().setCryptScheme(new AesGcmSivCryptScheme());
}
Change the scheme without losing existing data
Encrypt with the new scheme and keep accepting the old one until all data has been written again. Then drop the old one.
getSecuritySettings()
.setCryptScheme(new AesGcmSivCryptScheme())
.setWhitelistedCryptSchemes(List.of(new AesGcmCryptScheme()));
All schemes accepted by one SchemeCrypt share its key, so they must use compatible keys. The two shipped schemes both use 256-bit AES keys.
Key factories
An ICryptFactory returns an ICrypt bound to a key; newCrypt() is its only method. It decides where the key lives, the scheme decides what the key is.
| Class | Key | Notes |
|---|---|---|
KeyInSessionCryptFactory |
A new random key per session, kept in session metadata | The default. Binds the HTTP session on first use. Data encrypted in one session cannot be decrypted in another or after the session ends. |
ApplicationKeyCryptFactory(SecretKey) |
The given key, for the whole application | Data stays decryptable across sessions and restarts. Manage the key outside the application. |
ApplicationKeyCryptFactory(SecureRandom) |
A random key generated on first use, for the whole application | Data does not survive a restart. |
NoCryptFactory |
None | Returns a NoCrypt. Never in production. |
AbstractCryptFactory |
Implement getKey()
|
Base class: builds a SchemeCrypt from your key and the scheme settings. |
KeyInSessionCryptFactory creates a session even for a stateless page. A stateless application that encrypts URLs needs a factory with an application-wide key:
getSecuritySettings().setCryptFactory(new ApplicationKeyCryptFactory(loadKeyFromVault()));
With an application-wide key an encrypted URL is the same text for every user, so anyone who has the key can confirm which page a leaked URL points to.
CryptoMapper(IRequestMapper, Application) uses the factory from the settings. CryptoMapper(IRequestMapper, Supplier<ICrypt>) takes an ICrypt directly.
Ciphertext format
marker (1 byte) || nonce (12 bytes) || ciphertext || tag (16 bytes)
The marker is the id of the scheme that encrypted the data. It is authenticated as associated data together with any associated data of the caller, so changing it to force a weaker scheme makes decryption fail. On decryption SchemeCrypt looks the marker up among the accepted schemes and refuses the data when it is not there. Strings are this byte sequence in URL-safe Base64 without padding.
Limits
- The page store key lives in the session. Encryption protects stored pages against someone who can read or write the store, not against someone who controls the session. Wicket deserializes what it reads back, so keep the store private.
- Encrypted URLs are deterministic by design: equal URLs encrypt to equal text, and whoever has the key can confirm a guessed URL. They are not an authorization mechanism. How Wicket keeps applications secure explains why.