HSM JSP Provider
HSM JSP Provider
TgHsmProvider is the standard Java Security Provider surface for the HSM. Use it when you want to work with familiar JCA/JCE APIs such as Cipher, KeyGenerator, MessageDigest, Signature, KeyPairGenerator, KeyAgreement, and KEM.
Add the JAR
Maven
<dependency>
<groupId>in.titaniumguard.hsm</groupId>
<artifactId>jsp</artifactId>
<version>0.0.1</version>
</dependency>
Gradle
implementation("in.titaniumguard.hsm:jsp:0.0.1")
Register the provider
The provider is an AutoCloseable wrapper around the HSM transport. For local development, the plaintext target path is the simplest way to connect.
import in.titaniumguard.hsm.jce.appliance.HsmPartition;
import in.titaniumguard.hsm.jsp.provider.HsmJspProvider;
import java.security.Security;
try (var provider = HsmJspProvider.forPlaintextTarget(
"localhost:50051",
new HsmPartition("alpha", "partition-secret")
)) {
// Use Cipher, KeyGenerator, Signature, etc. with the provider instance.
}
What the provider exposes
| JCA/JCE type | Algorithms |
|---|---|
Cipher | AES/GCM/NoPadding, AES/GCM-SIV/NoPadding, ChaCha20-Poly1305, XChaCha20-Poly1305 |
KeyGenerator | AES, ChaCha20, XChaCha20 |
MessageDigest | SHA-224, SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-384, SHA3-512, SM3 |
Signature | RSA, ECDSA, Ed25519, Ed448, MLDSA44, MLDSA65, MLDSA87 |
KeyPairGenerator | RSA, EC, Ed25519, Ed448, X25519, X448, MLDSA44, MLDSA65, MLDSA87, MLKEM512, MLKEM768, MLKEM1024 |
KeyAgreement | X25519, X448 |
KEM | MLKEM512, MLKEM768, MLKEM1024 |
AEAD ciphers
The AEAD cipher SPI supports AAD and both generated and caller-supplied nonces.
- AES modes use
GCMParameterSpec(128, nonce) ChaCha20-Poly1305usesIvParameterSpecXChaCha20-Poly1305usesIvParameterSpecCipher.updateAAD(...)is supported for all AEAD modesCipher.getIV()returns the nonce used for encryption
AES/GCM example
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
SecretKey key = KeyGenerator.getInstance("AES", provider).generateKey();
Cipher encrypt = Cipher.getInstance("AES/GCM/NoPadding", provider);
encrypt.init(Cipher.ENCRYPT_MODE, key);
encrypt.updateAAD("aad".getBytes());
byte[] plaintext = "hello".getBytes();
byte[] ciphertext = encrypt.doFinal(plaintext);
byte[] nonce = encrypt.getIV();
Cipher decrypt = Cipher.getInstance("AES/GCM/NoPadding", provider);
decrypt.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, nonce));
decrypt.updateAAD("aad".getBytes());
byte[] recovered = decrypt.doFinal(ciphertext);
}
ChaCha20-Poly1305 example
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
SecretKey key = KeyGenerator.getInstance("ChaCha20", provider).generateKey();
byte[] nonce = new byte[12];
Cipher encrypt = Cipher.getInstance("ChaCha20-Poly1305", provider);
encrypt.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(nonce));
encrypt.updateAAD("aad".getBytes());
byte[] ciphertext = encrypt.doFinal("hello".getBytes());
byte[] usedNonce = encrypt.getIV();
Cipher decrypt = Cipher.getInstance("ChaCha20-Poly1305", provider);
decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(usedNonce));
decrypt.updateAAD("aad".getBytes());
byte[] recovered = decrypt.doFinal(ciphertext);
}
XChaCha20-Poly1305 example
XChaCha20-Poly1305 uses a 24-byte nonce. The API flow is the same as ChaCha20, but the nonce size is larger.
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
SecretKey key = KeyGenerator.getInstance("XChaCha20", provider).generateKey();
byte[] nonce = new byte[24];
Cipher encrypt = Cipher.getInstance("XChaCha20-Poly1305", provider);
encrypt.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(nonce));
encrypt.updateAAD("aad".getBytes());
byte[] ciphertext = encrypt.doFinal("hello".getBytes());
byte[] usedNonce = encrypt.getIV();
Cipher decrypt = Cipher.getInstance("XChaCha20-Poly1305", provider);
decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(usedNonce));
decrypt.updateAAD("aad".getBytes());
byte[] recovered = decrypt.doFinal(ciphertext);
}
Digests
Use MessageDigest for SHA-2, SHA-3, and SM3. The provider exposes each digest as a standard Java security algorithm.
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.Security;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
byte[] payload = "payload".getBytes(StandardCharsets.UTF_8);
byte[] sha256 = MessageDigest.getInstance("SHA-256", provider).digest(payload);
byte[] sha3 = MessageDigest.getInstance("SHA3-512", provider).digest(payload);
byte[] sm3 = MessageDigest.getInstance("SM3", provider).digest(payload);
}
Notes:
- The provider supports
SHA-224,SHA-256,SHA-384,SHA-512,SHA3-256,SHA3-384,SHA3-512, andSM3. - You can create a fresh
MessageDigestper algorithm and use it exactly like any other Java security provider.
Signatures
Use Signature with a key pair from the matching KeyPairGenerator. The provider signs and verifies the bytes you pass into Signature.update(...). If your protocol expects a digest-first workflow, hash the message yourself before calling sign() or verify().
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.Security;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
KeyPair pair = KeyPairGenerator.getInstance("Ed25519", provider).generateKeyPair();
byte[] message = "signed payload".getBytes(StandardCharsets.UTF_8);
byte[] digest = MessageDigest.getInstance("SHA-512").digest(message);
Signature signer = Signature.getInstance("Ed25519", provider);
signer.initSign(pair.getPrivate());
signer.update(digest);
byte[] signature = signer.sign();
Signature verifier = Signature.getInstance("Ed25519", provider);
verifier.initVerify(pair.getPublic());
verifier.update(digest);
boolean valid = verifier.verify(signature);
}
For RSA, the API is the same, but you choose RSA as the algorithm name:
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.Security;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
KeyPair pair = KeyPairGenerator.getInstance("RSA", provider).generateKeyPair();
byte[] message = "signed payload".getBytes(StandardCharsets.UTF_8);
byte[] digest = MessageDigest.getInstance("SHA-256").digest(message);
Signature signer = Signature.getInstance("RSA", provider);
signer.initSign(pair.getPrivate());
signer.update(digest);
byte[] signature = signer.sign();
Signature verifier = Signature.getInstance("RSA", provider);
verifier.initVerify(pair.getPublic());
verifier.update(digest);
boolean valid = verifier.verify(signature);
}
Notes:
- The provider supports
RSA,ECDSA,Ed25519,Ed448,MLDSA44,MLDSA65, andMLDSA87. - Treat the bytes passed to
Signature.update(...)as the digest input for your protocol. - Use the provider’s key pair generators to obtain keys whose ids stay attached to the returned key wrappers.
Key agreement
Use KeyAgreement for X25519 and X448 shared-secret derivation. Both peers generate a key pair, initialize the agreement with the private key, and perform a single phase with the peer public key.
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
import javax.crypto.KeyAgreement;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
KeyPair left = KeyPairGenerator.getInstance("X25519", provider).generateKeyPair();
KeyPair right = KeyPairGenerator.getInstance("X25519", provider).generateKeyPair();
KeyAgreement leftAgreement = KeyAgreement.getInstance("X25519", provider);
leftAgreement.init(left.getPrivate());
leftAgreement.doPhase(right.getPublic(), true);
byte[] leftSecret = leftAgreement.generateSecret();
KeyAgreement rightAgreement = KeyAgreement.getInstance("X25519", provider);
rightAgreement.init(right.getPrivate());
rightAgreement.doPhase(left.getPublic(), true);
byte[] rightSecret = rightAgreement.generateSecret();
}
Notes:
- The provider supports
X25519andX448. - The shared secret returned by each side should match exactly when both sides use the same curve and peer keys.
KEM
Use KEM for ML-KEM encapsulation and decapsulation. The sender creates an encapsulator from the public key, and the receiver creates a decapsulator from the private key.
Sender side
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
import javax.crypto.KEM;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
KeyPair pair = KeyPairGenerator.getInstance("MLKEM512", provider).generateKeyPair();
KEM kem = KEM.getInstance("MLKEM512", provider);
KEM.Encapsulator encapsulator = kem.newEncapsulator(pair.getPublic());
KEM.Encapsulated encapsulated = encapsulator.encapsulate();
SecretKey sharedFromEncapsulation = encapsulated.key();
byte[] encapsulation = encapsulated.encapsulation();
byte[] sharedKeyBytes = sharedFromEncapsulation.getEncoded();
SecretKey aesKey = new SecretKeySpec(sharedKeyBytes, "AES");
byte[] nonce = new byte[12];
Cipher encrypt = Cipher.getInstance("AES/GCM/NoPadding", provider);
encrypt.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, nonce));
encrypt.updateAAD("payload-aad".getBytes());
byte[] ciphertext = encrypt.doFinal("payload".getBytes());
// Send `encapsulation` and `ciphertext` to the receiver.
}
Receiver side
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
import javax.crypto.KEM;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
try (var provider = HsmJspProvider.forPlaintextTarget("localhost:50051", "alpha", "partition-secret")) {
KeyPair pair = KeyPairGenerator.getInstance("MLKEM512", provider).generateKeyPair();
KEM kem = KEM.getInstance("MLKEM512", provider);
byte[] encapsulation = /* bytes received from the sender */;
byte[] nonce = /* nonce received from the sender */;
byte[] ciphertext = /* ciphertext received from the sender */;
KEM.Decapsulator decapsulator = kem.newDecapsulator(pair.getPrivate());
SecretKey sharedFromDecapsulation = decapsulator.decapsulate(encapsulation);
SecretKey aesKey = new SecretKeySpec(sharedFromDecapsulation.getEncoded(), "AES");
Cipher decrypt = Cipher.getInstance("AES/GCM/NoPadding", provider);
decrypt.init(Cipher.DECRYPT_MODE, aesKey, new GCMParameterSpec(128, nonce));
decrypt.updateAAD("payload-aad".getBytes());
byte[] plaintext = decrypt.doFinal(ciphertext);
}
Notes:
- The provider supports
MLKEM512,MLKEM768, andMLKEM1024. - The encapsulated shared secret and the decapsulated shared secret should match byte-for-byte.
- Use
KEM.Encapsulated.key()to obtain the sender-side shared secret. - Use
KEM.Encapsulated.encapsulation()to obtain the bytes you transmit to the recipient. - Use
KEM.Decapsulator.decapsulate(...)on the recipient side to recover the same shared secret from the encapsulation bytes. - A common next step is to feed the derived secret into another symmetric algorithm, such as
AES/GCM/NoPadding, as shown above. - The same flow works with the other ML-KEM variants; only the algorithm name changes.
Notes
- The provider returns TitaniumGuard key wrappers that preserve the HSM key id.
- Use the provider instance directly in
Cipher.getInstance(...),KeyGenerator.getInstance(...), and similar factory calls. - For decrypt paths, pass the same nonce and AAD that were used during encryption.