cryptography-kotlin: one crypto API over native platform providers
A cryptography/crypto library for Kotlin Multiplatform
At a glance
- What is it?
- cryptography-kotlin is a Kotlin Multiplatform library that wraps OpenSSL, CryptoKit, WebCrypto and the JCA behind one typed API with aligned defaults, and tests that the implementations agree across platforms. It ships hashing, MAC, signatures, AEAD, encryption, key agreement and key derivation, with the actual cryptography delegated to the platform rather than reimplemented.
- Who is it for?
- cryptography-kotlin suits a Kotlin Multiplatform product that needs the same crypto calls on Android, iOS, JVM and the browser without writing an expect and actual layer per platform, and it suits a single platform JVM service poorly, since the JCA alone would do.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A uniform API on top of four different crypto engines
cryptography-kotlin is a cryptography library for Kotlin Multiplatform. Its premise is stated in the first paragraph of the README: it wraps well-known, future-proof platform native solutions, namely OpenSSL, CryptoKit, WebCrypto and the JCA, with a type-safe uniform API, aligned defaults, and testing for cross-compatibility between platforms.
The design choice that follows from that premise is the important one. The library does not reimplement the primitives. It delegates. On a JVM target the work is done by the JCA, on Apple targets by CryptoKit, in a browser by WebCrypto, and on other targets by OpenSSL. What the library provides is the common vocabulary, the type-safe surface, and the guarantee that the platforms agree with each other.
That guarantee is what distinguishes it from a set of expect and actual declarations you write yourself. The README says the implementations are tested for cross-compatibility between platforms, and the repository contains a testtool directory, a compatibility test server storage path in the build scripts, and a dedicated set of just recipes for compatibility testing with generate, validate and loop steps. So the claim is checked rather than asserted.
The alignment of defaults matters more than it sounds. A platform API that returns a differently serialised key format, or that has a different default padding mode, will produce results that differ between an Android build and an iOS build of the same application. Uniform defaults are what let a team store one artefact and read it on another platform.
The audience is Kotlin Multiplatform teams, and specifically the ones who have already hit the problem of writing the same crypto call four times.
The algorithm surface, and the provider matrix behind it
The README publishes the supported operations as a table, and the list is long enough to be useful. Hashing covers SHA224, SHA256, SHA384, SHA512, SHA3, SHA1, MD5 and RIPEMD160. MAC covers AES-CMAC and HMAC. Digital signatures cover RSA-PSS, RSA-PKCS1, ECDSA, EdDSA and DSA. AEAD covers AES-GCM, AES-CCM and ChaCha20-Poly1305.
Symmetric encryption covers AES in CBC, CTR, ECB, OFB, CFB and CFB8 modes. Public-key encryption covers RSA-OAEP, RSA-PKCS1 and RSA-RAW. Key agreement covers ECDH, XDH and DH. Key derivation covers PBKDF2, HKDF and scrypt.
Two things in that list deserve a comment from an engineer's point of view. The presence of MD5 and SHA1 is a fact of the API surface rather than an endorsement, and it means a project using this library can still choose an algorithm that is only appropriate for legacy interoperability, so the choice has to be made deliberately rather than by default. The presence of AES-ECB is similar, and it is the one mode that leaks plaintext patterns.
More important is the second half of the story. The README says detailed documentation is on the project website, including the full provider support matrix. The matrix is the document that decides whether a given algorithm works on a given platform with a given provider, and it is the first thing to check. A uniform API does not mean uniform coverage: the underlying engines do not all offer the same algorithms, and a platform without a native implementation of something you need is where the abstraction ends.
Two dependencies and a two-line call
The quick start is short enough to quote. In a Kotlin Multiplatform module you add the core library and a provider:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("dev.whyoleg.cryptography:cryptography-core:0.6.0")
implementation("dev.whyoleg.cryptography:cryptography-provider-optimal:0.6.0")
}
}
}The core module carries the API and the abstractions, and the provider module supplies the implementations, with the optimal provider selecting the best available backing on each platform. Splitting them this way means a consumer can add a different provider later, or pin one for a specific platform, without touching the code that calls the API.
Using it is two lines:
// get a hasher
val sha = CryptographyProvider.Default.get(SHA512).hasher()
// hash a message
sha.hash("Kotlin is Awesome".encodeToByteArray())The shape of that call is the whole API philosophy in miniature. You ask the provider for an algorithm by name, it returns an object with the operation bound to it, and you call the operation. Nothing in the call site mentions OpenSSL or CryptoKit or the JCA, and nothing in the call site changes when the target platform does.
The documentation points at a Getting Started guide and an API reference on the project site, and the README itself does not document the full API, which is a reasonable split for a library of this size.
The repository is many modules and a build tool of its own
The top level of the repository is a Gradle multi-project build with a lot of separate modules, and the names tell you how the library is put together. cryptography-core is the API and the abstractions. cryptography-providers holds the platform implementations, and the module names in the just recipes, cryptography-provider-jdk among them, show how a provider is its own module with its own test task. cryptography-bigint is the arbitrary precision arithmetic that the elliptic curve and RSA work needs, cryptography-random is the random source abstraction, and cryptography-serialization handles key and certificate encoding.
There is also cryptography-bom, a dependency list for version alignment, cryptography-gradle-plugin, and cryptography-version-catalog. Two directories are worth a second look. swiftinterop holds the Swift interop work, which is how the Apple side of a Kotlin Multiplatform project reaches CryptoKit, and testtool holds the test tooling behind the cross-compatibility testing. The build itself is in build-logic and build-settings, with the Gradle wrapper, gradle.properties and a justfile at the root.
The docs are generated with MkDocs, there is a karma.config.d directory for the JavaScript side of the test runs, and a context7.json file that lets tooling such as an assistant fetch the documentation. There is also a CLAUDE.md and a CHANGELOG.md, plus an .idea directory checked in.
The gradle plugin in the list is a hint about the ergonomics: the library is not just a set of jars, it also has a build plugin, which usually means the setup work of wiring a provider into each target is something the plugin does for you.
Compatibility testing is a first-class command
The justfile is the entry point for the development tasks, and it is where the cross-compatibility claim is operationalised.
Building is a single recipe that can take a module argument, either a short name such as just build core or an exact Gradle module path such as just build :cryptography-core, and it passes flags to skip test tasks and native linking. Linking the native binaries is a separate recipe, and there is one for updating the public API ABI dump after API changes, which tells you the project treats its public ABI as something to be tracked rather than left to drift.
The interesting recipe is the provider test one. The JDK provider tests can be filtered by class, for example by an AesGcm pattern, and they accept a step argument with the values generate, validate and loop. The step names tell you what the compatibility cycle does. The generate step produces output from one implementation, the validate step checks another implementation against it, and loop repeats the cycle. The recipe adds a flag that turns on the testtool when a step is given, and the compat-clean recipe removes the compatibility test server storage, with a comment saying to run it before a fresh generate and validate cycle.
That is a real cross-implementation test harness rather than a claim, and it explains the testtool module and the compat-clean recipe. The rest of the file is ordinary: a docs recipe that runs the MkDocs preparation task and then mkdocs build with clean and strict flags, and a default recipe that lists the available commands. The style note at the top explains the preference for just's inline conditionals over shell scripting, so that the resolved command is always printed to the user, which is a nice touch for a build tool people are expected to read.
Where the abstraction stops, and what to check first
The first limit is coverage, and it is the one to check before anything else. The provider support matrix on the project site is the answer, and a uniform API does not remove it. If a target platform lacks a native implementation of an algorithm you need, the library either cannot offer it there or falls back to another provider, and which of those happens is a decision you want to know about in advance rather than discover on a device.
The second limit is what the library does not attempt. It is an algorithms and primitives library. Key storage, key lifecycle, certificate issuance and distribution, secure transport, and anything to do with user identity are outside it, and the README says nothing about them. On a platform with a system keychain, the pieces you would want to pair it with are the platform's own, not this library's.
The third is that delegation means the security properties are the platform's. The library guarantees a uniform API, aligned defaults and cross-compatible results. It does not implement the primitives, so the constant time behaviour, the hardware backing and the side channel resistance of a given algorithm are properties of OpenSSL, CryptoKit, WebCrypto or the JCA on that version of that platform. That is usually the right trade, and it is also the reason to run the compatibility tests on the platforms you ship, not only in the JVM.
The alternative is to use the platform APIs directly. On a single platform that is simpler and there is no abstraction to learn. On a multiplatform project it means writing an expect and actual layer per operation, and having no test suite that checks the four implementations agree, which is exactly the work this library has already done.
Editorial conclusion
cryptography-kotlin suits a Kotlin Multiplatform product that needs the same crypto calls on Android, iOS, JVM and the browser without writing an expect and actual layer per platform, and it suits a single platform JVM service poorly, since the JCA alone would do. Before adopting it, check the provider support matrix for the algorithms and platforms you need, since the library documents a full matrix rather than uniform coverage, and note that MD5 and SHA1 are still listed as supported hashing algorithms, which is a choice you have to make deliberately. The licence is Apache-2.0, the last push was on 2026-10-13, and the newest release is 0.6.0 from 2026-04-02.
Frequently asked questions
What is cryptography-kotlin?
It is a Kotlin Multiplatform cryptography library that wraps OpenSSL, CryptoKit, WebCrypto and the JCA behind a type-safe uniform API with aligned defaults, and its implementations are tested for cross-compatibility between platforms.
How do I add cryptography-kotlin to a Kotlin Multiplatform project?
Add the core and a provider to commonMain dependencies, for example dev.whyoleg.cryptography:cryptography-core:0.6.0 and dev.whyoleg.cryptography:cryptography-provider-optimal:0.6.0, then get an operation through CryptographyProvider.Default.
Which algorithms does cryptography-kotlin support?
Hashing with SHA224, SHA256, SHA384, SHA512, SHA3, SHA1, MD5 and RIPEMD160, MAC with AES-CMAC and HMAC, signatures with RSA-PSS, RSA-PKCS1, ECDSA, EdDSA and DSA, AEAD with AES-GCM, AES-CCM and ChaCha20-Poly1305, plus symmetric and public-key encryption, key agreement and key derivation. The provider support matrix on the project site shows the per-platform coverage.
How do I run the cross-platform compatibility tests?
The justfile provides provider test recipes that accept a step argument of generate, validate or loop, plus a compat-clean recipe that removes the compatibility test server storage before a fresh cycle, and a testtool directory holds the harness.
What licence does cryptography-kotlin use?
Apache-2.0, with the licence text in the LICENSE file at the root of the repository.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/whyoleg-cryptography-kotlin)Was this useful?