Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Symmetric Encryption #210

Open
wants to merge 28 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
40bde8c
Symmetric encryption
JesusMcCloud Nov 13, 2024
d5ee5d7
add note to changelog
JesusMcCloud Feb 12, 2025
043280a
Fix RSAorHMAC mess
JesusMcCloud Feb 12, 2025
19fe6a0
Revert "Fix RSAorHMAC mess" and move it to its own branch
JesusMcCloud Feb 13, 2025
72ce790
rid sealedbox of aad
JesusMcCloud Feb 19, 2025
7766f16
address PR Feedback
JesusMcCloud Feb 20, 2025
122de00
better docs
JesusMcCloud Feb 20, 2025
781390e
Update docs/docs/supreme.md
JesusMcCloud Feb 20, 2025
3be2f60
Update indispensable/src/commonMain/kotlin/at/asitplus/signum/indispe…
JesusMcCloud Feb 20, 2025
5032064
address more PR feedback
JesusMcCloud Feb 20, 2025
73e1da4
fixes
JesusMcCloud Feb 20, 2025
cb784e9
fix missing comma
JesusMcCloud Feb 20, 2025
295ce2a
rename CipherParam to PlatformCipher
JesusMcCloud Feb 20, 2025
95ef085
mv MAC -> MessageAuthenticationCode
JesusMcCloud Feb 21, 2025
cf41657
tmp refactor platform code
JesusMcCloud Feb 24, 2025
9d46d78
more cleanups
JesusMcCloud Feb 24, 2025
2895dab
work around KT-75444
JesusMcCloud Feb 24, 2025
3c4694a
fix compile errors
JesusMcCloud Feb 24, 2025
c4fbd97
fix JVM compile errors
JesusMcCloud Feb 25, 2025
94359e1
fix jvm delcaration clash
JesusMcCloud Feb 25, 2025
8e3931e
fix bogus import
JesusMcCloud Feb 25, 2025
1006e80
move key generarion
JesusMcCloud Feb 25, 2025
8f8f4fb
fix ctor visibility
JesusMcCloud Feb 25, 2025
9698315
move JCA extensions
JesusMcCloud Feb 25, 2025
fe3205a
more JCA extensions
JesusMcCloud Feb 25, 2025
6f1a7a3
Make secret keys secret and prepare interfaces for HW-backed symmetric
JesusMcCloud Feb 26, 2025
9c1266a
adapt to new source layout
JesusMcCloud Feb 26, 2025
cb8d856
fix test case
JesusMcCloud Feb 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-jvm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ jobs:
if: success() || failure()
with:
name: JVM All Tests
path: indispensable-asn1/build/test-results/**/TEST*.xml,indispensable/build/test-results/**/TEST*.xml,indispensable-josef/build/test-results/**/TEST*.xml,indispensable-josef/build/test-results/**/TEST*.xml,supreme/build/test-results/**/TEST*.xml
path: indispensable-asn1/build/test-results/**/TEST*.xml,indispensable/build/test-results/**/TEST*.xml,indispensable-josef/build/test-results/**/TEST*.xml,indispensable-cosef/build/test-results/**/TEST*.xml,supreme/build/test-results/**/TEST*.xml
reporter: java-junit
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@
## 3.0

### NEXT
* **Note: We are deprecating and will soon be removing the debug-only serialization for cryptographic datatypes like certificates, public keys, etc.**
* We support robust ASN.1 encoding and mapping from/to JOSE and COSE datatypes and our ASN.1 structures support pretty printing
* -> There is no need for this misleading serialization support for debugging anymore
* `@Serializable` suggests deserialization from JSON, CBOR, etc. works, which was never universally true
* kotlinx-serialization is not the way to go, given how robust Signum's ASN.1 engine has become
* `indispensable-asn1` is only lacking ASN.1 REAL for a proper kotlinx-serialization plugin supporting ASN.1
* Once there, getting native ASN.1 serialization for kotlinx-serialization becomes a no-brainer
* This note will be prepended to the changelog entries for all point releases leading up to and including
Indispensable 4.0.0 / Supreme 1.0.0
* HMAC Support
* Symmetric Encryption
* Supported Algorithms
* AES
* GCM
* CBC-HMAC
* CBC
* ECB
* KW
* ChaCha-Poly1305
* Add algorithm mappings to indispensable-josef **This is a binary-incompatible change**
* `ivLength` and `encryptionKeyLength` now return `BitLength` instead of `Int`
* `text` is now properly called `identifier`
* Moved `HazardousMaterials` annotation from `supreme` to `indispensable`
* Moves `SecretExposure` annotation from `supreme` to `indispensable`
* Expose `SecureRandom` as API dependency in `indispensable`
* Rename `ObjectIdentifier.parse` -> `ObjectIdentifier.decodeFromAsn1ContentBytes` in accordance with other similar functions
* Add dedicated Android targets (SDK 30 /JDK 1.8) to all modules

Expand Down
159 changes: 151 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@

## Kotlin Multiplatform Crypto/PKI Library with ASN1 Parser + Encoder


* **Multiplatform, platform-native crypto** → Check out the included [CMP demo App](app.md) to see it in
action!
* **Multiplatform, platform-native crypto** → Check out the included [CMP demo App](https://a-sit-plus.github.io/signum/app) to see it in
action!
* **ECDSA and RSA Signer and Verifier**
* **Multiplatform ECDH key agreement**
* **Hardware-Backed crypto on Android and iOS**
* **Platform-native attestation on iOS and Android**
* **Configurable biometric authentication on Android and iOS without callbacks or activity passing** (✨Magic!✨)
* **Multiplatform AES**
* **Multiplatform HMAC**
* Public Keys (RSA and EC)
* Private Keys (RSA and EC)
* Algorithm Identifiers (Signatures, Hashing)
Expand All @@ -45,8 +46,7 @@
* Serializability of all ASN.1 classes for debugging **and only for debugging!!!** *Seriously, do not try to deserialize
ASN.1 classes through kotlinx.serialization! Use `decodeFromDer()` and its companions!*
* 100% pure Kotlin BitSet
* Exposes Multibase Encoder/Decoder as an API dependency
including [Matthew Nelson's smashing Base16, Base32, and Base64 encoders](https://github.com/05nelsonm/encoding)
* Exposes Multibase Encoder/Decoder as an API dependency including [Matthew Nelson's smashing Base16, Base32, and Base64 encoders](https://github.com/05nelsonm/encoding)
* **ASN.1 Parser and Encoder including a DSL to generate ASN.1 structures**
* Parse, create, explore certificates, public keys, CSRs, and **arbitrary ASN.1* structures* on all supported platforms
* Powerful, expressive, type-safe ASN.1 DSL on all KMP targets!
Expand Down Expand Up @@ -95,9 +95,72 @@ implementation("at.asitplus.signum:indispensable-cosef:$version")
implementation("at.asitplus.signum:supreme:$supremeVersion")
```

## Rationale
Looking for a KMP cryptography framework, you have undoubtedly come across
[cryptography-kotlin](https://github.com/whyoleg/cryptography-kotlin). So have we and it is a powerful
library, supporting more platforms and more cryptographic operations than Signum Supreme.
This begs the question: Why implement another, incompatible
cryptography framework from scratch? The short answer is: Signum and cryptography-kotlin pursue different goals and priorities.<br>
cryptography-kotlin strives for covering a wide range of targets and a broad range of operations based on a flexible provider architecture.
Signum, on the other hand, focuses on tight platform integration (**including hardware-backed crypto and attestation!**),
and comprehensive ASN.1, JOSE, and COSE support.

<details>
<summary>More…</summary>

Signum was born from the need to have cryptographic data structures available across platforms, such as public keys, signatures,
certificates, CSRs, as well as COSE and JOSE data. Hence, we needed a fully-featured ASN.1 engine and mappings from
X.509 to COSE and JOSE datatypes. We required comprehensive ASN.1 introspection and builder capabilities across platforms.
Most notably, Apple has been notoriously lacking anything even remotely usable
and [SwiftASN1](https://github.com/apple/swift-asn1) was out of the question for a couple of reasons.
Most notably, it did not exist, when we started work on Signum. Hence, there was **neither ASN.1 parser, nor encoder on Apple platforms**
that was actually usable. In effect: there way no KMP ASN.1 codec in sight, much less a type-safe, user-friendly one.
As it stands now, our ASN.1 engine can handle almost anything you throw at it, in some areas even exceeding Bouncy Castle!
cryptography-kotlin only added basic ASN.1 capabilities over a year after Signum's development started.
<br>
We are also unaware of any other library offering comprehensive JOSE and COSE data structures based on kotlinx-serialization.
Hence, we implemented those ourselves, with first-class interop to our generic cryptographic data structures.
We also support platform-native interop meaning that you can easily convert a Json Web Key to a JCA key or even a `SecKeyRef`.

Having actual implementations of cryptographic operations available was only second on our list of priorities. From the
get-go, it was clear that we wanted the tightest possible platform integration on Android and iOS, including hardware-backed
storage of key material and in-hardware execution of cryptographic operations whenever possible.
We also needed platform-native attestation capabilities (and so will you sooner or later, if you are doing anything
mission-critical on mobile targets!).
While this approach does limit the number of available cryptographic operations, it also means that all cryptographic operations
involving secrets (e.g. private keys) provide the same security guarantees as platform-native implementations do &mdash;
**because they are the same** under the hood. Most notably: **hardware-backed private keys
never even leave the hardware crypto modules**!<br>
This tight integration and our focus on mobile comes at the cost of the **Supreme KMP crypto provider only supporting JVM,
Android, and iOS**.
cryptography-kotlin, on the other hand allows you to perform a wider range of cryptographic functions an all KMP targets,
Most prominently, it already supports RSA encryption, key stretching, and key derivation, which Signum currently lacks.
On the other hand, cryptography-kotlin currently offers neither hardware-backed crypto, nor attestation capabilities.

</details>

The following table provides a detailed comparison between Signum and cryptography-kotlin.

| | Signum | cryptography-kotlin |
|-----------------------------|----------------------|---------------------------|
| Digital Signatures | ✔ (ECDSA, RSA) | ✔ (ECDSA, RSA) |
| Symmetric Encryption | ✔ (AES + ChaChaPoly) | ✔ (AES) |
| Public-Key Encryption | ✗ | ✔ (RSA) |
| Digest | ✔ (SHA-1, SHA-2) | ✔ (MD5, SHA-1, SHA-2) |
| MAC | ✔ (HMAC) | ✔ (HMAC) |
| Key Agreement | ✔ (ECDH) | ✔ (ECDH) |
| KDF/PRF/KSF | ✗ | ✔ (PBKDF2, HKDF) |
| Hardware-Backed Crypto | ✔ | ✗ |
| Attestation | ✔ | ✗ |
| Fully-Features ASN.1 Engine | ✔ | ✗ |
| COSE | ✔ | ✗ |
| JOSE | ✔ | ✗ |
| Provider Targets | JVM, Android, iOS | All KMP-supported targets |


## _Supreme_ Demo Reel
The _Supreme_ KMP crypto provider works differently from JCA. Configuration is type-safe, more expressive and you'll
end up less code. **Nothing throws! Do not discard the results returned from any operation!**
The _Supreme_ KMP crypto provider works differently from JCA. Configuration is type-safe, more expressive and more concise,
meaning you'll end up with less code. **Nothing throws! Do not discard the results returned from any operation!**

### Signature Creation

Expand Down Expand Up @@ -232,6 +295,87 @@ val isValid = verifier.verify(plaintext, signature).isSuccess
println("Is it trustworthy? $isValid")
```

## Symmetric Encryption
We currently support ChaCha20-Poly1503, AES-CBC, AES-GCM, AES-KW, AES-ECB, and a very flexible flavour of AES-CBC-HMAC.
Every symmetric operation is rooted in an algorithm, since the algorithm defines characteristics, such as nonce requirement,
authentication capabilities, etc. Hence, you need to know the algorithm.

### Baseline Usage
Once you know decided on an encryption algorithm, encryption itself is straight-forward:

```kotlin
val secret = "Top Secret".encodeToByteArray()
val authenticatedData = "Bottom Secret".encodeToByteArray()
val secretKey = SymmetricEncryptionAlgorithm.ChaCha20Poly1305.randomKey()
val encrypted = secretKey.encrypt(secret, authenticatedData).getOrThrow(/*handle error*/)
encrypted.decrypt(secretKey, authenticatedData).getOrThrow(/*handle error*/) shouldBe secret
```

Encrypted data is always structured and the individual components are easily accessible:
```kotlin
val nonce = encrypted.nonce
val ciphertext = encrypted.encryptedData
val authTag = encrypted.authTag
val keyBytes = secretKey.secretKey /*for algorithms with a dedicated MAC key, there's encryptionKey and macKey*/
```

Decrypting data received from external sources is also straight-forward:
```kotlin
val box = algo.sealedBox.withNonce(nonce).from(ciphertext, authTag).getOrThrow(/*handle error*/)
box.decrypt(preSharedKey, /*also pass AAD*/ externalAAD).getOrThrow(/*handle error*/) shouldBe secret

//alternatively, pass raw data:
preSharedKey.decrypt(nonce, ciphertext,authTag, externalAAD).getOrThrow(/*handle error*/) shouldBe secret
```

### Custom AES-CBC-HMAC
Supreme supports AES-CBC with customizable HMAC to provide AEAD.
This is supported across all _Supreme_ targets and works as follows:
```kotlin
val payload = "More matter, with less art!".encodeToByteArray()

//define algorithm parameters
val algorithm = SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be useful to have a shorthand for common algorithms

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which ones might that be and what would the shorthand be?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure offhand, but just "a good algorithm" (AES 256 GCM?) might be useful to have

ideally we'd have AES-GCM-SIV and expose it here

//with a custom HMAC input calculation function
.Custom(32.bytes) { ciphertext, iv, aad -> //A shorter version of RFC 7518
aad + iv + ciphertext + aad.size.encodeTo4Bytes()
}

//any size is fine, really. omitting the override generates a mac key of the same size as the encryption key
val key = algorithm.randomKey(macKeyLength = 32.bit)
val aad = Clock.System.now().toString().encodeToByteArray()

val sealedBox = key.encrypt(
payload,
authenticatedData = aad,
).getOrThrow(/*handle error*/)

//because everything is structured, decryption is simple
val recovered = sealedBox.decrypt(key, aad).getOrThrow(/*handle error*/)

recovered shouldBe payload //success!

//we can also manually construct the sealed box, if we know the algorithm:
val reconstructed = algorithm.sealedBox.withNonce(sealedBox.nonce).from(
encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/
authTag = sealedBox.authTag,
).getOrThrow()

val manuallyRecovered = reconstructed.decrypt(
key,
authenticatedData = aad,
).getOrThrow(/*handle error*/)

manuallyRecovered shouldBe payload //great success!

//if we just know algorithm and key bytes, we can also construct a symmetric key
reconstructed.decrypt(
algorithm.keyFrom(key.encryptionKey, key.macKey).getOrThrow(/*handle error*/),
aad
).getOrThrow(/*handle error*/) shouldBe payload //greatest success!
```


## ASN.1 Demo Reel

Classes like `CryptoPublicKey`, `X509Certificate`, `Pkcs10CertificationRequest`, etc. all
Expand Down Expand Up @@ -584,4 +728,3 @@ In particular, external contributions to this project are subject to the A-SIT P
The 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
A-SIT/A-SIT Plus GmbH and may not be used in derivative works without explicit permission!
</p>

Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import at.asitplus.cryptotest.theme.LocalThemeIsDark
import at.asitplus.signum.indispensable.CryptoPublicKey
import at.asitplus.signum.indispensable.KeyAgreementPrivateValue
import at.asitplus.signum.indispensable.jsonEncoded
import at.asitplus.signum.supreme.SecretExposure
import at.asitplus.signum.indispensable.SecretExposure
import at.asitplus.signum.supreme.agree.Ephemeral
import at.asitplus.signum.supreme.asKmmResult
import at.asitplus.signum.supreme.os.PlatformSignerConfigurationBase
Expand Down
Loading
Loading