Class BotanBaseAsymmetricCipher
- Direct Known Subclasses:
BotanBlockCipher,BotanStreamCipher
This class provides a JCE-compliant Cipher implementation that delegates cryptographic operations to native
Botan library functions via JNR-FFI. It implements automatic native resource management using the Java
Cleaner API to ensure native cipher objects are properly destroyed when no longer needed.
The class name "BaseAsymmetricCipher" is a misnomer - it actually implements symmetric ciphers (block ciphers, stream ciphers, and AEAD modes). The name predates the current architecture and is retained for compatibility.
Cipher Categories
This base class supports three main categories of symmetric ciphers:
- Block Ciphers - Traditional block cipher modes (CBC, CFB) with padding support
- Stream Ciphers - Stream modes (CTR, OFB) and native stream ciphers (ChaCha20, Salsa20)
- AEAD Ciphers - Authenticated encryption modes (GCM, CCM, EAX, OCB, SIV)
Lifecycle and Resource Management
Native Botan cipher objects are created during initialization and destroyed either:
- Explicitly when re-initializing with a new key (old object destroyed before creating new one)
- Automatically by the Cleaner when the Java object becomes unreachable (garbage collection)
Thread Safety
This implementation is NOT thread-safe. Each thread should use its own Cipher instance. The JCE API does not require Cipher implementations to be thread-safe.
Initialization and IV/Nonce Management
Ciphers require initialization with a key and optional IV (Initialization Vector) or nonce:
- The IV/nonce must be provided via
IvParameterSpecduring initialization - IV/nonce sizes are validated by
isValidNonceLength(int) - The IV is stored and can be retrieved via
engineGetIV() - Important: Reusing the same IV/nonce with the same key is cryptographically unsafe for most modes
Usage Examples
Basic AES-CBC Encryption with Padding
// Get cipher instance from the Botan provider
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7", "Botan");
// Generate key and IV
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); // 16, 24, or 32 bytes
IvParameterSpec iv = new IvParameterSpec(ivBytes); // 16 bytes for AES
// Initialize for encryption
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
// Encrypt data
byte[] plaintext = "Secret message".getBytes();
byte[] ciphertext = cipher.doFinal(plaintext);
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decrypted = cipher.doFinal(ciphertext);
Stream Cipher (ChaCha20)
Cipher cipher = Cipher.getInstance("ChaCha20/None/NoPadding", "Botan");
SecretKeySpec key = new SecretKeySpec(keyBytes, "ChaCha20"); // 32 bytes
IvParameterSpec nonce = new IvParameterSpec(nonceBytes); // 8 bytes
// Encryption
cipher.init(Cipher.ENCRYPT_MODE, key, nonce);
byte[] ciphertext = cipher.doFinal(plaintext);
// Decryption (same nonce required)
cipher.init(Cipher.DECRYPT_MODE, key, nonce);
byte[] plaintext = cipher.doFinal(ciphertext);
AEAD Mode (AES-GCM) with Authentication
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "Botan");
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
GCMParameterSpec params = new GCMParameterSpec(128, nonceBytes); // 128-bit tag, 12-byte nonce
// Encryption with authentication
cipher.init(Cipher.ENCRYPT_MODE, key, params);
cipher.updateAAD(additionalData); // Optional authenticated data
byte[] ciphertext = cipher.doFinal(plaintext); // Includes authentication tag
// Decryption and verification
cipher.init(Cipher.DECRYPT_MODE, key, params);
cipher.updateAAD(additionalData); // Must match encryption
byte[] plaintext = cipher.doFinal(ciphertext); // Throws exception if authentication fails
Incremental Processing with Update
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "Botan");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
// Process data incrementally
byte[] part1 = cipher.update(data1);
byte[] part2 = cipher.update(data2);
byte[] part3 = cipher.update(data3);
byte[] finalPart = cipher.doFinal();
// Combine all parts
byte[] complete = concatenate(part1, part2, part3, finalPart);
Re-initialization with Different Key
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7", "Botan");
// First encryption
SecretKeySpec key1 = new SecretKeySpec(keyBytes1, "AES");
IvParameterSpec iv1 = new IvParameterSpec(ivBytes1);
cipher.init(Cipher.ENCRYPT_MODE, key1, iv1);
byte[] ciphertext1 = cipher.doFinal(plaintext1);
// Re-initialize with different key (automatically destroys old native object)
SecretKeySpec key2 = new SecretKeySpec(keyBytes2, "AES");
IvParameterSpec iv2 = new IvParameterSpec(ivBytes2);
cipher.init(Cipher.ENCRYPT_MODE, key2, iv2); // Old Botan cipher destroyed, new one created
byte[] ciphertext2 = cipher.doFinal(plaintext2);
Different Padding Modes
// PKCS#7 padding (most common)
Cipher pkcs7 = Cipher.getInstance("AES/CBC/PKCS7", "Botan");
// No padding (plaintext must be multiple of block size)
Cipher noPad = Cipher.getInstance("AES/CBC/NoPadding", "Botan");
// Other padding schemes
Cipher x923 = Cipher.getInstance("AES/CBC/X9.23", "Botan");
Cipher oneZero = Cipher.getInstance("AES/CBC/OneAndZeros", "Botan");
Cipher Mode Categories and Padding
Block Cipher Modes (CBC, CFB):
- Support multiple padding schemes: PKCS7, PKCS5, X9.23, OneAndZeros, ESP, NoPadding
- IV size must match the block size of the underlying algorithm (e.g., 16 bytes for AES)
- With NoPadding, plaintext length must be a multiple of block size
Stream Modes (CTR, OFB, ChaCha20, Salsa20):
- Use "/None/NoPadding" or just "/NoPadding" (no padding needed for stream modes)
- Can process any length of plaintext without padding
- Nonce/IV sizes vary by algorithm (e.g., 8 bytes for ChaCha20, 16 bytes for AES-CTR)
AEAD Modes (GCM, CCM, EAX, OCB, SIV):
- Always use "/NoPadding" (AEAD modes don't use padding)
- Provide built-in authentication - no separate MAC needed
- Support Additional Authenticated Data (AAD) via
updateAAD() - Nonce sizes vary by mode (typically 12 bytes for GCM)
Implementation Notes
- Cloning Not Supported - Calling
clone()throwsCloneNotSupportedExceptionbecause native cipher state cannot be safely cloned - Key Size Validation - Key sizes are validated against Botan's key specification during initialization
- Nonce Reuse Warning - The implementation includes a FIXME comment about preventing nonce reuse, which is a critical security concern for most cipher modes
- Memory Safety - Native resources are guaranteed to be freed even if explicit cleanup is not called, thanks to the Cleaner API
- Mode Setting - The JCE API method
setMode()is not supported because the mode is specified in the transformation string duringgetInstance()
Concrete Implementations
This class has three main subclass hierarchies:
BotanBlockCipher- Block cipher modes (CBC, CFB) with paddingBotanStreamCipher- Stream modes and stream ciphersBotanAeadCipher- Authenticated encryption modes
-
Field Summary
FieldsModifier and TypeFieldDescriptionprotected final jnr.ffi.byref.PointerByReferenceHolds the reference to the cipher object referenced by botan.protected byte[]Holds the Initial Vector (IV).protected intHolds the cipher operation mode in native botan terms (0: Encryption, 1: Decryption)protected final StringHolds the name of the cipher algorithm. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionclone()protected byte[]doCipher(byte[] input, int inputLength, int botanFlag) protected abstract byte[]engineDoFinal(byte[] input, int inputOffset, int inputLen) protected intengineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) protected byte[]protected AlgorithmParametersprotected voidengineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) protected voidengineInit(int opmode, Key key, SecureRandom random) protected voidengineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) protected voidprotected voidengineSetMode(String mode) protected intengineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) protected abstract StringgetBotanCipherName(int keyLength) Gets the native botan cipher name (e.g.protected static booleanisDecrypting(int mode) protected abstract booleanisValidNonceLength(int nonceLength) Checks whether the given nonce size is supported.Methods inherited from class javax.crypto.CipherSpi
engineDoFinal, engineGetBlockSize, engineGetKeySize, engineGetOutputSize, engineSetPadding, engineUnwrap, engineUpdate, engineUpdate, engineUpdateAAD, engineUpdateAAD, engineWrap
-
Field Details
-
cipherRef
protected final jnr.ffi.byref.PointerByReference cipherRefHolds the reference to the cipher object referenced by botan. -
name
Holds the name of the cipher algorithm. -
iv
protected byte[] ivHolds the Initial Vector (IV). -
mode
protected int modeHolds the cipher operation mode in native botan terms (0: Encryption, 1: Decryption)
-
-
Constructor Details
-
BotanBaseAsymmetricCipher
-
-
Method Details
-
isDecrypting
protected static boolean isDecrypting(int mode) -
getBotanCipherName
Gets the native botan cipher name (e.g. 'AES-128/CBC/PKCS7').- Parameters:
keyLength- the key length- Returns:
Stringcontaining the Botan cipher name.
-
isValidNonceLength
protected abstract boolean isValidNonceLength(int nonceLength) Checks whether the given nonce size is supported.- Parameters:
nonceLength- the nonce length- Returns:
Trueis the given nonce length is supported,Falseotherwise.
-
engineSetMode
- Specified by:
engineSetModein classCipherSpi- Throws:
NoSuchAlgorithmException
-
engineGetIV
protected byte[] engineGetIV()- Specified by:
engineGetIVin classCipherSpi
-
engineGetParameters
- Specified by:
engineGetParametersin classCipherSpi
-
engineInit
protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException - Specified by:
engineInitin classCipherSpi- Throws:
InvalidKeyExceptionInvalidAlgorithmParameterException
-
engineInit
protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException - Specified by:
engineInitin classCipherSpi- Throws:
InvalidKeyExceptionInvalidAlgorithmParameterException
-
engineInit
- Specified by:
engineInitin classCipherSpi- Throws:
InvalidKeyException
-
engineUpdate
protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) - Specified by:
engineUpdatein classCipherSpi
-
engineDoFinal
protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws IllegalBlockSizeException - Specified by:
engineDoFinalin classCipherSpi- Throws:
IllegalBlockSizeException
-
engineDoFinal
protected abstract byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException - Specified by:
engineDoFinalin classCipherSpi- Throws:
IllegalBlockSizeException
-
doCipher
protected byte[] doCipher(byte[] input, int inputLength, int botanFlag) -
engineReset
protected void engineReset() -
clone
- Overrides:
clonein classObject- Throws:
CloneNotSupportedException
-