MeshLink-crypto provides pure-Kotlin, constant-time cryptographic primitives for Kotlin Multiplatform, with per-primitive native fallback.
MeshLink-crypto provides seven RFC-standard cryptographic primitives as pure-Kotlin, constant-time implementations. Each primitive also has a native fallback path. The library selects per-primitive at each call site. Callers never choose a provider.
The library targets JVM, Android (API 21+), and iOS (arm64 + simulator). JS and WebAssembly targets are out of scope. Built with Kotlin 2.4.10.
| Primitive | RFC | Pure-K | Native fallback |
|---|---|---|---|
| SHA-256 | RFC 6234 §5.1 | Yes | JCA, CommonCrypto |
| SHA-512 | RFC 6234 §5.2 | Yes | JCA, CommonCrypto |
| HMAC-SHA256 | RFC 2104 | Yes | JCA Mac, CCHmac |
| HKDF-SHA256 | RFC 5869 | Yes | Platform HMAC |
| X25519 | RFC 7748 §5 | Yes | JCA KeyAgreement, Security.framework |
| Ed25519 | RFC 8032 §5.1 | Yes | JCA Signature, Security.framework |
| ChaCha20-Poly1305 | RFC 8439 | Yes | JCA Cipher, CryptoKit (iOS) |
The library has three layers of dispatch, per primitive:
- Optional CryptoProvider — a consuming app can inject a platform-native provider at runtime (CryptoKit on iOS, custom JCA on JVM/Android).
- Native C-API / JCA — the library calls the host platform's native crypto directly (CommonCrypto, Security.framework,
java.security,javax.crypto). - Pure-Kotlin fallback — if the native path is unavailable, the library falls back to its own constant-time implementation compiled from the same shared source.
The pure-Kotlin path is the only code that holds secrets and is authored in-house. It is held to three verification gates:
- Wycheproof test vectors as the correctness oracle.
- Custom constant-time lint (
ConstantTimeRule) that bans data-dependent branches and secret-indexed array access at compile time. - Timing harness that asserts no early-exit in secret comparisons.
See Architecture for the full design and Constant-Time Discipline for the security model.
import ch.trancee.meshlink.crypto.Crypto
import ch.trancee.meshlink.crypto.SecretKey
// Sample data (replace with your own key material)
val ikm = ByteArray(32) { 0x01 } // input keying material
val salt = ByteArray(16) { 0x00 } // public salt (empty is also valid)
val info = byteArrayOf(0x01, 0x02) // public context string
val plaintext = "Hello, world!".encodeToByteArray()
// Hash — RFC 6234
val digest = Crypto.sha256(plaintext).getOrThrow()
// Derive a session key with HKDF — RFC 5869
val sessionKey = Crypto.hkdfSha256(ikm, salt, info, outputLength = 32).getOrThrow()
// Encrypt — ChaCha20-Poly1305 (RFC 8439). Nonce is generated internally.
SecretKey(sessionKey).use { key ->
val encrypted = Crypto.chacha20Poly1305Encrypt(key, plaintext).getOrThrow()
// encrypted = nonce(12) || ciphertext || tag(16)
val decrypted = Crypto.chacha20Poly1305Decrypt(key, encrypted).getOrThrow()
}See How to Get Started for adding the dependency to your project.
- How to: Get Started — add the dependency and make your first call
- Tutorial: First Encryption — encrypt and decrypt a message step by step
- How to: Integrate into a KMP Project
- How to: Run Tests and Quality Gates
- How to: Prepare a Release
- How to: Add a Crypto Primitive
- How to: Follow Security and Usage Best Practices
- API Reference — full public API surface
- Supported Primitives — primitives table with RFC and platform mapping
- Architecture — dispatch, module layout, pure-K engines
- Constant-Time Discipline —
@Secret, the detekt rule, and the timing harness
- ADR-0001 — Field arithmetic: radix-2^26, 10 limbs
- ADR-0002 — Per-primitive native-or-pure-K fallback
- ADR-0003 — Wycheproof + constant-time lint + timing harness
- ADR-0004 — Secure storage is out of scope
- ADR-0005 — Typed keys, internal nonce, no-throw API
- ADR-0006 — Single shared KMP module
- ADR-0007 — ktfmt + detekt + kover + abiValidation
- ADR-0008 — SKIE excluded
- CONTRIBUTING.md — prerequisites, build, test, and git hooks
- Contributing a crypto primitive — step-by-step
- Build & test conventions — Gradle flags, kover, detekt
- Agent workflow — the 5-step agent workflow
- CONTEXT.md — domain glossary and terminology
- SECURITY.md — vulnerability reporting policy
- docs/proposals/ — design proposals
- docs/rfcs/ — RFC spec texts used as references
- Open a GitHub issue first.
- Implement the primitive with test vectors, green constant-time lint, and 100% coverage on the pure-K path.
- Run
./gradlew check --rerun-tasks --no-build-cachelocally. - Open a pull request with a Conventional Commit message.
Code of conduct: be respectful. Security issues are reported via SECURITY.md.
See LICENSE.