Java API Reference
Complete API documentation for MetaMUI Crypto Primitives in Java.
Installation
Maven
<dependency>
<groupId>com.metamui</groupId>
<artifactId>metamui-crypto</artifactId>
<version>3.0.0</version>
</dependency>
Gradle
dependencies {
implementation 'com.metamui:metamui-crypto:3.0.0'
}
Gradle (Kotlin DSL)
dependencies {
implementation("com.metamui:metamui-crypto:3.0.0")
}
Quick Start
import com.metamui.crypto.signature.Ed25519;
import com.metamui.crypto.signature.Ed25519Keypair;
import com.metamui.crypto.encryption.ChaCha20Poly1305;
public class QuickStart {
public static void main(String[] args) throws Exception {
// Generate Ed25519 keypair
Ed25519Keypair keypair = Ed25519.generateKeypair();
// Sign a message
byte[] message = "Hello, World!".getBytes();
byte[] signature = Ed25519.sign(message, keypair.getPrivateKey());
// Verify signature
boolean isValid = Ed25519.verify(signature, message,
keypair.getPublicKey());
System.out.println("Signature valid: " + isValid);
}
}
API Documentation
For detailed API documentation, see:
Package Structure
// Core packages
import com.metamui.crypto.*; // Core classes and interfaces
import com.metamui.crypto.hash.*; // Hash functions
import com.metamui.crypto.signature.*; // Digital signatures
import com.metamui.crypto.encryption.*; // Symmetric encryption
import com.metamui.crypto.kex.*; // Key exchange
import com.metamui.crypto.kdf.*; // Key derivation
import com.metamui.crypto.pqc.*; // Post-quantum algorithms
import com.metamui.crypto.util.*; // Utility classes
import com.metamui.crypto.random.*; // Secure random
JNI Integration
The library uses JNI for performance-critical operations while maintaining pure Java fallbacks.
public class CryptoProvider {
static {
try {
// Load native library for performance
System.loadLibrary("metamui_crypto_jni");
useNative = true;
} catch (UnsatisfiedLinkError e) {
// Fall back to pure Java implementation
useNative = false;
}
}
public static boolean isNativeAvailable() {
return useNative;
}
}
Exception Handling
Checked Exceptions
import com.metamui.crypto.exception.*;
try {
byte[] encrypted = cipher.encrypt(plaintext, key);
} catch (InvalidKeyException e) {
System.err.println("Invalid key: " + e.getMessage());
} catch (EncryptionException e) {
System.err.println("Encryption failed: " + e.getMessage());
} catch (CryptoException e) {
System.err.println("Crypto error: " + e.getMessage());
}
Runtime Exceptions
// Runtime exceptions for programming errors
try {
Ed25519.sign(null, privateKey); // Throws NullPointerException
} catch (NullPointerException e) {
System.err.println("Null input provided");
} catch (IllegalArgumentException e) {
System.err.println("Invalid argument: " + e.getMessage());
}
Memory Management
Try-With-Resources
// Automatic resource management
try (SecureBytes key = new SecureBytes(32)) {
// Generate random key
SecureRandom.fill(key.getBytes());
// Use key for encryption
ChaCha20Poly1305 cipher = new ChaCha20Poly1305(key.getBytes());
byte[] encrypted = cipher.encrypt(data, nonce);
} // Key automatically cleared from memory
// Multiple resources
try (SecureBytes key = new SecureBytes(32);
SecureBytes nonce = new SecureBytes(12)) {
// Use resources
} // All resources cleaned up
Direct ByteBuffers
import java.nio.ByteBuffer;
// Use direct buffers for better performance
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
directBuffer.put(data);
directBuffer.flip();
// Process with zero-copy
Blake3.hash(directBuffer);
// Clean up direct buffer
if (directBuffer instanceof DirectBuffer) {
((DirectBuffer) directBuffer).cleaner().clean();
}
Hash Functions
SHA-256/512
import com.metamui.crypto.hash.Sha256;
import com.metamui.crypto.hash.Sha512;
// Simple hashing
byte[] hash256 = Sha256.hash(data);
byte[] hash512 = Sha512.hash(data);
// Streaming hash
Sha256 hasher = new Sha256();
hasher.update(chunk1);
hasher.update(chunk2);
byte[] hash = hasher.digest();
// Hash string directly
String hashHex = Sha256.hashToHex("Hello, World!");
// HMAC
byte[] hmac = Sha256.hmac(key, message);
boolean valid = Sha256.verifyHmac(key, message, hmac);
BLAKE2b/2s
import com.metamui.crypto.hash.Blake2b;
import com.metamui.crypto.hash.Blake2s;
// Standard BLAKE2b (64 bytes)
byte[] hash = Blake2b.hash(data);
// Custom output size
byte[] hash32 = Blake2b.hash(data, 32);
// Keyed hashing (MAC)
byte[] mac = Blake2b.keyedHash(data, key);
// With parameters
Blake2b.Parameters params = new Blake2b.Parameters()
.setDigestLength(32)
.setKey(key)
.setSalt(salt)
.setPersonal("MyApp".getBytes());
Blake2b hasher = Blake2b.withParameters(params);
hasher.update(data);
byte[] hash = hasher.digest();
BLAKE3
import com.metamui.crypto.hash.Blake3;
// Standard hash
byte[] hash = Blake3.hash(data);
// Keyed hash
byte[] keyedHash = Blake3.keyedHash(data, key);
// Derive key
byte[] derivedKey = Blake3.deriveKey(
"my-context",
inputKeyMaterial,
32 // output length
);
// Streaming
Blake3 hasher = new Blake3();
for (byte[] chunk : chunks) {
hasher.update(chunk);
}
byte[] hash = hasher.digest();
// Extended output (XOF)
Blake3.XOF xof = Blake3.newXOF();
xof.update(data);
byte[] output = new byte[100]; // Any size
xof.squeeze(output);
Digital Signatures
Ed25519
import com.metamui.crypto.signature.Ed25519;
import com.metamui.crypto.signature.Ed25519Keypair;
import com.metamui.crypto.signature.Ed25519PublicKey;
import com.metamui.crypto.signature.Ed25519PrivateKey;
// Generate keypair
Ed25519Keypair keypair = Ed25519.generateKeypair();
// Generate from seed
byte[] seed = new byte[32];
SecureRandom.fill(seed);
Ed25519Keypair seededKeypair = Ed25519.generateKeypair(seed);
// Sign
byte[] signature = Ed25519.sign(message, keypair.getPrivateKey());
// Verify
boolean valid = Ed25519.verify(signature, message, keypair.getPublicKey());
// Export/Import keys
byte[] publicKeyBytes = keypair.getPublicKey().toBytes();
byte[] privateKeyBytes = keypair.getPrivateKey().toBytes();
Ed25519PublicKey publicKey = Ed25519PublicKey.fromBytes(publicKeyBytes);
Ed25519PrivateKey privateKey = Ed25519PrivateKey.fromBytes(privateKeyBytes);
// Batch verification
List<byte[]> signatures = Arrays.asList(sig1, sig2, sig3);
List<byte[]> messages = Arrays.asList(msg1, msg2, msg3);
List<Ed25519PublicKey> publicKeys = Arrays.asList(pk1, pk2, pk3);
boolean[] results = Ed25519.batchVerify(signatures, messages, publicKeys);
Sr25519
import com.metamui.crypto.signature.Sr25519;
import com.metamui.crypto.signature.Sr25519Keypair;
// Generate keypair
Sr25519Keypair keypair = Sr25519.generateKeypair();
// Mini secret key from seed
Sr25519.MiniSecretKey miniSecret = Sr25519.miniSecretFromSeed(seed);
Sr25519Keypair keypair = miniSecret.expandToKeypair();
// Sign with context
byte[] signature = Sr25519.signWithContext(
message,
keypair.getPrivateKey(),
"substrate".getBytes()
);
// Verify with context
boolean valid = Sr25519.verifyWithContext(
signature,
message,
keypair.getPublicKey(),
"substrate".getBytes()
);
// VRF (Verifiable Random Function)
Sr25519.VRFResult vrfResult = Sr25519.vrfSign(message, keypair.getPrivateKey());
byte[] output = vrfResult.getOutput();
byte[] proof = vrfResult.getProof();
Sr25519.VRFVerifyResult verifyResult = Sr25519.vrfVerify(
message, keypair.getPublicKey(), proof
);
Encryption
ChaCha20-Poly1305
import com.metamui.crypto.encryption.ChaCha20Poly1305;
// Create cipher
ChaCha20Poly1305 cipher = new ChaCha20Poly1305(key);
// Generate nonce
byte[] nonce = new byte[ChaCha20Poly1305.NONCE_SIZE];
SecureRandom.fill(nonce);
// Encrypt with associated data
byte[] associatedData = "metadata".getBytes();
ChaCha20Poly1305.EncryptResult result = cipher.encrypt(
plaintext, nonce, associatedData
);
byte[] ciphertext = result.getCiphertext();
byte[] tag = result.getTag();
// Decrypt and verify
byte[] decrypted = cipher.decrypt(ciphertext, nonce, tag, associatedData);
// Using XChaCha20-Poly1305 (24-byte nonce)
ChaCha20Poly1305 xcipher = ChaCha20Poly1305.xchacha20poly1305(key);
byte[] xnonce = new byte[ChaCha20Poly1305.XNONCE_SIZE];
AES-256-GCM
import com.metamui.crypto.encryption.Aes256Gcm;
import javax.crypto.spec.GCMParameterSpec;
// Create cipher
Aes256Gcm aes = new Aes256Gcm(key);
// Generate IV
byte[] iv = new byte[Aes256Gcm.IV_SIZE];
SecureRandom.fill(iv);
// Encrypt
Aes256Gcm.EncryptResult result = aes.encrypt(
plaintext, iv, associatedData
);
byte[] ciphertext = result.getCiphertext();
byte[] tag = result.getTag();
// Decrypt
byte[] plaintext = aes.decrypt(ciphertext, iv, tag, associatedData);
// Check hardware support
if (Aes256Gcm.isHardwareAccelerated()) {
System.out.println("Using AES-NI hardware acceleration");
}
Key Exchange
X25519
import com.metamui.crypto.kex.X25519;
import com.metamui.crypto.kex.X25519Keypair;
// Generate keypairs
X25519Keypair alice = X25519.generateKeypair();
X25519Keypair bob = X25519.generateKeypair();
// Compute shared secrets
byte[] aliceShared = X25519.computeSharedSecret(
alice.getPrivateKey(), bob.getPublicKey()
);
byte[] bobShared = X25519.computeSharedSecret(
bob.getPrivateKey(), alice.getPublicKey()
);
// aliceShared.equals(bobShared)
assert Arrays.equals(aliceShared, bobShared);
// From seed
byte[] privateKey = X25519.generatePrivateKey(seed);
byte[] publicKey = X25519.derivePublicKey(privateKey);
Post-Quantum Algorithms
ML-KEM-768
import com.metamui.crypto.pqc.MlKem768;
import com.metamui.crypto.pqc.MlKem768Keypair;
// Generate keypair
MlKem768Keypair keypair = MlKem768.generateKeypair();
// Encapsulation (sender)
MlKem768.EncapsulateResult encapResult = MlKem768.encapsulate(
keypair.getPublicKey()
);
byte[] ciphertext = encapResult.getCiphertext();
byte[] sharedSecret = encapResult.getSharedSecret();
// Decapsulation (receiver)
byte[] sharedSecret2 = MlKem768.decapsulate(
ciphertext, keypair.getPrivateKey()
);
// Both parties have same shared secret
assert Arrays.equals(sharedSecret, sharedSecret2);
// Serialize keys
byte[] publicKeyBytes = keypair.getPublicKey().serialize();
byte[] privateKeyBytes = keypair.getPrivateKey().serialize();
// Deserialize
MlKem768PublicKey publicKey = MlKem768PublicKey.deserialize(publicKeyBytes);
MlKem768PrivateKey privateKey = MlKem768PrivateKey.deserialize(privateKeyBytes);
Dilithium
import com.metamui.crypto.pqc.Dilithium3;
import com.metamui.crypto.pqc.Dilithium3Keypair;
// Generate keypair
Dilithium3Keypair keypair = Dilithium3.generateKeypair();
// Sign message
byte[] signature = Dilithium3.sign(message, keypair.getPrivateKey());
// Verify signature
boolean valid = Dilithium3.verify(
signature, message, keypair.getPublicKey()
);
// Deterministic signatures
byte[] detSignature = Dilithium3.signDeterministic(
message, keypair.getPrivateKey()
);
// Serialize/Deserialize
byte[] publicKeyBytes = keypair.getPublicKey().toBytes();
byte[] privateKeyBytes = keypair.getPrivateKey().toBytes();
Dilithium3PublicKey publicKey = Dilithium3PublicKey.fromBytes(publicKeyBytes);
Dilithium3PrivateKey privateKey = Dilithium3PrivateKey.fromBytes(privateKeyBytes);
Key Derivation
Argon2
import com.metamui.crypto.kdf.Argon2;
import com.metamui.crypto.kdf.Argon2Config;
import com.metamui.crypto.kdf.Argon2Type;
// Generate salt
byte[] salt = new byte[16];
SecureRandom.fill(salt);
// Hash password with default parameters
byte[] hash = Argon2.hashPassword(
"user_password".toCharArray(),
salt
);
// Hash with custom parameters
Argon2Config config = new Argon2Config.Builder()
.type(Argon2Type.ARGON2ID)
.timeCost(3) // iterations
.memoryCost(65536) // 64 MB
.parallelism(4) // threads
.hashLength(32) // output length
.build();
byte[] customHash = Argon2.hashPassword(
"password".toCharArray(),
salt,
config
);
// Verify password
boolean valid = Argon2.verifyPassword(
"user_password".toCharArray(),
storedHash
);
// Derive encryption key
byte[] key = Argon2.deriveKey(
password,
salt,
32, // key length
config
);
PBKDF2
import com.metamui.crypto.kdf.Pbkdf2;
import com.metamui.crypto.kdf.Pbkdf2.HashAlgorithm;
// Derive key from password
byte[] salt = new byte[32];
SecureRandom.fill(salt);
byte[] key = Pbkdf2.deriveKey(
"password".toCharArray(),
salt,
100000, // iterations
32, // key length
HashAlgorithm.SHA256 // PRF
);
// With progress callback
Pbkdf2.ProgressListener listener = (current, total) -> {
System.out.printf("Progress: %d/%d%n", current, total);
};
byte[] keyWithProgress = Pbkdf2.deriveKey(
password, salt, 100000, 32,
HashAlgorithm.SHA256, listener
);
HKDF
import com.metamui.crypto.kdf.Hkdf;
import com.metamui.crypto.kdf.Hkdf.HashAlgorithm;
// Extract
byte[] prk = Hkdf.extract(
HashAlgorithm.SHA256,
inputKeyMaterial,
salt
);
// Expand
byte[] okm = Hkdf.expand(
HashAlgorithm.SHA256,
prk,
info,
42 // output length
);
// One-shot operation
byte[] derivedKey = Hkdf.deriveKey(
HashAlgorithm.SHA256,
inputKeyMaterial,
32, // output length
salt,
info
);
Android Compatibility
Android-Specific Setup
public class CryptoApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Initialize MetaMUI Crypto for Android
MetaMUICrypto.init(this);
// Enable Android Keystore integration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
MetaMUICrypto.enableAndroidKeystore();
}
}
}
Android Keystore Integration
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
// Store key in Android Keystore
public class AndroidKeyManager {
private static final String KEYSTORE_ALIAS = "MetaMUIKey";
public void generateAndStoreKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
KEYSTORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.build();
keyGen.init(spec);
keyGen.generateKey();
}
public SecretKey getKey() throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
return (SecretKey) keyStore.getKey(KEYSTORE_ALIAS, null);
}
}
Spring Boot Integration
Configuration
@Configuration
@EnableConfigurationProperties(CryptoProperties.class)
public class CryptoConfig {
@Bean
public Ed25519Keypair signingKeypair(CryptoProperties props)
throws Exception {
if (props.getSigningKey() != null) {
byte[] seed = Base64.getDecoder().decode(props.getSigningKey());
return Ed25519.generateKeypair(seed);
}
return Ed25519.generateKeypair();
}
@Bean
public ChaCha20Poly1305 cipher(CryptoProperties props)
throws Exception {
byte[] key = Base64.getDecoder().decode(props.getEncryptionKey());
return new ChaCha20Poly1305(key);
}
}
@ConfigurationProperties(prefix = "crypto")
@Data
public class CryptoProperties {
private String signingKey;
private String encryptionKey;
}
Service Layer
@Service
public class CryptoService {
private final Ed25519Keypair signingKeypair;
private final ChaCha20Poly1305 cipher;
public CryptoService(Ed25519Keypair signingKeypair,
ChaCha20Poly1305 cipher) {
this.signingKeypair = signingKeypair;
this.cipher = cipher;
}
public String signAndEncode(String data) throws Exception {
byte[] signature = Ed25519.sign(
data.getBytes(),
signingKeypair.getPrivateKey()
);
return Base64.getEncoder().encodeToString(signature);
}
@Cacheable("encrypted-data")
public byte[] encrypt(byte[] data) throws Exception {
byte[] nonce = new byte[12];
SecureRandom.fill(nonce);
var result = cipher.encrypt(data, nonce, null);
// Combine nonce + ciphertext + tag
return ByteBuffer.allocate(nonce.length +
result.getCiphertext().length +
result.getTag().length)
.put(nonce)
.put(result.getCiphertext())
.put(result.getTag())
.array();
}
}
Security Provider Registration
import java.security.Security;
import com.metamui.crypto.provider.MetaMUIProvider;
public class SecurityConfig {
static {
// Register MetaMUI as security provider
Security.addProvider(new MetaMUIProvider());
// Set as preferred provider for specific algorithms
Security.setProperty("crypto.policy", "unlimited");
Security.insertProviderAt(new MetaMUIProvider(), 1);
}
public static void listProviders() {
for (Provider provider : Security.getProviders()) {
System.out.println(provider.getName() + " v" +
provider.getVersion());
provider.getServices().stream()
.filter(s -> s.getType().equals("Signature"))
.forEach(s -> System.out.println(" " + s.getAlgorithm()));
}
}
}
Performance Tuning
Buffer Pools
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
public class BufferPool {
private final ArrayBlockingQueue<ByteBuffer> pool;
private final int bufferSize;
public BufferPool(int poolSize, int bufferSize) {
this.bufferSize = bufferSize;
this.pool = new ArrayBlockingQueue<>(poolSize);
// Pre-allocate buffers
for (int i = 0; i < poolSize; i++) {
pool.offer(ByteBuffer.allocateDirect(bufferSize));
}
}
public ByteBuffer acquire() throws InterruptedException {
ByteBuffer buffer = pool.take();
buffer.clear();
return buffer;
}
public void release(ByteBuffer buffer) {
if (buffer.capacity() == bufferSize) {
pool.offer(buffer);
}
}
}
Parallel Processing
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ParallelCrypto {
private final ExecutorService executor =
Executors.newWorkStealingPool();
public CompletableFuture<byte[][]> hashFilesAsync(List<File> files) {
List<CompletableFuture<byte[]>> futures = files.stream()
.map(file -> CompletableFuture.supplyAsync(() -> {
try {
byte[] data = Files.readAllBytes(file.toPath());
return Blake3.hash(data);
} catch (IOException e) {
throw new CompletionException(e);
}
}, executor))
.collect(Collectors.toList());
return CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.toArray(byte[][]::new));
}
public void shutdown() {
executor.shutdown();
}
}
Native Optimization
public class NativeOptimization {
static {
// Load platform-specific native library
String os = System.getProperty("os.name").toLowerCase();
String arch = System.getProperty("os.arch");
if (os.contains("win")) {
System.loadLibrary("metamui_crypto_win_" + arch);
} else if (os.contains("mac")) {
System.loadLibrary("metamui_crypto_mac_" + arch);
} else if (os.contains("linux")) {
System.loadLibrary("metamui_crypto_linux_" + arch);
}
}
// Native method declarations
private static native byte[] blake3HashNative(byte[] data);
private static native byte[] chacha20Poly1305EncryptNative(
byte[] plaintext, byte[] key, byte[] nonce);
// Java fallback implementations
public static byte[] blake3Hash(byte[] data) {
if (isNativeAvailable()) {
return blake3HashNative(data);
}
return Blake3.hash(data);
}
}
Examples
JWT Token Generation
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
public class JwtHelper {
private final Ed25519Keypair keypair;
public JwtHelper(Ed25519Keypair keypair) {
this.keypair = keypair;
}
public String createToken(Map<String, Object> claims) {
// Create custom EdDSA algorithm
Algorithm algorithm = new EdDSAAlgorithm(keypair);
return JWT.create()
.withIssuer("metamui")
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + 3600000))
.withClaim("user_id", claims.get("user_id"))
.withClaim("roles", (List<String>) claims.get("roles"))
.sign(algorithm);
}
public DecodedJWT verifyToken(String token) {
Algorithm algorithm = new EdDSAAlgorithm(keypair);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("metamui")
.build();
return verifier.verify(token);
}
// Custom EdDSA algorithm implementation
private static class EdDSAAlgorithm extends Algorithm {
private final Ed25519Keypair keypair;
public EdDSAAlgorithm(Ed25519Keypair keypair) {
super("EdDSA", "EdDSA");
this.keypair = keypair;
}
@Override
public byte[] sign(byte[] contentBytes) throws SignatureException {
try {
return Ed25519.sign(contentBytes, keypair.getPrivateKey());
} catch (Exception e) {
throw new SignatureException("Failed to sign", e);
}
}
@Override
public void verify(DecodedJWT jwt) throws SignatureException {
byte[] signature = Base64.getUrlDecoder().decode(
jwt.getSignature());
byte[] content = (jwt.getHeader() + "." +
jwt.getPayload()).getBytes();
if (!Ed25519.verify(signature, content, keypair.getPublicKey())) {
throw new SignatureException("Invalid signature");
}
}
}
}
Encrypted Database Fields
import javax.persistence.*;
import com.metamui.crypto.encryption.ChaCha20Poly1305;
@Entity
public class SecureUser {
@Id
@GeneratedValue
private Long id;
private String username;
@Lob
@Column(name = "encrypted_ssn")
private byte[] encryptedSsn;
@Column(name = "ssn_nonce")
private byte[] ssnNonce;
@Transient
private static final ChaCha20Poly1305 cipher;
static {
// Load encryption key from secure configuration
byte[] key = loadEncryptionKey();
cipher = new ChaCha20Poly1305(key);
}
public void setSsn(String ssn) throws Exception {
this.ssnNonce = new byte[12];
SecureRandom.fill(this.ssnNonce);
var result = cipher.encrypt(
ssn.getBytes(),
this.ssnNonce,
this.username.getBytes() // Use username as AAD
);
// Store ciphertext + tag
this.encryptedSsn = ByteBuffer.allocate(
result.getCiphertext().length + result.getTag().length)
.put(result.getCiphertext())
.put(result.getTag())
.array();
}
public String getSsn() throws Exception {
if (encryptedSsn == null) return null;
// Split ciphertext and tag
byte[] ciphertext = Arrays.copyOfRange(
encryptedSsn, 0, encryptedSsn.length - 16);
byte[] tag = Arrays.copyOfRange(
encryptedSsn, encryptedSsn.length - 16, encryptedSsn.length);
byte[] plaintext = cipher.decrypt(
ciphertext, ssnNonce, tag,
this.username.getBytes()
);
return new String(plaintext);
}
}
File Encryption Service
import java.nio.file.*;
import java.nio.channels.*;
public class FileEncryptionService {
private final byte[] masterKey;
public FileEncryptionService(byte[] masterKey) {
this.masterKey = masterKey;
}
public void encryptFile(Path inputFile, Path outputFile,
String password) throws Exception {
// Derive file key from password
byte[] salt = new byte[16];
SecureRandom.fill(salt);
byte[] fileKey = Argon2.deriveKey(
password.toCharArray(),
salt,
32,
new Argon2Config.Builder()
.timeCost(3)
.memoryCost(65536)
.parallelism(4)
.build()
);
// Create cipher
ChaCha20Poly1305 cipher = new ChaCha20Poly1305(fileKey);
try (FileChannel input = FileChannel.open(inputFile,
StandardOpenOption.READ);
FileChannel output = FileChannel.open(outputFile,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE)) {
// Write header (salt + nonce)
ByteBuffer header = ByteBuffer.allocate(28);
header.put(salt);
byte[] nonce = new byte[12];
SecureRandom.fill(nonce);
header.put(nonce);
header.flip();
output.write(header);
// Encrypt file in chunks
ByteBuffer buffer = ByteBuffer.allocateDirect(64 * 1024);
while (input.read(buffer) != -1) {
buffer.flip();
byte[] chunk = new byte[buffer.remaining()];
buffer.get(chunk);
var result = cipher.encrypt(chunk, nonce, null);
ByteBuffer encrypted = ByteBuffer.allocate(
result.getCiphertext().length + result.getTag().length);
encrypted.put(result.getCiphertext());
encrypted.put(result.getTag());
encrypted.flip();
output.write(encrypted);
buffer.clear();
incrementNonce(nonce);
}
} finally {
// Clear sensitive data
Arrays.fill(fileKey, (byte) 0);
}
}
private static void incrementNonce(byte[] nonce) {
for (int i = nonce.length - 1; i >= 0; i--) {
if (++nonce[i] != 0) break;
}
}
}
Testing
JUnit 5 Tests
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CryptoTest {
@Test
@Order(1)
void testEd25519SignVerify() throws Exception {
// Arrange
Ed25519Keypair keypair = Ed25519.generateKeypair();
byte[] message = "test message".getBytes();
// Act
byte[] signature = Ed25519.sign(message, keypair.getPrivateKey());
boolean valid = Ed25519.verify(signature, message,
keypair.getPublicKey());
// Assert
assertTrue(valid);
assertEquals(64, signature.length);
}
@ParameterizedTest
@ValueSource(ints = {16, 32, 64, 128})
void testSecureRandomGeneratesUniqueBytes(int size) {
// Act
byte[] random1 = new byte[size];
byte[] random2 = new byte[size];
SecureRandom.fill(random1);
SecureRandom.fill(random2);
// Assert
assertFalse(Arrays.equals(random1, random2));
assertEquals(size, random1.length);
}
@Test
@Timeout(5)
void testArgon2Performance() throws Exception {
byte[] password = "password".getBytes();
byte[] salt = new byte[16];
assertTimeout(Duration.ofSeconds(2), () -> {
Argon2.hashPassword(password, salt);
});
}
}
Mockito Integration
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class CryptoServiceTest {
@Mock
private KeyStore keyStore;
@InjectMocks
private CryptoService service;
@Test
void testEncryptionWithMockedKeyStore() throws Exception {
// Given
byte[] key = new byte[32];
when(keyStore.getKey("encryption-key"))
.thenReturn(key);
// When
byte[] encrypted = service.encrypt("data".getBytes());
// Then
assertNotNull(encrypted);
verify(keyStore).getKey("encryption-key");
}
}