mystic-crypt

mystic-crypt

A modern Java cryptography library: symmetric and public-key encryption, password hashing, digital signatures, key agreement and post-quantum cryptography, with an API you can use correctly without being a cryptographer.

mystic-crypt wraps the JDK's and Bouncy Castle's cryptographic primitives in small, focused classes with safe defaults: authenticated encryption (AES-GCM, ChaCha20-Poly1305) with a fresh nonce per call, memory-hard password hashing (Argon2id), modern elliptic-curve signatures and key agreement (Ed25519, X25519), and the NIST post-quantum algorithms (ML-KEM, ML-DSA, SLH-DSA). You pick the operation; the library picks parameters that are not a footgun.

Installation

Requires JDK 25 or newer, at build and at runtime: the published jar targets JDK 25 bytecode. Replace ${latestVersion} with the version shown on Maven Central.

# gradle/libs.versions.toml
[versions]
mystic-crypt-version = "${latestVersion}"

[libraries]
mystic-crypt = { module = "io.github.astrapi69:mystic-crypt", version.ref = "mystic-crypt-version" }

// build.gradle
dependencies {
    implementation libs.mystic.crypt
}

Quick start

These snippets are taken from the README, where every one is compiled and executed by ReadmeExamplesTest.

Encrypt and decrypt data

AES-256-GCM with a fresh random nonce per call, prepended to the ciphertext:

SecretKey key = SecretKeyFactoryExtensions.newSecretKey(AesAlgorithm.AES.getAlgorithm(), 256);

byte[] encrypted = new BaseByteArrayEncryptor(key)
    .encrypt("attack at dawn".getBytes(StandardCharsets.UTF_8));
byte[] decrypted = new BaseByteArrayDecryptor(key).decrypt(encrypted);

Store a password

Argon2id with a random salt; the salt and all parameters travel inside the returned string:

PasswordEncryptor passwordEncryptor = PasswordEncryptor.getInstance();

String stored = passwordEncryptor.hashPasswordArgon2id("correct horse battery staple");

boolean ok = passwordEncryptor.matchArgon2id("correct horse battery staple", stored);

Sign and verify

KeyPair keyPair = Ed25519Signer.newKeyPair();
byte[] document = "release 11.0.0".getBytes(StandardCharsets.UTF_8);

byte[] signature = new Ed25519Signer(keyPair.getPrivate()).sign(document);

boolean valid = new Ed25519Verifier(keyPair.getPublic()).verify(document, signature);

Agree on a shared key

X25519 Diffie-Hellman, with the raw shared secret run through HKDF rather than used directly:

KeyPair alice = X25519KeyExchange.newKeyPair();
KeyPair bob = X25519KeyExchange.newKeyPair();

SecretKey aliceSecret = X25519KeyExchange.deriveSharedSecret(alice.getPrivate(), bob.getPublic(), 32);
SecretKey bobSecret = X25519KeyExchange.deriveSharedSecret(bob.getPrivate(), alice.getPublic(), 32);
// aliceSecret and bobSecret are equal

Post-quantum key encapsulation

ML-KEM (FIPS 203). HybridKemKeyExchange combines X25519 and ML-KEM and stays secure as long as either half is unbroken:

KeyPair recipient = MlKemKeyExchange.newKeyPair(KeyPairGeneratorAlgorithm.ML_KEM_768);

MlKemKeyExchange.Encapsulation sent =
    MlKemKeyExchange.encapsulate(recipient.getPublic(), KeyPairGeneratorAlgorithm.ML_KEM_768);
SecretKey received = MlKemKeyExchange.decapsulate(recipient.getPrivate(),
    sent.getCiphertext(), KeyPairGeneratorAlgorithm.ML_KEM_768);
// sent.getSharedSecret() and received are equal

Hash

byte[] digest = Sha3Hasher.hashUtf8("hello", MessageDigestAlgorithm.SHA3_256);

Features

Symmetric encryption

AES-GCM and ChaCha20-Poly1305 with a fresh nonce per call, for byte arrays, strings, files, streams and serializable objects. Chainable encryptors, hex-encoded variants, key-committing AEAD against the "invisible salamanders" attack.

Password hashing

Argon2id (recommended), PBKDF2-HMAC-SHA256, bcrypt and scrypt for interop. Salt and parameters are encoded with the hash; comparisons are constant-time.

Public-key cryptography

RSA hybrid encryption, Ed25519 signatures, X25519 key agreement with HKDF, PEM/DER/PKCS#8 keys from OpenSSL or Java, X.509 certificates, key stores, Ed25519 key blinding and SLIP-0010 seed derivation.

Post-quantum

ML-KEM (FIPS 203), ML-DSA (FIPS 204), SLH-DSA (FIPS 205), and a hybrid X25519+ML-KEM combiner for the transition period.

Hashing

SHA-2, SHA-3, BLAKE2b/BLAKE2s including keyed (MAC) mode, HMAC as one call, file and object checksums.

Secret sharing and PAKE

Shamir's Secret Sharing, Feldman verifiable secret sharing, J-PAKE and SRP-6a, AES key wrap, PKCS#11/HSM provider configuration.

The capability matrix lists every algorithm with the classes involved, and what is deliberately not implemented and why.

Command-line interface

The published -all artifact is a runnable uber-jar:

java -jar mystic-crypt-${latestVersion}-all.jar --help
java -jar mystic-crypt-${latestVersion}-all.jar hash --password-stdin < password.txt
java -jar mystic-crypt-${latestVersion}-all.jar checksum --file build.gradle --algorithm SHA-256
CommandDescription
hashHash a password with Argon2id or PBKDF2 and print the encoded hash
verifyVerify a password against an encoded hash (exit code 0 = match)
keygenGenerate a key pair and print or write it as PEM
kemRun an ML-KEM or hybrid X25519+ML-KEM encapsulation between two parties
checksumCompute the checksum of a file
der2pemConvert a DER-encoded private key to PEM
certCreate a self-signed X.509 certificate
keystoreManage a PKCS12, JKS or JCEKS key store: list, create, add-keypair, import-cert, export-cert, delete
signSign a file or stdin with an Ed25519, ML-DSA or SLH-DSA private key (PEM)
verify-signatureVerify such a signature; exit code 0 = valid, 1 = invalid, 2 = error
obfuscate / disentangleObfuscate text with a substitution map, and recover it
encrypt / decryptEncrypt a file or a text with a passphrase (AES-GCM, PBKDF2-HMAC-SHA256), and decrypt it again
shareSplit a secret into shares and combine them back (Shamir secret sharing)
keyxExchange a shared secret with another person: new on the recipient's side, send on the sender's, receive back on the recipient's
convertSay what a key or certificate file is, and convert it between PEM and DER or between PKCS#1 and PKCS#8

Prefer a desktop application? mystic-crypt-ui puts the same library behind a Swing front end.

Documentation

DocumentWhat it covers
Capability matrixEvery algorithm, its class, and what is out of scope with reasons
Testing strategyHow it is tested, the coverage and mutation numbers, and the defects the process caught
Coverage exceptionsEvery uncovered line and surviving mutant, with its reason
Key agreement, explainedFrom book ciphers to the Double Ratchet
ChangelogWhat changed in each release
JavadocAPI reference
WikiTask-oriented guides

The crypt family

mystic-crypt is the top of a three-repository stack:

crypt-api-> crypt-data-> mystic-crypt

crypt-api holds the algorithm constants and interfaces, crypt-data the factories, readers/writers and models, mystic-crypt the ready-to-use encryptors.

Quality

The release gate is 100% branch coverage plus PIT mutation testing, and every uncovered line and surviving mutant has a stated reason. The measured numbers, with the commit they were measured on, live in docs/TESTING.md.

Contributing

Pull requests are welcome: fork astrapi69/mystic-crypt and open a PR against develop. Please add tests for your change; the conventions for contributors describe what a good test looks like here.

./gradlew build            # tests + coverage + formatting check
./gradlew spotlessApply    # fix formatting
./gradlew pitest           # mutation testing (opt-in, minutes)

Questions, bug reports and feature requests belong on the issues page. Security issues: see the security policy.

Support this project

If mystic-crypt is useful to you, a star on GitHub helps others find it. Sharing it or reporting a bug helps just as much.

Financial support is possible through GitHub Sponsors, Liberapay, Ko-fi and PayPal.