{"id":22006336,"url":"https://github.com/a-sit-plus/signum","last_synced_at":"2026-04-14T06:08:38.956Z","repository":{"id":200758893,"uuid":"700518667","full_name":"a-sit-plus/signum","owner":"a-sit-plus","description":"Kotlin Multiplatform Crypto/PKI/ASN.1 Library with Attestation and Hardware-Backed Crypto Support on Mobile","archived":false,"fork":false,"pushed_at":"2026-04-09T11:45:32.000Z","size":41721,"stargazers_count":165,"open_issues_count":65,"forks_count":18,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-04-09T12:25:47.908Z","etag":null,"topics":["aes","asn-1","asn1","attestation","cose","crypto","cryptography","cwt","ecdh","ecdsa","encryption","jose","jwt","key-agreement","kmp","kotlin","kotlin-multiplatform","pki","rsa","signature"],"latest_commit_sha":null,"homepage":"https://a-sit-plus.github.io/signum/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/a-sit-plus.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-10-04T18:37:07.000Z","updated_at":"2026-04-02T20:22:14.000Z","dependencies_parsed_at":"2026-01-28T15:08:24.463Z","dependency_job_id":null,"html_url":"https://github.com/a-sit-plus/signum","commit_stats":null,"previous_names":["a-sit-plus/kmp-crypto","a-sit-plus/signum"],"tags_count":50,"template":false,"template_full_name":null,"purl":"pkg:github/a-sit-plus/signum","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-sit-plus%2Fsignum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-sit-plus%2Fsignum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-sit-plus%2Fsignum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-sit-plus%2Fsignum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/a-sit-plus","download_url":"https://codeload.github.com/a-sit-plus/signum/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-sit-plus%2Fsignum/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31784269,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aes","asn-1","asn1","attestation","cose","crypto","cryptography","cwt","ecdh","ecdsa","encryption","jose","jwt","key-agreement","kmp","kotlin","kotlin-multiplatform","pki","rsa","signature"],"created_at":"2024-11-30T01:11:37.625Z","updated_at":"2026-04-14T06:08:38.943Z","avatar_url":"https://github.com/a-sit-plus.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/docs/assets/signum-light-large.png\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/docs/assets/signum-dark-large.png\"\u003e\n  \u003cimg alt=\"Signum – Kotlin Multiplatform Crypto/PKI Library and ASN1 Parser + Encoder\" src=\"docs/docs/assets/signum-dark-large.png\"\u003e\n\u003c/picture\u003e\n\n\n# Signum – Kotlin Multiplatform Crypto/PKI Library and ASN1 Parser + Encoder\n\n[![A-SIT Plus Official](https://raw.githubusercontent.com/a-sit-plus/a-sit-plus.github.io/709e802b3e00cb57916cbb254ca5e1a5756ad2a8/A-SIT%20Plus_%20official_opt.svg)](https://plus.a-sit.at/open-source.html)\n[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-brightgreen.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0)\n[![Kotlin](https://img.shields.io/badge/kotlin-multiplatform-orange.svg?logo=kotlin)](http://kotlinlang.org)\n[![Kotlin](https://img.shields.io/badge/kotlin-2.3.20-blue.svg?logo=kotlin)](http://kotlinlang.org)\n[![Java](https://img.shields.io/badge/java-17+-blue.svg?logo=OPENJDK)](https://www.oracle.com/java/technologies/downloads/#java17)\n[![iOS](https://img.shields.io/badge/iOS-15-white?logo=apple)](https://support.apple.com/en-gb/108051)\n\n| [![Android](https://img.shields.io/badge/Android_(indispensable)-SDK--26-37AA55?logo=android)](https://developer.android.com/tools/releases/platforms#8.0) |  [![Maven Central (indispensable)](https://img.shields.io/maven-central/v/at.asitplus.signum/indispensable?label=maven-central%20%28indispensable%29)](https://mvnrepository.com/artifact/at.asitplus.signum/)  |  [![Maven SNAPSHOT (indispensable)](https://img.shields.io/nexus/snapshots/https/s01.oss.sonatype.org/at.asitplus.signum/indispensable?label=SNAPSHOT%20%28indispensable%29)](https://s01.oss.sonatype.org/content/repositories/snapshots/at/asitplus/signum/indispensable/)  |\n|:----------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|\n|    [![Android](https://img.shields.io/badge/Android_(Supreme)-SDK--30-37AA55?logo=android)](https://developer.android.com/tools/releases/platforms#11)     |       [![Maven Central (Supreme)](https://img.shields.io/maven-central/v/at.asitplus.signum/supreme?label=maven-central%20%28Supreme%29)](https://mvnrepository.com/artifact/at.asitplus.signum/supreme)        |              [![Maven SNAPSHOT (Supreme)](https://img.shields.io/nexus/snapshots/https/s01.oss.sonatype.org/at.asitplus.signum/supreme?label=SNAPSHOT%20%28Supreme%29)](https://s01.oss.sonatype.org/content/repositories/snapshots/at/asitplus/signum/supreme/)              |\n\n\n\u003c/div\u003e\n\n## Kotlin Multiplatform Crypto/PKI Library with ASN1 Parser + Encoder\n\n* **Multiplatform, platform-native crypto** \u0026rarr; Check out the included [CMP demo App](https://a-sit-plus.github.io/signum/app) to see it in\naction!\n    * **ECDSA and RSA Signer and Verifier**\n    * **Multiplatform ECDH key agreement**\n    * **Hardware-Backed crypto on Android and iOS**\n    * **Platform-native attestation on iOS and Android**\n    * **Configurable biometric authentication on Android and iOS without callbacks or activity passing** (✨Magic!✨)\n    * **Multiplatform AES**\n    * **Multiplatform HMAC**\n    * **Multiplatform RSA Encryption**\n    * **Multiplatform KDF** (using platform-native hashing)\n        * PBKDF2\n        * HKDF\n        * scrypt\n* Public Keys (RSA and EC)\n* Private Keys (RSA and EC)\n* Algorithm Identifiers (Signatures, Hashing)\n* X509 Certificate Class (create, encode, decode)\n* Certification Request (CSR)\n* ObjectIdentifier Class with human-readable notation (e.g. 1.2.9.6245.3.72.13.4.7.6)\n* Generic ASN.1 abstractions to operate on and create arbitrary ASN.1 Data\n* JOSE-related data structures (JSON Web Keys, JWT, etc…)\n* COSE-related data structures (COSE Keys, CWT, etc…)\n* 100% pure Kotlin BitSet\n* Exposes Multibase Encoder/Decoder as an API dependency including [Matthew Nelson's smashing Base16, Base32, and Base64 encoders](https://github.com/05nelsonm/encoding)\n* **ASN.1 Parser and Encoder including a DSL to generate ASN.1 structures**\n    * Parse, create, explore certificates, public keys, CSRs, and **arbitrary ASN.1* structures* on all supported platforms\n    * Powerful, expressive, type-safe ASN.1 DSL on all KMP targets\n\nThis last bit means that you can share ASN.1-related logic across platforms.\nThe very first bit means that you can create and verify signatures on the JVM, Android and on iOS, using platform-native\ncrypto hardware.\n\n### Do check out the full manual with examples and API docs [here](https://a-sit-plus.github.io/signum/)!\nThis README provides just an overview.\nThe full manual is more comprehensive, has separate sections for each module, provides examples, and a full API documentation.\n\n## Using it in your Projects\n\nThis library was built for [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). Currently, it targets\nthe JVM, Android and iOS.\nIt consists of four modules, each of which is published on maven central:\n\n\n|                                                                                                                                      Name                                                                                                                                      | Info                                                                                                                                                                                                                                                               |\n|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n|  \u003cpicture\u003e \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/docs/assets/asn1-light.png\"\u003e   \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/docs/assets/asn1-dark.png\"\u003e   \u003cimg alt=\"Indispensable ASN.1\" src=\"docs/docs/assets/asn1-dark.png\"\u003e \u003c/picture\u003e   | **Indispensable ASN.1** module containing the most sophisticated KMP ASN.1 engine in the known universe. kotlinx-* dependencies aside, it only depends only on [KmmResult](https://github.com/a-sit-plus/kmmresult) for extra-smooth iOS interop.                  | \n|     \u003cpicture\u003e \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/docs/assets/core-light.png\"\u003e   \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/docs/assets/core-dark.png\"\u003e   \u003cimg alt=\"Indispensable\" src=\"docs/docs/assets/core-dark.png\"\u003e \u003c/picture\u003e      | **Indispensable** base module containing the cryptographic data structures, algorithm identifiers, X.509 certificate, …. Depends on the ASN.1 engine.                                                                                                              | \n| \u003cpicture\u003e \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/docs/assets/josef-light.png\"\u003e   \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/docs/assets/josef-dark.png\"\u003e   \u003cimg alt=\"Indispensable Josef\" src=\"docs/docs/assets/josef-dark.png\"\u003e \u003c/picture\u003e | **Indispensable Josef** JOSE add-on module containing JWS/E/T-specific data structures and extensions to convert from/to types contained in the base module. Includes all required kotlinx-serialization magic to allow for spec-compliant de-/serialization.      | \n| \u003cpicture\u003e \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/docs/assets/cosef-light.png\"\u003e   \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/docs/assets/cosef-dark.png\"\u003e   \u003cimg alt=\"Indispensable Cosef\" src=\"docs/docs/assets/cosef-dark.png\"\u003e \u003c/picture\u003e | **Indispensable Cosef** COSE add-on module containing all COSE/CWT-specific data structures and extensions to convert from/to types contained in the base module. Includes all required kotlinx-serialization magic to allow for spec-compliant de-/serialization. |\n|    \u003cpicture\u003e \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/docs/assets/supreme-light.png\"\u003e   \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/docs/assets/supreme-dark.png\"\u003e   \u003cimg alt=\"Supreme\" src=\"docs/docs/assets/supreme-dark.png\"\u003e \u003c/picture\u003e    | **Supreme** KMP crypto provider implementing hardware-backed signature creation and verification across mobile platforms (Android KeyStore / iOS Secure Enclave) and JCA compatibility (on the JVM).                                                               | \n\nThis separation keeps dependencies to a minimum, i.e. it enables including only JOSE-related functionality, if COSE is irrelevant.\nMore importantly, in a JVM, iOS, or Android-only project, it allows for processing cryptographic material without imposing the inclusion of a crypto provider.\n\nSimply declare the desired dependency to get going:\n\n```kotlin \nimplementation(\"at.asitplus.signum:indispensable:$version\")\n```\n\n```kotlin \nimplementation(\"at.asitplus.signum:indispensable-josef:$version\")\n```\n\n```kotlin \nimplementation(\"at.asitplus.signum:indispensable-cosef:$version\")\n```\n\n```kotlin \nimplementation(\"at.asitplus.signum:supreme:$supremeVersion\")\n```\n\n## Rationale\nLooking for a KMP cryptography framework, you have undoubtedly come across\n[cryptography-kotlin](https://github.com/whyoleg/cryptography-kotlin). So have we and it is a powerful\nlibrary, supporting more platforms and more cryptographic operations than Signum Supreme.\nThis begs the question: Why implement another, incompatible\ncryptography framework from scratch? The short answer is: Signum and cryptography-kotlin pursue different goals and priorities.\u003cbr\u003e\ncryptography-kotlin strives for covering a wide range of targets and a broad range of operations based on a flexible provider architecture.\nSignum, on the other hand, focuses on tight platform integration (**including hardware-backed crypto and attestation!**),\nand comprehensive ASN.1, JOSE, and COSE support.\n\n\u003cdetails\u003e\n\u003csummary\u003eMore…\u003c/summary\u003e\n\nSignum was born from the need to have cryptographic data structures available across platforms, such as public keys, signatures,\ncertificates, CSRs, as well as COSE and JOSE data. Hence, we needed a fully-featured ASN.1 engine and mappings from\nX.509 to COSE and JOSE datatypes. We required comprehensive ASN.1 introspection and builder capabilities across platforms.\nMost notably, Apple has been notoriously lacking anything even remotely usable\nand [SwiftASN1](https://github.com/apple/swift-asn1) was out of the question for a couple of reasons.\nMost notably, it did not exist when we started work on Signum. Hence, there was **neither ASN.1 parser, nor encoder on Apple platforms**\nthat was actually usable. In effect: there was no KMP ASN.1 codec in sight, much less a type-safe, user-friendly one.\nAs it stands now, our ASN.1 engine can handle almost anything you throw at it, in some areas even exceeding Bouncy Castle!\ncryptography-kotlin only added basic ASN.1 capabilities over a year after Signum's development started.\n\u003cbr\u003e\nWe are also unaware of any other library offering comprehensive JOSE and COSE data structures based on kotlinx-serialization.\nHence, we implemented those ourselves, with first-class interop to our generic cryptographic data structures.\nWe also support platform-native interop meaning that you can easily convert a Json Web Key to a JCA key or even a `SecKeyRef` on iOS.\n\nHaving actual implementations of cryptographic operations available was only second on our list of priorities. From the\nget-go, it was clear that we wanted the tightest possible platform integration on Android and iOS, including hardware-backed\nstorage of key material and in-hardware execution of cryptographic operations whenever possible.\nWe also needed platform-native attestation capabilities (and so will you sooner or later if you are doing anything\nmission-critical on mobile targets!).\nWhile this approach does limit the number of available cryptographic operations, it also means that all cryptographic operations\ninvolving secrets (e.g. private keys) provide the same security guarantees as platform-native implementations do \u0026mdash;\n**because they are the same** under the hood. Most notably: **hardware-backed private keys\nnever even leave the hardware crypto modules**!\u003cbr\u003e\nThis tight integration and our focus on mobile comes at the cost of the **Supreme KMP crypto provider only supporting JVM,\nAndroid, and iOS**.\ncryptography-kotlin, on the other hand, allows you to perform a wider range of cryptographic functions an all KMP targets,\nMost prominently, it already supports RSA encryption, key stretching, and key derivation, which Signum currently lacks.\nOn the other hand, cryptography-kotlin currently offers neither hardware-backed crypto, nor attestation capabilities.\n\n\u003c/details\u003e\n\nThe following table provides a detailed comparison between Signum and cryptography-kotlin.\n\n|                             | Signum                   | cryptography-kotlin       |\n|-----------------------------|--------------------------|---------------------------|\n| Digital Signatures          | ✔ (ECDSA, RSA)           | ✔ (ECDSA, RSA)            |\n| Symmetric Encryption        | ✔ (AES + ChaChaPoly)     | ✔ (AES)                   |\n| Asymmetric Encryption       | ✔ (RSA)                  | ✔ (RSA)                   |\n| Digest                      | ✔ (SHA-1, SHA-2)         | ✔ (MD5, SHA-1, SHA-2)     |\n| MAC                         | ✔ (HMAC)                 | ✔ (HMAC)                  |\n| Key Agreement               | ✔ (ECDH)                 | ✔ (ECDH)                  |\n| KDF/PRF/KSF                 | ✔ (PBKDF2, HKDF, scrypt) | ✔ (PBKDF2, HKDF)          |\n| Hardware-Backed Crypto      | ✔                        | ✗                         |\n| Attestation                 | ✔                        | ✗                         |\n| Fully-Featured ASN.1 Engine | ✔                        | ✗                         |\n| COSE                        | ✔                        | ✗                         |\n| JOSE                        | ✔                        | ✗                         |\n| Provider Targets            | JVM, Android, iOS        | All KMP-supported targets |\n\n\n## _Supreme_ Demo Reel\nThe _Supreme_ KMP crypto provider works differently from JCA. Configuration is type-safe, more expressive and more concise,\nmeaning you'll end up with less code. **Nothing throws! Do not discard the results returned from any operation!**\n\n### Signature Creation\n\nTo create a signature, obtain a `Signer` instance.\nYou can do this using `Signer.Ephemeral` to create a signer for a throwaway keypair:\n```kotlin\nval signer = Signer.Ephemeral {}.getOrThrow()\nval plaintext = \"You have this.\".encodeToByteArray()\nval signature = signer.sign(plaintext).signature\nprintln(\"Signed using ${signer.signatureAlgorithm}: $signature\")\n```\n\nIf you want to create multiple signatures using the same ephemeral key, you can obtain an `EphemeralKey` instance, then create signers from it:\n```kotlin\nval key = EphemeralKey { rsa {} }.getOrThrow()\nval sha256Signer = key.getSigner { rsa { digest = Digests.SHA256 } }.getOrThrow()\nval sha384Signer = key.getSigner { rsa { digest = Digests.SHA384 } }.getOrThrow()\n```\n\nThe instances can be configured using the configuration DSL.\nAny unspecified parameters use sensible, secure defaults.\n\n#### Platform Signers\n\nOn Android and iOS, signers using the systems' secure key storage can be retrieved.\nTo do this, use `PlatformSigningProvider` (in common code), or interact with `AndroidKeystoreProvider`/`IosKeychainProvider` (in platform-specific code).\n\nNew keys can be created using `createSigningKey(alias: String) { /* configuration */ }`,\nand signers for existing keys can be retrieved using `getSignerForKey(alias: String) { /* configuration */ }`.\n\nFor example, creating an elliptic-curve key over P256, stored in secure hardware, and with key attestation using a random challenge provided by your server, might be done like this:\n```kotlin\nval serverChallenge: ByteArray = TODO(\"This was unpredictably chosen by your server.\")\nPlatformSigningProvider.createSigningKey(alias = \"Swordfish\") {\n  ec {\n    // you don't even need to specify the curve (P256 is the default) but we'll do it for demonstration purposes\n    curve = ECCurve.SECP_256_R_1\n    // you could specify the supported digests explicitly - if you do not, the curve's native digest (for P256, this is SHA256) is supported\n  }\n  // see https://a-sit-plus.github.io/signum/supreme/at.asitplus.signum.supreme.sign/-platform-signing-key-configuration-base/-secure-hardware-configuration/index.html\n  hardware {\n    // you could use PREFERRED if you want the operation to succeed (without hardware backing) on devices that do not support it\n    backing = REQUIRED\n    attestation { challenge = serverChallenge }\n    protection { \n      timeout = 5.seconds\n      factors {\n        biometry = true\n        deviceLock = false\n      }  \n    }\n  }\n}\n```\n\nIf this operation succeeds, it returns a `Signer`. The same `Signer` could later be retrieved using `PlatformSigningProvider.getSignerForKey(alias: String)`.\n\nWhen you use this `Signer` to sign data, the user would be prompted to authorize the signature using an enrolled fingerprint, because that's what you specified when creating the key.\nYou can configure the authentication prompt:\n```kotlin\nval plaintext = \"A message\".encodeToByteArray()\nval signature = signer.sign(plaintext) { \n  unlockPrompt {\n    message = \"Signing a message to Bobby\"\n  }\n}.signature\n```\n... but you cannot change the fact that you configured this key to need biometry. Consider this when creating your keys.\n\nOn the JVM, no native secure hardware storage is available.\nFile-based keystores can be accessed using `JKSProvider { file { /* ... */ } }`.\nOther keystores can be accessed using `JKSProvider { withBackingObject{ /* ... */ } }` or `JksProvider { customAccessor{ /* ... */ } }`.\nFor more details, please refer to the provider's [configuration options](https://a-sit-plus.github.io/signum/dokka/supreme/at.asitplus.signum.supreme.os/-j-k-s-provider-configuration/index.html).\n\n#### Key Attestation\n\nThe Android KeyStore offers key attestation certificates for hardware-backed keys.\nThese certificates are exposed by the signer's `.attestation` property.\n\nFor iOS, Apple does not provide this capability.\nWe instead piggy-back onto iOS App Attestation to provide a home-brew \"key attestation\" scheme.\nThe guarantees are different: you are trusting the OS, not the actual secure hardware; and you are trusting that our library properly interfaces with the OS.\nAttestation types are serializable for transfer, and correspond to those in Indispensable's attestation module.\n\n### Signature Verification\n\nTo verify a signature, obtain a `Verifier` instance using `verifierFor(k: PublicKey)`, either directly on a `SignatureAlgorithm`, or on one of the specialized algorithms (`X509SignatureAlgorithm`, `CoseAlgorithm`, ...).\nA variety of constants, resembling the well-known JCA names, are also available in `SignatureAlgorithm`'s companion.\n\nAs an example, here's how to verify a basic signature using a public key:\n```kotlin\nval publicKey: CryptoPublicKey.EC = TODO(\"You have this and trust it.\")\nval plaintext = \"You want to trust this.\".encodeToByteArray()\nval signature: CryptoSignature = TODO(\"This was sent alongside the plaintext.\")\nval verifier = SignatureAlgorithm.ECDSAwithSHA256.verifierFor(publicKey).getOrThrow()\nval isValid = verifier.verify(plaintext, signature).isSuccess\nprintln(\"Looks good? $isValid\")\n```\n\nOr here's how to validate a X.509 certificate:\n```kotlin\nval rootCert: X509Certificate = TODO(\"You have this and trust it.\")\nval untrustedCert: X509Certificate = TODO(\"You want to verify that this is trustworthy.\")\n\nval verifier = untrustedCert.signatureAlgorithm.verifierFor(rootCert.publicKey).getOrThrow()\nval plaintext = untrustedCert.tbsCertificate.encodeToDer()\nval signature = untrustedCert.signature\nval isValid = verifier.verify(plaintext, signature).isSuccess\nprintln(\"Certificate looks trustworthy: $isValid\")\n```\n\n#### Platform Verifiers\n\nNot every platform supports every algorithm parameter. For example, iOS does not support raw ECDSA verification (of pre-hashed data) for curve P-521.\nIf you use `.verifierFor`, and this happens, the library will transparently substitute a pure-Kotlin implementation.\n\nIf this is not desired, you can specifically enforce a platform verifier by using `.platformVerifierFor`.\nThat way, the library will only ever act as a proxy to platform APIs (JCA, CryptoKit, etc.), and will not use its own implementations.\n\nYou can also further configure the verifier, for example to specify the `provider` to use on the JVM.\nTo do this, pass a DSL configuration lambda to `verifierFor`/`platformVerifierFor`.\n\n```kotlin\nval publicKey: CryptoPublicKey.EC = TODO(\"You have this.\")\nval plaintext: ByteArray = TODO(\"This is the message.\")\nval signature: CryptoSignature.EC = TODO(\"And this is the signature.\")\n    \nval verifier = SignatureAlgorithm.ECDSAwithSHA512\n    .platformVerifierFor(publicKey) { provider = \"BC\"} /* specify BouncyCastle */\n    .getOrThrow()\nval isValid = verifier.verify(plaintext, signature).isSuccess\nprintln(\"Is it trustworthy? $isValid\")\n```\n\n## Symmetric Encryption\nWe currently support ChaCha20-Poly1503, AES-CBC, AES-GCM, AES-KW, AES-ECB, and a very flexible flavour of AES-CBC-HMAC.\nEvery symmetric operation is rooted in an algorithm, since the algorithm defines characteristics, such as nonce requirement,\nauthentication capabilities, etc. Hence, you need to know the algorithm.\n\n### Baseline Usage\nOnce you know decided on an encryption algorithm, encryption itself is straight-forward:\n\n```kotlin\nval secret = \"Top Secret\".encodeToByteArray()\nval secretKey = SymmetricEncryptionAlgorithm.ChaCha20Poly1305.randomKey()\nval encrypted = secretKey.encrypt(secret).getOrThrow(/*handle error*/)\nencrypted.decrypt(secretKey).getOrThrow(/*handle error*/) shouldBe secret\n```\n\nEncrypted data is always structured and the individual components are easily accessible:\n```kotlin\nval nonce = encrypted.nonce\nval ciphertext = encrypted.encryptedData\nval authTag = encrypted.authTag\nval keyBytes = secretKey.secretKey.getOrThrow() /*for algorithms with a dedicated MAC key, there's encryptionKey and macKey*/\n```\n\nDecrypting data received from external sources is also straight-forward:\n```kotlin\nval box = algo.sealedBox.withNonce(nonce).from(ciphertext, authTag).getOrThrow(/*handle error*/)\nbox.decrypt(preSharedKey, /*also pass AAD*/ externalAAD).getOrThrow(/*handle error*/) shouldBe secret\n\n//alternatively, pass raw data:\npreSharedKey.decrypt(nonce, ciphertext, authTag, externalAAD).getOrThrow(/*handle error*/) shouldBe secret\n```\n\n### Custom AES-CBC-HMAC\nSupreme supports AES-CBC with customizable HMAC to provide AEAD.\nThis is supported across all _Supreme_ targets and works as follows:\n```kotlin\nval payload = \"More matter, with less art!\".encodeToByteArray()\n\n//define algorithm parameters\nval algorithm = SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512\n  //with a custom HMAC input calculation function\n  .Custom(32.bytes) { ciphertext, iv, aad -\u003e //A shorter version of RFC 7518\n    aad + iv + ciphertext + aad.size.encodeTo4Bytes()\n  }\n\n//any size is fine, really. omitting the override generates a mac key of the same size as the encryption key\nval key = algorithm.randomKey(macKeyLength = 32.bit)\nval aad = Clock.System.now().toString().encodeToByteArray()\n\nval sealedBox = key.encrypt(\n  payload,\n  authenticatedData = aad,\n).getOrThrow(/*handle error*/)\n\n//because everything is structured, decryption is simple\nval recovered = sealedBox.decrypt(key, aad).getOrThrow(/*handle error*/)\n\nrecovered shouldBe payload //success!\n\n//we can also manually construct the sealed box, if we know the algorithm:\nval reconstructed = algorithm.sealedBox.withNonce(sealedBox.nonce).from(\n  encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/\n  authTag = sealedBox.authTag,\n).getOrThrow()\n\nval manuallyRecovered = reconstructed.decrypt(\n  key,\n  authenticatedData = aad,\n).getOrThrow(/*handle error*/)\n\nmanuallyRecovered shouldBe payload //great success!\n\n//if we just know algorithm and key bytes, we can also construct a symmetric key\nreconstructed.decrypt(\n  algorithm.keyFrom(key.encryptionKey.getOrThrow(), key.macKey.getOrThrow()).getOrThrow(/*handle error*/),\n  aad\n).getOrThrow(/*handle error*/) shouldBe payload //greatest success!\n```\n\n\n## ASN.1 Demo Reel\n\nClasses like `CryptoPublicKey`, `X509Certificate`, `Pkcs10CertificationRequest`, etc. all\nimplement `Asn1Encodable` and their respective companions implement `Asn1Decodable`.\nWhich means that you can do things like parsing and examining certificates, creating CSRs, or transferring key\nmaterial.\n\n### Certificate Parsing\n\n\n```kotlin\nval cert = X509Certificate.decodeFromDer(certBytes)\n\nwhen (val pk = cert.publicKey) {\n    is CryptoPublicKey.EC -\u003e println(\n        \"Certificate with serial no. ${\n            cert.tbsCertificate.serialNumber\n        } contains an EC public key using curve ${pk.curve}\"\n    )\n\n    is CryptoPublicKey.RSA -\u003e println(\n        \"Certificate with serial no. ${\n            cert.tbsCertificate.serialNumber\n        } contains a ${pk.bits.number} bit RSA public key\"\n    )\n}\n\nprintln(\"The full certificate is:\\n${Json { prettyPrint = true }.encodeToString(cert)}\")\n\nprintln(\"Re-encoding it produces the same bytes? ${cert.encodeToDer() contentEquals certBytes}\")\n```\n\nWhich produces the following output:\n\u003e Certificate with serial no. 19821EDCA68C59CF contains an EC public key using curve SECP_256_R_1\n\u003e\n\u003e The full certificate is:\n\n\u003cdetails\u003e\n    \u003csummary\u003e{ \"tbsCertificate\": {…\u003c/summary\u003e\n\n```json\n{\n  \"tbsCertificate\": {\n    \"serialNumber\": \"GYIe3KaMWc8=\",\n    \"signatureAlgorithm\": \"ES384\",\n    \"issuerName\": [\n      {\n        \"type\": \"C\",\n        \"value\": \"13024154\"\n      },\n      {\n        \"type\": \"O\",\n        \"value\": \"133352657075626C696B204F65737465727265696368202876657274726574656E20647572636820424B4120756E6420424D445729\"\n      },\n      {\n        \"type\": \"OU\",\n        \"value\": \"130A542D556D676562756E67\"\n      },\n      {\n        \"type\": \"CN\",\n        \"value\": \"132B542D52657075626C696B2D4F657374657272656963682D41757468656E746966697A696572756E672D3031\"\n      }\n    ],\n    \"validFrom\": \"170D3233303932303132343135305A\",\n    \"validUntil\": \"170D3233303932333132353134395A\",\n    \"subjectName\": [\n      {\n        \"type\": \"C\",\n        \"value\": \"13024154\"\n      },\n      {\n        \"type\": \"O\",\n        \"value\": \"133352657075626C696B204F65737465727265696368202876657274726574656E20647572636820424B4120756E6420424D445729\"\n      },\n      {\n        \"type\": \"OU\",\n        \"value\": \"130A542D556D676562756E67\"\n      },\n      {\n        \"type\": \"CN\",\n        \"value\": \"1340542D42696E64756E67732D5A6572746966696B61742D4157502D3165306436383063656464613439636539313337386462613934326533663432346663663164\"\n      }\n    ],\n    \"publicKey\": {\n      \"type\": \"EC\",\n      \"curve\": \"P-256\",\n      \"x\": \"/wlkNNLhIKmO7tQY1824tD6FSf1/evXzQui1quzsSpw=\",\n      \"y\": \"SggoS/B464PKcHXT9phYxBPOnMEwL/ZC+Q9vZXoxY/g=\"\n    },\n    \"extensions\": [\n      {\n        \"id\": \"1.3.6.1.5.5.7.1.1\",\n        \"value\": \"MDEwLwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwMy5vZXN0ZXJyZWljaC5ndi5hdC9vY3Nw\"\n      },\n      {\n        \"id\": \"2.5.29.14\",\n        \"value\": \"BBRQQnap5sOMkNX+lCHhWGstLkEe6Q==\"\n      },\n      {\n        \"id\": \"2.5.29.35\",\n        \"value\": \"MBaAFAgwoHa6fUvtsBT+jMHkTBAnomXU\"\n      },\n      {\n        \"id\": \"2.5.29.31\",\n        \"value\": \"MDQwMqAwoC6GLGh0dHA6Ly9jcmwzLm9lc3RlcnJlaWNoLmd2LmF0L2NybC9vZWd2LzFhY2Ex\"\n      },\n      {\n        \"id\": \"2.5.29.15\",\n        \"critical\": true,\n        \"value\": \"AwIHgA==\"\n      },\n      {\n        \"id\": \"2.5.29.37\",\n        \"critical\": true,\n        \"value\": \"MAoGCCsGAQUFBwMC\"\n      },\n      {\n        \"id\": \"1.2.40.0.10.2.6.1.1\",\n        \"value\": \"MA2gAwIBAIEGcmVhZGVy\"\n      }\n    ]\n  },\n  \"signatureAlgorithm\": \"ES384\",\n  \"signature\": \"MGQCMEAqUL8qRpPwDi7u1qeEXfJp7Pk4GE4diI9GTSTE/yzFEHJD/o6SRy+lCbJgo58+AwIwCTsMgGdWLIMkN9n1KsuLt6jD/FFF1qzHuj5cTH4JeY0bNwLPxvAUVk3V43pCfMgD\"\n}\n```\n\n\u003c/details\u003e \n\n\u003e Re-encoding it produces the same bytes? true\n\n### Creating a CSR\n\n```kotlin\nval ecPublicKey: ECPublicKey = TODO(\"From platform-specific code\")\nval cryptoPublicKey = CryptoPublicKey.EC.fromJcaPublicKey(ecPublicKey).getOrThrow()\n\nval commonName = \"DefaultCryptoService\"\nval signatureAlgorithm = X509SignatureAlgorithm.ES256\n\n\nval tbsCsr = TbsCertificationRequest(\n    version = 0,\n    subjectName = listOf(RelativeDistinguishedName(AttributeTypeAndValue.CommonName(Asn1String.UTF8(commonName)))),\n    publicKey = cryptoPublicKey\n)\nval signed: ByteArray = TODO(\"pass tbsCsr.encodeToDer() to platform code\")\nval csr = Pkcs10CertificationRequest(tbsCsr, signatureAlgorithm, signed)\n\nprintln(csr.encodeToDer())\n```\n\nWhich results in the following output:\n\n\u003e [3081D9308181020100301F311D301B06035504030C1444656661756C74437279\n\u003e 70746F536572766963653059301306072A8648CE3D020106082A8648CE3D0301\n\u003e 07034200043797E977E359AAABFC9177E7C95FD5B4BE4AC24C4FF13F3233F774\n\u003e E8B65FE5FBA5057513BD076CFFB2E17567AC9BD43737FB6BDF496CC6DCB47194\n\u003e BBE7512F0BA000300A06082A8648CE3D0403020347003044022079D188C09E20\n\u003e C70AFF096B9484DDDE70484485FD551676273A517E818B94644E02206B222905\n\u003e D343C1D6FC9319A364CECA7E67956E4B99D63537E17A9F5D4093D7AE](https://lapo.it/asn1js/#MIHZMIGBAgEAMB8xHTAbBgNVBAMMFERlZmF1bHRDcnlwdG9TZXJ2aWNlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEN5fpd-NZqqv8kXfnyV_VtL5KwkxP8T8yM_d06LZf5fulBXUTvQds_7LhdWesm9Q3N_tr30lsxty0cZS751EvC6AAMAoGCCqGSM49BAMCA0cAMEQCIHnRiMCeIMcK_wlrlITd3nBIRIX9VRZ2JzpRfoGLlGROAiBrIikF00PB1vyTGaNkzsp-Z5VuS5nWNTfhep9dQJPXrg)\n\n### Working with Generic ASN.1 Structures\n\nThe magic shown above is based on a from-scratch 100% KMP implementation of an ASN.1 encoder and parser.\nTo parse any DER-encoded ASN.1 structure, call either:\n\n* `Asn1Element.parse()`, which will consume all bytes and return the first parsed ASN.1 element.\nThis method throws if parsing errors occur or any trailing bytes are left after parsing the first element.\n* `Asn1Element.parseFirst()`, which will try to parse a single toplevel ASN.1 element.\nAny remaining bytes can still be consumed from the iterator, as it will only be advanced to right after the first parsed element.\n* `Asn1Element.parseAll()`, wich consumes all bytes, parses all toplevel ASN.1 elements, and returns them as list.\nThrows on any parsing error.\n\n`Asn1Element`s can encoded by accessing the lazily evaluated `.derEncoded` property.\nEven for parsed elements, this is a true re-encoding. The original bytes are discarded after decoding.\n\n**Note that decoding operations will throw exceptions if invalid data is provided!**\n\nA parsed `Asn1Element` can either be a primitive (whose tag and value can be read) or a structure (like a set or\nsequence) whose child nodes can be processed as desired. Subclasses of `Asn1Element` reflect this:\n\n* `Asn1Primitive`\n  * `Asn1BitString` (for convenience)\n  * `Asn1PrimitiveOctetString` (for convenience)\n* `Asn1Structure`\n    * `Asn1Sequence` and `Asn1SequenceOf`\n    * `Asn1Set` and `Asn1SetOf` (sorting children by default)\n    * `Asn1EncapsulatingOctetString` (tagged as OCTET STRING, containing a valid ASN.1 structure or primitive)\n    * `Asn1ExplicitlyTagged` (user-specified tag + CONTEXT_SPECIFIC + CONSTRUCTED)\n    * `Asn1CustomStructure` (any other CONSTRUCTED tag not fitting the above options. CONSTRUCTED bit may be overridden)\n\nConvenience wrappers exist, to cast to any subtype (e.g. `.asSequence()`). These shorthand functions throw an `Asn1Exception`\nif a cast is not possible.  \nAny complex data structure (such as CSR, public key, certificate, …) implements `Asn1Encodable`, which means you can:\n\n* encapsulate it into an ASN.1 Tree by calling `.encodeToTlv()`\n* directly get a DER-encoded byte array through the `.encodetoDer()` function\n\nA tandem of helper functions is available for primitives (numbers, booleans, string, bigints):\n\n* `encodeToAsn1Primitive` to produce an `Asn1Primitive` that can directly be DER-encoded\n* `encodeToAsn1ContentBytes` to produce the content bytes of a TLV primitive (the _V_ in TLV)\n\nVariations of these exist for `Instant` and `ByteArray`.\n\nCheck out [Asn1Encoding.kt](indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/encoding/Asn1Encoding.kt) for a full\nlist of helper functions.\n\n#### Decoding Values\n\nVarious helper functions exist to facilitate decoding the values contained in `Asn1Primitives`, such as `readInt()`,\nfor example. To also support decoding more complex structures, the companion objects of complex classes (such as certificates, CSRs, …)\nimplement `Asn1Decodable`, which allows for:\n\n* directly parsing DER-encoded byte arrays by calling `.decodeFromDer(bytes)` and `.decodeFromDerHexString`\n* processing an `Asn1Element` by calling `.decodefromTlv(src)`\n\nBoth encoding and decoding functions come in two _safe_ (i.e. non-throwing) variants:\n* `…Safe()` which returns a [KmmResult](https://github.com/a-sit-plus/kmmresult)\n* `…orNull()` which returns null on error\n\nSimilarly to encoding, a tandem of decoding functions exists for primitives:\n* `decodeToXXX` to be invoked on an `Asn1Primitive` to decode a DER-encoded primitive into the target type\n* `decodeFromAsn1ContentBytes` to be invoked on the companion of the target type to decode the content bytes of a TLV primitive (the _V_ in TLV)\n\nHowever, anything can be decoded and tagged at will. Therefore, a generic decoding function exists, which has the\nfollowing signature:\n\n```kotlin\ninline fun \u003creified T\u003e Asn1Primitive.decode(assertTag: Asn1Element.Tag, decode: (content: ByteArray) -\u003e T) \n```\n\nCheck out [Asn1Decoding.kt](indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/encoding/Asn1Decoding.kt) for a full\nlist of helper functions.\n\n#### ASN1 DSL for Creating ASN.1 Structures\n\nWhile it is perfectly possible to manually construct a hierarchy of `Asn1Element` objects, we provide a more convenient\nDSL, which returns an `Asn1Structure`:\n\n```kotlin\nAsn1.Sequence {\n  +ExplicitlyTagged(1uL) {\n    +Asn1Primitive(Asn1Element.Tag.BOOL, byteArrayOf(0x00)) //or +Asn1.Bool(false)\n  }\n  +Asn1.Set {\n    +Asn1.Sequence {\n      +Asn1.SetOf {\n        +PrintableString(\"World\")\n        +PrintableString(\"Hello\")\n      }\n      +Asn1.Set {\n        +PrintableString(\"World\")\n        +PrintableString(\"Hello\")\n        +Utf8String(\"!!!\")\n      }\n\n    }\n  }\n  +Asn1.Null()\n\n  +ObjectIdentifier(\"1.2.603.624.97\")\n\n  +(Utf8String(\"Foo\") withImplicitTag (0xCAFEuL withClass TagClass.PRIVATE))\n  +PrintableString(\"Bar\")\n\n  //fake Primitive\n  +(Asn1.Sequence { +Asn1.Int(42) } withImplicitTag (0x5EUL without CONSTRUCTED))\n\n  +Asn1.Set {\n    +Asn1.Int(3)\n    +Asn1.Int(-65789876543L)\n    +Asn1.Bool(false)\n    +Asn1.Bool(true)\n  }\n  +Asn1.Sequence {\n    +Asn1.Null()\n    +Asn1String.Numeric(\"12345\")\n    +UtcTime(Clock.System.now())\n  }\n} withImplicitTag (1337uL withClass TagClass.APPLICATION)\n```\n\nIn accordance with DER-Encoding, this produces the following ASN.1 structure:\n\n```\nApplication 1337 (9 elem)\n\n    [1] (1 elem)\n        BOOLEAN false\n    SET (1 elem)\n        SEQUENCE (2 elem)\n            SET (2 elem)\n                PrintableString World\n                PrintableString Hello\n            SET (3 elem)\n                UTF8String !!!\n                PrintableString World\n                PrintableString Hello\n    NULL\n    OBJECT IDENTIFIER 1.2.603.624.97\n    Private 51966 (3 byte) Foo\n    PrintableString Bar\n    [94] (3 byte) 02012A\n    SET (4 elem)\n        BOOLEAN false\n        BOOLEAN true\n        INTEGER 3\n        INTEGER (36 bit) -65789876543\n    SEQUENCE (3 elem)\n        NULL\n        NumericString 12345\n        UTCTime 2024-09-16 11:53:51 UTC\n```\n\n## Limitations\n* Only DER encoding and parsing of ASN.1 structures is supported\n* Higher-level abstractions (such as `X509Certificate`) are too lenient in some aspects and\n  too strict in others.\n  For example: DSA-signed certificates will not parse to an instance of `X509Certificate`.\n  At the same time, certificates containing the same extension multiple times will work fine, even though they violate\n  the spec.\n  This is irrelevant in practice, since platform-specific code will perform the actual cryptographic operations on these\n  data structures and complain anyway, if something is off.\n* No OCSP and CRL Checks (though it is perfectly possible to parse this data from a certificate and implement the checks)\n* Number of supported Algorithms is limited to the usual suspects (sorry, no Bernstein curves )-:)\n\n\n## Contributing\nExternal contributions are greatly appreciated! Be sure to observe the contribution guidelines (see [CONTRIBUTING.md](CONTRIBUTING.md)).\nIn particular, external contributions to this project are subject to the A-SIT Plus Contributor License Agreement (see also [CONTRIBUTING.md](CONTRIBUTING.md)).\n\n\n---\n\n| ![eu.svg](docs/docs/assets/eu.svg) \u003cbr\u003e Co\u0026#8209;Funded\u0026nbsp;by\u0026nbsp;the\u003cbr\u003eEuropean\u0026nbsp;Union |   This project has received funding from the European Union’s \u003ca href=\"https://digital-strategy.ec.europa.eu/en/activities/digital-programme\"\u003eDigital Europe Programme (DIGITAL)\u003c/a\u003e, Project 101102655 — POTENTIAL.   |\n|:-----------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n\n\n---\n\n\u003cp align=\"center\"\u003e\nThe Apache License does not apply to the logos, (including the A-SIT logo) and the project/module name(s), as these are the sole property of\nA-SIT/A-SIT Plus GmbH and may not be used in derivative works without explicit permission!\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa-sit-plus%2Fsignum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fa-sit-plus%2Fsignum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa-sit-plus%2Fsignum/lists"}