Bruce is an ergonomic, lightweight, pure Java wrapper around the Java Cryptography Architecture (JCA). It makes common cryptographic operations straightforward without adding any runtime dependencies beyond the JDK.
Features
Digital signatures — sign and verify data with RSA, DSA, ECDSA, and more
Symmetric encryption — AES-CBC, AES-GCM, DES, and other secret-key ciphers
Asymmetric encryption — RSA public/private-key encryption and decryption
Message digests — SHA-256, SHA-512, MD5, and any JCA-supported algorithm
Message Authentication Codes (MAC) — HmacSHA256, HmacSHA512, and more
Keystore management — load PKCS12/JKS keystores and serialize them to bytes, text, or files
Key generation — generate RSA/DSA/EC key pairs and symmetric keys on the fly
PEM support — read and write private keys, public keys, and certificates in PEM format
Multiple encodings — HEX, BASE64, URL-safe BASE64, and MIME BASE64
Pluggable providers — works with any JCA provider (e.g., Bouncy Castle)
Multi-key APIs — select a key by ID at call time for key-rotation scenarios
Type-safe algorithm enums — compile-time safety and IDE auto-completion for all algorithm names
Zero runtime dependencies — pure JDK, no extra JARs required at runtime
All operations are accessed through two static facades. Add these imports once at the top of your file:
Usage Examples
Loading Keys from a Keystore
Keystores can be loaded from multiple sources:
Prefix
Example
classpath:
classpath:keystore.p12
file:
file:/etc/ssl/keystore.p12
http://
http://config-server/keystore.p12
https://
https://config-server/keystore.p12
Keystore Serialization
Digital Signatures
Message Digest (Hashing)
Symmetric Encryption (AES)
Asymmetric Encryption (RSA)
Message Authentication Code (MAC)
PEM Support
Key Generation
Multi-Key APIs
Bruce supports selecting a key by ID at runtime, which is useful for key-rotation scenarios:
Type-Safe Algorithm Enums
All builder methods that accept a raw algorithm string also accept a type-safe enum constant. This provides compile-time validation and IDE auto-completion as an alternative to remembering JCA algorithm name strings.
All enum types implement the AlgorithmId interface, which exposes the underlying JCA name via algorithmName(). The string-based overloads remain fully supported for custom or provider-specific algorithms not covered by the built-in enums.
Using a Custom Provider
The Bytes Type
Bytes is Bruce's universal currency type. It wraps a raw byte array and provides convenient conversions:
Building from Source
Run tests:
Generate Javadoc:
Contributing
Contributions are welcome. See CONTRIBUTING.md for the issue and pull request workflow.
// Load a PKCS12 keystore from the classpath
KeyStore ks = keystore("classpath:keystore.p12", "password".toCharArray(), "PKCS12");
// Extract keys by alias
PrivateKey privateKey = privateKey(ks, "alice", "password".toCharArray());
PublicKey publicKey = publicKey(ks, "alice");
KeyStore ks = keystore("classpath:keystore.p12", "password", "PKCS12");
// Serialize to raw bytes
byte[] raw = keystoreToBytes(ks, "password");
// Serialize and encode for text transport
String base64 = keystoreToString(ks, "password", BASE64);
// Persist directly to disk
keystoreToFile(ks, "password", Path.of("/tmp/keystore-copy.p12"));
KeyStore ks = keystore("classpath:keystore.p12", "password", "PKCS12");
PrivateKey signKey = privateKey(ks, "alice", "password");
PublicKey verifyKey = publicKey(ks, "alice");
// Create a signer and a verifier (string-based)
Signer signer = signerBuilder().key(signKey).algorithm("SHA256withRSA").build();
Verifier verifier = verifierBuilder().key(verifyKey).algorithm("SHA256withRSA").build();
// Or use the type-safe enum alternative:
Signer signer2 = signerBuilder().key(signKey).algorithm(SignatureAlgorithm.SHA256_WITH_RSA).build();
Verifier verifier2 = verifierBuilder().key(verifyKey).algorithm(SignatureAlgorithm.SHA256_WITH_RSA).build();
// Sign
Bytes message = Bytes.from("Hello, Bob!");
Bytes signature = signer.sign(message);
// Encode signature for transport
String b64Signature = signature.encode(BASE64);
// Verify (decode first, then verify)
Bytes sigFromB64 = Bytes.from(b64Signature, BASE64);
boolean valid = verifier.verify(message, sigFromB64);
// String-based or enum-based algorithm selection
Digester sha256 = digestBuilder().algorithm("SHA-256").build();
Digester sha256e = digestBuilder().algorithm(DigestAlgorithm.SHA_256).build(); // equivalent
Digester sha512 = digestBuilder().algorithm("SHA-512").build();
Bytes hash = sha256.digest(Bytes.from("Hello, World!"));
String hexHash = hash.encode(HEX); // "dffd6021bb2bd5b0af676290809ec3a5..."
String b64Hash = hash.encode(BASE64); // "/fVgIbsr1v..."
// File hashing is streamed in chunks (does not load the full file in memory)
Bytes fileHashFromPath = sha256.digest(Path.of("/var/log/app.log"));
Bytes fileHashFromFile = sha256.digest(new File("/var/log/app.log"));
// Generate a random AES key
byte[] keyBytes = symmetricKey("AES");
byte[] ivBytes = new byte[16];
new SecureRandom().nextBytes(ivBytes);
// Build encryptor and decryptor (string-based)
SymmetricEncryptor encryptor = cipherBuilder()
.key(keyBytes)
.algorithms("AES", "AES/CBC/PKCS5Padding")
.buildSymmetricEncryptor();
SymmetricDecryptor decryptor = cipherBuilder()
.key(keyBytes)
.algorithms("AES", "AES/CBC/PKCS5Padding")
.buildSymmetricDecryptor();
// Or use type-safe enums:
SymmetricEncryptor encryptor2 = cipherBuilder()
.key(keyBytes)
.algorithms(SymmetricAlgorithm.AES, SymmetricCipherAlgorithm.AES_CBC_PKCS5)
.buildSymmetricEncryptor();
Bytes iv = Bytes.from(ivBytes);
Bytes cipherText = encryptor.encrypt(iv, Bytes.from("Secret message"));
Bytes plainText = decryptor.decrypt(iv, cipherText);
// Encode for storage/transport
String encoded = cipherText.encode(BASE64);