GitHub

User guideSecurity

Encryption in Wicket

The crypt API behind encrypted URLs, the encrypted page store and upload tokens; its schemes, key factories, settings and ciphertext format.

ReferenceWicket 11 and later

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, ICrypt and ICryptFactory live in org.apache.wicket.util.crypt and work on strings. URLs are encrypted by default with SunJceCrypt (PBEWithMD5AndDES) from KeyInSessionSunJceCryptFactory; AESCrypt and TrivialCrypt are alternatives. Stored pages use an ICrypter (DefaultCrypter, AES-CBC, or GCMSIVCrypter) set with StoreSettings.setCrypter. Neither default is authenticated. Wicket 11 removes all of these in favour of the API below, and KeyInSessionCryptFactory and ApplicationKeyCryptFactory are 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.