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
}
dependencies {
implementation "io.github.astrapi69:mystic-crypt:${latestVersion}"
}
<dependency>
<groupId>io.github.astrapi69</groupId>
<artifactId>mystic-crypt</artifactId>
<version>${latestVersion}</version>
</dependency>
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
| Command | Description |
|---|---|
hash | Hash a password with Argon2id or PBKDF2 and print the encoded hash |
verify | Verify a password against an encoded hash (exit code 0 = match) |
keygen | Generate a key pair and print or write it as PEM |
kem | Run an ML-KEM or hybrid X25519+ML-KEM encapsulation between two parties |
checksum | Compute the checksum of a file |
der2pem | Convert a DER-encoded private key to PEM |
cert | Create a self-signed X.509 certificate |
keystore | Manage a PKCS12, JKS or JCEKS key store: list, create, add-keypair, import-cert, export-cert, delete |
sign | Sign a file or stdin with an Ed25519, ML-DSA or SLH-DSA private key (PEM) |
verify-signature | Verify such a signature; exit code 0 = valid, 1 = invalid, 2 = error |
obfuscate / disentangle | Obfuscate text with a substitution map, and recover it |
encrypt / decrypt | Encrypt a file or a text with a passphrase (AES-GCM, PBKDF2-HMAC-SHA256), and decrypt it again |
share | Split a secret into shares and combine them back (Shamir secret sharing) |
keyx | Exchange a shared secret with another person: new on the recipient's side, send on the sender's, receive back on the recipient's |
convert | Say 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
| Document | What it covers |
|---|---|
| Capability matrix | Every algorithm, its class, and what is out of scope with reasons |
| Testing strategy | How it is tested, the coverage and mutation numbers, and the defects the process caught |
| Coverage exceptions | Every uncovered line and surviving mutant, with its reason |
| Key agreement, explained | From book ciphers to the Double Ratchet |
| Changelog | What changed in each release |
| Javadoc | API reference |
| Wiki | Task-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.