Skip to content

Latest commit

 

History

History
348 lines (264 loc) · 9.98 KB

File metadata and controls

348 lines (264 loc) · 9.98 KB

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

  • Zero runtime dependencies — pure JDK, no extra JARs required at runtime

Requirements

  • Java 21 or later

Installation

Maven

<dependency>
    <groupId>com.mirkocaserta.bruce</groupId>
    <artifactId>bruce</artifactId>
    <version>2.1.0</version>
</dependency>

Gradle

implementation 'com.mirkocaserta.bruce:bruce:2.1.0'

Check Maven Central for the latest release version.

Quick Start

All operations are accessed through two static facades. Add these imports once at the top of your file:

import static com.mirkocaserta.bruce.Bruce.*;
import static com.mirkocaserta.bruce.Bruce.Encoding.*;
import static com.mirkocaserta.bruce.Keystores.*;

Usage Examples

Loading Keys from a Keystore

// 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");

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

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"));

Digital Signatures

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
Signer   signer   = signerBuilder().key(signKey).algorithm("SHA256withRSA").build();
Verifier verifier = verifierBuilder().key(verifyKey).algorithm("SHA256withRSA").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);

Message Digest (Hashing)

Digester sha256 = digestBuilder().algorithm("SHA-256").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"));

Symmetric Encryption (AES)

// Generate a random AES key
byte[] keyBytes = symmetricKey("AES");
byte[] ivBytes  = new byte[16];
new SecureRandom().nextBytes(ivBytes);

// Build encryptor and decryptor
SymmetricEncryptor encryptor = cipherBuilder()
    .key(keyBytes)
    .algorithms("AES", "AES/CBC/PKCS5Padding")
    .buildSymmetricEncryptor();

SymmetricDecryptor decryptor = cipherBuilder()
    .key(keyBytes)
    .algorithms("AES", "AES/CBC/PKCS5Padding")
    .buildSymmetricDecryptor();

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);

Asymmetric Encryption (RSA)

// Generate an RSA key pair
KeyPair keyPair = keyPair("RSA", 2048);

AsymmetricEncryptor encryptor = cipherBuilder()
    .key(keyPair.getPublic())
    .algorithm("RSA/ECB/PKCS1Padding")
    .buildAsymmetricEncryptor();

AsymmetricDecryptor decryptor = cipherBuilder()
    .key(keyPair.getPrivate())
    .algorithm("RSA/ECB/PKCS1Padding")
    .buildAsymmetricDecryptor();

Bytes cipherText = encryptor.encrypt(Bytes.from("Top secret"));
Bytes plainText  = decryptor.decrypt(cipherText);

Message Authentication Code (MAC)

// Load a secret key from a keystore
KeyStore ks   = keystore("classpath:keystore.p12", "password");
Key secretKey = secretKey(ks, "hmac-key", "password");

Mac mac = macBuilder()
    .key(secretKey)
    .algorithm("HmacSHA256")
    .build();

Bytes data = Bytes.from("Authenticate me");
Bytes tag  = mac.sign(data);
String hexTag = tag.encode(HEX);

PEM Support

// Read a private key from a PEM string
PrivateKey privKey = privateKeyFromPem("""
    -----BEGIN PRIVATE KEY-----
    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7...
    -----END PRIVATE KEY-----
    """, "RSA");

// Read a public key from a PEM string
PublicKey pubKey = publicKeyFromPem("""
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu7...
    -----END PUBLIC KEY-----
    """, "RSA");

// Convert keys back to PEM
String privatePem = keyToPem(privKey);
String publicPem  = keyToPem(pubKey);

Key Generation

// Generate key pairs
KeyPair rsaKeyPair = keyPair("RSA", 2048);
KeyPair ecKeyPair  = keyPair("EC", 256);

// Generate symmetric keys
byte[] aesKey    = symmetricKey("AES");
String b64AesKey = symmetricKey("AES", BASE64); // encoded for storage

Multi-Key APIs

Bruce supports selecting a key by ID at runtime, which is useful for key-rotation scenarios:

Map<String, PrivateKey> privateKeys = Map.of(
    "key-2023", privateKey(ks, "alice-2023", "password"),
    "key-2024", privateKey(ks, "alice-2024", "password")
);

SignerByKey signer = signerBuilder()
    .keys(privateKeys)
    .algorithm("SHA256withRSA")
    .buildByKey();

// Select the key to use at call time
Bytes signature = signer.sign("key-2024", Bytes.from("Hello"));

Using a Custom Provider

// Use Bouncy Castle for extended algorithm support
Security.addProvider(new BouncyCastleProvider());

Digester digester = digestBuilder()
    .algorithm("BLAKE2b-256")
    .provider("BC")
    .build();

The Bytes Type

Bytes is Bruce’s universal currency type. It wraps a raw byte array and provides convenient conversions:

// Construction
Bytes b1 = Bytes.from(new byte[]{1, 2, 3});       // raw bytes
Bytes b2 = Bytes.from("Hello");                    // UTF-8 text
Bytes b3 = Bytes.from("cafebabe", HEX);            // decode HEX
Bytes b4 = Bytes.from("c2lnbg==", BASE64);         // decode BASE64
Bytes b5 = Bytes.from(Path.of("secret.bin"));      // file contents
Bytes b6 = Bytes.fromPem("-----BEGIN ...");        // PEM-encoded DER

// Consumption
byte[] raw    = b2.asBytes();
String hex    = b2.encode(HEX);
String b64    = b2.encode(BASE64);
String text   = b2.asString();     // UTF-8
int    len    = b2.length();
boolean empty = b2.isEmpty();

Building from Source

git clone https://github.com/mcaserta/bruce.git
cd bruce
./gradlew build

Run tests:

./gradlew test

Generate Javadoc:

./gradlew javadoc

Contributing

Contributions are welcome. See CONTRIBUTING.md for the issue and pull request workflow.

License

Bruce is released under the Apache License, Version 2.0.