From 40bde8c627589880725a6a797cfa062b912988e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Wed, 13 Nov 2024 15:41:45 +0100 Subject: [PATCH 01/28] Symmetric encryption --- CHANGELOG.md | 15 + README.md | 142 +- docs/docs/features.md | 34 +- docs/docs/index.md | 77 + docs/docs/indispensable.md | 12 + docs/docs/stylesheets/extra.css | 4 + docs/docs/supreme.md | 243 +- indispensable-asn1/build.gradle.kts | 10 +- .../signum/indispensable/cosef/CoseKey.kt | 1 - .../signum/indispensable/josef/JsonWebKey.kt | 29 +- .../indispensable/josef/JweEncryption.kt | 80 +- indispensable/build.gradle.kts | 10 +- .../at/asitplus/signum/HazardousMaterials.kt | 5 + .../at/asitplus/signum/ImplementationError.kt | 3 + .../indispensable/X509SignatureAlgorithm.kt | 3 +- .../asitplus/signum/indispensable/mac/MAC.kt | 40 + .../signum/indispensable/misc/BitLength.kt | 1 + .../indispensable/symmetric/SealedBox.kt | 223 + .../symmetric/SealedBoxCreation.kt | 110 + .../symmetric/SymmetricEncryptionAlgorithm.kt | 527 ++ .../indispensable/symmetric/SymmetricKey.kt | 211 + .../commonTest/kotlin/CryptoSignatureTest.kt | 4 +- .../indispensable/EnumConsistencyTests.kt | 44 + internals/build.gradle.kts | 12 +- .../at/asitplus/signum/internals/Utils.kt | 7 + .../asitplus/signum/internals/InteropUtils.kt | 28 +- supreme/build.gradle.kts | 22 +- .../signum/supreme/symmetric/AES.jca.kt | 52 + .../signum/supreme/symmetric/ChaCha.jca.kt | 20 + .../signum/supreme/symmetric/Encryptor.jca.kt | 157 + .../supreme/hazmat/InternalsAccessors.kt | 2 +- .../at/asitplus/signum/supreme/Throwables.kt | 4 - .../at/asitplus/signum/supreme/mac/MAC.kt | 34 + .../at/asitplus/signum/supreme/sign/Signer.kt | 2 + .../signum/supreme/symmetric/Encryptor.kt | 98 + .../signum/supreme/symmetric/KeyGen.kt | 118 + .../signum/supreme/symmetric/decrypt.kt | 168 + .../supreme/symmetric/discouraged/encrypt.kt | 93 + .../signum/supreme/symmetric/encrypt.kt | 52 + .../at/asitplus/signum/supreme/TestUtils.kt | 8 + .../at/asitplus/signum/supreme/mac/MACTest.kt | 91 + .../sign/EphemeralSignerCommonTests.kt | 4 +- .../signum/supreme/symmetric/00ApiTest.kt | 206 + .../symmetric/00SymmetricAgainstReference.kt | 4684 +++++++++++++++++ .../supreme/symmetric/00SymmetricTest.kt | 976 ++++ .../supreme/hazmat/InternalsAccessors.kt | 2 +- .../signum/supreme/os/IosKeychainProvider.kt | 2 + .../signum/supreme/sign/EphemeralKeysImpl.kt | 1 + .../signum/supreme/symmetric/AES.ios.kt | 180 + .../signum/supreme/symmetric/ChaCha.ios.kt | 55 + .../signum/supreme/symmetric/Encryptor.ios.kt | 80 + supreme/src/iosMain/swift/AEAD.swift | 110 + supreme/src/iosTest/kotlin/Test.kt | 4 +- .../supreme/hazmat/InternalsAccessors.kt | 2 +- .../signum/supreme/os/JKSProviderTest.kt | 2 +- .../signum/supreme/sign/VerifierTests.kt | 6 - .../asitplus/signum/supreme/symmetric/Test.kt | 298 ++ 57 files changed, 9294 insertions(+), 114 deletions(-) create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/HazardousMaterials.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/ImplementationError.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt create mode 100644 indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt create mode 100644 supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt create mode 100644 supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt create mode 100644 supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt create mode 100644 supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt create mode 100644 supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt create mode 100644 supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt create mode 100644 supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt create mode 100644 supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt create mode 100644 supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt create mode 100644 supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/mac/MACTest.kt create mode 100644 supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt create mode 100644 supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricAgainstReference.kt create mode 100644 supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt create mode 100644 supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt create mode 100644 supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt create mode 100644 supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt create mode 100644 supreme/src/iosMain/swift/AEAD.swift create mode 100644 supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index c6ad67b21..130aa1a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ * Rename `ObjectIdentifier.parse` -> `ObjectIdentifier.decodeFromAsn1ContentBytes` in accordance with other similar functions * Add dedicated Android targets (SDK 30 /JDK 1.8) to all modules +* 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` + ### 3.14.0 (Supreme 0.7.0) * Certificate Improvements: diff --git a/README.md b/README.md index e44961629..c4f22adec 100644 --- a/README.md +++ b/README.md @@ -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 +* **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!✨) + * **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) @@ -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! @@ -95,9 +95,71 @@ 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.
+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. + +
+More… + +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. +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. +
+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 — +**because they are the same** under the hood. Most notably: private keys never leave the platform and **hardware-backed private keys +never even leave the hardware crypto modules**!
+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. + +
+ +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 @@ -232,6 +294,63 @@ val isValid = verifier.verify(plaintext, signature).isSuccess println("Is it trustworthy? $isValid") ``` +## Symmetric Encryption +We currently support ChaCha20-Poly1503, AES-CBC, AES-GCM, and a very flexible flavour of AES-CBC-HMAC. +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 + //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*/) + +//The sealed box object is correctly typed: +// * It is a SealedBox.WithIV +// * The generic type arguments indicate that +// * the ciphertext is authenticated +// * Using a dedicated MAC function atop an unauthenticated cipher +// * we can hence access `authenticatedCiphertext` for: +// * authTag +// * authenticatedData +sealedBox.authenticatedData shouldBe aad + +//because everything is structured, decryption is simple +val recovered = sealedBox.decrypt(key).getOrThrow(/*handle error*/) + +recovered shouldBe payload //success! + +//we can also manually construct the sealed box, if we know the algorithm: +val reconstructed = algorithm.sealedBox( + sealedBox.nonce, + encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/ + authTag = sealedBox.authTag, + authenticatedData = sealedBox.authenticatedData +).getOrThrow() + +val manuallyRecovered = reconstructed.decrypt(key).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*/), +).getOrThrow(/*handle error*/) shouldBe payload //greatest success! +``` + + ## ASN.1 Demo Reel Classes like `CryptoPublicKey`, `X509Certificate`, `Pkcs10CertificationRequest`, etc. all @@ -584,4 +703,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!

- diff --git a/docs/docs/features.md b/docs/docs/features.md index 209ee26d8..f4ba2321f 100644 --- a/docs/docs/features.md +++ b/docs/docs/features.md @@ -80,6 +80,7 @@ On the JVM and on Android, supporting more algorithms is rather easy, since Boun and can be used to provide more algorithms than natively supported. However, we aim for tight platform integration, especially wrt. hardware-backed key storage and in-hardware computation of cryptographic operations. We have therefore limited ourselves to what is natively supported on all platforms and most relevant in practice. +Different block cipher modes of operation can be added on request. ## High-Level ASN.1 Abstractions @@ -89,7 +90,7 @@ semantics. The `indispensable` module builds on top of it, adding cryptography-s Combined these two modules provide the following abstractions: | Abstraction | | Remarks | -|------------------------------|::|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|------------------------------|:-:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | X.509 Certificate | ❋ | Only supported algorithms can be parsed as certificate.
Certificates containing other algorithm can be parsed as generic ASN.1 structure. Parser is too lenient in some aspects. | | X.509 Certificate Extension | ❋ | Almost no predefined extensions. Need to be manually created. | | Relative Distinguished Names | ❋ | Rather barebones with little to no validation. | @@ -99,12 +100,37 @@ Combined these two modules provide the following abstractions: | X.509 Signature Algorithm | ❋ | Only supported algorithms. | | Public Keys | ❋ | Only supported types. | | Private Keys | ❋ | Only supported types. | -| ASN.1 Integer | | Supports `Int`, `UInt`, `Long`, `ULong`, and `BigInteger` and custom varint `Asn1Integer`. | -| ASN.1 Time | | Maps from/to kotlinx-datetime `Instant`. Automatic choice of `GENERALIZED` and `UTC` time. | +| ASN.1 Integer | | Supports `Int`, `UInt`, `Long`, `ULong`, and `BigInteger` and custom varint `Asn1Integer`. | +| ASN.1 Time | | Maps from/to kotlinx-datetime `Instant`. Automatic choice of `GENERALIZED` and `UTC` time. | | ASN.1 String | | All types supported, with little to no validation, however. | | ASN.1 Object Identifier | | Only `1` and `2` subtrees supported. `KnownOIDs` is generated from _dumpasn1_. | | ASN.1 Octet String | | Primitive octet strings and encapsulating complex structures natively supported for encoding and parsing. | | ASN.1 Bit String | | Relies on custom `BitSet` implementation, but also supports encoding raw bytes. | !!! info - ❋ marks abstractions added by the `indispensable` module \ No newline at end of file + ❋ marks abstractions added by the `indispensable` module + +## Signum vs. cryptography-kotlin + +Signum and [cryptography-kotlin](https://github.com/whyoleg/cryptography-kotlin) pursue different goals but their features sets have grown to +overlap considerably. +The following table provides overview about what is supported by Signum and cryptography-kotlin, respectively. + +!!! tip inline end + For a rationale behind Signum's design, see the corresponding section in the [project overview](index.md#rationale). + +| | 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 | \ No newline at end of file diff --git a/docs/docs/index.md b/docs/docs/index.md index 337f91844..24a1d23aa 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -17,6 +17,8 @@ types and platform-native functionality related to crypto and PKI applications: * **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) @@ -34,6 +36,7 @@ types and platform-native functionality related to crypto and PKI applications: * **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! + * Parse, create, explore certificates, public keys, CSRs, and **arbitrary ASN.1* structures* on all supported platforms This last bit means that you can share ASN.1-related logic across platforms. The very first bit means that you can create and verify signatures on the JVM, Android and on iOS, using platform-native @@ -76,6 +79,53 @@ implementation("at.asitplus.signum:indispensable-cosef:$version") implementation("at.asitplus.signum:supreme:$supreme_version") ``` + +## 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. + +!!! tip inline end + A feature comparison between Signum and cryptography-kotlin is part of the [feature matrix](features.md#signum-vs-cryptography-kotlin). + +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. + +??? info "More…" + 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. + 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. +
+ 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 — + **because they are the same** under the hood. Most notably: private keys never leave the platform and **hardware-backed private keys + never even leave the hardware crypto modules**!
+ 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. + + + ## Demo Reel This section provides a quick overview to show how this library works. @@ -123,6 +173,33 @@ val isValid = verifier.verify(plaintext, signature).isSuccess println("Looks good? $isValid") ``` +### Symmetric Encryption (Supreme) +We currently support AES-CBC, AES-GCM, and a very flexible flavour of AES-CBC-HMAC. +This is supported across all _Supreme_ targets and works as follows: +```kotlin +val payload = "More matter, with less Art!".encodeToByteArray() + +//define parameters +val algorithm = SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512 +val secretKey = algorithm.randomKey() +val macKey = algorithm.randomKey() +val aad = Clock.System.now().toString().encodeToByteArray() + +val ciphertext = + //You typically chain encryptorFor and encrypt + //because you should never re-use an IV + algorithm.encryptorFor( + secretKey = secretKey, + dedicatedMacKey = macKey, + aad = aad + ).getOrThrow(/*TODO Error handling*/) + .encrypt(payload).getOrThrow(/*TODO Error Handling*/) +val recovered = ciphertext.decrypt(secretKey, macKey) + .getOrThrow(/*TODO Error handling*/) + +recovered shouldBe payload //success! +``` + ### ASN.1 Parsing and Encoding Relevant classes like `CryptoPublicKey`, `X509Certificate`, `Pkcs10CertificationRequest`, etc. all diff --git a/docs/docs/indispensable.md b/docs/docs/indispensable.md index e0f8d748d..4376b599b 100644 --- a/docs/docs/indispensable.md +++ b/docs/docs/indispensable.md @@ -73,6 +73,18 @@ It contains essentials such as: which is implemented by `CryptoPrivateKey.EC` * `KeyAgreementPublicValue` denotes what the name implies. Currently, only ECDH is implemented, hence, there is a single subinterface `KeyAgreementPublicValue.ECDH`, which is implemented by `CryptoPublicKey.EC` +* `MAC` defines the interface for message authentication codes + * `HMAC` defines HMAC for all supported `Digest` algorithms. The [Supreme](supreme.md) KMP crypto provider implements the actual HMAC functionality. +* `SymmetricEncryptionAlgorithm` represents symmetric encryption algorithms. _Indispensable_ currently ships with definitions for AES-CBC, a flexible AES-CBC-HMAC, and AES-GCM, while the [Supreme](supreme.md) KMP crypto provider implements the actual AES functionality. + * `BlockCipher` denotes a BlockCipher + * `WithIV` denotes a Cipher requiring or supporting an initialization vector + * `Unauthenticated` denotes a non-authenticated encryption algorithm + * `Authenticated` denotes an authenticated encryption algorithm + * `Authenticated.WithDedicatedMac` describes an encryption authenticated encryption algorithm based on a non-authenticated one and a dedicated `MAC`, to achieve authenticated encryption +* `Ciphertext` stores ciphertext produced by a symmetric cipher. It has dedicated accessors for every component of the ciphertext, such as `iv` and `encryptedData` + * `Unauthenticated` denotes a ciphertext produced by a `SymmetricEncryptionAlgorithm.Unauthenticated` + * `Authenticated` denotes a ciphertext produced by a `SymmetricEncryptionAlgorithm.Authenticated`, it also contains an `authTag` and, `aad` + * `Authenticated.WithDedicatedMac` restricts `Ciphertext.Authenticated` to ciphertexts produced by a `SymmetricEncryptionAlgorithm.Authenticated.WithDedicatedMac` #### PKI-Related data Structures The `pki` package contains data classes relevant in the PKI context: diff --git a/docs/docs/stylesheets/extra.css b/docs/docs/stylesheets/extra.css index fced9b32f..f96d3e558 100644 --- a/docs/docs/stylesheets/extra.css +++ b/docs/docs/stylesheets/extra.css @@ -72,6 +72,10 @@ font-feature-settings: "kern", "liga"; } +.md-typeset details code { + font-size: .7rem; +} + .linenodiv { padding-top: .12rem; } diff --git a/docs/docs/supreme.md b/docs/docs/supreme.md index d1f237d1a..e225a8c95 100644 --- a/docs/docs/supreme.md +++ b/docs/docs/supreme.md @@ -9,6 +9,8 @@ types and functionality related to crypto and PKI applications: * **Multiplatform ECDSA and RSA Signer and Verifier** → Check out the included [CMP demo App](https://github.com/a-sit-plus/signum/tree/main/demoapp) to see it in action +* **Multiplatform AES and ChaCha20-Poly1503** +* **Multiplatform HMAC** * Biometric Authentication on Android and iOS without Callbacks or Activity Passing** (✨Magic!✨) * Support Attestation on Android and iOS * Multiplatform, hardware-backed ECDH key agreement @@ -28,11 +30,14 @@ implementation("at.asitplus.signum:supreme:$supreme_version") ``` ## Key Design Principles -The Supreme KMP crypto provider works differently than JCA. It uses a `Provider` to manage private key material and create `Signer` instances, +The Supreme KMP crypto provider works differently than the JCA. It uses a `Provider` to manage private key material and create `Signer` instances, and a `Verifier`, that is instantiated on a `SignatureAlgorithm`, taking a `CryptoPublicKey` as parameter. In addition, creating ephemeral keys is a dedicated operation, decoupled from a `Provider`. The actual implementation of cryptographic functionality is delegated to platform-native implementations. +Symmetric encryption follows a similar paradigm, utilising structured representations of ciphertexts and type-safe APIs. +This prevents misuse and mishaps much more effectively than the JCA. + Moreover, the Supreme KMP crypto provider heavily relies on a type-safe DSL for configuration. This type-safety goes so far as to expose platform-specific configuration options only in platform-specific sources, even when the actual calls to some DSL-configurable type reads the same as in common code. @@ -348,6 +353,242 @@ specified when creating the ephemeral key. The Supreme KMP crypto provider introduces a `digest()` extension function on the `Digest` class. For a list of supported algorithms, check out the [feature matrix](features.md#supported-algorithms). +## HMAC Calculation +The Supreme KMP crypto provider introduces a `mac()` extension on the `MAC` class. It takes two arguments: + +* `key` denotes the MAC key +* `msg` represents the payload to compute a MAC for + +For a list of supported algorithms, check out the [feature matrix](features.md#supported-algorithms). + +## Symmetric Encryption + +!!! warning inline end + **NEVER** re-use an IV! Let the Supreme KMP crypto provider auto-generate them! + +Symmetric encryption is implemented both flexible and type-safe. At the same time, the public interface is also rather lean: + +* Reference an algorithm such as `SymmetricEncryptionAlgorithm.ChaCha20Poly1305`. +* Invoke `randomKey()` on it to obtain a `SymmetricKey` object. +* Call `encrypt(data)` on the key and receive a `SealedBox`. + +Decryption is the same straight-forward affair: +Simply call `decrypt(key)` on a `SealedBox` to recover the plaintext. + +!!! tip inline end + All data classes (keys, algorithms, ciphertext, MAC, et.) are part of the _indispensable_ module. + The actual functionality is implemented as extensions in the Supreme KMP crypto provider. + +To minimise the potential for error, everything (algorithms, keys, sealed boxes) makes heavy use of generics. +Hence, a sealed box containing an authenticated ciphertext will only ever accept a symmetric key that is usable for AEAD. +Additional runtime checks ensure that mo mixups can happen. + +### On Type Safety +The API tries to be as type-safe as possible, e.g., it is impossible to specify a dedicated MAC key, or dedicated MAC function for AES-GCM, +and non-authenticated AES-CBC does not even support passing additional authenticated data to the encryption process. +The same constraints apply to the resulting ciphertexts, making it much harder +to accidentally confuse an authenticated encryption algorithm with a non-authenticated one. +Signum uses the term _characteristics_ for these defining properties of the whole symmetric encryption data model. + +#### Characteristics +Cryptographic algorithms have various obvious properties, such as the underlying cipher +(AES and ChaCha branch off `SymmetricEncryptionAlgorithm` at the root level), `name`, and `keySize`. +The broader _characteristics_ also apply to key and ciphertexts (called `SealedBox` in Signum.) +These are: + +* `AuthCapability`: indicating whether it is an authenticated cipher, and if so, how: + * `Unauthenticated`: Non-authenticated encryption algorithm + * `Authenticated`: AEAD algorithm + * `Integrated`: The cipher construction is inherently authenticated + * `WithDedicatedMac`: An encrypt-then-MAC cipher construction (e.g. AES-CBC-HMAC) +* `NonceTrait` indicating whether a nonce is required + * `Without`: No nonce/IV may be fed into the encryption process + * `Required`: A nonce/IV of a length specific to the cipher is required. By default, a nonce will be auto-generated during encryption. +* `KeyType` denoting how the encryption key is structured + * `Integrated`: The key consists of a single byte array, from which encryption key and (if required by the algorithm) a mac key is derived. + * `WithDedicatedMac`: The key consists of an encryption key and a dedicated MAC key to compute the auth tag. + +In addition to runtime checks for matching algorithms and parameters, +algorithms, keys and sealed boxes need matching characteristics to be used with each other. +This approach does come with one caveat: It forces you to know what you are dealing with. +Luckily, there is a very effective remedy: [contracts](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.contracts/). + +#### Contracts +The Supreme KMP crypto provider makes heavy use of contracts, to communicate type information to the compiler. +Every one of the following subsections has their own part on contracts. +
+All contracts can be combined, meaning it is possible to steadily narrow down the properties of an object. + +* `isAuthenticated()` + * if `true`, smart-casts the object's AuthCapability to `AuthCapability.Authenticated<*>` + * if `false` smart-casts the object's AuthCapability to `AuthCapability.Unathenticated` +* `hasDedicatedMac()` + * if `true`, smart-casts the object's + * KeyType to `KeyType.WithDedicatedMac` + * AuthCapability to `AuthCapability.Authenticated.WithDedicatedMac` + * if `false`, smart-casts the object's + * AuthCapability to a union type of `SymmetricEncryptionalgorithm, out I : NonceTrait, out K : KeyType> +``` + +As can be seen, this leaves quite some degrees of freedom, especially for AES-based encryption algorithms, which do exhaust +this space. As of 01-2025, the following algorithms are implemented: + +* `SymmetricEncryptionAlgorithm.ChaCha20Poly1305` +* `SymmetricEncryptionAlgorithm.AES_128.GCM` +* `SymmetricEncryptionAlgorithm.AES_192.GCM` +* `SymmetricEncryptionAlgorithm.AES_256.GCM` +* `SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_256` +* `SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_256` +* `SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_256` +* `SymmetricEncryptionAlgorithm.AES_128.WRAP.RFC3394` +* `SymmetricEncryptionAlgorithm.AES_192.WRAP.RFC3394` +* `SymmetricEncryptionAlgorithm.AES_256.WRAP.RFC3394` +* `SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN` +* `SymmetricEncryptionAlgorithm.AES_192.CBC.PLAIN` +* `SymmetricEncryptionAlgorithm.AES_256.CBC.PLAIN` +* `SymmetricEncryptionAlgorithm.AES_128.ECB` +* `SymmetricEncryptionAlgorithm.AES_192.ECB` +* `SymmetricEncryptionAlgorithm.AES_256.ECB` + +In addition, it is possible to customise AES-CBC-HMAC by freely defining which data gets fed into the MAC. +There are also no constraints on the MAC key length, except that it must not be empty: + +```kotlin +val payload = "More matter, with less art!".encodeToByteArray() + +//define algorithm parameters +val algorithm = SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512 + //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*/) + +//The sealed box object is correctly typed: +// * It is a SealedBox.WithIV +// * The generic type arguments indicate that +// * the ciphertext is authenticated +// * Using a dedicated MAC function atop an unauthenticated cipher +// * we can hence access `authenticatedCiphertext` for: +// * authTag +// * authenticatedData +sealedBox.authenticatedData shouldBe aad + +//because everything is structured, decryption is simple +val recovered = sealedBox.decrypt(key).getOrThrow(/*handle error*/) + +recovered shouldBe payload //success! + +//we can also manually construct the sealed box, if we know the algorithm: +val reconstructed = algorithm.sealedBox( + sealedBox.nonce, + encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/ + authTag = sealedBox.authTag, + authenticatedData = sealedBox.authenticatedData +).getOrThrow() + +val manuallyRecovered = reconstructed.decrypt(key).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*/), +).getOrThrow(/*handle error*/) shouldBe payload //greatest success! +``` + +#### Contracts +Encryption algorithms feature one specific pair on contract-powered functions to determine the type of the cipher. +While this knowledge of this property is purely informational, it might still come in handy: + +* `isBlockCipher()` smart-casts the algorithm to `BlockCipher` if true and `StreamCipher` otherwise. +* `isStreamCipher()` smart-casts the algorithm to `StreamCipher` if true and `BlockCipher` otherwise. + +### Symmetric Keys +Symmetric keys share the same characteristics as symmetric encryption algorithms, to ensure that keys can only be used +with compatible algorithms. + +#### Generating, Importing, and Exporting +The main function for key generation is `SymmetricEncryptionAlgorithm.randomKey()`. +This always works, even without type information. +For algorithms with a dedicated MAC key, an overloaded variant is available too: +```kotlin +SymmetricEncryptionalgorithm.randomKey(macKeyLength: BitLength = preferredMacKeyLength) +``` + +!!! Note inline end + Parameters and properties of the different key types are deliberately named distinctly and the functions are intentionally only available, if enough + type information about the algorithm is available. `hasDedicatedMac` is available on keys too!! + +It is, of course, possible to access the raw key bytes to export the,. Depending on the key type, these are: + +* `encryptionKey` and `macKey` for symmetric keys with a dedicated MAC key +* `secretKey` for symmetric key which only use a single key + +Importing keys is also straight-forward. For encryption algorithms with a single key (and **only** for those), +simply call `SymmetricEncryptionalgorithm.keyFrom(secretKey: ByteArray)`. +In case of an AEAD algorithm with a dedicated MAC key, call `keyFrom(encryptionKey: ByteArray, macKey: ByteArray)`. + + +??? warning "Danger Zone" + It is possible to manually generate a nonce/IV for algorithms that require an IV/nonce. However, you typically don't need this + since IVs/nonces are auto-generated when encrypting. If you insist, you can call `SymmetricEncryptionAlgorithm.randomKey()` + on algorithms that require a nonce. You must, however, explicitly add an opt-in for `@HazardousMaterials`!. +
+ If you really want to feed a manually generated nonce/IV into the encryption process, call `andPredefinedNonce(nonce: ByteArray)` + on a symmetric key object, prior to calling `encrypt(data: ByteArray)`. + +### Sealed Boxes and Decryption +Sealed boxes represent encrypted data. There's more to the ciphertext bytes to encrypted data. Most notably the nonce/IV, for +algorithms which require them. In Signum's data model, the algorithm is also part of a sealed box in order to match characteristics. +Yet, sealed boxes are a bit more relaxed. They don't really care for whether an AEAD algorithm requires a dedicated MAC key +or not. Hence, there is no contract-backed function `hasDedicatedMacKey()`. + + +!!! tip inline end + If you want to decrypt external data and don't need to pass it around as a `SealedBox`, + use `SymmetricKey.decrypt` rather than `SealedBox.decrypt`! + +Decryption is possible in two ways: On the on hand, you can create a `SealedBox` by calling `SymmetricEncryptionAlgorithm.sealedBox()` and then call `.decrypt(key)` on it. +Alternatively, it is possible to directly call `SymmetricKey.decrypt()` and pass nonce/IV (if any), ciphertext bytes, auth tag (if any) and additional authenticated data (if any). +The first variant will allow for arbitrary combinations of characteristics for convenience. +The second option, however, will only allow passing a nonce/IV if the algorithm associated with a symmetric key +has the corresponding characteristic. +The same holds for the auth tag and additional authenticated data. + ## Attestation The Android KeyStore offers key attestation certificates for hardware-backed keys. diff --git a/indispensable-asn1/build.gradle.kts b/indispensable-asn1/build.gradle.kts index c4a0d57d9..e39abb25f 100644 --- a/indispensable-asn1/build.gradle.kts +++ b/indispensable-asn1/build.gradle.kts @@ -274,6 +274,11 @@ publishing { name.set("Bernd Prünster") email.set("bernd.pruenster@a-sit.at") } + developer { + id.set("iaik-jheher") + name.set("Jakob Heher") + email.set("jakob.heher@iaik.tugraz.at") + } developer { id.set("nodh") name.set("Christian Kollmann") @@ -284,11 +289,6 @@ publishing { name.set("Simon Müller") email.set("simon.mueller@a-sit.at") } - developer { - id.set("iaik-jheher") - name.set("Jakob Heher") - email.set("jakob.heher@iaik.tugraz.at") - } } scm { connection.set("scm:git:git@github.com:a-sit-plus/signum.git") diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseKey.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseKey.kt index 265aaf397..c7979ed14 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseKey.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseKey.kt @@ -6,7 +6,6 @@ import at.asitplus.catching import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.SignatureAlgorithm import at.asitplus.signum.indispensable.SpecializedCryptoPublicKey -import at.asitplus.signum.indispensable.asn1.encoding.toTwosComplementByteArray import at.asitplus.signum.indispensable.cosef.CoseKey.Companion.deserialize import at.asitplus.signum.indispensable.cosef.io.Base16Strict import at.asitplus.signum.indispensable.cosef.io.coseCompliantSerializer diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JsonWebKey.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JsonWebKey.kt index 2180c3491..9c6bbdbcd 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JsonWebKey.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JsonWebKey.kt @@ -5,6 +5,7 @@ package at.asitplus.signum.indispensable.josef import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.CryptoPublicKey +import at.asitplus.signum.indispensable.CryptoPublicKey.* import at.asitplus.signum.indispensable.CryptoPublicKey.EC.Companion.fromUncompressed import at.asitplus.signum.indispensable.ECCurve import at.asitplus.signum.indispensable.SpecializedCryptoPublicKey @@ -16,6 +17,7 @@ import at.asitplus.signum.indispensable.io.ByteArrayBase64UrlSerializer import at.asitplus.signum.indispensable.josef.io.JwsCertificateSerializer import at.asitplus.signum.indispensable.josef.io.joseCompliantSerializer import at.asitplus.signum.indispensable.pki.CertificateChain +import at.asitplus.signum.indispensable.symmetric.SymmetricKey import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -36,8 +38,8 @@ data class JsonWebKey( * The "alg" (algorithm) parameter identifies the algorithm intended for * use with the key. The values used should either be registered in the * IANA "JSON Web Signature and Encryption Algorithms" registry - * established by [JWA] or be a value that contains a Collision- - * Resistant Name. The "alg" value is a case-sensitive ASCII string. + * established by [JWA] or be a value that contains a collision-resistant Name. + * The "alg" value is a case-sensitive ASCII string. * Use of this member is OPTIONAL. */ @SerialName("alg") @@ -301,7 +303,7 @@ data class JsonWebKey( } JwkType.RSA -> { - CryptoPublicKey.RSA( + RSA( n = Asn1Integer.fromUnsignedByteArray( n ?: throw IllegalArgumentException("Missing modulus n")), e = Asn1Integer.fromUnsignedByteArray( @@ -309,7 +311,12 @@ data class JsonWebKey( ).apply { jwkId = keyId } } - else -> throw IllegalArgumentException("Illegal key type") + JwkType.SYM -> { + require(k!=null){"Missing symmetric key k"} + algorithm + TODO() + } + null -> throw IllegalArgumentException("Illegal key type") } } @@ -367,6 +374,12 @@ fun CryptoPublicKey.toJsonWebKey(keyId: String? = this.jwkId): JsonWebKey = e = e.magnitude ) } +/** + * Converts a [at.asitplus.signum.indispensable.symmetric.SymmetricKey] to a [JsonWebKey] + */ +fun SymmetricKey<*,*,*>.toJsonWebKey(keyId: String? = this.jwkId): JsonWebKey? { + TODO("Define algorithms an map where possible") +} private const val JWK_ID = "jwkIdentifier" @@ -374,6 +387,14 @@ private const val JWK_ID = "jwkIdentifier" * Holds [JsonWebKey.keyId] when transforming a [JsonWebKey] to a [CryptoPublicKey] */ var CryptoPublicKey.jwkId: String? + get() = additionalProperties[JWK_ID] + set(value) { + value?.also { additionalProperties[JWK_ID] = value } ?: additionalProperties.remove(JWK_ID) + } +/** + * Holds [JsonWebKey.keyId] when transforming a [JsonWebKey] to a [CryptoPublicKey] + */ +var SymmetricKey<*,*,*>.jwkId: String? get() = additionalProperties[JWK_ID] set(value) { value?.also { additionalProperties[JWK_ID] = value } ?: additionalProperties.remove(JWK_ID) diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JweEncryption.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JweEncryption.kt index 3fc8f5e78..b15afd0bb 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JweEncryption.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JweEncryption.kt @@ -1,5 +1,8 @@ package at.asitplus.signum.indispensable.josef +import at.asitplus.signum.indispensable.misc.BitLength +import at.asitplus.signum.indispensable.misc.bit +import at.asitplus.signum.indispensable.symmetric.* import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -15,43 +18,51 @@ import kotlinx.serialization.encoding.Encoder * and also [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518) */ @Serializable(with = JweEncryptionSerializer::class) -enum class JweEncryption(val text: String) { - - A128GCM("A128GCM"), - A192GCM("A192GCM"), - A256GCM("A256GCM"), - A128CBC_HS256("A128CBC-HS256"), - A192CBC_HS384("A192CBC-HS384"), - A256CBC_HS512("A256CBC-HS512") +enum class JweEncryption(val identifier: String, val algorithm: SymmetricEncryptionAlgorithm<*, *, *>) { + + A128GCM("A128GCM", SymmetricEncryptionAlgorithm.AES_128.GCM), + A192GCM("A192GCM", SymmetricEncryptionAlgorithm.AES_192.GCM), + A256GCM("A256GCM", SymmetricEncryptionAlgorithm.AES_256.GCM), + A128CBC_HS256("A128CBC-HS256", SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_256), + A192CBC_HS384("A192CBC-HS384", SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_384), + A256CBC_HS512("A256CBC-HS512", SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_512) ; - val encryptionKeyLength - get() = when (this) { - A128GCM -> 128 - A192GCM -> 192 - A256GCM -> 256 - A128CBC_HS256 -> 256 - A192CBC_HS384 -> 384 - A256CBC_HS512 -> 512 - } - val ivLengthBits - get() = when (this) { - A128GCM, A192GCM, A256GCM -> 96 // GCM: 96 bits - A128CBC_HS256, A192CBC_HS384, A256CBC_HS512 -> 128 // all AES-based - } + @Deprecated("Clumsy name", ReplaceWith("identifier")) + val text get() = identifier /** - * Per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.3), - * where the MAC output bytes need to be truncated to this size for use in JWE. + * For integrated AEAD algorithms, this is the length of the sole key. + * For bolted-on AEAD algorithms with a dedicated MAC key, such as AES-CBC+HMAC, + * this is the **length of the encryption key without the dedicated MAC key**. */ - val macLength: Int? - get() = when (this) { - A128CBC_HS256 -> 16 - A192CBC_HS384 -> 24 - A256CBC_HS512 -> 32 - else -> null + val encryptionKeyLength: BitLength get() = algorithm.keySize + + val ivLength: BitLength + get() = when (algorithm.requiresNonce()) { + true -> algorithm.nonceTrait.length + false -> 0.bit } + + /** + * for integrated AEAD algorithms, this is zero. + * For bolted-on AEAD algorithms with a dedicated MAC, this behaves as the name implies + */ + val dedicatedMacKeyLength: BitLength get() = if (algorithm.hasDedicatedMac()) algorithm.preferredMacKeyLength else 0.bit + + /** + * For integrated AEAD algorithms, this is the length of the sole key. + * For bolted-on AEAD algorithms with a dedicated MAC key, such as AES-CBC+HMAC, + * this is the **length of the encryption key without plus the length dedicated MAC key**. + */ + val combinedEncryptionKeyLength: BitLength get() = encryptionKeyLength + dedicatedMacKeyLength + + /** + * Auth tag length. Should we support unauthenticated encryption algorithms, this would be zero. + */ + val authTagLength: BitLength get() = if (algorithm.isAuthenticated()) algorithm.authTagLength else 0.bit + } object JweEncryptionSerializer : KSerializer { @@ -60,12 +71,17 @@ object JweEncryptionSerializer : KSerializer { PrimitiveSerialDescriptor("JweEncryptionSerializer", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: JweEncryption?) { - value?.let { encoder.encodeString(it.text) } + value?.let { encoder.encodeString(it.identifier) } } override fun deserialize(decoder: Decoder): JweEncryption? { val decoded = decoder.decodeString() - return JweEncryption.entries.firstOrNull { it.text == decoded } + return JweEncryption.entries.firstOrNull { it.identifier == decoded } } } +/** + * Convenience conversion function to get a matching [JweEncryption] algorithm (if any). + */ +fun SymmetricEncryptionAlgorithm<*, *, *>.toJweEncryptionAlgorithm(): JweEncryption? = + JweEncryption.entries.firstOrNull { it.algorithm == this } diff --git a/indispensable/build.gradle.kts b/indispensable/build.gradle.kts index 2166b02a1..a82e9e05b 100644 --- a/indispensable/build.gradle.kts +++ b/indispensable/build.gradle.kts @@ -145,6 +145,11 @@ publishing { name.set("Bernd Prünster") email.set("bernd.pruenster@a-sit.at") } + developer { + id.set("iaik-jheher") + name.set("Jakob Heher") + email.set("jakob.heher@iaik.tugraz.at") + } developer { id.set("nodh") name.set("Christian Kollmann") @@ -155,11 +160,6 @@ publishing { name.set("Simon Müller") email.set("simon.mueller@a-sit.at") } - developer { - id.set("iaik-jheher") - name.set("Jakob Heher") - email.set("jakob.heher@iaik.tugraz.at") - } } scm { connection.set("scm:git:git@github.com:a-sit-plus/signum.git") diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/HazardousMaterials.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/HazardousMaterials.kt new file mode 100644 index 000000000..19aee4051 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/HazardousMaterials.kt @@ -0,0 +1,5 @@ +package at.asitplus.signum + +@RequiresOptIn(message = "Access to potentially hazardous cryptographic functions requires explicit opt-in. Specify @OptIn(HazardousMaterials::class). These accessors are unstable and may change without warning.") +/** This is dangerous. It is exposed if you know what you are doing. You very likely don't actually need it. */ +annotation class HazardousMaterials(val message: String="") diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/ImplementationError.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/ImplementationError.kt new file mode 100644 index 000000000..a81f1bb6a --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/ImplementationError.kt @@ -0,0 +1,3 @@ +package at.asitplus.signum + +class ImplementationError(message: String?=null): Throwable("$message\nThis is an implementation error. Please report this bug at https://github.com/a-sit-plus/signum/issues/new/") \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt index 7b2c8d1b4..43acfc3d3 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt @@ -98,7 +98,8 @@ enum class X509SignatureAlgorithm( ES512, HS512, PS512, RS512 -> Digest.SHA512 } - override val algorithm: SignatureAlgorithm get() = when(this) { + override val algorithm: SignatureAlgorithm + get() = when(this) { ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, null) HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt new file mode 100644 index 000000000..6c7810b84 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt @@ -0,0 +1,40 @@ +package at.asitplus.signum.indispensable.mac + +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.asn1.Identifiable +import at.asitplus.signum.indispensable.asn1.KnownOIDs +import at.asitplus.signum.indispensable.asn1.ObjectIdentifier +import at.asitplus.signum.indispensable.misc.BitLength + +sealed interface MAC { + /** output size of MAC */ + val outputLength: BitLength + + companion object { + val entries: Iterable = HMAC.entries + } +} + +/** + * RFC 2104 HMAC + */ +enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MAC, Identifiable { + SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), + SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), + SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), + SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), + ; + + override fun toString() = "HMAC-$digest" + + companion object { + operator fun invoke(digest: Digest) = when (digest) { + Digest.SHA1 -> SHA1 + Digest.SHA256 -> SHA256 + Digest.SHA384 -> SHA384 + Digest.SHA512 -> SHA512 + } + } + + override val outputLength: BitLength get() = digest.outputLength +} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/BitLength.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/BitLength.kt index 9d38d0d9b..79c494215 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/BitLength.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/BitLength.kt @@ -30,6 +30,7 @@ data class BitLength(val bits: UInt) : Comparable { @Suppress("NOTHING_TO_INLINE", "OVERRIDE_BY_INLINE") override inline fun compareTo(other: BitLength): Int = bits.compareTo(other.bits) + operator fun plus(other: BitLength) = BitLength(bits + other.bits) } @Suppress("NOTHING_TO_INLINE") diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt new file mode 100644 index 000000000..27f885af4 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt @@ -0,0 +1,223 @@ +package at.asitplus.signum.indispensable.symmetric + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +val SealedBox, I, *>.authTag + get() = (this as SealedBox.Authenticated<*, *>).authTag +val SealedBox, I, *>.authenticatedData + get() = (this as SealedBox.Authenticated<*, *>).authenticatedData + +val SealedBox<*, NonceTrait.Required, *>.nonce get() = (this as SealedBox.WithNonce<*, *>).nonce + +/** + * Represents symmetrically encrypted data. This is a separate class to more easily enforce type safety wrt. presence of + * Construct using [SymmetricEncryptionAlgorithm.sealedBoxFrom] + */ +sealed interface SealedBox, I : NonceTrait, K : KeyType> { + val algorithm: SymmetricEncryptionAlgorithm + val encryptedData: ByteArray + + interface Authenticated : + SealedBox, I, K> { + val authTag: ByteArray + val authenticatedData: ByteArray? + } + + interface Unauthenticated : + SealedBox + + /** + * A sealed box without an IV/nonce. + * Construct using [SymmetricEncryptionAlgorithm.sealedBoxFrom] + */ + sealed class WithoutNonce, K : KeyType>( + private val ciphertext: Ciphertext, K> + ) : SealedBox { + + override val algorithm: SymmetricEncryptionAlgorithm = ciphertext.algorithm + override val encryptedData: ByteArray = ciphertext.encryptedData + + override fun toString(): String = "SealedBox.WithoutNonce(ciphertext=$ciphertext)" + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WithoutNonce<*, *>) return false + + if (ciphertext != other.ciphertext) return false + if (algorithm != other.algorithm) return false + if (!encryptedData.contentEquals(other.encryptedData)) return false + + return true + } + + override fun hashCode(): Int { + var result = ciphertext.hashCode() + result = 31 * result + algorithm.hashCode() + result = 31 * result + encryptedData.contentHashCode() + return result + } + + class Unauthenticated + internal constructor(ciphertext: Ciphertext.Unauthenticated) : + WithoutNonce(ciphertext), + SealedBox.Unauthenticated + + class Authenticated + internal constructor(ciphertext: Ciphertext.Authenticated, NonceTrait.Without, SymmetricEncryptionAlgorithm, NonceTrait.Without, K>, K>) : + WithoutNonce, K>(ciphertext), + SealedBox.Authenticated { + override val authTag = ciphertext.authTag + override val authenticatedData = ciphertext.authenticatedData + } + } + + /** + * A sealed box consisting of an [nonce] and the actual [ciphertext]. + * Construct using [SymmetricEncryptionAlgorithm.sealedBoxFrom] + */ + sealed class WithNonce, K : KeyType>( + val nonce: ByteArray, + private val ciphertext: Ciphertext, K> + ) : SealedBox { + + + override val algorithm: SymmetricEncryptionAlgorithm = ciphertext.algorithm + override val encryptedData: ByteArray = ciphertext.encryptedData + + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WithNonce<*, *>) return false + + if (!nonce.contentEquals(other.nonce)) return false + if (ciphertext != other.ciphertext) return false + if (algorithm != other.algorithm) return false + if (!encryptedData.contentEquals(other.encryptedData)) return false + + return true + } + + override fun hashCode(): Int { + var result = nonce.contentHashCode() + result = 31 * result + ciphertext.hashCode() + result = 31 * result + algorithm.hashCode() + result = 31 * result + encryptedData.contentHashCode() + return result + } + + @OptIn(ExperimentalStdlibApi::class) + override fun toString(): String = + "SealedBox.WithNonce(nonce=${nonce.toHexString(HexFormat.UpperCase)}, ciphertext=$ciphertext)" + + class Unauthenticated + internal constructor(nonce: ByteArray, ciphertext: Ciphertext.Unauthenticated) : + WithNonce(nonce, ciphertext), + SealedBox.Unauthenticated + + class Authenticated + internal constructor( + nonce: ByteArray, + ciphertext: Ciphertext.Authenticated, NonceTrait.Required, SymmetricEncryptionAlgorithm, NonceTrait.Required, K>, K> + ) : + WithNonce, K>(nonce, ciphertext), + SealedBox.Authenticated { + override val authTag = ciphertext.authTag + override val authenticatedData = ciphertext.authenticatedData + } + } +} + + +/** + * A generic ciphertext object, referencing the algorithm it was created by. + */ +sealed interface Ciphertext, I : NonceTrait, E : SymmetricEncryptionAlgorithm, K : KeyType> { + val algorithm: E + val encryptedData: ByteArray + + /** + * An authenticated ciphertext, i.e. containing an [authTag], and, optionally [authenticatedData] (_Additional Authenticated Data_) + */ + class Authenticated, I : NonceTrait, E : SymmetricEncryptionAlgorithm, K : KeyType> internal constructor( + override val algorithm: E, + override val encryptedData: ByteArray, + val authTag: ByteArray, + val authenticatedData: ByteArray? + ) : Ciphertext { + + @OptIn(ExperimentalStdlibApi::class) + override fun toString(): String = + "$algorithm Authenticated Ciphertext(encryptedData=${this.encryptedData.toHexString(HexFormat.UpperCase)}, authTag=${ + authTag.toHexString(HexFormat.UpperCase) + }, aad=${authenticatedData?.toHexString(HexFormat.UpperCase)})" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Authenticated<*, *, *, *>) return false + + if (algorithm != other.algorithm) return false + if (!encryptedData.contentEquals(other.encryptedData)) return false + if (!authTag.contentEquals(other.authTag)) return false + if (!authenticatedData.contentEquals(other.authenticatedData)) return false + + return true + } + + override fun hashCode(): Int { + var result = algorithm.hashCode() + result = 31 * result + encryptedData.contentHashCode() + result = 31 * result + authTag.contentHashCode() + result = 31 * result + (authenticatedData?.contentHashCode() ?: 0) + return result + } + + } + + /** + * An Unauthenticated ciphertext + */ + class Unauthenticated internal constructor( + override val algorithm: SymmetricEncryptionAlgorithm, + override val encryptedData: ByteArray, + ) : Ciphertext, KeyType.Integrated> { + + @OptIn(ExperimentalStdlibApi::class) + override fun toString(): String = + "$algorithm Unauthenticated Ciphertext(encryptedData=${ + encryptedData.toHexString(HexFormat.UpperCase) + })" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Unauthenticated<*>) return false + if (algorithm != other.algorithm) return false + if (!encryptedData.contentEquals(other.encryptedData)) return false + return true + } + + override fun hashCode(): Int { + return super.hashCode() + } + } +} + +/**Use to smart-cast this sealed box*/ +@OptIn(ExperimentalContracts::class) +fun , K : KeyType, I : NonceTrait> SealedBox.isAuthenticated(): Boolean { + contract { + returns(true) implies (this@isAuthenticated is SealedBox.Authenticated) + returns(false) implies (this@isAuthenticated is SealedBox.Unauthenticated) + } + return this.algorithm.authCapability is AuthCapability.Authenticated<*> +} + +/**Use to smart-cast this sealed box*/ +@OptIn(ExperimentalContracts::class) +fun , K : KeyType, I : NonceTrait> SealedBox.hasNonce(): Boolean { + contract { + returns(true) implies (this@hasNonce is SealedBox.WithNonce) + returns(false) implies (this@hasNonce is SealedBox.WithoutNonce) + } + return algorithm.nonceTrait is NonceTrait.Required +} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt new file mode 100644 index 000000000..f45ff3c4a --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt @@ -0,0 +1,110 @@ +package at.asitplus.signum.indispensable.symmetric + +import at.asitplus.catching +import at.asitplus.signum.indispensable.misc.bytes +import kotlin.jvm.JvmName + + +/** + * Creates a [SealedBox] matching the characteristics of the [SymmetricEncryptionAlgorithm] it was created for. + * Use this function to load external encrypted data for decryption. + * + * Returns a KmmResult purely for the sake of consistency + */ +@JvmName("sealedBoxUnauthedWithNonce") +fun SymmetricEncryptionAlgorithm.sealedBoxFrom( + nonce: ByteArray, + encryptedData: ByteArray +) = catching { + SealedBox.WithNonce.Unauthenticated( + nonce, + Ciphertext.Unauthenticated( + this, + encryptedData + ) + ) +} + +/** + * Creates a [SealedBox] matching the characteristics of the [SymmetricEncryptionAlgorithm] it was created for. + * Use this function to load external encrypted data for decryption. + * Returns a KmmResult purely for the sake of consistency + */ +fun SymmetricEncryptionAlgorithm.sealedBoxFrom( + encryptedData: ByteArray +) = catching { + SealedBox.WithoutNonce.Unauthenticated( + Ciphertext.Unauthenticated( + this, + encryptedData + ) + ) +} + + +/** + * Creates a [SealedBox] matching the characteristics of the [SymmetricEncryptionAlgorithm] it was created for. + * Use this function to load external encrypted data for decryption. + * + * @return [at.asitplus.KmmResult.failure] on illegal auth tag length + */ +@JvmName("sealedBoxAuthenticatedWith") +fun SymmetricEncryptionAlgorithm, NonceTrait.Required, *>.sealedBoxFrom( + nonce: ByteArray, + encryptedData: ByteArray, + authTag: ByteArray, + authenticatedData: ByteArray? = null +) = catching { + require(authTag.size.bytes == this.authCapability.tagLength) { "Illegal auth tag length! expected: ${authTag.size * 8}, actual: ${this.authCapability.tagLength.bits}" } + when (isIntegrated()) { + false -> SealedBox.WithNonce.Authenticated( + nonce, + authenticatedCipherText(encryptedData, authTag, authenticatedData) + ) + + true -> SealedBox.WithNonce.Authenticated( + nonce, + authenticatedCipherText(encryptedData, authTag, authenticatedData) + ) + } +} + +/** + * Creates a [SealedBox] matching the characteristics of the [SymmetricEncryptionAlgorithm] it was created for. + * Use this function to load external encrypted data for decryption. + * + * @return [at.asitplus.KmmResult.failure] on illegal auth tag length + */ +@JvmName("sealedBoxAuthenticatedWithout") +fun SymmetricEncryptionAlgorithm, NonceTrait.Without, *>.sealedBoxFrom( + encryptedData: ByteArray, + authTag: ByteArray, + authenticatedData: ByteArray? = null +) = catching { + require(authTag.size == this.authCapability.tagLength.bytes.toInt()) { "Illegal auth tag length! expected: ${authTag.size * 8}, actual: ${this.authCapability.tagLength.bits}" } + when (hasDedicatedMac()) { + true -> SealedBox.WithoutNonce.Authenticated( + authenticatedCipherText(encryptedData, authTag, authenticatedData) + ) + + false -> SealedBox.WithoutNonce.Authenticated( + @Suppress("UNCHECKED_CAST") + (this as SymmetricEncryptionAlgorithm).authenticatedCipherText( + encryptedData, + authTag, + authenticatedData + ) + ) + } +} + +private inline fun , reified I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.authenticatedCipherText( + encryptedData: ByteArray, + authTag: ByteArray, + authenticatedData: ByteArray? = null +) = Ciphertext.Authenticated, K>( + this, + encryptedData, + authTag, + authenticatedData +) \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt new file mode 100644 index 000000000..2ba356695 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt @@ -0,0 +1,527 @@ +package at.asitplus.signum.indispensable.symmetric + +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.ImplementationError +import at.asitplus.signum.indispensable.asn1.Identifiable +import at.asitplus.signum.indispensable.asn1.KnownOIDs +import at.asitplus.signum.indispensable.asn1.ObjectIdentifier +import at.asitplus.signum.indispensable.asn1.encoding.encodeTo8Bytes +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.misc.BitLength +import at.asitplus.signum.indispensable.misc.bit +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract +import kotlin.jvm.JvmName + +/** + * Base interface for every symmetric encryption algorithm. A Symmetric encryption algorithm is characterised by: + * * an [authCapability] ([AuthCapability.Unauthenticated], [AuthCapability.Authenticated.Integrated], [AuthCapability.Authenticated.WithDedicatedMac] + * * a [KeyType] ([KeyType.Integrated], [KeyType.WithDedicatedMacKey]) + * * its [nonceTrait] ([NonceTrait.Required], [NonceTrait.Without]) + * * the [keySize] + * * its [name] + */ +sealed interface SymmetricEncryptionAlgorithm, out I : NonceTrait, out K : KeyType> : + Identifiable { + val authCapability: A + + /** Indicates if this algorithm requires a nonce.*/ + val nonceTrait: I + + override fun toString(): String + + companion object { + //ChaCha20Poly1305 is already an object, so we don't need to redeclare here + + val AES_128 = AESDefinition(128.bit) + val AES_192 = AESDefinition(192.bit) + val AES_256 = AESDefinition(256.bit) + + /** + * AES configuration hierarchy + */ + class AESDefinition(val keySize: BitLength) { + + val GCM = AES.GCM(keySize) + val CBC = CbcDefinition(keySize) + + @HazardousMaterials("ECB is almost always insecure!") + val ECB = AES.ECB(keySize) + + val WRAP = WrapDefinition(keySize) + + class WrapDefinition(keySize: BitLength) { + val RFC3394 = AES.WRAP.RFC3394(keySize) + } + + class CbcDefinition(keySize: BitLength) { + @HazardousMaterials("Unauthenticated!") + val PLAIN = AES.CBC.Unauthenticated(keySize) + + /** + * AES-CBC-HMAC as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1) + */ + @OptIn(HazardousMaterials::class) + val HMAC = HmacDefinition(PLAIN) + + /** + * AES-CBC-HMAC as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1) + */ + class HmacDefinition(innerCipher: AES.CBC.Unauthenticated) { + /** + * AES-CBC-HMAC as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1) + */ + val SHA_256 = AES.CBC.HMAC(innerCipher, HMAC.SHA256) + + /** + * AES-CBC-HMAC as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1) + */ + val SHA_384 = AES.CBC.HMAC(innerCipher, HMAC.SHA384) + + /** + * AES-CBC-HMAC as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1) + */ + val SHA_512 = AES.CBC.HMAC(innerCipher, HMAC.SHA512) + + @HazardousMaterials("Insecure hash function!") + val SHA_1 = AES.CBC.HMAC(innerCipher, HMAC.SHA1) + } + } + } + } + + /**Humanly-readable name**/ + val name: String + + /** + * Key length + */ + val keySize: BitLength + + //TODO: why are there ambiguities for sealed box creation? + sealed interface Unauthenticated : + SymmetricEncryptionAlgorithm { + companion object + } + + sealed interface Authenticated, out I : NonceTrait, out K : KeyType> : + SymmetricEncryptionAlgorithm { + interface Integrated : + Authenticated + + interface WithDedicatedMac : + Authenticated, I, KeyType.WithDedicatedMacKey> + } + + sealed interface RequiringNonce, K : KeyType> : + SymmetricEncryptionAlgorithm + + sealed interface WithoutNonce, K : KeyType> : + SymmetricEncryptionAlgorithm + + /** + * Advanced Encryption Standard + */ + sealed class AES>( + modeOfOps: ModeOfOperation, + override val keySize: BitLength + ) : + BlockCipher(modeOfOps, blockSize = 128.bit), + SymmetricEncryptionAlgorithm { + override val name: String = "AES-${keySize.bits} ${modeOfOps.acronym}" + + override fun toString(): String = name + + class GCM internal constructor(keySize: BitLength) : + SymmetricEncryptionAlgorithm.Authenticated.Integrated, + SymmetricEncryptionAlgorithm.RequiringNonce, + AES( + ModeOfOperation.GCM, + keySize + ) { + override val nonceTrait = NonceTrait.Required(96.bit) + override val authCapability = AuthCapability.Authenticated.Integrated(blockSize) + override val oid: ObjectIdentifier = when (keySize.bits) { + 128u -> KnownOIDs.aes128_GCM + 192u -> KnownOIDs.aes192_GCM + 256u -> KnownOIDs.aes256_GCM + else -> throw ImplementationError("AES GCM OID") + } + } + + sealed class WRAP(keySize: BitLength) : + AES(ModeOfOperation.ECB, keySize), + SymmetricEncryptionAlgorithm.WithoutNonce, + SymmetricEncryptionAlgorithm.Unauthenticated { + override val authCapability = AuthCapability.Unauthenticated + + /** + * Key Wrapping as per [RFC 3394](https://datatracker.ietf.org/doc/rfc3394/) + * Key must be at least 16 bytes and a multiple of 8 bytes + */ + class RFC3394(keySize: BitLength) : WRAP(keySize) { + override val nonceTrait = NonceTrait.Without + override val oid: ObjectIdentifier = when (keySize.bits) { + 128u -> KnownOIDs.aes128_wrap + 192u -> KnownOIDs.aes192_wrap + 256u -> KnownOIDs.aes256_wrap + else -> throw ImplementationError("AES WRAP RFC3394 OID") + } + } + //on request, add RFC 5649 key wrapping here. requires manual work, though + } + + @HazardousMaterials("ECB is almost always insecure!") + class ECB internal constructor(keySize: BitLength) : + AES(ModeOfOperation.ECB, keySize), + SymmetricEncryptionAlgorithm.WithoutNonce, + SymmetricEncryptionAlgorithm.Unauthenticated { + override val nonceTrait = NonceTrait.Without + override val authCapability = AuthCapability.Unauthenticated + override val oid: ObjectIdentifier = when (keySize.bits) { + 128u -> KnownOIDs.aes128_ECB + 192u -> KnownOIDs.aes192_ECB + 256u -> KnownOIDs.aes256_ECB + else -> throw ImplementationError("AES ECB OID") + } + } + + sealed class CBC>(keySize: BitLength) : + AES(ModeOfOperation.CBC, keySize) { + override val nonceTrait = NonceTrait.Required(128u.bit) + override val oid: ObjectIdentifier = when (keySize.bits) { + 128u -> KnownOIDs.aes128_CBC + 192u -> KnownOIDs.aes192_CBC + 256u -> KnownOIDs.aes256_CBC + else -> throw ImplementationError("AES CBC OID") + } + + class Unauthenticated( + keySize: BitLength + ) : CBC(keySize), + SymmetricEncryptionAlgorithm.RequiringNonce, + SymmetricEncryptionAlgorithm.Unauthenticated { + override val authCapability = AuthCapability.Unauthenticated + override val name = super.name + " Plain" + } + + /** + * AEAD-capabilities bolted onto AES-CBC + */ + class HMAC + private constructor( + innerCipher: Unauthenticated, + mac: at.asitplus.signum.indispensable.mac.HMAC, + dedicatedMacInputCalculation: DedicatedMacInputCalculation, + dedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation, + tagLength: BitLength + ) : SymmetricEncryptionAlgorithm.Authenticated.WithDedicatedMac, + SymmetricEncryptionAlgorithm.RequiringNonce, KeyType.WithDedicatedMacKey>, + CBC>( + innerCipher.keySize + ) { + constructor(innerCipher: Unauthenticated, mac: at.asitplus.signum.indispensable.mac.HMAC) : this( + innerCipher, + mac, + DefaultDedicatedMacInputCalculation, + DefaultDedicatedMacAuthTagTransformation, + tagLength = BitLength(mac.outputLength.bits / 2u) + ) + + override val authCapability = + AuthCapability.Authenticated.WithDedicatedMac( + innerCipher, + mac, + innerCipher.keySize, + tagLength, + dedicatedMacInputCalculation, + dedicatedMacAuthTagTransformation + ) + override val name = super.name + " $mac" + + /** + * Instantiates a new [CBC.HMAC] instance with + * * custom [tagLength] + * * custom [DedicatedMacInputCalculation] + */ + fun Custom( + tagLength: BitLength, + dedicatedMacInputCalculation: DedicatedMacInputCalculation + ) = Custom (tagLength,DefaultDedicatedMacAuthTagTransformation, dedicatedMacInputCalculation) + + /** + * Instantiates a new [CBC.HMAC] instance with + * * custom [tagLength] + * * custom [dedicatedMacAuthTagTransformation] + * * custom [DedicatedMacInputCalculation] + */ + fun Custom( + tagLength: BitLength, + dedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation, + dedicatedMacInputCalculation: DedicatedMacInputCalculation + ) = + CBC.HMAC( + authCapability.innerCipher as Unauthenticated, + authCapability.mac, + dedicatedMacInputCalculation, + dedicatedMacAuthTagTransformation, + tagLength + ) + } + } + } + + /** + * ChaCha20 with Poly-1305 AEAD stream cipher + */ + object ChaCha20Poly1305 : + StreamCipher(), + SymmetricEncryptionAlgorithm.Authenticated.Integrated, + SymmetricEncryptionAlgorithm.RequiringNonce { + override val authCapability = AuthCapability.Authenticated.Integrated(128u.bit) + override val nonceTrait = NonceTrait.Required(96u.bit) + override val name: String = "ChaCha20-Poly1305" + override fun toString() = name + override val keySize = 256u.bit + override val oid = KnownOIDs.chaCha20Poly1305 + } +} + +/** + * Defines whether a cipher is authenticated or not + */ +sealed interface AuthCapability { + /** + * accessor to get the key type due to type erasure + */ + val keyType: K + + /** + * Indicates an authenticated cipher + */ + sealed class Authenticated(val tagLength: BitLength, override val keyType: K) : AuthCapability { + + /** + * An authenticated cipher construction that is inherently authenticated and thus permits no dedicated MAC key + */ + class Integrated(tagLen: BitLength) : Authenticated(tagLen, KeyType.Integrated) + + /** + * An authenticated cipher construction based on an unauthenticated cipher with a dedicated MAC function, requiring a dedicated MAC key. + * _Encrypt-then-MAC_ + */ + class WithDedicatedMac( + /** + * The inner unauthenticated cipher + */ + + val innerCipher: SymmetricEncryptionAlgorithm, + /** + * The mac function used to provide authenticated encryption + */ + val mac: M, + + /** + * The preferred length pf the MAC key. Can be overridden during key generation and is unconstrained. + */ + val preferredMacKeyLength: BitLength, + + tagLen: BitLength, + + /** + * Specifies how the inputs to the MAC are to be encoded/processed + */ + val dedicatedMacInputCalculation: DedicatedMacInputCalculation, + + /** + * Specifies how the inputs to the MAC are to be encoded/processed + */ + val dedicatedMacAuthTagTransform: DedicatedMacAuthTagTransformation + ) : Authenticated(tagLen, KeyType.WithDedicatedMacKey) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WithDedicatedMac<*, *>) return false + if (!super.equals(other)) return false + + if (innerCipher != other.innerCipher) return false + if (mac != other.mac) return false + if (preferredMacKeyLength != other.preferredMacKeyLength) return false + if (dedicatedMacInputCalculation != other.dedicatedMacInputCalculation) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + innerCipher.hashCode() + result = 31 * result + mac.hashCode() + result = 31 * result + preferredMacKeyLength.hashCode() + result = 31 * result + dedicatedMacInputCalculation.hashCode() + return result + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Authenticated<*>) return false + + if (tagLength != other.tagLength) return false + if (keyType != other.keyType) return false + + return true + } + + override fun hashCode(): Int { + var result = tagLength.hashCode() + result = 31 * result + keyType.hashCode() + return result + } + } + + /** + * Indicates an unauthenticated cipher + */ + object Unauthenticated : AuthCapability { + override val keyType = KeyType.Integrated + } +} + +/** + * Typealias defining the signature of the lambda for processing the MAC output into an auth tag. + */ +typealias DedicatedMacAuthTagTransformation = AuthCapability.Authenticated.WithDedicatedMac<*, *>.(macOutput: ByteArray) -> ByteArray + + +/** + * The default dedicated mac output transform as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1), + * taking the first [authTagLength] many bytes of the MAC output as auth tag. + */ +val DefaultDedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation = + fun AuthCapability.Authenticated.WithDedicatedMac<*, *>.( + macOutput: ByteArray + ): ByteArray = macOutput.take(tagLength.bytes.toInt()).toByteArray() + +/** + * Typealias defining the signature of the lambda for defining a custom MAC input calculation scheme. + */ +typealias DedicatedMacInputCalculation = MAC.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray + +/** + * The default dedicated mac input calculation as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1), authenticating all inputs: + * `AAD || IV || Ciphertext || AAD Length`, where AAD_length is a 64 bit big-endian representation of the aad length in bits + */ +val DefaultDedicatedMacInputCalculation: DedicatedMacInputCalculation = + fun MAC.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = + aad + iv + ciphertext + (aad.size.toLong()*8L).encodeTo8Bytes() + +/** + * Marker, indicating whether a symmetric encryption algorithms requires or prohibits the use of a nonce/IV + */ +sealed interface NonceTrait { + /** + * Indicates that a cipher requires an initialization vector + */ + class Required(val length: BitLength) : NonceTrait + + object Without : NonceTrait +} + + +/** + * Marker interface indicating a block cipher. Purely informational + */ +abstract class BlockCipher, I : NonceTrait, K : KeyType>( + val mode: ModeOfOperation, + val blockSize: BitLength +) : SymmetricEncryptionAlgorithm { + + enum class ModeOfOperation(val friendlyName: String, val acronym: String) { + GCM("Galois Counter Mode", "GCM"), + CBC("Cipherblock Chaining Mode", "CBC"), + ECB("Electronic Codebook Mode", "ECB"), + } +} + +/** + * Marker interface indicating a block cipher. Purely informational + */ +abstract class StreamCipher, I : NonceTrait, K : KeyType> : SymmetricEncryptionAlgorithm + + +//Here come the contracts! + +/**Use to smart-cast this algorithm*/ +@JvmName("isBlockCipherAlias") +@OptIn(ExperimentalContracts::class) +fun , I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.isBlockCipher(): Boolean { + contract { + returns(true) implies (this@isBlockCipher is BlockCipher) + returns(false) implies (this@isBlockCipher is StreamCipher) + } + return this is BlockCipher<*, *, *> +} + +/**Use to smart-cast this algorithm*/ +@JvmName("isStreamCipherAlias") +@OptIn(ExperimentalContracts::class) +fun , I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.isStreamCipher(): Boolean { + contract { + returns(true) implies (this@isStreamCipher is StreamCipher) + returns(false) implies (this@isStreamCipher is BlockCipher) + } + return this is StreamCipher<*, *, *> +} + +/**Use to smart-cast this algorithm*/ +@JvmName("isAuthenticatedAlias") +@OptIn(ExperimentalContracts::class) +fun SymmetricEncryptionAlgorithm<*, I, K>.isAuthenticated(): Boolean { + contract { + returns(true) implies (this@isAuthenticated is SymmetricEncryptionAlgorithm.Authenticated<*, I, K>) + returns(false) implies (this@isAuthenticated is SymmetricEncryptionAlgorithm.Unauthenticated) + } + return this.authCapability is AuthCapability.Authenticated<*> +} + +/**Use to smart-cast this algorithm*/ +@JvmName("hasDedicatedMacAlias") +@OptIn(ExperimentalContracts::class) +fun SymmetricEncryptionAlgorithm<*, I, *>.hasDedicatedMac(): Boolean { + contract { + returns(true) implies (this@hasDedicatedMac is SymmetricEncryptionAlgorithm.Authenticated.WithDedicatedMac<*, I>) + returns(false) implies ( + (this@hasDedicatedMac is SymmetricEncryptionAlgorithm.Unauthenticated + || this@hasDedicatedMac is SymmetricEncryptionAlgorithm.Authenticated.Integrated)) + } + return this.authCapability is AuthCapability.Authenticated.WithDedicatedMac<*, *> +} + +/**Use to smart-cast this algorithm*/ +@JvmName("requiresNonceAlias") +@OptIn(ExperimentalContracts::class) +fun , K : KeyType> SymmetricEncryptionAlgorithm.requiresNonce(): Boolean { + contract { + returns(true) implies (this@requiresNonce is SymmetricEncryptionAlgorithm.RequiringNonce) + returns(false) implies (this@requiresNonce is SymmetricEncryptionAlgorithm.WithoutNonce) + } + return this.nonceTrait is NonceTrait.Required +} + +/**Use to smart-cast this algorithm*/ +@JvmName("isIntegrated") +@OptIn(ExperimentalContracts::class) +fun SymmetricEncryptionAlgorithm, I, *>.isIntegrated(): Boolean { + contract { + returns(true) implies (this@isIntegrated is SymmetricEncryptionAlgorithm.Authenticated.Integrated) + returns(false) implies (this@isIntegrated is SymmetricEncryptionAlgorithm.Authenticated.WithDedicatedMac<*, I>) + + } + return this.authCapability is AuthCapability.Authenticated.Integrated +} + + +val SymmetricEncryptionAlgorithm<*, NonceTrait.Required, *>.nonceLength: BitLength get() = nonceTrait.length +val SymmetricEncryptionAlgorithm, *, KeyType.WithDedicatedMacKey>.preferredMacKeyLength: BitLength get() = authCapability.preferredMacKeyLength +val SymmetricEncryptionAlgorithm, *, *>.authTagLength: BitLength get() = authCapability.tagLength \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt new file mode 100644 index 000000000..071bc75d8 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -0,0 +1,211 @@ +package at.asitplus.signum.indispensable.symmetric + +import at.asitplus.signum.HazardousMaterials +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +sealed interface KeyType { + object Integrated : KeyType + object WithDedicatedMacKey : KeyType +} + +/** + * Symmetric encryption key. Can only be used for the specified [algorithm]. + */ +sealed interface SymmetricKey, I : NonceTrait, K : KeyType> { + val algorithm: SymmetricEncryptionAlgorithm + + /** + * This is meant for storing additional properties, which may be relevant for certain use cases. + * For example, Json Web Keys or COSE keys may define an arbitrary key IDs. + * This is not meant for Algorithm parameters! If an algorithm needs parameters, the implementing classes should be extended + */ + //must be serializable, therefore + val additionalProperties: MutableMap + + interface Authenticating, I : NonceTrait, K : KeyType> : SymmetricKey + interface NonAuthenticating : SymmetricKey + + interface RequiringNonce, K : KeyType> : SymmetricKey + interface WithoutNonce, K : KeyType> : SymmetricKey + + /** + * Self-Contained encryption key, i.e. a single byte array is sufficient + */ + sealed class Integrated, I : NonceTrait> + @HazardousMaterials("Does not check whether key size matched algorithm! Useful for testing, but not production!") + + constructor( + override val algorithm: SymmetricEncryptionAlgorithm, + /** + * The actual encryption key bytes + */ + val secretKey: ByteArray + ) : + SymmetricKey { + sealed class Authenticating( + algorithm: SymmetricEncryptionAlgorithm, + secretKey: ByteArray + ) : Integrated(algorithm, secretKey), + SymmetricKey.Authenticating { + + class RequiringNonce( + algorithm: SymmetricEncryptionAlgorithm, + secretKey: ByteArray, + override val additionalProperties: MutableMap = mutableMapOf() + ) : Authenticating( + algorithm, secretKey + ), SymmetricKey.RequiringNonce + + class WithoutNonce( + algorithm: SymmetricEncryptionAlgorithm, + secretKey: ByteArray, + override val additionalProperties: MutableMap = mutableMapOf() + ) : Authenticating( + algorithm, secretKey + ), SymmetricKey.WithoutNonce + } + + sealed class NonAuthenticating( + algorithm: SymmetricEncryptionAlgorithm, + secretKey: ByteArray + ) : Integrated(algorithm, secretKey), SymmetricKey.NonAuthenticating { + class RequiringNonce( + algorithm: SymmetricEncryptionAlgorithm, + secretKey: ByteArray, + override val additionalProperties: MutableMap = mutableMapOf() + ) : NonAuthenticating(algorithm, secretKey) + + class WithoutNonce( + algorithm: SymmetricEncryptionAlgorithm, + secretKey: ByteArray, + override val additionalProperties: MutableMap = mutableMapOf() + ) : NonAuthenticating(algorithm, secretKey) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Integrated<*, *>) return false + + if (algorithm != other.algorithm) return false + if (!secretKey.contentEquals(other.secretKey)) return false + + return true + } + + override fun hashCode(): Int { + var result = algorithm.hashCode() + result = 31 * result + secretKey.contentHashCode() + return result + } + } + + + /** + * Encryption key with dedicated MAC key. Used for non-authenticated ciphers that use an external MAC function to + * bolt on AEAD capabilities, such as [SymmetricEncryptionAlgorithm.AES.GCM] + */ + sealed class WithDedicatedMac + @HazardousMaterials("Does not check whether key size matched algorithm! Useful for testing, but not production!") + /** + * Do not invoke directly! use Supreme's `SymmetricEncryptionAlgorithm.randomKey()` and `SymmetricEncryptionAlgorithm.encryptionKeyFrom(bytes)` + * This constructor does not check for matching key sizes to allow for testing error cases! + */ + constructor( + override val algorithm: SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>, + /** + * The actual encryption key bytes + */ + val encryptionKey: ByteArray, + /** + * The actual dedicated MAX key bytes + */ + val macKey: ByteArray + ) : SymmetricKey, I, KeyType.WithDedicatedMacKey>, + SymmetricKey.Authenticating, I, KeyType.WithDedicatedMacKey> { + class RequiringNonce( + algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, + secretKey: ByteArray, + dedicatedMacKey: ByteArray, + override val additionalProperties: MutableMap = mutableMapOf() + ) : WithDedicatedMac( + algorithm, secretKey, dedicatedMacKey + ), + SymmetricKey.RequiringNonce, KeyType.WithDedicatedMacKey> + + class WithoutNonce( + algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>, + secretKey: ByteArray, + dedicatedMacKey: ByteArray, + override val additionalProperties: MutableMap = mutableMapOf() + ) : WithDedicatedMac( + algorithm, secretKey, dedicatedMacKey + ), + SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WithDedicatedMac<*>) return false + + if (algorithm != other.algorithm) return false + if (!encryptionKey.contentEquals(other.encryptionKey)) return false + if (!macKey.contentEquals(other.macKey)) return false + if (additionalProperties != other.additionalProperties) return false + + return true + } + + override fun hashCode(): Int { + var result = algorithm.hashCode() + result = 31 * result + encryptionKey.contentHashCode() + result = 31 * result + macKey.contentHashCode() + return result + } + } +} + +/**Use to smart cast*/ +@OptIn(ExperimentalContracts::class) +fun , K : KeyType, I : NonceTrait> SymmetricKey.isAuthenticated(): Boolean { + contract { + returns(true) implies (this@isAuthenticated is SymmetricKey.Authenticating) + returns(false) implies (this@isAuthenticated is SymmetricKey.NonAuthenticating) + } + return this.algorithm.authCapability is AuthCapability.Authenticated<*> +} + +/**Use to smart cast*/ +@OptIn(ExperimentalContracts::class) +fun , I : NonceTrait> SymmetricKey.isIntegrated(): Boolean { + contract { + returns(true) implies (this@isIntegrated is SymmetricKey.Integrated.Authenticating) + returns(false) implies (this@isIntegrated is SymmetricKey.WithDedicatedMac) + } + return this.hasDedicatedMacKey() +} + +/**Use to smart cast*/ +@OptIn(ExperimentalContracts::class) +fun , I : NonceTrait> SymmetricKey.hasDedicatedMacKey(): Boolean { + contract { + returns(true) implies (this@hasDedicatedMacKey is SymmetricKey.WithDedicatedMac) + returns(false) implies (this@hasDedicatedMacKey is SymmetricKey.Integrated) + } + return this is SymmetricKey.WithDedicatedMac +} + +/**Use to smart cast*/ +@OptIn(ExperimentalContracts::class) +fun , K : KeyType> SymmetricKey.requiresNonce(): Boolean { + contract { + returns(true) implies (this@requiresNonce is SymmetricKey.RequiringNonce) + returns(false) implies (this@requiresNonce is SymmetricKey.WithoutNonce) + } + return algorithm.nonceTrait is NonceTrait.Required +} + +/** + * The actual encryption key bytes + */ +val , I : NonceTrait> SymmetricKey.secretKey get() = (this as SymmetricKey.Integrated).secretKey +val SymmetricKey, I, out KeyType.WithDedicatedMacKey >.encryptionKey get() = (this as SymmetricKey.WithDedicatedMac).encryptionKey \ No newline at end of file diff --git a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt index 36e9753f0..a17391bbb 100644 --- a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt +++ b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt @@ -41,7 +41,7 @@ class CryptoSignatureTest : FreeSpec({ rsa1.hashCode() shouldNotBe rsa3.hashCode() val ec4 = ec3.guessCurve() - ec4.scalarByteLength shouldBe ECCurve.values().minOf { it.scalarLength.bytes } + ec4.scalarByteLength shouldBe ECCurve.entries.minOf { it.scalarLength.bytes } ec4 shouldBe ec4 ec4 shouldBe ec3 ec4.hashCode() shouldBe ec4.hashCode() @@ -81,7 +81,7 @@ class CryptoSignatureTest : FreeSpec({ sig3.scalarByteLength shouldBe sig1.scalarByteLength sig3.rawByteArray shouldBe encoded - val r2 = BigInteger.ONE.shl(ECCurve.values().maxOf { it.scalarLength.bits }.toInt() + 1) + val r2 = BigInteger.ONE.shl(ECCurve.entries.maxOf { it.scalarLength.bits }.toInt() + 1) shouldThrow { CryptoSignature.EC.fromRS(r2, s).guessCurve() } } }) diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt new file mode 100644 index 000000000..fb0453414 --- /dev/null +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt @@ -0,0 +1,44 @@ +package at.asitplus.signum.indispensable + +import at.asitplus.signum.indispensable.mac.MAC +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import java.util.Stack +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.memberProperties + +inline fun FreeSpec.enumConsistencyTest() { + T::class.simpleName!! { + val listed = T::class.companionObject!!.let { companion -> + @Suppress("UNCHECKED_CAST") + (companion.memberProperties.find { it.name == "entries" }.shouldNotBeNull() + as KProperty1).get(companion.objectInstance!!) + }.shouldBeInstanceOf>() + + val discovered = mutableSetOf() + val queue = Stack>().also { it.push(T::class) } + while (!queue.empty()) { + val cls = queue.pop() + if (cls.java.isEnum) { + discovered.addAll(cls.java.enumConstants!!) + continue + } + val o = cls.objectInstance + if (o != null) { + discovered.add(o) + continue + } + cls.sealedSubclasses.forEach(queue::push) + } + + listed.toSet() shouldBe discovered.toSet() + } +} + +class EnumConsistencyTests : FreeSpec({ + enumConsistencyTest() +}) \ No newline at end of file diff --git a/internals/build.gradle.kts b/internals/build.gradle.kts index c997ffc6e..ca9b843bf 100644 --- a/internals/build.gradle.kts +++ b/internals/build.gradle.kts @@ -81,7 +81,7 @@ publishing { withType { if (this.name != "relocation") artifact(javadocJar) pom { - name.set("Internals") + name.set("Indispensable Internals") description.set("Kotlin Multiplatform Crypto Library, Internal Shared Helpers") url.set("https://github.com/a-sit-plus/signum") licenses { @@ -96,6 +96,11 @@ publishing { name.set("Bernd Prünster") email.set("bernd.pruenster@a-sit.at") } + developer { + id.set("iaik-jheher") + name.set("Jakob Heher") + email.set("jakob.heher@iaik.tugraz.at") + } developer { id.set("nodh") name.set("Christian Kollmann") @@ -106,11 +111,6 @@ publishing { name.set("Simon Müller") email.set("simon.mueller@a-sit.at") } - developer { - id.set("iaik-jheher") - name.set("Jakob Heher") - email.set("jakob.heher@iaik.tugraz.at") - } } scm { connection.set("scm:git:git@github.com:a-sit-plus/signum.git") diff --git a/internals/src/commonMain/kotlin/at/asitplus/signum/internals/Utils.kt b/internals/src/commonMain/kotlin/at/asitplus/signum/internals/Utils.kt index bcb822796..4c99ef50d 100644 --- a/internals/src/commonMain/kotlin/at/asitplus/signum/internals/Utils.kt +++ b/internals/src/commonMain/kotlin/at/asitplus/signum/internals/Utils.kt @@ -1,5 +1,7 @@ package at.asitplus.signum.internals +import kotlin.experimental.xor + infix fun T?.orLazy(block: ()->T) = if (this != null) lazyOf(this) else lazy(block) /** Drops bytes at the start, or adds zero bytes at the start, until the [size] is reached */ @@ -22,3 +24,8 @@ inline fun <@kotlin.internal.OnlyInputTypes O, reified T : O> checkedAs(v: O): T inline fun checkedAsFn(crossinline fn: (I) -> O): (I) -> T = { checkedAs(fn(it)) } + +infix fun ByteArray.xor(other: ByteArray): ByteArray { + check(this.size == other.size) + return ByteArray(this.size) { i -> this[i] xor other[i] } +} diff --git a/internals/src/iosMain/kotlin/at/asitplus/signum/internals/InteropUtils.kt b/internals/src/iosMain/kotlin/at/asitplus/signum/internals/InteropUtils.kt index 6971cc80b..e4cc25cb9 100644 --- a/internals/src/iosMain/kotlin/at/asitplus/signum/internals/InteropUtils.kt +++ b/internals/src/iosMain/kotlin/at/asitplus/signum/internals/InteropUtils.kt @@ -3,31 +3,18 @@ package at.asitplus.signum.internals import kotlinx.cinterop.* -import platform.CoreFoundation.CFDictionaryCreateMutable -import platform.CoreFoundation.CFDictionaryGetValue -import platform.CoreFoundation.CFDictionaryRef -import platform.CoreFoundation.CFDictionarySetValue -import platform.CoreFoundation.CFErrorRefVar -import platform.CoreFoundation.CFMutableDictionaryRef -import platform.CoreFoundation.CFRelease -import platform.CoreFoundation.CFTypeRef -import platform.CoreFoundation.kCFBooleanFalse -import platform.CoreFoundation.kCFBooleanTrue -import platform.CoreFoundation.kCFTypeDictionaryKeyCallBacks -import platform.CoreFoundation.kCFTypeDictionaryValueCallBacks -import platform.Foundation.CFBridgingRelease -import platform.Foundation.CFBridgingRetain -import platform.Foundation.NSData -import platform.Foundation.NSError -import platform.Foundation.create +import platform.CoreFoundation.* +import platform.Foundation.* import platform.posix.memcpy import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner fun NSData.toByteArray(): ByteArray = ByteArray(length.toInt()).apply { - usePinned { - memcpy(it.addressOf(0), bytes, length) - } + if(length>Int.MAX_VALUE.toULong()) throw IndexOutOfBoundsException("length is too large") + if (length > 0uL) + usePinned { + memcpy(it.addressOf(0), bytes, length) + } } @OptIn(BetaInteropApi::class) @@ -136,6 +123,7 @@ class CFDictionaryInitScope private constructor() { } fun MemScope.createCFDictionary(pairs: CFDictionaryInitScope.()->Unit) = CFDictionaryInitScope.resolve(this, pairs) + inline operator fun CFDictionaryRef.get(key: Any?): T = CFDictionaryGetValue(this, key.giveToCF()).takeFromCF() diff --git a/supreme/build.gradle.kts b/supreme/build.gradle.kts index 9df071c88..dfb07b4c5 100644 --- a/supreme/build.gradle.kts +++ b/supreme/build.gradle.kts @@ -16,6 +16,7 @@ plugins { id("org.jetbrains.dokka") id("signing") id("at.asitplus.gradle.conventions") + id("io.github.ttypic.swiftklib") version "0.6.4" } buildscript { @@ -35,9 +36,17 @@ kotlin { @OptIn(ExperimentalKotlinGradlePluginApi::class) instrumentedTestVariant.sourceSetTree.set(test) } - iosX64() - iosArm64() - iosSimulatorArm64() + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.compilations { + val main by getting { + cinterops.create("AESwift") + } + } + } sourceSets.commonMain.dependencies { api(project(":indispensable")) @@ -53,6 +62,13 @@ kotlin { } +swiftklib { + create("AESwift") { + path = file("src/iosMain/swift") + packageName("at.asitplus.signum.supreme.symmetric.ios") + } +} + android { namespace = "at.asitplus.signum.supreme" defaultConfig { diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt new file mode 100644 index 000000000..5454c4797 --- /dev/null +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt @@ -0,0 +1,52 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.symmetric.AuthCapability +import at.asitplus.signum.indispensable.symmetric.KeyType +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm +import javax.crypto.Cipher +import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +@OptIn(HazardousMaterials::class) +internal object AESJCA { + fun initCipher( + algorithm: SymmetricEncryptionAlgorithm.AES<*, *, *>, + key: ByteArray, + nonce: ByteArray?, + aad: ByteArray? + ) = + Cipher.getInstance(algorithm.jcaName).apply { + val cipher = algorithm.authCapability + if (cipher is AuthCapability.Authenticated.Integrated) + init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(key, algorithm.jcaKeySpec), + GCMParameterSpec(cipher.tagLength.bits.toInt(), nonce) + ) + else if (algorithm is SymmetricEncryptionAlgorithm.AES.CBC<*, *>) //covers Plain and CBC, because CBC will delegate to here + init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(key, algorithm.jcaKeySpec), + IvParameterSpec(nonce) + ) + else if ((algorithm is SymmetricEncryptionAlgorithm.AES.ECB) || algorithm is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394) { + init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(key, algorithm.jcaKeySpec), + ) + } + + else TODO("Algorithm $algorithm is unsupported ") + aad?.let { if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) updateAAD(it) /*CBC-HMAC we do ourselves*/ } + }.let { + @Suppress("UNCHECKED_CAST") + CipherParam, KeyType>( + algorithm as SymmetricEncryptionAlgorithm,*, KeyType>, + it, + nonce, + aad + ) + } +} diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt new file mode 100644 index 000000000..be504e75d --- /dev/null +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt @@ -0,0 +1,20 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.indispensable.symmetric.AuthCapability +import at.asitplus.signum.indispensable.symmetric.KeyType +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +internal object ChaChaJVM { + fun initCipher(key: ByteArray, nonce: ByteArray, aad: ByteArray?): CipherParam = + Cipher.getInstance(SymmetricEncryptionAlgorithm.ChaCha20Poly1305.jcaName).apply { + init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(key, SymmetricEncryptionAlgorithm.ChaCha20Poly1305.jcaKeySpec), + IvParameterSpec(nonce) + ) + aad?.let { updateAAD(it) } + }.let { CipherParam(SymmetricEncryptionAlgorithm.ChaCha20Poly1305, it, nonce, aad) } +} \ No newline at end of file diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt new file mode 100644 index 000000000..b81dc4013 --- /dev/null +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt @@ -0,0 +1,157 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated +import javax.crypto.Cipher +import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +internal actual fun , I : NonceTrait, K : KeyType> initCipher( + algorithm: SymmetricEncryptionAlgorithm, + key: ByteArray, + nonce: ByteArray?, + aad: ByteArray? +): CipherParam = when { + algorithm.requiresNonce() -> { + @OptIn(HazardousMaterials::class) + val nonce = nonce ?: algorithm.randomNonce() + + @Suppress("UNCHECKED_CAST") + when (algorithm) { + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaJVM.initCipher(key, nonce, aad) + is SymmetricEncryptionAlgorithm.AES<*, *, *> -> AESJCA.initCipher(algorithm, key, nonce, aad) + } as CipherParam + } + + else -> { + @OptIn(HazardousMaterials::class) + if ((algorithm !is SymmetricEncryptionAlgorithm.AES.ECB) && (algorithm !is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) + TODO("$algorithm is UNSUPPORTED") + + AESJCA.initCipher(algorithm, key, nonce, aad) as CipherParam + } +} + +internal actual fun , I : NonceTrait, K : KeyType> CipherParam<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { + @Suppress("UNCHECKED_CAST") (this as CipherParam) + val jcaCiphertext = platformData.doFinal(data) + + //JCA simply concatenates ciphertext and authtag, so we need to split + val ciphertext = + if (alg.authCapability is AuthCapability.Authenticated<*>) + jcaCiphertext.dropLast(((alg.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) + .toByteArray() + else jcaCiphertext + val authTag = + if (alg.authCapability is AuthCapability.Authenticated<*>) + jcaCiphertext.takeLast(((alg.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) + .toByteArray() else null + + + @Suppress("UNCHECKED_CAST") + return when { + alg.requiresNonce() -> when { + alg.isAuthenticated() -> { + (alg as SymmetricEncryptionAlgorithm, NonceTrait.Required, *>) + alg.sealedBoxFrom(nonce!!, ciphertext, authTag!!, aad) + } + + else -> alg.sealedBoxFrom(nonce!!, ciphertext) + } + + else -> when { + alg.isAuthenticated() -> { + (alg as SymmetricEncryptionAlgorithm, NonceTrait.Without, *>) + alg.sealedBoxFrom(ciphertext, authTag!!, aad) + } + + else -> alg.sealedBoxFrom(ciphertext) + } + + }.getOrThrow() as SealedBox +} + +val SymmetricEncryptionAlgorithm<*, *, *>.jcaName: String + @OptIn(HazardousMaterials::class) + get() = when (this) { + is SymmetricEncryptionAlgorithm.AES.GCM -> "AES/GCM/NoPadding" + is SymmetricEncryptionAlgorithm.AES.CBC<*, *> -> "AES/CBC/PKCS5Padding" + is SymmetricEncryptionAlgorithm.AES.ECB -> "AES/ECB/PKCS5Padding" + is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394 -> "AESWrap" + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> "ChaCha20-Poly1305" + else -> TODO("$this is unsupported") + } + +val SymmetricEncryptionAlgorithm<*, *, *>.jcaKeySpec: String + get() = when (this) { + is SymmetricEncryptionAlgorithm.AES<*, *, *> -> "AES" + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> "ChaCha20" + else -> TODO("$this keyspec is unsupported UNSUPPORTED") + } + +@JvmName("doDecryptAuthenticated") +internal actual fun SealedBox.doDecryptAEAD( + secretKey: ByteArray +): ByteArray { + if (!this.hasNonce()) TODO("AEAD algorithm $algorithm is UNSUPPORTED") + + if ((algorithm !is SymmetricEncryptionAlgorithm.ChaCha20Poly1305) && (algorithm !is SymmetricEncryptionAlgorithm.AES.GCM)) + TODO("AEAD algorithm $algorithm is UNSUPPORTED") + + return aeadDecrypt( + algorithm, + secretKey, + nonce, + encryptedData, + authTag, + authenticatedData + ) + +} + +internal actual fun SealedBox.doDecrypt( + secretKey: ByteArray +): ByteArray { + if (algorithm !is SymmetricEncryptionAlgorithm.AES<*, *, *>) + TODO("unauthenticated algorithm $algorithm is UNSUPPORTED") + + @OptIn(HazardousMaterials::class) + if ((algorithm is SymmetricEncryptionAlgorithm.AES.ECB) || (algorithm is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) { + return Cipher.getInstance(algorithm.jcaName).also { cipher -> + cipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(secretKey, algorithm.jcaKeySpec), + ) + }.doFinal(encryptedData) + } + + this as SealedBox.WithNonce + return Cipher.getInstance(algorithm.jcaName).also { cipher -> + cipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(secretKey, algorithm.jcaKeySpec), + IvParameterSpec(this@doDecrypt.nonce) + ) + }.doFinal(encryptedData) +} + +internal fun aeadDecrypt( + algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.Integrated>, + secretKey: ByteArray, + nonce: ByteArray, + encryptedData: ByteArray, + authTag: ByteArray, + aad: ByteArray? +): ByteArray = Cipher.getInstance(algorithm.jcaName).also { cipher -> + cipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(secretKey, algorithm.jcaKeySpec), + if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) + GCMParameterSpec(authTag.size * 8, nonce) + else IvParameterSpec(nonce) + ) + aad?.let { cipher.updateAAD(it) } +}.doFinal(encryptedData + authTag) + diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt index 126900aa7..be96400dc 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt @@ -1,7 +1,7 @@ package at.asitplus.signum.supreme.hazmat import at.asitplus.signum.indispensable.getJCASignatureInstance -import at.asitplus.signum.supreme.HazardousMaterials +import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.supreme.os.AndroidKeystoreSigner import at.asitplus.signum.supreme.sign.AndroidEphemeralSigner import at.asitplus.signum.supreme.sign.EphemeralKey diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt index 19da63e12..9ca250167 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt @@ -1,9 +1,5 @@ package at.asitplus.signum.supreme -@RequiresOptIn(message = "Access to potentially hazardous platform-specific internals requires explicit opt-in. Specify @OptIn(HazardousMaterials::class). These accessors are unstable and may change without warning.") -/** This is an internal property. It is exposed if you know what you are doing. You very likely don't actually need it. */ -annotation class HazardousMaterials - @RequiresOptIn(message = "Access to secret and private key material requires explicit opt-in. Specify @OptIn(SecretExposure::class). Make sure that you actually want to externalise a secret. Check yourself, before you really, really, wreck yourself!") /** This guards a secret. Do not expose it lightly! */ diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt new file mode 100644 index 000000000..bb733132c --- /dev/null +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt @@ -0,0 +1,34 @@ +package at.asitplus.signum.supreme.mac + +import at.asitplus.KmmResult +import at.asitplus.catching +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.internals.xor +import at.asitplus.signum.supreme.hash.digest + + +fun MAC.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) +fun MAC.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) + +private val HMAC.blockLength get() = digest.inputBlockSize.bytes.toInt() +private val HMAC.innerPad get() = ByteArray(blockLength) { 0x36 } +private val HMAC.outerPad get() = ByteArray(blockLength) { 0x5C } + +fun MAC.mac(key: ByteArray, msg: Sequence): KmmResult = catching { + when (this@mac) { + is HMAC -> hmac(key, msg) + } +} + +internal fun HMAC.hmac(key: ByteArray, msg: Sequence): ByteArray { + val realKey = (if (key.size <= blockLength) key else digest.digest(key)).let { + if (it.size < blockLength) it + ByteArray(blockLength - it.size) else it + } + check(realKey.size == blockLength) + val innerHash = digest.digest(sequenceOf(realKey xor innerPad) + msg) + val outerHash = digest.digest(sequenceOf(realKey xor outerPad, innerHash)) + return outerHash +} + + diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt index 8cb391a26..d12ce4e43 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt @@ -3,6 +3,8 @@ package at.asitplus.signum.supreme.sign import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.* +import at.asitplus.signum.indispensable.RSAPadding +import at.asitplus.signum.indispensable.SignatureAlgorithm import at.asitplus.signum.supreme.SecretExposure import at.asitplus.signum.supreme.SignatureResult import at.asitplus.signum.supreme.agree.UsableECDHPrivateValue diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt new file mode 100644 index 000000000..17a73c117 --- /dev/null +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt @@ -0,0 +1,98 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.ImplementationError +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated +import at.asitplus.signum.supreme.mac.mac + + +internal class Encryptor, I : NonceTrait, out K : KeyType> internal constructor( + private val algorithm: SymmetricEncryptionAlgorithm, + private val key: ByteArray, + private val macKey: ByteArray?, + private val iv: ByteArray?, + private val aad: ByteArray?, +) { + + init { + if (algorithm.nonceTrait is NonceTrait.Required) iv?.let { + require(it.size.toUInt() == (algorithm.nonceTrait as NonceTrait.Required).length.bytes) { "IV must be exactly ${(algorithm.nonceTrait as NonceTrait.Required).length} bits long" } + } + require(key.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } + } + + private val platformCipher: CipherParam<*, A, out K> = initCipher(algorithm, key, iv, aad) + + /** + * Encrypts [data] and returns a [at.asitplus.signum.indispensable.symmetric.Ciphertext] matching the algorithm type that was used to create this [Encryptor] object. + * E.g., an authenticated encryption algorithm causes this function to return a [at.asitplus.signum.indispensable.symmetric.Ciphertext.Authenticated]. + */ + internal fun encrypt(data: ByteArray): SealedBox = ( + //Our own, flexible construction to make any unauthenticated cipher into an authenticated cipher + if (algorithm.hasDedicatedMac()) { + val aMac = algorithm.authCapability + aMac.innerCipher + val innerCipher = + initCipher( + aMac.innerCipher, + key, + iv, + aad + ) + + if (!aMac.innerCipher.requiresNonce()) throw ImplementationError("AES-CBC-HMAC Nonce inconsistency") + if (macKey == null) throw ImplementationError("AES-CBC-HMAC MAC key is null") + + val encrypted = innerCipher.doEncrypt(data) + val macInputCalculation = aMac.dedicatedMacInputCalculation + val hmacInput: ByteArray = + aMac.mac.macInputCalculation( + encrypted.encryptedData, + innerCipher.nonce ?: byteArrayOf(), + aad ?: byteArrayOf() + ) + + val outputTransform = aMac.dedicatedMacAuthTagTransform + val authTag = aMac.outputTransform(aMac.mac.mac(macKey, hmacInput).getOrThrow()) + + @Suppress("UNCHECKED_CAST") + (if (algorithm.requiresNonce()) { + (algorithm).sealedBoxFrom( + (encrypted as SealedBox.WithNonce<*, *>).nonce, + encrypted.encryptedData, + authTag, + aad + ) + } else (algorithm).sealedBoxFrom( + encrypted.encryptedData, + authTag, + aad + )).getOrThrow() as SealedBox + + } else platformCipher.doEncrypt(data)) +} + +internal class CipherParam, K : KeyType>( + val alg: SymmetricEncryptionAlgorithm, + val platformData: T, + val nonce: ByteArray?, + val aad: ByteArray? +) + +internal expect fun SealedBox.doDecryptAEAD( + secretKey: ByteArray +): ByteArray + +internal expect fun SealedBox.doDecrypt( + secretKey: ByteArray +): ByteArray + + +internal expect fun , I : NonceTrait, K : KeyType> initCipher( + algorithm: SymmetricEncryptionAlgorithm, + key: ByteArray, + nonce: ByteArray?, + aad: ByteArray? +): CipherParam + +internal expect fun , I : NonceTrait, K : KeyType> CipherParam<*, A, out K>.doEncrypt(data: ByteArray): SealedBox diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt new file mode 100644 index 000000000..4f60c8b12 --- /dev/null +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt @@ -0,0 +1,118 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.KmmResult +import at.asitplus.catching +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.asn1.encoding.bitLength +import at.asitplus.signum.indispensable.misc.BitLength +import at.asitplus.signum.indispensable.symmetric.* +import org.kotlincrypto.SecureRandom +import kotlin.jvm.JvmName + +private val secureRandom = SecureRandom() + +/** + * Generates a fresh random key for this algorithm. + */ +@Suppress("UNCHECKED_CAST") +fun , I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.randomKey(): SymmetricKey = + keyFromInternal( + secureRandom.nextBytesOf(keySize.bytes.toInt()), + if (authCapability.keyType is KeyType.WithDedicatedMacKey) secureRandom.nextBytesOf(keySize.bytes.toInt()) + else null + ) as SymmetricKey + +/** + * Generates a fresh random key for this algorithm. + * [macKeyLength] can be specified to override [AuthCapability.Authenticated.WithDedicatedMac.preferredMacKeyLength]. + */ +@JvmName("randomKeyAndMacKey") +@Suppress("UNCHECKED_CAST") +fun SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>.randomKey( + macKeyLength: BitLength = preferredMacKeyLength +): SymmetricKey.WithDedicatedMac = + keyFromInternal( + secureRandom.nextBytesOf(keySize.bytes.toInt()), + secureRandom.nextBytesOf(macKeyLength.bytes.toInt()) + ) as SymmetricKey.WithDedicatedMac + +/** + * Generates a new random nonce matching the Nonce size of this algorithm. + * You typically don't want to use this, but have your nonces auto-generated during the encryption process + */ +@HazardousMaterials("Don't explicitly generate nonces!") +fun SymmetricEncryptionAlgorithm<*, NonceTrait.Required, *>.randomNonce(): ByteArray = + secureRandom.nextBytesOf((nonceTrait.length.bytes).toInt()) + + +@OptIn(HazardousMaterials::class) +private fun SymmetricEncryptionAlgorithm<*, *, *>.keyFromInternal( + bytes: ByteArray, + dedicatedMacKey: ByteArray? +): SymmetricKey<*, *, *> { + require(bytes.size == this.keySize.bytes.toInt()) { "Invalid key size: ${bytes.size * 8}. Required: keySize=${bytes.size.bitLength}" } + dedicatedMacKey?.let { + require(it.isNotEmpty()) { "Dedicated MAC key is empty!" } + } + @OptIn(HazardousMaterials::class) + return when (this.requiresNonce()) { + true -> when (isAuthenticated()) { + true -> when (isIntegrated()) { + false -> SymmetricKey.WithDedicatedMac.RequiringNonce(this, bytes, dedicatedMacKey!!) + true -> SymmetricKey.Integrated.Authenticating.RequiringNonce(this, bytes) + } + + false -> SymmetricKey.Integrated.NonAuthenticating.RequiringNonce(this, bytes) + } + + false -> when (isAuthenticated()) { + true -> when (isIntegrated()) { + false -> SymmetricKey.WithDedicatedMac.WithoutNonce(this, bytes, dedicatedMacKey!!) + true -> SymmetricKey.Integrated.Authenticating.WithoutNonce(this, bytes) + } + + false -> SymmetricKey.Integrated.NonAuthenticating.WithoutNonce(this, bytes) + } + } +} + +/** + * Creates a [SymmetricKey] from the specified [secretKey]. + * Returns [KmmResult.failure] in case the provided bytes don't match [SymmetricEncryptionAlgorithm.keySize] + */ +@JvmName("fixedKeyIntegrated") +@Suppress("UNCHECKED_CAST") +fun SymmetricEncryptionAlgorithm, I, KeyType.Integrated>.keyFrom( + secretKey: ByteArray +): KmmResult, I, KeyType.Integrated>> = + catching { + (this as SymmetricEncryptionAlgorithm<*, *, *>).keyFromInternal(secretKey, null) + } as KmmResult, I, KeyType.Integrated>> + + +/** + * Creates a [SymmetricKey] from the specified [secretKey]. + * Returns [KmmResult.failure] in case the provided bytes don't match [SymmetricEncryptionAlgorithm.keySize] + */ +@JvmName("fixedKeyAuthenticatedIntegrated") +@Suppress("UNCHECKED_CAST") +fun SymmetricEncryptionAlgorithm, I, KeyType.Integrated>.keyFrom( + secretKey: ByteArray +): KmmResult> = + catching { + (this as SymmetricEncryptionAlgorithm<*, *, *>).keyFromInternal(secretKey, null) + } as KmmResult> + +/** + * Creates a [SymmetricKey] from the specified [bytes]. + * Returns [KmmResult.failure] in case the provided bytes don't match [SymmetricEncryptionAlgorithm.keySize] or the specified [macKey] is empty. + */ +@JvmName("fixedKeyDedicatedMacKey") +@Suppress("UNCHECKED_CAST") +fun SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>.keyFrom( + encryptionKey: ByteArray, + macKey: ByteArray +): KmmResult, I, KeyType.WithDedicatedMacKey>> = + catching { + (this as SymmetricEncryptionAlgorithm<*, *, *>).keyFromInternal(encryptionKey, macKey) + } as KmmResult, I, KeyType.WithDedicatedMacKey>> diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt new file mode 100644 index 000000000..ac28d4667 --- /dev/null +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -0,0 +1,168 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.KmmResult +import at.asitplus.catching +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES +import at.asitplus.signum.supreme.mac.mac +import kotlin.jvm.JvmName + + +/** + * Attempts to decrypt this ciphertext (which may also hold an IV/nonce, and in case of an authenticated ciphertext, authenticated data and auth tag) using the provided [key]. + * This is the generic, untyped decryption function for convenience. + * **Compared to its narrower-typed cousins is possible to mismatch the characteristics of + * [key] and [SealedBox].** + */ +@JvmName("decryptGeneric") +fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>) = catching { + require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } + @Suppress("UNCHECKED_CAST") + when (algorithm.authCapability) { + is Authenticated.Integrated -> (this as SealedBox).decryptInternal( + (key as SymmetricKey.Integrated).secretKey + ) + + is Authenticated.WithDedicatedMac<*, *> -> { + key as SymmetricKey.WithDedicatedMac + (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( + key.encryptionKey, key.macKey + ) + } + + is AuthCapability.Unauthenticated -> (this as SealedBox).decryptInternal( + (key as SymmetricKey.Integrated).secretKey + ) + } +} + +/** + * Attempts to decrypt this ciphertext (which may also hold an IV/nonce, and in case of an authenticated ciphertext, authenticated data and auth tag) using the provided [key]. + * This constrains the [key]'s characteristics to the characteristics of the [SealedBox] to decrypt. + * It does not, however, prevent maxing up different encryption algorithms with the same characteristics. I.e., it is possible to feed a [AES.ECB] key into + * a [AES.CBC] [SealedBox]. + * In such cases, this function will immediately return a [KmmResult.failure]. + */ +fun , I : NonceTrait, K : KeyType> SealedBox.decrypt( + key: SymmetricKey +): KmmResult = catching { + require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } + @Suppress("UNCHECKED_CAST") + when (algorithm.authCapability as AuthCapability<*>) { + is Authenticated.Integrated -> (this as SealedBox).decryptInternal( + (key as SymmetricKey.Integrated).secretKey + ) + + is Authenticated.WithDedicatedMac<*, *> -> { + key as SymmetricKey.WithDedicatedMac + (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( + key.encryptionKey, key.macKey + ) + } + + is AuthCapability.Unauthenticated -> (this as SealedBox).decryptInternal( + (key as SymmetricKey.Integrated).secretKey + ) + } +} + + +/** + * Attempts to decrypt this ciphertext (which may also hold an IV/nonce, and in case of an authenticated ciphertext, authenticated data and auth tag) using the provided [key]. + * This constrains the [key]'s characteristics to the characteristics of the [SealedBox] to decrypt. + * It does not, however, prevent maxing up different encryption algorithms with the same characteristics. I.e., it is possible to feed a [SymmetricEncryptionAlgorithm.ChaCha20Poly1305] key into + * a [AES.GCM] [SealedBox]. + * In such cases, this function will immediately return a [KmmResult.failure]. + */ +@JvmName("decryptRawAuthenticated") +private fun SealedBox.decryptInternal( + secretKey: ByteArray +): ByteArray { + require(secretKey.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } + return doDecryptAEAD(secretKey) +} + +@JvmName("decryptRaw") +private fun SealedBox.decryptInternal( + secretKey: ByteArray +): ByteArray { + require(secretKey.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } + return doDecrypt(secretKey) +} + +private fun SealedBox, *, KeyType.WithDedicatedMacKey>.decryptInternal( + secretKey: ByteArray, + macKey: ByteArray = secretKey, +): ByteArray { + require(this.isAuthenticated()) + val iv: ByteArray? = if (this is SealedBox.WithNonce<*, *>) nonce else null + val aad = authenticatedData + val authTag = authTag + + val algorithm = algorithm + val innerCipher = algorithm.authCapability.innerCipher + val mac = algorithm.authCapability.mac + val dedicatedMacInputCalculation = algorithm.authCapability.dedicatedMacInputCalculation + val hmacInput = mac.dedicatedMacInputCalculation(encryptedData, iv ?: byteArrayOf(), aad ?: byteArrayOf()) + val transform = algorithm.authCapability.dedicatedMacAuthTagTransform + if (!algorithm.authCapability.transform(mac.mac(macKey, hmacInput).getOrThrow()).contentEquals(authTag)) + throw IllegalArgumentException("Auth Tag mismatch!") + + @Suppress("UNCHECKED_CAST") val box: SealedBox = + (if (this is SealedBox.WithNonce<*, *>) (innerCipher as SymmetricEncryptionAlgorithm).sealedBoxFrom( + nonce, + encryptedData + ) else (innerCipher as SymmetricEncryptionAlgorithm).sealedBoxFrom( + encryptedData + )).getOrThrow() as SealedBox + return box.doDecrypt(secretKey) +} + + +//raw data decryption + + +/** + * Directly decrypts raw [encryptedData], feeding [nonce] into the decryption process. + */ +@JvmName("decryptRawUnauthedWithNonce") +fun SymmetricKey.decrypt( + nonce: ByteArray, + encryptedData: ByteArray +): KmmResult = algorithm.sealedBoxFrom(nonce, encryptedData).transform { it.decrypt(this) } + + +/** + * Directly decrypts raw [encryptedData]. + */ +@JvmName("decryptRawUnauthedNoNonce") +fun SymmetricKey.decrypt( + encryptedData: ByteArray +): KmmResult = algorithm.sealedBoxFrom(encryptedData).transform { it.decrypt(this) } + + +/** + * Directly decrypts raw [encryptedData], feeding [nonce], [authTag], and [authenticatedData] into the decryption process. + * @return [at.asitplus.KmmResult.failure] on illegal auth tag length + */ +@JvmName("decryptRawAuthedWithNonce") +fun SymmetricKey, NonceTrait.Required, *>.decrypt( + nonce: ByteArray, + encryptedData: ByteArray, + authTag: ByteArray, + authenticatedData: ByteArray? = null +): KmmResult = + algorithm.sealedBoxFrom(nonce, encryptedData, authTag, authenticatedData).transform { it.decrypt(this) } + +/** + * Directly decrypts raw [encryptedData], feeding [authTag], and [authenticatedData] into the decryption process. + * @return [at.asitplus.KmmResult.failure] on illegal auth tag length + */ +@JvmName("decryptRawAuthedNoNonce") +fun SymmetricKey, NonceTrait.Without, *>.decrypt( + encryptedData: ByteArray, + authTag: ByteArray, + authenticatedData: ByteArray? = null +): KmmResult = + algorithm.sealedBoxFrom(encryptedData, authTag, authenticatedData).transform { it.decrypt(this) } \ No newline at end of file diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt new file mode 100644 index 000000000..01a23ed6e --- /dev/null +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt @@ -0,0 +1,93 @@ +package at.asitplus.signum.supreme.symmetric.discouraged + +import at.asitplus.KmmResult +import at.asitplus.catching +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.indispensable.symmetric.SymmetricKey.WithDedicatedMac +import at.asitplus.signum.supreme.symmetric.Encryptor +import kotlin.jvm.JvmName + + +/** + * Encrypts [data] using the manually specified [nonce]. Check yourself, before you really, really wreck yourself! + * * [nonce] = _Initialization Vector_; **NEVER EVER RE-USE THIS!** + * * [authenticatedData] = _Additional Authenticated Data_ + * + * It is safe to discard the reference to [nonce] and [authenticatedData], as both will be added to any [SealedBox.Authenticated] resulting from an encryption. + * + * @return [KmmResult.success] containing a [SealedBox.Authenticated] if valid parameters were provided or [KmmResult.failure] in case of + * invalid parameters (e.g., key or nonce length) + */ +@HazardousMaterials("NEVER re-use a nonce/IV! Have them auto-generated instead!") +@JvmName("encryptAuthenticatedWithNonce") +fun > KeyWithNonceAuthenticating.encrypt( + data: ByteArray, + authenticatedData: ByteArray? = null +): KmmResult> = catching { + Encryptor( + second.algorithm, + if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).encryptionKey else (second as SymmetricKey.Integrated).secretKey, + if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).macKey else (second as SymmetricKey.Integrated).secretKey, + first, + authenticatedData, + ).encrypt(data) as SealedBox.WithNonce.Authenticated +} + +/** + * Encrypts [data] using the manually specified [nonce]. Check yourself, before you really, really wreck yourself! + * * [nonce] = _Initialization Vector_; **NEVER EVER RE-USE THIS!** + * + * It is safe to discard the reference to [nonce] and [authenticatedData], as both will be added to any [SealedBox.Authenticated] resulting from an encryption. + * + * @return [KmmResult.success] containing a [SealedBox.Authenticated] if valid parameters were provided or [KmmResult.failure] in case of + * invalid parameters (e.g., key or nonce length) + */ +@HazardousMaterials("NEVER re-use a nonce/IV! Have them auto-generated instead!") +@JvmName("encryptWithNonce") +fun > KeyWithNonce.encrypt( + data: ByteArray +): KmmResult> = catching { + Encryptor( + first.algorithm, + if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).encryptionKey else (first as SymmetricKey.Integrated).secretKey, + if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).macKey else null, + second, + null, + ).encrypt(data) as SealedBox.WithNonce +} + +/** + * This function can be used to feed a pre-set nonce into encryption functions. + * This is usually not required, since all algorithms requiting a nonce/IV generate them by default + * @see at.asitplus.signum.supreme.symmetric.randomNonce + */ +@HazardousMaterials("Nonce/IV re-use can have catastrophic consequences!") +fun > SymmetricKey.andPredefinedNonce(nonce: ByteArray) = + catching { + require(nonce.size == algorithm.nonceTrait.length.bytes.toInt()) { "Nonce is empty!" } + KeyWithNonce(this, nonce) + } + +/** + * This function can be used to feed a pre-set nonce into encryption functions. + * This is usually not required, since all algorithms requiting a nonce/IV generate them by default + * @see at.asitplus.signum.supreme.symmetric.randomNonce + */ +@HazardousMaterials("Nonce/IV re-use can have catastrophic consequences!") +@JvmName("authedKeyWithNonce") +fun > SymmetricKey.andPredefinedNonce( + nonce: ByteArray +) = + catching { + require(nonce.size == algorithm.nonceTrait.length.bytes.toInt()) { "Invalid nonce size!" } + KeyWithNonceAuthenticating(nonce, this) + } + +private typealias KeyWithNonce = Pair, ByteArray> +//first and second are deliberately swapped to avoid mixups +private typealias KeyWithNonceAuthenticating = Pair> + + +val KeyWithNonceAuthenticating<*, *>.nonce: ByteArray @JvmName("nonceAuthenticating") get() = first +val KeyWithNonce<*, *>.nonce: ByteArray get() = second \ No newline at end of file diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt new file mode 100644 index 000000000..c45d37004 --- /dev/null +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt @@ -0,0 +1,52 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.KmmResult +import at.asitplus.catching +import at.asitplus.signum.indispensable.symmetric.* +import kotlin.jvm.JvmName + +/** + * Encrypts [data] and automagically generates a fresh nonce/IV if required by the cipher. + * + * @return [KmmResult.success] containing a [SealedBox] if valid parameters were provided or [KmmResult.failure] in case of + * invalid parameters (e.g., algorithm mismatch, key length, …) + */ +@JvmName("encryptWithAutoGenIV") +fun , I : NonceTrait> SymmetricKey.encrypt( + data: ByteArray +): KmmResult> = catching { + Encryptor( + algorithm, + if (this.hasDedicatedMacKey()) encryptionKey else secretKey, + if (this.hasDedicatedMacKey()) macKey else null, + null, + null, + ).encrypt(data) +} + + +/** + * Encrypts [data] and automagically generates a fresh nonce/IV if required by the cipher. + * + * @param authenticatedData Additional data to be authenticated (i.e. fed into the auth tag generation) but not encrypted. + * - + * It is safe to discard the reference to this data, as the [SealedBox] resulting from this operation will carry the + * corresponding type information. Hence, it is possible to simply access + * [at.asitplus.signum.indispensable.symmetric.authenticatedData] + * + * @return [KmmResult.success] containing a [SealedBox] if valid parameters were provided or [KmmResult.failure] in case of + * invalid parameters (e.g., algorithm mismatch, key length, …) + */ +@JvmName("encryptAuthenticated") +fun , I : NonceTrait> SymmetricKey.encrypt( + data: ByteArray, + authenticatedData: ByteArray? = null +): KmmResult> = catching { + Encryptor( + algorithm, + if (this.hasDedicatedMacKey()) encryptionKey else secretKey, + if (this.hasDedicatedMacKey()) macKey else null, + null, + authenticatedData, + ).encrypt(data) +} diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/TestUtils.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/TestUtils.kt index 8aba04290..8776d66a0 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/TestUtils.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/TestUtils.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalStdlibApi::class) + package at.asitplus.signum.supreme import at.asitplus.KmmResult @@ -10,3 +12,9 @@ internal object succeed: Matcher> { failureMessageFn = { "Should have succeeded, but failed:\n${value.exceptionOrNull()!!.stackTraceToString()}"}, negatedFailureMessageFn = { "Should have failed, but succeeded with ${value.getOrNull()!!}"}) } + + +/** String -> UTF-8 bytes */ fun a(s: String) = s.encodeToByteArray() +/** Hex String -> bytes */ fun b(s: String) = s.replace("(^0x)|([^0-9a-fA-F])".toRegex(), "").hexToByteArray() +/** Decimal String -> Int */ fun i(s: String) = s.toInt(10) +fun unreachable(): Nothing = throw IllegalStateException() diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/mac/MACTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/mac/MACTest.kt new file mode 100644 index 000000000..2e2fcb1bc --- /dev/null +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/mac/MACTest.kt @@ -0,0 +1,91 @@ + +package at.asitplus.signum.supreme.mac + +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.supreme.b +import io.kotest.core.spec.style.FreeSpec +import io.kotest.datatest.withData +import io.kotest.matchers.shouldBe + +class MACTest : FreeSpec({ + "RFC4231" - { + class I(val comment: String, key: String, data: String, + SHA224: String, SHA256: String, SHA384: String, SHA512: String) { + val k = b(key); val d = b(data); val ref224 = b(SHA224); + val ref256 = b(SHA256); val ref384 = b(SHA384); val ref512 = b(SHA512) + } + withData(nameFn=I::comment, sequence { + yield(I("", + key="0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + data="4869205468657265", + SHA224="896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", + SHA256="b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", + SHA384="afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cf" + + "aea9ea9076ede7f4af152e8b2fa9cb6", + SHA512="87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545" + + "e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854")) + yield(I("Test with a key shorter than the length of the HMAC output.", + key="4a656665", + data="7768617420646f2079612077616e7420666f72206e6f7468696e673f", + SHA224="a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44", + SHA256="5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", + SHA384="af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8" + + "e2240ca5e69e2c78b3239ecfab21649", + SHA512="164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554" + + "9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737")) + yield(I("Test with a combined length of key and data that is larger than 64 " + + "bytes (= block-size of SHA-224 and SHA-256).", + key="a".repeat(40), + data="d".repeat(100), + SHA224="7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea", + SHA256="773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", + SHA384="88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b" + + "2a5ab39dc13814b94e3ab6e101a34f27", + SHA512="fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39" + + "bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb")) + yield(I("Test with a combined length of key and data that is larger than 64 " + + "bytes (= block-size of SHA-224 and SHA-256). (#2)", + key="0102030405060708090a0b0c0d0e0f10111213141516171819", + data="cd".repeat(50), + SHA224="6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a", + SHA256="82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", + SHA384="3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e" + + "6801dd23c4a7d679ccf8a386c674cffb", + SHA512="b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3db" + + "a91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd")) + yield(I("Test with a key larger than 128 bytes (= block-size of SHA-384 and SHA-512).", + key="a".repeat(262), + data="54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65" + + "204b6579202d2048617368204b6579204669727374", + SHA224="95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e", + SHA256="60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", + SHA384="4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c6" + + "0c2ef6ab4030fe8296248df163f44952", + SHA512="80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f352" + + "6b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598")) + yield(I("Test with a key and data that is larger than 128 bytes " + + "(= block-size of SHA-384 and SHA-512).", + key="a".repeat(262), + data="5468697320697320612074657374207573696e672061206c6172676572207468" + + "616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074" + + "68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565" + + "647320746f20626520686173686564206265666f7265206265696e6720757365" + + "642062792074686520484d414320616c676f726974686d2e", + SHA224="3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1", + SHA256="9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", + SHA384="6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5" + + "a678cc31e799176d3860e6110c46523e", + SHA512="e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944" + + "b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58")) + }) { info -> + check(info.ref224.size == 28) + check(info.ref256.size == 32) + check(info.ref384.size == 48) + check(info.ref512.size == 64) + + HMAC.SHA256.mac(key=info.k, msg=info.d).getOrThrow() shouldBe info.ref256 + HMAC.SHA384.mac(key=info.k, msg=info.d).getOrThrow() shouldBe info.ref384 + HMAC.SHA512.mac(key=info.k, msg=info.d).getOrThrow() shouldBe info.ref512 + } + } +}) diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt index 415bf1b23..d291e0fe8 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt @@ -3,14 +3,14 @@ package at.asitplus.signum.supreme.sign import at.asitplus.signum.indispensable.* import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.pki.* +import at.asitplus.signum.indispensable.RSAPadding +import at.asitplus.signum.indispensable.SignatureAlgorithm import at.asitplus.signum.supreme.SecretExposure -import at.asitplus.signum.supreme.dsl.DSL import at.asitplus.signum.supreme.os.PlatformSigningKeyConfigurationBase import at.asitplus.signum.supreme.os.SignerConfiguration import at.asitplus.signum.supreme.sign import at.asitplus.signum.supreme.signature import at.asitplus.signum.supreme.succeed -import com.ionspin.kotlin.bignum.integer.Quadruple import io.kotest.core.spec.style.FreeSpec import io.kotest.datatest.withData import io.kotest.matchers.collections.shouldBeIn diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt new file mode 100644 index 000000000..576840f57 --- /dev/null +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt @@ -0,0 +1,206 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.supreme.succeed +import io.kotest.core.spec.style.FreeSpec +import io.kotest.datatest.withData +import io.kotest.matchers.should +import io.kotest.matchers.shouldBe + +@OptIn(HazardousMaterials::class) +class `00ApiTest` : FreeSpec({ + + "Utterly Untyped" - { + withData( + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_512, + SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.AES_128.ECB, + SymmetricEncryptionAlgorithm.ChaCha20Poly1305 + ) { algorithm-> + + //create a key, encrypt and decrypt works! + val key = algorithm.randomKey() + val plain = "Harvest".encodeToByteArray() + val box = key.encrypt(plain).getOrThrow() + box.decrypt(key).onSuccess { it shouldBe plain } should succeed + + + //if you load a key, you are forced to know whether a dedicated MAC key is required + val loadedKey = when (algorithm.hasDedicatedMac()) { + true -> { + algorithm.keyFrom(byteArrayOf(), byteArrayOf()) + //Compile error + //algorithm.keyFrom(byteArrayOf()) + } + + false -> { + algorithm.keyFrom(byteArrayOf()) + //Compile error + //algorithm.keyFrom(byteArrayOf(),byteArrayOf()) + } + } + + + //creating sealed boxes + when (algorithm.requiresNonce()) { + true -> when (algorithm.isAuthenticated()) { + true -> { + algorithm + //compile error + //algorithm.sealedBox(byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(),byteArrayOf()) + algorithm.sealedBoxFrom( + byteArrayOf(), //nonce + byteArrayOf(), //encrypted + byteArrayOf(), //nonce + ) + algorithm.sealedBoxFrom( + byteArrayOf(), + byteArrayOf(), + byteArrayOf(), + byteArrayOf(), + ) + } + + false -> { + //Compile error + //algorithm.sealedBox(byteArrayOf()) + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) + //Compile error + //algorithm.sealedBox(byteArrayOf(), byteArrayOf(),byteArrayOf()) + //Compile error + //algorithm.sealedBox(byteArrayOf(), byteArrayOf(),byteArrayOf(), byteArrayOf()) + + + } + } + + false -> when (algorithm.isAuthenticated()) { + true -> { + //compile error + //algorithm.sealedBox(byteArrayOf()) + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) + //why ambiguous?? + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(), byteArrayOf(), byteArrayOf(), byteArrayOf()) + } + + false -> { + algorithm.sealedBoxFrom(byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(),byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(),byteArrayOf(),byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(),byteArrayOf(),byteArrayOf()) + } + } + } + + + //creating sealed boxes + when (algorithm.isAuthenticated()) { + true -> when (algorithm.requiresNonce()) { + true -> { + algorithm + //compile error + //algorithm.sealedBox(byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(),byteArrayOf()) + //why ambiguous? + algorithm.sealedBoxFrom( + byteArrayOf(), //nonce + byteArrayOf(), //encrypted + byteArrayOf(), //nonce + ) + algorithm.sealedBoxFrom( + byteArrayOf(), //nonce + byteArrayOf(), //nonce + byteArrayOf(), //nonce + byteArrayOf(), //nonce + ) + } + + false -> { + //Compile error + //algorithm.sealedBox(byteArrayOf()) + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) + //Compile error + //algorithm.sealedBox(byteArrayOf(), byteArrayOf(),byteArrayOf(), byteArrayOf()) + + + } + } + + false -> when (algorithm.requiresNonce()) { + true -> { + //compile error + //algorithm.sealedBox(byteArrayOf()) + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(), byteArrayOf(), byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(), byteArrayOf(), byteArrayOf(), byteArrayOf()) + } + + false -> { + algorithm.sealedBoxFrom(byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(),byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(),byteArrayOf(),byteArrayOf()) + //compile error + //algorithm.sealedBox(byteArrayOf(),byteArrayOf(),byteArrayOf()) + } + } + } + } + } + + "Authenticated" - { + withData( + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_512, + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.ChaCha20Poly1305 + ) { + + val algorithm = it + + //create a key, encrypt and decrypt works! + val key = algorithm.randomKey() + val box = key.encrypt("Harvest".encodeToByteArray()).getOrThrow() + box.decrypt(key) should succeed + + + //if you load a key, you are forced to know whether a dedicated MAC key is required + val loadedKey = when (algorithm.hasDedicatedMac()) { + true -> { + algorithm.keyFrom(byteArrayOf(), byteArrayOf()) + //Compile error + //algorithm.keyFrom(byteArrayOf()) + } + + false -> { + algorithm.keyFrom(byteArrayOf()) + //Compile error + //algorithm.keyFrom(byteArrayOf(),byteArrayOf()) + } + } + //compile error + //algorithm.sealedBox(byteArrayOf(), byteArrayOf()) + + //correct + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) + + //correct + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf(), byteArrayOf()) + + } + } + +}) \ No newline at end of file diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricAgainstReference.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricAgainstReference.kt new file mode 100644 index 000000000..e1326d8dc --- /dev/null +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricAgainstReference.kt @@ -0,0 +1,4684 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm +import at.asitplus.signum.indispensable.symmetric.authTag +import at.asitplus.signum.indispensable.symmetric.authenticatedData +import at.asitplus.signum.supreme.symmetric.discouraged.andPredefinedNonce +import at.asitplus.signum.supreme.symmetric.discouraged.encrypt +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FreeSpec +import io.kotest.datatest.withData +import io.kotest.matchers.shouldBe +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +@OptIn(HazardousMaterials::class) +@ExperimentalStdlibApi +class `00SymmetricAgainstReference` : FreeSpec({ + "AES GCM+CBC and ChaCha20-Poly1305" - { + val reference: JsonArray = Json.decodeFromString(pregenerated) + + withData(nameFn = { + it.jsonObject.keys.first() + val obj = it.jsonObject + val alg = obj.keys.first() + + val key = obj[alg]!!.jsonObject["key"]!!.jsonPrimitive.content + "$alg, key: $key" + }, reference.toList()) { + val obj = it.jsonObject + val alg = obj.keys.first() + + val key = obj[alg]!!.jsonObject["key"]!!.jsonPrimitive.content.hexToByteArray() + val iv = obj[alg]!!.jsonObject["iv"]!!.jsonPrimitive.content.hexToByteArray() + val plaintext = obj[alg]!!.jsonObject["plain"]!!.jsonPrimitive.content.hexToByteArray() + val encrypted = obj[alg]!!.jsonObject["encrypted"]!!.jsonPrimitive.content.hexToByteArray() + + val aad = obj[alg]!!.jsonObject["aad"]?.jsonPrimitive?.content?.let { + if (it == "null") null else it.hexToByteArray() + } + val authTag = obj[alg]!!.jsonObject["authTag"]?.jsonPrimitive?.content?.let { + if (it == "null") null else it.hexToByteArray() + } + + + when (alg) { + "GCM" -> { + when (key.size) { + 128 / 8 -> SymmetricEncryptionAlgorithm.AES_128.GCM.apply { + val key = keyFrom(key).getOrThrow() + val box = key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext, aad).getOrThrow() + withClue(encrypted.toHexString()) { + box.encryptedData shouldBe encrypted + box.authTag shouldBe authTag + box.authenticatedData shouldBe aad + box.nonce shouldBe iv + } + box.decrypt(key).getOrThrow() shouldBe plaintext + } + + 192 / 8 -> SymmetricEncryptionAlgorithm.AES_192.GCM.apply { + val key = keyFrom(key).getOrThrow() + val box = key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext).getOrThrow() + box.encryptedData shouldBe encrypted + box.decrypt(key).getOrThrow() shouldBe plaintext + } + + 256 / 8 -> SymmetricEncryptionAlgorithm.AES_256.GCM.apply { + val key = keyFrom(key).getOrThrow() + val box = key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext).getOrThrow() + box.encryptedData shouldBe encrypted + box.decrypt(key).getOrThrow() shouldBe plaintext + } + } + } + + "CBC" -> { + when (key.size) { + 128 / 8 -> SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN.apply { + val key = keyFrom(key).getOrThrow() + val box = key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext).getOrThrow() + box.encryptedData shouldBe encrypted + box.decrypt(key).getOrThrow() shouldBe plaintext + + + } + + 192 / 8 -> SymmetricEncryptionAlgorithm.AES_192.CBC.PLAIN.apply { + val key = keyFrom(key).getOrThrow() + val box = key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext).getOrThrow() + box.encryptedData shouldBe encrypted + box.decrypt(key).getOrThrow() shouldBe plaintext + } + + 256 / 8 -> SymmetricEncryptionAlgorithm.AES_256.CBC.PLAIN.apply { + val key = keyFrom(key).getOrThrow() + val box = key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext).getOrThrow() + box.encryptedData shouldBe encrypted + box.decrypt(key).getOrThrow() shouldBe plaintext + } + } + } + + "CHACHA" -> { + SymmetricEncryptionAlgorithm.ChaCha20Poly1305.apply { + val key = keyFrom(key).getOrThrow() + val box = key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext, aad).getOrThrow() + withClue(encrypted.toHexString()) { + box.encryptedData shouldBe encrypted + box.authTag shouldBe authTag + box.authenticatedData shouldBe aad + box.nonce shouldBe iv + } + box.decrypt(key).getOrThrow() shouldBe plaintext + } + } + + else -> {} + + } + } + } + + "ECB" - { + val reference: JsonArray = Json.decodeFromString(ecb) + + withData(nameFn = { + val key = it.jsonObject["key"]!!.jsonPrimitive.content + "key: $key" + }, reference.toList()) { + val obj = it.jsonObject + + val key = obj["key"]!!.jsonPrimitive.content.hexToByteArray() + val plaintext = obj["plain"]!!.jsonPrimitive.content.hexToByteArray() + val encrypted = obj["encrypted"]!!.jsonPrimitive.content.hexToByteArray() + + when (key.size) { + 128 / 8 -> SymmetricEncryptionAlgorithm.AES_128.ECB.apply { + val key = keyFrom(key).getOrThrow() + val box = key.encrypt(plaintext).getOrThrow() + withClue(encrypted.toHexString()) { + box.encryptedData shouldBe encrypted + } + box.decrypt(key).getOrThrow() shouldBe plaintext + } + + 192 / 8 -> SymmetricEncryptionAlgorithm.AES_192.ECB.apply { + val key = keyFrom(key).getOrThrow() + val box = key.encrypt(plaintext).getOrThrow() + withClue(encrypted.toHexString()) { + box.encryptedData shouldBe encrypted + } + box.decrypt(key).getOrThrow() shouldBe plaintext + } + + 256 / 8 -> SymmetricEncryptionAlgorithm.AES_256.ECB.apply { + val key = keyFrom(key).getOrThrow() + val box = key.encrypt(plaintext).getOrThrow() + withClue(encrypted.toHexString()) { + box.encryptedData shouldBe encrypted + } + box.decrypt(key).getOrThrow() shouldBe plaintext + } + } + + + } + } + + "WRAP" - { + val reference: JsonArray = Json.decodeFromString(wrap) + + withData(nameFn = { + val key = it.jsonObject["key"]!!.jsonPrimitive.content + "key: $key" + }, reference.toList()) { + val obj = it.jsonObject + + val key = obj["key"]!!.jsonPrimitive.content.hexToByteArray() + val plaintext = obj["plain"]!!.jsonPrimitive.content.hexToByteArray() + val encrypted = obj["encrypted"]!!.jsonPrimitive.content.hexToByteArray() + + when (key.size) { + 128 / 8 -> SymmetricEncryptionAlgorithm.AES_128.WRAP.RFC3394.apply { + val key = keyFrom(key).getOrThrow() + val box = key.encrypt(plaintext).getOrThrow() + withClue(encrypted.toHexString()) { + box.encryptedData shouldBe encrypted + } + box.decrypt(key).getOrThrow() shouldBe plaintext + } + + 192 / 8 -> SymmetricEncryptionAlgorithm.AES_192.WRAP.RFC3394.apply { + val key = keyFrom(key).getOrThrow() + val box = key.encrypt(plaintext).getOrThrow() + withClue(encrypted.toHexString()) { + box.encryptedData shouldBe encrypted + } + box.decrypt(key).getOrThrow() shouldBe plaintext + } + + 256 / 8 -> SymmetricEncryptionAlgorithm.AES_256.WRAP.RFC3394.apply { + val key = keyFrom(key).getOrThrow() + val box = key.encrypt(plaintext).getOrThrow() + withClue(encrypted.toHexString()) { + box.encryptedData shouldBe encrypted + } + box.decrypt(key).getOrThrow() shouldBe plaintext + } + } + + + } + } +}) + + +private val pregenerated = + """ +[ + { + "CBC":{ + "key":"df7c618a64c19f669fd43f53aec2ca94", + "iv":"48309aa35bbcdfa64cb1554e12ea8a45", + "plain":"40ff984a7371c715168d3068e1b0a85c", + "encrypted":"6f4ed8e8c13770e7e5fa76e4877dff93244e9af688972c934bc55a1d8f4bd762" + } + }, + { + "CBC":{ + "key":"0095d55fa59664231e6d6f39ba8222f5", + "iv":"60e9a76855f828e88c387cb1a0653360", + "plain":"35b55d15f14b0f1c665e337875504a23", + "encrypted":"f43cce559f7389749422f837043faa66904a25f69e65039670b5575cfabe894e" + } + }, + { + "CBC":{ + "key":"91db7c66b70e2de78c90ca27912c1053", + "iv":"c2d661f0cec597f3b8bfc21fd70f183f", + "plain":"7fe174a0bde0fb48c6bce018e9f5483e5eca9c", + "encrypted":"3f7511d41fd857532129980028a65780ea6896a75615bde2a72076bc205fe41d" + } + }, + { + "CBC":{ + "key":"960f5ef96139fe5aa0fcd2b915567c81", + "iv":"d23028eb4bed0bfd10e935863c906c8f", + "plain":"02", + "encrypted":"96fc7f3fe46f7188190cba22d4c2d0c2" + } + }, + { + "CBC":{ + "key":"e14cc83ab2f827ce51612e86ad9892ee", + "iv":"1335405ffa93c3e7117a2cd8a90c07ae", + "plain":"43348fbc325da8265a92ab3616b55912765cb7fc60acb862db3682733dc66febe9041dd3743afee1870acb75176eb6e1244bc44a8dae58030a1fc03efbaa6128d3e6ac2b6ef1fdf3fa0a776419a97b61b96f4b617ad87f04144e5fe24f54091c1bd1f50bd9f22669264f865f32123c11975fffd2dd7dca5db4778cf83b4750d48a5444a4d6ae5d5b8ee192b811bc213eb104099833cc19dbde97f3e2370086ba09a1f3928992c8e2571271aff8d435766fc6005058442baef82bc61b40e3de0861345ed13578cbe3352f09254b25e4c90b7af709c94e35216ca2b4e27e413926cd6f623ef2050d9d2d89221ea60025e3db18c3dc3fbf60888c16175430aaaf965177b5b72aee0adce7e25c79171c710d987e33be0f02c1996254788c7d29132f6a47cd569b916e29b5e8af36511337aeda24a619ef1b96eb551e0b6b336bf84ae5fbed82c52ec440018ebf4d84d47ad1590d2529e757decc5f5699c99947afed2de1050442a47905a9dec0c673e0a8c5bf519a42a83e13e0e55417d4545a9c977e429d8f1b9b707ff7bae1bcf9e62848e23beec1c28f102504fa45597387f63ae663cbb1714038f2846f47d8c54f518b6680fb4ed7a36a4eef259ce0d0517c188e503ab1bbf98e6e19bf880fb72a76d6d2c0e2619337492c7dba35a181305b751ab675538e6db2c5cb109a1471de4a34f74f21f91f8216ea96665a44dab167df9127d10df9f3d8949fa8c2e474d88353571dd8cb26d2bb61500f0580e203af213ede2a437fd37e348966410d575e05ce902bc1bfcbda717d2baa51bcf1052c5d6984243fbc95b2c055be69aee55947df6686a115e82b0f755e95bea79819d1479f45f6cf7dc30c69a263b20814ade4f98f6feaa8d0b528e7f51e9f3e219efc77cfac5f28110f8a8ed90ffcc0d3eb3d77f80d7bfd0b55db910f2df8d6dfbaf11e9295bcd3627a19dfb85ca4d7eaaa15aedd66d8bf56cdc1575d8f9adec5b0b20172bf8344c207f1e5ea39264efc21882ee77593bf1ee5fc025f911ff892b611c83db90627adec196062b32ae06e1b9e6199c99f0038f95cf382b9d62732a66a6d5f7f48962c1251c923ab89c59112968884e9c9ecc55c664eb2735167ea26f7e3f6c110aafa6957160c82cb28f28427c6cc800035c11c0f8247603513c228b7eca4d66763d488d9559cc0b3aeaa04635682596a21b553d56f8938206181fe8eea35c971730c3107c32f7cdecca44a2ca8e2e576ecb235207c0c2dc4e925fe2da9ba46cfbf1304fd7232df638ccb75568a85975ee024fea800d01de3d1b92d896cbf9716ac506c8298e0bb5752ef87cb60cbe0a843a76551fd053b3416452ccd9e98fa5e4962f02cef73e15243b1f200cea675573efe75ed17a41571ceb09e1825844235e24cb0dd57bdd4e573503d4ee58bd74a30751e1e729c44e8337c4e1330e7c7068b9b2de12d45bb505cd58f7429e3e9bb7b5744eafac2990f7e2860f16257e840b4e1b30c420ed66660d802d2efb3d3e9d499d6e61b270b6abace1397f77bac089f3c0ed72875c664faf08dcef49b1ccad11bdacee882846be956922b1f42d5439a0ba2e8fa625aa89bf429d56e36e5643a01e477191687a1418da4dcfd7d28414403ee993d4439cb0b9f8b6777e62f324686af298d52041a0d782a7fefdcea52a1b577046f612a9d6f78c87752ed0e746ce6fb27970a9615cb17362f4544efbfa5a9dacd57b922038de2e275dd59ae", + "encrypted":"e11a271fb7cff36e2272b4810cee7c3b165b70f9c42dfb7d351ee9a1c7653414c34cca010bcfb35df7dc42aa80e553fa2372e6a363c32ca6f2f38bcf0bb3cf8de0f620d28b4034b02bc745abb3beed5f91e6a49fca1532cfa794c3345ac3664d5e04132c7d97ac4632db41916254320489a8ff640b8052dca595435a9bc883cdc3a77ce63d07f08467777a90dcbe70d01611f1caf5d2586d0ab7ee88b61957ebc51861f4f1788e16b69291bea289cf8de4998c2f44a6efffcb7fea6a2e0b9b8b5266291da4d1c20fda20141dd9a016393d521821f50dc590c4e3f7a63f9d495720e0802f5e018f746a1f72c2bf25c4ce258d7bbcefa3c77b800a7dc41666c6b897ce5c2edfac24177eedfc7fa03fed86d43b8af235684adfe81961c863a8abed64677e4df89c9e044b0a08ccf93d39ae0dc4ed9ca01a905e89976c5794a04f62e551049fce38028d871d73fe4a1bd26094e0319d1c6954b38c1b44e059e0264f608b5b5b7926910cc8354f91931010c2479fbcc6fa9ac881810fa50c0c748c9b592e1274e2c0f9533e69e270b819da8daa7eb3523e460d97cebced909b41dd48f622ef0f1ccc06dc34a29afdb160e252fa892cbc2a647296432383c11345340f4e8ccda7f2eb1a10273a4c7a9e3ef9bcb8e69a56cc63cc955354ebd1de84b3e48a1f34c51ad58c71dc191a1f1c072c14f450d48d24d0660b806e9e80b197dc0f538b067333098799c72c27ad6321d97a8cbb9cdc5a1896c66b3cd2c57f69ac68b7ca6358de783625926598f1e00b209ddc3e866e5a605b867e15f62cb81f136c981cba1f78585625873833d2d0aa5e0e6c8a07d75bb8f39f3fcb264d889ff871c1aea62373e55db2e433ce49b626127664a6fa914650c08a511ce87f61ceaf95300d9d464f42388c5ea26577fd0d19c7be71b37ff2313f2ca76689b78cee3ad29d2c535810f557ba7f406cbde193cf290e8e12f677da4a375e8b89cd2d8c08dcede84d1713fcb7242b4f2b86f5bf9a5c33696c757df1f21878f9ba04cb895911371f0255e7ca12316675feec90768bc050bb931e864188f78085e935842aece193fcdfef84982db258b861a45adae8269bfaa762964b8db1b62add051c5ecbb93cea59b91cc76cee28f99effe5148e6d6ed3d4753d8ab078bd15057094137e3cc5cc8cb865fd515ef01a2488bef0f997bd1440411d20dfa4754e266b1f3f02a73536d3236bd1656d7929f92e28dce2eedb8c7df08c98ecc6ede1e61ed659efc1c05358a87703db61eb75aaa141f9cb5ab47aaf7e9208368b79646202b51a459340652900c1922e43c0535e7e3ca5dd20c0554c3245a021adf5f956cfda6b31bb06c17a2019b48faf479b8d86f1978968e16f4c0353b7c08aa0143b872a84a1a650b7dfff34772ab66610b50e8e54c6726008d3f949273e56b65e9eb435077b34cd21f687427ad11b869ecda0fd54ce6aa2bc417604e3af09fefb25e7ce1bb630967e828062b6b6d931de1dce4f66017a36ca79ea02716eaf5ceeaba2945ecfc30b3a91c408a7cafd3eb4e7856fc9bb7657038c1b93a9547a3d7841ae892586e762188aadb02eb8f6f4cd2a9afbd4f0b6a498ba2d17373bea4716042b4c5cc53648b8481e98c4186fa554c89249a2b3e697ce6025d2adca858b48eb508a53485855a56f6966962ebae55a1294dcbd41d729148bcb50ee20f4c240cd3851b393ed27c6daf53065aef028b1bea786baa10b7f878710b9ca957fb99a68d93a8b8b27" + } + }, + { + "CBC":{ + "key":"7e174df5fb9264d0fedeeaae6f72514c", + "iv":"b1dca06008cfecd687ba199436eb9dbe", + "plain":"7da2054e3a2b9dc34dc24e3e18c2a720cddf7d0bc379521e3f68684b00efeeb7de7f2b7ef93a15073539b0d8677ab7a7f5608492d8e3", + "encrypted":"004575e024986ad2e72abeb015672b028cd77e4e203437e5481d469a799f09c51a7339068158fb54ec0de3243a1f61ac06f67b482190226137d0d0aeea345d72" + } + }, + { + "CBC":{ + "key":"9d14837985908fcff01317223578c216", + "iv":"9962ff37142ee4c142b91e7829652cf5", + "plain":"9d93f861409e192098a75e661de84d39", + "encrypted":"18c1818e83ca2b1aba0fcf663ca1378a61fe07dc1bcba3a5f3adee007da14b01" + } + }, + { + "CBC":{ + "key":"8d5f5c880ad141e86ae2be3545382e1a", + "iv":"506039602e90a323390a70e7323d6aa8", + "plain":"525809668e339c749a5cd3ac88a39959194e9e8dab71ad314dfa8d0d1a68dbf7", + "encrypted":"4a834fe1e6db539db0f2fe440b8ba69af0ac6e58baacf0ff139de01a71322417b49e3011b8cb1b044d574f65fc1ec992" + } + }, + { + "CBC":{ + "key":"0a003cd4308902c305e03b94f85b93c9", + "iv":"54a2e7d00c6ed6bb30aed86b8012a15b", + "plain":"a97fcd5736f5505320c6171ccbf5767fb832a5cf7b73661efc46bcdb2d3569d46553b4623b9ec8bba4d1cb9e71918bc14f6080372f6d288ffa7454b9488b822b90cf57f935f4a8393f5a110c20ec21b5e2c82125058a50aa078209c1dd8ee41a74c682b584169235d9506741df7fa3f7b20829e59c0778a268e9e77439b4a95a0fd636988ce50c3cf20baa93217aafec04b18668f60e663b5fc150c31d5cc67283a59f5b197b74d4bec063147bbd6b41ef1a491bf1192e896a376c654b15b4d34bf97a472bc67c91c2c033b13f3e16cd7c2da2262fa607c185ae90a2d5d6f73c153c5e7f0cc53b68d7216101274de795b4f684a5faddda0fcff2907a0a3f120a", + "encrypted":"e39ff644df503a57a596fc1b2c4c1dd940d8ebc576bdcc7a015ccb58202f9ad357d7f2825c66e190fef78073a08e3291ccb4846e302c3e41bb1e3d9a7fdbb1d580611dbbf1f21fdead2c24db6ba314e204c144476ab9aa74073b75744cae630a227d79e026bc11e5c5d1dc8029909b7e176e0598947a4e60b4c30193de1c1d16aacff1456887351b3462cdc751a6cf096a5653ddcb7d04509318edfe5a6e8b4a74388a3cddc3ce819dc19bc233cc051fe9bc57a90f0decd1b7447f4a1912b49857b94768bd8a878fcd409612dd8fe5336290166bd4e804ba7ff36b0c2f914bccd5542f30a9280001206a788c1c8e7c12b15d7aeeb33ce4836fd3767ed2b451c2c7d2c854e2c53ed390c12eabe9a8d8d4" + } + }, + { + "CBC":{ + "key":"004bdee1d8f0fed5bc5e47d2bc8768c5", + "iv":"5efabbe7fc8102d2c4c4eb1dafa7e859", + "plain":"2565110a4c8aeea15db815e46e55077b", + "encrypted":"721170d598a1d054c5cbd02bf88e15c485fef9d1527fbab64c00a9c3bd2a12af" + } + }, + { + "CBC":{ + "key":"3d0dea886321728a5ba5f36028bbd612", + "iv":"8b7774224be964ec51c651c08d6fb549", + "plain":"310dab0d6c2694afcfdcd3b96725b0c8", + "encrypted":"f839736d68d1785223adc5fc5daf0b11ae7cfa9a135f358d5e8d669c47e10cf2" + } + }, + { + "CBC":{ + "key":"96e1a40fdd4bf75982828ac2dcdfd34c", + "iv":"eb28d9b3736a18a98497abd4c8ab5cc0", + "plain":"38039c0148c797a9a3710b5b93783ec767ca11", + "encrypted":"cbf679a7d9d55fb627786333fc038dfaf68702b4103d70dcc5a56a95e51409a5" + } + }, + { + "CBC":{ + "key":"1706c0f1b27556ba9da34ca169716c11", + "iv":"e648d0e462b0117e15d5d8e1054b3ee1", + "plain":"b4", + "encrypted":"4d66a4522a4683f814815456ca427022" + } + }, + { + "CBC":{ + "key":"f182a56bac365c64b1450e1906a46767", + "iv":"754a19313e9f0470d8ff044053bc0ec6", + "plain":"ea6dab2a150fdb0ef6ca9e4b1ad8ea0ef45d2274667b4742ab8c7fa0dac4c24df2809e63d50397bf8a777ddb1d14cc2cef15de54b16da2c7e1b04e95ec2767c40fd3b0b8c72e5268828f44fa4d95ca3e32bbd5add37ee5fe2cc1cc1ced19300a96a645a8857f4392fd1b713d5b095e0b2c2f4019111318909df352768658c618560060d263c97a168791a0f01be8a9c706f37d9289bcb38fac71f12f9c8dd609323b6f61158365253bcd1dc64f70d3c3215b381df43dfff3e07d8087db49145c3e1d1d68aeb0c6864611e60f9ffebbed70c0d5f9a7a648200b7b2ff383aeed227ee977993b18b0d26bca3dbf170536a1b72a4b57134a683a3d273a5c7a1aa9d408e06332f33473b2e0e1faae1d22447e7f6cd0c70ae57cfd80db2bf1f4fe6186fde5624784f7c2f5448594e276c571e23c0e0af02cff03c99918044eaafea7c72aa005184fd3556f1bd9dbd92f067a6642f5c80574a66b4258a669b0d4c8ed806863dfca6e93b9506b67701d9f18d766d40b00da097364c3c5c91ba762f68a5bab7689db2a506fcbdc7830e055ed515ef2187ddd3f86218d9324aa23edb0b455b6dea203c1e799a9109f56c8e4e1b152cac43b6702f8f5b7d2d6c5286ed33e0ca7f773c178c4626ceadea517a0f9911981acd1f54585c437371420f015331456962b65febcab6ac2359e8ddcf6df0a61d797dac389f3cb3600b446fb6579794e9fea8e4d6c7c5c651ae46ce0a356214b46a2c2f042e735494b84ec93d4ad185e088a96f3fcde3b7e9d1951b3b0ac1b6d33e51c44b235fce7b67fe8e94538fe85eff9ec3d16789617c91f0bde7986d270e8168e2092cc788b4e5a220f089265b106652c60e7e24cd70ca9db87f4e860944f8cc785f78f3dde253f93b48ce5dc36355750f988797e1e75e054aae2bffcbfbde4200126ed7b9b832f7ebe59c759903dd5617d09ad2371e540f30cf30cb103761982cee5f0dca0077721a21379f88e91542c86923f2dde36dc50d57a78bc24d31e397369b1171f3de0b88e31ec7efb2c13daa0fbbd1f00a0df1105bc43fb95fe3cc0a2c178d40288b574dbcd513c0054843d36630be17ee221f574107c4861b4ce5beb30daa220997dd3a1cb53384b70f8da7b90cabfb9db4a36921276a38f15c87658f1255973bff0ae1befc11a132eb76eaca8cd334315914b5f90a9b1eec1d893e89ebb6f4c0004130ac3a0c54c795de620f6263090378d4e4ef86ca76ed5faaea6a7e90f376ca9d53e0cfd54dd1417389875f06b62456ab283342c478cc1853a0d3f1daa61a0bdc2befadc3bb4e214d6565239f5e25e33ee5e94bddcdb5b465001d5f778f76c8ad713423aa94e42dece29624889ad3d3343589155f87ec225bd0989978589fbf6b386e1b9bddaf6b14c8357b1c9634f17fcef2fc0cd8373d676ad32126c72d73ff29d3727e8bbee4a6569a6b027e161ee15bc5fd68b0dd806a072a1822996260ea7d2117891ad2532f7f4dcdf4ee542829fdbadbc49c2dc99acc0da5bb39bdbeb64cc5e958398545492f7c7f7c10f13d28814e9a55d0e81adfb4fb1c44cc818c121efae903c1359c07c9ed30b2463c37cf4e814acb8b2800d6d1a62597a5b0922098bae64538da468c751a5b528bdada716a096e34b099a038657d4259cd7833e4a93219b070f156d48069be7fdf0aa13d3afc03d6e0d9a5df7d801dd659a10894efc79e6c8ad7ce85bd301a3794d100798aa41617ff5edb1", + "encrypted":"a753c9d4507fbc306f1e1d90bd0cde75d2f25cbb43663ca9e2d2cd0573c530070fde6c89bc31248d95b4bf38246153f70d652804044c6ac96b07753781b07147cf0a5ed568d20b364c713bdb85adbdf630cc52b21c32d6e970c168e26a6e81d5618b8b97aaeab4fb56a6292d973e4bbe2cefa302ae4c7f7bbe4d4cf7455497e63dd9b60d31b8dcde2ce7bc5ec38e1066430fb6a9302033d4ffb6071627c3be31ece286d61322d8581f8d30fda3ab20f132f29b635cb205834511eee469511595965cfdfece02c0c087ce942e2f75b7b7c820fb8088fe9e46c8ee1caa2f5625a24ce3c22d9f435c17be3cb7e260ffb7fb064e90fdd82ce475b8f31120db998b1cd69c530c64420df141ba458f7840bbb1a932e5ce00077dec5d7a3fbba77721da38f0cb427b69aaa6c02544d56307e221728a4113a19b43cb33e70e992ea1ef4ffa9cb1e1a5b8f84332b1e8117d2381b5f9937b264ceee04215575c9b4007b9a16b0cb3d47732e5a1f3f3b78655de65df987cee2ecf92fa9fcf208540ae20a74bca06f3ae45243b35c6a90c1a6ad966238d3008f30d8a1801890a5b66fc63c1cae44eb3a21b96cb5c6fa36f4a4df15c55419df5eeceada404b182188a83e332ad5d1dbbdfcc240426542f22dea614838efd09eedb519816de0169c6d37da3794b1faffccda6bcc169730c6ba08f7858ea1ae66ec794c3e8d24ba73eeb81afca99524d00fa71d8cad8fcecc0ebf8a275eef04f734c5d0149ae28d1b24e51a133880bbcd427d8d680263061b33e5a57b78aa6f9c98347b37c1e593d33e56fe7911cafe874cce79b3e3eced063dfbbf3a0e7e41b086ae4b4dc9a4bb1fb507d62c53e7279f748be0dd3a7a8f663dc439e4335d1b0781b122ef48d1015be758c63d0a663eb04629c3e056f7bbeee501f64106d5c9b3117026b787206e6d3f731020d136d868de70491add58330cfae0919cd5d5c9bee1b7a1f9a9553162fcc9d5ea7a2aed07c6318212bc078f8f889fb3a9b9e3e81ee38e27fb27c57e25892a030e1b948d3b2f43c021e34725520b6862b5a4b88df9eff03dd94d89cab3558f9c9a0ed889de0cb89dad18bb9ca065c2bb0dcfbb233ffe706bb8dfc8954dda3eee8513c3d047d05e00122339d01f066a7ef7fa6eed028ce2fe467c0d7be9efbc4f3f51779f55a16cf31da92fd969d2b628ec8313d37368af1e5b5cb25fc842e8c30a6a243292a908d202a0311d299e45dd7c3be990505d22e2d8e08090ee510bed1965395e10655e39786ba07890dfd2bb688613561662e603654a6f0bc11e2ba7ce0c477e7035cdfd5aaa2ec651ab949feb034e320804a8bd1a83e10784325eae7ec5782dd35cde99cfaf73cb14a19185c8901472546750cb2e64bd6bff66630bbc696244ea976afa2398c10cc1fdf6992788d90b739c4390a1a252183a44b67aa077a883519af1b48fce18f0915b2d2f376ff891c87611c1443d8b8a60c73879433a07e045ebddd303a3e404af50ab2545c47924ffb7127a2ec71006a41423fbfe630d1340e13186742de9cf849e6b6e5c8379ce91c81b0435dfcbe5398d83b7a29d7e921ca3446e497f6dd609a3573145a2183211f6dd44ed73f26f23a132912eb02a551bbf774d4e39ddf27b044d881db072c2ac9c4b7d8f965a95c39a86377cc5ad49a5e6fca6cfedd65144c98de8354695867aa2bd389be0fb59d3cb6f63f9eec3c3772a96aba813e2997dfc0de5c516c2478031d107315f5ba7e5d4b486af23b" + } + }, + { + "CBC":{ + "key":"d53c804e54b5de6663b641c7ed44233c", + "iv":"d53f4ee8c9829aa6aa34e3d766818b9e", + "plain":"d4a2a3466f023b2990da85c10c39d372f6a987595b9db4c9b1c3a25da7aa5937a1ef3b19c99f3ff2c952a1e2129f20ea2de1a08b267f", + "encrypted":"f8c0f83bec45faf670aa8b171a8414c29b01140b6ed74d53b00553fd1c906cdec5881b550089d0a8ea59067d9b9429f2917079bcaa49933b2a4276253a0a58c0" + } + }, + { + "CBC":{ + "key":"f7e96d4a5f4f5abe9415f39cb3c42ce1", + "iv":"45847c779469b0e684809fdcabfdcadd", + "plain":"4e59ac8025aec44dc821f727ea815275", + "encrypted":"9590c928f50253d704647f9794630e30415440937e03e82a0f7cd0709fb3a20f" + } + }, + { + "CBC":{ + "key":"ef8df1a15fcee46db110eac6bbce8768", + "iv":"1be383a46fc24cd8e0d391d9ac9a9801", + "plain":"3c13649c59f68e8628ee23d37b7c5824c9d6c8546d8c8b9826126724250d10ff", + "encrypted":"8d22faaa7a98eb977bd04fca2e6554acca89455b90b11c2395d473e6891b816a6e73d7316bd76823a5b4fc6e2596f678" + } + }, + { + "CBC":{ + "key":"1ac5e1fdae1829d24293832fe2d5f52b", + "iv":"cceb3ac58ff989f077a33f3aa71a7555", + "plain":"0a10fd59ddbd7faea13081e9cbb4d37e80b882b7c745ca0b9446a3c49da0f2caf7e475a2f264a64a44569698ac0525ff6ec6a512b4f81612bca68de67f5d7af54f213a9de22ecddb5541236dc076a49c1341e83eb337263ebef8593a83f2220050b4a2d254d25529323b0ddf686743872170759752e81c85856e2df82461abb9188722e3bc10087b8e6e69df77ebc3735ef8829108e8c9e6e75f25900ebbebbcd591fd13d77dfde4727024c5be39d19466c09bf2aacf5e1af9692e687c612730dd5bb1c3fc92cefc3638e4eaebabd02c0169ca97983abaf14d66a57898fb84eb55808ac2959f251d1f68e54ae8d6e805e27791487c251fb9af4162c62d5822cf", + "encrypted":"4ffb28db2e7a35749d83b70015276490b68f8aa66d1ecf2d190921e02b74d21918e8e0054291ad922a33b1799a2f3c0969f16983efd1f36ad7288182b2958b1d9f21798b0555a66071a3106b4625887a26690b391221948e3166ef309090d857e5c8bba457643d0730671f718c523746b5e959645f094d415445e5bca498e6e72873fa3fc57947cc09094b4ad1ef1ce77d0c341603d4a03a30195bc65f23ae5fd9c2151902c360c45acfacd53369496518de70265b7fd258fc86184c9ee2086680a508eff545693eadd99ee1bdd64562fe1d62f77aaadbb517bb2c07de8eb3b72aaf535fecbbbae26d1fec3736cb04b529e0a0b36975267926660a8dab940bd91c2ecdab3e3baf755306d49d1d8ecbdf" + } + }, + { + "CBC":{ + "key":"7a15ba4c57c6edf17db0aafcf922a67d", + "iv":"108a6a7326a6d77d97ae66a06ef036fa", + "plain":"e91bf759f9921e97d5b2bfa1e716f1d3", + "encrypted":"f96d2e39bc82b3ae4fea7a6b845ad02b101615c367134c2e8eccd4a931aa9756" + } + }, + { + "CBC":{ + "key":"a126d5b2cb9c51c72be507e67cece827", + "iv":"b87827d1916753ac06c2e9b89f22556b", + "plain":"2f7390b5c3843598686e80fe73cafdb1", + "encrypted":"d5172f1e6724a58e0622ba4bcad9f39da00dbda8332507b3f36899ca04e9a5a7" + } + }, + { + "CBC":{ + "key":"dd52668ce7c589efb24ee9d5eb87c756", + "iv":"ed16c91eccc7c016aaf03834513c28df", + "plain":"a89c755832526d3a48a99f2a72d0824b515796", + "encrypted":"fe4fc65d7ee5c9da814547d80086009d344d865034a08b0d04ebfe145005a98d" + } + }, + { + "CBC":{ + "key":"eff15a49eeb7f026fec1aa913017a4ca", + "iv":"4fa25e380a384ebd4f2a3300f4c8a39e", + "plain":"70", + "encrypted":"71fb6707a3253fa668f3d7e26466a708" + } + }, + { + "CBC":{ + "key":"ac704c25f6573fea6e492e97bd1cb380", + "iv":"955b4368d84f3b0995e017d18ddc651d", + "plain":"f48bf277a641595ccea111b6fdf199273a1174d1de0a18e197914f3cd4e024eac4d142e85ebf9c5769f73195f8f2c5213d3e091e78968e81163baf8af0ca4b82b7f73faed2299b31cbcf939864f0eef0e15d74aca35f78ecb683f43c2e960cfb662c1ce08850b251173ca83b6e42315e11abe04113499d9e4de4fae317ed9e5a8fc964727732f4028747bc417f7ebbcad4bad52fd15eaf1f8571b47e51fed8104da874010672cb7fa5ac63fa742224145223433d5422cdd44c76b11f39476cc4d97359f68c96f0e9643be86bf32853b3aac3e7f353198ccadf3f340daf1c79c5e73eb132cfb3a0ab8d274c01b90de21b5d2b2117fa0c7f328eecbe98d97d415864c3f46103ea9791924133db375c8ccc5f644765be465cd017350760615d755aea90cf78ef4a9d0cbc2aedac3dfb4694ec8f39feb47c4161d3fc95eefffbbafbcb0b7713414ba751a55664d1d51b9f346ea5491ff01e4c2af7693214208a9d25d1ef150226ceef17bc536d960aebba3b3fe3ef95da2741e46d6bc6aaf03260f6a368e7b77fc830c9f8e11c636f942e608d962a98e107820a5d0351a4a9f794ed5be148ce1a55ce18a0b0c654a94c26fba8c049e930eb97cf0982acaf7372776993d91fcffe270cc8459b05a519272d807f3b4e2f92ad4a5fae729ed8bc19f8aaf8f3fae308b2ff8dfa32dec3d9a07518fb2af476fd01504456e7f4e2d6922d6281ec35fa42e9c3aaf8e876fdec5597b43f4462b092dcd2cb1c48fd30850c28cfb0f99ff929b50208cfbf8dcfc355e307cca7eac1ab488dabd1eb0b058ce87bf5661f378bd01c769b8e81dd7d898f745d1faf835bf3d798996c74366cbf664b33759188005fb6f47e71657846eaec53139f46e8c388518a31b44d3b7796bb6540fe976969b2d10e70087f9ef4157468e87065157ba17250883608502edd55d13b052574cde9614d0f969ad6abc8eb2e46cdf531d6337f3d7e1e16e9e41558b174b1643f913f6aa8b814ae6626a2f7f8f9bdf5da400968fb03ed693dcbe046d83fdd2c8d2ce1bc19ff0f4738e97d8aba42e4666f4f10e092028125099d1fa6b0bac4f783fc2f9d8b26270f474e78c8fa59a39b9e1285e2cc40d531100a7a57121e1352b8192d41cacf8307606ea840f17ed73171d868d5215d6281a0e9c73c41d0b8a0ed83a3d269704e39abc0a69308e974a79839e074f44fbcf9a987fd81a797706d0dd6d8914a4fee5536d58490994c672112680936685bef6a2b78b3ff9ba9841b0e89883467ce3b52742bb66455db116dc2f44bc9dd8f167a8d30c01aa774b4d807e70933d6f648bb26d38458b21c83c11d1a1bae4dbccbd0b1f3dfa7cec7db085fc079a54c56ce2105ae9a0e156bd4533d55376b9ed5327aee7c39857591c146456f18174ff5500422ca40fa835b6d31a261d4e97ebb11feab2fadb69a6100adaad9937d8eb6623549135993f3163de93bacbf1eb4f592a4b81ec2058d6766ab4c60059c0af46471cfd7c90bcf9b6ccbf4958567751e925c4f743677ccdefb60b90137ad475bd2a13cfc6e9d648dc538b1f2ae5a2723346f8a806423f8c1d65217b85d9c06107c13ae8657094652f0673ecdbb66417ec6c5ca3bcfe0aa21b82ea6b27eb98ca993661d9a5191c0eacb6d3bbc96c5a58643eb00c8db1e86da8029aae3edaa335f670e365079327b5ad2285912d98d8321c69dae26e562f8b33c64d9bcf8d6edaa9a81cf27dc4c4d8b7ef5", + "encrypted":"b8b9a18d0324435976e9aef1ce94be1814f3605bb4e3b27cff7dcd048a9f482f7ee4557e1c858aa295bab466ea83d26643763d0b9f74e058f74dbd2417729fba4080980c94cb7a0726e17723e92d6b24afe7e1cec325dffc09db5ae4a23384bf7214a7a01abb575eb46a8c9a64607d11c18df82bd2e736fb10319a1c5a17cbc16de811813e3db8f89b1067472ceea0041565f926ea2fd4e96565ac32332ea41634eaae75c6c23ab7e9b065ee285b3cf8d77a8e02de5aeedaefca9f6a537a5e49936292b8a20e15e3115b5eab7eead29a0bd5890d687829871f0da17b32197ba38414114169272ca853259c8b3d34f575dfa9efdf2da99ea9f6d1abdbf0fc0ba35f615c165cc0b87d9400ba1abed6a5a375c7c66af749c26a9590deebfef794fe24367e28e715bcd9228fc12d43637d1ff7951f43690c4e0198910f47e1d96a36e7ab684b22a1bd550e87c5d9b2b8b940b83fd0c8ecdccd9fe47c0b51fc74eeac90134846a0e7d93654ca8fa9cbd798756b3de56b5941ee92b4a38beceaf05ca10ba55b7b02ff58a8c9e78010905d904f182464ff83710c3b529c014c62241b279184cea0382e9e8cdb42cf5f39edba1cba9b52d2266c46399e19645f2c1560da27b7f8f4b0daefc1dc5b092764fd867ed43bab76f223565b8d060091e930f71290de0a5f028e776a61b7d57edb25edf5491af97a33714819253d7f233f7f4a00adab58c93411d490ea92c25af819f6ad56c2854c6e6c0122524a18f17ee492310886a50de723b5d1b07a52309c5f1ff36c0779db17bea25b230a1ca21571bc152f27ce399841de06afb6098b6db84b48913acefa4b4892bb611e73cbdbc0069ab3294af7531cba8668c7beeb0043131224f0d908be709f0f5c6de4459972bc5d715bf30e722ead88a32300ec522bc757a51b26c5f962b9ab1a7e39b872d9697a018b1bbdbaedac6f9e7c49e0bc3a716688c66f1de4801889304bcd184ff8b1da5d20d1a30c6adffba91832b89978b8c6928ce32d71c272d5f1695537ecaeb08eadb575135e3a3ad7f995dabf092e710dbe862c8e39889d614aa19f4bfbec9cabb28153bde1de70f85eb574933b43fda9119e5694503d5dcadbf4166f118062c1bfe74d969ace0782daf1f3c7c26c5bfd2baad278fe89b5c5ada626d742152db4558af49080191fcf171cc2a0442624105a9d556f2e76bf2310b595a3f3ed9feb97adfd4005bdd4514021b7a8858e805892484f9e98d6f92022791dc27445fa7e24d104ba1570a17f0aeb177eaf09e54b56e7924035fef7744a6ead1a7ea712b51769a576d56e0bbecca27051b927de21bc2328190ae871beba2a783022fab559817be1b8b686320d95e17f73cc7348d0c21fb6221f7a2076ea97655efcf3cf7e65648796a50ba6a6448fb2a4fab15b10aaa5bff77eb1ff8bc17ed22a957fecf82eb090fb7248b18d0c5b535880f71a34714fd904eeaf0b79e57faeab83e93a41cfed53192f8744b66924e33770dbe2e60fc4bc4851963abe61ba4430daa49384c8922fcdeb8195fe18a139b20d325a2a0fcc4bcbd9e71aa84c2fc09bbe8966afa1250ea673df891ef5dd554a84cde35df9dafda2600c6b277a6ff424c3d5ad84674121f23db50ff953bc6ea3898e567ee8c838ca0e593633ce9de1a10765f41a226730a43989661b7353465d3e800fc3daff0c649afcff5c0bf287edad97c988618ec152a954a822292f860bd91ebfa9d09d2bc1e1342843fbb74c37fed88046" + } + }, + { + "CBC":{ + "key":"ef0762bdd3aef9d27658654cd0b4a2ce", + "iv":"f547d3f1a6cde55d2e6b8eb6cd033b06", + "plain":"86be907003c0ff86014ff01dabe116bcd18a4cdc274c2316f200422eedefd40ac725c0536ec59e29b7b8521385440672c4f243aa89da", + "encrypted":"63f04216130aa2ec317793dbc10a33bae9faceb70d3cb003ad0c76d2564e0678555afd3f86e10ce5299eff8d2de67a6964caf4211a16adcd31002fe7d5d297bb" + } + }, + { + "CBC":{ + "key":"5bf291e95ae69a519af8bdd7137814a7", + "iv":"22b5882e067e1d6a27ab3ba8aefc3756", + "plain":"3f79085b118659edd704f5e259b49162", + "encrypted":"d17e0b9e26a520f281da154c985804e4f1a1f8bc81de5af73bc321270c51df3c" + } + }, + { + "CBC":{ + "key":"60b6b6bece859a774d99e82d7e3edfad", + "iv":"737e7d85161475811cca2c79cea4cb97", + "plain":"ab0c8ab1d3c59f67f4da0a40fd2909021d8bbf4549ad135ed76e2eb3203c143c", + "encrypted":"e208069bd9e8667b19a8cfa5d0dfabffda2ff228b99c66369a3d1c3e9c2aeb8d2878c1100e44fadce43ff668b3dcb9e0" + } + }, + { + "CBC":{ + "key":"0a1e3564dfa49aeb116505278688d126", + "iv":"8f949c004f7e46698fcbfbe3ed4ba373", + "plain":"11c2af24fe6758c399286833f14ab2db9f82073b8cb002c35df5ffa1a9339a9b8dd329ca526b070bef203726abc549e1073968c50bb8e647a4d39193af5184492a9b21f84b3cbb73d873858ad39d336d383c7059248d2e9ac5f5a2fbe0f8624727ab5db325875d1d549433f86812bbb8d6b82b6bc9d072061adac8d0a9db61c574336c4348de9a47b413793f02be8990b48623979890f7f0b0bb8ee79b48505242119ca6ce5fc8efa823b8709423bf9ca721a526ac1a18d5b85a1bcdb63691d4237037735b035435d0ff4e21684ca670ed5f2027ae0e69cc7fe32d54ef1f97b465085ebcf8ffbf90104d19713aa24a1b41805ef7fcc762b8e8bd392177c2e33c", + "encrypted":"b8ff8fd493d2670adb7bc45779cdbdf3b530248ff81a6af9ae92fec40cf33f63ea7406a780947597bcbcd1442de7c25c91cd6839d09f40c8ee3ef8beace9a535f988989d50198b18e2d9ecb7204edd9249a9ceaa52ab2386225f22eaec77d07d9457ceeb704f016006e3ad92d6db693809b852cbe19cddbcd5beb0d906d3786483e85ce9106b3609476bc35fb41e747556d64907dee635d126e2351e73e21e8b00ea7a24dc53d9a0180dcda606b948fe376a8572eabd1ec84067436364e92b213087c6ad4f5f802b1b133b0f5cff0f59f4d87bc3f2ac5937f0eda916143aa7a0fe0dbb9cc700dd7aeaa3807c0c6d52f981d34fc399cae3f0cbb6ecc911eca3b266ba46064f36f3eb307802985cf8a00d" + } + }, + { + "CBC":{ + "key":"d4db3572b2d96f3f7f9826efe2c4525f", + "iv":"05680daac4306cb93b73a6bffbc5659c", + "plain":"834044463f783ea74db1ea3805f7662c", + "encrypted":"33418437031d4f57d55893d60614602ed0fa95169e971e0eea6bccbc4c7cb6ef" + } + }, + { + "CBC":{ + "key":"665eb7ba5461ea2ece46f3886015028b", + "iv":"4db5698d8a2d69cb71c69d7fca2ec6b0", + "plain":"976231b0c04ad1e442c1129418f62ace", + "encrypted":"2039b591302c72de2d95d89f88ceabb4843928f7e6c63dc5d04a871dbbd12be5" + } + }, + { + "CBC":{ + "key":"6dc8bd59d7263066613e71c1fc67fba3", + "iv":"01a80bdb224747252cb3876621b498dd", + "plain":"b76a1b8a514d2042826ed21cf6521756e5c2b7", + "encrypted":"c115bca36cb954867c920b5375bc976af0135ddb104249858d09c90d5b90de85" + } + }, + { + "CBC":{ + "key":"a98d6f39b9f049b5439c65659d0fb625", + "iv":"c68ce3095a0897c3e2fac2a5b9ccd736", + "plain":"8a", + "encrypted":"6f85b5a4f1f4d4890fef6697612b1e93" + } + }, + { + "CBC":{ + "key":"dfbe169ada3075359592cba3648a70c3", + "iv":"dcffb8b7dcc75d2224cf9be01b5d55ab", + "plain":"c45bfc2b555eedb4596a38b7504737c73b7835a0a0dc5fa66f60c993197c6676ea0340b158ab88f3ad5fe564c5712aa66e4ea962c5415c58f0777466e5e048ed0583b0cd02e85880ddf30dfb02c0375b0bb3b56e8967f1727b3dd2e8efe6ead189fbf799fc1e949159e4c170b190bbad34b43f9630314ab6a8d56a19c96f360994c9f55619e22049861cfdce96bb9f0e8d95947d2be825d812d2ded4e0f854d141316f70c9bbed3348d020b8a9ec0f505e8ec61f193ffea628dd7c460f559105b63de4f9efef51f7fa1b18570e09c97042644d82beec37619fc223310936724b1442f094a7d36986e17a5c085b0f56b13a58b26b668646e496bceec9e71e04f744fb80ce60ccf4c20806cb48e95c77444d04a836cbdbf80be038122479628f63c5ef182e5874eeb9e6a1d58103b5a959fa27dc9a0e82f9053da9a23bff84e9ee971932de6c3909a8be518ee955557a0a486a1c6887376310a2b3df68c558efb5a3676dbd39d2c81dccb5043768635bc06a63016883312b8ee6f5c2e038e3462bbd5b0da88be13b5d91b092c9249aa72089349a7f2d68884e5f4bab5f4a7e40ebe935b4240e419a403cbfd2bc04565a205a7af21c146cf4092cd59f167f78955b6dec1a0981a3a6ea285405128ccfce3b0686cc6179030d9fcf9a8287bd5427e72033b06aa254ba16c053684ea634fa251d344f9b91becf71428aa924c24e7e3fc98093c602e0578d681f0709ae2c572766443eeeafdaeb1ffd87779f9e96e0573074a813a57e31c4898f8f732f7239b970ba963d24882e4e038e798fa46633678f3411c3252d79466524eab225316ab57f540319fcd2301e73109d5d51ea9c61665d7e8c3e7902053b8c947eb750b454e9b2dab22d6689bd4b151a7a61eb42c29cc8337278fc278680f6721d265f4a5fb92593494967da7a1fd3dd5f3f7f4ee4951cddc245ddf55596138412c73d0fec49ceb04cbb7c7af4b7fa68543926d7cad020ffaefe9aadee8b4303d4a71a3a28f8dbff7cd11f9dc5d46e1fc54c6086025778e25d9f95204bf71c5434385c5d597667b379f2b7caad8f627b6dc2f43fb2eb00603d883f94a9ceea840c9acfb376c4e39602dac058d7b8a6552f6f04687f52f3950472a4c9c58521aa6f38434d8835e223c5e2bbbf15bff3e84d2fb28d68625bff6b51e78ee8ec9748b6723771348a406e70ccb67d5163769f70f9900826adb1787e91a64d39ab82354e979c9b9d767d75df12d065de603cafc1081969be5a66a723c658d1cd567ec9dd93bbe8afde8b8263f015d6bd55cabe40abb3b8efb3c00c64883cb80b2a850a0fafd5fba364948791a6f42f39ce90b1a83612a0c629fd247b3eae4f372cd632d72246ec80adcda2b1f15b464d144cf752d421bd54d94bfbf4cb341873422e7661a2ae5576bf28c1b88ddee55a1684a404cec8679b62906b2e07c14421a3ef7ff213ebe890eeb40190a2d5ca97fb607b0edc7576c95fbd8452ca9e5feea506dcb8594332650b0b511a8fad47bcfb47132c4427bcf0c95d4634e909c79dd74dcfc11a9a3f9a7f4923feef98d24848ebdcb3d9974285225d2b5b585b365a16f7117ffce5ffd253060f439c6fd89fc4e906e36ab282a6bd2d68e90d11ab231892bbbda8284910834b4ade6257b093ce64c10f0d19c6096ae07be98218d0f12dd071b0a0b625e79c6b458148f2fa6b405f6c819053749cec58b37d92b2d750fe923c39ced6a2141139", + "encrypted":"6070f59813638575c814fb2153d52a296a9fd4960ff88c529682eada0f878bab0c5ff6e1d026e7ee1c1be32750b2128ccbbc989744b79b6c90dc4e286b4370637caa1e32f4c1b422a48788bd0b108c2daf6d8f62cf14e72986127fa2d5697afd9c03d5e8366f17a8aa2b21f6acc12c29bd4121b906ad2244cab1aad41154ef0da74d9cf154ba9b3976c741bbfa7dbc21cfc9e3f4fd3394b095d50b8cadba5132068c6c715cc576189ed01a3d4a8dd19c41aa44efa739d34dd9ed1a8eef0485bba75f62c5f3181c0a9e68eb8d204382619984ca8a6650ba868ce497fe61a2760598acc15f6fe2e1ab92b0fbe933ad552349c5f9d212e18e81b9d3aebe25e433a0466b97d5327ca8de99dfdf98296525a89c1de1d46e87a398be1cf38d086fa9fe85493dc1bbe0dbfabf472551db3766c1c46e84cdcf06f5231a4b11fc692923c4712bd731b88430fff0e51c9966f17a2a777ba4049f359287878a37c6ba4491ce3b50a66f799682b57840e59e2611d369eb81fb2fcd9fee597ca616442413849008735ec6d0db52089de499cd359646088477dfa13bf5944f06434ff85e1be9262a519c418f7a5601dadffccb1350e97b4ce1f334a99b35adf20a9e1123ec832e8fd847ca83b466a482e9e9205eaef536dc3a7aece459a163dc80e41324e7cfd51cf5dcfe3c0c299efe406f34a7e61a0225298ff41995af54f6a0225439a07447981b6bb14ee043ec2cdfe3f21718ef7e67bd72ee9baa2ab80ceef6d7372d1534ebbf3139c5dcb925b53001d1d1e8d54e33a2db1bc9519f4db1e5e9ff4573dba97b13076b6746c647afd5314de26ad0d6f7a5493423177e039b7c5b8131cce9ea2a3b234fb7cd8a430dfd55850ecc2e0473e98ffe2ed61e01e8563318dbd62386742703b8c2fc2ba78637eb9ad5077aba4d04ff8394e14f105116e3409e33341d7140c8088bfe1cb2210881cc2b9383944aebec573ec541436737c1702997b0170fd780a821ac9cabb831d80b5b2241e657789c966f9efcde986564bb54a4c478e8df39b5badff58e7329a0d17334879db99a864b37794bf399c123d62ee47cc07a6f24a9e8173cc9f20e7f4993d74d61bf5fc92c70b526fe1b5ed2662b61fba88bec2c531d586e7fe63d4a0f17b5cd40d3ac1cf2473f4f934d5ced5871c07e854ac688971be2350d483e77d327d028ba4ee41dd73cfcd97274f9813a46460ac7384265e7e664eff07d41b5cd70e2d8fb2b4525a59bfd37e24d5550d643bd07af4581dfde4b32823aa2a18620488c75484d63fd510b23c1dcdcf0fec9028537251506b2011d07477c1b88b713a92d8666258fc0f9cdafbe9daeb3aa85c664ac7673c6a5c076484a96ae446d22498268d40411e16c59640dc67e55ba54bb1d77c7a1bd6b9a1ba3901e676a2788b96ab8d861c89add3b4cd63e84e084acb05b4bcd37fb81e75cb4f27a77e51757755efbe0cde31e06714c3620644eada328bd54adc6470530ece9ecbae4aeb254ed96b706117da5f9e41f197f60c833bf991e8a2b398863ea85b01482810676ba80b7ddb800d127a1c72b5ac8e657677daf33823843fcfdea61aa152e45aedff731577e88a4c721e95069324b703bcd6936ad72b55fa42db2a3df7c03c10b305113d5951b2f919ecce70dd8ecad6895d18c5552e9cad8f90ee11039f844b7803dd60bb4c1a169732ca5e6504894f4fc314ac7c5e1a2212f6fc99fdedfbcd49f192d003ca8866541873a3a4f3a329df5038402d8c0" + } + }, + { + "CBC":{ + "key":"3054e98a7db9e232bcc867a20fab660f", + "iv":"6d69e8a903181dc70522368a08552079", + "plain":"571747b09f9ad08d80f2d2b8861092b29e754554ae14038799bfa2f84c2da065ce94000fff7554e7730bcf1483315b756ba2cd0ea9ab", + "encrypted":"f8d7aa4d2d5c33c4302f310c6bd3ebde4f3fa9fbae715bbc5b4c55fa9373e76ce7bcbcd63fa1e24734949c373d28f4d88ca124472537dc2d916820a76c6f9d9e" + } + }, + { + "CBC":{ + "key":"d5fbc3a2f3f105778c6a5ec121720f7f", + "iv":"1ce149bd82ef5ad340a8795445651793", + "plain":"68a7784c922e71ceeec6ad2e60070daf", + "encrypted":"bda2182f20c1a25151c9d2193839e86f40eccc432f63caf9d341bb74e7c13edc" + } + }, + { + "CBC":{ + "key":"3aa2284dbf9eb93383cd93d4acd3a300", + "iv":"dc272a4d07a4b489cc1334d57b804bf1", + "plain":"f457e6631c568c032949c38c2407d0741b91538381fa077e7e48afb7debb55f8", + "encrypted":"13cf575a49ba8830a17a7fb5d5cc9beb7c34e713b29a0487d0aa99178893f8effd9790c6e75d6da4e2d8b8c09d54578a" + } + }, + { + "CBC":{ + "key":"bafe9a4ac750cb8002228bbf482892c7", + "iv":"3d0af6cc5c556ff880c67acccf931979", + "plain":"b931c2ee05901539f343ed18657f1aed18740b880810dd278152ecda0dc2879718fcb056b8ab902c8dc331d96551b55e91ea00127f83ea529e3641f074be894a355d2e30279078d2c38b067f963e49df5ae7b1b2d50a7a16719e78b8c6d580177fbd242c7bd6075d849bfb017599ab159219fb6a0e8059e7763c96db45d28a6701be00522d4ea54e5f9cece3ce1453ebde7645ef786e70560755068249b89c194cd02562eae81ae45412e44324688ffad42c5974e3eea746a1297f1dde2839de30eab6ef1153ecd632d1a7d47e24eb50826885db9b5233c0a8d68df9d2f5216a362824152b8c753b65b3a2f3c843d8e67d3ee48c8e1a7cdbb54eb138f66ad1eb", + "encrypted":"e483dddf686e024766db7a705ccf4fe7ed9ce6fa7f745100f84c8054873ce22aee0b6d406bc694024756a626e4224bcf76f0b2897b3f6035edc41ffc39e9d7789c235c62e4bb86004001adf058bd8368100ba7662e0506ef37fa52a2b8abf1d34d2c547c12a259d0563b63a83f0ed22c8fe3f3a08e1f2a58436cf3872f7fd23dd58ff32fc115207c9ab926bf01d5875edd17d06b997ae9821b0267010e66699cae7d9086487ab3796baa8c987837091b4fbf659f931096f03b43694f1af00757d00df26d27f27d4ce61216fadc390d4f0d02eff1f0f3d02df6af955078b67a540242e217689524ec6d9bb818f6f7d50d01b478c5abcd791b9b0b35ab3d2137ef0d6e8dc6a2f0792a73eb0f9b3b82a416" + } + }, + { + "CBC":{ + "key":"00752f7893d0c556134f50a8cb95447d", + "iv":"0b91c370a4fd0cd8d21b9d841e513865", + "plain":"6b5c56212686f0500929da49494e8e67", + "encrypted":"d52bd69c847a91ab73e408faa7b101508cff59c5580a359bfa3dce51ac0deb3c" + } + }, + { + "CBC":{ + "key":"8fa675026a6bb2b4849eb7bb7589641e", + "iv":"2c10b0e92d262b53047c0ec26a3851cb", + "plain":"1d328504aacfa906b77b8db082dab5b1", + "encrypted":"88eed9df0b1cd6c7ae6da8083f67a1f04f93d7dc2af3d22295a98a7a0d8711ce" + } + }, + { + "CBC":{ + "key":"9a794ff7abf7e18d35fbf3cb742be716", + "iv":"a312c3e429abfbd1dbbbaa034ffbf0f4", + "plain":"22bdfde977f1de7a463143ef77ef43b43833d6", + "encrypted":"48d965e5a070f44824d92da1557890a9ffa9f4fac1bbd2aa21c1c33d0d0d1fcd" + } + }, + { + "CBC":{ + "key":"119f71c7fc815b6386d8f93fea6079e3", + "iv":"8bab5f5f1cb7ed633bb1506256d964cb", + "plain":"3c", + "encrypted":"fe7ebf67fed1302150db4157e40269b8" + } + }, + { + "CBC":{ + "key":"aa5ad3e4ed1e1bfc247b7a79b37c7c78", + "iv":"2acad1521de60d8bbbf4cd59be854da8", + "plain":"3c8d738358b3f070cf9485a7fef3653ab98005acfddca61c7d3c7cbeb5144a5f509c5e2f350e34bd0b4987e857845a4252d3ccc4390e58480f9301ad1e491be28c10bc2c63be15c554c318e413764bd34f18a39e5688bb81eecb9aded12fd5bc096dfc14482e00e0f89ddc23e24886f64b7b37f9df4a157735a7a02a6a4080caec5a71443c1e5c072e493a51fbf74cf7740f404eae3e7890a99821da1c5a92b0a7109a02a8d737c86d7510e462c2e5f23ea22739cdd600812042aaa7e3051c89692d7e2dcba025aa598f47f1040fdd6e68bce8b5fe88b40b6c4a0970493737844063559935be1b0efd5b4b3547f6db8ab33ed2a3c6fdf3a4db4072c8ad2f51661574704a20e2cd7c73eeda1abbca23ce94043b10b589a7a8fdc9143db626d1e1643d6b8bcdd8ccfbdf39e490b71ad40938e4b2489c0a2e55a5a807a5fb2a1df9be5274cbbda4eb144b16393810d0366ca929f626b2e6750f0f4fa2d479df2efc62626214026a04117e38846d2d1b0c0077959399077c4515055c5ff165fdd935a4bf351845d9ab9ffaf03d7088faab6092340ec80cf8c42845d37698ff6470e1419b4c447bb53df61021c9b8c10cc273951567b39710e27ebff1d75457e21f5450fb1e71ad2eab508e5b2393d3a72d93845f2e8fc5250fa4dd93f122999dbdf28e343f2e75f0aa1fdaf55101c323242e502c6be369b9c5dd2b9c3980924a444619ea9c315049142eeae9a0142e07fdc42e832c64b9b6013aa877ae7eb0b37b4a1ab22be2481a1c3885dc4304eb5f124b680ebc1b2519fa607e1807fab5f3516cbe5e38a22045ca187f84fe26c1c330486958caf4bb30bcb4d320d0c30b776e79a45f4e98901b9da08e74643141db61723a82f958f1b8097c427ece634c3506f2d30d7e5321a207d745642ac00c9775fbb325dd3aa725a7e7983733b3a742b99664b433c086eb4ee46d4890d42c80df7ff4eb5425343725914b240496002421331ec02fcfe80fb89b5c0ca5f9cf8be96f85882ba4742b1fabd5396f52c93aee751c8a08ea33332077ff48c38806c0e762639ed6bb9ebcee5f65107d7a8f031c195384fafdd07e66f5a656958dcee900aa1ddea0044737198dd9ef7172a082902210832e776070d3033c120ead8aecff84321bb0e221965b5af2e4b3580c9f999e2d8bab9578b51fad29928f89851442318559a55af667946fd638d86f5f9c8462c58ef885509d2485c6e84311c63486ddbc3947f018a2f72ce2ea7db0a271d563f4f57b7833e87a5c32c8472a8ce24316ae5a88bf78c544282bcaaaa7c5e9fb46bd89bbd2c413f8764f8596427fcb16024e9c07b584bc817fa990d10c59089c652b1d22e925bf8852d69cb301ff99b386d7f891178a4d7222d58563b4dd3aa3d073b5823c548c5800754b5009732f96c6ccc480b9b07bd57aa787f10fc828258a72e2724ba9074778073a6d153ffa8bfeab70e6e801a3ad461d2477a08836add771b2d08f9b9adee3b6bd8e1f8c9c0d815defad658803538e794e3d7c85979fbd3e5a743ba1191205d51b3a033e109bf82cb1dd187d09414878e86b36d9710db031ef3c8b8a41b9382d59079cbba3f8554612e79b99620d987b9441b37fe6099e9b05a7b323829b4b489fee8a854cd5902915de314a410cff1ad023cfa4a92ca9676803441a487cadda6c5d231366923c91e2d69b5db9e12c8cad6b18027652adf22303e2f2fd2942a2c1e6639e189bd2b10b", + "encrypted":"5d6bfa4413c6536e0f6207de7075af0a0d763a29c730b9217dbd7af691e5ec274177eff2305bec08cb23c38a0378834ca327e0ca9d5e51c49e9bf5701c7fe89a93f2fa0502b85f82e70ab36b217aa357c1d95dcbdb0c29d8fa796b11a5f9bc1e642ddbd4dbef24253ad4a88dacf19ad3486248de34e1a8ff2ccc760bfb1b15a122b5e65238be6f03d36ff685497db1142a68df68550aac44e310866236c9e99c47626c5e1bd1064dd5d77f8132383c761e563fb0a8036b9dfd6b4e303f2f7fc53cc1615dab9403f68be8d5f7d82d4056e8f999bd04750f9082680459680578e9f21c7ed7f87e2b6c60311abd9e44cbaad1cfef54e7e94983c69b8e381cb8d2f2b7edd2f30fdfc581ec95e31286a522db7e019719977a71240e66c648fda3655e76c7bf26a308c4ad17c08f4b2021f0840278a1599a259e85036a9fafdce56ad265f43cbe55fcd78162b0e83e3f0fb53480c0db9aa97abfa2050ecfa5ad522ec16fdc63544ee59206c335269718f33ee0a61f2aae25ee0a0f1508e6b94f9fb8632dc594d0f3f87dcceb2cb249eaf10d2701efe7d2af38dc15d1edbb7ec18e92b40d77964d984c1a62bed622abc3abf065e7a495e2aaba0237112ceac4ff5f7e7026ca730d1ee479b2a3607882185dbd9ce227ad5e215e09e11fdefe259b1029b119a1ff0c9b2d5fdb52d1ee7e6342b8c6793d8ec72bc286dc5643e9169fbe07fb229959105f03dbcb599ac192ffd0a127b2d0b657cf55debf9b94ef4a8075b257abcbebc59001d26e47f1e25c1f000c1e61dd5c3c3c64771a5b1ba8748d2e5591fa0328501eacd7ab8ed93a39b69c08a26aab9d40c505c1d535fc8effa51b637a2f05e10829baee3ded50b5fb063d4eb13c318bbbf3bcf499ee44cd862f53e6a112d066feacf198349129c559fd2887c8eeed19208b158eff285cbc1c79fd3c649f3b1e3a46deef7cd1ccbc4b431e43797b6f0f4d64ec8545a3d01c65580b4e3bb59339b91bdb0276069ae6f54ac19a530ce89cf2fe845c0ed8f77b65c4730ef21f5e983ceefd1a6907ca26b494ebe6d768bcd97752999d0dadb03d4313ae544ba7eae3452d1f478ff6bf4cafa83f79b4a161bbd313057b3ee8d8cb3b7e8cc569a5c594c8a6a93ffab36f768a707b9309f6670f863d7c56393b7dca530dbbb45f8b6ececefb49a1a6196193adfcdf40bf6a1580d4f25a975a899cb7a9bdb18c8d2623909aefc07903c125ab78f0595f074f547e82133cc73fde3d5ab96225b85134148e1c6186bef12efd9d7ac0b620c031c71c1a2303afc3e6c57bb09d1795188ca1f58f7c15ac13f8aa44239bd5838eed9d910c3c83305ef5328ee77456aab2abdc972106aa5f4b5faa3c2a25ec5681205ee05a3bea75b7795ec662845420028aafbc0dd6f9d5c5f04b45cf1a1b7415d2c0d14ad20109d3061d5826655a5874a7e03464b9c633cf4ec8ec5a2d0aaee2f739eb982b7516d467aec334f29761c8919872d4ee361d5a42a183cc14c51060bf12631c762579f143ca28c00be1cc2ac2e426e8102ebb464809e614bb82daaf58e27563b70aa77280bdf824dfe9ebdf79f68dfe884e2fd7227bf2816873a00e5a8927ef56150a5c29a4909dbc176c6bc440208b98f6d1e99910abd8d590b4aa46ff3cbfd5cab675a3bb81b31a419a5ba4f88f3101263c2d4d594c4cb2df4d491cdafd591161f72e11c3a13c328f00995e4b47153f38bc68a8eecfc0f6022950fa862c6caccf7f8df846f1cf6038c39a" + } + }, + { + "CBC":{ + "key":"39632da50afd545db528269a60bd4f0d", + "iv":"b09cea0cbb756b5dc865c97086391641", + "plain":"da05c373dc437634b098155597c2fd5372449c974d3f676fb8a3299dace556395acd5c19630b9f316f4d5066540646c782e2ccf1d0a7", + "encrypted":"f73f15ebe5f3e8c31073a0b390313cb6f87572a4e4114df18c3a81dcb18c09579391f9e0be0f434216a9bf566a902eb22cb8d43e4b2b166bd550cd770fc52738" + } + }, + { + "CBC":{ + "key":"7858cbd03e562b85fac4bf5d1ca8b9ea", + "iv":"75ee35e346a730358762220b668a3b03", + "plain":"30f69805ca6218ada4eb8db29b7081b6", + "encrypted":"a0f2e8f394ed1b2030c36ce751b3d8c799f109a7bd2c97f7fa3fb44c28e61607" + } + }, + { + "CBC":{ + "key":"f1842a2dd82654f96aaab057a89ab49c", + "iv":"ef8d0634ee85aa92957d54ac20440b64", + "plain":"eb9af5ca18f03356295b66a64d0e346396b9513f34c33a2b5c075f4eb85efad2", + "encrypted":"2e06c961959d5bcb755829870e6eedb90b5f3bccfe3f3577a279931b7eaa6b5797265ecb8ffa4645fa1d9720cc56d36e" + } + }, + { + "CBC":{ + "key":"6bfe4c7fd89d7b6fcf1d82493ab889ca", + "iv":"2e08831f3ec1f6df9437c9bd09e4f27a", + "plain":"84c5204e195f79bfa7294b2b0123ace56027cac4b72da2973d592bff70619a48658f6bc7858e4fe2ba3b25b1e5a3c41923092473db8ee3c00aeda2b0b758485e0fa33dc2aa5268e5e89bf6e78151aacc4768883b7633dadc773a0319f12eb225600bc99fdb644f15f4c16d0db6769472a8031a15b67ba3f16bae6899b438d557badd6e396427d4d00d5fe0e9af4bf66d0fa45037a9145c8bf46b9dad84118f935b3ce9671c303afb5453bc079ded0550ee540e325e0129a1329f83d1dbf2fa1c5d1f8c467ba0280cef58a4f8c29797d3d460bc00896d524a4d06adc434a6a24189ebd110a5eb8bd846252f22e090627aa39eaae6e02bfbed36c6723b69396254", + "encrypted":"52737672c25734c57bacc43231854ad35a3637669134b51e574cf739333f0128181396fbc6d326cc5bef9e6971fa89ed9afa12a9bb6b9f4985820461e3ac37e296a6deef71126033d30820bfaad6abfe5c9e41132ccf0e7a93007e87f1b0abce3764b4d9b83ba111728d9f8e89795788d4507af99afd5bb2e83a18119a1a1c1b7a5b7064455729d4254ba721ad53366bc8633907e407f6c371431dad41f434c03886176717b82ab8c19ca862414b83bc1873dd714d2f3621fee4cb4df681e02b67748ea53836cdfe15d620af5a3225f2dcb2f1dc0f5769b589cde1a37baf049086d53500cb2dbd6a05057c2644c7c06c63bf5b8f67949b22d04f2b3a83f880e44037c19ce406c103f06b0907ce695434" + } + }, + { + "CBC":{ + "key":"0bfe3f34ace541bc994a3966c233e426", + "iv":"e8c32528c9ddb4f55b1c03fd02f9aa05", + "plain":"2b1c23fb4a31e826b2a7289b18cb4bd1", + "encrypted":"d873413c8c714a39017d35d6f3d001d6a3f51be559c4cb9485a80cdfb802207d" + } + }, + { + "CBC":{ + "key":"cec7ab53a37a76c21d6a70e356d214e3", + "iv":"d1f6671ff7e4b66a1834ef8b633717ac", + "plain":"eec7fbb67db0b356c8d9df1e82c1b555", + "encrypted":"d3aa5af7e6c8aa77c6c6a25e871256e2dcf7c6a957fff07e35119dcea2250cc5" + } + }, + { + "CBC":{ + "key":"c0cf5cccf67c42a43954449206512aec", + "iv":"d440af039fc9ed15a10ec9015bff96dc", + "plain":"edd854679463a0299529f5ce379b7e028c29e1", + "encrypted":"488bb7d5da24129a1e743dbb068a0e850ca57c68c30e841eba236e61f686c4a0" + } + }, + { + "CBC":{ + "key":"d2bb5a7db4b5fa5d4274a6e8795ceb98", + "iv":"39aba2854de28ac7e005df8868d07580", + "plain":"93", + "encrypted":"5e46e7ab55dcb740b50c482da8d70834" + } + }, + { + "CBC":{ + "key":"57ebd5328f2d378f6f027cc99c74a59a", + "iv":"3f89b5a30318930f7733ccdd7292fab2", + "plain":"80fb91e6c375b35758477d20dc6fef27e4f511181ac8948de03376c28190f1d96f4665fbc828ac8fe8283fb6aaca0feb56e9c2b5a343a60ff1e066483878b0c8478992cfb3f5042199b465b5af42de8390e7ff4710abfb154f32c6a23377acc2c8d246c0d65ea50341491fabe1f85f20e5a8071672fbe095a18dfe545667ead236173173b9d7e53121db2ebe644df3a3ffde755a053be21dd77462526db58da6e3950d6b553adb918784ddb187f00a3e6c0576ff818fd0b9b1551365eadc96d4809a4ce9d7b797fcaff535bba397d32aa40368311bdf67e6a039d0ec43a6486d23dfcc3b5dee297b8087775b6c2cc3cec663749372a836b6eb498626b4faa080ba21dc9cb89f40444852ada8ee420284767d53113a372232efb5dc0647f574dc084b1fd5a4caedba10de89c771935f02fdbe0e775dd608a50a5f4f21b6b2fc59a5de5e8436383aa5753564c8dc760350a989720a09789dfd2e5fa0a18837dfb2b5c3d3d2fb0ec1d75606386b92aaeea6b4eb55d3afb6e019ca846b11207c10aef5b5d568edc03081548d722ad803b3216b6fea0222067c9a07e58a588e6ddbc6919df531715d2170f03560964aa4cdd6938f4c16cfc1ec71fe2e60b675d2aaf16f354b6c402e7d0eb234634c6264f5694b5f5ade4feec87d48fe15920cacc318574f02f8a38f239f21afecb50c7d314cc874c6b6a1c6f847860ac5e2908acb2accce577eef4ce8a587c36132a1c0319e1bd7453b54ae6a5c43d51e7fec6de7e628576448c74fce24baa868d6ce14fa7a3a6c4ff74bfba429d3edbe7faf7acc305513fb638952fb26711dd2689a343efd66d43611936bb1471195ba35b298645787e498b9d022efcfb5d87ad31ac678c5947479b36bd321485033cf506a4e3cecdbf85f2bd6ff41ce9c50d7994cffa689d47864267ab67819c8b5ee9d80aefe93ccbd5b7f4342c8fc06b2b99d29f8a69aa53f999160318a9c7fc42ce96ff1937da60abed68269462b7e26da02c8b254ecc0442e4eccbce0727aa842849a01f4c7f497948d6edeaf1e9b5ad39bab2dc25f9a7a1c04a0f505fcc70af3c5b4fbe6b887ae61ce6ebb2f104eb0b238cec6a15224184c9184d123b152fde3c13a3a40c132f8b488874c554afdfc07a9a776eefef95ad2edf14b4b81b4ff042f0a07be184dbcb1fefd7f83e085b5749104e109dd097fd69f75671ae99e2d9c2e8d11046e64e4c2a46ff004fc6281ddbf9c3eb103b8f54e8ebb0d9c572f4a884a42adf2c0964772bb568db588ff4ade8873061ab91fe4a31d740178ccfc0239f28498c75de5d3f8f497bd2fa50e3196f3647b90b5b4953e9578292f6bf982c2a935c2bb7db9ab6f1edc1e13878adb05fcb6defe90b187042c641be141bbf50216516ed3a35179923c83f8c2e185f8659ad092be15bb032b135876bfc7dd3cf98c97af06b967c3d2eb5e471b19699c30d21f556be1df70c7461a827cfdef50ed19f5d8bdd7ee5dc096bafe01d545c9964432cc3f3d9a1b9a42ff6df27b38664d46ac4118366fa23c41a0b382827b06f2112b5404546ea661b51a95070c4bc798d7e5e5ec2dda69bb053ff3042ad63e17783d2dba999fc4c0de0ada353b924d780058d8e7ed944c8fef1ab635f04f015c0ffd7695a882d06a3b3e259d27764ac68eaa48a6a7d0d70a246c49029b02c01609b51dd382bdfc9a6f0f8222e2b5edd1807de5cbd2bbfbb76c7a4083cb640c1b95ed3df83e1aae", + "encrypted":"6e4658d1879186f60973f229c8bd0396eb502bf614114773bd9c0ca5f9bd25fae00c7ea59dadbc00ff748d64213e75920bc7adc06e27365bff7b42634f279acd791f696e3994b8ec238f98cd5e392e607070aef442e65b2cb9f93d66c34a1a1bfb3d3297af18860f1dbbec821f8145cfc7a3b6ec789eaa08f97036c8449222cfd91b6e08e2173675b0927af99b247740713ddac070522aeeb47db404a33bb0e9c928039d0c7068761213b9b02fa93e8a6c86806a22080682d5d104ee2ed4d7370d557178bb9a10f35e989daf104e2ca305ede30ef8b49b059cdd943ebc08bb72d6a3f3e945ebcb94aed835b2a7f7cc120e0d093aebb90499d38fdce37cb10fba5b1cd68ffa441d4e013e09a1c334263affb001d23b9975af5c90b3c44a120e9ce0fd13dcc6902daa1fa360be2d67ca474f034a5d3ccf8eca77911b17454d215d2b69bb6329e037eade95747a7d7eda4605b78ff338da7b7d5a78a8f8952a46f70354eeff051d5ba1b0e4e6a9e20f76adb257cb8ec88ef8eb1a96753ebd5786f491ed15a659b52b04c2f65be102a5a7041d5ddbac5f83d9d45dc472a6e70398079dd561946e3d1ab9b74a26d000987ab04a2743fe18a9b4c044b142c0ce25231a0534a3cbfdcd5fd24e55b0b12d71bc0ef3ac4c95d2274bdea3fcbbed39685a7baeff1a9c24417d707660d3d0b310f30450ecf7c26bd1d4fa8ac2ba6d9bc88adbcdbde3ee54612715612502a4042983efefd75070d8592ad5f183d542a791f3bbb3e2714d11ee87505010703c953b744d9dc30b39beba77f366a552456ee642fbb642f51ca4c303ea1a80fedbba2afcd3ded635dd00eb9faf39170464e0f23731243973de32c7f639d28f3f9a3983fac97183336907f26f849d0806dafb24b7baf14369d5dbf88cb6a97d55642cf52f8c46547de0899b3552231f46d6a1899aee6b099d42dc70e33c05be47d6828f3f4cce6756901e17b4f404efa9ca9ffc4f72904121c1a7f2a44d8cce810b408f1809a06dcb0bdd767ba1b29e21cc7d7e15c41be8b00a68bc26b4cad75e26c88d7c6d737b1148b3c4af59b554e0e948e1d8335254a63f3d3093e7ff3bb6039ce074c47a5067e700a0f35997698d95f7620a0a09024afbb3d0981870209a5bab38b0f586c0df7624946ed67f93e41e9073f348c46131b6cefaad0c35dcab70e956c0a0152ab84d79ee2c120333a14aab52961a4446651f5997d25c0a2c5cfbbbbcdcf71415b93a111e7a1ffc3cb1bbeb9e9c43215c02f85c56f7c0ba443373ccaaaa49cd5af3de0f64d38662cc1fc452e88c599e5a3467d45ac8216d15d8fcc46692ee78f1e8007c8067bb2139e310bb2ac425da4872a5b2f60953b92cce11853407595698599324acab35ade1b762fee349698e0234185e9c3b8eef64bd78e37d98f237f054641806dc3034c683b27dc0f7291c8d986c1be0cdbc0a33b7cb41756c1772fa7c6d864c38dea33a15c45e3f948592a85ba793824b90ff838a21a2725829ef4d9cf11c144579e9192c42955ad7d3a14b3f961676edb54cc4d099df989066793a0160777039f2ede82a6477e577c6f9059d19e6e47d3256b41e29ed9e1fa755516feed9399225464cf0330e113a711bf78c1f2edcc498d7ac8b794964293443e8304d122b778cce8f2ffaab795ffcca785837d12c18c0e887b0a45166d7c5c34004dc3b0b4ccf5a5f263df80713e48158900bdbadfa0ee27384658c288dfa099356087bb51e8b4fe7e5e4d3ceef39" + } + }, + { + "CBC":{ + "key":"1f955bdde4278179e6e1e354cbf7656f", + "iv":"405281a35edfb95abc952317a3a87a11", + "plain":"e10e192a4f087ac1f52c435cee30c3c6fc14c3cb8edbce43d333282702601971ffd6aa05934e9960e3310366a633a5b2aa62b787b592", + "encrypted":"2c40d045d859b8204610bae1fb0bf73a451a0d69b3b1bf575aac431288daa32348d404f8618297ceebbc1cca5ace22cfa18376dbaf59023845756fbf72de1daa" + } + }, + { + "CBC":{ + "key":"03a277d50cec89167eb53ebc48c393de", + "iv":"3aa6dbd9602b819f36ef31ce2a5e158b", + "plain":"13cf3e0590235c517da37230d7a21c78", + "encrypted":"8f8bbe900b8bc2b48306912729633ef703d2877cf21c92fe472d968f9412dbbc" + } + }, + { + "CBC":{ + "key":"c4396e00699ccb4bc656d2cc258be1d2", + "iv":"f5cec9a6a32f1ee6047505fe363d8885", + "plain":"f21cdac74bef956abb00a456843ba2b5dfcb718e76a4afa5a3f2f1f13f630987", + "encrypted":"89d64fffb4eaad857ade94596f0ae04f92a1951421fb5732b2c50f97b178734570659ffa6eb0831b29c886b059c054ce" + } + }, + { + "CBC":{ + "key":"bb8076c12c3a7bf96fee8256196bac1b", + "iv":"a0ac62346b98522b48b82516c161adb0", + "plain":"b3f3491b41a1e37f89e1bdd3c90e4368af040b6b61f32d0f7530806020c8cabe99cdf6dad900a43e53bd6dbecfb2f7c91dd0c1d5cac4653b616daafa76652e0691f9ba11f0941f32b1ef6009bc0f245725f70c68b4d89ab255cc8e282b368a7b280c087b50f76c063d227ecd66e612881ad12a87f1a402d9a48559dd190ca5159601a7a818e0e19477258e9147e7ccdfbae83329226cc4c24bb0878d46aaa4e301396f6ad496c2d7c57b6e5aadfa194f397bb3c1ec4e4a2e03cafe245954d4429d1776f0e9f3b5ea3132d391a20e0233ae56123c75298ae7b2c4c6c5cb3e5fc8df26f75b21f7b0059f7143a19cd4e145b2c81252a81b49cc8b46bbe417e127bd", + "encrypted":"18de93042d87a83fa4d102fa19912a7e1a8fa9e26ebbc813d4171d6121df7f61dbcc83bccad0bab6795f52c41414a187f592682d29a2cd8768d1a42c9062d11be2d421044e3e221a32a2847ed84c02d125e9c6881a4c134feda084764bf3469e14faf90410129828d9d0c98d1c1277ef5d9a44ccd75d40920563e8363ed733c95070785d1b3e5c75ff349d55b496eff7316550e5f5c7432cfc74fd3c77214d9d009b9596b81507db7a183c0cbf84056ea66ad242e63af2e1369342f034806ce9634cc7aebc03411b928a23d568838e9b3de4fc7f75e75fab7d1412104b6b23281a7967e3cae39985e54ad6d851a3c6e0fc76105dec56626b9e546847ca64bb325d93dacfe753e20d926e4a1f86750191" + } + }, + { + "CBC":{ + "key":"f4a3ff2a0cb386a5496ff17349246faf", + "iv":"1d056cb93871348e5eddd803ea65f83c", + "plain":"18f207cff9005a98f481e74676b338ed", + "encrypted":"d3e0efe1250ce33a9d986f790501129308cc8d9931308a05bfb764409ffcf670" + } + }, + { + "CBC":{ + "key":"f132fc03aa1c85c99b8f4dd7f858eb7c", + "iv":"32dde15a48bfb3890ac354d3e96b78f2", + "plain":"b812b053b21c04eeba03b2d8a634b653", + "encrypted":"c090ffb0cadfb69206ad01d535e34fdacb48ed458bf42f4241dcc673f2dee93f" + } + }, + { + "CBC":{ + "key":"07ef863a3da3d0d4f7550fc57b7f857a", + "iv":"47b73a7f94cc127e833d9166462b6e97", + "plain":"2d1dbd33abc2db00e8669ddd712102167a134f", + "encrypted":"5751c6fb91d6062c6f154223c78d3b18d452009929f78070553d00cac041d5dd" + } + }, + { + "CBC":{ + "key":"515e1995457d8acce3e227627e9042d8", + "iv":"880d08d4dc1ec81e26538387e82a85d4", + "plain":"16", + "encrypted":"965aea5aeed76e79a66689f0a56ab1f2" + } + }, + { + "CBC":{ + "key":"9b7b69e21cf0e00b38bf7da03cbf3098", + "iv":"a43e06e841188a740fefea472360eb19", + "plain":"75ae4e0defd7b24b0f322f5943f52de959a934d2e91f0c31cffb85f9f979311ea3e528976e0431f10cd5d6c12d46ff5490c6299762af379ebebd10f6d349655a560dee792120c24d8729c1cbd389323623fc118eec3c05745a3def1453f3195e4230f0a5d81154b18354f89b9910b83e6c5f63e2b0874a748dd4cb6d91a1c31a5880cda72fdc7a339e9b367db4b8dd427b0e5e54374fe5f366cb61d8176bdd606f56f940b05afffee2f49a283b65da033373c3731e2c6bb19173d63e28e2c9e29294f3f66df2fe347e5333edb989fc6b4de423a17585c5a3475c9776d8ca4c27d0687bc820baaabed78f56f4aa3cf8344fed620b979fab21b1d6021473f0ddcf0c5d8f3a047fe58dee19c3218403075a0bce5f48c4ce7b1c7f23fd694893feb235180ea97aa90bc8e402e83d04912751d2dff49cdc8650a357da996a612f9fc5b2096f12cd66a43438560d8be4e34ca08e9b525b0cd56b8b16e6081ec7e333240fcd1cef0b9d9bb8a0677c25a0014b8e00f84cce9e3cd84b54bc74147209941d9eec3fd391c33cc9e703f2586ffea0f4b1963efe884376308a0bf0167b4aaf7914e054ffa1f15ab9eedf04e1a2e2588601f23a13d109153d3401fafec16c95f07cbe87ba5cf0f575e05c9cdeadf8244357dff9b012a086d92879398a84915ff7bb21508b4b8e5777ebb7af085bbdad3c60ecfff8b8257f373b69aa17f27e32ac8bb88363f980da088ae4a8e75036d6fbd9dd4451f1c7f2c2407587fd69b7ee6cff9df629ceae5d556e126a42705b1dc7298e087eaa90d5d10397ce1cf021a44cf9816b6740358752e599f81cb20fc93673138c51454e60cc89c860b58880d604f397cb4de08b3efe37b6ccc1bc61fe7b032068db30da3d94c2e3efe7fecad64c9db8498c800102b91c1d09e056da0c2047466dbc5f9d2fd2b5cb9446d04c54f73bb4330132826c333847e6accdfec9a546b26fcf2b6bbcb3ca1b1fa1f12ea932b6c4fbf4bc7456b26eae935830ca7687c14479674af5335af60f9a8d311f0c1eeae1fff14ce382e6a532f8dfcd98f3e06a6fba05e862fb2e956461b8e5cc42d0641ae7c377f65a775afbc14aad93df48d3ea4ea28b680d7f7350f8c489c2aabffbf0a7941c043c46534d6313bcab5315505e25ff87f0b2c4cbf6180cc046c68b37498e973277cc28348fb6a45856c4b2dc49b3c7fc25252e15726422929663724422f6a807c87778a093f2edb0f46d9b3f472014c20e1586dbf4c4c73b367dab80dbd8b63d724e85affd354dcf6dbeb3417aa7a338d82aaeb33f875be9619bd112ba9a7f21258eafa53b326bbe9bbbe3e7f842d193044b7d4c09b2875981f783801164825c444fd834769c972d3df499820184fff637b5aef90b57fb790a2958af157dff0c2fe05ffcbb3ff704646852113470d32f58c1296a31936257a64f307c40a9e0821d9f9df8f1e33201d0b9a29ea0192057444d6ce315d13853a300712359f56153d780e94e819437a79a5f75d940a3556608e2abc307daeaec63697ff5d6cb120b06968d081efe744cce18e7e86c7170dbf2d9083b4d36df24c03655d9ea1ca0024d7aad4ecc70804ab6471431a99323a7e5cdb11ce4595432fdf441314721dcbb2bdc1cf66e9d5cb7a515931b81557f3c4e4057c96cd5abc552d76369694dbe67fca0cdaaeeb9a0cc1463702962cf9a6342f4cf2ad16cbdcdfaf2108a59008357f7f85b79895cdfa3d8e62c0ba2", + "encrypted":"114011806f4307e0c3ee955bd107f86a875ece4e8a6f94973d735624c5c604af2f0aad43374a3261a5f0600c0ff215c9ecbc21c4fb2e40771eb878c5cb9f020a9b486c07b04461554373057fc1c027362093320c6888b3a0e719007a58ef6e9fa3f1c8080524a2abdb64d4a41800c02a9d4f54ea3c4e10118bb84832026df7e49f023f36613a8aee590c4920837a06622e2f45ed50c3005eded7a11fa6dae6a91aaab28eddefe4d2dda554bae3807e3d17084c0516e8ee65ed7077879bafb4a11a37b8afec17f5e9d06e1eeaab999a04468d27bb0f7b86f938e2d7a1bcfc768540ca5bdfcd4c4bb0aebe876642b119fb93261c3f3c19e04e1a5f2aa2af46a3998260c335aeeef600dcbaa209052cb0415a2fececae9429c4b6c6bcc9a04fa31c6e1984653ff024da5853e88349e0158541e438d95b28b9038bde7bcd58dd108478a8bbfc0ef300ab08459dcad855a1a9a2452c6669dc6ddd21175f4c73b1ff459f02cb63b6da148d60a5495c79f30235735a32e3e13cc915bf5d4249cb0201847dabacd712b02733a9007e1481031502c66ea552401df969c1a47616ac1173c41b0a936afdc45343ca481e65d2843bf310699e4cd41e78d4bf177f76cee5738020d6fdf9efcf7ceb24819370b4f78f091cec8207eb2e2592a8baa1b136a6562014b3fede0bd6936cd45809b6ff280ec4d3158892558f4e89338ce730205ee61bb5b92ac9bc351690531292955dee7735082cc6cf2f883137925e0d3cae385c1e5929001054d7e1b9a28c61b565e66b5f8c67c467340aa408e62039fa6984b76cdb8cd4f3991f916ca006f202002ea26bff39f938b7eb9669d0e570fd58ab2eb5e6258e5efb1577c8519a6ff5b10bdc6966e14c620d597d6a0a1313a8e157c1b951f06b283fc40d34e9baf56f4787e338de21ffa6bdc0398ae8d5b6aea12733f1c22329022730cc02edc2658d570cf13cb11e403f9294c33ae92a6d80f647dac0245e2c92880ad6351b33818490d73ab0a5cd31387261bcfb0131dfd3ee3dd8ba02a21496bbb67ecb7b88273f3317e4cef48371aa2c9548c66221a9e60e2c0ee9b3b5ab0fdcb311b1f8b07345853812daaf966017e6aec824811f210f4d1f6ea57d2a23087daad2320e8f5cebbf8c4a86908f515632d7fe70a33a190c75042f6c27018f686762924242d183c39c98c588dea57e81f457a441fcf6f8d0e9ae1fbdcadd8e572795897523d9c2c4d571e928b5b16af9a8a032733f665653d1854359bee48837a0fef954e941e7d0bca8cdb501058b701d6da9b718136d4ec673263c78161e2677f3e2c4c35bee9010f7f6aa2ee4bdd0dcfa78e79d82d1702500d116f8847089bfe3c8633c19c80add8b69e00e3f406a2eb1002a7a06b29fa2d62c44973eda8d8e7231368f1ecbfd66d6d7dce9f9061652497c7d09eeeccf8101ac1349eacd9a425cfabd0108dce5ad7992aaa970e89461f5a6aca5ebeb7b79b75b3015d6308e2c87a73a502d9a81859f641a1b63bdde0081ec35eabb318901086df4731e910371eeeab7f8b9dff44defe11a6f6eb3bef905c7294d7ef7b696ab68e177f6b17205c82194f6d6e32f9f8a05a21642f2d13bca505f6fc72979fffbe5aa000cd2bad3c1947da3ade7f09a0aebfa84a3b361547626242b290b98fd28064f37102d357376820d8231d1d46e5f195056a86e092c5853da1ba18ba285ecc518dbc7f56f0c89c0753f5083fb093645130108cf969ed7863efd002c387490c190" + } + }, + { + "CBC":{ + "key":"d577b3ba9a00a80cb6d1a49f55e0d7ee", + "iv":"0620bb4e7ac109938866451ff2f14b2a", + "plain":"34155f6c6e9c255870e1c8fc9466df878aa0e81d8a4cd7c2d89610f5b13d063b8e23d972a7901a02f9a363d154384b63df39498c90d7", + "encrypted":"8d333d824a5fdfac9d5ed7e0b0b77d9cf6f0f94150f098da8f9ad950704e4fb1df02cfbb125678ed264135a90e8551f5821462583f85809253f6c757529f7167" + } + }, + { + "CBC":{ + "key":"6d4b2af304327f3b1477f21bcbc5cdf7", + "iv":"f6396639529501a87527eaed70db6cba", + "plain":"349d5d70c1ad132b305ecc5737341f56", + "encrypted":"8c6be61c444944d68d15bd9c8a8863e85f931ad98f220d72ec66775c8d50bfe0" + } + }, + { + "CBC":{ + "key":"8003e1b1c828238b97ad23d444e2277a", + "iv":"f574bf19611b60a48e4a2b3a7411651b", + "plain":"44adb6933e15a65b80801b2141f62d99251c839ce315209b9f20d1aca508e081", + "encrypted":"19530a6f1a75e4bb5cc2a855b9155a93a317562031484db538603d89724aee33624b765893862a3176da0808e4313a78" + } + }, + { + "CBC":{ + "key":"a101656893be33325d95697f29aa733b", + "iv":"1259b6f1677ba92f16ade8a48fbcb7e9", + "plain":"a7dfb5026b2f5a488b9cc5a3ab8abb2bbc2c09c649c9e00601f6ef0839e3b41d72cd4671fe7c1f48452b5fc4d62cb8adb9aaf480fa2e77ab9b9d6bd92df8fb92ef59606cc80635bfe548d757f07da4cce59ea7d8fcedf126399a7b01d41575f4dcc1d7eca2f52589eed54caba9cef41699a22d406170e5661d2dbd516eb3a9441c2de414a28c771cb53134b94fc1b656d0876bfbfd67e63d705bfbc4d8707a1982399cc87e443de278104a17a9b5c695bebd5bd289a6cc0e7d0eb2da93fc7c0a86373d7d8543d09a9ad74bf6399e426317f6c1df506901ee37a5df3fcf08f3d939097a9b7bd08a6b5e5a7a6300ca066fa89eca1c4d7037da4c61465ecc7db14b", + "encrypted":"af02e1869dd39052bdc3aa868b7791d24b60052a0323366f5b38bf15a838465a7a1a40d4966b120f645db654cf5f3251a3dc4bfea920dd4b11993624462e457a7e4c129c98df9b89e25818afe8414661e6f2dd521a2b1c1c1b9e3f10b76cd1c3a2b5f7ebbbdd00238ad3069e6ff1da521e7174778a7137eca1a1bc14da1e2dcc684a83929b80505822f9f296bebdbd60946b753a71a5105a282c21e3c1a64ec1c6f65fe56ee91d6ca265574af9e22cc6ae772a7528322bb0f1a0f4e3061e7872f32fa74a915265c3f68576a5600758fc66b6c04cc0fa6b0c3098429f973a1ada8f24ff21bd05010a3fbbb5b451d0fb7aac4a034d289194a27520e41b79b695165002279745f27739657582010bba489e" + } + }, + { + "CBC":{ + "key":"e94fe5e8bd9d852da34f969d536299b9", + "iv":"033043bec533e687a1948f1366348a59", + "plain":"f9029f1b91d50c2154d494de463333bb", + "encrypted":"5160ba08a6978caacfc71b30b7e48c2c4999f7402846b8b05ca7c17fc1f32a26" + } + }, + { + "CBC":{ + "key":"077ac1ec8fcbf2f7d1675cb4e1cbd8fd", + "iv":"a0cdd559b0b931f713dc715ef129910e", + "plain":"175bff7dc0415d650b88c0efa6c2b4f9", + "encrypted":"418ac86695d4f69463b11d097a078870e35eda96ecdb91cf67254b87730ea538" + } + }, + { + "CBC":{ + "key":"2aaaeef49a30b20afa639c6df2803860", + "iv":"cbd1885f26d8188396479f9a6af3af7a", + "plain":"89760723538cf5c5c16e6e5605b7a102204abc", + "encrypted":"16fd520e4f435a6dd405d75ecc5c4b9148727bed0012ccd322f4d65ec8e1de16" + } + }, + { + "CBC":{ + "key":"bdf12d8f5e3bf221dad790f4c8da4bc2", + "iv":"91fca7e2fd524e66cb2d18b8c07d0e80", + "plain":"05", + "encrypted":"e5214e2d30058ac25c3bf5bbd3e73c0f" + } + }, + { + "CBC":{ + "key":"6a8913f2623e0bfb89881c52ea791147", + "iv":"dc0a0e34361465ab4a19c31e6ba4637b", + "plain":"3c9b331556e3622563002f389bcb37ff32be4d5226261076b00a67b274848af15ae2f19172a6a7aeb96e05e3257be473c65ad7140e3bc03eddc221465224e8d657a239b676c1c7635592a73efeb1c8e4629e599eb949de4652928cfd2bb891d056a70e48f7fd769636c1101d96b9cc10d6143df99088025055eeaac0febaa0588803560a58e080b5b43aa664f608971fcc7c3b04f974b3ee1d8150002ef72b03dee286db23a1a5412a6989e18997b17dedc5a819517fb25dbca72e7faf62246bab0bed69e8eb57f06cd0d5d728e14d84e8944703bf25947232e866b41f502c17ff0f80b4dc2a835583328941e15b7f3bd9808f24e26ad3aed65290d0a6a26186310cf2ecbc427774a92a13e74d76c16b36b5202816d588d5a10702132ebf3f36f5c22dd2bf8e58bc0fae81876fd13bacd1f09715e2a092d1fa174dca5070eeb9fe907fa4e289d838455b6664135e12a4997d48c0f5b80d2e08aadd2275eb58d0d9b60651e42b65f694a0aca43236a2b937ec26b8e52cafea39f48b5d2db4b851db2140e2b94106308bf9600f61d3297a8df003af529f4d09f747d5648d9d81e818c4135f9cecada4696661e6ecde0ff585da35c3a23ab481bd898b75b97912ea35ec231de6878eca3555c410f0915b31894ffe38dda3d99248dcab45e2b9f78be49ddf2e8c42f7f086f03b4a2c87899574712c0e6ed27ad77d1dfbdd7be6601b149e7f13e724f488a371e3255b14af9173168299b7b4a048a7f84f395db5afb06adb0b7d6a4ff70f9737561db95e245b517d05bb547738ec6fc02d43367a564bd683a8c3127d4845ac264d03dbf713b0443470c7067ce76d26545c688fef655e8f93ead789c062fa067d45bda0fe620177af5b7ebd16e0b3708332aa47232494a7fac8f0701a28e5bc36bc0594151c00e2b131b033156255a1e61fb1e988e3250911231a1f2793596175446acb8a5d38247d9cff642bd6692301d4321bd877ad01cc2dbd6b9c2b323433b6f609245b85899858211124279e5d7b154177d94471ef3a572e7f5c2cc6d4b0e119f3cde2a4acdfdc18a96cdd4a5f397f424de5cb0996f117b5ae92fe61c01385a47ed60cb9fbac62ac1d8c1ed3588e20f08863a0804848aa3ed97ceaf01135eedce74cb689393191c5fa374c5433e238d6cf94f2bc745f3031aa9ec0cc3e66a86866184bf09335a3aa97345cdeb6467dd6f340ba92c2633fb46927453e6499dca1bd7e0d7f454a6c9aed33c60612647117f49b34279b5bea50a5512302f8b7ca564ba7fcbc6b3af57f76209356dbd560e0fc559c483d7a22a2c2988aee14bcea23bf1d71140117aca9e7319c257e788edf45705ba3023be99b45798ac67dd599f6baa5e33334c04022af3bf73fb74a7bd314831c99b72267f7d44696f44e2c9179b450efa623bde4b8c7721949c370d17adc50bbdd6bba7fa930c9261bd33111c28f81d662bb60fefdbbf556a508f99eb6ffdceb51434d606804e0e3309484b367589c9dd8d7738f89eaba8bc8e14701693e82247a09ded4079828a6fa9e6fa1143d30a6b17124ad93064d3ee0ce4886a9b26993d02493af94c615927b5085509d68b87998270db11487c3f96cbc28e60adf6e8b5bb92d6992a6d87eefb3b9a27e195997a2f665c121d23fa3dddad8ad407e2f86f467780de6f3e1ece5bf1677524caee528153dc0334a9ccf071afaf27f58bcb30d935c84970a16d832961a992d952d380b7db2", + "encrypted":"790105a5c46fcf89c7563f9479afc01e0e0846d7c93f710716edbd236931ea1f121fb3b4b7f29ec0f6c5a2a19aafcf78b64bd80a8cef8aafceee274207d3f4c9c4ceb25fdad1ef441ec78fb8f7bd074e984f4f94d6f489cc6002b6b3d8cc5812a34d76b7a6b6d56e32607165c0a4b2bb545986967d4fe4748887194eb990862e526b1e474264311dce8480a361efe71d706bcdde812fadcc1423af5b224a80529ee97671b872e76d12bcfece7eade57658b9fdaa6abfea7bacc1618304d1fac3a22b88784a5e41785362ae3f548997cf3b3eaeeb5ab9af8cbbb1009b6cf10f5e2b2c96262cfc23b38f3164c233130403549579c2168ebe9d6f326143730636c0c25c16b6e8a303ac3d727ce7539fbb5c65068aeca07c32900601761069ddd839abb26c9d11056aea726dfbbdf87624896841a29fd72468816e49fca2940a270e43802936ed13fb799d9f3fb849f0858778059fe4b61cb2d49c67c41d162734ac7bd88b8773e25c51fb3ce71b7722b66e2b3458ae57b1979fc07a9f65d193f9c1b3212ff1216650a792339b13602b025c15d3de54a60fbd8182cf05ce84e8f09627e477c42fd169d291547f25d81bd0a75bb340b9dfd0d78c551f8cff66bf7040507b643641308494b447dd414f8ad5cdb4735888089b5f01f357804c776aab584be39e2714bae7c9ebada1d50f27b03a8214ac4c0146824dd68f0e00b14f04213f07dcdb3f60ca7fc9be2b771b5918eac6652dd8cff00217fd3687df04f731cbd3ba4d38a533161d9bfc6bdcf265d8b33f2c64074d75813f4f2bc7d54a06273592c98a88eb1783005e08b10bc97d6be7edfab0b9710a55fd25aacecd53ef9b94cfa618966148dde6430f9e338fd32e8ca6ef0a0848e7922b908b058921655a044a2d9ac41f20f13749589751a5ba824dd552727e8ee83ab70404b54aae04d81ca31623fbf64163467fe95df27ad47dbed6712f6528988fbfb4bb16daef6208980af3befef48e95b89349e55c23356823d9a850316cae2d236b5a77e74ed369d570206638a4e947fc7cbad6c4e21080eadb37a24395becebca33727ac5ecd353335f36394fc261d27b3a646f6c908f3028576f4412b3c810c382898b38ab3002d6db6b78077f1a090392f7ab2c1958d33aed0d1f0a0120fe919f742d6f4c66213ae8376ea489c0f39aa85173a14cfcf89038739a01d9667cfedb3c4aa525d6c1ee0ea6c9f846f554076f84cfdd436fbf784e5b3c5711d95be3eab31363cd11e3590a54ac6138d7aaa7032d5e11916d4081ec03d51ba8fd31f7e59160084ced300b4abc7dd4062a2f7e105502be4a7d2447e100ef72ce76ca72302f62c5e96bff84cdf27e16b60c72370b0284e0324e0fd11d26d40e0e1b991088c41255f4ac0f547827253accec06168416a36dc4349b8b14a432f7d479b3907acb188f977e1c52f3ec465810fb7eddc1647948841e08c7ef2d42d39476537e54e4bbfb7497fc126631e55214fce89e23dd8ca62d278d7e1b697e70422d516b6d870dc9f4ac3a313c5bc91d6340a678885079a58713081810f9241495c63a7c418a37e0628f11bbbe79f124d2446270bc867e5f4061dc43cebf6d00b1139553101cb2c6ee68fe701a2f547d833420bc30be0d086006cd7e296af5fdc6afbd2df2af9edc48689558436258f0117cf08ad1f01f6b00f59d7ed77344df116109b324cb195c6d2536111adc2e212ac9837670fb635d40a50165c25e798df2eebb12b490b53da81969f" + } + }, + { + "CBC":{ + "key":"befbcc30ea3feffe53d3472140854f02", + "iv":"bf29e809844bfe00aff43596a32bf971", + "plain":"e7a04e1d8eb582c3f1d2a0a6bad8c49487a7f656aacdeeb2f66d378652b60902449e3e825e46300650b3521cd1a8b69f53ae58e3f4fd", + "encrypted":"df64fd08f47b506618ae412eedf8b7776fe4756089e29a2261c3823ac87eaca1cb6c413ff49b5f01e2873acad4c400f6632db3daec5994409824d434a50844a0" + } + }, + { + "CBC":{ + "key":"e4f5a45e0f3004e013bd79b71d918b06", + "iv":"4b37989ebb64bb581ce49fb3860086a0", + "plain":"c7be1c35b0056706021b05c61c34271e", + "encrypted":"5cbaafd78adc458dfd35cf014fa525bd416737ad9624bc7cf3a2498ed23ea457" + } + }, + { + "CBC":{ + "key":"d0472ee476044250f5c168e6841b743e", + "iv":"1146ac3b2e0ae2af3f0fd800ff5650a6", + "plain":"2e67958417ec35dead82b3026b446b1bd3233e6c615fd562702f207f56b0b2d9", + "encrypted":"f88d653be55048c6793b2d3140f1e8b7678997466fd418b3ac688155bfe8ecbcea6b4fde332b8a7df3acf9de5e7954f0" + } + }, + { + "CBC":{ + "key":"755ee6520d8870f86a53c5e51ee24c6f", + "iv":"804012c23badd1d13f83f3143768b725", + "plain":"bfa048eef35e2c56c365be361c6a1d39f599b2c28258c93b472c67ff3e955c56d466fd8aae23b180ee1ea096789e6767250020a2c009979b8721f6acf4a3cca51b04175dc40777a5c17f7124a1438dcb3ac99bf71d7e2f00631da7ded75e4e6392ade85d49449e7bf4e3d8f52e92af6bb832475f1605947a748fd6c95ddc4b83f6804180607774eaab23fb7e60d2a3c87716dc335e571af901ee807d0e0717a651aeafea2b22a348c7863c8b89094310269b3f0140884f36e53d12da88d519345ac97be31de6f462978cced9f0e1f077c4bc18fefb5873e6ab7369a3aa646d33e0ebb22fb5722c7cf0a29a235f2895c9fe9390a73f095c4834d7f247fc9429f7", + "encrypted":"c69b7e5c870b02434d002506e99e1e9a9b47184c6d5286e448a93db29bb579ceb55d6b7661c64bffc5a57a64d0bf945e078afba232605c18d5cf6ea96edb5dddc9697366d115183a958c0f8a5b1821f7592f231080fae5943c38d69e08d5b70f268430c74dffead453cd02c9247a02b6cd0c6799a991e77cc81da71c33d9b5809fb0b4de65866727547c31d85819386bcba17d81c561c23952c477510f253956dafac95209486078f0b0b3539f55fd6942828f55c7a987d0fb188bf30b46044d3a437a6883be80384df1a1cad1fbfebaef47b048a786f3e5831b42ca7dd44e6f623702adec3142f49fcf00ec675411d96ca6692e54e9ae3bec4ca2612693b8848f8f76f6815b7f7fc1654a9b0f0e5e8e" + } + }, + { + "CBC":{ + "key":"5dd0439ab7b53d8420861ab25b6ef59829fbbdc1a8899c49", + "iv":"5a255d08a03fc0a5b4baec123ad1a98d", + "plain":"bdf2e2a2da558e9435467b864199bfef", + "encrypted":"6fd749d3dc2ddef9c0e3d1e39c785636086ece2659b62124566ec93fd11cfc70" + } + }, + { + "CBC":{ + "key":"33f5f6af3d000c6d8489c5cbd3c2b2521c16fd96335d81fc", + "iv":"f10411b5804599ef8528c63aae7cb2c3", + "plain":"8b4e81d535f91c38b434169b674760cf", + "encrypted":"ca598f4febfb34860b563475e831f4c92a98a49e306f481fe804053921ff1def" + } + }, + { + "CBC":{ + "key":"dd271d89b59804dafc22ee2e72a42a266f2ed1ae952bb49f", + "iv":"35caf5d80083ed45bc7321b9aec5c434", + "plain":"9706002c0f3859c83a63ec8741ef17141a1b49", + "encrypted":"3d062b0b516ed41d1f3daea66aaa7d585300b045e720d0d593e3d19497bbb17c" + } + }, + { + "CBC":{ + "key":"384d40cc525dafb59bc0cc999cbd5aa33dd7b3be2d694bf7", + "iv":"5335e128a3b00fc00be69edaf83db0cc", + "plain":"ec", + "encrypted":"3c8085a5f3f198ad863a6db02ab62a0a" + } + }, + { + "CBC":{ + "key":"2d90a47530cc9d6bf3facef5eeaaacf47288c9714177cfed", + "iv":"b1983fb9b7e885ad3cd2fa1d6d72aaf8", + "plain":"55c83bca43053f42c544417efed50b1c10da57294ac34aa6491b0e5ec41c1395f15355bfa605a6f393709bfaabe03861a52fff0a80fad17076d7dd28ba58d10a182ae7b18d032de5aea4af78ebc27a690f80bd75e471f88b04dab879757230f134a5565227e7b28cbad61b2388d200dc856bc7e46112fdd2d4d747d8cc7839a707ef591bfd860fbb38da03177a0f12c7b931a4c13c5e43d94a989bd214bd00c0e215cd866ed3e2a15d798268f164c526f5d659e4199afd43ecc749acc15e91ac18e21bff30db8ce3812dfd329a96eb6c0a53bfffdf83ab658b66f0b1b836937f6ec7ab6125e9d742ca334027a9d49dc76040b4290cb2f6ef66ff79b9512498c1c743a5265991848ee7d589d604eb52e18d47fd42c49e38c1af0850515b6318bfd7ed2c734ca24702a50d2a07dc5559c9558e55f3da7057974b2f213515665272c717a8ce77dda5b8f5fbfa7770dc878f23a137bcb2062681397043fdf77f867f48d87f94d873905fff430a06a6cb884f7c0cb3451e556377053b5aff36733a4bdec943f41ac85be05b62fc4d3816025c563a272232838dcf61718bebd68bac9ca2a7b8d1ca56389506eb39ad71eb92e2c809731fa8a96ffbc63036e9eef0b2fd051d73bdd7d5274b0a7c8f8730f218a9951541302c05b8b131be63c07882d9da00c504a34219053fee53ad2c2b2f15818cf88a4d7bfe01c4f7b0a54054f8f7a1318f452ec7c9e313eb9a61a70c4bea0d516deef2401f41cc4ecbe8caaf9643e147bfffa15d35ceb4349984c50267f10429e77e6da4ef6c48257368289d8362c3c94577578da8f111b1136e822cce0b97b628d8d7315a057a4d0e984ce17c7d5620b915b6628d94b4e8927110a6876a25589535832109f29d152f8377e0d19b033b682d981087e6e1c240d014dca66b871fce5a80ae39e3287ca605b0b5ec655b29f006061d3beef8d1f84d0d3a3637279fbcff6058b1c5df338a5c750a04bb2c83b44aff220f385591f89d8428d950c3fc651f16fad23ee00d56dc83cfe403376c970d977c7134b4b6250f6ede5e7f70f1478d6302a5999640ba2291cb0ef6644eff8b36f77c818487207685f11d94005b74d6376d699551c5fb77f42b080eed63ecddc4cc4eb53e124730f299cfe3214dbe00c769bb231bb1c936920cd1e8d9c9b00b45f030b95b7d79b095cf25144758ff2aa32c0922a6842210340a307f9b3002808c7023ad23e7d43e85445bc41767f0c0f67d5548889e8be8083f523c4d69d089b6d1ca137c2bffe647abf12a899c85c2d3c38bfc9d5f1ea45eb1c954ba6026f3f7380233491be94d5a9863e477ed4d3de7e2f160c43c5c4b7341b219e5845964efbe7aa210764be2387b933422a8e72f3ea8f8c49a6de6d9f68df6e05ec215968c6b53244f20bbe425fae9c9f2fae7b97ca37636680cad609245392474c50762f02c688c89b6bc1e0a3ab0d9f53b62c11add0c34883eac57cfd673e50a83a845ded2c65a2c4fcbeb6f11c4df8d4fd7989ca29571f437770df0ac3c5f12f5811c768a70017736cdbc22542d45612fda159b132411f6671ef6bde98141b10818c3cff7305f9dd03bae6ab0a65179dfbd0a35caf6d52bbd8b31643f0f654bb12830f2eb7febcd3bd4ccdedaa17d340e70ab08fe123619f58ce414fad72df186e1385d2af2f2985fdf7b9d12fdd08612de5bcabef393e6d5a86f4012b57b92d5d9815551d0eeffe652e54e9e16a782c7b0", + "encrypted":"8470cf10eedb04c856d68c6c6c1de84db4b27fa5060b9404b13732933c06c7bc6b1f53e0ef6cf1de455669b7f8a72494639b782e5e16b2383f23b4eab38cbb07dd8cc37525d311af603cb65498939dbc532978b40b2266d0e903641a5a6e12f7ede5f987f8d806c55c6abd90549fe9ef8085e1ea7711a36f24a8c10da6a5a0fb8be8cd8cab966725e5a6be793b258e055bcb3c6957bfd821dc6fe42c8f58045b4aaa95515a77be8555f758ba14c1c2229f822f58df162e9e4473592cbb04ecb70e912543c8613f72d006d082956a732e634556d0205ff6bd07fcf43b72f6a0d5924787a0f05410b8b5766a9e87cad79d7ac525ee4c1ce627d5e4ac9a0a186556d41de738d2fa6320dd8d7b1adde59be292403d265ec4fdb773934be3d9e8fa487d7d15f0076a661f42a36de3d52381eceecba5d344c4ea6453760cb4fde0c7541e1d19629f7928e6b339ecf96026ecb369a8fae15f2c9b0456bf9cf545d63a05190f5cf5ffc1323ff515681ec4d86348d629541199aa79881b76b06246767457bb83e0ee7bf77335d64606447e37afd7d3d38f8d5458369c6eabea754ec3c96d9a42822ee13f451da425c509df3ffe8ca03207b15b91de54409a516d543481e32ed8fa22bc1e29e8df0399c9bfdbe395fc4ab2dc17134aa4720211ae61493aa4ee4017a08d9cf8bc7e685e46166454faf03c0fd28e5500a2f0336c3499193926c0d06aef3dcf4606fa62590c05725dcbf5f4b72b02bae98e998a79392c42b85f804685c1455c583b5c15949f1a0a268180c5a57515096d07fd21ea7b0f6318c22cdfd9f0a6ff959512ae64c1614d6cc85fcecd97b8628a0079eb1e6b8f1a3813998157bb5990f1e0426108bfd33ffd6348f827fbf0bd8df36359aaf41e4354940ee6716dcd867072c3d512e9d57e62e8290ee8d589d6039267bedae19d15a016b38a9f2a3554c53466fb058d0c08148a96a080f51e5cc559ee7bc49b3786f2f8d0b13f8a5ab65b742202ca034a7f45fee5342efadb6a0c6180c4447cd39394fe6bb2a0cdf565e7525561e7339230d173faebcac27a0a0c01b3b8dc0d36b91e4251e4cc7af005c59b5e367865721af160d6e1b10c8a5ae328917db21a63338967be77400d62cdb301af87ae14178f07b0faa837ffa109604caf7682ff528082ddc8e981c01d11f4a84b152d80da31ec330edbdc779ca4f13f854a28aa0b23048e2cc6053fdb3b55887c972e1313d46332e8bbdf36be8be719c20df29763fa672abc1b450f6536f0acd4ea0b1bd010b66dc861c82434ae682028139b710e6e89425f716ddfe744f7b7e8b97863c468a6d516ccc0e1e1535846855a4d7dee0dc28951e1ff265d3073f3c706dd583b56c5ad8fdb361cd1d15b52cfe5ba5edf4306576a4e81a9c4acb22af9aedd0bb463cf53ed1127453107a9bd6d936a250234e6363a981ed13f41f3c92606455c5d24c4971e5f2e7a8daa2a90a1ee65d7776af6c76ca809e4e57ce666577f71fed2cef7e13841c615574d196417b99cecbf9e062d54a9d428bdc89f7adf03975199ff902ed420d1e8146afbbd2049c088428115c04e03dab1cb704b81ccec68aab023fabda69e3c9877b789ce688be2b1e0227a9721785355ed67317761748ce43ca26d5cd5c74c4f818b30deffeb98de6531c0f35fc3bf0e978d2e76d076ef8386b408d943234e87f5cd59f2d30e63aa18db22da0e6facfab7d7354ee9af36fe1c0b658b9e93ee7a2b0b864794b1be220c254c40" + } + }, + { + "CBC":{ + "key":"bf06c40870133c26f1dd74a7d7cc94b034337e0fb9d07995", + "iv":"7e12317ce6650401b91f1eaa3fce9ae8", + "plain":"9a01554ad465637a0e2da989c8ff2961490bde44d37a8da8f0d0449abb3cb96d02c40180df179053a6107b1ed6e89a35f3e68f41dccc", + "encrypted":"db6a668d9a130ce5ab39f0f280b3d2d13fdfcd5a85bd012ee615aa9c3f6e48d615a461feaf6db89d492e9d086ac87e31978f483d544fca2edb175d4be4f3754a" + } + }, + { + "CBC":{ + "key":"3f8cfdb82c1d63414509f620df13b02e29ad1a7ad2fe5bff", + "iv":"bad141e908f5f762ec5b7fa9a8b6fd31", + "plain":"fea4eef11a7fc6eaa284e999115fb957", + "encrypted":"32b251d52cb4ec782392963fe3f592dbe5e8403c86e7afc0ef3b09b2c5abc764" + } + }, + { + "CBC":{ + "key":"30c46a5133448aebade81f29b0df2540ba4a434ba105c364", + "iv":"28a8e46e201e83a8f550568483d16fd3", + "plain":"4cdbcb384c6c15db099efb307f39015a146c3ff195ac4aee97bc439c231ee3d9", + "encrypted":"1688a886f76733170a1b5b5508b4897aad93ba83c03c26117424ab52721d537d1f45e6198a9ea7b7a15bf29995511aa4" + } + }, + { + "CBC":{ + "key":"f7e6d9491750452fa6d7e301c4a326e8b2c8be466fb0e9ac", + "iv":"edc59d2e80b47cf474a1b18059197f92", + "plain":"9c5104fd4ece45c5ae1631e3effaa8aa937bed1c2d6541579e9406b6019245832ca983d8fc3e9825ec00dd4caccca7d1d2021a5e80cf55cbc23fcf8744a7901ce98d2b63df144124bdb81cd1e4fc83afddd501ecca3ea5592f2062e306c67aa398c27f090efb3202f90330d570dbad3dec42725205f3d51f3de03c26f3656c67153bafee4a433c30af734bd034aeadf866d697905d883deee04eeea174f8e68734d74cf1908dd877b59f31c2f17b4cee1fb005095a94fcc8e921b1a37a426ad71861d2a1377aae867e675cce2630d7f83851c7ef27acaa650db9abf518814c60ec83242b1e42388b5ad29de7c68fe050ab680eaa0f27a079ffdf4818b89ca2f9", + "encrypted":"bc9db3bc6b379c6c3becc27fb835456bd62656035a4ae2d20261a44c58f64e599539f1a98b4c191ffd6dbbcb34e5944a6265badf5278d29f2058d6839ae70438a45ec6e56b9514684410126c0baf5cbc867a5690610ae2930043767eae2cd0283f2979a2dda4a7336bcc3d00feb8dc17d761509e7db6a4b6f2dc64473520cb7afc882765c6f1e763f8b82f002ebb05e29cfcbdbf433ce1352e7dd3000d75571b4458f54908909c59f9b0ab8cb7eb27e80d5d6deb3fa2697c0a25b66c4fad78c2b2d0c3e93ae80d68fd15ab3751a2c63dfea2fc6ec0e16a8df6cbba2bc3eb2e0378b7320d3c43bc07ace45d3f0328a7d9ea9b9b665e483030cc7c7d08a9bfc6a19d1986a28b7e747b914f4f9e102deae8" + } + }, + { + "CBC":{ + "key":"cc87c3618d91287d981728eabfd1c4c2f6223208225d027a", + "iv":"3c558486023cd4cfaf55fede79f10208", + "plain":"f2f09ecdf426d33b36209b58f7863d61", + "encrypted":"6d17cdc78b7272771b357a4c71f206542a4216b29d47dd755321921e6df36559" + } + }, + { + "CBC":{ + "key":"b7c39c3d19da3ecc6727c6514267f87cfa16ad3937865489", + "iv":"9bd49e10aa0f2e0588d495a5a775dc31", + "plain":"3da29e57dc5355c099631f8a985d3f5c", + "encrypted":"c348b7006ca498991163ac8e50ef315eb23db7f1209de65853d00d7af27fa953" + } + }, + { + "CBC":{ + "key":"9c973f64a8b8b56c92a72221b8ab2992816297bd1e5092e2", + "iv":"7d6d28f12f894da6655d80509b29cfc2", + "plain":"cbeff04953810b5102a2996ef8fb36a7bcb15f", + "encrypted":"aa9c9d92b3217adaeb4e28d620048df49b06226a312b0429d1a157b0d14efc6e" + } + }, + { + "CBC":{ + "key":"05fb7d6d8ceb31b45bc7056c62b911e1c188e3d155d62244", + "iv":"f472eb361a1fac29b4761227e4cc316e", + "plain":"a5", + "encrypted":"e39af6fe1e80c300449fe48a17931c8c" + } + }, + { + "CBC":{ + "key":"dd3136e029bba517c62609ce805f9b7b27e4589e059886b3", + "iv":"4a97436ec714332a9baac89c3a1ba2d3", + "plain":"badd2a0f8677fe3ab275e995a1d0be785cc5fe7411d947d027cd838a6615c5a77732d803f542609f504a702ca3a8100504d53fc3fdbfdf18ed796f20acaa235c35b7743d3151cefc66dc71358d18204d2f476953d3f014d03e305685b7207debe66e2910dad6387e97ca9552075eaae70864d5d7720df86b366bf91a2e942da986e1e0960df20cb142e78d153a7be9ac1bf909168c8cf06ed7e3f1c65603f937e55d28f6bb67c991abe0da4294f43a447d1ef983177b3a7e6e1072317002be681c590b0d4a2b58eb9cfa6e762400456c96a97cb3d38bfd068204af77c8310c9e942d1d457f9ffc4dc33c9ed374d0a44d119e0399a0ef16d2bc727313814c52402d966357570a6beb7378c403a19d91427628b7fb9fb18e84ced1fc3da2789206857590968ad9c0f5e80f80710e1d8a1ba12c96c63c122f09c41c1151c0c129149375398e7841f045015a55a9643ee137d1e23f6185cd6f884b7f4514e58604c29f71824bc9276f70d3f7efeb88d45e32de8916340c25eed63846c31e95b0cd0f462b26846646804c8b61579622839c2532aed4a63f5c0403cd66c136672f87f5732a6fc8e284afb93bbae0ba44199e3bbf9603506cde8e04456b8705c73488b2820570ce10b854dea59993f28e69e5a6a6131c4c969d13d4f4ed216d8a14fb124734e5af1a8b0e9634097c3acd0b6d1240614a7106e4551eea262b6fb935348b927bddd51d68b62c2418c639ddb8546960aa3f5cc74dbaac1ea3d1af41435e2440c57dd568e67b210366df57f40964b19993b071f7303148748c5c5b6957064582d94221e975a4698fe220ccb308d5aca0c4299bed3e78ccafdab88ac729a8a17a0b479b9d1e814c3dc93b879442bd205002b61736d226cf063fdf9808984fe471fe900b5dd6cb7dc849611ca82840a58eda4bedcc8c96dbd3078b5ba4c2d6c106756d525d36e7af0086eca1ec1fff8ef12ace6f7df8798bbbedaf6e72854afcea46251d4efa5e065d0e88b55e80c339d02786c34535b3447d46a1af86e16cd4725bf5390fe0f2385ca8507a2bfe6c8820bd1ad4a02644d6a979f06e1418c7f5c4dc04ad7511233ceea7302b4459504cf5c562e903eeb23d3ac62a89b74003afe92d00e524af468e8f136f2e848f20365d381edd8d1bb332e918a277514d962af7ddf27c52825c4dfa1d2aceaede5f1a822a7de9a9b9e53acd6d5aacb45705bb5e2f73b29ef5c8acef8d42a0d0d0aa4d1e382c59d1fe3e56d8ef929bbbd25637e852bfe018cdf48f7d04617be38f228cf0312788145daca796c307bded8e682b00b92c3f0828dcb215f336a52e0b91aea0413a305bebf3a594f5f154e7e36756ff322c642740180bb48ffd14b186c9b010af80e90dc113d3a8433774f2d7e3dc76cb5eadd6fc264f78b912328c1b0dbdaf41dacb0cbcfaab8d109abd5075309df87f8b758f2ffbc2eadec5999c2f55a80c8a9b22942b32a14232606566af9530da46e454537964af0c2659e39046209ad82e28192abf571ef68ba1cc5d34b901aac6db36d3d6490b697c51ce07e932e957ed49c1b1edd39161ec6f0201792d5183f01dd634a49bf98278b1faf2ec79f1fd61b81a97ea16c097e7eb529ecb15b50f23327ac92979c83a9b3137c4a9f7ff6c2b9d0aa1d1f743cb85f4ddfabe4490f22dc12e964bccf59e9bb5c423eccebf13eaa09f002fba4362c8c48e79ebbfdbe13bd0483402396f43f8e7c8617c53f5786b", + "encrypted":"94ae14737e3a46e0449fc9f9f5cc645b95372886124fe0d98a6aa0d6199b229682b407514e3363a66a64fc6635401da143c6519c83df6cc3d157eabf5905bda9bc1821ed82b8cf511fd89cfe2df97c15e74bd820af9be1ca941c8379d47b4c6e2961384fbfa0526417a9b41d92ee33ef48df718b87a537f200e515df3567cd889c1606e16dd9d9958be1c8e2435b57afeeb20523beaaec5eab477bb411ed7738e00502b52bb737fa28af3fb4da79d1713a9af31f315b7e886e2b5f074f5d528f89aa349abdf067aaf820e3146a1ea1d6220d6b4a92fe4bed9a0ab2089fde27936f498350cfb9080cb79e974d4e0b114349f71659fff212517be1fa19d560ce18bf5d177787e715b3fd9c5f866912488da1b55f273c48b318bfc38c1ac5783f9cdc718a84fc97d259ae319fc4774f962250496b9cc22aa83f6ac5a92aa3148e931ed63a50c8e3d22acf3c241909cfb57f7f121398099e9bf108c12ea61449ccf46893b1373b1965d6c264eef8c088d7a2e7e6eba62bb3d328bd9a4511d751cd82be8cbbe423d197591cceab073e2ea659a1efb80a0e7a41f8483e41ca61101a8a031e7f33a49e28eafb61ab6062df1131c38fe71b4170fb18560f31a8bbb7eecc7a6196d77352973c3b5fbce031b367e48a0d23aef29a940e04e21f0d9be4f155520a6bcdb5bac7a4d35abc504776e339805c8e30aa6004a57ad04ed35b1ca7789f470a3b001d57add063a0d08888383b0b196cb8f5cda4a38f41eeb77454ff9c4972cea14ebd5c8e1109ea1008e6f55cbbaf7c4bd98bfd3bc972479ca53fc321ca151f215946480c12cbee2846e47e1bd9ae453f3fe0da42c03d5fcf291b60b90a5514b4d2189eea661a9a57023e26be6bb9b8498ff5a58401c700c3c603aad2e96757855dc604325f8b589feed12e84b817e506014bf09b609bba348746f2670ef17a9ca17470ae149f23d16f838cb7d8711d19300f8df29cf09076a0131929ac17cad25e57f5afe58f485c5e69b791eeb53b766919eee6fc1727b34d00285f13123d3cfd7b4b3c468e79946d9460e52f1b9c314afa2a1fc04f4a42433b4680955704a40c6b78f6a51991ac3ac0d23d03de2c746ec10f11d19aea76f1e4dcdfd5d13a97a4b04182a097fa3140cdb628f37b66d36ad2b8da378fa284cca77406d1fc9670768f790785f8b6c34c010c8f2bb8cbff97494fd328d9dfa9515a6006c3df7670a440fc4a7395dcb250943e30e67c67727f69e6d8e96eb056dc96d2a4723982f5f967e7469e4852ae761954a69ff048cfd63c48e8cc3216b02cc2a7a87c9c26979decbc0a3af45bf5c7cbe3e8b5f0e15481c5b64a059a8376b87ab758592d50a04dc389f89442b999ae4a612b364f949599a78ca168bf13291e5c588f2eda164c7f60e9697be1135f1b969288ffdadf724f5ea545c92ad9945e832dedde2bb1f2b7e15ccd2f15f94656921f6f1f7997ebe6768e0c1b0f5122d9806a5fa3944566bbf7630843d68466e60f91dcc3dfc038237022e7af3c03544ccd64570ec79d8063d07b2c51926c8b22e89af43583cf16c5e412f11fcf679560dafc02ffd479d1f418b1a920dd2e27eafe226196d5b9a80768316492c7a6cdaa6c895bbd7ab504ee442624d8617c73d32954b33e8a82b37693d4b0add050e579ff8adff3890c2e10ed4b241a6e1bf1051ea6ffea8b02be8d96555b37b075f5a487cc2fc0b6e877a271ba3af145d8aa76466ffd526dfa37642c0f88ddc2310e196323e6" + } + }, + { + "CBC":{ + "key":"76a383d7bf648822e7abed41b06a290a3409f22c4cbc8961", + "iv":"691a320ac2154cfc4bbd399dd048d5ce", + "plain":"09511f639873e11dc44ba6954c805bbe26aab739d747a8e3a183f4379f198be6a8fa42f051a001b5953091f12c770e6cfbf2a87437c1", + "encrypted":"704483e6e47de0421e0849f8422d34342d8eea01f50155d2426a9c6e099959acd8527e5e3e56b06ea1992029c69d4fbbce3a1827eb779aa69a4cd3c77c7fcea2" + } + }, + { + "CBC":{ + "key":"bcadd52b9a872042ddc212d2bab797c3a378c0b6cfe086fe", + "iv":"331bf1b36ae8f55d24ca6e1357d4749d", + "plain":"788ba4524e127240461fd270ae6a240b", + "encrypted":"c7b8d7efeac9661ff4f47f8234873a7266ba85fa0c8f5eb074323f522bab910b" + } + }, + { + "CBC":{ + "key":"725dd578281f06b1455e51529540d6ae28f609863212ada2", + "iv":"ab5ca1d9c8a2ccf1497ef6830cd62b69", + "plain":"c73ee9c4966a922f9dede484fa9a94091195b3d73815f85eb786f45578f3b3c0", + "encrypted":"c6bc797c6746eff007aff4ab7a70d0c610c78277225158b0c107d47e858e61287883a4d397a915ff3e71de0c3c677afa" + } + }, + { + "CBC":{ + "key":"4ecc7560640fa823b08732f981bd34eddee25512e8eea4c9", + "iv":"a57d8c680c66c16cf7588c2d26379724", + "plain":"f08586e69a1b14d1013735293a5530f54efe29acb1f5181d90df8295eeb5f514c0cb05bfa28e2543e64a3ee80597372899e609448a2ae60bb9925464222c3133b5794265e6333f24c4dbfac72caab766be967ce33796f334e774b13e1f091e3ea8d03e17bb66e26a75196e27d596706a7dc9c8728511b40d9a5e2d4c338059285b0dfe41cd00ff3b99e71e79bc042fa6476e83b2d857d50ff854484725e5727a667689f4af430d1e7399f4862823ac375a5a30d6c476ff82b6fab57c0c2a887f357fcc9389b2d41fd44b4aaa48140d141a1b0382a0fbaf4e106d517aaa03ccac669485b39eacd8386b2fdb358ae99b55f416e18b9aa87fb3bb65dace5a33bf54", + "encrypted":"d60aea6c079202fe4add0048c6dbd9515a7f8d8e79d54dd1c797f781dc0aa7cb07e6dab4ef3ea83eac064f6d150a1054e26af726830a5e6b8b1114b2030a32dde8de68201ff39969f5b64b2e316a328180d51d62ec86e92a004e36ce19011b31d0d14d601b0069b9fa5f6d0a3ce78eada70d098f2570c2ba84092d1381720a5a4f9ff96e33672fdb92a6ffe367f350413e2db4e36c7722aa3580143a36446fe3395cc379530282bb592d38a380f2b34e423885ae9f3a2d78beac4f550d91d897b1fd4a0f24c53e07ca9a92a4f0176264550f27863602fdcdc748049c5be285633fbefb55f591da05400925c557c0f1cb8fa4aa70931f8fc8370538071077a342e8aa5537f27b5a2250355e9db41f98bf" + } + }, + { + "CBC":{ + "key":"aa81c6c804f34b669136cd5a86eeabd24c2d30188b20ba97", + "iv":"9c3445d1859c376296ed28d4bbba287f", + "plain":"7f95f67d3cea517479e77f75e343be6e", + "encrypted":"c8bb4ac117645ba355c3be1d1efd339dfa122d123219a764a44fdc0c50bb1558" + } + }, + { + "CBC":{ + "key":"9b967bd5908f2740b4ec4a88332330e63259d7e27c219193", + "iv":"02dcc5189a5f2d2361cd85427a5b4f53", + "plain":"9cfdd69fac95b23f31703d14c11942ac", + "encrypted":"36d5fec29f5db0f478dc1c2ed9ea034f3c3df21ec41b482b50723b703459bd08" + } + }, + { + "CBC":{ + "key":"4a951bed6ca5b1fc40c816df1e8c87afecb42f4938186ff2", + "iv":"ff015d20967c23f4afc61ee6659b3d2f", + "plain":"cf617125f412fa384929ef2d127d0a02643272", + "encrypted":"076aa507de80f9cec9cb368ec89de0845e59d07dd4a3b4419df9403b3a19a0a3" + } + }, + { + "CBC":{ + "key":"c88b61c1264490455d7a84cadd90e3afa7aad4863608c4ba", + "iv":"f0620ce15b055fd14ae4d4a56ebf466c", + "plain":"ca", + "encrypted":"5ba614777e1f2868e0a4a52821a33cc9" + } + }, + { + "CBC":{ + "key":"b8cf3497a64a55d068122ca620da09167a74198f1f79f4c0", + "iv":"6635ee790792b6a77ce0208a3aff7264", + "plain":"73728806bca8d367990adfdd46543646f708c12bffc914bde8d53d6c43ec9e2a63b53305a1c9b122ba29076bffb9b3c8f1a1c39b3f24011242d5a65fd29714c7202d974f756faaa5159386c6b900a4aabd3760ce96664c1806a7521bc3302e29559962a144d3a215ea033d15094403dd2d8395a196f3b22f2ec3f0062ec7c3ada23114818680667a4fdecf16aa196ae8df95aa85659b23ac0e8988f3765b871b175c697bbbfade4b2599985f1737032f3b63b4c638d20ebb09296d52d1c8c0e96f6408a713af03822d5abcc9957c15846a775a94515032291db975a13598198c10b91a1392b26de7eb678e09dc2ac2e11662314e93e7987a709e16fa47b01d9b889cc93b85f0627b83d0b68cc4075f654de8398a1a06efff557282995407fb02612093bbe38dd0e1e8b2011cb2c71346452f13ea54e2f2d19ea42d991676d4985adf965f138732bef8b8862865c707c719888bd0fff457f1d95a54ce6c0529f2e77f833a3cbc6ebaf4b3c8c06ae3eedd8df4d67213486b757fcd56be616cf51161af82b860b1ef768056f470bd164aa0be8bc82e8f8c8653c95a7b0bbe3fcf23f89b5c8bfe07227eb1900faf6062a3f5f47b8c97748a23d0b6880da9bf023d813b47eb99a892672e3c40e936fbfc1953841938f0c0f29b4e567d6f5f3f600f2d2872391bff9d854283eac9d8f53ab228d95f933f84c4274206cbc64d8b9713e7e5c94ecfdbb7a678349b0787feb43f43610c1773aa93bdff84f4f7a98783116bbe417d860ff5f0327d1cee1e264198a1e774531997a6cb783145b3960f4636b197b77810eea943f8d89f1050c700eb163e245225bb4889c0fc4db8ff3b53ff5d931914ef47c14955077e4fe4e09d73062189d256bb401f4c5e7b72406641ea7c4c1b93291f6fb328c7aaa27a2db546d529af08e4fdcff3f3bb65e172a69f330712400d806208f0bd27138b35e160c70e468be4fd257f9110161fb56eff498e815618e99aaebcd7b5cb813c023d75996243f56adf68467b1a5ea876e7222c02e0e25fc0637ffc35ac0d55b2aca8f8b9a11c32f9602b166a327c014a3725a4b0043b293051c585a7408eaaaada7eda27d6f1e405535d6f7b71587ef058bf649892de515169546cdfa7ce36f00beb49a3eafbc9c9c5c3a6fe73cc8cd32887f4ca0e6fbe5fad5d7b7405db5eae413b4572de9c052fae082d5cce01ef887300e420f5a8e72305e63f2ab46157b308dd86675eb49084fb7e128eef3ae064060c05fd6c989eea8562b5dda115461539c8eb60014534322775b3a447424ac616a996262917cce6c8f170d15c29968b3462f5394a17b22dc0daf883569435360323442a1df65dbdb06254634b661220ba79090ac148a9f873db39b59639a6fef91d48c56a4c074df1a6a889b5bb58cfb4298bba1b7fb2603316407173c42949d7debcda026405cda7f12b5c201aa24ae52be73b27167e89364c61482e42966d547506c41e42ad6dbd99a9287c33210b67425db879127e2ae410b6d19dbbefe6edbc9ddefe24c6d8b6d029e40b72e42aab46390df00df6f2072152acd82e8a289e0e3d155577af60d528f91d700b016f0afafe395edd22da07e9f4a0b75f9a690f1433520661104ee58631bf9fb8d747c0370d9378fb3c4f4789b5a069fa3cb13b6c25c047cfcd6aab4b339c9bc6ee0f5302c3cdde949f51dfa7f97571ba5b765d3f0cc6e3810816497f474e3fa3545ddbfed8bc21fd19", + "encrypted":"ddfec4ca07a316b838e2161f3c126c8170411143d73123ded7ebe44eef80a699cdcb680cb21b879938f1ead7ef8d6e87ec7e872a2d17e6a3d53478a10a7bcdc4a45b0cf6ce9e4e0bab68e308f1f4ff01a4c67f6bd67580ce5ddc95d49ce6a29faf866661633d1dd9ecb37ade8b4fdcf1f047a85ffa9da6ca55f819ba56b2e075712e11134fc1596f35163a5f1e120b68809bf807ad2d76ef96b789a8f5ddd457619a9f1465c895c077fec524efc4f24dd7d14d7c681e7ece78a29e730d3789ff0816da9ba8e6adb076c9590223d5c4e58630d5ad985f5e8c8c38325e151160b3cad363dc6f77d83e69bd381f999418a42acf5950e1261b7875b5537d1b340a284d21ef448392a45672fe8f23a2b584180f821f6f12c5fe6fdeb4fd131409d1c8ddd940068174d9bc5b0f60bac6a1e69c347d7cbd25683b5cd1e88f9ba4195f0b412476c4f1e9424c2434d637760399f77400cbf53131b4a4dc3b760d94a0912b91c1f2f1aa9997df0479a7a3322ec835ca57a731b24cd9850df72ebdd88fe9019698aa5473606443a686b1a30253b601d611f25d1c2c29215c29aafe6ad3d425715d81621149b6b6945350ffa46f7fb8e266e77f844a0512fe20a52712bd51ff8ca73ad9e4bdea8de6e438af49d954a18d68ec7bcf967c454cfa97b9f7bfc71292264425f1f18fd055906db5b59b20fd1db30381c9b60efc3f7e0819c2e66577d16a33ad971bed77216e2d56ecbafab33e057acab5383925bcf7b784b249bce6abcfedd6ac8c4b4227d3dd7bb1271ebae923591d9f500825bca7cf803c0fb4a5708eaa5c0ccc7ec1f36a3e9da540cb133460f206d61fa0cd193078cf51d8300564892088d692d9e444289baafb9b0841b879e229ecbc125ea18dd6e091097f68058416ce255e3597d66d7a8dd1d0ceac26e131a404dbd02a1d62827fee1126cdfd757a4152180ea7010009462c7daa9fb77af43064c3d71a7ac7b3ef836c11e81793eccc89ece557626e405ab66df8df4657a3979954ddf4a309bb2aa111be5f13194fa879c85fc8a110408688acdb1810ff30fdb892e1a4da9343a3827894f6e5c84840463f2600247dd08ee8a03a8d51dd2fc670d3231c95604b0569b8921799f37fb6fdc653c3c6619088c9117c435519a1ed42b62b63b243a18df787b7a2f4f5c0a070a0e139936f4617f0b83c7e4605f56619f2ea53c7a4f04df3f357e3f4236c43705637c6d2052dd7e644e062e4bba636349d0d706d4f2a3cc2a04b5b6b9076d755014a9d83244a3fa67d103379cb6a1e26a28a05b990d36aa99a18ea37ae588a3d568379f10e3282672dd6c12a42c671446375937269449f12889a57842b80444abe8ea91020ac5d35a0f6159b3eae042161d561f3a3ab51ab1f49b343a19161c4d8e54b5e4c60406842f481ebc7c064f3d28eb4af4978238af954cbb60bd947ef9e989b70900fffd3fcc5cb971a6f8b034b85ef9dab0daa67573ee99e9197802ce10d9f71b0ea9a7353e8e87d4d034e9b697389668008e62d7c8c4abb72491660735d583b17ffd33998077f1f67dc7cb7b3634cbe8da743991f4e1b4708df3417712905ab3e0f0f6168bb9c26b880cd234b032c5dcfca137f31007db4af5c638bd8f90e014d916752999f09575e24f7ffbe656be879ebb13efe24c87891d188f4268c3ce59eb82ef02beaa65fed7aa8a3ce3f652f391d775054445db6852e23c10a6b5972db263a2b41371009eea9804d75e8b6dff0e73377907dab" + } + }, + { + "CBC":{ + "key":"fe8e1c396f98507eb0b945f16a4e1bb65eacb1b9dd7ec0d9", + "iv":"a436cccf58b2ee8a6d6b10677aebf6bf", + "plain":"b2e68b6f45cc5772a57742c26f6ebcaf79b70defcbf6b9a727214ee5782e37767e5412a3c1802374a6bcd616f7605c5a94cd6ee89f17", + "encrypted":"49c9a07bf2b3242476a895e5e6a26ae012cc3f33003a7438a6d76a52955c3f681db94b7cdf752f94dd3c89d373fe28fde974ef95e9effd3848b2ef6c8ef00c92" + } + }, + { + "CBC":{ + "key":"6feae881416859e7f51aced7dfb629e48ba4f3d31606114b", + "iv":"de5c09f2de75bf28c5d1dfeb13dbeaa3", + "plain":"96e9558dc9b726103e4c43de173a4a99", + "encrypted":"a836bd2c5192abeddc25b0a35b543e2380d5ccb1762a57d526e8188e3bdae614" + } + }, + { + "CBC":{ + "key":"400d277dcf89627e5fe161455f37052e0bb7deacf279fba9", + "iv":"8dd05bd0acbe6bee890520395dc1899d", + "plain":"6ae3b66fcaabe04089489ef6372d0684584eec563c368967a28bb12c0aed2c96", + "encrypted":"4feb4256c73ffc05740012fb4aaae5f063b68df5264d23017f0746bda162a300b2e5610e5eb5e7b40296c8ef2a081425" + } + }, + { + "CBC":{ + "key":"18c53e242eafbc7721be83507cb8cca41e5b1a2c7572b4dd", + "iv":"5389d05b8eae76c2a2eb7e7b0c6c3dea", + "plain":"1bf2eb8204d313c4365b52917954ad3e306f7a0a3f435d6808f957f69aad338e9c8941c46d07c93b92791b6c12286f31c98293886739fb2fd4582b3cbdca2ac40f5a260832519f56fd16f7fe56805f8781b051802502f170b75903c5e6064fdc1053280b6a26c7609e36177361a3a57248ae66c8e9a36226557141e67888962f318b6157569370bf7193b22f0600c9a10acd9c5d7865b748aca48d759a75ca638978424e14aaabbeeddd195e723a2270237165f6edaaa7147c420e2703806c6b0c7ebbe44200b7ce82ced4eab6218131fcc8becfa535040e653d01c6be02399f67f529cf6f8929e80d2ea266c611f006053adb69f4f059d510a9e1cf15d5ae81", + "encrypted":"2abaa83fc31e8d7be6a8017e823d426cbe531b52a32627a7da6f2b7e0faae585898f6665ab7f3382a4a282e3afc599f6d767629d7b730d171d9153bf548d9ca1cbd67fa111b38b5b19cbe8f1f37359590735252de25531aa1bc86f79352f2d9d1a9a9ed010c10b90ac44b3b823ca5e0a907ffa44c900737d8bdddba0a3661158a3926f7261425e9d2918474cb0cdb179605b91361325aa72a193273f2147c032c1209565a06e24df888fb31ae12c6d0de83c4cf4ba69d50de4398d462ae9ad521766474266c32e12469df360f4d09286486e6ef7492c37f3ad9115e0696cbdd4e460470f6a0e7f655e6026625cf6de3020ef74907ea84950e8bf26ae2cb3bfd5f0e6095dd1cdb91003e446ab4aa7ea18" + } + }, + { + "CBC":{ + "key":"07cd25bf6853700878a73d8478ef1038504e8d7eb63d5da9", + "iv":"a66373b0b04101092aa9a4b26ca9b583", + "plain":"8b162c71fad6bf7f78ff2d0c8fbff66c", + "encrypted":"ad3b72fde5773fdd3af02aa62bd220efdcf1cf337db09f24877854789d365c3c" + } + }, + { + "CBC":{ + "key":"0bfe09fe7c24d8c933134a600006cfbae9cb87ea6159e0fb", + "iv":"5d8bbdd879a5088d0ca80a1c61bcf0bb", + "plain":"d74a3d4717be7b83c857ed6601a77329", + "encrypted":"b07e432ef3a84ba4039bc37848552574b8012bdd96a68a231bb7f81d022ce787" + } + }, + { + "CBC":{ + "key":"7eafa255c4c554e3b64b7e2a4aed247fd654d0d87ea65711", + "iv":"7c77d99b33b85cfb79c43779b5a3fd61", + "plain":"a683df8acb9f75bc5c17f8cca57524313e7e5f", + "encrypted":"534f29f5390d300649b512cf728274e90c4845a8e636d535fc1d1243499e343f" + } + }, + { + "CBC":{ + "key":"f4750089765298d70a4ffe8a751e1c757f17b3df23c92a0d", + "iv":"7ea6c65e1e741b4898ba9a7747867048", + "plain":"98", + "encrypted":"8dde12d1dc8c346b8fc004adda5669cf" + } + }, + { + "CBC":{ + "key":"2bd42bd2e5d7e27eef098483b522c898ffb76c0e608138d2", + "iv":"7ca7e57ab91ffc132130d01f6c2e6edf", + "plain":"e653780b46349865dc873646d17c273167b205a1139559b9c241c557b1b4b844052237f35599dec64db47d5b2487c8d0408386e40a62c51146a1eb8e6c5b4157f567e1cc84dfaa9a07c4523be8ee46a7ab33cb2a214e6b08ba0b369c7b1b74cfcc4698dd914eba759186a04944cb6df36dad932b6036f5d1c6b82921be99c5152f02cd11ef00053ce873ca3d725bb3066b2cfe5eed02d0931fbe88745d24b310083d1f985e0137cf1f27b96dc4cfcb8a32bf1d9e1d4b9fb789dfaa16b4baf8198128ffb7be92b3df38e042474c514c54ed6fa2e382661641b61761b862b4e3906abc84cc109f3d19f17b62fc47694eded6c12a0675f464ed39c52a814dd1f613ff293274c169f254f318e2f926a30a463021fb9cf6e5ff3fb3b8f152381fa2939067508157f4ff94a5b65d3a8efae5a1f7e50a98e695bd2c8b545ffe017c495156b76c644289be83770db42fa73126c8bdec4cdced64defc1fdab53b3dd39e4a9a4bc219e50296468ee531dabc747dc2c4c8683d508d1cf224e82b181ac5d6a72a154df88086d801dbe53b2ce08fdb7ff80faebc003e4a12f8976b94fe9d345363f4faae7863e0af3b07f11c45262193cc55e25abfa41b799a61dc112eb98c06ec287216f361b79d2698ad70a69fa15cc253b41a109353012f5a47a057c8944f43062cc52970b3e19375d105594f4cc53daedc186862b9ce57a96b50db48f21c730f9890e5b88c58279b6db7ebef0c89c46d458cea610c4c9e0d65742f5e7d130b013fabab28aeb59e7ea7c69d983480736e247182030514bec472c88df39884682f90a861210f95ae2382545e0fcb10508ea3aa9f9e755ac3951909137bae6a0f9ec264f9e3a810f72243b0c23ba816658a73263c0c5f6be296f5b76d69a104f461b4c5af52eb0709b4b24f3437b42fbc2d540715c691a73c2814da2d2043efbd7abf6e3fdb535b41ef1cc9eefe96755667c41b745a75be8f7c1adaa81fa66c5fe15b7d1aac1064c1c277bbf74628436ca9250e36e6d35ea7ae2bf296c19b615a6ea7380df9b474cccdf61441429e54334bb507c7775bcd15a0c0c2b8853adc6a6370d9971d285e16f12a57304abdf4ca1e19a4f2bc25ce055a61dc9fb22ac4d71d1baeb1839f20023798871fc958dbdde9d875ef8a2df96720b66b64fbad30c11c505572c6157b82012fb48133314088903b1895474b502c77f3ec0df121e1a7f43233c5e2cf46584102559d5636a9419232c05fdb247c6113b09f3ea3075f8a97158cfd7a954ad03a62d7bc004c490563faa11c8e3db8118a7eae5e4e47ca69a404857a068bb9c49d18b490e5d61fdfb6c824e7eedcd3a2998402866596932b85700d9c682c4c38555f66552bd83f2ea4c5d09e73a6861820500eb2d72a313292cc215ccda8580db0b6267160a40a9a6b7227d97f70f4023e69fc6b7379bbbd4ccb7c6927d676dd43ff3392d47babb5885e364d418c2c1a793d56e2bca37b9c1ea8c629d4cbe20ec750fecb28bdbf3ba2a81e205cd8f8a89b42185b737d67215b048e90012b2f199b2c6270fcc3b1e55341f335e70273970c4418870613b374f6b81f4046dac5377507f0246811ff8d411deae6178ee4ea840ce1f6d33b14a10a6d4699b6c18a5c12fecc91037ff5f4b838ffc4d3976659514f2972b31015c3b74dee5f169153b9847d5db313ff076bb84cf1767952b1845d51fb2168ab2c7c6fc9854e8d8d13be8b0da3684dba7e4239", + "encrypted":"9ef18fa8aa376ffa7f1a4a992a7d2354d4fd41cc0a112a790850442bd3a03f31469ba4051f67f110609b14b2954031cd443e6a52581a8f19bcc3ef62c068866abf8c395f4b5846b8dd633fd44d1ca6572a5ec3f8ba34d5b64ac12ae519cd6a836930248b3ca8862d8d9979ad4e1ac100530f0d689aafa741be16712e4a0f1bf234d81014b0b7a3b948ac1d7428fa3fd7feb843417e1e258f73c579d7402e4077120dac398624a1909dbdeb18e55f0ac85258e9f0b10274c93ee30a5415c59b87627ee2dad4e020081ce5a1e069466fc2d38e9f89ff276602a24aff542ca6fd8b078fca8519578f4614da062a333306721a1e989955d7505142e10cf90b2d2c90d0a68b47d991439f5bb66feadfe8efbc54c84f7fb0d7f22deb1ba13c07a880245c5d9946f2fb9217c6f256103efe1188e5732b23b05a8943344bf8408994a243da6d3d17988af0d3a9537e01a81cced770c76c0c30bd9805d518805b768813619a53db42d00ae00ecdfdd3e2d2baabc01b6e8cfcb7c4dacf12d1582e774da15060f91e21698015e3e212c73c903e2680bb45c38df0e341a28a40f4e7f2c11013d0589c1551596bafe3eaff2828f8516e178a0c582e781ae115ab864f3ff5c18220c5e5ab0a90359201ac598283dccd26fd669a8ff9b4e896c25420f9f132102222352fe582a2ee7073de67eb19253b26e1aabe05fb4e027a52b31af7a6eb3591aec47fd8a78247800cf0437d78f1529134e39f23f921d358b46904510a314a755ef2fafaff64642339f7215bbf3021e3c0d99291addf59b67676398506490f8340d865fda5edba3a0bb40b2c5a39799e96f8a73854e0b1a30c11de1adf1ce7ad08b9a56abe34e3c8dd9a7d5c4d321393231a924c13b7296abd6860934a25faa23c8a43ff41b8e64b34d3f8ec28c2361af507b850e4e0274517fca4df04d355e53f93a4ba2813517eff544a2c2d1899b46a66b73ce9894b84415c5089100a153f2adb056433562f4c7ded00eef6b4ef4dea756d0493915596ca660dfa641428cd3f7298abec1eb5271f22dd62e1bc67e767d036e7085bef978a8563b7047e417e913576f7319af084197f3d0c6659def0fe4e732ac0e38a4a23787ece768fa999ccf2292ebe91d5c0d06caa7e791d891252f209d2806894744a729d091beb3bedb8c3d6bf6fad3d4770c3544627c78a29576a40ad0e7572dd5ac7cd656081eaaa04f7eb345c0a8da165a54e9bfee94eeb01abd5541f6a42936c82f590d0d05bd18035eacb0abcbf0de5635bfeb96be2a9df94a8cb2672d21ddc7ddf17618f285f900b4a8527b67895075bb7af2c7d31b1b56cd43232b0b5c0081b42f7fcc10c9c421d07a64ae6d4612adfecb7284fc68dadbe0f5933886a123f4b11e67b1df001fe821b6c137da18a9ad3313329780de1e03feb24ea7686d64578b4e1b43b79d721187ed929510aef8096df6d67fd9e0e8a4e75b01099808aa270a507100e725c7bf31de25ceee207a54ac033eafc48192ee765dca0a4f5e2f70b21e1427edb0d5c8a48bd9ad8f5c0f5f0b6a06c6761534b4dcf2fe02e92476aed7045d157e805673e6dac005f99ebc8ff346c39d973fc07a7aa670c3209525f2e1f46ab5d64a79a18b353a577edb1c076bf06ca72cfa342bc06ab3f2de5b740c511b6187c9c0b9415bde219753f9a115e47072c649e6900266e48f1162cddc6d67ca9d40f33d0ab79d05cf37d20f644031149c1f264201845ec47b914b632f13b1890e7645616" + } + }, + { + "CBC":{ + "key":"7fb087b617160cb4f4da08ca65cb800274cf202e21d1a72f", + "iv":"3ee840677c97adf2431094479fe3457d", + "plain":"440914e9dd8b960bf832103b816b413f62766db7349e1ac2163a75d73bd7367b5a9927c81f8f6199893d5ed33460f9ba1b9c8090b697", + "encrypted":"c0c8c34bac90f71b035b9fe9ec84a8e0d9b9a1eb76ce2831a3205b30603a92c0d96f7a465035bdccd06c74b505daeb89192dc1fb6f4a68569c3f89f76e244e1b" + } + }, + { + "CBC":{ + "key":"3a8b0bd15f2600666f888b535866198fb885034bffec2f07", + "iv":"41b91c07b3d0919839f2cb8c2f88b958", + "plain":"d259ecb1d5fe15b7e7ae1a88f89d6f1b", + "encrypted":"548ee5a1e3cace506610cf66571f59386f1551be99c7d095f1c944305f9f68a0" + } + }, + { + "CBC":{ + "key":"6a0af48a0bd95302fa791112b4bdb01ae15eded118488655", + "iv":"8db72048178c82e0514d44b9cc4a229f", + "plain":"cc6860ffcf3724cfdb3a037e82dfce986a0bc1016cb9a9c52af6409b9bf58991", + "encrypted":"a33b1e16fbb10f86a167c7ef60d112c9adc2eeb0aed21248a9b699a0dd429a58e73dc4dd55eb4d4e77fe3f081efb6a1a" + } + }, + { + "CBC":{ + "key":"735da2549dcb771ea616b8ea3ae2cdd857a9210356e93287", + "iv":"ec8441139e009602ca8f687a1f5419c8", + "plain":"c097a382e2522307f8a40edc173ffbae328bfd693f9e9eae54f23c74c49704984ea774d146a734c09212cb52e14b7e658c74dd024d020aa374e68450619daed35431b483b11cb82b2b17863a8aa329795f08b76338a20323e34e0ab3e06edbf895db2129169fe92b57c817dc2740b1e182425fe4d85585e2daa5da7a76d648132deaf698b470301252253fc64531dbcf8f8a7eef3a924d001faf6e7701fcd727e07a08b00ad98af1c15eb112f139ce7a9c8239c2e50ef04db88f4bdd78accedd7061d584eaf11b37ab76ad04a0ade03e1c9efb4752c24c631e92d9f6eac503c3e76cd79af1156ccef897261c2e6cf18555674912b9ad50cfac75327b836e670b", + "encrypted":"efc4b5275607025a114d1c05e9ceb773eaeae41350842ef92abd566aed3807db6ce0564dfda0e15cfe53c486f966f6223081668297c7a24cfe140fb740bfe25263cf59ded9e210bf4d707a1e7b4428f368079f41984eb09f6d2d8a1363b1523b9184ebad73acf727a21cbd5d676edf3c3dc7fc90f113172647bdc473967033ed775372849b73bdea517aeb5b6b0073e9ebbdd1b87a0499f1d8bfe093e13c9446e17e271f05adb7ba76836c53f38841a3aecb58e384f4f05f2f5ca01ca53b1d7441bc14cf6df73c44efa88fc4b9be93506702fa6ffe3bba9bef64958d691cbd2105541ced8d41c73c10e92622be75305385699940769a411296b68ef78218a895b5b4d01a16b9fc31cd665a2b8607f9da" + } + }, + { + "CBC":{ + "key":"c678ee24e8dade1706946f85eb53bfce74d0d8487283b684", + "iv":"fbd6710a3e6ff4698c13c7089e598b0c", + "plain":"d5bc91a2c6dfcf339b398a78207de3d0", + "encrypted":"95f206c2013623d20e19cbbd13da46fa3c91cc5ffd8ac0e28a6a767cf7ae7a9c" + } + }, + { + "CBC":{ + "key":"46f1837ec66141c1df5c07d807acc01380bf885c39a11e08", + "iv":"825fb3fbe378fde169a42906876b2565", + "plain":"01e172f912076deb3c98f5907b9b418c", + "encrypted":"b92e38dd33c4ed4f4aaedef36d33eb697d516fe5a31aecedd0f35e3f75bb632b" + } + }, + { + "CBC":{ + "key":"3d9ede61d81a002469bc9e8b19d39f316629e9359ac33282", + "iv":"5b88cabb0f9843d96f79a18a2ec49967", + "plain":"0aa471d0e1faf49bb0e013cd537fb1c3d1b216", + "encrypted":"9220403e02d6f7e899c03908970c4d6e939aa645c3e02375525f95b4d8b97fad" + } + }, + { + "CBC":{ + "key":"a1cbf27cef3381a29f28430b12c366194e69291fc51d48f9", + "iv":"26b7e486c8e49bfdff93e778534de000", + "plain":"f2", + "encrypted":"f0459cfcca2f205b52109a21d067a2d9" + } + }, + { + "CBC":{ + "key":"74763b19ec8d9435fe10fe4dd2c5dbc9735d01e171690fc4", + "iv":"9294b381003a2a9631919df6934be437", + "plain":"0a8ac79159c29c609f4fdd165a5077200cbc5ef25d8bf04edcae87e246c97580d1185f0e2827e26964dbad5a3b3299bf36f22a50b40493ba1ac83275db8b2c532131afe0509cde8c7c9498f3fddab2493eb8128f2a45beb552a7f1c6fbe069ca4f5703517d9712642198a27e16dd59a720ce4a8ea57ebd3f836b39c016a092e923e633e12e0f9ff26b69d82f4d9ff4c467605f2c658fa457f77cc1340e62033ac77b11c777b6b5ea76cdf328d4ba20c7ac1a66c5456e40d479a8950a2cb2cd409b7172c9248d1d2a4164f5b171e39af1fb2ba59ea1bf796abb38b0ae19def9b96b067e133b0839618fb60481a00cb7c17b98cc611198245d6419b36fc6427cf765db69cf333ed00b556ff9a3dd7377eb1df354ca56702cdb2c9591a6fa2f19fea1ca4c7d37487aa5bc4917c42ddb117106f81582acc3092b55dfb4081c3be392e105065931e4c01bb3cd90d6d302426cc3e6fc25af0e21777e95bce80e1f2964b398fb1b9504f8ff7359e7f671ba28431d6ea9e378e1645d1cbc156aa01005580509df730d0f17d98d16803b2ff8dbf3c5ee979d57ea4cf02c8ea3cda7fd53bb72bf1cb577072bf9711c6a93bd21053ec43084ee0b93ae933cc1501d65e804ce4ae7baee8eb4477d06cfc53e8086062ff2a0188d0df9083629172b37eca310d6282e3e910477028899ed12cc6808f9d9a3c2bdd84139ec4b094632f773cd0ad5281b0a921f17aa6019e5a2cf2d3f376532892cd678291e70d17e6f0aea7e351e37e57868f6a07971e64316bdf4a7d8b882a081fd59456411cfca2aa506f82be87e2ce75ee04eb5dc3951e1bde0947e12f342c2914489c3270bc1ecf20c0f647ef07328bb4f4a9a7cc0c9820f4f1bc98dbcd072b1d1f53f05eb5bdb91ba99cb600decb0035b272c1bb8436f25325b2c3e08bb6861092593c2a569df7e3299853619eef9ecf2a8b4a6ee34780433ca6ae5f49cb967c9bd4e87e977e1d3bae7af6f619590139eab4fec6ef2929d94fb421a285481318188643063fad2266616e5663bbc0c588fa9a14ddf21188f5b0701d7654545d560a7311a5065ceacfc3b3843a64c1f192f15e7c5008e138140e7f6ef0eafb907edbd7e223be4f8230b5d4472585860bcb006a363cab533e60166dbe461f753d9a8c3e323c663c2cdf253eb2b599c1c2f4f2094d7cbc86e069c7c6ae21ba337faefcdc26e5e6608a2050d839406bfab20b5bca4ce1e9e401f794af8f97f139d1367049a52fb9d09c5ed6e4ef4db36788843c162b7986fa1b50350405a8bf3202f6faa51e877fb2bdad75a472c6e8b9257cfaec7321cb06cf55ee1815e984a95e01eeb818adc96aae1411107c53dcb4bbadcca5e89db5da88e5506bbacd7c10eda4aa02c10cd93b33b98d89a138427bc269b7be8ad72950285ac44551243d63924e1c4a8619fc153d6c686f616aaad32f3f56582348068c7ff03376ce1a74e99ac964438248472a2e75f5af948a14fd522d78f900583024a25de7e43923e71daa4e6eb403254a508a1f32d0434a89fd44ad88b22273fcf2f28b38bc5416bfe1807e1d9646d141b5fde07cf7464804f6cc255ce7230762d87ea1d4c55c611be5cc86ce9844cc1009020630fdc11baf5d948a5b069b2ef20b5e62f322af8f2344841e3c2b2c4a66594332b95d2e823beb0736a1a96a818a4117db91381998acbcfa5addadac7d2af81f3b739d3b277684f3266bdbf2ce4afcfc9e5b05d862c05", + "encrypted":"37722d5840a5e288981691646d32e1effd613c70472ba04fb6e20dc637fa7764caf1fef1197c729c80777e84e2d587e5761b70baa704771eefadd1ddd8d5e81f5c48360adb99dd853b859946367d77bb9bae8f6ffbaa6eb562d9a7065e1d905c9b7861d97e56b4c0e300a0a6719ec2b1521e035d31db46b994b81c32f03f0d2da161de5e0013bff623e1ccc724495c0a2a89696b85ad872f788bcd0edc74e487de3aaaa1185cc1025a1259ab55c36e3965b90b534e19f79197f0ea8b3e7c4e3a6fc5db19b30fa2d25eb43ea5b02a0ad4bd6cd865b64f3e32d89c0f73cd89a6e5c17bab52af749a33468e8682d942150e5f585bddf3ad5ea4c0c3bcaa6a38043305f9d977431eb10af4fc11864ca5059dfe69e1f24d918e39c3fcb27d141c5be7b427bce7a1471acf85fddb30244189a7e66b9925df6489c0b0d83b0ef24fdb0ce4b29f89212dc238883e66dfde1a77c6e7e8b52c6d4ed0ab930e582b4647dd93499b002e05a53c52081d9feddb71dbc69acf0cd5e247a9a86cd37245977a0e4650218d801c4db6577ce75ff81e663351070cc24ec3db9204659c0217b01c995311d1d699b0157ab4e3150942692d474323d5b77f4a26b6afa97392ef57f20a817b6cae047e9dd9ab4830d643004ff710a0e8d1c932806513cedcc91345777300fdd2793d1c0edb2626c39e7f62f49167d1c8e3d5cc9cd1f27321b0712a9f037922fb8df6f698dd99bdf415655cb2484f84a29672fe4cb55c76cd45b0484e7d2a9460276b37e5539a9ac5afc62308f1cb2403a3d21b9d9dd50ac6dbd10656b7f5150aecb5b22df2714085ebce81e70b7fa97fbcf5ad1fd5124e52933e074d2e6d78a99dd112aa1c88bd12535bbad51c177316e673af9aa816dc87778665c6652105c7f77bd8b353a716204b830fa579cb1ff84868087188df99ffc5a4058e05f7c8ca8a244a951354ded1e323a75964cc60a0e1775f32c60e0544c7f0f7ade80bcd33747b10c665bd05c7c6a82634d95deef7eeb28aa2a2da13421c00e275e883640d0850646ad3a2ba8583186b27d2000bb93db6603a4afe97c67500a1e89100847d99503e08c817707da45ddcee0ae3028b051f96a5e3167359568883438f781d0f6f656f5b4648ac766f86ac085b1b054e22527569a94d65698983b7e3197251c7a774e0969bcc0bcfcefd66dec719e39b03f951ae99b0e6e113a04861a5133418d166f9cb0bd98b9161cf48fcb446e64a855ee25b62cc3f0dd991a533fe09b1cce4b951573c74299aeb0c3806bf16d065e796c662c77e540cb5846795b4294adba29c72c98da03802264932b1e990d30351f46fc05055e24fe84407a215fbad5c72f1692628d91cfe5caf3f89d00cdfd9de41f997333ad2ffde958c1e621dc96b4cd3605e5431694813d9a35529826e47e82ec4303999bf15a62944a00f8cc7f38f6dac27fcf35f920677861243b22b4e6882a62b88fa596edc3d2a8daf4c5623b72a0fc530f27de7a759c6a21fe4a81cda51bfb2a7081f9f8c4bb132cf7a6bcc388e0b04ed0f18f600998cb8af62533b63576862f0e403b03480344a4dacdd4237f61c66157645f3aa5e8369a76be4718bf8c08a4f35947ba613c35bed2687582dd0a8bb8e86792307705166439fe4bd2e4d42cd8d55fb3fe1c64d98f822caa16f7016c9586459195c1825b97bc18d5622b4538a6166ad78c08ba8580070e057f4f9e50626753b7529e7aea24166810cd5a4e730dcbf88e5c5f31f872f96" + } + }, + { + "CBC":{ + "key":"a553ee0988d4b7faaee1e3ec9e9d11517b864d38edcb5e31", + "iv":"f5d82ba420aa83659ccaf2f6d649921a", + "plain":"bb2e6756fa4b7cdcb8faa09cf61b38537ca40214e43cd19ea2662bcbf26701ca700af79f28ac6d52567a68409507317d793e2d8967e5", + "encrypted":"89349a18a24d4006b54a6e95268236a182596e7c7c93b60d41efc7667e46cd34004b487851289c9aee03159e7392c3da2d2f34238a3832bd4abbf925cbf441a5" + } + }, + { + "CBC":{ + "key":"cd2531dfa963e1f13c71873444be4bde10fce4d4aac6f2e8", + "iv":"ffcb39b6c6a4b5ec94f5735b628ee259", + "plain":"c8b3f712249068b39ed3f21732a5e432", + "encrypted":"286b566c5434d49a6e9c7efd3f1183e15a6cbcf5fdb1d724bdabc54d2b3eb4dc" + } + }, + { + "CBC":{ + "key":"313d424a96a91a4ce0ff71a375b9ffe25c5f928363e24a62", + "iv":"bfd6b9a54a5989922343b3a573f47cc0", + "plain":"fb7dd331d44d24328462839bbe770330736569f84afdcc9da43593f659e9caab", + "encrypted":"603a0722b045c6c22a857ab3e39f6412bf7b5b0f540c687a3b92f34c448ae78e98498c6defb5248c3698ad39e893596c" + } + }, + { + "CBC":{ + "key":"a1b7acf43da7c4d82726215786f47b665282ba38c05715d1", + "iv":"1379d49be4a2bd7306450c1b09093965", + "plain":"234da1ef9d13d601cd530123fe6bb426927a33a190c62cb0b309f0bfbcba7a89a0369b7a395ef82a023511dbbc467a3e936d62ad199a8164130d18da5c66298450e5407741b4c70220d54aa97d3c98eb85af3d42906e5c0339a52b5308d0b359da89aa4489a3bd99651eb5dbc8c440b2a39b1199ddbc4baddfbdb5e3413874e215ead5781e94ba5af49ffb700f716421f2d925ae307cf813985c33ffd979f7de097dc3d43bd4454241717a899d78a38f1c808fb18d48801399b056bd010f956033da3d2df1dc519f75364a31eca1d1c7f1b3eb107ebeee3ae16cdad31672d1138062001286ecde40523720e8ee63816abd82f3a92a8634a26f2ebaaaff8d48c5", + "encrypted":"619b7f58bd9337bae206d47df9ef81f256a7f9e84e847d1dc100cdbc0f70b057d3dfa4d4ca71575e7b72c851426f720fd94f08c2dfe8f47e6c1d577d03158bc46622d5cf739e8a4c5ec0a95aa0dd6f3a4f6eac866d31af0adb6c3c3820e10e389cacb83221beba315231d0bb0845f918eb80f4fba9f44ed1a6bbbc9371cc7c42c371455f4a3239167d90964745993b3d7460e61f330f3aa46e133e3b9d4e66820944aa574796c58cf2bbc57975a6a3143e7d59aeb870a7a77ffab80ad189588964763be6136bb5f0fdf490b319e4fc2a66a9711a4609ded8d56d4dc40de97a1f6c11068ececc5e9fa2b3c52f23648f61e44f570584985f8d4a3db72497947b8ea15dd68e61f700836572eb5a8ed76511" + } + }, + { + "CBC":{ + "key":"2ca7771f57434bd42e04dda636a4ca0a6a77c02fcf6bf23c", + "iv":"663fb6ed3b523c65f4f67879ced0c94e", + "plain":"6becfb8f056b67a8c4d0c6faeafdf7f4", + "encrypted":"209f4466b196c35cc058a7a9daa85395f96802e0459379334cca5fdd3c9613ef" + } + }, + { + "CBC":{ + "key":"b1f8d1e7225bfcba655665d8bd8c6fc7d1a3e553afd41194", + "iv":"230be734b98788a27d088b3f64cdbbbd", + "plain":"6508b75532d7d1230dbc55ba7d45f916", + "encrypted":"44289956568015624a3380f30915bc04525fcaa48288df43ca1ff30a3be9e2c4" + } + }, + { + "CBC":{ + "key":"438d238b418041492f68e5292f74894c3c1720d0f3bca30c", + "iv":"f0361ae46fcc0440fdcb22413e98693e", + "plain":"18b8bcfbbc3510129afd76f7dd47f11ea3fb4e", + "encrypted":"89860d631b43740fdeae305f794352611e4916748da83edb88a0e484b9c16e1a" + } + }, + { + "CBC":{ + "key":"37e74b916ec7c1c4e6e8b7dcf0a9c762ded02848aa6aa52e", + "iv":"b88586a604bb0174a758f02a51b665ae", + "plain":"e1", + "encrypted":"7ca4ebbe554e968d9aff79809f0ac765" + } + }, + { + "CBC":{ + "key":"be388d0baf1b15049de7a8ed7240ee5e25b49cd1e91cacab", + "iv":"cf47a2ab07d7882c482ccfce7fb10b60", + "plain":"1a429f01b895b18df1eee8eeb199f7bd8877bb5d86f58e19d06a6bc1c220bcfe9b59c25a928706726019a0467ae93de2b294e110986494d207021e3c36bb047d6101fcbcb55cb33379229b932866eb41ed2b91ce6949da27432f5703e0c393655e3538e304119923632801d530169d080b5e49c7417e215e5107bff4cb6625250d4c4183f0bd1daf49d1168d62778368efc2f44ac9816c360922dde431454fb057853d0e4b4c88d18eb3aedf6ff1196e87a67f5722412a0890fe8fa5504dc0cc8c48f1d02d0e5cd718946222c0ad521c89203d064279aeaae4db227c3aba28cee8767672d7647595b5cce2df33b4780cf4eacfa8119f7fe1b79298c20e861e8d8cc2905c0880c59474ec96954d1099d0a0a3ac0edd69acdbbc94ec2e74d2b0d6cfdce5db5a32267b67e0d68fd49f5d8f9aca5698931b6f84dc5d866cc590303a4d759eae3eac79f5ff8dc965723d2a4281aa026d11020ca953b28e13433ec672603f7ce29fdb90fe8f7efac1aaf0eb72537ec3eff088da7669b18c5274b0d15b072dc94d13f435997ef5fe5322a03bd9ac7f10a973b9e255fb7ee2e09d72c2404aa609437049780524902db91cab2d060bbef1b06971b5163fe70362ec342da8e6490f56aa76155280d4ca332cf6c848284eb3197a14916d15561b2c790e50743eedd72d1e270fb9277922662bf729fab0e93733c805d7be9c0e540b56b9f56a66b71b61910707433043f1429070fb84955464c349b630106c7ed0ab47722a1a95fb81fb21cd4e5081972fdfdff22b4e6aa3bad9f9252d70ad2f711e81607b6ae2f2c6e1a09db24e00a54e8ce413ada6a7f8005857e78c47c0098d6984d8f619f1cc2c9aeba97594416e05e5923b1fd11e21f9f1256613dd3dfd38158cd2ff117d9bc977e2e86e31a291c6921443db2328282b4423830397feff4701e8442f2918da77c90176ee617258b970f7f672854ab78c75cb03186aea370d24ec29ffe7155f818778be374d467be4b0adfeb99adc251527de5dbdaf615373f9093554e95cb81a3813dcbd25c1bbd67107ee14c35faaae18ee8d16d5156b3fe17852389f0febfe8402bdd6ec3c3c1819ea493b33615d129db6cfa639bf631a005e285b533ebc3fe54396676529dc100bba7af8541677ca747c4694b4299fca626c5106b8c69727828826fdb662e0a68b8a163c2013c2f5255cf829991d4f3547627d685246171e2071355812968877ceb2d7d0076fa7421ebed28838c6648e4ed314e8771f8b520d7f456fed3c54fbbfe0b5dd1199ef418b834601e901d662c71be4cf24469198413713a08e8a6487e8e103ca79120a30b92b1cf5b50df4da79b2ea260d442e58e150da16d1ffc117a0f31f6d9a02fa91f0e190dc44ae41bbf405af3526b6b50f61d099eebfed1eea23dd01a953154c7e61e0211866e8814d916fa9ffcf51377244557e7565fcfc1c2ff3c0d8957358f66c3b9c2137394792e31b9836715d7494eee385db0275236691125e2636b4aa216f0db8f32dcb9bb2472c27ad9294b544d0efc6c881152d2535a2443e4c34a8d9308509fc68f8a0921ee30f2907689ab325bf53fae344c4efdd8b3c4a95e77220712e3bd2aa010a97e4fc725c0c97e92e10e2123086a8c2fa9124129cd6c026ea55f04e4c25fa1e049b2ea14983e0c9cbcc9944d302653aa6645ddf5778685372f93d5f7fd42683efcfb0dfddec61d0b875c73e1432e2451146c48f262e214b", + "encrypted":"fbd49c49c0bc0e2e6f5e64d70eed8d8da90fc2990cf1985c6036d4327cb2a85ef31026471dcad2f6a44117441bc8982d5f6feee707b1c6d96a5ef92a936f67702c2d5747feb0ca0fb744091326050f0d1968d1a133b1653c156b9b4fcf40292ac3b6f11951eabb59c35c4b192d113f36315eabe14d453bc0cb546023f9f0d272664de0491e146e0e327ef9017023d04e569996743db1bc83d3d66f0f0b5e0d4e4031c3fa79ade91cf9a82a9df3bc8394f32bcacb114d129017ae61befc51e18352ed153f28fd1956133ed7686aca9310fafc5ddaabb8cc0e58f66e54a53a793951fdc169da9a562fd83f57649d5c54f766bdd839574ee43b597c4808945be6ec7e6eb72276b3fbae5b0a293270c55b532b44799c08e10ffb2412325658438fbe1bfeb1b262317a74ba46db267e83152118397361cc3d7fedcc3363eec4857b020b61e4a05b8896da1c8e6b892e1421ff5f6ab645de694a8d9cc240ff1d4d78765dace0aa28cd01ff56f42546199f4dd7f60167143d70e22738ec42c36eb0e4c7e79d28a43041608f504b67dc77b83235beda03d265fd17610d71e91e9d01443ff3a75431fd020ffcc7c76b0f545340f8a4889f4127ef3f738b7e011b7cf39e3004cc6e447ef5fdcd29a9a8768464ebdac52e5d4c7bf2cf51b052aa53f05f37e2cc1c372bb076e593f5409638f756b05fbcfe80594621c79e1c32682db632298262baf854ecd5182764d421f23b4ae9243a3815b47883e277bce4646a0cbac6d5a0b5ada587effe78cf0e19038e7166484d7cfc8341cc13b5a04ba46d961986565f487fda008b8ac969bfc8ad358880f676ae70a509f39a9afc092235939438d887eeacceefb7b820efdec6cb12c43453c41e29409e7edfd1ebbed237fc24889481fd606b7c74c4ed932e71a2d2bfdb4fb6ed1d219ee6583457733d7bfbb7355e49f9cf8c8756ff78a81e35d37ae636e4dfaf9b8e3f93f908074536ab0738b6ddaba9a885b1253866f86dea0759cc9e04661049e7a42f4136b0b79e9c58d00d4714b76a9c41912ef9246ec17ec242a6ec0c343e6fa7f0e23a6467c54272533c734b8156a3faf5317ff9df693725585f0c8a076593103cf171ec3563372096b55241d00a6a36448a1b9cca76ea899b83606547175bbe9022f139b86118c9d2c00d5322a205a05377cb2261513b555da743d954d5140ba5adc0532f521d3233c3853ae5ff566405396ce6066024be43dbb5fe7e802cb3edfb93d79ea9d1d6d1c39b730e9ea7f00b0595e1752635dfb5a2fc7a318e365b71ab1debb6ed85b84a4be57cde1dabdf0634f76063104b3a575bbcdd6c17359a6ffdcd0a894af2cc0b1d08db03acbfac4ef513d7368db6378251dc9bb59fa9b403da67b4a126d914c87230e1769fbf8edd8aca71355eae0022fd0b5e82e6ab3c8a061d02ba8dd26cfd318997b097b223f6a70a9d98fbb9de4953fa7e0a0bdd18e514af6b9492d62054426cf5ca5a2e1fa8570737dc69849b77df8161f6d87a81a1c82a292a4d14cc75c174075b3f9277d5a1cd315fc2b150e4b1b3433462e579a1772998d77b3a1c8a28244b6e5c29921a7096acd856cff0a13d1bad78ab8e2d09bdff129693c445f296623e5dd2e26c54d2bc9783e699af1a14073384079670a4791abcfddfe63e32d7d3c9815891c885654464be06cc1e58622bf4bab364752f8dc0515c38d10d3b2861f6b157a4f261fc50046578b57561694096dba0708a2260917ffba37e84715895" + } + }, + { + "CBC":{ + "key":"f8e0425d00792c25496e7eb28db9be4a9261a8dc947bca1f", + "iv":"e8c28725c7bdec5b740e9fdf7d02b086", + "plain":"512bd373ffe01f5e3f37d2f1336ea88d257c016aeab21f344f6cc30fbd0b9011f5d31629ef1c6e5520ce8cd94dbadd0cf65c01f5e560", + "encrypted":"804186993f664482db23796775b85623d802c9660b8fd1f5f0efb20ddc742a307cb4d831d97ff2ed689722aeea6d878d7ec665875c715a4cd3f7a7a12b92f2e3" + } + }, + { + "CBC":{ + "key":"3abfbf902e988b7688f11d4f9bedeff41ca1908007ed8c61", + "iv":"faf406a42ba439bacaacf03c93080656", + "plain":"66106992829271d3574dfefa3154af12", + "encrypted":"0a69090ccb582a586a1bb4edaafd1ca233c0442118a987090030aacf7e2fa280" + } + }, + { + "CBC":{ + "key":"42c1dc33898a79ed642cdade18d018616e2a2fee06feb541", + "iv":"b18f81055fc5608efb01b2ac815fb3cb", + "plain":"5af21541e46b8615d729f6a1817cca3c6b3c071432eeb5e3e0f1930f9498a07f", + "encrypted":"3c8678cf535bf7ab1a335886a4505309296461b5ef06281b99beac70550588a5837a744de28ab9a28eb333fc7b7b5c80" + } + }, + { + "CBC":{ + "key":"a5c74fae255da1f968ee41897346da59b9263f2dc7740fc9", + "iv":"90e625947990903b8370e2057e355e3a", + "plain":"4b35c0dfb42edf1140de5b035d733f731b4eeda6e61c6bff54f073d0c7ea75e6a3471232cd3eb752477502861503660e5df4827d231be1709989b8303aab2c649561f63389bd62ad2a925f0a2868e61ac8e8d9c32d8fcf366ff980a2270ca27d25543d50ab4638491e2983802ff6755da2734f5588ae3cef0881d6e9371d7fd572b982ed2953f3b480cc1971bd12996b229fd39bdad8ecbcaeeb3539cd2a30154757b3df0e14c58da71760bc7f373a69b6477955aa6d9707afcd28e1b80a7206b34ca7bf90333f3e88c6e93ba3eaaafefff26583a376e0c93665fbab995bb420ec84f44d3d58984ab7df6da3a012b57012cef36ea8fbf0efe1c97c5a54c99911", + "encrypted":"460344f1643a50a725ebdb81c663c69ac4e8ac4d04f8a2509239667a421ced1b2aa25572f17b3102817b26e93949cbb2973742f9b763158308234077dbc8ed27f8eeb26a4bd69f6464d93d17410d3e06c1b8b01ef5a0aebd50fabed38452a3901c4e54837eeecaf40eac9d03f26911080c74d576960804b5f1f580ae5cee0a653b57e5a5fbd61b05b5d52f48cb61f3330fae6db903a9e1394ba407d31b3b61e56e39d1e5cbb5d41b421883daabb2fb251f4030f42c6d63dff14693d16170c87f22a36c05fd5d484992b83e1825b798fe8def98aa954eebcdf9e696b358298f5f53ec519656ee6ab1864231d59c59b2270515aeb40aaa95bcb745d252897f920d083966849af727e2283bba34905ad5d4" + } + }, + { + "CBC":{ + "key":"2052b5807d2a74ee2f782091c3f8f2f5fa6f937bc5691685", + "iv":"db321cc78d0dac886615ec61b5bf1410", + "plain":"507e0c8cfa9bfda8189c61e135fc65a0", + "encrypted":"8dc6d0824e4bd395a68feecabd6b688fde7cf20e5b3675bea6805946c14af465" + } + }, + { + "CBC":{ + "key":"7f8bc3c000f46fea1d95fd017bcfe8a6b1832417f28ceebb", + "iv":"e81899d41fb1693091d94f6b05a11e3c", + "plain":"489e326c5d3862035af1766e34bfd98c", + "encrypted":"59589840c7834a4213465d66f118d330a4a44d2d693f9b60c9855b1ec337c7e6" + } + }, + { + "CBC":{ + "key":"c0e87167bfbf21aa90219b5e0a916c71f3e98081a9fe929d", + "iv":"44a90e47e93b4e52aaae593c1ee62caf", + "plain":"eced6b55dc6dadf7e6962eeac2ba8338dd7eb8", + "encrypted":"b70460881d3c4d428691152936426ca8ccf35f507e8b39bcdd527f006c740862" + } + }, + { + "CBC":{ + "key":"4cd14e3a038fcb42cd2836dc06daa13f453a2ca1826ed22e", + "iv":"7119aa62a1945982a531f80d00f4f389", + "plain":"98", + "encrypted":"fd818144f9f0dbef814b97fc37565c76" + } + }, + { + "CBC":{ + "key":"4ec4a9f95b152ebc6a8123d760f5fce1d607b6158f318689", + "iv":"c73bcc2f1c5a2d0bbbe1e8036db8e754", + "plain":"735928c100082e785709acda3f5e3994fa23b0873048a689456aca22cc514d4eb890f13b7cad5f131d7318485ae5cf09725d6715563bce4f751d9b0acdbac6aa078981ff39371e26b93885016f2a3b489bad172e7a9fc074b831c396c4dbdbbb650ba43a433fc947a8ea026ddddb1aa265652805f4ffbdfc68a3bfa17d25572768dccc1cfadf33eff70007052c224736bedc7987ea26bd3012298b6ee2c83a1de55323c410290bbc061c5b531ffaace6ccf6040bbe02be206b9b4ea57aa4bfd07071980691b8069ebcc339e26d40e6ff380f25c620da316e92b232027fab2b82c0d9a771ec69f340235e91c8ebc710ec2485e13ae685cb349234f3b938d3ac5fb6f4ec30acbed07de42129935ef302270a8f53baa5a06a43781aaa51022749d1b286111f0b71aad4f9ccab799217a05db834a6b700e3d724c8999388f8b369c09a35976c715711f2a1c3d527fd66f57f4a07e1bff084b42e9c46045358bb255460d85f0201993bcdf092e56dc23f90ced94ad62ada2033d70cc04d1157b9a997f06754ae3fa71f818294fa878c6e8c022a425ae8d56ea74c325cb98fbf96876356431cc94bffd6033177b0c15d3e2d07b58c8443cc126610496814f4b4ffa99533671acfeadabb634c893209c1fdf940e7ae70d99d8a409dc3387b481ea3aa1ab7909f41cfe616c832df043ca5cf203dc77145a9468d26d69236be773380f0f74cb56cf5859fe15b568b8d84b35b9cfbc4b61f93d481c892604c1eb790d06ab7f76f7c4321d8e19e529046b7866b910c0e60b81ef05ef069e864493de774c1d6432a88b336fe784c2cdaf8ad917ee8f4dd8f34c35b9a128af440d156e644e374afe2ce609cd9bb6cedb48cfd64d6e680b5aa0b4fd0449c6aa78537ed5df103c5a9d72e28fccceb5961fd12f18e1ea5b7c753129538a3bbf28863f10bcaed37782c1b7a00191195fcd1db47b3b77c9305da4b5f6ac182348fed55a74a856fa842f2ff3cc04dd7be222fe0c975f72d66e39dbf9d69e19e3d931b9717421ca20fe672c7e3648ca65432b988ecdc8383234726a4132496de645b1b80f7837320dbe52036bc7d3aa58143981f597c497f05e4b4257a1f2aebc214f2f2ccc6b7a6fa94f9fecb968da5e2b724049902455140d4bfbe978ae03d9c7617ba52e9e31d4972e0c363c4ad535331ac6e3dd6a16d6f08330856262d211f7c7c226c29332cc475de827e1d4562d13b258ff35901ba8091eaf03d23ecff667edcf3609e9e164bb6ac9647519d6ab3dc512960faa81f3aa09d35f5a935d630fd425d9266146a07016fcd394c81114350d51ad1706eec068501e3396cf028593e6b0f6f90fbe33ae13f1122a87198fc6a0923a351bfc5b32d7af263350f036a7f80a00c75b434dafd1e1457d8cc5b3b79d30e69c7bfafa063b10cc940654288226a5dc629f36229f12ae895b2b07a2628062b70007ee82e3d184551ade10564d9326be23e84ba1f47070388eb4a24273123dfb2fb8f1218eb150e9a91ec78075948acdf9b0dbcf9e746980bb5af7780a332fc61e5734be9df400571abf525b9a7a37e2f8a344aa70766e94b02932e32eb027e2ebdf62c14e5d5123f60db012d4109f929b3772e6603d771a028d62ea95380eddf7101704154847654ea4dc6895f3ce5c80032b80f61c59f77477ffa9e3982d9b4cc0b4729f42555640505d6108fabbad17683c43d7b1bf54fb464e2be65f87fb57969af617d0224", + "encrypted":"6f368d2c359ad647f7f09e0ab6eb5ea318bc07315f4b4bda9c9da02e79d84278d4a7ce7c6e8e6e5d1facb08ac16d620720a832405b3daa0a0b7f51a700b71d2e91b60aa92248bb73e5a0bd603dd8600e770f72d2c829c36d2c1fee764997709808bd89ec7feea75d09f1bf375863fae102eb5a842cd1ad38d58dca4e1d0d59c926b6acdde0420d7ce088b6f25492e2500c0d474697df030c0b959d8acc7dc846c052da1c9794b3dcfb6ed2638073b474ff15a737c770b8037d3a54f57d8c40a7515735a33f26c3c2b0f10dba6f26c4f6465a1ec18c328ecc8b11c42f7b241f34c420a36842a0ad7ac0f271deb768f2bd8d1552fd9d1c87facb05c8bb74af98688539b4940b513e260a65d7f90bd1accb9a30e702686f0ed2419e70a4b475c77894158ee3a3ac1cefd9bdb01ea6947efb24512a7a7cb872a37b4f1ee2acb6d85736b88de3625609a6ad716b8c058d3cb478b7a878dcf60c58cfc57276d5e6f1febf27b564ee1da87580160b8248c62bb96bae3123b178bcdb47898a192b6219a4fdf67f314611f6d40af1cee63a05b7756b25f3ac2593fc466a98f1bf14d1a7d606ffee7b59ecf12d3535e409688eec1ff02e2cba9bd2a88d88a7a19510525613bb1bc2179cae76b601a0b36955465a8c76fb89abc636baceaab582ac5dc36a9219cc14517fa4e066d98ffd32446cf56f9dea586519ee01e4e86145ef523744f62119d6e22e04481a1aaa87eda91a62e5d77b9559674c5153c80685920fdd788cc9f4643dd8362d55ac0a3c9fd041661b832742ab68aea8ff789d905f24be7f548ff0f9f142d9984c19a98faac0e754ca05fa8dbd7ac8befd363074741004d4b650db08d4ba60c04688d986fe7aee223ca33376f85921b9f39e95ee7beaab4ed6f313b112adf48b2998a7d340273dea35d6091b5e5ecfaa23c36bcb71cd9bc13d30710ccc01b2664e62a4719c42b6906f4818ab2ba252e6c6a029f2ab080bd4ec8deab359489ba5f9948eaeb7e344a39ab955e3f9a72146e89f9db8b4f3b6fee0b38b9ae85f8a08eef77a51ab26e008842f598f2d08ab0bd098d53c8cebfbb59d52a3bcd74898bc20266c48b524561251df658b743b99ed3f2622bd5a46c8bef347124e2b6d22e9325d7c115866f2da0bf1588281eb977c895404bfe8f66e6aa2e4281b1459177ac08e5dccbb549006b89ea23530fd8310cc1488b9f36d639e202e35af6587ec2541bec357efc7cd0256f974b4ce2a26ec0533b5eaa33fbce6255556d83ba96c6b2f1d99359ebe6658227bfde4617a8072a6c8168355a75912e6cd13b65e29adcb40f71e3203d00a5aa7edeb12c1d0b02798188f8baad45a1a650e85e359103f382dccfe835d28ae96b9041fc93f33e3c4283d1af8ebf0c095727f0b991f47cd7009491fb7fefa83346e8b3a9440bc7ba630785c63ebb02611f5c01c97eff972e6d77e509a655a2aff2f996c6896ef67eed55741c29e73dc582775d2605edef34b802c81f526816677a429cbce0b58a67f34dedcf7be7d727959e8f9ea3f6da6a8f6685cc46a6b243b6b7af556fb6f2b3c248e487bfc792c6223dd9af61ec247be00023f4896085f1e2abb34b74a591885fad2921152129eabe02207d8c850628a11df7f32bf93f919152b70e0c31a05ec1420c365be43d321fe6d2c9aa3b32aa7a16f9c5d1e240f05c338205937022a61bc89011006fe77ff1867f0f985b65548a4fd4f88d1724dd65d6d9817f81c80aa79096d81d6bc865265" + } + }, + { + "CBC":{ + "key":"04d8827770ad49cd23ffcda5884bc4dde702571ac65ea841", + "iv":"9e863baa9b71823a5ecfb4135694e42c", + "plain":"7c9df950c11f27dbba2049ea5334dd4405085936d0e2ae88f081ec93352d044c8b9a9153e1515a6ae246229e8becff7c18ea41259d64", + "encrypted":"9f80344a11666079b883a7cf776c92334c91da81ec197aaad5692f4f9ff1a70718ea3d887fc554bcea9cb35032f653718316ae09d2cfbfd13be4e1a366c69f53" + } + }, + { + "CBC":{ + "key":"21c27c54d92b42ddf92e1721122dd57a7444e07fbd794f7b", + "iv":"a34bd7424ec4412180985fdeb4059729", + "plain":"9b4994ffe6b726b7655e5de064659f49", + "encrypted":"e753bd47b9f744b6b8f95359ef9ddcfb326a46a37cc3689ce1c63a47d876efbd" + } + }, + { + "CBC":{ + "key":"92464e9f9d743e1419a7336359ef85fc98b025fb9b3d3204", + "iv":"305cea706294b4b5ea8ff059fd47495d", + "plain":"59e54ae553ffbd891fb1cd9ed5722f66e011e4f4a5a284ed0b2c7f1b23f5ef0d", + "encrypted":"03f167defbaa1a9aba935c50ce241d06cff9be949422eb29f3e79b6060cbd76efcb097e797ef8c2d220ce3c68fbcd40d" + } + }, + { + "CBC":{ + "key":"1afadd6334427ec6c204c328d759c337e2cf9fcf5852e5aa", + "iv":"ac2816523e6e7a1bb73cdf299897e9be", + "plain":"8da2703e20c65f581aaa1e0eef13082bb64dcafd1a00d59f4f4387940adab545dea81e6c19abb4d80584fc801320577eaf44655954d6cdb1bc0f6c92d60465fceff90e6f0605154aa77cb4d3543e15bf8dca9e1a85b42ac6d7dd61871f1d563178b9b0c02983abfb85e82c33acc3fc2675263b0e9437aedafb4397f914c68a77bfe1e73b75fa4447fc26bc9300ec5bbf96cc78ce1de9d483aeb0ac705eac059258b61071733e1a31ccdfb2e1716f2f84b2de68289aa1ce42ca427850dbe96f8b42c7c963325a5645ad76f3a388624abb70048e4c488f657c6323b69bba8b159fbc219d1a6e390b3a2df67679e6a8ccdd8f50534b957fca6d1be18e85885e5803", + "encrypted":"170947e80b07c955995604c47f9d3bb8dbac2aaf840c6a603f8a76b402ae3942adc88b089982aff7a370fa16299f199ef5c67564a7f3b5055a63daf31f1bed2de35e0d7d4602bb243853178ed54630e59ad92b08b6aad25068a25276924c0439cdb11ba62543c69be5f6890576eba67a6071ff2e033fa8873fc7c0e349fde01ddbee55545a06754469cb82a4c341417d079266f66e6b3929862b2d278593c4656893f6a8cb831bac8fa2e6334c8add9308c00fc51b9ece3116776ebeba5ffa708725c3621bdb5cfdd7bf1ef0fd45167b061bf5eb97cadd4ca7b7f8aff47caea669644bb363a6e3d9b89847f23ed6e5100189634eff86d1d7754192e5e2c2b1642e6412272a108747a0421b7d8c7a84ee" + } + }, + { + "CBC":{ + "key":"5b6f3a802907e0219c7feb04670ac730e9ba268fefb87e93", + "iv":"9167d5093776c1ec31ed874ff5a8a3e2", + "plain":"54d0cba56410ded6c43807b4930859a1", + "encrypted":"7e206a73d5cfdb22e6506ab95589bd3b31f2c99befd55e20546fc38551af57e4" + } + }, + { + "CBC":{ + "key":"8c298c518d0d0b5a5f4ac732e8aa58c55427148e52a653bc", + "iv":"a9c2ba6d4dd8e1549dd2cdbb45b25510", + "plain":"dfee600097967adfef5102ca26f082b2", + "encrypted":"39939ce7f592a9e5539b450119f3c9d54d00b82672deab1c2d208050e7a0c17c" + } + }, + { + "CBC":{ + "key":"6055f79321672ffa7e13f1307d80d1cef3f6dd7aa8986506", + "iv":"714324d5e31f01376b5325f56f1174c1", + "plain":"ac66f807b24fb0134a05087ca5a68b031de225", + "encrypted":"742aa7b63d15b111813f2c0b677145f7c54f01fb50fa91d9b50c55cb8e4ce00a" + } + }, + { + "CBC":{ + "key":"40676a18dd5314913f0912a10e723446c59c850ddedb28b2", + "iv":"83fcbf0e6bfaef5ba100cb16c2f6d619", + "plain":"a8", + "encrypted":"7a2543e0fc5a1dace7a7bf624bf57792" + } + }, + { + "CBC":{ + "key":"c5c95bea362ba902b16975627c8fe22bfac1fabe1c476beb", + "iv":"d59d9f7e9db845ee8bd01676711b77d5", + "plain":"f953e11083f071d58c2e59cf14facb1b82ea29ea8cf849a5edd2da211e9af3e3db2a1c31e2dc52f007bff71842c9a62784faf6bb957c1c01daf1a9e10bbadc5fcda3e2117c62f743bc394144e21d12c373b113a65e5bb6a290bb27b8f2f4ab7d472e44c676e56d840ca3f189f5cad1b81e0d298c836b809a8d73e156594535077f5eb71fd71b6f7b996ae1a1bcf4a59e3d3523d52d8822d69601de21307480272e9d1c3d921150742b66c1ad860a9b521b7b2cb6e4b02a3849c873dc8f78f2adac590448b2e2acefe6eeb16451ff88986328d56884e46216607be15486106b873ecf1d759f484e56ee7191a22690acab0a5c8b6c13364c76ba0954a29405d81aff756137047b98d293873146681eb13d9de3eff607ffa0451e2d9c464e28dc6294e7315e13dcad2a8158fea7067f88a6b3188eeed8e478f2060fd85fcf3eb599030b8df4877f4f5d912c09ab7b43240c3cb279ddec6f02ffc9301ae2eea26fa530184d318bda116a7be2b28ae4ce656251ecefcaa348e393c843e33ba52fa9ea539669820013ae7ff3b034d5d0aa23a75ca4f88dafe2d62382f61aac08931dea3cceba2eea1db65b258c1c2ee553c7af0fd07fdaff3a7e83901f36c3ac75fafd2dfaad76609373a4fe74f4c4a9c9c1a66682b8609f40efb94620e4111d3ba86390f93d1f81530c2040ee8d6766ea5216437626ecd4eb184c2077b471e88b782eec3eb83726eb96371b89e37c2e45f1f8974b4f798458551747f8a9318a82a6a93d7f37ad2bb74ccf767c4a87a1dca7510dbbd44fbab1dd90d628456946d707ab424dc1a9a182cdbffbf23d32ddd43a83b8bac4b3208fa852858c55bd4f06b1387a78d06e199f3d3dd6b20a30df5161b857f964f22050a1729bb84b60efbca7866d47a230f9f184bfa88f3379872a387508a049a596f360c8e6c7c8b0cf9d4750c18104548ed85edc94e6b521cdd68a3a020b9e69b9de0eaa81e8dc1804dfb00d5db76b6ccb04a6fa056fb1f4a8cf6b7aef6c70931af7b859e6ba1f24ed7c1f81fb6f525ad2a7123ff7581986974da3b85fb18cc92e1a99b2f66ef9464cc040a6e3649dd12712e91756bbdf2bdb1c23b1858944c22fc7fcc164e5d377383607c64afce9ace3751aeab7d24c0307172f98d5d6e8ac72c0bd517028611b8189673972e1b063cfc890aef11b281506d963105de3c9d2846e2f3011af6a4965a806766126ff36b6fbe41c6a684e1e693955cfdce004d8d93f685b163e63625db9a304a41512edc23718a30dc208c6e96c699052e2095be925d67ad87997ac6ab68c64affb32ea24b35ba1f529163027091f484bc8e86bce59938c4cc42fef81b5c6a150afa5cf9492d233eae70f1e6ca36a574bfd341126c11520d42d4e379685133ee1298dbafb3db2f826091d6ca85dac13deb26c6429286b2b1e7df0febc8b238637b328cb15532a69a6effc8ce853ed6ec55ea582caf091dcf080acec7043ae10d95f835138bc8ee88ec83be75c0eed8e06971f3d99cf6dcc0903bb76389e03bac5ef47fa62200d97f414aabbf167029aceb0d01349459c4b99524b89ff6aca79b9073026fcb26eb2375f1f6267628dffa2ee57c2afa0cd28db352651a3c66873714f1169a5bff2fc93da3cec37cff88d0b64834f8ec905ad422669550956498561ff718bead1ce205d6ed2c7bd4768de82675f070f239322b217d75e46d6674fd77478ff5eacfb288a74d715681e50292772", + "encrypted":"c8142815d84d221ef73d644468c1f3323364fe5fe23b28dfd357728749a24b7f8b423b50eb86cfbe81929fd55dafc7ad515e4be4c781b49c3feaa1611156d00eda4d8e2f7b07617913e95b89cef4f6abec64e09b5f0815a7fde102dbbe29917dc7094db2d8b8a9eb83edf0ab301b9864eb5a8e88a580f92f221dc11b870a927b635217164facb11807beca9294936ad50aac49b05ff320627faa69b32397fb3b96569aa8aec34e0aa63a4e1b550768dde4c70ca46ff32bf4e7811f0e54aac687706ef27341dc95b64706d0bed2701c2858c58f6e734bfbaa4ecaecde3a128a20c501aff676bf3f570ad5793421a5295f53e1e87b0bbce906bb3f25ae2da84bb16fc658272763bedcc581a77d34a901dfb12b585ff916dcae8f616a11c3fca4112a1db336206f997730bea6ddf3820b6ffe76497386d6e895d2477c975328f265b698346e9fe6e36cc71db95b0a1e77d5c1f4cd0a01fd75496ad94a35ab3472dbdf82df6a483af203f5049404b4885883f36376ace9edf3412c91282d93a7630f88f0a2e8a5a0736a5f0b859901307a8afd9449ca7cf7995113c0d15c2352b99213800fb9768624aaeed403a75cecd6ccf14c5a91bec08ef470b62817020291064de41e76a51b93aae086cf3b019e9ffe27b366067e81a36978d87f474e07b490fdc11f85f4c68b4eb3499f0e966ce84f306970e235f21a97257f13e89990d66684d82c53c02ff4507531aa3b95837266821797ec29822c5db94865237c9a29cb1f4e5acb1d9f01bab5efa202f65822383ffdafa2c5e7d62b35e20540f97aff94fb110150c36a777c3bfb678485f3a6c9a358291d7540940975bcbf1ec191b5fa965b2489d5c33406b1f19993b91dac6e5983dfdab1992692f642e9034577488be446ed7d4df5650e71dce9deb6127321d73e67bdea374fd6e33da4c53a25364fdb6c1a30db18fb08ba9dc9eb304e14a92c4126002da650a83bbd6144d8b924a96b5203e23594dc626d92ef2f6486db04655f0333554ae1df5f97ef6161f42c4a6dd1e56faebd60bc6480a10ada6b6035369c9a0f3045ce0a7eed59a2461aa735b83391a2798f69770a7f09adfec26fc8e70fc7acc45760a6ba5b78e4e2affdbf8e33dc9937aeb75a03784a4e47815bbb859fc023a00aa75788c577dc1a2dc75270f17dea0f80755ac58760ed4ef75663431a8b2e5fe865ee7677ed7bacb38b5ed27b1453d42e07856522d44aabcd868c17ad1a2152a6641285452e03ae722d538d07aadfea3dd63fa9643785b97cbfbd22608625aa7de197d90388cd2e6e194599cc2f0159d36c56eeca0143f1754e932f7ef5e852762a1dfa04466aa01024b2e8aee9dd7f1a4f283ab66658cf7f152ae1482cea8800b759cf8ea11a28fa0ffcaa850802407ab16d15b13a77da4a00bcc461b49e8a25049eb0d9eb987ec7926e064f5874773c58b6e147dc4039780beed69e1e1e3c7ec138e9a01ed068ea2ae5e7ee0272863c9b9985b6370615e1a9e4e843ad2b3699d3079437e8f9998b7964d75c98d00954965f4d8c36a2abe3abd520887ce23b376291a6b693f5f6feaf42163602059036825a87e8294e44c1d56c6ab7a079804ddcbd77b821d98ee0d2e2736b0c66296f87ca30f9c57789e7b47c1594a819dd7cb6c4283463802d198fa5a952fe8fe1b733873ffd821699995b8aa8b1d20433ac091f6d5a7d63d35e197dbc8a08926b03f6dbe303907ecca0613a0e6fc49618b46e3f3c390ea0c4837a4f" + } + }, + { + "CBC":{ + "key":"ec1e0f5dc7b11e8eabd8b67a75f6418c6fb5b441bb7a7c9d", + "iv":"1818fc37066caea462b11e7ead174e84", + "plain":"3c57c4cf8681951be6f369c316a2741a797eaf9c6af7db8684774289f2fb40317a1f71ddb7edb71afd06688126b380a4e1e1d23ccb86", + "encrypted":"ffd21f1ee64ebf9e6f0fe390162154569c7508833cc34415e2d67750718af05ff4c1fb2061f5c927a56a5686f899247e82b14ef863ec641a81d3fc5211ff472f" + } + }, + { + "CBC":{ + "key":"9a3045dba04aef1357211a181d6ea0c56fe4adf5e251e1d5", + "iv":"9eda755246edfa528c6afc9c80713aeb", + "plain":"a8564fc3fff249fc7ae9765f1e3076ab", + "encrypted":"caf0f380bbf41d5460aa0edae749d7d65d1f6b7d0468a2d3f8749d350ac35afe" + } + }, + { + "CBC":{ + "key":"44f7a3079169175f5464644726a7f5899b94b49fc0144dbf", + "iv":"cd03c173c0b2093fa49959e5172eb460", + "plain":"e748ce8d19892547040a498bf4d3eb2659b4fa0c816ed6acb844b3b7b6f92650", + "encrypted":"9c20a82b243c6c989fc63ab67ae5e301721aac52b82b6a2c6767ff2de3f4ccdc6f1818fa70160b9bee026b7d1645dada" + } + }, + { + "CBC":{ + "key":"8c064580057428dcdb4e16a921c4e6bf168e53313740aa13", + "iv":"a867e6710f4928eff40d3a9a3b90793c", + "plain":"948d3633bd5523fa06fd6ed20a5a01b1a4dc624a9b2c6386cce1970116dbe5c561b1d48b7f4d6c0c2d79c1ff2329d3506a4aad19fafd0a7843bd80d4c1ea64c55f99354a74f07262e99df8e35cf0dcb11718adf63613996c230f174ca0946a7a17dcd4445973f52d54ae811f67f996d9b8c7d91c0ff11b4486be5bde8f4e9c4ed7101c7de63609dc78ec47521e737154401e2835cc5ae0597d42398f188731de4706b7864596b1fa9ba3d7c02b5eb22b1003b2684e172ec30d1e1c50fd37e4ea0dd5835e92445378c3842335928fe941330723cae6952ab8f2948b8c2a8cdc643561d9bb83ddc304a39a935ab475941723c030ec199f91478025f82aefe9921d", + "encrypted":"be47fedb098c9f8b7dc398b6df8887a892408b45947443f39acd4730e5b4b3f5d22602b89dc007a7f7e0667403e9e47c55d8c5015fdec9a824f1168eac8ef494cb1873fd5b3c91f0201e0c1cec8d550bc884ccb94120fda947f5a827a623ae397126d1c83126a2894a8f94a7a5b658bb777283034a861b9c58ba46c223150161fd3e17bb27fc11733f01ffecb55dfed11a72635fb4e1f7a829ea23ed7cdd8be3af96f78bf778c38aef8418e2e106069bc5e53787eae8dce7761a5be224f5382d3fba4c57abaaba2a3e7df4d7621883285a4e18bb4900d86f9c739fb496293648ee4ee7c0322b8e736b4368826b7b2c78695220b1df62fa0510347ae152e5c3d27aa86ff9957a998da36b9b1b5f3399cc" + } + }, + { + "CBC":{ + "key":"db9874db6d36c040419a56389bdb7a13b1361bb4023207e4eb9bae594c4358a9", + "iv":"fe6ac0e1d8009d3f85438439f6d6c9b7", + "plain":"7b5ec1bdf310ed5bebb2eb1e021dadec", + "encrypted":"1288b24c92f36f591069d5cc4eccda6aa795ee9e1c38af7d634ddac69f3487a9" + } + }, + { + "CBC":{ + "key":"2d9cff76c8d80848b7f38dbf61f3e5380b01cee42ae7ef5e82037590b912b686", + "iv":"707af51858de9c7a1d3b06800638c177", + "plain":"a7a054afbc00a1d68d760c50e71ddc2d", + "encrypted":"dcafff7b6b07e8d5bc63690acf7dfa21c78594e7c051ad41ef45fb710c793027" + } + }, + { + "CBC":{ + "key":"3b086a4d2b445b2deccd2384584d56acff7486a1dfdc44dff0fdf83cbcd5e873", + "iv":"e256d9558e96dfb7fde518fb02309d3e", + "plain":"0384d13440bea562dae35be11b0f2cb6dc0e3e", + "encrypted":"00a5dc088756500c53433fa2196de89f197e555096970d65172f246c4f02d3c8" + } + }, + { + "CBC":{ + "key":"9c9398f98aaa0ef14b453c09ea224ed2d05aa084466ea8eba9cd20f3a0af54a6", + "iv":"4499fb2995ddb4f57cddef1611a1a5f2", + "plain":"a2", + "encrypted":"4e6a09222150490ae39e6bb708f35c6e" + } + }, + { + "CBC":{ + "key":"72a287ecccd8033f48bbd94d85efb3a9270bd5e4cb7862d83f86eaba7ddf9800", + "iv":"9688e2a617c5d1eaf3704f217196eec9", + "plain":"a11e03a6c122598a30bc322d2b7d1d0fb9b75d062d1ef9435e78a44ae4f82b7d671e3d627ff909b93ed8cecf5bac0fb6bd1d708566ed29f8da3b0aacd0c6f29c9e74f6d71c77a83176a027f849548e0133e01b7651e6a6a43058db6cc1e2f11c3183d5d07956c4c2c5782cca0ffc39d727a96ab7cab2b774c2c3c976de805ef3ef0ce21632e0ebeb525ab2298dcdd44f6c1332cd80b2da96f73b59b6ad247dcb8c7f2e7eef04832c2d0b3aff9a6f71e8a803e9437b1830092469d6d077465c366e19c425de4cf700db0f21d9a835f261d0ed7a3f37dc5188b81dd4300398b492e88dc2976399dc9d6966ce94b81f1491cec4c53f0a6ce95cb2c49618a28348832123b6c955e17b745072d194ff0fcb95201b4afa8303f53f7ac812ca89b949786ca5f8a7f4b4971905fa7793de44de8c13700564bbe77cac40b18fdd82f4558b4627cf1a11b94c62951697718c64d1342ec85756158c38b9d0dd4fbc3fae68ffc9b00819e438e2b04cc5d0b9e90cfa9bcf88703cdf90120b3a6c4c5f59c53a93d7d011df9a096571d7fa2e0f871313edd5af54ae646c5c4b92048813f9537ba33245d72eba1354442180d77be2e521f318fa2edee475feafab26f86de5674f339e9e48d9122124a4518cbe39c2e841c5a1f8a6be0d5bca30debf246e3df9e929bb6d13c5397f302e562d609427a234aee51b10d108f3108889bb81489cbced1e41984df7185be39a8f7b2beefbf549fa3eb5afdf196e3f1a502cd1754a26a21ba77fceb8b4600ba8793b9a759f550d4b0f2a462bfc35eeb51a184b2c162cd2c2173494b1135eff29967584d60c70d2f632f2077ee98a87e7e9a87f2e833485d49bd10f5986ff291d3bb0aec3a78b4dccd048e365df22c93302811be22b69cf7928b18eb0252369fc2e2e4ed6161619b6b8f50afead66a43024175c88d1247d33f44c4b5bd1aa861b154fc9317bf8ac1a28ca3647ed21648a40025e92cce8200e34350ab5581b3646f824cb60785441ad82503baecefbb8209ec0bffc05be7716c9fa3a0e8d360b769360d2c82cbc1f8a692aa945c135442b2ad788b87bdb7ea88d00c3493313176162b375334e5c2d53b65f913b40d11f3698ae678e48c3d0cccf445e230710a5735525e2ed01aeb9ff7c940c942a3f05f2d9fd0eec42886125981594724085961fdb96fa08201d69e5191d2cd207224e85f587ccd385b190b7c58e31946c6ea605285a95ea8455671db334769205e7fedfd1eb0ba9432635b8823a40d6309e10f071820fbf717125cf6b483864a16bff551908cb044a660c3f2487055b87f175e5a01b359aef0d6c8eea256077b4b9280d05b777dae19e749a0b320215af29841cf041bcd64b34153dc46358ab2405e78ecdc0463669c0de0c51a9da8789c300af94a015705eac58defd86688406b5e46be95b9f70dd10ca407c474536645bbca7313a44ecadf71c737cfe58c0dadae0e53bbae9fdc26ded2605ea560f23f506058435506d92bc29287f15e795b1cc06ca93128b68e9b315d5be335a70885516c05bd3f964e337bfcec624dbd1ff18aaa3cf05d13b456d2989c65b5ef238452a6dfe35f65a73f482bc04c0abd3d9eb490c120b94845cd09ab6d0e590184e1c764ee203bd0eb5962dd351374d939236518019ba2c1d98740fc75f297febd951b705432e63e0c9551eb6935bebcb6f9e6e89baaeae82eb94d7c1c7c3b8b6513c289f9ac683ff4a53271e2737", + "encrypted":"849c61574d0537d0ade3bdcd5052434ef22e109d416e37153564486c357e95dd9966c57973bc6eec0f461e58fcd2c84803c16d3f7aa4482d710ab63aeb3fb0bee934572e86181c82b16d74c7bda6f8e2ef59a0fb81f19b17196e8dff59cee53f27a417b12915fd0a55138bb62f5c4174c73816e2629a533ae7705a2d108d305c04fd7c256848bd0413bb7b1354a921963865a4b1485e405d480ce64f305164915b737a0bb1e48cfec50da22cedffb4a70cd284085202463178ead10a9a66ff4d037bce1a8e1ae4050b5405e6e9a97e082f9f4a102204cfea30fdf2124159af9c93083a02788d504c378564e57d937728cd9f45135f81caf2ed9e073a78ceea0ed12281df046ae6b1aa506714edd62b590e06f10682ccf498381d0479f60444c3da155549a85d05c39a79918ea04f0fc22c7ee9a5250fadd9eb6561935855911fa9d55fd2393dd4d9dfd82b1b965b166ac08e2eeae38c8ad87ef20061aad8f7049a8fed46db898341bf952e28bfec14e0fbe8264f10aaaecf2112071015558957dace13a7f88b321b5e8952433350d55e21d254614d6912a11f72e54fc59863d393fb4507484cf23628f2ee1045325af9fb2ece83a38c17185e751839274fb8317c1af565432e27c50179cd505fcc6d4c4791cdf2785c54d743fafe45edafa0c9d579c718ec3c99e545995ea582369b6a257d94b305bb9218f5f72f21ee92398fc7a6a4db07154d7f9a2705b2a300bf2bf61cdbd853f8f2178a181c1d9729e6f43ab20b18aaa646351101be51903c7c017236137b36ec9e9bae1ff149bc80682e267083292b9d6a1d740dc0e4be85db39809ba01de81939926b50d7aca6a3d9e0f38c856f584ce5541e3ac4b4056d565896cf1712866dd48a211e88ed3b5c91e6b59e945425daa2dd0f134c4d3edb3cac4c7f1f967b4314429ea1cc857465a907164be115253a692fa8e70d63e8a52284125bf2f1a3a864ef4d8b9003d89604356cad9056aa5bfeb04e9262387459d67351446003f8d03623265fd3707a160ae29e764cfaf16b690d7abd82649d848535cc9de6b6a8869bf006591ef34a3829c65a294ad98d35e5bede484ace8dd1468a18232a533986c43a3ed83a337c966ffd1a79703349921371bdf9912be3a5d871f3b4d45c754e0f39b6245ea6d37bfa4de96bc80fff4673fe39dbd71511a8c9b2d91c905de16037b1c0c4b13db0044d2c353ea6cd3056e5daac143388f8d121ecf07ee03dd2d4fcf31f1c9c1a6e5fb6795560886e308e1519e07569d7e85970a53692df7a2c83020d155963bbc34c805eefb5f1bafecd8dde1b865cf7c6eefc72cca270df97230fc8c66c93a85d5192611b8c887619cc672b2cebf1229cdffab45304ea98a9bf7e597900182563fbc1bec7d786361a9c027f162e09832ec94dc3c458b260b720f68365022b43cac2e885e8086c79ee4c64e96989e5c8a70c144fd5a9e991780953ae972ca4f3edc837418bedcbd2e2585a54bcbf5156590e9fd76a4cd510a86c770ac9e56cc706ce66dd35b5d5b3bcb1ca54abe2807e7a22f9ca90c7fd2bc1c9e78125fb9920b079ea36a4e3552fa546a79ff1b679ba0aaeac84e316f43aaa02c3b0641633ca2a016413d96e6cf5ed2bb55c71202ee1313c9cc840c731887637470756402af53af3467675e96d67a0c1e5d41742e0ea9234150af1d65b708bbf78cfab593c6faa2babdfaacd398cd1909d04f7dd4a8b9a701f347f4da71a7e692ba317c926cb718316b9" + } + }, + { + "CBC":{ + "key":"111d8e3124f0b52167f61d3fd564caeb4494deab4cc3db65132c9ad2ba42151f", + "iv":"f90330f8c7596e9ad00ab93323a8aea5", + "plain":"031a5d50a1e7958691d0c07ac86ada42e6897b2d2cc4cf7070ac15336ce32292393145d31e14b5643a603a10280ba57a4aac0eff54e2", + "encrypted":"e5b00a035f1bcac298fdf7502fe23ed80f319b4887328c8236c0e18aee7094ac166e9811c657e77d25ae7efc228a3479c7b4c338c4f1446d204d368923901f47" + } + }, + { + "CBC":{ + "key":"805db5fbd3dc64eec980d3d758bf41845279e10bfcbcf75e17e330a943e51fb5", + "iv":"5c6090c8a3f579c7c1053e0517b838b3", + "plain":"2d5fe11445d3ca8f2fec1b603817647e", + "encrypted":"fec78cd7064cf731c0137b5ad68e644ff3e45c4b430381ed1be93e7643ff26f9" + } + }, + { + "CBC":{ + "key":"947499812331eca5bae3a9df67a3e5d7d82d1b83531667a1f187b6d0fcedc129", + "iv":"28aa8bb4313f1b5d7df2bc4c0c69287c", + "plain":"13e1888d7d2ea70b86bc423b3253263fffa1c8c921f3a44b986a9f7fb1731db4", + "encrypted":"07e03d23a511d7bd0a804d806f8410203d7a7154708f9e99c7dad61dbc7f900011de72af45b8d4d3c9a9b25291674b6f" + } + }, + { + "CBC":{ + "key":"3420852a512878d9c8e93f0c581ef2d3474029cb2d64b2d9631519bf8b9b57f3", + "iv":"500ab1277845c9977af355059beff307", + "plain":"5a699a2fe8df5c99b404ffff62d865a2ceb5e1a2d7dee75633ae6b1081dca484da7e9f4a30ad36295dacc52f8802a1474c2b01829143fdd6640c6d1210a7440746c3735a41d24b4facf0aa646a7639f8f228c5bb5463f45f26251b6a370c692226d8f9bb80f33556a0e72e2736e242fa8d8a2f3a1707a4d93038152296fd2f41fc1cd2ac5c5e55e10368b3fed01e5a30422557b51fd79ef83ad86380dc01ab720a0a7275cd6c1851632ac4a9100cba3600f49abcc6bffd14eff334278278074ae0c6909c81d78d5875093003a847c6d8006319a76cc9c92d3909f5fab65a083e35f9faf5d874133c087955354478eea396652ca74a9e143a150691261de74e7f", + "encrypted":"ea26d5869bc0fab0e74952bac4a45221455e05e31974fbe05dee215d6b15b856cc95a96a1e43a98dfb59621e7d426aae6cc6689f90bfeb67ea66111f2f5fd1149641e6672c159c0ed3f027b94317125f8fe167859842f880fe233eb1720944bac8c38be943bef555dd70e0669f6d1877bc05bca3abb0e26455a0b54ed48ae63927f0598f4371c3d423975f6ec67019c6b378205c4201d3c91d2403051ee8d59484a8109982f298f3f219207a07a6ac92a98cde2ef851882d492e3beb0b66c1c191cfbd687a34a8dbe04bf935644e411dd6f8add5198885eb45a2511797a1674438c15f2f38d83387a1085beaef17eaecf71395931fc7bb6854e83085fb313a14cb4cc9fb05172c3439b02c4e2ca2b964" + } + }, + { + "CBC":{ + "key":"01b13021d0a37f8d6adf24f78d02a8fde5cda466ebbce1fb906d68d7c35eef21", + "iv":"a0c55953039d3883fc0957c82e13139f", + "plain":"980ca73e6bddbb1e2f3f3f596943a98f", + "encrypted":"3463d9b70add25722e4b13f89b8f7811f2bd16622bd7f91a1e9c8be4f8d775e1" + } + }, + { + "CBC":{ + "key":"4444d220fe316783476ff5040fa83c14becf00196182d15db9b2dd125ae01b5b", + "iv":"82c4264b70080627a607e81dbb80501e", + "plain":"97837b7eb7f8ab110998d6ca0805f321", + "encrypted":"8a151f039c8a460ce752d5d1f3d555280c1967746d720c984611b79126d1b507" + } + }, + { + "CBC":{ + "key":"60023064a5b167c35a3cf4724f04bb4beab46874f01373e427e254a7667fbf61", + "iv":"a4379ec05a27d573fa6c539d1a50ca94", + "plain":"b693419cb6ed9e0e95d9b64f3f64f02814efbe", + "encrypted":"3a4dde7b7957483ccc3a22f64b0e63bd3c00c4e5241b93008fe8a09b1b47c929" + } + }, + { + "CBC":{ + "key":"32f500b9c8cbd19266fee5cc9d062a30ce2bd8e35d7b5e4344a409d0b06e432a", + "iv":"3a9985dc5804397abcba857e12db3262", + "plain":"fc", + "encrypted":"b3cd3d02bd983ecbc830c9ad9379441f" + } + }, + { + "CBC":{ + "key":"c5e92e1328f0b57e4346547302df4b4febf194120960c99f6adfff5f442b54a2", + "iv":"a1a8b075818ca6880ca7262174b16bc4", + "plain":"91cb91850c2195f050f4e7e4a052ba7cd5e0a91b4e6f8e783b3b55eba02c6cb1cda7c9c4f43adb8d1515eafee5a3ccf5e1ad439dd53e093bf835b291f98835d7ccdbd5e683e1fd300691063a4228d1cfa74a9f846c6616d66cd70c0d4cfb6ac28f91ed3170c4cb639cbc306a3487bddf5fe07af7d2ca59188d9c86df4442c2fc341d1eace5596ab502e17e55e81d7bcdcfe28ccecd082a9a08c3daa25bcedbaa501bce34e4218d01ac2774e43c0e4ecc8e537f004135bd9744ab6032ee95a865ce6f00e2f6343f00b3bc99c158c5d4b4f74f02fd27b47d1936d82697d9f12f67e52ac7ac4810e7d2b9ed996e1a6833cb4d3649f9d72bd3bc65e111111e6993a875d2e2294f37128c1c2e66217a4f0627230355a4cc8a086c815e62132edc1dc7d6e41cf2092c3dcc82d666a41776d81cb702e05b5e8bad57bc01a85aa1c874e8f59807d02e5e66a8899f1592201b3755d536ba0ca699800eb272b400df74cdcebc2be47cbcbc39c5e752a4aafb97fa8addd1f2b2de81ee922657c61bfd7d7a70159b0edda80f265009dbbb7bb276e17e6b217136cc65e94da93498ad0b7fb7853fe2116b9494b7a145387b61aa705fb14e2fc67f2da2ab0b87a1ee1b4050a9f314c79323786e51b10673cee4330298946df2cf1713f28805c550593cc3f8e053fc25f75a2fd5e90101098c922d8e17843ca5e0587d7057c491cefe5491ae61064ba08a75355243727564baf059b3c817f1196e268650ec5ece3ad7c1c06f17e4378daf8f4cc85a936b81cfacbcafab57c7dd78ded46d6b5bbdd520c5a26b4eb2663b3c4dcf56603b180e6bb32b12a7fc58611e7f94f7685affda9fa6d4035c7550b9b9dfd681560c1fd8d64dc4f3aefee3caf90007b51e47e6c1a30ea349323ebea96386ce0e33ada09d111d23f34fd4b53600b1a4386bec88ec6ff081694c66aad90ee26b76ff5e254fcfc653bce7abe86d624def8e3935633bf4b1653310d1305f2b089b76bd5ba54059c88fa3ad1782d96a85b42491fcde77b31473935901167cd81f8f646b72d5c32983da386a4af72d9afe7e2391d23ea23046126d7bab315174e9a50476a74218b0911e29dddd624e226b9847109e65e279b248231f4c62129573aaf1a900969ba2e05a374b122316f83bd194fd3bddf82f278e6396cfde8fe01832b0e79f9b5b163ddf905650fd2749c1cdfd79c8a60024478296464116d55a8ceae43a8cb46ee49db1e48542a6fbe9fd68cb62fefea57996867c1868b497668f371a44ed99f2ce4e5255d11cb676d3653bbaaa1f264a708f7a294b97366df3bed0c9aa43f6c84128b1ca15d34ae8ac2de9067d2279a8b3b750e69b07033945ca47f9b20e1532cb8dbe1f962ba3673969be3e9cffb923e2052a5e59064edf4fd2531864735641c8c393f74cb3a9ac24beedefb39682f5bb6c5d1b97be24b6e047005bd0cf9ac1fe727b1f85a5dd31a262b056abb5b41b9f6fbb5b711cf238d9d61000b38bdc0691ad8dbdf53550804edfe926ef707371bc25efa0e5cc2cf3682a6d54f4e688ad6b3cc31e0f227819d3709ac9413a303870a5a60d86407277f92600044e395f77aa4a5d9fd43f494f3e2b8f2e0ef905243f2d3bf9375f58d0375dcf6e7d870fb6cab73530baed14cc16e8e36a70ae4680bff82907b667eef66659a5e6aa33c24b1eb9e0787c5fc8f05fc75c1bbc62bcbcb3fbb34b8487e16b1266ea6ae34c504abd0946ff0e4ccf9f", + "encrypted":"ca94211dcdd43a917e541b9dd800cff451758d117a602ebcb475517bb932fd816c006674e6d3ffe3d1863b9cdd6ea63dd310e997e66840f4912ba52c361f9b97df46dd56e2e3815bfedb07c83d57ca324bf47eb8279c84d6544d3117b591715f65976c5901aae756d6a03b764a55e0de9285c257a4bcc736e10eb3306be4b1e9b7ede0cb934c09df3dd31c130a7219ed294fe2de3e4d76f0432d5f46b6588c55ad69d3ea925688eab375c9fb70a798e5622f1a742d04afd9ffac4ce477aa88d97a0e993ee9fd7b6a2d499a2bc674e74e33d0700f7f9bbc0eda3c4e8d295950eb278a2fd92656b58ebc67fb501b55a8f03468d7228d3472fcdf0504675b101a1600358d17d1948332b4c54586ef121a8599f6b36bfee1fd448d3a4695af9438d6ef27d562522bfc6ce46299198fdc4835a1bdc07ec18a0c27425b67e99633d6ead87ac884a6921dc01e841047cd23787e6587f72ccd1c69c10983a030af285330713dafe1b7b40cc8c02ac130d897f6cbc71100fb4cb5651e87ce67b6a714ff0feff56de8f132dc3b276f87028de5f97801fd269bf0e48b7ce88275ba80488789c0a71317d5b05bcc879bdf94097e2c67d94fd64a062eb7283e9d0e15501d693c308f4e2b844f74b32ae9595b9dd9377d8d90fd6b5e2aba5f455039d9184aff7da6c65e164ecf13cc2c817e3a2627beb0f86a965485ad65120e26a2c93dd86cd5c48dbc1d0b8d6d95cb5570d0d37658f8d0cc4e1afcea8830dad036c2a8f9d4754ae0d86731a0f6a3afbf875da10add3bda60b39cf2bf1587d3a8f9692adf240fb905d07a53e586e25512f10c1586d5de2b116fc9328391f0b14ea1dbe00224e8d4048081d20b7779645e3edad90a2b408180ae395be5230f0b1d6b09c824fc81b9e0ca5eecaf2e4b7ac2334011efe4368c32647f847c6ec6988b88549332e081cad3f4771b10bc718af12de980e9f9761b7daa407678b441202439f83709761a851967233727ef2ef5951523e8342cc7204622a23c554340db2d81d9bc7df2f8b4f437392ac784f7f4106e0c1929b1691e2c97088c905677bb223e465a13ddf2c4b880e0c6dc336023594b5d08080bcb238f1af5db17b20c5937aab2692e623c33937f5ab441fd7d7c8fac869e065a67d8cf810b95c2871f1ba6a6d423d8f5f588a490ad9892508b1ed42b9c1b9724b8139305c217c67b058262ee0b56a99fd041ba2803bc4ad3b4e7d0a68e34a98d83367b22d62ffc4c58e72758b4748f85718062b8886831d43305017e6f7d5cc797d3e1ac44411f37be5165ad125816ef94ba62b646652b9759ed7c345c46633e0abaa071eef2bc72927f520049779e517ce5c2c85597aed2569db0d0eb3d00b5297565b38c8a80c54547708917c3628d280e28c46ada0bec5e88949581e70ae7f59e2d854a1e03c9ffe302e59f652ba40deb205f0aec5d184ce74681a86d73bfbe5bf2bc682259ee457b69b7638d56c37b28ebc3b56a93bbece6401108124f9a7b18983cd755a0aa147c1747c7799c086066e323c1938966e54c29203043adb0af7e25b9f83a7971def8aef1abd2a75ad3bf1950b901aaf37f9447abca98141e30a2a0f5b2c47e3e086dabdbb690e6cfedc372f4f1580d0539bd5eaf6704dac0fd7537020352850fed6d4d6ec242143b8d8047e9f0facb7b7124fa29742095b08383012be632b25fe7b3b0700d90b80402e7b062e8dada0132ca1e012ce306ff0884a5c77c4fa968229a1c28f5cbfcc487" + } + }, + { + "CBC":{ + "key":"c8437b23aaaf75d3d4ee8ed675dc576d7f0b4c4fe89ca71fd8797e93fd02dc8d", + "iv":"380e2cca1c986eebcfb15d711197f1a4", + "plain":"f2948833e9272653f7a4f835d3cab897bce5ef274567fdce1267f02e01224a4c32df0a61ef8e5b5215f8221cbb5aa025774a602a7b9a", + "encrypted":"d5388cc3bc71da2ec609b6c487324453793e4a39cd858a792580fdf9ef3aa86123ae7354ccb531ca22fee655dbe5c46acb110cba7c3a2ef0a8ad5c9d6ce6df95" + } + }, + { + "CBC":{ + "key":"7511047e777a5625c444663c20a43a8780cd25274f8011d9bc2bdd8bc95db3fc", + "iv":"51513cd4e66807c9f11cd064f0a67e37", + "plain":"5bf04a22b4424e7b207f23e3932e8901", + "encrypted":"6f7e53e826f438673b68d0e9efc20b419b9b39b9338fcb397cb29e8c0b723c9e" + } + }, + { + "CBC":{ + "key":"24b2cf6225cbae8a98199877f22ff6f8fc0cad1c1935ef1c2036d390f49535bd", + "iv":"883ebf375f677902fd7155712a6775ab", + "plain":"5420e673dfdeb124f70254a997f0805fa663451c9c61fb2727f2d4bd8884e383", + "encrypted":"3b78ef5ae279617352e688adf96efa2ed5d3186eb348f055a8dfd797c714423c3ae04bf8d4556b23551b30e0eff47087" + } + }, + { + "CBC":{ + "key":"d3a71e56606485697e4991a28ea7bf4709fcb78b62aac11860237fe92a958e4f", + "iv":"59ffda7c7959d366f6021d972ca7e46b", + "plain":"17a29d5075ca6a950a1a5b7cc0de6ecd3eb5e7fc8faa8b59fb0ebd05ec4d84673735272cb255ceb12ee3b59969c49234c2fbc499f5ef46e82c292dc03da0afa4b94ee3c8d44934cbffcd49ce7da55bb615aeeb1e9231d2443da07d324dc0d87a014073b1589b473782c9b4e866cc967df0f9d9f4b8f9bcaf8ba75bd0f461f0ec3c1095b422d9da235d196156ce179c35a6364a7760080541e6b4fd55b72a7b9e3f2974539af56532ada2f8e1c035ec0d9821a86751b2f91e7f7b0461151a872fcd1f222fd9b9d38b0db3da9728408ffeeef0c79c9fd6539833f3ffc176cd78918a321ec7d1a0edfc9a0a1268fdb2e9a77e22dcee427e0ed90a559d3c548ed2a8", + "encrypted":"ad729de24ec0f08fe84af4a476632bb84678da8877a430edc42685b4e5588a6888fb9d52a700db29301611289ac2a85b590a321df30205ccfa43bb1d317173f52c269cc12971fb72ea8fbe266501f7a7d35f48e59bd259c307109bb98ccc40bd768e6ebc98290362db6ed9f58c716914b5cd5c84eac8c9f77db1219c22cf7528930d266f3836173baff5dd02850e571530a45d80f0d5103d9094e168cd88e164cb374845ce57ae86084531389f2df4d05b29a1e34c1c87d186b7951037730a80a4cb1012220e60974e0bbd187301fff8cd08d798d14eab3ff07fbef04a7acfd765d6510574811753121845fa5ded87fda73a4e973f5a13013b30a5170cef4ef740dbeb229069380b75a4552a5539407e" + } + }, + { + "CBC":{ + "key":"1c170514241dba0ac19dfd032ac7e786bc9c7bf7abc8ffbe7475589ba107e266", + "iv":"ca8265abcf701fa59cda9783cd7dbc91", + "plain":"7cdf9600e5cf27c40029a5486578446a", + "encrypted":"bdcc0abf06133e8d90f8c656e1b4de7e0b57b3ba69b83d538aedfc4e7615387e" + } + }, + { + "CBC":{ + "key":"7afe4165affab29d3e4dfba6302917c2caf20d89edddc5ed7394258b4f22e2f4", + "iv":"e9ab8e12e6a70ade401bd05efaa54e84", + "plain":"b788ee34b33262e4add9743944ff4123", + "encrypted":"152257ac24ee6b6598ac23dcfa02fa60f1044fce61fe5d951fb4b9721da3619c" + } + }, + { + "CBC":{ + "key":"7294c85f6c7dff54334d8968b4977df739c45a79475e99056907eec1a87495a2", + "iv":"f6cade26f5195dc00e713c6f54d7984f", + "plain":"47702533552a6aef3ae9497bdd8b26b043f027", + "encrypted":"25982c970eff303fba88c4d89ceac11ebb7486dc6cf23beeb2af733d23c255a8" + } + }, + { + "CBC":{ + "key":"0a6366dfffb4ac5fd8a3906afcce8189187181ca477d2f957292f35d1a953dbe", + "iv":"d6c3fc1291612c6899676d5ed563ecae", + "plain":"e2", + "encrypted":"fc698ac8c6bbf663ca90ca7102aa6fb8" + } + }, + { + "CBC":{ + "key":"a22e51cc274f6f55c565de0a554f8c6b05ebd744f7e497d72c81e464a597aee2", + "iv":"ce9cf440162ae13ecec655e09c08191e", + "plain":"41d08d8501b8350880f57ecf5374aabbf200b3c7dc14c6deb8706152ffeb8eea21450708e565a0699dea9dd5fe713d9cdebf5cfadd0e2487e8d2eff6f302b8bf3dccca20ffdacc6af33ff3d588a76cc968f0f138237f4aa10d51bb9d4c55f654543cda78a8089c79f160f3465484f94b16c41afaa65ef35b7ba57327ae3898fa7ce6688387c4aeeed1893d940e163a017f03c54d0633f9c2ee87c9e02eda9933e8504babdf61150847f76c813b79d9b9d8112c4ef08e65d45028fd792459012008c7edd2f01d3b41c4f1e08b9bc3797164a92ae6a361a5d77f12971671f273e1e1349c8a29a5bdcb1c6dbfe1d38d5e16516a0c76b6ae133934933ccfc871167792b8f055d9af89cfb62fd1204c916d9624e6c090a09432dc6e3bd76d6589e0da8af87df097da3b1969274e7cb82e013e8eca09f12a51aa6e1124a27896640a9dc9225a07a3ae68a6a51e246aa0ff88ba9c885ac6fe7d4f4c8f28001d706677a8a7fc592445519966201353e442a81d2e05ff45905e27b4b74d0990f45a3e6e38008baf21e7ad2a2152ab6b89d80400c4ddfe2cb93119974ab05c3512b458dcc82d7eb498abf8604c391e621548a9f99b2c2dd53049edb10ceee84fde6db8469479f2f4e076ab3919f105814b5f46420289dd92fed308ceb8af15869536b7b03e8c2eb7472924e2f872cdf16af0cba2d917d2107d5d791d84538199ca0f99f509871eccff51468788837ba2108e9673bf10b3554628bd11d5a52ab31e34f04ed30c2b262584ce0ab247c09c0189e67c8cf701f78183f82b9af3ec1dcf55f049cb3932a7e1d444c2574f7d9278747c505b253d6002576cf5aba209654b9c65f70ef7926a931dc9cf7a40d9765e85ec9794340a217f846ce8a49c488b56e5d12d27a20360d86dd2e14c9479bb429aeafc5aef267e7f478154c456a33445045213bc4a1996706dbeec40d1fa9c9eed5614486d35014f7c12b6a0756aeb11777ed2bd5d6d541843095187f910a27617074568a4cec10a22119c0d6833cdd88954f42aefc7ebd6a68d9d05a2340b66190775ab5a80726cda2038a13d4f3989daf4b2939a0295800953131c7d3407268d8887f04561cf5277d953545676236d1a43e955c1c3da84bdbbd1dce783acd53f0212f227eb568ebd9f585a7ab313d33e785cb8fe0da1361f02c6fd91e633233b94a60695c9d2f9cdc6cd61bfa39cdf59143737b2210401ce74a45b552d6345d6ffc00c2a8e89a271aea0b08d519346e820cfc8a9427ae45c2b71c89ff442bb9c707d638c0cb6a661e193694351726412ce76f5d6b20f8950c1773f7dc3bab3cdaceaefd6d0c40bfec7f943d3a295606ef9541d09649c6749ca49800d6432ccbcb8bd3fb0a57b953b096cdaefc448aef21142f53a73deaed1309e08e40b02eddcfa8c49550ea316b7af1462c24961cf5f7e4f2c6ee688f1cbce48f01524609c5f28c721ce8dca0e9629da8553043c26d6b0fb510a7c5fd93faa67c9142dc1f0c8f7d6e5c3b073077439cc7b1a4a977c3aa4b7bd1355f6ab7f2613ea1eb683adc22bd022417eb00b7387596cf0509af129fbab4db51737d67115e9deb6c85dc043824ca1263da4aa4837bab470d326fecf67898a037841947cf8465f604caeb17e80a15520be81dfccc255ba27ad9f2c0bf19c24ca82cd36f3cd550ad1185dffda28490181e4443f6ee3065bae6890b2a22baab700125c894355ba7a89fdac241f0e700890f7", + "encrypted":"49b913afe497bad308d59a85e39aa44a6109704c55311f25323e6974ad6c9fcc86da400bdaf975bd284e134b1494c2dfa89227539be9e301e4288a4cc103949c674651fb20fe733a77927f428482b4db280efd2f4decc6f35530251e945b44e53ea66615f70359ff8c67037d63de2518683a542c8f12b996e5b38de1ab61697dfffef6719369aa9a05601acad6c3d86922ab2a7ff7a21e17c1b087f17a4804dac6eb2d3b07ead5245c6413e974c25116cf199ae01ece42168353e7d362e2932051be576985691eacb302420b94cc96f9d5e515c842036d119494ee4ad4a0b4034faed23c04e51e0eacb5ff80b43b1b3410df5e9e3b2df7ffff4ddd26ff1cf37a480433219d93167f8f40a85893ae61f68b1d7895cd7b21345059d41abca6a5d5b4b5941eef11cf62fae7799e68484f3c54fdbd30cc1ea724d5215b966f7f2b50d4b5ac4d99f07d2a5fb70daeaead5634cce29a3c67b0cdaa16f673e95e19b98724e2f40400064349e9f7055923711f2d54b3d92d71225a188e665d27c011253b924540d1c1a531158bbf2bbf0abfb72048f76c47968ff7a8d4b3626cb9b333d77ac5d91c4367b175fcb7a247e3e224d70e9777580b88ce903f080e0c8d5e8e938911ebcc7d434e20f14ee9436fff13e5e300263b1231c5a56b132e74dcc7750b50c8af3b85be0b4c0799e1e9803f14e1a27e59892a6de449ad284758d3e8083bd72d881643fc7e232460940ba826a2236cc33db9050d8971d64c450ef5438ff959f4ee16f0fd2ba9fb6eb6eff1dd8cf3afd8761be9e3b0344eb2aad413cf6df205bf74940c4c2ec7cfc4bb93143fbe1e74549f6f81e12e956841118a9a8fbae32ef8ce6c7de6ac3f26de906a57ea0016ff07169a38edb9b816786f5a376ad4a7d68bd34009ad8f41b9a97af29e07c65281bdecaebb71178be53ea5ab79ec1c40b868700dcbd88f9116d43a5fb181d3c39cd346f8adc9526c81c474331878589ab9cf598f9ee8726217cffd4baba6e44009480c7a82143afb9aec846f0fe0eafe38c0fb893666a9d2afe0116e05f704dd742b0a05f7e5babd11048503c0615ce0cccab707eda07735475b63d65191524242831ee2160a3e76107540ddadfe4da6ac8a313d91a17946349f367845dc262381b9c113d99ffe354ec489293b6561019de72222b656ca19d9afd25c94ee26b352e6005215d95c62613650e44402f11ff6d32ba45d2d93a68d9bb7727bb0ad9584540ac19902db583fa3f96948e4336da119211174f2ced0e072cddad13b21bec270eb71435fd727505c4e56354b72d8412c349c5943d902ee468061290849a878a50c521f986770965de203ec5fc0d0710b8b89ad850b5ee1cd530149c689e69f8b27c7f5b47f00ea6a45e25bd205fd66f6886470025939c5cc96b1f7a150972654070742906f4db3b5bdc8c2413cd93cdfc408fb6aa0a48d560bd4527247d31715592693bd6420db8532d64caf1b770e9a4e0a86527434c2219fd01f2605d0f500a8fc4a376378c572cf243c5c5b6734a1020690401c96f2d802cd82ceb7c1591004d6e0bda430ce96365e2c99b015798202eb19f878a386f72961c01728ccd00790b044f6775d47f7a219c9ae31600219c387ef844e334a96641b58009839148f41e9ec1ebc4644c6e5b48737ea9860ec988a5562dcf07d681d666d8d0c67ccfdc09fca0acc7703e6ffeb3b39180512b27060e062b6da4d4fd6d0390d1ae9e058ff90ce95bd399bb50a85af0f70cf" + } + }, + { + "CBC":{ + "key":"e25afd78bfb3eb642dfa8b833da9fc5ef570f061fae5802641c2c02589877c41", + "iv":"3fecfb1041b47dd3849294486eb169a4", + "plain":"e60a588f56a0a4a57494ccb84f78abfa55e61919df1258aca563174c193538f21d9cc847faf1fbb940e0366688b86e798d056bfc1069", + "encrypted":"9874b3eb859d15580bf6236dee5477d43fa0cd95c7b5fcbc78d407ba9228e7159909823e0d4f6ef7e8bc8cc88f52414e682f4e04281557791e9657c31e6f88ba" + } + }, + { + "CBC":{ + "key":"e9ffea7f57bc03046fc99af945eefa2c7ff47363853584a4f06df04e41598327", + "iv":"dacc64fb995f53f1296da484e008dd43", + "plain":"e1607ffa7a4094e66177a27a32200a10", + "encrypted":"606cf23382edb540113d82643e341e0ac5c1b3ce3166f3c914c9990c3b47daf6" + } + }, + { + "CBC":{ + "key":"80a5e8f2383735981c0e4cf6fd7068a97892032516f658ffeca98b2b79ff4a28", + "iv":"f91047f40010c895d3530f62a250a69e", + "plain":"b18724c207ab37a59c99eb973abf93d5dc044f82f8432529aa1ebe9d4d2ad66e", + "encrypted":"e682599d0ea0a5be9a42b70356f9665df534af77b2b5c5c5455215deaf56a9542837b0dd21b39d69f1612353e75ce8bb" + } + }, + { + "CBC":{ + "key":"a57ae689650dd6f289c677a5d85c4b33cee2acd04fe3e49aaf4f8fb135a0c4fa", + "iv":"f9baed7d4c7e4cda8229a826aec7d8ab", + "plain":"b1cd4c1857401c59aded3f5617742cb800d44780071089629cd49c39bb77f3aa4a51c36620f3915c16a9159840e42a81ec99b20e9bb08f5403ca5c21d5e0a33b6db40cc6b64d2d5318d64d089375f2491871e54774192c9688e54dfae85aa18d45e68e6de273bb9588628541497dddf82cad7c4dfaaa32b1ffd9bcd1e39d711bee448279c980f282593016a9752d0ebe22288807d0a9dad40146b026294712a7a8ad5bf9ff8602ac9888041348771d1737c93d7548abc6d998607fd354af52a8e9cec283e0b2889eb010a0036d9d7de3cfd9104d8b919882b7ce280ceaa5614ea7db1d4525a37b2deb891b29d6225deb0dccbed2bad4aa23ae1f43a58d0a783e", + "encrypted":"62983ca87ebafd621c3c686bbfba1b6a4df66a4a6005d825484c3c5ff3c7da053d034e96bdd94782d46d1547c020d4a4d5a7ddc8d1955d4ace3f9eb1a5fa9a6303c515f378778aa1dfcfc9363d3c3f1c8537522c21d542c185809253ecdcb2852129b7e1f4f04a796309e0a4ff52380d75325a59bd00889eba6599dd7c75a06a4bf65070d8aa69b639cb6dbe42bafbed255f961c3b36aaebfd8219a0dc450fc8ab343028a98e74c2fc6b52d460b82b477bc3569c67bf63103ef7f673e2b60bef682f7aa7fad7992bfe479d372dba1be4b894b6b14e904e61f4126d3235bfbe6c7b59975b6deb98ff7658e4aa63c588e0b42487625c1f8de289388b2ae2c020285eeb6dca55cf86bcdf72c46703a6b348" + } + }, + { + "CBC":{ + "key":"b258cc426ac396ce894b97bbe69623b22d6c556670500acc9d6511767e68bdf1", + "iv":"eacdc92f38096861298879a4adb11382", + "plain":"598566980749990c0c12e1bf3771c1f7", + "encrypted":"87a1f2e3cd10e687b530cd4bea9183f6d133874214ffc2bdc414c2dfad2d02de" + } + }, + { + "CBC":{ + "key":"fda2dced48467dcf75ddb40b07e22861be6d1f754ec2eb2bcf51eb2c65e07a2a", + "iv":"f0b08b6180ce3c92bc14f23b8e3942b6", + "plain":"1644dbc93d767d426b7215e196f57e9f", + "encrypted":"8e3497169f419eb72569007d94a05ea839d1422fe3c8370ed95bbcdcf18b8ed0" + } + }, + { + "CBC":{ + "key":"73b868798b2b5c76f02220ba7918d6fd15f1e87f3f56532ee3d23e588f5f26dc", + "iv":"d16466b85c5e62018ddd43703149af93", + "plain":"0de989c42a143ceb7140f6cf4a35d8053ed2da", + "encrypted":"d4f7f110dbbc22fa70889d57b3a83f77c39e84302880539e36a2ba20a0ee30d2" + } + }, + { + "CBC":{ + "key":"7e6fb73fa835be0c1b120d1ad192e4e364c473edeb3066d895c2ee533b3aec4d", + "iv":"81a8406dc8b541d0ed7877c1c3c599f5", + "plain":"0f", + "encrypted":"7c97e08d2ec6cd35e91ea0632444f647" + } + }, + { + "CBC":{ + "key":"ae3281edf54d1019d1816ce8542f4f3c925bf0f38eb39e4343c1c1a17e2a0b6f", + "iv":"b24b8bfe0082435332dc945adc01422e", + "plain":"ea0a4106addec63340a1a90b4af7751c18b9db40111ae30c1e6eff30f8c11864bb75483f3e989ce7e7ed0e68380b17086d7f841023b2a705de6d53ea5bf7fcd476a43bbf186c066d93d7fc28c9821e8dfbc325ab1bf932abcbab2c6da2c093c614fcdd120224643886a2ec2c2297344204a54270bfd64b737173d8574d3502100826f0102023301b0175e99a64fc68e70fc218aa7e99fa0d50b728da5a7bf9eabf22f82cd2fdf9b50c59d7276596825ab13e59401f9839e618dc6267d0502e7923992d9fbcc8a9f199e00a37452f51da50a8815b632696d43d14ad69bf7753c0cf4a139f1c18b5f81b6ca5676414a3ba66338f8c9ef00d018e64a3e4fbbdf6baf42e335af918cc05669887d257d28ebb9cf69949257ce06ed5a2f6ad5b2639f78050b3f334689465309d1a3b95b8da581db00a2c7e4cc5b182aedfc9272ba7496b215e61010e35d50888a44d9e16ccda7d76c10d610cf42488c5bc3ad92540c1f56a0f321522cc14308b6259d8239565e8a02604a0bc47c1072a1aad95cfc6d63aa00393c19b4ff2524ef40ca3425cfdc6ca721ffbeb1a6df27b7a90f4ca42f52d20233ef558a397b7af947ca4ebbe44293a2afae97ecf41feec3f726aff4973b89ee666c6125ae8ac605fc4fb253537221ee7c987dd59553ea5827a8834e5ff4e903b9dc01222415859b12f14cb6a4a4389d601dba1977b5aec7003802ccfcd12402f75c598149c926c110051a4ffa8c2f8122f639970b0d8df82c6f02da4e124bc58dab0cb22539f961ef95d991c13d7c620b2987dbbdf855f60a6cb83cce618bee81120dfc903d8f981496fe91af26ef9e4047bc572c350aa37d90125d73b987403c27ff7fcb262976752e2e2920b361f8102c99ccd618144d27e27dae8006a2abad120c3de2f3d8a15984a93e6c6ebe0fd044f3d2ba4dd59b0bd231bad5d706c740db148b4a44e30f0e36d24d9f1adfc309c7bc414520a11ff8db6efaec89faba4f821f0dbb04b2daa1bd00851ea17447ce51055dd3640f6cd2bd2bdf03c99dc9e9e5df8218e38c3d2241960f8896a7d01e53c6a282ee02fc9026844a2bd7be1f78932e87b67b168199430f2f530d8908fb27d3730e5c92d206fcc8f94d9f5a5ebd748347296c40c13a2a922c218f57f8f73e431837664b936231619ba4afb77e22d6c87c54291725d36ce19d9d3bbc5310bfd4c5f84e02357e1c95ccf04f8c01fb44c013ac139150f602a6e89fbbf0dd9b2ca247b53799cc287f38bc99901dbb242cf22f2199efdd5ce291f071928c3bcf7c5d1bec93821715d9f6bc912bdabffe9fce443f26cdcfa2cdd6103090053472420aac786ea980f0b5de12e8c985870db67d72a0684d5e4a01c777977db9a7e8137340e6b384e25bf9a28e20bc433bd07736916c0d3030f7dd6e6135b5da2081ff41e5ca68326c5cbdfc9e28c2ffab8cd97628a0c86898f535fae559f0e0894c7d4ad71e64558028adc78d7cc63e0b07cd0441880a52461e709407f1c0ca2b0b4ecaf8635eb4a482a996b64ccb063c9a177b238f4843414f27aaf959acf2afed1b4176e1af2f28850c85645c3cf3dc25bdf0c5897d0573229f9dfd75e6cacdb05baf52f7a8cb4f536af498cac0f130da724d63526995f2a57a348e00a347b97f0c356553e9174a651cc6c25a3f5c990a8223337c6cd0a6fc1d57efa8cc641eededc96b8736df66d0d88bb408d600114a59784ef4b54b9ba9f74007bcdbe40", + "encrypted":"6283ebc359455ef61c46b326e4cf644124b330551985d84b80850c11a375f836d41c7e0726d2442b65a5e8344b43616220f587a4f31a8ea1657db7d2227e497122f8d618d0c78056fd8d21df7ab7af0df0b2a113be687f2d9e3b644abe65ca4c2b207ee1770066ef77881de63da4c0f964fab1f6ac3fb2a028d652836e26530f0e1ee8856450cbb5b73048d0668df990dfa426001c748250a885e933f5ac045a099465a81c628a2128e2788ae40c825b017e8f112b7a08cfe6920731964c95890126abf75058ffd047ec2279adbefb139dd07aa54acc087ef522d76fab4c03b7e1d6d74526bdab6484f1b4203450a58624af0a5427a97c0502a22bfc014ccf8012e1ee914e7185161138fd8fa31397846e68f7061782e73a9e13c0cbe92867990ad085215984580e03ff44c6243b63ced284f6ed423315c9c5ae2783c73b297371df24e8aba02dfa1a4b91ed9b4f142901baa432abf02faef8f2e6846750b772fa989858f54fd2ab0afd53b52d27ddb06b350c68ca1baa6339e32fe10aa16623de855830d781e9f076ccadeed56953dc2fdde67d138185d387577ef301d1f4beea262f6d967d38dc4cbb7dde34b4250da7b50b25cd7f4ff1116e283e5610ca9a980dfc5f370397e620ae8041ceec409a7d4332e2d083673fb8aa7687b7fe76dd8ffc58f5e035e95bf0b330af8170bcf77e24e25b0c22fefaff187bcaf8de90754d851285aabc705ed19e2c2576a93b3587172486cc4c7e069d7362344cb0499e1a731247d65eba08748eab6ac6b8f78b6d4029fc25644ab53d5c6de3dae31fcf5002ac5812f2533c70448feb7acc53207e930f7f3165e79e3e3d0e0b9df3f18ef40b28f604189cb833ee57f331110fb81e37843243e7276aa86fdaa0206f8c579a1cb6c9c0bc505001aada2acb7fb96e7d8657d92e1da1f9d7f7b12ae929bed714383dd021e0dc4a87803c5ad6beda0003c304208600c5e51b2d3fe1947cdc5ef9ca4678d6a28be8002a991a6b3ea235a2f31fa4f77ae7337d82f1fccf89b92cfdf8b313b504cac3809be79c08f15804f2b855ab280e971a017773277b1c78ce97de2a85786ac88e534e181471b31f91ccbd4f47014c28d907a126c8f3d9cce61df5db0f9678d3ee6f3154b8ba6961edc8f64c17930408dfdf1f1f809e5a95d823636205d8a644359a15c1e672f7c5047709414e945d3230ad6baee9096904a79c92a77f76eccf2bb3aaaa7d532e4de3b559708d9c09b30a516035d34cf0e531bc9654e3c232ae0d19d54c823d0400ba786c0ae4efdb910054a7e3112324c0b1eedf8cf60790339ded8edc7856091fc5e262f53227d6199dee3bcda5774b07e6c50defd7865556d593dcf4508d94f6ad199c55d728f544f1c2e5bd5ea4c532181d4a186095110c9e07bffd5bce39270ebd5f4ab0c50a5789746960abd740b0857964519a4f31e70a7906a327bd447c90a1efbc36252039e7c091bbbfd07eace792356e4d5805bca4dacb397e06c1a96ba8d357622f072d78872f457de6281cd34e65d961a1587a99c8ce03f8a12aa03500341ff0ada72060bb15eb0208a587f25d2a56726cf36829b078cacfb96952f0f1bc763ee8261f56164c58cfd99663d01901024c9fda520fb976991163835a8bdb39df11033c98cf224058faffda3cfc96ce087b539217dc30467c62765fd4c991eaad1219dbfab43c1d70aad8cbed551d56c11819395b97309b1a97a9242ed941f831891e8a00dd1fcb3544241555dc" + } + }, + { + "CBC":{ + "key":"948b2fac2f12498a1c5f75227a85f22e17c716eebc9d9a40d24f625a0a9be132", + "iv":"b27681aa3a7a85f2d5d66b8ca76af960", + "plain":"74107a52f7ca99e7317e8923befdc61d115f31eca320a86448288696e4e67669a87a0c7b78f0dc25b3ff434186a9d9a3142ad9f5afd6", + "encrypted":"37db21d6b635e1fcacb300e089275d135153f6e5d43bd6db33b169a5cac663b9d473a2d7f4c96e1d6d84a65fc13b597635827d3ca66753264cafac09c5fe2e5b" + } + }, + { + "CBC":{ + "key":"fe9b0bef56f3b46869da00c4ea317faa464ba1d9dde4171478cc42066f238b49", + "iv":"d4effca58d43372e69e46628be59f751", + "plain":"7ad8a1338dedb42da25531933b23f107", + "encrypted":"c091548849da5310555fde4742b6b756f34ea9846c3942160d1513d047156285" + } + }, + { + "CBC":{ + "key":"de37b79bf943cc42f8a556ab8dd6f1f7210493c88b37e75cf0dbf05bffa6d7f6", + "iv":"28cf0e7fdc6a8bb75f3c449ec9dcf9bf", + "plain":"5714acafdd2928e14c8e16fc5493e8d62feca4b770ad07db683906fdb2523700", + "encrypted":"3f2d5acc07e6f15c493abd4df51f24ab47dcd12d22793659af5da7aa6cc07e9598768884432c131488f00deeeb0178f1" + } + }, + { + "CBC":{ + "key":"20e8943f401511e3de969fdea37adb920d4a24c94f7c5c4389f76617bda8767f", + "iv":"5db4ffd0d0cefe7e6bdb4b93e04ef4bf", + "plain":"decaf6335a76105e67dac38639e556194bdf62ee946d44995fd987231f21236b8973b9e67451af22bee9c5d1f101fc7e985c7ff48dc44a879f8cb1985f3f674730034b6df5f3d598b20ba8520bb7575764ffd84fe04ead8533327fabac7e661d9def4a2f501e658fd2f1a61de25f9f7ad057d67c051bee14e31d75e75be20830925621ccfb82f25bb8bec81f32c6ef02b6eecd4abcc02668ac314f6c68f8f6c4167a04c7ee1ca8fed7d875782d8c0385878f6db3349a0411ee43455878e6c1fb36a22c0bb71cbd2ada9ec8ffddb03f13b44914bddcf705baf774ba53a1ffd9d4a723372083798049a5a247483a7155aa6fc24575f49c85bb41fd7290c673b1af", + "encrypted":"a98fc1e764ee5b12a6c15c294d1efd67cc00d47e84f8e6dc01eb459f64edce07949fc44c8b887030001349feeb8068513509d1afee077d80cdefc9db28548be4ef022924b7e5284f0b6959accb37a1863b80eaf1ac699ae896fe319a41004caf4f61cd0c64e166ff054ffd25986f0e9da23e2c115e37b44a4fc6505afe26351ec1f1337b69171fc6d97b53454672d1721e8f944ad494a48a77b02f4eb2b4b043162f986f3781ad929e034b2ec5ac97ba8cb50b7bd7863ddc10a1e3fda6856890298f493ef0bf2a0f4cc49f905a5bfa861781f4ecde4ea736658d1d7da787eb96d49e82e31b596dce5707a2129f38e6b2526b7fde6a1fcab6f170aea5f0d2e794ed3c22ea1acfd738e7725cb462005ee1" + } + }, + { + "CBC":{ + "key":"0c711e76137a838f58047cddeb6717e6263d6e8550114678a10128920b6dc797", + "iv":"ececd9c26abc17368ceccbfd73099496", + "plain":"593288c389b8f3c1326ac715944c248f", + "encrypted":"3a51bbd69f1e7c4924c4fadd7d9f17b5454b52f4cdb5668e76498aebf9f116b8" + } + }, + { + "CBC":{ + "key":"8c012dc2b4bfa23c928972c998e32331ab5754285e765b8a09259ec8685080e2", + "iv":"13e7095016e20b7111d7db20541fe549", + "plain":"6efc1e9aa0b9c47b5c93dd8e89c6cc58", + "encrypted":"002e57af15b80ea67008bf23fb74a59bcf5763003ac4d2a817e4cb50d63dd597" + } + }, + { + "CBC":{ + "key":"32b3f1e7e74865f343784d62dce893e2442031356de008f39f4f6b5a78dcc5b1", + "iv":"7a198709bb9330ba721d5c6cf15f2bf1", + "plain":"19087ad51aa87246e08afdee86c1fb3f91828d", + "encrypted":"30b8dcc280e3891919402d8131aa9e636f874e47b1957d3e8ca5b1e7037b15bd" + } + }, + { + "CBC":{ + "key":"a7e3b901ab26c0c96d8e66141cdfbbbb15d25d434523a7e1cd53c812c8cacdcb", + "iv":"5ce2faac6f0d61400d1e32fb3047422a", + "plain":"07", + "encrypted":"70d6a238ff18cc5c7bc749c2d1837c54" + } + }, + { + "CBC":{ + "key":"92ca1ca8f4f583954561b4ddfb5d27b058cc1e0256ad4c4e288b71967fb776c4", + "iv":"7ad042071936c38d709c7251bc783788", + "plain":"17add250eda8762e998efeae9d8ad3fcdac8bb5e3ac83ad2182c21afb93d6437ae007e31c2e20bf86cc96460a588536f38c1a2783b459938922d22ae0ef757893fd3d3088b83d72845846d9da5a4d2663efabbe3f3db76d9f44855a13c8ba55eec19b31c1531c4ffe4178f60a578e5d8fc26f3e571e86ee69d3d3927a11df80bc73633415d26cf074caf0ec2b1b4d8a739614bf98bc4118bf8c45ae92ec2b39ef2ccb9f698f6e887f01e73a6f0ed7d24c91d22ef83ccc5156ba8b1466bd4e2e96071488fab3b72cc04a62dbce2b8af756f4143d54b8fd858a6bd90b1bdf11b7c3901e513dececa77ee57c9ea5eb1bbc3cfe53b38a7096012e3450fbf187115496a070cc8595dd00252a69c03a3529e31ebffd5d80a88ba821559e51a1f66c9c2c4a831a958ed388809d5fa357318d68bb022bb997166f0b1a6538c97f0a9d2a6893acec86e8a0e1c33aa900b83162a78262d1e78f0c6fb9c39984094c4c39f0417f81d7086899d1da8fb5bcd573776020f6571f94b0e76523bee69e0a338523f2b622dae3f62a76c120745aa04c01a61caedc635e125fbdf7ab3270fe053fcb3667a3b3a38f21955ad13d394c6bdc564cc5f3d28a5fb66e2c1172b47f9486a20422ec8df2198db424081c31a6da512a351415dccef63e228cf84339ed464708b55c730a326cd9b290e8f38663df79993d13e013eebbf51d95999643fb04d2419b7517de1ea860d626fa57249df743059fdf68b480de8afd6608ae51583acc3829aebb4f0ab54ec6e071fc1aac1f61b26cc46daec1bb00df94bc03023a012a265d900697c4505a1974ef5aa472f7ac5514b97408bc9f6968101011c51686e4003eb6935112b4847575338cc5afd53d5b3757fae6c354fa369af348204d64a26e9db406e22edbc9e137a475357a08ef7021daf866ed5cacaa9ec48972ce843c47c6bdef163fbea79d5bc59c358cf111181dea0eb37b91619b21b1e30016ed0c990205f75cddb9e0a5c18fc4ffedef153c163dc726236c96e288b16fa453975fd72cfad41fc7ef774f8fbb63ae97e9816d787fd738d28f11ca821a54d9f43d74e30b16f07a7df90910a0f52b38fa395489ce1e83d3b1f40908b5d768f12e762164156b3aa322842c9c64298136fe3c0186f5f4a310d4d08bb026ff11f35737edddd0b476c056cd2a67428081e052f5bdb4aac71ef55af1197a1e667269a4f9121910256356cd396307147d471197cf8805b9ce371221335a19041051cbb1ab855f1c8fbd85852737ad06713685230c36c25dcd9ad9df5f12d75ee3e8b0c76448f68cc8b683525bd23258c8586bb1251d8fbdbb7812fcbe637d9cb8e50d6386ad4b1d00c40f08074017ddd144085819094293b9da0fde9b60031968736eee9204a66eeb576956c5799a4334cd9e888a749f8ca08f712da571c1333f3c2a1319da8a620e2eda5438299c86a0f16f490f322e2e23b07990c9ffed17fb97bf1f55b76a63d271997753ba05df313745958835fb58a793e499c1a941a3bf5c2d1f1d76f5410ab81036b55ee20c80cd285fccdf9cadfbe99d884b9a0b76a5b17abb63c6e2060c43b022789f21d59ce3e7dbf26c07f091e4c3324124396bc1a025a6ac7a274e6f663328da14be5301abfcaf0ce08f3f15108eadf98dd11e468a4f69cbf12e011f3c6d42a016b6200cd6fe23568ea5e4aff1bb53d0d404a4ba584bb1e70a13a59c1c0e59a517932a5ba8dd38585b0c36e97", + "encrypted":"9f06e97dee3e8f454e10c35f894d988937f22078c230f441b8704cc6a75008ee6c1dd701009774b0da7c0065f7ffa6290cf32e0e5c5a17db34d9d79ee91a876f3566999b1f7adcdbdcac7cb9c188a91c8f4bac44837c0735bd6736e29b72ef59bd2b3b68593bd6b7b409f337785578d140287ea4ee343f762cbb8bf1ead2037462f029226704c63257c473912d8a454dec566daf425928f349b20b61dcf6b04153e26a1112bb0e0062d5cca77c678ed6081c2de5c2a52b3155859e333eba5e484b8865ccb2a5da09c6b6d4716564852b4b953310ecf043b4dd5dfc2f32d2917258fe5ce99d13323ddc92e101a284b47a3eeec8f6f0ffea76f3b5a80d8617f75a8635b631f6ebd4a402b862c92c220b53b1954dc9011fb19d7423c05c613855b4904c104ffce2ecab5cffb4cebad09eaf6bb53991b16992350e4d0ec7861bbc8b021a1219f404de0e71f0fc04cbf3d7a8ab2e4306792365b70440316ece6d4f93938f347dc384a4091a2854ab2f0f8a767814d82bd414876cf9260f7a4ea1502da7466ac789727356f81aba1ad75c911c47b34f94b8f83573f90d048a93fe1c84b789b844db99b371517718755c26e0e2dd8104e75d18aa483f4738e66e113db774783ae62234b0f78dd4ff9548d5df5c1f3e259345f1dc2e135c91f3a051136995f06baa7f13d4b50cab8b86bd0cc321bdb41a614f902ece447641c3ea8517bcfcb48ffb7dfe4ad0c583eb64ab27780d36bcc820d4c8dbf6530452afce51da990c89b3daf29d98eb347403ee44fcc25dc083fc0fc6498a0c029810edd6eb7c64f2cbdf59f077ac48aec7b827ded602a2b1321f9d443f7f6201167131ffcd71e67e833efc993e5b9dc07315bc739c8064d84df0580734e91548440079aa9b16b9521fac132fefbf0c834c55a651932429ef03a5246d11bd6fa239757f50598ae37a296b5d2a3ac9ad5d7116ed01111c89f03d91b09f289fde98d778a3fd4c18715b6a9fcc717354c43b3c33e79b2f858105571918d32cacaa0ca645e661bb98ad8018029fa6f28d03ca49ff445ea861f3ed54f8006cb5cfb993754565333b814974042c79de7b96bdea3cefbad1d66287f4ff799ba5a8302b79a9056d3f6e991c4483952c69da8010ef5780bca6d2d0df0e62a5110deca9a5ad08f0a6b382c9b70e76f284888cad6f8c227b91707c091eab8f2a91a45adefb1c0720d50b5ffe911af529960e3ebaf96d0142659a79b54b82c77f3218506a953bd3da06147d9d0601b1c0e85e64f81ab7045ac55dae1801a1c90e9791b5a837f7b5045d1c9a67df37535ce8359b5d6513c5a4730b3afaa02833dbdb5a09213750ffd9a1efe636e281a9f576af53c8d35124909a3722da32c0384b6d0982015a7c051b082e212d8411d3d5c482f1fc1af7128e25d0708cfc3685363703a290b9795ac1134a2b0131b3ee97b318c045f5d8a9da64ede148078211ce00b4afd2302173d3789b8c45e317dca7a7d6df8c619e1374f1de8e20c6495a604a5f39e5e613772da2e9834f88fe73156b2ab3aee4bfccc15e0c7dbcbb1eef5b9a778fa4aad149a2120895d9af0c050f67c453200a79b7b4c9042f6b7fcc1606e34fcd57de13809b49032112faab8af96fe5cda7ecb4e5022f5beb536dfbecea4be44d899ca8f1064ac1b5948668f9e10f08e325b391c73eb804592b3ff4a198be5aab0c7cd8e86f1497e0cb86b1055dbd86b5c657596d4674c0e77ad1b4af4e8eba67058b7080460855631c6b" + } + }, + { + "CBC":{ + "key":"3636c4084ade3a80717ab2a1732ffb65b07c0e48d91dd9fdb594b70a4cf2acb3", + "iv":"307bce8fc1d79c32c82f60f4f32e3b66", + "plain":"b087b99cbbd87ae4a788660fbd98fbe0960dfb28b6d1534e95fbd3d439f30be6e3da42962eed6652b49866cdfea304d5d4e30d0aabfd", + "encrypted":"f07b27043f69d734f931da8cd94034108d8011e7265efe57dc7287a81a78a3ea668c786dc069a9a8ff0d80f7a17fbf84241d22b2e72f41074efebd355c582763" + } + }, + { + "CBC":{ + "key":"581b357eb7e67af809b4c5fcaba677793361993ed5ecc8cb00ee3a0b1cd9de28", + "iv":"0b0391f722d0133a2875a0ab365aad4e", + "plain":"ae379e27d1a873920443be963db3ed40", + "encrypted":"51d1c27ad71e3b54a972fa590991c424ad0afbfd7ceb52df83c44b88a061566b" + } + }, + { + "CBC":{ + "key":"ea3c1f3cadbf7f28f5533e7d6ba0831e73e19dddf3ad31fd8235ea86eebb43f4", + "iv":"fa3ab751aca22105281fd22115736827", + "plain":"ac13d6f1fccdd90ef2cca62d9d11b32dd709d1a84aed73e0a6ecc8bb2056bd49", + "encrypted":"b168ecc9a11bc88932ca4b7f0d69c86efff834f2f87843420e34d4125c1493c9779fd4e027c2e1fa6046d316b9ea9d78" + } + }, + { + "CBC":{ + "key":"2533cdbc148d3a22848085fc067982e40dbfb24606da9910337a35f6b9a730d5", + "iv":"85cb5d91f4914547aa2a8fe5ee268781", + "plain":"0a797cd6e9a51793d9dc63caf6c12bfaaa4fda6e0482c27679e802f31ab1458c5af995dec1fe6cec70b4faa64e107563a910b401306a6d3f55a3daa834acfe30fa590cd81a89cded95a399edf6b31c047dbc1ade1caeeb7222bcf149d2a998eb2e66ba13491c0876f6af3044b767be52b7ca5e98aaa2e461a3e49c42cf3e29336f8dbc432572f6984479aa2528a5b10db44911ef3eb01f6b08662ca638a5bb0b22c6b4fe2b88eef687699690a83d00f3d921b8552835b3a3f0e714ad7239bc1d748fd6864d53b196fb30328a92fef8a3ef2b53c5f782aeb907ff0e05244d7ee7d4d62f401011ca191f3a3b138ba8db45c7cb698758dfdaa7e3fa2c0edf60b567", + "encrypted":"db1e1f98d4ea5d1e6b8efa20c3c86e530b3b7ae0ba7c5406c8937e053090f4b9d7d8db16cf7a00c0bca67b2122fa156c29b4532dd7da9a72906ea00d67ff531720c47457e3aee4f344755cb3e0b4db4ef1f5b834271b58952a716019b945c49fb0164254f5abe69d942ad15f4db96e2c1699d4e65eb04c7fb62fec5ab68164ace67937f14b409dd00c4f079b125c4a00ff35a382efb602fe9dca7879a172103a1130035499d98a75853174f2885b1e495a236bd58df3081e96260e6e0dd8e99ea6c58e63026ab6be225d8ea39aeb9c724cc94d496008857abbb06a72de2d2a74ce45c150225ba3896d8292fa2252e7848f6d5e6900b18c2b2e23b7aa499aabd6fa1b4553f32c1c026db09a0c7d9a48f6" + } + }, + { + "CBC":{ + "key":"2729ced2d5df84a785e0a5484d7ead519d8f2016a1d2496f0c393d713ed832ee", + "iv":"060833a2fc02911bf3c90db4a8c3e4a2", + "plain":"bed979711b3e2221753b8e59f689dfee", + "encrypted":"e656de368a993e189d7a95cdcd4d54a562810035ef3a66ad080cdbfda8cf8eb2" + } + }, + { + "CBC":{ + "key":"91de824e1f4f7c682a52590455ec4dbb6b4b7b5ec8c661ac3ff55902a42aebf3", + "iv":"32aaf2526c67cf92b62b0790e9438422", + "plain":"8265c5c9f45e44d6dc81b327832d65b5", + "encrypted":"4558f10390fa35a5e18ee09e4bb9508f4e4effc2c68a52e448f18d722909516b" + } + }, + { + "CBC":{ + "key":"032cfde5f5ce6ff7d791ce7c47e8dc2d24191f8b2eb9eb5f6169fb28af59886e", + "iv":"42a460f61538dbdace9c24c162eeab09", + "plain":"d1cfcdb070884e1111d219fec1202372fb4114", + "encrypted":"edb48bee5120103a14179c5c22f427ab506bbb9069c5b070029614fe727e55ac" + } + }, + { + "CBC":{ + "key":"86ecc62097e977e9f50858df3b4518fdd0f643f579bfbf37678b022d880cd37e", + "iv":"96dde166456ebcf7812c00b6f8dafc2c", + "plain":"84", + "encrypted":"88e5643113559c368345dbca97ecfab3" + } + }, + { + "CBC":{ + "key":"47b493869419e22314ab4ef3a0d763cad57d9c9f2220b05dbf84143ae3106cb6", + "iv":"9a053daa04fd588a24f286cec054391b", + "plain":"702b2d653b07a2b5b8b3bde89a5f1a68daf657892d3337178a1e357d201dcf5906ffb1d700648d92053518290f29039da4f46e2ab1046afe2431884743b68908501757ee1b74a9ccbaa950ae569c9c68366003e592c120d44285197a5fe6322013bc5e27887731a6c1031790909b72ccaf90e91df7ca06c8fe4fa9f16cdf6ac2ccaba37815be35fe8a076a363ceeb00c2ca2b98f94ea84cb4afb5ceb13b763e479fe14faced249bb854ea7a1428a00f1d3375d9688d1227f554bcf752708ab088c51e12b0bd0e432e50906c35183a90b48e1182f818b4d293f9834648c90d0e6ef9034ed5a53c35b38d33a5febc6e220c75b9e926f19f01beb7b774985156c5bae19458976fbc1eaac1fe17d4f0f12bcaca53609ad1cbe8a4bc35f5d0942dbebc70e0f8a22545265a56043b993aaca87751089efc8f8a5107105f97c26e0798e7e664c5ae5c0012cccd583dab7b4d38a2b5801c9a953c62b09a347b9e63b42bb40b513f076e06a6d9a751ee680cd212efa24476f8e26dd5965712ae7357d7c2f86b74d153e8dac745c3f629749998524ed9bd5eafe77451c9b285a94d5e9e70b7c69fcd9ef4cbf1b8576dfbfdf5466d87c06ae59411071816d06b86f6c3c9e1c97110547b5e8349715da7be28b2274ac4822802b76346fac35dad14a600f376d910bf51c119952d02a8ba4ed043dcaa5ff12df333cdff5a48cbd57c333bd968347dfc18008afdb940b24047fd8cbf93e7c87b3c0879570b5f7f758db8829fab9a3850f53ed502fecf268c87003c8d78493ff987531abc22233ccaefc64d7d324cecff22bc91c0a7bdc82416a43a2e14163749c8ecf6ecf15fc3efe0197eaafaeeeb029298d437eac6acd0a733ed12b6855e4a634cdea553ed1784531e75af4d0b4f794a92010a31af977f57ac21feddabf492116f3fb5b37daac707e8aa73cf81b011b56797158740a0f3b5f4ab3439bfc085d64ff7320f30225171d0ee959f02860da633ebea9032c55cf475afcc1752231d63c2c362b2b856eccaa1b60896b5fc81099902cc80c9d17eaadf885e4ceea16f570b738574eb591a3fe2f4b9fbdedc5c237a31ccd638190a4c4c4784b3e29b2b9445d84f70b2e1b5a83849670033069bdd8eb8469310aacce9d20ad0fe25192811d7fb2bbc1d2a1c629187228bd772ef3188f0b4b3f84e0e253558a88a90d7a90b25f70a458574a154de8eb1128cc404a89450804f90a706f7a901b9d02e89d4782ded32bf4dfee2174fb440f42d9780362bd3c37012920055349d5b3ff100a90613c05c4fc0863435c2fd38be9d91fb3b709c90caef44661fc915c4243b36eca48c77fa981d1c3c7a6f4eb863c2801b121fb45c75b45129a30f76ff4042e4d857cbdb49aca429d72efd13255755cede592c15fefd855c27b40e8de6dddb876f512b15f9603e796bf153fa1b2c8de8dd2f8885b9768030dc3dfcb90f59298d3554bd7c156bc7046cbf8554442196806f4d1f3bac16a3e35c05b1f1decafbdeb64f6e5fd2810b467158accaa2a2910ee887b183c98695b895877ca421281a11f8d9566ad422e505f4446b9dac94f26793749b3806cc4446f9e3d8a2b26fbaba0266610fabcbec7a7be928c74c7dce3e08e74f94c65a6c8c7ecc377128778229df2612e443a9ddd12cbd33044c89cea377712e83034eabfbf47200a2d39a03fe2d871e326b5219927d77bfc2564b8df654a7857180c0e8b163fb6883100936a6d", + "encrypted":"821347ade7af7d95e3457927ae6c0506b9769364145096e2b90540de9549bc1cd56f13cf870ee684e062060f6e84fab414d22c8c1a1943e1e15b6276fbb1bfc390f18863d0eed09c8c9cee2da481ce48b401d3608ba9a15ca659babf2c40d033e086892c64b4a2740e107c082a6ddd0a54e045cbcc34f42d4938a79ede7c2e6a2fde1cdad3d45d987d382827b851621b5385d6d3ddc0891552b592936047cc2d12cf3e14dfc735a0dd07492503b556984457a79e9bcef8f4f3feb854bb1862bcf300a52a5731a57769f53917ee60e8f313a58cc199459cd5fd3fdac3d6a74578779f1f6fba461d861fef4e66b3ed105e30c5f7d6288778f5fdf18941ebacb743614f3dae446ccac6df71bac7d4c3c2af7ee1d756a59ee85441a45d75797598cd4fa26dadd8a1420d90db05e50b8b3151947cf367332288b9d5098b15cd987959c8c11d53dee41870de5f229717966f8ee9761c516d34a441ee954a8119d029a1713ed0223424210a0160beac38b81f9d44f0c27347f911e504953cf3af6727ec962c52aece070a2074eb92d62f0a7576c83575aac1cdaaeaa4f5e57187d8789f5799af9a1408ba183e0359ce5dc4bb7a974070883ed662e23b87f2337020eebf1556731a5667dbfdecaeae465107f11eca99ee7a750298d346bd7beffbcb2c61f73ba831f63c8403f765a4ab8bc4033b61fdbc76b84022c8fdbb07bed6589873fe60e6ede9aa94d097de1677151a42bf9798f7079004dd111b24304424ccbfda27c0c5329bc6bf5f21745a24c9f56e274e3c3f18dd22ac00525cd302d74fb038939ec9b6fb801235b761fa435a550bea824dc8914117a06be7eed5c50a7cd49f12e4266acd17c624851321a256eaf66faffd9b6162f7894b8e6106eadb162e7e6fa4c4ff7f072a6c8a297c016eabba8eb696b643ee58519ea8e622a6ae4b54aba5e42547d9b54a8cde0e115ce695373a508989c84609b89e9a5543787d6f1f945d6324ad93b904f16a010dc362b7e925141bf2959e7394e5d66d8a0c3214ea2d91369099ad92ea76aab345bd0e2e28e43e98e86742d9892543447f70022a721084cfbf095d25dd72c9eaed3d06f9b3125def9788ad9cd6c390a7badd76781a93c2b7a5aa001c884bf3536e1f7320f92e7b4f1f95f67a0f45fd44cb285f71e4bf12faded9e2a7237acb4165a389fd7c9418b5834c7e24b39de2e4e2c3e2701d153501defb1327b808c8c6cd2c93f9c4d38cca362400119ed812ba4d705c5a047dab5a156b3630723d964983fa429acf6dc7641d99045eee8e7416bcc41556f3717e824bbe31c44876d43f4f1985362f5e9cb8b3d559ee3ef44692b9db678ed21721573c0b2a577dc51b52ee5f15a88576922347949d956d6aee4b4c6902659cc96bd17b357de354ba7dda3ee91207db8ba4d2e69bdac097ebe9d9ea4928cfec9d1e7d4ef1c05797c5c4ef1650266517e3e16a7543aa45673935c2849c888db1b3047620b32fcbebbc69fdb3ccfd46994acbdbdac988989798bcde47b34ea25c738f46fea068b733a70a653e24a444893a864e394e1c92b239aa16784a48a1ff5b7f707b6306358bf4e9a1c225f68443ecda9c505f833123968508fb5024cd6853a7f30831118b511f4a208cd249e9da5039b2d7ca2db6f842c5d125d0746387e190f16e22afc8825018eadcbb19a539d30b8aec61a9d785cdffd95c3637528d8e81a19ca31d038d72e001483f88b36778fb617e973f124e4e3e1eaba172702f14" + } + }, + { + "CBC":{ + "key":"02c4a4ff117ebdfa0a89ed0734dcdcf5eca2cb3a683c7cb00270d5561330093f", + "iv":"d2220e1263282d3b0bc802975394e6a0", + "plain":"7f2b184f412e8cc4c9a2a951281b1377236ff114ab0ab05af4716833f2e5acca7457a0b6eb577644838671101ef08026e360d8a1bed6", + "encrypted":"6086281cf7a87069f27c741957cbc653101c8bef737c183a2b900899432c8fb014cefde592172f5c4e56a10d8edce4b0603950fd517d7f6154f430e91fa23c0e" + } + }, + { + "CBC":{ + "key":"52122583ce1b3edba78bbf73eed8a19c9ef7f2a062c8a5712b07a5973c2781a1", + "iv":"4ac4dac2958175643f9729d2f43c5864", + "plain":"fa197c5701d854bafb62af7d025fdb90", + "encrypted":"d7f3bc21788542477317b19461736e5ff181b3627f69933363f325e03209d5b6" + } + }, + { + "CBC":{ + "key":"fe801d11c73329b1013f444cb67a4bb4ce15779d2dbe197bf1c5eebbbe1eef86", + "iv":"86dd2ac3e56fbf996c1be8246d8068f8", + "plain":"a6654c9c849fbaed4594e281f7a9700cd967f65b753546b2c316b669d4b21ca9", + "encrypted":"6ad7a827ffd0a148771eb2bfeefd6845a681322a22bf677167bcaa4e0b2b596c85f94fd9abc47daa33d799b72ca9336b" + } + }, + { + "CBC":{ + "key":"7ad9aef035ac8dd06daf53a23be449aaac856173e6c9a29ac7a9c72fef72dfa8", + "iv":"8f175ef595c9220b4ccecdd0e1f668d1", + "plain":"02d909f28acc89713ce724afc3cd1b2af14b8c35665701f98b6470500bee22809ad49f0e814e4c9eaec56e72a6e79b26cf42f2cf0eac527139ecb0dd836b10c016b26fe27f1fcff62fba4fd3422afe580b1311545724f1d97e71664dbd76cf44005ac0ca14ffd5a7f922a861dae0a0d30879b822b88df4945a7b421cff153d17add75868eaef9a47a98bc5a56ea3c5ed8a2293781a83f48a50a0cd4b5402a396d9c16107b642e69dab8f445b3825ab8ca23b22a1222187971c117882070a405c3ce19c697607e70cacba932752dfb5a295994588fc613503506e048ea6d299f74e19270efb18cf515bd815ae69b5d8aa8353190fe2df1759720d93d0cd59b2a1", + "encrypted":"d7fe9f1da90a12d6db05076bfcf8dac401da8e381be65d8047bca72368bbd043633f6477306e490b39e7956255195c7f577494ef9723b2c1b14eee4a78511fa8f0962b27bdd376fae35188190a7754b0878d5ac5ab0e8a0d0ff322bc8a5741c6593769c4b500241a0cde7c639a2df8a4cd310ddf7e47fe582b21068536e5e144029e544867e403e113b75186649a89e4e3c6eefab8913f5449f93452fd9e5188db1f3152d0929ea708ddc724875f5066e850a3e7febda60fc6092026ba67f7f1578a2568a89f7638eb5067e1cdb6dc9a3a8f7d4fd0e2ca1d4d7fd49661b185df3499bcbb25f83526422d10d42e158a0f43d70b21cb9d32bed8299e9ee80f038070aaa7497706870984d9fbf4476b8192" + } + }, + { + "CBC":{ + "key":"2853df449ffb9e74874f41278bd0f3fa7073eed0e4fe72f873341f51746fb39f", + "iv":"788d2aec1e5220b28210900af4c01a8c", + "plain":"62f00cc170e0a31b4f64b49f1eaa0ad2", + "encrypted":"0c93de935c4b27fb00d14ede90e6cb96c89b12c4efd2b6a65e4d1e183e850a63" + } + }, + { + "CBC":{ + "key":"d4ad329a92293eba5710c5f523806fd065bdc53f500322894513e0f084bd6f33", + "iv":"d6f2965a318e1ad56c55ca1534e9b366", + "plain":"5a4f83f1290c60075a4e1c327ee55387", + "encrypted":"7e4aca487d9174d5300691507956001cf34143c6604c5456575f4e4bca0ad0ad" + } + }, + { + "CBC":{ + "key":"881591dc0c4b7cd5ae84720f54219619dd71a9b17db4548d0dce16b684e2f586", + "iv":"0f63788a1f14716e8fbd6687f37df1f2", + "plain":"fd9a36bf274781489fa882a02985f67a2e905b", + "encrypted":"fe572ac96784b9c73ffe1624b757c6152b53f33b20d5a18515be88465198100e" + } + }, + { + "CBC":{ + "key":"a746da3e268eb000178410a3ee64be184e74b1aa86be6dbc97894aae32f59214", + "iv":"732b45717e8f75631573ec96fc897265", + "plain":"55", + "encrypted":"bb6fb305833f441bde220eb7d39f192e" + } + }, + { + "CBC":{ + "key":"cf234dd7d76e9197bd24a3817c856cf44e624feff166ce495b689a062743f4cc", + "iv":"a4bc987bb766daf028c22cc11b6b1047", + "plain":"52ec087c113038077303303f4495b10cfac1c89c5b0cf1eb731f2ecb06bb908392814aef7a725f4fe2cd82bdb5b1dc9944d945d7f6b24439388301e2ad89644cd297e7c5aef13e81669f2c3e77622d71b9ce886483179a24662d9d1b8b48b316ce30e95380a8cbb0c75c146addc69225bcd638ab1b814e2f52974f9ea53829d7b3df15e49b24d8aa6cd90636d809fa60c465101ef55d229818b61d6e5fb464b6136673d05f9852ca8b591bd09b210246a41bc1bc8c7808841c875b27684a2099f93dddcc157667781389ef5a64acfd013ada3c6ff4d5678372d1f8611d966e36903bb97b30e3a1b19dfb63be863f09ba865c2f8e8958bdd50f02df28b2879168c9efdef1af20fd76697680bf0c0b66a38ffce85e00f7c70c279007e6f5516d601ab481e7a543b9bd4bdbd1f5bbdae6ced6d04f12ea3c5f03177058e03c131a79c943eb23d89ff431b3a1093ac40dcad10ab0db4ea54ddc6ba59edcc9b20a5e6fc4412a7852e6906319a04340b8bad17ffe6d64654abdccbef5692837ff083b449786b90c0ab2794c0f67791f7ff19759dc94c642025b2124f5980bd9b70418900c5895e516f19f206004b2700302479a2b2c4c3bf1a1b60fd9d5e25c6adf7c132081e978d6d37f09ebfe1afc74c17c83d4c06776b283719c0ff925a9422a9a98be423aecba52846ec882fc8ed1f5f79f3dd3e882d85945e598bde066346b8232ac201df9762913c40e8bb58da6e12b2f1d84082381b38a81e7e847cd7169576c2ce50c48551fbe58ed670531448c217b1fd889a5336ad052bd4006cbf56bf47696f57047c7c0c6d72f64741df2021f21fb52370a6ebf78c854fe3695d95e496d21ef32b65ef3bb00f8ad4bcf7d6a2664a433cc97011602b96b5caf4ef740dbbeea16532546e59fb4395817d71aa39c0ad227a7f47ce4a6524b9aea7b662eb46825b629932cdb6cb3a917330d56368a6965391cb8819cffa7e3f1a35c0037451c9f13bee6250fdf0209765127f262dd06ccfc4133aaa97b97410bdb5dde5c0f00f009fd91bcd535984e82a2fa73cc5c0113cf0c217f99ea274d4abccd6def8a5baf5c13fa58c0072c6b02a7c629ab7faadc92335e68c96c2d54e11f93513d5c7952861590ab1eca89d789a65d037973359c615c64db470c8e501a48d52667c99b855cdb8792232361e6dac44044bcb9c6ff26f192c9d20b8df3f9e3ea792f8104c5ea95090d2386d26845a6391527f514fd65251d393d0f24d973d1f41cc44da21c0039580753fef0b30ac7abe547d7185900cfaa0401560b1c059886226afef8ab509973ed645b910fb4507bb9bb88d40ff925d5249f88c46ea94ae17c83d59fde6416f27ebd8f0b8be4c865666a527d1d25ee1e48c1851eff469fe07a176412d66c671c25be872c60e47ab46853b00ad61f149bf3f2ec1b9bd95338bb61507443c620836e5e45fd692cd694a9593cabe514eb8257becddb59d0987a639bfede48d04e11f8615917b6cf652328f5be466cffe0c8b18a66575c4399908f269a1b2b2152e787196540a58b47ba7e2953e6f0a9522d226fd80d238f8214ad3a081189ddf2bc071f0ab8a1a7e2a95cabee0a655f0efd641b18d9bb58bc40ed3ec61fe3e3956bf8caec7a5907735a1b120f83aec89e8047bc57fde80536bb5c81d4e01a85e9ce9b794923460f6fca5f1f45509009f6df4e0fc0b0a68e4eeebc3bbf77c20ed6317b783e0036f740fda4a52ca329bc", + "encrypted":"5449d17d127f934199fd297e799af39924b547e64db7a318f5000138f5e6102fdd914fd3929d71b1012b2f82eb3bc8fe32f712d5378a6c532f6a519a199dc1c390ed43088b2798f32d8315c47a8adb306ecce01f6f6124840c238fc4744a930a8b7ad7f3d89f4b52f93abf584ceda558caeabd0aca3ea699e57a33f5e6168382523e52d4823c92b339bb3f49d12334f6e949ff39b4eef121065a93fadd787f2dec5a911449b9010009bc156a5cabb644f0951f90d66a099f4bba18f7c5e93c208d8f9c4f2db0d149bfba36f4ce116540b1bbc5aa0086eaf706b848a0c3a851e293be7bbe1d3cc632dd5abb3c4145500a70148de41775addd107e5e026bbde4e65b0a07545062b6fad15d86b60377b26a655bea9fbe70f0be9969d8aacf30c06a3082693a3a619b94a442f832d3a1d46f9396200980fd858a191a9fd2653a76f50ec5aaf273c0cd38873a9f1903f3530d86e80eb56831f5cad947452f530acd22f53e3933c4ec07878a7a24dc6f9416dd94adc291781c7fac056af73a5a8dcca04f067d194983a2e5203c65962f1b155048829c1ab3d3cddd6b5395fbd2da01e9b34c2cf66fc05b435e25efba1087c6fa71e42c6bac474ca938373863543d62c06d105bc5d7dab10aa047a743392ec59b1ef8d1170691196bb64a5d8def72873d96b8bc86dd9c36e3caec0734410f94a0aa276fce07a59e1848b6b50635abba429aaa094fd6d574ae4a5d21f9b504590911869037243bce449f8a4c9f9b37bd2dd7d101945943c9050a5f945c12b7aaa6109a2387dc0f78b6e0eb0f6fd5940356f9ce0cb4ef6e2b6745d60d88c365f80592bde37eaf559af0cd9c826dbbe901069a3c2113b760a62572f5b2cf06f2f48bb2811d71cd23d73f629ae0ebe967977962b2fecc4bb230efec4360a36578d502a5c316fb984b1ff57cc4a0376255ee217e660f85921ebb7aff9015ab6ac81f13e31e30dfe9df54728bea11d41ba03f86e65e07ccff5a4d0396f05b5ce4b0cd381f8a6a6e7829bfea0c4a34c6d17dbe88b8c9a2ae073bfa77fc527a17024fe9e90d8a822f8ba43c91a862b1b9a68e22e01ebd1ac558e71d9be19792c16f0c96d0d9652220167f681c71d3ae80be508a38d29feb05bdea6f0714a39a18e86e1ca3009a7af2d28f7734939d0e1c4ffd9e551c41f543bd107b71ebd45234eb1926c771ad3f36af6293502da44f259e22a725837ef4aaeda0f28634f984e2ab17e10ec3de2751bb428ee7ffb7f225835e5df97210672e33914c235f2732defb83e2acb494bad2c17ffa43476da542b92acaa238e99a6f7780824fa3f2eb53341255233555735473e6dcd2a80dccc0761992efec9c306dcdac293f8855eaaf091b84e68f98c033e7e9198cfd2900d74248c2d3036a88ff93e42a26c992b2bab37433b28b00fb5cf998342ba57605a45abbe2b7cd917d9fb437a319a5198e914e5e6e1e429f546b1015373b9f9623e29204b72cb1b1e00eb9b5fca93289c20c37cb44c35df6b579700d28c21adbfcd61d25057cc3d4b6dd2a7fedcf20f716676615d32f403470a3d0f77908860d30db66a705ad4ce3f6e482b7998f0823d6a818123f82250ef393eb075df810e7704cd5af0eee572a105fc8af4a4e4a0b28a823a689707030fbf4e876a0ac357d38c2d8a6645f7d7032a215897494a9c82ff4df86ff5b4a51b467053623c2a1d329e62f5265d7ad40d4518e2025174d8b794f74630775ccdc8e0025ac6a691309fac0babb3f9d" + } + }, + { + "CBC":{ + "key":"77c0caec364df182800826e27442c9d135c4c8ae77c6fa90b2c4f04d2c64cabd", + "iv":"2149e6f626ef42abdf81d72b087ec8aa", + "plain":"1bc61690baafdc61d14f7f49fc70819e5887e82c2b14198805d124f31225d8393b2ab49dce5ce6696fc2550f8f442e88a891d90942bf", + "encrypted":"df829430e56cc5be0867638f63af8c8df48c44275cfb4dc90d31f6fd048efc7ef88464fbef1356c7f3bd6575780af4a361c3d78380d2da3b899b59e38e84e22d" + } + }, + { + "CBC":{ + "key":"ec25f9f780dd27da2b6c1b382ac7b51332ac17c8509ab856bbecd71cc683cbd1", + "iv":"59d85b3d93421a99f71de2452e447f98", + "plain":"2990501223f5f31ae4b7afcf2a9bc0e9", + "encrypted":"b4c55517a0b8dd0d3072d614d3e0e2957435f9a8fe5efb6476ef53a6eab6dfee" + } + }, + { + "CBC":{ + "key":"52d83152ab5fcd7bb274bb19e3067f2ff365506943771bd446569ec0cbff22b5", + "iv":"19c41cb7dbe6e5be3419930851a01010", + "plain":"9fcacc65094ac10ab581eaf67149f12178a16934021efcf0e29381928f087cff", + "encrypted":"252a05f38b387f961f368faa890958086f026da2f632ef71ede33971bff72d2c5cfac1cea8c9e05b9773c0fbe667ba5c" + } + }, + { + "CBC":{ + "key":"0fe6b7fa0aa3b0006104e1c48eacd8d4e61c5697e91cf8ddd49ca0895e1dfd62", + "iv":"d90e69cc30b853714cd2d548d5f00243", + "plain":"edcc59f2b8640626de3a2668b7f62da5f45983f9459c0b46908e1a879c6218e1679c2b82f150cf770c89e7cf815dbf5737cce14537c2659b2cbe296329cda02ef29ef254c2a0e6ba4b6c2bc7cb45d3edf946e3a03c760cb8efc1241c82587ef473f6b4cc2d4479490464639106e9f374f4cd5228a41198b045a5a7184d4e38c104d325b87eec0900596bd516417012b1d88a74468b509344c94a7f4c43a208530a9e007f09df78a0408f8ee07bda24954db0cd39c65016673a684a147fbcad04f62b6d6df0c99117a5e930cb861acbc7df355f5c1becc53e5e8687e36ad29e363959ae2806f7dc374dc323aa69a083d781586954d892c165c53b4572c38aa049", + "encrypted":"3d6e8c4288d9dc0a575f674ce6aa21c172dda3be43b72d547c80f5a91bfdef118d54ffa28256102955be9e116524dc9dd59470f7e5bd895b8fcfe8c9b71ed68fb71a596d2e5ac7ad519ce947eb95eff9f0ad9c62313ed78da72761297262da0882da3468cb54abf8dfca85a4bf971fce9adebbb0b608e2d0ab3d3ee95947d4c4bfcbf93ef68acf6f4f8a9e36de9e660a326f87d1ddd9f8ee667cd3db551737d09840b4eca48fa9513652aba723fe5a197378a0e6f59e61ed9bba5157d5cd5b12ead5b2ffa1d82f50bfee80dd5c6bee56f723bc82515b9e8963ae8bd2c3bdffc618edec77f928183bb3117548dfcb81e5507363985dd56494bd16b46c94f78853eeb43d47924e8bf7e6a0d8e89fc50e09" + } + }, + { + "CBC":{ + "key":"bde811c963e405fe9e5411da5085ae1b53bd30fa1a5d2d2666868fb128740c78", + "iv":"cca7e38db8bee2151106b378f6efde99", + "plain":"cebd5f85a03e58acccfa58523192805b", + "encrypted":"81c73030d239bc7b35750dba70ae20e9db9b6bc9bc77808a30a9fd00ca3aaa4d" + } + }, + { + "CBC":{ + "key":"5a5b29746e31408bd6198f5a33f78842076cc0c16051e338603fd6b6f09ebd86", + "iv":"36e0b5a91686a41103fcb20c60e4c557", + "plain":"152e4daa561560dafe79abc8c0822633", + "encrypted":"854642d5bd70d6679928450bab3c828ac9111efb3b44cec38dc780354b67d411" + } + }, + { + "CBC":{ + "key":"d4290ceab95f3b1efad3dafb68ea5bb7e6a4358d90d9dc837d794c6509af9451", + "iv":"6e3d20b7b4efe9583d4c69373e4b6a0f", + "plain":"b9269aa9c798f1974fadb5bdd3ab87e62db7ec", + "encrypted":"06bed2fc58598d0e8e2236502de6bc0676dc878e07bfe1feac0ff1656e111aac" + } + }, + { + "CBC":{ + "key":"bb4fd36475f3c3e4d3195fb35dab49e85f94a1a0fed5ca5f4f37e1ee5128f533", + "iv":"26d57cda2d871e3819222f942fa81f8c", + "plain":"73", + "encrypted":"dfe65fa1d1f94f31b5a45ee6e35695f7" + } + }, + { + "CBC":{ + "key":"264105518d5d14474268652f02eea7a8f10c123e406526081bbfacb84c517282", + "iv":"02430dbb0c6074770c94a4df9796d5ea", + "plain":"23554232c22fa1e2e5a238465b704e01b8576325c7bef5f343401787d9064cf2422f8316ae18c640583524fda499f4fe35b4cc5459707d01c377d347eadf0647ffc52dabdf8a06899ce26466ad94cf6dc06b8ceb65e454abde56e912a5013ccb701125f9f8f042f4b7779550d108e0b5d1d1d748bb1c0be773460a850025344ded6d82a255648c1e7f66108089b7c756f3cdc7dd6c745b21eea7a577faf7b3c6da02808b64f3eedc719847048375b3f9a925458c41830ac197d5e531a085051a21a17073679ad2c2e32d606f22237243f074ee5cb701b17e8df46769d108c8bc8dec33ef34ff9833447b8fd95f40b68d3cba498d52d13a7f873b1b28edaf56b01d8cc36ff603db5974a804a3cad7cbf9fb7fadcc537ab100661f92cbc7fff59d0352118dd836404861de9664bbeafdb4a007295775a93aa4a5455666d214e5820398215378aaac9c52cb58ececb96cfe2f58acc53d712a604f6a042b11aebe0be5d47b3cc28cb555ecc3f10819ab4d535a54213e97a8eb57b98d6cc1bce22a7fea402c918157a24f7fc544e306ed406492dcd9da47ce0369e79b078af904d7b1906a3a0cd0a8bb625088f9360127149d02bd6f123548267364fb6920c19c02653d8d39e6b2c324ec6436956eedc0cbaa912d1e996aaf32f3b47b9903989152351834227890a2e0ded08327d632200b7312a8e089b03f115ccc61dffa4b0b5846104e8cb33c27f4351b160ae0cca6359fdcb5aa0a6d651b7e66d3463dc63eafdd9186717c0b6a2c81ae3c3b2a44c8abcb6cfd80c8ab33626f12d313b9abbde4084bed1787cc0718dd5de615baaa60961cc952b86721616da2736459e8ce46706fd112e8f977614479e0dd60a08cbc949ac3c06bb42fa5592ea4e836bcde4d68de801c90de43bad26c8ddfa10f73df501d8a0774c5c495c896937d44956a4e86e0d010ddbfc4866dee105ad5619c2e7e958d94a696aa1459db90b7d739c4fc031582dc1d361fae828237b366ca7f08e9437f8e180ec69189419f38dc01ddbc16e1efe7bdd7a002478e2aa239a8e837fabee980a6b83132168a962ae00c8d295d3ea24ece10a2e812678f1e51884f6af3a3b35c0e07c0569d79b36f12cba39fdd257a17d8d9225be81a0bad7d87cc4e683863ca8c3fe313a34c03f8a96535ff5fc8fe6c5fd299067ee15203846395338601a58e548a7c58d95f1d3b901189a5e83d6eca6efbb7d7ce413e7a008cd045837e90a6c3badfc9810b56fd95fd0fbdcdc14538176bdb427eae53887de375d4e9ae5f50e47cef4089195b2176ba530934f9352e347e65cb1206ef0b558a00a5444fc6cb46dedaf92d82a9940c4e09bc3c979c0c2045674fb0345bd04f194dedee66dc8ca6ec6e6339f4b22ac3de07bac6b2772a37982bc4addf9c87d778034dd8fcb11902feddf76784aa4bf748c81b0a934f94f8cbb2311a7f560994f336c877fddbaf9c3bedb81eebd7b6e2e1ebced43106b638fee3e8dd314868f805ca34ca53885e08660f9f95a02677a1f0a0284bec64a1ee8301b10deee1650f4395c007d9cd01d06022f817c2760760aec11af8e778593875cbc7acd2dd5bbb08dbd09fab34995b1350d7e3a47180795cbc739e82ea316b96c0579419035883f547ab971199a8115a4034b087cede2e5cd6e5a4c7449a8d210dcb573f4700da6b32138c4edc8242cdc42277668c9502fe7fa4d08ed798186cdfe8f537f5cc18cff05d2b88e09a", + "encrypted":"27771814c9a55b16092026605446d8d3b804ed7680fb1ba2ce538e7ed3a09def3820d3ea39abace65586e04b0b6e9bb47a1f0b6db3670d47d6ed593f92080959b5e8385c78bbbf8ea6ad7c77f5757cb49183abbf8cca032a14561b9a5821d4dc951c9810a66978bfba8e22bada6f196936f4f459842d589ef5a84949dec8ed22c45e16f93d12cb60c54507cfe493ebcb19e8c56c0f3c421c2a790ce7e28af597e5c624ea451701f44b4c33c66d2dbe41d082ed7b026c8b78a9fd13efbc892e38f9474aeba1d88132d484d73f88e579342b96b5879e492237e466c9f0ea31a419ba771ffd2d02d735ad07da3a0df13055510f1126092f62b0d8896040c1e336dff01f99441a0e601b7b26bb599ea63feaea534e10f0b34aef48abb5f79556c86efc9abb4fa5579f1a38f19de913c88c33d870d882506a29e46fe7896d20b9ae8b3f1922f56722a2f806065d4568714627ee7922e0bf6f4057ac29f807040456bc950157022af42cf0948b6b3eb9eb643bb8eb103486d127f182e9bbdda2d741c2de192f6fd0c5b27c283bb7c48a56c9fd3d60409a825cd60af2f047b68cea1a5ebc0a6250ba9996d55caed3f7070c5f23b51ae7d892db7277f102344b35b61837a061e10898e6d502b5c202658da4b062a3be7c1d9b4514f18ab70f777707832abfaba4bb645d3231f6c70da3a240f4bedcd3dc0d41495acd8c24f6b8b7c84aa53ec57e00c25b5123c875512434d8c7471cc22d4b53bb374cf327a67fb7a48a6592ac8a53610b119f6d7af29a98e3a690bfd69f4d1cd6f61f313ff5edf1ff18734de5be98fcc373d7d639d6993b227b180167b65cd5e477523103bfa8e4a6e5d928dab5448358659dd362877b1a1469ab8bbef6d291e7ca582f36d93d4a45a4e204976827fe5c02a04ebe14e01a5ad67e089fdbc81090774171f979edd6c0be4584c5abcc2fdc485338e34386aa42df6eb838bfd156f980da4d4ff972e0fdbdb36d7ff6bfc4b0c470cb1c79ffa079623c3a3b2e496cc83bae0e180730df20db9fe6a898e5b73c7324411759058750ae8ff58cdb876d8b5a7e99464179fe4dea307fb7770c7e043383647bacbbe773d2316a5efac48ca81f13df7d64d845937e49fe6270d67fbdeef80ea256172e46d426282e9daede73c1a3e7e90f309201f72d48f075589d8c58267281690d737ad1b654e6ae7f4c5204a877fc7a844b71687b94301d378db42e49e9f4e0f07f13350901c3c1203176ad9ff5d62ab022e64ae0c9bf7c11ea650d8e7aa1527f124ce293af2e781aed8b4369f0102543c5cbff6319a7a614fdcf037be4a01f7719ad70f5344f86f0fa16c8ed236ed19b638899d8739da5a26df2da803a03eed4276128730fc805fa4a2d0a4371b87f39a4d8d79007d6b405c3cd8b87d52f958f98821056171bb9670e7f3ea3210a48c07e00cc02c88e726cb2884eac905dd7c6b27a53132f2fbab7594c567011cc3c25f6dac10f86b0000d078c06ca1bc6482fd788b19cce17ccb587591b9386bef8cd1003e83955606f88309b6564f9e5883de19e474118139b592a4fc14371a1b2532a2336b5bf5cbfb44e03b60a76049e17713408b193305ee510e055ff8bd7befec115ff4913f9138ee31db95e5fa6b1a291d555a3652a4786a438b1088da4125052cf3e634265e4bb38ee24e24c32a1ca81ceb76971422b3362f73b06adfdbf78a5fc691cf9ede5c9ca1c9dba98bbe0a289c420e28f23af53842150412998916470bcb6ef" + } + }, + { + "CBC":{ + "key":"2714f3fad632794a1d0410065c57c71addd6bdda9697f4cba043d884e2a47504", + "iv":"91e784e5ed6edf7a378ba5e96549b286", + "plain":"4e9c17aa6e425f846067d229233dd8604ddbf64d1385fef2cf2a92ba1d52b0d67887001b9f3b522f7a511de344ba4476fc09e80deeb8", + "encrypted":"c74f200c15d6191b9ab39d46443c7d7b6acdb199e945048414ba01e1878329709b8b3a0d144bcfdb62f801e2da6baad8ecdc6ce753ae5e7068307304b646430c" + } + }, + { + "CBC":{ + "key":"1cefbf29cec6e528e0b74e47f330efaf457c18611b5bb626c8d106965611e655", + "iv":"09ed6971f243f0329c62d6c682912c75", + "plain":"c1587bed5f718e880b773e83a28f7f1c", + "encrypted":"dc460f6f21f19295dd90155930562f74fd511de4099f35d22a9895b281b4007e" + } + }, + { + "CBC":{ + "key":"95019355e67a6e93bc14db40558d09c07ac4cc0e1e7ef3ffc11c7289ca3f45d7", + "iv":"fa169610bc8e8b0dea20146f074eaf7d", + "plain":"7c10f3eb1d31a3efa0ac0a57f898a6d309489f682e4339a43e9fe8ec745492d3", + "encrypted":"e16f04df9a6d1897f388db3db7b49e95c3e4de549c4922b291a8ffe6d2eeb60b517269c899f762f4ef0689a7445d968c" + } + }, + { + "CBC":{ + "key":"f31276a6b46e304e102df60c2624f9d67488f305763c66c8da68f8896770c375", + "iv":"d2277fc5dc422a86c53e66a8b8155f56", + "plain":"4b6d37dbe60da813bff114d39cd2ad538593812f0e91708a391adf70b26245e5bdd1da64983122188cd0aaed64a879778492f2317ae97b7ef230908168e214675b18ad03d88c80e456dbde1810cb80a425d4019a9730bc1093864a60045e2a2b51778674f08165bb0fcf810233e8650b45dee83e1b4079d6f28e804ad5ea393f190c02e9f9b086a7716b52b399b44c3e8a598a6afe9f71239f30e3f0efbf9bf0d4073c7bd682437a8ab05024c96e9d74647b9494fed1a2d0e6d027f427d23622210f2aa4b3be3f7c2b0d5edb365722583405b422bbfefdbcfb2e38cb54b2869a482acedbe1309d10a409b0d9bf8c7c990aabf0e68148e2097d5d081868a4a1ec", + "encrypted":"09f4cea8b59d6f2ac4da2b8a4e1ba297874cecb1efacce82efbe7687e734721e0d22a372fb75283b02457d0a972ac9a6ebad85b71ca1587dff162f7d5e758127d09ccf0d121d363a99528e80caf4491e5e09257f3b504e08943f2a804f683dbfe6a3cb59a69fbc72a414055075bc29baa1e1d07f7428efb5242e5762d69b87fd3e9dc55c5af02890eb032237e8699c22e7510494c40962a5adfda34d587fd0cf25f1f64409822f303ad56a9bbc604e82fbdf6494cfae57bccb8d7c02eff85b61e05cd1e7e8752e7774577fa5a138d92e827421cb140f24ba9a30e7156d7166057969f2bede1a9deefbfd98faa902902993e2b81d29271924ecd8de948de22befe49588fe52f750f9e81ae22025902a67" + } + }, + { + "GCM":{ + "key":"16504a70d5be7fe7ad13be4d52169d93", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"73e9f5c909caa13a3f5704dd", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"121638a6d635a223cbeca3ed", + "authTag":"fde9354659d5bf47e6d4aa364ac2c748" + } + }, + { + "GCM":{ + "key":"9287ce08f8cfe07550960a3014a03050", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"8faa2f0ad176fe98a165dd21", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"9dc60e4ca14b472b5612865a", + "authTag":"895de786fbbc39b8ad28ebedc20e2993" + } + }, + { + "GCM":{ + "key":"c618de38474d39d9155258e270e98938", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"6c85b77fa72750dd18c02869c5dbe04b84a06f", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"6d6d967dffe5a9533c06c53fbac64dd727f335", + "authTag":"828c9dca7fb3f1d4a0d6063000a79a92" + } + }, + { + "GCM":{ + "key":"eb622e93cb17bf656f4c2110dd6a5981", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"7b", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"de", + "authTag":"2f85b1a6c06a3ddf98b8475261267b63" + } + }, + { + "GCM":{ + "key":"a33e80106caa6611a9edbb7755e59cef", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"9605c567c91f3efca91d9953d8ca89e60e5d2792b6df5f728626d8e3d97b5bad5ac0905728ac25e410e24c84cf74fd6e238490ffe684e445bd3060c2e021e767e895584c34ad70ffb598c17e6f857e3ece5fbfc2d6301806bdbe456775b239c9d741340d87a97c24a109aaaab10d18d0b64035f531b820a47319ccc2e259641a13669f1aec6e6cd1f0cee1b2a0b6be168a66d1bc13c922141162b68bf3229aa4aba5593d842eaa1c893a54d9cac8fda05409f3498c0683e8467347c3795bcff2f90e27a888ecdee1b69d566ee010cb36f9cbf353b5fa0582ecedfc4fe7564493f900e0e6225e6498e249f439e65dd3a88250700a5a62127d340b4dd3d2d270ed3e208412c1a9392825c8b8f2f87260cf7e58ec782d7d324db2b39aa78a3d5204a618722d085a9e4327eb2e53ab6265ab49b2e2b58c617a1727b77bb97e614cd18708cf60c8fff7cb733f2e5519e83d5fcccd85690a8276bfa1c3a2145875a7a683ab0fd60ef8761b0c3bff876367516d766267825221ae0f46a32f42389936b403919bc66798bcd4cbe9b539344976554386a3a8b4312d0d0f62377c11f3f8240854af8517b42b3fc774235f5f28e7f0debf81b03358c74f140d05a35b33eff116ad05d49aca8a5554fb3d848c9f73af4eea5a4418c12db66d91570dbf2a85ae58538a79e6952437b2b68c5fd73a6cae8769ca8ec5dd1886dfc2e7e716c6275a5899ba8747b2c99511499d3ecb2ffb175aad53168b083b31f6694ef46e95bcdc465061af4187e835130146f7e1ff125cba07011cb1e11913ecf2b3241df9963b080bcb9464f90ea64baef4c3ec3f021c069cf9dbb734a802d69b92faaf60b4d9ab6a4a1a1eb9f56a9c67767a8e58a072aadaf260fec58642d8156ebf44edf365743d01b61467c06280dfde0afe0298e63ed8c211e719bc69897d97230d4d655c64fec5505e17d28a6f34f4b1bb1cd3ea0ea32b414dde415e19c4950b72db601c5c69afe8208643bd0b1ea1d28e4e17b49a95a9c60153c7facad7c9f3990c9dc33afff1ca3f90c444fab3fc55590f4a8cae123d30d4471381461c9e07c87e058dbad2194844b709cff1d42cbc25a98634861ea1f609e522cc7ffaf7512acc4f08c5a97f045febc400ab3770b0dc3a5a63d0680198c5e4bb9e108141218650ede186c1dccad58f4c27f52d22cd47b17727b3be0a01509097acb3a1f4d2efdd6877e25b3adcf2b3cc1fa29915556287b421b5f05c218b4f7fec069cf02ac06ea1c826ba771e73ee6cdfae4bd6bdabb944840cb48da19156a21007fb69072842244421f5202d8b222c3444327bed70c5c905464315b232b6246ad97286a7c21aa28e5900ff5a7d98705fa9278677effc8d985f3565849a2cad7e6b4d498ff900b487a3606a9272c5d779dd4123cbc9d5236f82be00c88b1f9e2966c770a019a2f496a89b819c31d43f5a619166b4809170e6304cd27fa1f4142f62a27806893e83b250891e4d09329d01fc9d85d00bb4af6902babf9a3a3dfa8c5e31e7ee042c79ce5833cf967c39790033cdf7f74e02c14f0fc721a3734a7a974a73709e342883cd1c72221b7ed15d56ad505dd32582c5a8dd539087778b338def87b0841f9c17a9f1b660710dfb2f223055a88f17e100ba64d531eaea6afea2ac754f43f4944951bdb7f3f9dba455fd19e19f891081851a02bbd73d89b67d2f5cdc948a479f98a08473319c6aa721cf08318fc8d682d4cadf98", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"07bbb76a561d495af2765ce11ba72b5a32f53e6a9b05f692f2b4519011eb0b6bbb91b71900b75e3b14cf8f0bf5af999999f9044279b4aface2539e59a0a8667e45bd0f8a8c221f885759a86c9d8c73feed5b24b9a722f00d9cf9f95f436106927b6621e6ceb5941b085016d10d19cbf941b45f3c4156084fc317faaa237f930a047dc1dab4ce7a26de759e53a35d8d842f6f21e7f2eb9473a14ca55481a30d93fde8f06d8160d46800b8cbe2ee5efb22e9ceb7168cd05c52251ad49f5caa7aed8e8cf915c0cda9a1f7e2aa48a09ea1efaf97437069ad2b00a68f31fc10f5741c81e75fbd264f5243615eefdefb1cf265720fe5a9a2ca9c4e8046242201c891b9950ee076834bd039c825f8caaa38e479c4c91a2d52eaf926faa4e68ba595fdf562b7a2ef56c9f65f18a5d7de331f63901ba45f219c0135feef652c8a00401c4b630df244fc5a302f69dd36218acdea089179d43629895845446f30d81f1fec0217cf7cc54c3311d08e235936ea37f85897cb8b66ddc21fe423e4d30e1ac11d5dbf47235fd3dae899486dbc4d3fae5850d8e90242ba66925347ff7c83ce979ad97ae84ca2fe6b8054e6e83b838f9ead6e9744ef7f50d899f0196c7ce60417d6d32b88495e2139c6c462791d306eb74eb49041612894a83db95933039266a0233ec3e29f69347f62215298f5ff2ac4633d839d1a04fbd9194089bad0a78353a7bdcef746f12c42fe10fbe4aa69464aff2f67cfc613d3b8a3d088c64e34d512b2e22ca1e538063ba110fcf1250afa69462e6d3062f446cd04e4c21f40125da0234a126aa6fc7bfeb9fb812d9b69d3805bc9e688294c8d72fd98fb940836d9095ff7b2063918c641153477a1a12b48c7756071f1c35544f964045aedb97ea50196c2e567e0381bacc4198d97339650ec07a2b6f4d1eba71bbf31c5860b4e0610793a9d78ecc43f6289c874d47e5007f55f71c1269edb7bc5174df46aef421e27be81dd40b790996ac6c0989124a053e0a96427178ef57f42359eeb2b5009e220c9f54d8c9a2c29da4a7dcc768e6d7b14cad91fdbcef979078a1bd158b542ca21e38fe2a8c95e953d2bda021d42effe2d73bb1ec76e135a89db1d796ddcd4afea240660a9970a81ae637b793e1c6d7c79a8e02cdee993e92bd8f8229c8f0050c055b8f871275bf6d102abbbf817e548aa171e3a9a80e029910a591fe029f0cffcc56f1106dae59a87b0ec5fdbbaf114028a348a5c26a4fff960ceface48f015735575b64af6423b8c19bdbde88f2250cf8f1212d024fc985c041fbe5230586caa6075a886292ed516108a2248130759327caac585fd51392c94b5da88a6d921cccfb0181f5c0f0f6a4398b531519b3bed4290d4c5ba20f1c448147bc9a220550c662a940a991acd28b420e410a2e4bf144d002dc0ef2f37de54c182a28c4ed93c4b50d0812ae82013884f45e54233080b0acc9dbede1783db2d5e569eece73469564fd57e81f459231e3725d0dd6732e64bf1f513a6783524b0d0388c22c9e5605f9825782a455d2801b7730fdcfc1f25ade17189ea425a16f0713b5997d6d3a3a96a82a307803358ae3c3140126c577ca0ed0583541fef3795a2814b9571d0d64d8eec8b32682b62c16a135b1821300f498b2dd11a3ebfca9a6de8abe6638a384dc3f577795a5ce8cb777c0320b2a279e2e98752f82663cf10341b568fb2527c3192b55ecc66fb764d116c0f2b899db742fc8486", + "authTag":"e80273f1df1f1731ed4cb94da71d0855" + } + }, + { + "GCM":{ + "key":"13df5ee26ebf05d7c9f08bf11259f699", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"48792be9281d6db729675bd1d45b3ceaed53acd8129ea07d7b17b732bc177df0d37aea3832f4307f7b3b4c42e7a4ae2ee392b8c72c75", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"ed26f2aa53f97aaea8482ec196bd9d9b8d0b4d731e04dc39400349cf11382a9cc31a7a796f300d1305c27739761d414264579a9f9099", + "authTag":"0f3d78dbb1e954a709333a09178737a0" + } + }, + { + "GCM":{ + "key":"373d16b9b3d43602d266fb65792fee6f", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"c12afe531205e347e4c0f7a6f5bbb4cc", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"7dd86943fcc27252e3e0e3d00c17ac5c", + "authTag":"108dac3da4573385933108f635264e75" + } + }, + { + "GCM":{ + "key":"7de4428237001556d092db9f1aded0ec", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"62779bf637366cbc27266a4f354475ce6484ba570a3de3c4b068832e7ebb9789", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"a6abb057fb82a498b5e93dc64ba0a5eda18fac4a42acf8cbb5d443fae33f5928", + "authTag":"bf5c6d0761832d8b464b2debdee9c9e2" + } + }, + { + "GCM":{ + "key":"4213903850aba38eba860b85f995263b", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"8ec4dc00631a069fd4d225501b43ee28131b0db07823e65fe755223224ec4051ec0877530de6b8291842f89da50091c7c6c625239ff160012793cb07a9a380ab08b3c856c6ff0c9999f5517e8224267eb1364b39f7d166afe96ff5264b9cf30441c7e79b7ffd51de79977b56eff392073201a2c5b59fcbf6eec74d472bc09a149121a66a2e86db18e6da6634308b396dcb82cfa21d518afe1c66a1fcc12d4371a0c6bd91574bbd841b81bbf555c010564877a0742f716197199dfb4e734eaa4ad443a5f6b0c495f3d59acd68752d56d6eb359002588df018f322aac8333d4ac2e54b4b1fb126759fe1ca323c53560a61afbd4744ef465df8fe1291edc21ea97a", + "aad":"b321d243dbd79b4c54d9d59b", + "encrypted":"154296f111e107a577b27604474d0372b56b651a934b4ba0dd663151c37821b2120e6dc3bbce8d817ebb1f0f2e2066cb88feda45a327aafd8f72da2ec2799c5cf72fbb4d19aa278c51f09107d0f40cda5588cbcb1ac0730226ca11c4efad08573b8363597105a46b11f8be98e2763c37cd14a220db787f2176a69c4c5e3f4af5811fa83c2c4758b2e64613dac742030092291547dd8b772c87b5574e50b631f16c996cfb178d848ce878b26e3b4cf72c76b0f350343d9485b603bd863e565e0fda97e01495ccc72590be6067fb7ab4813f91551d3d2da8f360fec134f175da7d4d491861919bd95f043a602b009832eec55ce8e7fb57d48de2314598b10dfa3c", + "authTag":"174f1eea7f4d0244a04316797b570510" + } + }, + { + "GCM":{ + "key":"821b0510637fec5f06add8b5a77518db", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"884abc954bd4f2ce8a5d21d6", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"be74dde34988ce75959b596a", + "authTag":"5fdd63c6037230ec6d13b742ea22244e" + } + }, + { + "GCM":{ + "key":"ec94699a4b7d9194eb144217aaf19dd2", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"9e87490291f4e226c1e2baf5", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"0c61bdcde70621c74ed3e49a", + "authTag":"ef700ca918121f1fc7aa562088e500b1" + } + }, + { + "GCM":{ + "key":"1c58af0af89f4b5fd6b40eb4d9eec45d", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"5d5e4763a8da1285bfe5eaaf50be167235afe5", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"46a2b4519fabd431dcc75d5ce402a60ede68f1", + "authTag":"3ca409fdd6bbf1afc80f66b8ffbce670" + } + }, + { + "GCM":{ + "key":"b7faea2129b503ea7383f2d7917e3135", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"4d", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"09", + "authTag":"efa0ab1900ebbf85a57e15107448e2e9" + } + }, + { + "GCM":{ + "key":"fddd15156d7e7abc6c5354c38c0a5c50", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"6a714e58085f1ebb4cb994223c4f816a3d6e25df30c9221af692b67a976434923142d50974da6e6917909a0856a4dc5b2af4e83083f488af2a0c25691aa5e55bb302da3bf0cf8799b3243fd754c00afb3ec6bc81cf93e87eace73aa1a74ec51a0b5b990e35586dd97c789346fa84ea93c4f250dd1556f3914409384f1fd4c198da2d61288dd49f1d80c39d52bb938684f3ef875c340b8241694f7eed7de7e614730c1701a5730da3a0f64a535cf068b15ec1689dcdad23181545b77736eb789fd7ef291bc45fc3d513e7f0320e10f874fbcefdcd7dc9f59190f69d276bcdc1526802eedb657fc1daeb65a66c7b619ca853ee3f023ebb44ed43f2e7e9656527547c6a3b7eae210d32521d9f13ef363653fedb2b1fdd2b9339fbb698924f3c46dcf02c145164db6504ad61b5bb1f7052e4dafe27636a8045e430ec7484a37df3b5f2ec272142e5d04997de805a3aa3fa5a28572494cc9e27d53c84273da270c0535b2bbf56115a916dabebb3b1e33c4aedfb404836300af58ab3e1c57c218634bd0b56e715e9a872bb267d1e74defbf61d5f9142b06080d0318fcf03c0b0f3a9d272042499056df4a2ed438b882a5aa34fd3057b2a05051ee3b23b5c15e1909adca095230adf2907b0b7d0b86e71697828ff014ecfd1e69bf1fd9208c2090efcd9cf87f0de391a8dcdbceb763ebca1a0153d2033f51ae53246247309526c7697444495d8f8d55bd470bbaf2e0b8b8ef6c6a27d946b170aa06eb119dc18d9dfa414c2a95136c170bf70330869bf9d72709ddc1b3a185a86419ea6089169a64f8d1bcf27d5dc05bda284388910a4b58567c93900305cb848ed0fdeb0e2a6e3e4bc6215f908fe75b02228e67b0c2d58ebe00d5bc655ff77af637e2753fa304b8981ed1fee4c44defac8d8294954974b3a906bd0bdabefc2cdb868f6ad96e58d8138603b2335205ec4f612381939e2753e04ee11f90b35f2b1f7b898b43defac7a5d94a82f7a93053047b520a487818954944c45f1ab8de30683999b6ce6c02774df8a06e89681feec409755bb82295d6173e92131fe4ed7b362a67f912b856538b6e1f13c9cba5386b1c8522f9e319835ee93892adc0f87a92e7d6cb1cda2788ac9d9dd6ae9a0fab5516744e1ef9a45c0baf0a613eceb675300e38ee8beeb5a30ea13eeafd9b670aa3d00a156deb641e88cc4f908f2b72b2dd854e5674bb68072b69d51ee3f4aa724b27c06a38d0f343812a4f9798e6027cd75259f4ab76e0e4ebd5a5a4c3b9232a0de8376fa16f63eaa90d17dc7da0d4c5b31d7e78c9baf136427dd9d6aa1c6a2cf1476fb05a5da2ac79342d3d57af4117bf173e0b936ae6c695ef32843d3355c82514b88867e1cb887f4536e683227e8f7991864ebaa776e011ea89845643ec5982241b3fe67bcc4fa6a26b4bbbe0ddb3e170a9dbff704a20925c20379629df28676011aebeab204df191c76adf89cd0839021e0486fe591b23b5dac160caaf798d251d5ec88138f363bb56ca82c36bffc8219cc597cf7891912175bbd9f3986f4e04dda24118cdac13eac0130339ab4f5d27eca71ec467aaf7d2a0f4e8bb4305484e1928306355bc608a5eb747fc7c5f7719b8692216bfdab972de2d53b39865136f6ccfbe7a8bd7621116e6ac02bbaea54827064555fccd795f3f13b6aeb12e387948b6b86baf6fa58cb9010d974de258d4150b039c103906a6edd75ab9b0062b6da8df47e84234eeb3886dc", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"a19c8f4cbdc71c08cef7ff21ac41d50fc50302332665e9c773618d3dbd1f03888ad7dc4090a6bba046cf2aa3b7e3b2bafb0a7a6822380d44ec4d5bfa30171c43d2c43cad44efc4b3c7e0f0564b701b06f3d2436f234671e18575710e1fcc06907565a63cefbf11924409f0efb0236b1e1a040b6f0687edddc042492376f8139905455eb3102920111234ac5b74d33e4ebb2eb33c88c0545f4d9a18a065b3544852b26c54ac8f068db1484a626f4a31a6f337ff6f087739c77d4ea3c74315392a6157c6809000e7f74704a54d54ef542ccc6323fbb512561f4b9867960f1154939987de1c5c829c8ac96b6c33ae5f92efc5e07ceb1183b4467efeda3fe9d1354ee129aae5c19a49137b6c365517c93f89d105dfefe8efda011cf45c79057f422ecd4f1dd3f88c5a37b0d8288e472146b57a8b56f8c219e7a347eb5d376c3060133ccae2eac0d2d89a8b32866a35a42555b5f8bbdf48798acbb8f54a9a95b2438d645281bafbf1323a785c5d67c762797fd3025aaf71161e373752e9a8ce8253b9715fc3d3177482d5a3f43fbcd85f2821a3f2380b4193c145ac93d7b0bfa8c00cf781053b06bdf9e48ac7a80312a612fbf66f4e660f9b828d399d15f8131161c111acfabb1c41e2643d77f57d2a5baa0e5bbd72a2d8a38ff92009de45a08be8ae634e4c265a2a88ce89646374e6a9f43fdbd2b7cfc5dadd4c38bf46e89974c89c078bcf7ee67b19d037cde58dc96e5a244142fa6682fdeab4a38914490cbfd150de5d2188c613a73727985042be5c4ca90f8fa53d7f17bffc38c33fa16fb51acc26375e84555a27735c1dbfbb8c3398ceac5f970e486988363e649811b71bc7a84a94ab9ebb5ceb3b43c01e060f51f354d6fe765614105e0a5425a04f6b316409918618996093be9c8c380479165c75da3d78f00117a2048f4fb5508da07261abf389100aff40f6f84e0ae3893e9a2499eaba0b84e44bb633c991d471c834930836edb17c7e80e233d4188ca620c1ec438cdde8c4afc00c75a9f5c965214ae695ed1037ced18740c6243d000e2b5c7901f646c42292d5c9778937aafd1a4111ce9cfd4167452847d74a6e7662f00e52b2cdef77920b76d64e32f555595aba656a53a2152cb83c4da6f70b0c42f0e664761870c02ea16e04478209caa4e18939be006a8dc77f57929bfffe95816c7a6861a5c7878827c95108ec7d616696e79d21428b841a0a6ebc14fa8fd33917f7eca59633e4c2441caa0fdb027e493267397814e3bff76878182de72ddce9a3e862ab4ba74f16d8f7314b92430d2949d9de66ea477a93a3506fdc35b4787e6c16349251515200a7ac01a2f62614818802f374bff706b1488a82459146b0fe590b963f876d1e48d7c79ddc5ce8901df3cf472444e0bcdd7e7f077b25d84bb7df9fa9adf7bbe2a9e9fd3e4a2c77af5c351481b2f332d125264a02a1e85a032a9f3644f25a666088eb453286b89d9ff0bd89fb65c4f2012af50a50fa9b059823f20baff3bc7538709a197a1f3dea64f2108d72b92308f694abf0460a47acbc2a3060ed8707483facfe53f9199533aa3d076ca5450f6913a5c98af5dff2d1f1e363b0da46bebe64d4f8afc6ba69887c6457c276e20cc9094ebad6d537b14257cb979d22894f22e26a18713d3def8ca44511aa12785324f85414b7ef4e8c838181c50cfef0a3f3d69308bf6033590ed72461a0eccf0e2a478f51b485f2a6f93eab3abd6dc6cfeb", + "authTag":"8a25c9beb86364ac3c6d058fc5c18571" + } + }, + { + "GCM":{ + "key":"c8644d0324ab1101dcddf0ed69159303", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"66ccdc229beaba6162149582deb92c390f4421d0f324a84fdb8ef1ada26dce732c81eff2527de39f189e6333da91be5e9b15557de023", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"79946f5513e63db2fc89722b7f8dc60c097c0031d9bc41c5dadd592647fa5c5f42e43cf474b0c3e55dd2f48b6f8fe42065435ae60542", + "authTag":"cb30332aceda1e7ed187186cc7fcd40f" + } + }, + { + "GCM":{ + "key":"0b8a703b95ee8a65ca76e3bf0b9f3de2", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"5d33d11bedbd0790a08aab1faceaa45a", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"219003d7205fd029f1a05d5d0f132913", + "authTag":"2b11981589b535735fbc27a66c288d3e" + } + }, + { + "GCM":{ + "key":"a309169b4900e4c75c70e9b7b3d48dd9", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"1345ae4b716ec5782feea61ac80134425f32a2dbb82a0f89da8c7990b5a041fd", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"481a3d6a3c5f959b3f46bb0b2c3ebba270c6e1d81d44c7b08a6ce6a09b5837ce", + "authTag":"8a028031e995c89cc1a61df88972e453" + } + }, + { + "GCM":{ + "key":"9b8eafcb9053a5e8699f9c83ed3d68d9", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"bb40b87b44f6a65006f3f74829966982a5678a898d6cb1c4ed89da6c3e8e60a3b866109cd75dafb330a260706c6d57add0cbe50696cb6782d8a7f51576976c8c81ae44d3a44b19319bec2bcf529c407bb362dbddde0d8c834d1a26104d919803ae565f0cba19d89128be3ed645b5b8dc9a78ead45212bfc33bc80ce6a286b3efc046429ce63dd4de5332021f43a7a4dfddd7a31b21fa188b43b46378468217ef091f308948075473acdda096ffeb49d0f09d3dd8b42abd3f3c68eaf7422fd73ff3bee59eeef34ef65aca9be174945891ce13403a7cc6952d96bbb469ce52ec08b69daf372d352dc92e30b5b5d93cf5913c60190d0a86cd572290d1e4ff2de9c0", + "aad":"7175443d16d1a9db40c568ab", + "encrypted":"acb8dffa19754de0a781c27c3b151c0fb52ef0257055d3589c048b0c25151811505c42a9e41c21280ab59f14686aff6ebd7f83639bd4e2c708341e1ce9ff1b40ea22c95ee2b1e7e52d2e3fc0d2f43e558daa8a93135c43cf8e887be7cdbc2b05c68359604066000169bfab80b6b243c95ca3eb9bb51ec7e48e2b94f1722dbee77fc9844f9185cfb9c2a0dcb39249554efa70b910d437844610c557b24029aa96a8a06fcedae9dd4b411884b7ce2e525629140467838ef19564e4af0e36633d7332fe017e6cea61770c2f2c6ad9ef1c43a886f502baed05ffd586ea90ee7fddecb87017642098e20f7f539f2fb5622772cc7b3eecf30a7ac7d509f6ee00648a09", + "authTag":"2ab6f6ea254775495d835618c9426011" + } + }, + { + "GCM":{ + "key":"5b461248c9d22e49869adfb026c38dab", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"a82073dde1de061b53dfdf53", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"2ebf758ea73cffc444db1d51", + "authTag":"85badb38484757fae3870c67018af633" + } + }, + { + "GCM":{ + "key":"a5fa00241146ae6a671eda60eace7403", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"b6e702970e16da6593a45582", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"b1addc66f3f33388b59d7216", + "authTag":"2d02a0e39a76c207366ffb3ac5f5ef17" + } + }, + { + "GCM":{ + "key":"c8c2631dfbcfccf386ef65949ec07a42", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"ef3407a4ec06c8d6b2d5b49f2ad3e7ce813c95", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"4b42acc49f109d8db8cf6973bd76157f2e93cc", + "authTag":"2536213ace9aed56f7871a7248679c46" + } + }, + { + "GCM":{ + "key":"2a090ec1a732dbef43d88373adc80fc2", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"b1", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"80", + "authTag":"f2a300a7441e9ae49e0101fcd3c72d0d" + } + }, + { + "GCM":{ + "key":"7bb8d0fd8fb3ff8b9ee21f9c0e9deb7f", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"2851918f7e1c478d5367add8dbcf931c60f885618e10253a7f70be27b5369ef7d8a166d1278d47e820c3e04cee6b238d06812a51a7bde3038cffd0ff0d7c141198e21dce2d5d40923a00bec4f3f6b4ec4859d79d66adbedb2ef76e5c27cd7e2a59380acdcb32febbf9e69994087e785b23fc6baa053aa1fc3d0c30208e7af1cf99f169e50a5fdb1a01f23cca71f00ee7572be907b611a0d6d09a9f6ec5e524fab90c26a8d20bb6e70ec5db21f1860d1508ba871baa49f21bc7a2b4730e6aee7d4788c519b250276cf1424ab2781790f0d35e26893c8acca51897e914ce447af2b91012b145bb3f11608bb565119c2f8ad60e8cc87d1f418d9e946d63bd2d4a13bb54804896cb7047a392d3783d6bf6651969ae4c45de8d04f4c29a5b0c56cecb81c253497f157c8e3acc5633f02f0fe841e6ed2f28d2c71b4884de28b40db9cca2b0edd3f08b0eb33f9434a273d7bdc1911b522477c3fd4e2e02ea07ecbff59d7017be68ca62eb6121dc194840cfa17d88f8ac252cadd76ba615074b9ae9ceba6169282f31701862c275becf3bfb2061c17c8e2091967941e90fd37480d8172de2ffa857c5aa633cc63351e1aa2e95c76c98dbd77fa7d88c8ac5ca38208cc14b29898cd2c8a4e666c9d9a6a99557823d0ee6f2029dd7598cad1950db62c0e3205424eae3e2d3439fdb280c23ca57e42d7c1471f405d97f8ec8457f005861d7f6f2690eee3309d055798188dd9183860a3133c0a8a1bbb67fdaca93fb1135ee99ac3921d9d263f08c2fb6d014d62ab806e731ef3b07af139c092de4833096af8e7ab2d38363cd54d57c6208e1806e38be86008988318442226c661914d910378de7a16b467fd881abb6b78b78124373a383df352d8bba58268d4ab517d4b61981974766199b946475e844e684c9c6eb51ae1ec306a36534accc8938af50425ed863acec052ebe48d87241bee3cb0b6ad44182bd04a304e0ef04e676b2da65602c6490bb5937daac06aa274e4589ca0691367a268fb332440da9173dce1c55225f8e33b58f23d4238ca6e3eec3e1e60007622a47fa84f79537ab385f25888a370503331b7c39e717c3ac6044fb0abbfaccae46855c299d7de8a4b9f0d15b4fe62de558fdf52afa751e191d4ddff477d97193934ade5bf60025f5ee3c69baa4186a95e04550d3d7b389270ac88998f69e8497d61aab93e7c4cac1d53c3d12e3f3cffb3b71b89c1c04f287f632ea23019a943bf756838023cc53b936b52db5895423584053b7f55782e1f86145f44383ea22fcf9d71a2f599bca1a94fe3510eabfb600a6d83fa9d99d89e849114ca09522d75dec8db5f26ccde1f938248092853d02606ed9a168c32946d696bf81792a80948472ba4a7e3af066f4e2699d63df56e79cdcf8deecd6662b84a5ea914e98377d3ec1817a0d18ff6437958c98237884f477fcb1f30692f986002571df8a01c954e92b20239d495f56e2b4b1b5ee468a33d9ee02915c26ebf35eac2ec855c73bbd11b27757a7ecb85b8860c1c9cba9999ce9f43f8e25243fa7b9c8e10fa3c3cc963c32066c5a649ab9db40eb349c43eaf33519c04e7ea679542eec01db9a8db4128d24bd9cc7544464d8f1f6a482f7180361f4e6478a8b426423db1eef38343cbf7259ae69e8f44188b584e66c394c85aeef2f802dc408e3dc9d85ff7c4d7222fc3d149131c61b0a5500ca13b4f7fa8639a4f34c04d52df614ca11f57c9039182defa7", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"208d282f64c6832b75ece773e11b11e8bc81823eff6941ea8ac00fd9d20d1518ecd8ab9ac026eb7816f5dde08bce1deb3f0f22d9a8a42d4b457f4022aaf3ca8db847920e4d12f53a7c5983ee99f766fdbbd4594e208fedde1afe367417f1f4f66629b7782cf0d886bfeb44a1fc359c6ea3f3d695f10a86da5c5620262456fedef422305554b057d2508aa3a814237c4dc3bd088cfb905142919975daaf6d33b37e707e2e122a71829a10879840defbcad3f892975f9714b11b87406aad2c13bb9d24385d9ad1ffdb11d5cebadf1f5a4ccb3acd2d9fc7f54cc8f3d9bf7d51391f50f10d7290927f540a3d1a0c423040a19eb9e6af70f55531246fca8c61609c13a3c0fc5a8168ef029811bda9464652f1a88cc5f0effa41b659c5840c0e490d7e031b6da2d1eae4a89823c604b26a3904423d0c3d0452e2b5cb7fc2b5bbc9f561cc67de8065239bd68d9b5eedc9a1570e66ce7c54b5ba677f9b61100311ce757c3209cc4f9fd83cf3c64e30307efd6cbc94355253c3ff5fa10c4011971e8b6e42f358f80ce0a9dbef8767202237fd53ece5b3d199d2c813b7eb711fa94e55c1a3ffa6a236956ad19335990437b8d3aa3f96f24f0fc5dd46ee77eab13fecce9dc90dc4c9aeeac1d8a2bb5bda1dc0dbcb6355efc2cbee47271ad364813fc1c855b70412b0d5d02a316f4a10bcd53caba3337d4cdfa483e3a7eb6479525c924de75744755990ff3762c6af5c75d50cbefe550fa47e26b4201121aa141e0501d722c4c075ab2a9fb9c955b2db46f65ccae3d6a3431f1b0bf95ab3a510b01aa2e6697fbb12c03da13011a535ba44e2e76056d344e357dd271f640bd314b4a517561c945f9f7e1919f6d56d7252bc07b1c1dbb4c022ccc2dfc961b13b231adc0390b2557e17c0490f25473ed826c014b0d48e7ad1cc868e92af2f6da29373613ad278ba58177b68bb799c649006af87a1388433d73a90f6872104f9a33b8a6c45ac6d05655b2f895c98b309312cf17fb85a5199c1b42913398afa458db4db413f4de2abd277447db2eb70ed062a51edc452ea4131f141cc52f2bbb9f2d2c18fd339c4bb237429619133f950439a512409e249bf0efb8a0fba8f4b7ef8d33c35bdc0afab7b38f46392c1e3fcf7c211740a42d08d6ea6a1b3aecafb277ab9abeb3590c89cb5b85515015dec144c26e1398f5a26165cd738fc5aafb90772ec03bbf6cf006ad2b23e708812bd881a433e7173aaf5fe0b12a2ba727629abbe752b2b45804f0b5662a9f1fab23e804248de85b7936a5e0b92a88d7336b4828f93f70d098d8b6baee263defbfc97c3270d5b4f8d5620eece222fc96a4cce8af6ef966e8ca96ffc2459aef092ca9708831fc9d4a6b5e5f12aab0b342a99bbe932f317313f4d151f5e37e4999bb12eb00ddb41632ff7a19f435e2d146daa9a4b80c919c63807c472d09f5d6df379ef163bd25fc2ee649b313fec78bd64cb1c3fa078b97235b9e10b3a90788ff0179bf0475decb48d3d59ff06dbbec40293f2cba6b099c3de96caa929951edb5ee1941e8b2892f4b19f32e13ffc7433d0b11fad29a42986b11317ffa6248eb4aef0ce5a5637a392cc0652468f25bc4fc1eeb6cdaba83bd26295656243f184ae93fe6fdfb966b21b6a40a7220c65d6641581051f225e0234e04efaac6172285ab926fac28efc42b56a5f9b9b2605d1a70da4665581b7bf730a6c006c0c4997f51034049d58263f1219553a2c4e2a", + "authTag":"43657c5f025f81a77cbd2487659e9011" + } + }, + { + "GCM":{ + "key":"7f9511801299c48cf306fc65d0ada18b", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"dbe68bb9f513aa519947c6a677127b661b16815c0f856b5a7d0c352cce861f643b192eb7999682ba49e4fd8b3fe34c9063ddd5bc35b7", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"b616cd86a0a9eabe95f3d948ce775f98535f0c2114aba10a18d9216492283196e1beb40dbe069f6b57825558d549ac3c87034e63cab5", + "authTag":"c65365d6de45b8ee4cd940ea49361633" + } + }, + { + "GCM":{ + "key":"42982a3f34c2149126fa67262fc35318", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"946887eb3a35fe74fa3febce26dc6935", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"bb5c72871c4ae9306e5da71b62a67ab0", + "authTag":"37fc05bd7282e6092b2e12b75b3af932" + } + }, + { + "GCM":{ + "key":"393169a6ae35ec0321c96d85da57eb7a", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"248a4bc7a7cfd413ce9441e2e1fec96cc1b7e5e3ab114a2aff5f88e1e4f8526e", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"4cf285169b297ee789e2a30ef75c6387df43ab8db99fd25244c4398328c9ab75", + "authTag":"2ac52d6c59efa2145276b97a4b36edfa" + } + }, + { + "GCM":{ + "key":"168ed119d274ed09df547cf86553b3a4", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"c7e5bfe7590b317283778581c69b1f7d40e961126d1160034672428d9eb512c128aa544f22e462d3ef56393e23ccd73c616ccd5bf30e896774fdc3b2ddc0695f514cafd90b43e8105d718e4497029f0d8c42371c27dfde0ee23a40436e9f85e189698020ac72eed18a54aa1a1ca1e45958928b86952aeb4c1d37ae1902cf9340ade823f3c8f32ec44e77043774f8a2468ffdfba2d20894db134cfafb9068861ea3b4947e0b9957b7a6801d8225959bfd01fb89763e1db02d7241a188e41623c4ef1b1adbfeb8483e547ea263054f27f27d1483bac7ae9265e90ad44cd1fbf6ea8c7362a9dfea48174555f99e84426c72a514c5a7c4a8ab24039b64ee97fa39d0", + "aad":"668517bd01e0e5f5fa151288ed6e2f998258e3", + "encrypted":"e8fc0dfd05cec0964a2e5d8986d1d54403a493aadcd9523384c0a6a35586e06ac9f65e9b95a7fd9490abae3262e4ef13c60711b7e7ca0e3441c02002bb5cd5ba840e1a8d8710811d2893728be3deac44a0dd4ee0c115ba9cc1f4457bfe31c92fd15953fef9e914dca3115f6d0ea603e9e6e39e3bca3ac1bdef5b99e014508e0e37ae79662c51f329519dfa93bd8ced667362c54549c7019afc3677445f37350f6acc8e708c65bfdcf42e75e991e328e60a9dcff3cde20145ad367e14f097063e7c91b4bdc0862676ed5e5461c310acc31466e5dec91f614284c1020d77ebb72b3ec5c4a351007096ebd737aef18ec6c77413772c3ee4b4e941bda6ed9c7bc066", + "authTag":"65e9634f50a0fc35aec5715b8620fa7a" + } + }, + { + "GCM":{ + "key":"0bf0710041858e00e5dbd885d5800370", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"3c45686a50cab5e09004d6bc", + "aad":"null", + "encrypted":"48e58b18a201226bdbe9ef1c", + "authTag":"409197701bb41bc5a0e176867ccb20d8" + } + }, + { + "GCM":{ + "key":"03c0f4d6732de4c0d3e894033b227b36", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"8e685f869e922b63d990d893", + "aad":"null", + "encrypted":"e8c47643a24f04885819e495", + "authTag":"09f007611b2555c7f2e0ac4948e239b5" + } + }, + { + "GCM":{ + "key":"76998455466c760775f18975e6858727", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"5c1656fe134acb4dc34a8efaaa94cc221a6ddd", + "aad":"null", + "encrypted":"e7f1854db57f06bc440ec9bbe438cfe095cbde", + "authTag":"a73c5b8c7d1aa1ffdfdd6985e68a6433" + } + }, + { + "GCM":{ + "key":"7560b43acd2e05a6dc9270c589984065", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"96", + "aad":"null", + "encrypted":"dd", + "authTag":"b2978e00215959b39ba270b9f63c830a" + } + }, + { + "GCM":{ + "key":"55d2a0312cbd2767e424e01f2da74dfd", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"999f393dd14a39ce80370358b2b1de6b7453e92c901ca3eedd9b728623624524f0d296d75dfd5857e397235864907bd2d4e8703a5fb94a8753c4d799bda0668382352552bcf523ab38a9bc2957ea969209480b10dfc480ced7d5beda43e6e61a331df9ab3d70fcbb99bc204d332465a70f96d173418786685b7ad32b9a8aae839fbab98b8a97e8f8baf29e3bb9e15c40453915a4316becb8f4959f7b001533e7add2bf9c7bf055019546cd22700f73e2c5610c93764d303c70df895d712c6849c133b34249766d7dbdf764fdf42be7bdb4a34c6a716eaea0c8942067f1a53e48d73a08e4809e38124d6b0526e1799c56450bab0b6d09451eebda79e1d972762823face8dd4992e56b851c7940d59394ba6a1c5ccbe2934c4ea532893c39544b963e9024a42ebe7f2f518ddb71ba99125ad97486ce4fe8ca6fb80ca5b0d9c20524951baf366dc90f964c8a5fe3b64e016da5f0ce89e13fef4207724586cf40c49380fc043f000ff6a32ba0c874ee35f03c6a4397d35281de26dac26a28e530ce030ba966143096230c0c09f2ea2091800eb5f0470ba16a928624359a518d5135d5db9ec3f522c8c12d11e3abf83b22cf9d3c11bb9252c611542fc56e480c1f472dd95adb89da16bf5fd246d0fa0c8b725ae1274f54f4ce997246ecfd57516d280bfe555b4efc0450471a0df86499106cf04632855e2c6841946de49f0f30d8093cbe389d83a884c6df25dd8edf6e4a723e0074d65c38243e2804086a4b37d7bef42ad75e071208ffd7956aee2c59152d4bfad52c498821778920f392dd66671476d9bc1cf6b7d1ee71fc7b05ec0f0394b96b536da2bb485f0923dedb00f432dc7ba0b46f7ad4c38ca5f23219e45213dd8f05147228a7c189b0c7605d4141f50762948927c5bd9126b2308e797383e89ad9d110aeacf17ac508434a0ceca5864b075365a2c2f606010a912c2fadef2ee57b98722b5c10e543afe1bde3d2ff2c4356cde6f0a90becd379607762594a311e90310a112c0a1f162f8dde42454ff905cab3d0f6c34300edde4f81ee21240f773d7336051967aa431a537fb926630caff4fab28219bb03d3dd0be3d3b867df3e31102e6677b0fd065b6c58759aabf362990b9c7e0833344f42013ce30c155d90328287e4db3e4a1a2430676b417415c14e841e455faed7fa3a023baea43c120fe4a2663cad45db90a738e7472a7ecc4797f662c82f54827444e46a612b6519c7a715829dd2f8931dba97061a331d0920066350dc095bb9b3d4e254eedb0d0e228a202f9f559a321d62b88d341f4968905e88e7db67178fd538840274723c873bffbd7a0ed303ee69420fd4cb2b63a829862a7b0061261e3be77b57d15337826e2f3d83009ddbb8d77940c829f31c6a606e4102aca6b6eb8228e37d9c0861f89121b47aa5050101725200ee20cc18dcd56e863d332a986d8f2f576da79023e0faebcce6c7250a43f1bcdfb1c32ce6ad3ab4703fa21f5232342a7e7eedb934df1c9d28fae5cacafee7ee6e98718c44237a21a6fe4251ef502085fadaa2f8afb5096e6b6bd0b7ec05ae4ca724f6e97a468b4700448837dc305bc790180790c11e15e11e3b5e6c5c097a1029148d0d9976677b3264527623070ec02e66d8525533b917cc706d8d6cfa846063f169a8dae19be66a1b6b38b97496b4bd1b16053b246cfd1c5dd7042823e939c122a634f83924db80c1b32bcb5ac00fc8ba8c5d740a910ef72", + "aad":"null", + "encrypted":"55ff0827c4f2ac56c792413adc7ade136e84ee30c9d0a87ae53403d1d99c6e6f4abd00fdacb4223d3b8dcbb26b1b50ba14e0d0006e0789c9df3bb83c9af9560035277860c5521cea3be595c9ccf895c627285ad2a16d2af4760526858a1f006858218578c7725d5452fb9eefc0ada3f9e2cbb1da9f9a8df6d5a968e4348c13e3b4d3444740ad0283c1d599ebdb94496965335e5d0d970cc3adb6ffad0ac5cf1e1e143b7b2736ff67286197bda16f11b5936013ab46834a47139b9bd5def8e6b5b76e1c48be810d18158ca1ecf8cb5eaec3495d3a7a7421b6eefafb5b3f621f5c6d07bced62981387e710e14a75b4bb301f29bf143be675cc2aeaf2699423db01fcaa2e88178a9f6fd81388657ee7ae6bc94e5d9f2e3feabbbbc1dcfad77eb31ad0242a044986b71864d33ce0717b16a53bfa179dbecf2390157c4f5a280196b367f88b16b4e789a0f20784ae3ce689c251a9c88005a496e1c2b49264d543f272ecbaf0847bfe6787a7f958600258b9ce6a514451a89de5b2631b203d695ff0f70ce58630f7451a7f16b8f6ade30d2870e4f35d73916fa690d195eb5fc550e544b05a5a55cedfadc38f179b498de9cea2b151d20005f9f15bd08ab7c02e6d43f88291a9928f60f2cfa7521fc00c9e62ae4e778631111dfb6654089cf5de4432f5dcce558abd30919ae35a134551115679825bd396b73ce556feb358d839a53124a856590b0138095742a095125dadaf4c3436ad1b0d3eeef08ad5071d1b8dce13a6b79610c8fdf0f4d407060dbed5f0babe0acb0065f6210d3c665fc4d7b6919fec92c97084d63d048ae4ee8dddd1c3e2268ab34590f17cb47fdc23c3bd93a8dc6b9afef85636c1223deb8aff3428fa37ceb1cce5160bcfa0dde5da07fddb2581c59a088c01c5ecd37f2976e22b6045bc4981f9eb009c14720053db25b9a5584d6c78be93cb8e0c14adb6b647647963860edd3ea64768135df5299f366f54dabb2c1eddb04b2a885999acbe8da0a6d1735af9538e74616b27cf0015c485a25545e7a3f74ad1a04ff657cfaf4ef1fd90a5a94e6b745d474376bbccd1c740026ebd873f54e6d535fc978da35f95a8e1e49171a374b774f2dbf91f32df8f7ffbc172ad196ee1d68f01c1b874c25248b491d7e6c8105e16b44020706253e8d9ca1e8465fd1938429d22833902bc3eda5aad3773c9aa39a1665e8fb05155fcc79668856afcb6357d49db41c4b8a56353670cb030720213432d513d57d3f0248f21ec16bd042669c755711a7936cf6de6a5b783cfe46f32a109d8a55d4e09ae491d54015fb22c3729d7bfe209a8504a7593433550f2f66ee5bd58fcfd6eb1e4afe290bf946e685ed2201e103fd0c529bc6780d807002fbba9e19f02cd2f62b6208f3d9535ed44888a3c3264c489b26a37beaeef0682c6cea0b25dac03ba51f0db89fe88a3ecb3e46f5f67a2e48cb24a0f4e454400c7df79317b922f26d7027d0b12a6ba141291c7948174275bcf759ba32b6f2c5cef36cdebe0a4f52302dad1bd4036fe9c7d5a2272bf94f7207200f235e34b044b86c0da0c8355bdcef7098fa6e85cfe41aa2897126bc3f0b8540ee5b7e160f31b718c32e037d0175953360d3127c4d01bd915a1455865529c27772dfbefc79b079341e18676bc350ab762dc42a578684212f2ffc613c932dfcc5919aec8daf110473afbfac539d8726a1d5b0b47f1f2c6074ddb8c6243b392ff02603401c856aa12", + "authTag":"56bfda193bd8518fdaf4576a5aba0dbe" + } + }, + { + "GCM":{ + "key":"102e89d47ad45bfbc4a07c4265bcfaa3", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"0f91a2614b764450b13fc9fe76bab3bab020b29d437d88d8ab3485f8065066c2bd825245b0c467a8ed03cf77974f771cbebef7101823", + "aad":"null", + "encrypted":"6013fc40022b6ed1f08f250057836a989442f482de26df7b2581d4a1c1f9e8673a47750d2a4ecdb89e9c9e233ee2902650ff999e5069", + "authTag":"d5b537a2e47a19aae342f3ef861967bc" + } + }, + { + "GCM":{ + "key":"e583fff5c903017c6c317c7b9b9e7037", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"c60f0cb367b474fd902dadc4c7474732", + "aad":"null", + "encrypted":"2b7b79bca6c19e585c6398dcbfa88cda", + "authTag":"d388be070c6ea1b6f43dd8acc50e8c04" + } + }, + { + "GCM":{ + "key":"f87b07b267f730fbe76314f368428012", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"25dc81d072a2b39af821637204058583094537c2446c37f7f25fcf02f6e5ed25", + "aad":"null", + "encrypted":"be93542763c6cfdc46896304cdd18583840c53dadbbf9ca98d08b7adcbafd7dc", + "authTag":"cd7356f631486b42edcccccab513a502" + } + }, + { + "GCM":{ + "key":"23b7d2587b5fd252cff31f95f162671c", + "iv":"5cd42c3ba4fd3a931b31a0b1", + "plain":"b4d2deaf569e23532ad9d34e8f1894aca2af3d4f2079c505c1a415054345b171f65964e79b9b50a1adaebe928155b9b4383cc3be2e44ff894a71d9c393d4910816a61d7e25c7af563c0cf4ca60f91d4950d54528f03a189dd727cbaa5b713b5a32e6006011bb8b5b727cc577237cc31cb8d5a479b3186fb4741c8c344cd37008bbdd3e12bc3e38d1eeff68a2899ce6d35874835de46d85e4997bc2b13fbd55ef4356cd6e041e0e67d7563e4caadf190c23f1f103d3956db5789d602ca78b8e47cd2e1662db2742348d7c5838f76e7f8517c603106dcc83a6ccd5d28e1577bee04a4d775a5a352f70c5f71f7343e0df1dd455d0c7f24523163ff74c999a5848b3", + "aad":"null", + "encrypted":"4b6a473620d01a7fba50f4a8f98909c203945db145267a8721f2b9f0cf6a3526561b7ef6c908d64f1e1399e5e00609add645b6b1e5ac46107557edf15f7e799d8d108ef17f8f8b6193b070963589c1e5b4a6d57262412027d81994eda5712314b92fa85cbbb91536835584fe7917dd08a9ef2e208494d905aec615d8cece20e0feedbdb9e15af06c65f10798e33a0fef62ec27048284e8f0bb811d07272850386ddcd1e03478877673f5ff742bb7c0e5c96f1eed6d280643dcec3f1dc2efe158f48182c6a38dc7ed9c57bfc463ddd7a2b01b4dc02a6ff872a4318efd37863811c8f164a2caad1ec13eb8d031a8f2862f14a783f84d42292054fda33eed2aa564", + "authTag":"b7fb70d7a6566c4f976e563cec30f870" + } + }, + { + "GCM":{ + "key":"092e8499f4c8323a7e586ea76c422343", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"258743020bd9a3ab16206470", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"a99f06acafd6b518406378ea", + "authTag":"001857596b747620931daa0b6349df29" + } + }, + { + "GCM":{ + "key":"51c0c5c1727f9ee13b7bfd590ce911be", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"5b6ee7b6e0006aa89e3b1661", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"73537ddc86ab6693d14ea333", + "authTag":"7d0ed0c664637df3f0e960caca16296b" + } + }, + { + "GCM":{ + "key":"2a009493969523a7a70c10f741afabc0", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"c645bf45b55d5b6871caaa97904a8c00c89d12", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"dff66b2df3a8626f59d7eca34119a6fe6ca7e5", + "authTag":"5c09f4783666c843b7c732e0b2cbdeff" + } + }, + { + "GCM":{ + "key":"5e60c720f607d60fbcc95f7c0a9e1dff", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"26", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"83", + "authTag":"cadcfed2b7e623d1f06bd93213c808bc" + } + }, + { + "GCM":{ + "key":"22d953897cb8fa72d565f93d1555fed7", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"9bcd2aaca4b65b1132b3579fe9029b45909499f1d545167a89c0ffe68bd2eb5c9cd1da3659f60c02145686d53c153c7bdcae400a95d255f3b55471ca3c4a0f1a5b31ef5fa407d602cbf5fbf7a70af3794872e47ebe147aba756363b878d913bbd245f058c352314f9698e5e5c7ddba5dba526c2824af2d6956b69ca984267c77e080b9b30bcc1f1b8ee4c137d24b8ecaf86b910b1fc61bd468188b81655082a45d1aaf0ed6eb28a2ce2ab238fed14c2f25c82e3b06b5b6a87cefe55dfb9c4b14f19f4c413c7452482a816dec6f05a5325bdf8127fbed525a25c53c3093d72643098a19dbbb554df9b168bbc49b546932e0d0419bd5d925f9e522c54cd93f25b98861f3e62bfdf26a369fbcc881d7f04e7bc76532711699335c5afc9bf2b8e312e97a6fe8d5108edd9f97bcf307a484aed6c7b383676fda594699d47c53694d9c24c474143973c0c3d3611c653ecd8dfc5e5867db08db5cee660ea80f451d063a69c293c01c7990f45840f2d863f0a2ccaed65737792f08353779183327135f008aca4f2a9ff13268f0e7b5760166d84abad41e870e24308734e88ea331f597d5f65c751204b271312f3877e00f4dec363605fb9afbe9ede3d62f4196dafb2f4f7e860e8b2bdc96948933d4d825a2d54c41e3e05ec4cad602fab64ecaee6fa7a83e5395c840805d02fae6a3247691408441502183c8665ce7d939411234cf2dd54be9dd129a1f1fa4f9eebe921eabdf4dae60249ffb7db506575fdce53dce3a1e959311d33c685234e322f399e900bd12c5379f58ebf8f4cd8568a13cb27c41292974e6eff88e92d808ad4dcdc0b8b34242047fc92f5141a74572eb3d2c279be057a4ec25e15a8a9ea52e0f6b87d95564a27def9746fa4fa02b2494b24eeb8c5d41b37f4182bdfabd6c2392a4a221d8d59ad83ad1a6c9e4a81515c32460e422cc798a2a9068974b4e795a87698d6b8efd3bfd5787e23fe8852e1bc3d21efaeb3edd3e6babb00614f230c78cb6bcfa1639873f96c623cbb6ef263b7b8570d47358e4978ecf4a14391ea58846e3e196326ea6f7099f85b1125c139d65e164cac5b49d87161f9adf48af63ae8e7caf6973bf6d4c6f171e4827d73dc68009a17beab9e1ebc51786347038ce664d53d0e60f732bf55b7e0172ee98b75e33fbdbb9783316b1d240ade924f2d400af68fbe9151d2eab5060af949e02dde0f8624683c62819487f0b8847a6b08cf2a3225c43f3102c37c670fbdfb9fe94ef619a4ef4eaf3decefbc52b360c7ddfd3f17bcd1bc863403f30d978acbd0a56aadf983ba94a376f099731a2fdbc007ce9c75ce7464754db521cd823e9dcc86a170f71feaf6b8c947e5f0d31d3bb403ce40127aaff7305a807c1c2bb90b0df9dacfaa87b364b74b590752e0524bea13425a233a906f8b1a5911994ec5de69c8e68d64416c3ded509732bf0c413d21719d41894996cf427bf87ebb3c63b0b8c51003c420d3216de9450d9eccc584ada87402e0fa892196b2e46e074a0f7c6763abb4ce28398ff1263f62853860409a25d155e6108ec69ec9b48ae4d62bd77fd2e52bae0ce03a1a9d443d8171f2295e6c34cc76fdbc6f38dacb685c9238aed89e781627db75b679c5b022380bc82ca3f83d3c19e9e7da29382af2f47de469cf167e5fa3030bddc278d5e8e47949b5d447129e26c72c6f5c8315aea166a0cceea2ecb3be38a83963e7225c41e055d0927fc3c1ed786b5baf54748", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"3ae270be3941099e8a4f6fa13ef67697413e1d73379f759a409a291c59c39a370b33906f640847f646c2278579f1b709c5caba1a4670ee18d1c4c9691d22b6aac4480c8929eacdf3e2db10c515d317d21b8ebfb379b71c2c418bbbeb35f860c1fe1ba1dd3a615acd0bca7d1078f628f3aa71634c0883929ebefb36e33e1532bada36c4890d92cec371512019f0a00a69eb9567bc5cd484ce2d47d20eae06bf3c110d9adad3717052b29034758bfab002ddcde24f13b5f46e27a930a19a873a7df6dcc190f87ba3542baa60406765cae57d299a480eb996c6b270ff61b538f2448721174e0df483bb73cb0b1db446a9aed7bc65b6887afd4e9e5f1029d21a06a057dfb8ac9dba2989fd211bf4a0261279345b86eb4c69e5d4f77529cd4487c7f85c724173d848b0b160edd06d2a1797a14b5ad9ad067a4c4d5171095688f75e13a2ba223a1fc1cd6ef697f0f9e8c809b775e02ba45d27eb4f36cf4ee993062d7c0dbac9fc3cab5afabfad8898d8529c9d9d25205d8abbb7cc4175434ad53f2b8c6c33e8b4a868b6943b1b9326147a7b79d2988067c48112cf5dbba61a8bf5ca7def7a7172971c4fb02e1f0d0b52b97c8fcd72ef66d041e1097fcf8aa5a8b6db5157a3c5ddefeb46e96886cb4e9bad0bd4ac99437160b4055736f23fc01ece60059c743628e283c56fa746e4d287c67b9fecde755ba6bb4f75379ce06c69d587a8543fcb1f38173d3b61a63e434631632708be7f0ecc1adc63515b44dc3015a0aca3d1e5fe7b7e899ec8e7519aefe02a395493c666a6d8b70c6962c9ead8f4bc04583d85cfc838a3670ebd005f93bc6fca68cc9af9fdc69d5e55b17753e3a532ec38e1fc194777d6f91d579b657689ae109112aa3298c14936c5b1bf2b4be5c2ce2ead95e03f5eea90f319de05523f310a6cd0896fad74eaca5428ea7a14cdbb5fdfa3889f4e5719fa4eaffddd55c61ba1e552ef5581c43566050fa947fa718698426044b3898ed71726613698f30ea37ce6d2c7231fab5544489c7c8103959fbfbfd1dbb053e696386926de47855e5014cb6fd267e4c5c6855d3e057d1be2164dd68e184aa6b9c29b71434cd7974ce1a18215e07ec6ef97c47c7abd01a012121b22f835985f1b8fe8ade0edaa0a3e8793b7fc3bfd5a10369b9d1681be23213e32b978d7d056e1b5141f6a067124e6b73bfeb53ebce5a8f877e773857bab637fe0f89e79420081b1f50091f73872d29cee95b89953129b411e966457e3b0eb01b757d550f08687f03aab91311cb606dd5cf780133ad23c1ddecd8cbbf9ad8b8d1ae00ce3279760ddc5bd00279ac2755447a72a77a08516bbabd0d326fb4eaf2f60e616c4f51c1edec2aebf0c491ca1b04383f4fca787618265acd50793d9ce792f7d26e3ecc1708fdf3f2bf12fcf4550e34c6219a72f3d6afb0f38acb9ce0259b5500477fe0cdde1b14b3ed1d77a7150aafcc70e2aa8b3a06d86cc277f18da0922d0d7144b81d0f5a908052202cfab5cb976b380ddf8a1f0b317020a24e0244144740af878cef2b1d545afcec0d9859d405b8d40d440531e7425f881ed94b3943d93c4267c58ecefbb97f97d55d23c613b807ab56af1b45e0d6dec13780ee5aff52482733cbceb95c3101823fe48c6cd8c31bc6cac082c1b6a83d6a85a852bef8f86466c61ed73e27899895ebcf9f858ffc332102f6a874a27582f1380a33bf8d66c60f53b9912814f857ee8450f2bc29cbfb6", + "authTag":"7a80a89e578e011808197813500f3181" + } + }, + { + "GCM":{ + "key":"b89691de6f7390316f5719139932f6c6", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"80bd8470b701cdee8ba93c741dfef4372d416361e3a0fcc66dd3f9744da2d72dfaad2b9f390955adc441aa4782673ce7aab6f1fd0adc", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"9a0b252b8fef3c397f1bd957c097a386134a713543c1cb8018c1b948411fd29d44f9056648929554413859474c3c426f13c582655c45", + "authTag":"8bfa91f0b7d8f22373f86511b7823de8" + } + }, + { + "GCM":{ + "key":"cf125377e3d33a4feff5b78a9e4f9646", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"91a01698b61d04e91b9b1ac6ee6fd304", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"703a707f9c54e9e83f6a23cc96124f06", + "authTag":"ef099e8c8aa865c02bf6eb2d75a138d0" + } + }, + { + "GCM":{ + "key":"d801d56ba2c5f558b9c48e10e4c8f2d9", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"8ec16e58cc9b350552b843c16bec621f5524e3ad34c23e0fb0c85669934a52f7", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"e3487ec600e37bba50e08bf3de6cff048ae016e93e760bb135cc11703045cc20", + "authTag":"5e2d58842405d3a4c9a312a4c43168d3" + } + }, + { + "GCM":{ + "key":"1759165eca19f4fea92cbdbfa662b4c7", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"5b97caddc5abf2d8c16a3f6d99b8418d83564f43c26db5744ad3d21e47e7dbcfc4d40e071154747a14ffc4d0bf299640be4044c1dd21a2d081a3f5ba34c9d5934844616ec5ce5a5dd6a5c505fb7ca9b69a85266a923910a7bf7744a07600c93f2eff4d6d8cc7ef2630a28385316a8e696e56131c0241baecf749c2810479ca49a6093ec97a01a165df491aeff980e23cdc0001f2c4d00e227e40f7c4265e56fceadcb749e0bcee92c9603c10b0ffd673bbd462e7f630343ceac8e75e805fe50674fc21f5d1b83f976633223b34f93f0893ad1e5c1824c651f5b0ce0de2e646af6e51267277caa0fc3b9afbcdc5938ef9e08d7345967fcd290736c57632492805", + "aad":"36f774889afa3d23e611ea54", + "encrypted":"7a8a2c55d65d9f5fdab0926d2d87e9b1f14ab1dcb221aae06fa7bf2d8903ec012b18567f1d06c3ecf009708743c9168011556685b6093bd601f3737928a0bdfd4dea0513224ce3aa975d4b89ee6625b68e2f6bc2779c48ab12ed541540b6ed8d3096657de57dadbdfb2db90e395b0ff1edd2881e570f223a9b6107271cb8f6d89f852f849c2afcd721a907d4776503004a8fa67aacfdb60e18ce207fea8d11e6be72cdf14812c8851a5f088917d7b4bfb4d522fff4fc3895855d9fae465578fd8c967288b0d65dc2d008e1d53d1315056ad36264dcd814f7b68874c0b63db970574f3b1b94cd5ea69c2d9a55f9ca14a75753002c5cb854929dc313854b5d260c", + "authTag":"e5b5c8eabc7bf743c6d96d090aabf74e" + } + }, + { + "GCM":{ + "key":"8bd45928454635aefda6fd7ae1c0c446", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"97070ba762667d625e22cce7", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"324355e9b3ec349def3e0abb", + "authTag":"3eef0f1ce648cc3d825880cd2b60fd07" + } + }, + { + "GCM":{ + "key":"3f55af4228d524dd6573683f2cc1cb0d", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"ee576ecb257585de252a58a9", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"c6811fd5fb1882f63d88ebbf", + "authTag":"2e1b149e8bab51e89420cbe44da10edb" + } + }, + { + "GCM":{ + "key":"0e4f7e985d288b95482bbfa1fa0fc3d6", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"a0ac98c9b627bd12d5e1b0d4d10b285d6105ac", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"cb52411f203ce3d584bf8817d06eab8e175768", + "authTag":"5e1a098f0d87db3a110f43b978edb883" + } + }, + { + "GCM":{ + "key":"9d443b8424f97738cfa820546e752acf", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"67", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"cd", + "authTag":"0daa202dca2147af60106721ae34bcf2" + } + }, + { + "GCM":{ + "key":"120215810bd10710ff65121988dae26a", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"a796487d26ea5d21a7306a0fd8b85dc74e780524e6a691a61cd386c822b054a5943b9d18a5ec7310e3496b12fcd8aeaaaae539d358c0651da0f5756264c136bb1a454a9209722c5d9f9785a18c8978fd8f27e8807d4c80cdaa0fa694389178dcc0dabe89eccfb7e8714ba17ded36f99a44e49cc24d72d1521cfffee0f2531c9e2dec3af9f318412a2b52ea5d0930d836ba77556d923aa49c9e1b42d181c26837322b3748a9b8b1f3b9bbfdbaf9c02f9a769ad3e36fbb2f1bd828600ff210a6344ae15eb523a0d87cc1a5ce02544a318034efa7c5590a1884d0fce21a31d4c4062129a31484cab77f22f6ab3dc3853cbd025ed254f75d0efe03dc500294c2fd9611966c3a2c9af478574c9841f945cbcfe90cebb9b6448610144f3534fcf9f88f0a3f89ad02cd297f595e11c7143aafcad3ace2f090a61290fb6518080fa3a6e1269ef08e461d4e16798f718b5525af45bcb7f56d7bbd9fe884ae43326b6d8f879630cc7c0193b35ea05d527c5f321b4afcd3b4a330e345f689d3c82bb284caf983dd3f0c282cc30789b963319b5a4c811e9bd557226709dae25178f26acb10e794fb729d19cb053ad7655fe545a585ccda1289d8300be1630db219babd8d32ec1e8d64decebfac0dcfb6fefad778ee1d082d845dd1104b83369ca57057d29fefee21b10f4ce0523b95ddeaf92ba17d03febac71d98da8ffa6157d8b2c5f5d537173ad16348ce866fbe9e68171f92cfa1f864b3a6c3a3a34ac46d2f072240e30369f6eb07d26e8cb78cfa95f0fa2e68eb5adb76380ad93ef1d4afdfc1046dc025ac3e38b250d71a5d5619d40527d2f9decd5d842d51b13191b7b551d03fb5038bca5ed4cd094feee3d19ffde14974a293befdbfa4a90e5bb7bb1aa28ac9cd672a862beed1016ecbeac939644a6e7c0f1ad594bb006a438b7536436ca5f81614984f7c478b108ae9f859ddf2557bcfead2c45276be9853f257daa56f5203761d1d45120416b74cff0c59357495841531dfef2e0d3c6cedc5338611f31ebdcde1dfe5dcab5ccc71833b62e33b450094b3973fae11c5966a918a4aacc8f225b163e3c54c30e2dd03dbeffdee3b92fc5602066f9b277401ee17ce9c2cfecab8f95d5eff9490c3c8c0a79114248140f6fd9d88ead3abf2de1e659be56bec1f3c3977db7be843833c26b78fd090c106a5cce533504c9f8e9faeb7b8cb59a5347be51f49d8090a4d09f4ab16b492f2ab985928e6f2616d00d15395c68ab0671b461982a2975ac0eff89769ddea5b8a9f8b0f60d0b39133cade47c6c523514a40e6dd00e3578d977c1b1dd7799d51b72fd0c0ebeb16093e0ad82403c51e52fe31a0ef03b9fd516aabe0afb6f533b3a48211bdd0f63a5ba88cdd6ca01f3dcbdd5e818ad594ae702f618ee6b69b0687bcd23af67dbf68a150bae3cbbf0a8de8a46fb0fa8efd228f3b6947e7d594550cd7f35d4a53aeed90af989416cdbb2f9fd36f6726ed8b6400372d2d990f41eb9000a1117bfb54c1af5c4255c3ea64ccfae97e52294014fee5d578154b2a8c6d7b861d90dd8e6dfaee6e03f8f76251e2fec03f24237c411c4b81fe5c0eabda7af3df9529030993101edb535e86bbf860829a798a7413c78f4c54cd27dfab7d264aaa004a54a2e2f9e1c934fd5ae9c4a35306b42edad02287fd733dbf200bef1bb2d7221b799ebe2f16f9d92d658a82768ae610b11960b1087fec43c695a5d30d80402d4db9226c5882", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"5fd54c179693600a4d0fa9b56acfecb8ecb1c7c0f3ee2247893c9ded9fc2c62723656f708ff32415753d6d9f147b0bc326553d0b755e815ac5c72c655945d522bd53d7adccd134a39b2ac10ef58026236f53f4f1bd70b3f5f1bf757ca0ff0c74f711738f4b4e2566e6b42d2f0b1d40059f5ae70e4b0bd47dfac9d79948b19bd911d8da6a4b650df664f4da05ee5eb73b17d039079bcd4681df949f3ab0faddaa8945d4448bdb57e6dcd2536d113218c1cc60684b85e966e3eb33e99b4ad5e61616ef4f9eb92947285b88fd33cf7959e9a4d9213f64eb32e926f67ca3103b12e5d12d7af7df77b251dd178a3bf9ec39e24ccb8b2d49a2189ec4b985ffa70ea9faefb6803d0405bb3310bbc10a98c986ecf70720d1c8cde22917d95c1ee2bb2d3d38dc8dd45b4b35ca38f08e8d755e64a846ce6c6fc9e1e81b3fbcad0c338d7774b3f86ff556202fe270a34dfad54359555b62a56cff6940168b5b944114a7b2924420af864ad55901be0db43835756f1272b92818fd1d1f98a5382594a64de2a0ec680b477241030ab98f320007d5ccd232b9ac3ea55b2f95ca387017ff862bc7ac38812b017773e3f87ae43b0745360bd0b6d1e67519dddcc1426144753b709cbe48375e9d2cf37d581ce60274da461f4a24cb1bc33bb2c349c6717e5a4d29aecdb77b217042e1a3a02a9959d0c47bb1cb77319940d7ed4d2d83dd71ccf678b43217bcf257e0d94d64f3c5c2fcf5d493866d8be3d6c771a57124034b104725c47cfbe15a58ccbe85c43e55155006575775d1327a53e64aeba7170144b4515a1916bcd7b9fbd1bd521edbbf9d4321b1fadb0bda645b236a26504ea09fe9854a32ccf6429fb905d36690f475aab00a841f8bce946bc1f58ce6a7e304c62e7526ccbec35418f5269e5e36876f6ba633c3a4ab62ab6c9f75dc42c8d079fd272ec34d5c7407d19d9c8fe34fddbb713dd849c467ebe2bef195fea1e85bd4d317308c72d1789cbc8c51994202f7ea7723fec5db9e0a584ea4b99d727f4c8c28cf3486153be522f5df19353be21b78c6f21340edbbbf8537a8e5ba4af4bb8a6e54944ac5f228ea56087fa15c34bb01e0e13b34b626c029c37054183bb4fd25079f227265d8f94b4e3387d8a317848d04c31deea00ca4e4cec01b214c13fbeaa210f4426db614e4edc2234c13e8383ec1fedbcc51e076d0cb4f6f6464f4b9082aa686b42087d0c19d29d2eae231e8ce23c5eea3f768f47e9a625fc6c55c91bba48422b77e1d5f61d47fa0d592d3579b2c73f8943bfd1eceab9c1e85383eff89e96d11aba7d2f4812e81f32622d4bbac9be1771d412ae1a6231722411de1d03b7a9850dcba8635f2ca614f2ea7697371040756a40f9c4c10fa84ff05c6edb349b5e17c2e0ad9f39b8d1603ccf67a9dc0a84f786166d11d71639bcbf196e62946e567618d702b4fd4edf1e6c8282cd942c4240d2c2e75fa1d9b2a43bebac3c42d1dc8d3b8ebee95473ad73c3055d6e87489a6bf61fb83a875f24b3e20c16505112270b91caab88fe875fb4832d3c98dcedcbb96decb45678526049d9349c6b59f835e9710d3f41c6725885fe6bf578e92845113560f86489bffd8c302aec27044ae52effa33d52f62927f9a9b7e9a46731f2928af6acf2b9d08a23027b7e6559ad5b082419d241d5b4ca1f7c4175b18a8b9009c144f2b1caeaf033e0dc933ff113ac055f109c4559c18a0988b8d03d264f3fa63f80b3ab6", + "authTag":"17051b61583e69d49100fbb62532c648" + } + }, + { + "GCM":{ + "key":"e11b79382d2f7b6d37b03472b8000aa4", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"f3506f44f0fa783002e5214c6d64fcbff807d12faea0a168120d60227b53fec9e54f749c459c65320f6b9b76c6715b8288b0a7ff4851", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"ecda0d610d78325cecabde67097e605ce905b4dff04e91be5b73babecf97cda7437ff5ad35a363e49919c5b08171d6ee5feb3534d6f7", + "authTag":"05009cb597f7d7a365cde8a278f44121" + } + }, + { + "GCM":{ + "key":"100fe0867252b91e0dc083da2437ff60", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"81b3df00a6f8743b423d25d4fdf085c2", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"3ca0b2e0608f8ae787bc6773f9d160a8", + "authTag":"712eec744ca174e407e645e10dea797b" + } + }, + { + "GCM":{ + "key":"757c1c43ae11e3ae02a829a5f9ae2b78", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"fbe250f6cbf501ff4583f8eeb336b762b025edbcfbfcc02b155ec72d3a700a1f", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"0558db72ef8d0054bb1e21d0a35e5476786beb37c7703cd5c2374f572fcbb244", + "authTag":"e703a5ac3cfec6c239d13b20ff8e69f3" + } + }, + { + "GCM":{ + "key":"ee354da5d4e44e64eabc4b7b57eb54d3", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"bf60c4396bb858ca1248c48fcadd174149d6f8bb46916bc1681f9c518cd7338829bad1520cdd450c7bf28aece003e65b1e7dd859c98581d761801ee8b4a1c4b76b1684d277c3420fb86a2600fe7c7ed3ce50fae879d4f48c36cbba8a785b932a33eb71957f12584b7549ab29976829faac47f030511533d02c7b90e4a1083c0df915215e38173330b77d4d236a27ddb3c8cabdffbeb84b5dd76c54624a9544719796b72fe0fbb39b04e5cd978accc13c69e4d369ddc77ed580eda4e329ddbf0737dfff23af8d3a4a467aa4122d8a33b0e7d66224f710dd878e3eca7a31f2e478891e74cfde68efe74b69e842b225a37a5d861e48230d86021774c3108c52a984", + "aad":"acd1f7a18c9b11449e51896b", + "encrypted":"e5033506d8659dfbc283edc00ef6f8da36bb3f34fb3f8070258eb9a97c99bd2cb3b0459a5cafe90563314822b551c4389cd70f8df359beee06f1045f84035b6507155cc0d305eea992563322af564bf6ac6fb3663085e747c343b2c39c80791c8781f0e90e385d74b8428b0b6ad2da2b19b023dadb23089ae5358e0871e6a1755d32e67d394f122253ba4ee35840105058b98d22d79e848e7abfa72e3c0f72a837dc168c42255b72a15bedfa13ebc7cfbb0861294b9cad271e5e2df5a8f69aaf64dec5766aec6a471406c346b4f6b293e2d927b0c943113e263bab8e84635d315ad037dba688aa772dd4c97e3fd25a42932b6877e8fecc76b2f16849d41df902", + "authTag":"449ccb32913aca5cb28f3fa9164ad521" + } + }, + { + "GCM":{ + "key":"f587be659934a3f0f22d66409d62620b", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"0b2dbd338c0fe735ce15ed8b", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"175dead617a244a9a19b5458", + "authTag":"75d924d5b9c8125a7e23d61bd402f441" + } + }, + { + "GCM":{ + "key":"9a1409d6b3a0b22bf486870e11fce6c9", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"d4533ad4ab6832ed7f9f39c1", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"0966d8f930b413ebc32f220c", + "authTag":"ce5b0f9f489ce7742dc659c8407f7c43" + } + }, + { + "GCM":{ + "key":"5b0c6671f40c4c6e505a218b19873582", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"00f3c95c2eb4d00ea79452ff5df0c67faf2ba9", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"134666b0e7a4c32fbff56f7bc791264a598509", + "authTag":"e8ca87e450b122ae2be472c1aa2fb8e3" + } + }, + { + "GCM":{ + "key":"f0d8050d081ea45a0eac8e334984220d", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"41", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"ee", + "authTag":"691eab0710d19390bfda8c282fce3e07" + } + }, + { + "GCM":{ + "key":"04bf55c7ae407daf9c0987fbfa8ea571", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"956b48465f0d044058262cc7e8928baae9f830396b78cb4907b8fbc1175bd7a04f18dce77b9a5c81221a53a56e98f8c26d1111b646acfb01dec68a04264a8cb0d3819b85bb0e3b76228b449617f659f8617bc57fc9701b058ece308b0b87213e2b8741be7653baec5a46ec2ad7456e46e61be14ffcaeb70a753393f75ea1893481144ba1a99f50a7a2a5692ef03bbc75e13f641142a577272f5558118bbd689308dbac0953175eb830ecf24519e3d862be9eb9ed2f3bb88eaf1035eac209dc5caa2ffec970ba4e65010852c254b659f23570cbc1e85da40c920f3fe9a8e56d5cae8b4e217657311dcaf0006921dad24c8dfe40403358c08364dc594dd41e18fb130846930e76061407f8d32793781da241894f0dd67e3502124ab27a9add5873d469ecfa38b0597d50dd28ea74bc320de81ddce9a0c4c93f687acdf1d3e01d98b43138cea734765aa5653f498ebdbdd2849bd558f32b297cd2a207d870bd45e489de362274275dc8aa3bd63fa7a89f0d594d2b748c67e30f6b991592a540e72580127504abd91c7ed6683e6e427cfda6b4740663394d63344938fd1533907cf02e78884beed23db70d9c1e8f7653538018e9e90c7c4f88cedf5fa2b403a561e4e1aba261ef7a935b9dfa9fa91eca455991a9eca57739df22b3120549c539b054ffcd254ec73711e117d0d5353f099c53d960135a841bd512d5232f3923fa274e57aeacca8805e1ced61ae956c956a5b570c96b99dd97164b82a9a6f8bcf8ca188c92bf3492ce2d6c54ff5de51c7deb96345af94b32e84ba9161fe05d02ded7f49d7715390a404e51719b35d400382782fd4bd13ea0edebc366f6fe7ec08232063653eb8d9d3ab08e353b18bc92b00a45bfe236e76eee4e89c1e2c0e93edc407646306cef465bc732271da9c610635b8aaaef142d02a51517047ddc2200acfffa5088b9c32aa792ff46f3f0ac31ac4faa7a4bd0137aab5243cb1b78d20b761689c1c5255f0a6bf881af6daa2aa80b4c9650d0871b65f94887d18e9bbefdb5cdecbc60b3be0288f86e6e7ff24f5f5be15202275e79e6dcd0d99f1bc6e1e65e2e9854695a9681c3a9fe8027ebbbb914dcfcdcd7153285648b8b951f30ea542c6019eff88e594f911c24222221f9e964ec1fb9e17fe9aa5b9e7ac848b41024b9c33add1c178e184b62161ffff37bbb864f61f4610b71e7fa9d57a4aee96cae0e3d0ada3be4ee1ee2f95abb7f36b1cc0231156a665449ebec1b1262c3475a30bd2f09c13e9b1963d12699e376ce50fa43f09dbf1d547058067ca1c4c84edd2a1df6dd4473abc6a8e4ce8bf11c1d552f6f4ed9b57342d711599bf4f4540c049255eeef18f05924792eca226d44a93d8b9d37e9de5c46601899ab85d69e9d3f904ff5c48f479228273124fd5e6050ef13bd6719ea0c765397a6032206f65deabb2227e69694b55f7782237e020cb7a43427d0b33a57f3fb98dd9f145bfca2ab230f0c851e17289c1316291ee5f78ffa422bc4a71d1c676add2ec4b4f80c19a2f288f2c12aa3df0a4681f4842d1ffa224bef2b032716c2cfc9f8fa19d0018515d294f78df1e18938ba213f9f665b583ad9bcb59ce1004386fc1c4e44a54f1b4befc305e2e6bc5e0c8e18bfcdfeb07f69bd2a3df22bc615eb2f4dd3c84742a7d534b643ba533209c4a4f000437e605cc9256ebcc98c46d1b82726ec307eba085455e5b03b3bbbb9ddd8dbec1e4d57c8f0e7c71d005f49", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"fd519f88c9a5518f445b4b3f0e39d19163db3a7adabc6842674b10f874a4ce3a86caa3faa4944c59f8e67276ce220c9def74fc44a8f7f517f2fff6c4a6ccfa057a8f404f001b1582c9992d4a590b42cf678f9e8a7445aa9f05df5da2904e3b29b0ae820e10e055be0aef0a45d0cdd07b87ae497cbaf116e02b1312a2ada05d929b6489aae60fa91ed2a641c772818020fbed346a0036c05bd99e4d75914de4dd94600ffe746d5ab22120d3640b0ceebf43f51a3672719e237f411696183fa1ae6d56029c44847434b5f683a4787bb694045acf7cdc7de269fbf7239eabe6cece5f7064f9c1451d3ad04f841a43476d6ef94dbf9271a2042ef60f2c380b0af0adf657cf9314be543a09baa968273beebc1f7985822e9fc6fa1245d8bbb9e6bb5d9680e4d4aafa7918602c536c2cfb8f4af13783b5939dac65fa5935945b3cbe11042f5563bffd1ea40f0f6fc092ae963c9802e7c8d2ef4785741e69765a3535ac7c01bc8c5fb34028fdc635d9ed3cac689ef662e3158b4ca115ddcd17dd2ca30fa9d8133ab003247232efff6708f71589ad85cd9637d8ff1bfbd155910bac7f70b5102a8a1a10739e0a4bbb6a3034e53b4fc210117d016c6d155a74ac0d8a79868822ce56a6deedd0d13cfcbddc7d710d5e39c834619cd8c306e48dd2839a49b674f209739ac6868267629decce8e0254da6d124084bc0ac36e1fc0b73eb26f502849cbe698fac0c18fc2d23382d86646e1acaadc6772ea2b5571e9810667e34fff096a5c60241681553a0cf1621552f8d12f90599897021ac31eea11fc3e55732a51f94e2b0f1d9c9b2d188c6998de5522cc8df3855370a4dc8c3b00907e26896891e3104b63420406108b75e911fbcbacccf61233dcbe1ddc0ab64f263adab4cab21354bff798630204b58d8e648cd6ba23d33bb02c86a85570cafb0eb480c881b162fd5c717d70ddefb84014cdd5503f3ada02b72b5c6912fae967f20ca6a0b07eab38b5c53004d766158398efb20c5f4304b624a2cf2797872daff108ea0d5f4d3d0bcc4c079f5d5a86d3f4cae37956f83b8fa8d6eadbef7f2492725bf0a0207747a3664baf711f31757c9d2124de0b9e0fffecb97470c119115d0fc7506cde40c32c027b87e9fd3642539893420ad67ba47d5a880cb3b524fa94609760aa1032e04042a8f9befd330f70b4186f8edbf59ef46eec8cbae6820b0b0538d67e24519684e4e47990d66d43e283334fc808e43ea42f655e57991873a87a339dad21dc5af00c507227f2f3e0d88ba88a07a34ab1d8e13f41bc7215970f5c29e83dd1390033f6a786c26ca8f89dcf3a30afeaf1396c5934286739fd06ee4579b4f923fe9e201fa5c17b8194ea4ab786e6f90d35f272073367451ffd95324dad45e59f0ee698f58a76c81283c24b5a30547e3735d2dead6a6632f6cadfa066f4b1bc3b0b03127520a1b519ec60419c739a45d15aeaa0987325ee2795d6078d08f7855665b03d1a3b4559b8b0c342ac8d65a11de9a93e115e088969fb18e4a14dbcd424b97187c20826327a2cb1159c2da92e707e41b0ca777f233bb6fbc96009ad9f4a1085328da25f823d03de6d84a3acefc3834e040619b17802d6f7f785c9cb03fbe4fc3076e0267bf52180dcaf76ad0647e518223d000a4407bde557d8e1c77a4bf6968d7b8c4ee540acba341529e1660694a9064ad4fd2241b4576da51dae6b40931bffabd9dc6e000529aed13d636e78c3", + "authTag":"6b2d596bfc4bb8bf9772318a1a982b18" + } + }, + { + "GCM":{ + "key":"b8c0c154fe604ac7698dcbdfdb3e421a", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"55bb96c7efab19da17fc933196d22a62bce6596704fe60e8b5309f8b6c91ad9dc72862ad95313086d5451a585d12ee4c65e161d01bd7", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"3f056db83cd357cfd996dc1937ca088952e56a1ecee0f8ad7a022f9357d47a02127628594f282bc527fa021d8bbb0b20ed0d9431f0b7", + "authTag":"6cf601b21a7d722d90894365b45680b4" + } + }, + { + "GCM":{ + "key":"77d253ec49a10455be84a8535dfa820d", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"1d30a24e513e58e1d2ac16065388f1dc", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"fa87d64d18ba72b7c618f884f46f14e7", + "authTag":"6a9f1100e54f6966a100cc055feb5f14" + } + }, + { + "GCM":{ + "key":"638833fece28fb570a8ab2d6d10fc020", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"1ffa5b0457e7d95d4fa8e99aeb0a55ba6b2afc37e05bdf9e1d05eec844a8550a", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"e70004fa2c7918eb3b14a6aae235cabdce8f2bc73723cfc03c4d5ce1c0ae6d39", + "authTag":"f8e71c69c9a276664a706587d4746a2d" + } + }, + { + "GCM":{ + "key":"2f63d358e922c307b87570044115ded2", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"a24859a12e7a241487e5834ddf34c73c2b94cd018f5180af73cc1e1e1b3c25c824244b1d592d0b7999c01dc5365c7f4eaeddb7f46f6458419d722ff3a265496c66901b0fb2a6b6aad53e25ff3d133352fceebb469b3f6cdbd24e2a203b9189d05e767f7a904e120deadde7b1f51d0e64b8f52d92999d361524280e42b6e826fa678a84a56ce4b857c8170733390779afedd1946dd97c86c656523b16d252bf150e12fe8ce5500c0b005ac4bc40704679fcc583a93bb087ec0de0e63df66a4691be3055244e5128946158e766582c7c70bf02569ec3050fea7b5c683bd1d567ac81e16699e915847d1afe286b686e86e05bca6928a062ac1134021b4411d57a71", + "aad":"21a08e243a12da67650bd741e91f43fe02ebc6", + "encrypted":"c15aa746c5ab3a5d9268ea03c6ff51ded09ffc21f935eaea294341179bec0fcf8755efabdb801820ff3bfdb720e182555b3a2dee21240248cf713e8e94ac1d00c5e713b420e7b531a428dcf3fc44feb3ef29cce4ceac166c3d05bd7dbd20c6ea5bdbc13e14a76d689ecd53447e429124a982e6caaa8e95a369db8a2be3a62873338bb2bfcca05d7359910c8ead5c10e87ed955a0c03319c7c6f9143a6973f2358cd76f0fb95f980e7bdfc04cdd67d5e3317aca7264d9e90ba122f6af1059b3c32eb06cbcc037c90d2d34c083259138a66d0a33c53236e6e72120ecdae073c561eee8e7f15c147b4bc2280da2be63475a6f904372afb15a78f8045d6854f89cf1", + "authTag":"88724ec887a90ab99d77f0805ceed587" + } + }, + { + "GCM":{ + "key":"22aeb6bbb3ae4b5637e5f0612d1a62e2", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"21068aa48840235d828aa73c", + "aad":"null", + "encrypted":"a5329dcbd05289304115c618", + "authTag":"f181307e03109506338838766283485b" + } + }, + { + "GCM":{ + "key":"42a228442cbda323c14dd5b5f227e72f", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"84b339b8b8c499222575174f", + "aad":"null", + "encrypted":"0cbbc03922a5b17cefee5f96", + "authTag":"e9f4a45497b1152f2b06b94cc104ba45" + } + }, + { + "GCM":{ + "key":"e6a898a830525cb01d3466877f36d1ed", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"54700418e297171d58865d4436a66f6cafa660", + "aad":"null", + "encrypted":"bceec2a18791cec0b1a858ee04e4c0b72c207b", + "authTag":"2e4570fbc86af3e6f441c8bcbceb3689" + } + }, + { + "GCM":{ + "key":"53b5b337f1a5a2aa284ca2622d68ef59", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"28", + "aad":"null", + "encrypted":"3c", + "authTag":"8efcf0d6105ba72647ae4f16def3350c" + } + }, + { + "GCM":{ + "key":"a1bf49ff5d9cac4e8cb98c40b8ee43ac", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"6aa738ccfb5c5bdb89aa39f0ed86327749cbadb544c4cfdac151eda98e8aa156ce74d001005a055fc8b96e55ec9fa0edff50162009814724ba2a336434a958ea4018235295ef3f266f5a44b9592ec66783292e4af2de8f7c66b4bbda04be785aca92f5db549f40175aad3806d5aacb9e3f8e778ca11b027db676fae5db1ee7d1f435e336890263263df5df0ce4a514ebc5055dcdad668a2d9d70e7e12a7966a45ad187e889a105568a016db292f1dc1d76035cde5c817b41dadb2330dcf2b8f772fc31f1608ac7bdddd81937794c1ef6a22f306c4794ebcb909d78bcc0cbc520179eb9a8f70f0d51e4bb778f8b4633f81d02530074c126d3e3c904d1d56e6b783a3073e11b9adac276f41b006bba178bce193093fc4d3e6c525c21bd86cc7a727121e0fcfcae847e4a23d3c994583479a948f554b9d3aa7a511ff31b1b94d7d874ad980e64ce76a426692ab4e5cabd8f6324378fde071a214b2dbbe262b7e92e82d223d8e1e199add8f0e0019e0712b57687cc4860d64356a60e669e46a8afc9f4594226524dc229d375b48e8938ac1b3ca462b8ee40954c608a3e3a676fa873386b8c5140a01b03c8b2123700d9892a8ece65ed722691c18b54460189f25886784aff1ea1f746faea2d12b34330da248c07a0f98334f166997bdf62b0c862e9e647022af8c6d3f30945ef499a587a4af0c31acf6351b44eeb1211dffbb838f8127b8f0819831781f041eda0dcebb3fba1fcef09bb7ac4d9d813cdf8edababd984a5ba038edbc388d11e394346c3f5fdff21c3b939014116420e564986a0dbb0a12132895a4b9e1686c1e60db7d7700124e92e0e668cd95c052fcc6a972c608a0963418c87fe2f5532f1795ea518a10449a615ef800e5baf60812e6ebb213eebab82b0389150ba0f27cd2aebfa9e7830ff14e44089f771cfc44da1285d1d3fe5c9161243eeb3bdc886d34d87c7b36e3fdf85231e398ef4a1f59ec14497914b0e08c14e70617db91fd47a1ae1fbd0e36393273283a595e5c466e6534a5e67668728afcd90f42f09ff6fc704a3a2ec4ffb8d9136e4d254de91e9cb61385ea74f5bc3463f4e61ddebd001e956d78a8d74a95dd6f0def7f4a1dc90289102c30e30d6ad2ae80e854bf593eb33bf4c55d1b8b90ccdc690e8478c4c48228f1b33deefdf1a75cd3586b41cb18268f4160b38f64e0d4e58f59c54ba3c65af06b6403c4c93bd8aff877816b9f2e617e19254e19c3d13fb72003da64500edeae246bb4b7288ad2f7e501c8468e9b886e4d350a8e2b6903a3e451c0f5206b22499e53b5e82b2b9bc317e86e14a7244d25756fa347afd156a9a0ff509e59f941b98848149d0357fd276921477b2769d6d46204ca9aeedfb76e78a16e91dbbac48faffa25a947273b9505736cf96d860f9c37dfdccc61ee52c91e0a589a2a3e3f26dd3bd6d634403e4917b14997da59752683ab1c94cd5022b25ef981d179c154e615f57685016372420754bab83c2c3c98193625a4c7dcc37631585e1544a1eb68496ee3d4af92a3693b44f0ee9c74a80e22d04d81e4926a1ab2ea115aaea17680fdf6d750f82c9f45b474c94ee45785b273afb31ab8b4eda6d4a5dd8bd01e40d2c3f4a49b9407e078e02f1c6339aa1aa6ca04283c8472a15be2c0f0c84c7e912d2d8c62d36fecbc60f5bb6d2121702b48b91fc1397c0694e6c9c07bca1346137f8ff704d308348b68d64d5a0d4de13f54d1aad589d49c73b", + "aad":"null", + "encrypted":"fd9722b7260617c644ad860a934b1a537adeb56fab3e9a1fb9f490e64aefa3b92f506ba861a7fd83f854cf8c7fd7df45817dec1c007a9fec1ffb6c4f0cb01db33a41f4a5830a81dde390325e760de8765579f59ead3361cfb3e012cb27dfbf7955a045dc5d3d16e61642eef66f8435b6cd18a3e1eaa676a74c153d0f5b4f18ec786842d16912d3ba28244d65b97b3859156a0811d4b5841facb134b016e9cdf1f02a1b8a1783d1f84ae0f394826c7a962db9cc9afc492b5daf33585140ef083c6e017572f4ad40348c3611d2cbad1ed90f6aa94d56d3ffa7ddf771d48e827bc5aed5308604654612f2de74218892f98cf32aac34d470909877f332a45eb464deac5df47b8a2bdcd470298d57587219f1959ccbd158d1233ffe48d0e10f74f09ab6caa64f56365efefdf21c3811ba4d326cf0df06fefc6a9d011859a03bf45a329c3beee173223e81dfae8253c3dc9ac824b41e9f753a4de2c686a1fe86ac2cc6d835eef03612bf221bd0cefaea4c25d5f40c637f958832ac980b2dff0bf61a71be208c104089bed3b3e8d1eb13319f678fe01e52e9195f207d0a784e4255ce1a5ddded6c30b7a4d0062aef155e3e21b0db43aca19c8dd9995cbe7c0a705e0b7f07a835746b0e1e06e9a74d11adcdb37d19a087b3b519b441d329dc5bfb4d220c3896ae79a3904c223cb2cf1737cb1fd3c8f2f8ac8529bf816cff6a99af307dbaf06960fbc405ddc20a3fd3c9a0a596f083b7dff3514ccb507f20d027927116968014aaa76ca8965dd339d4d390c6a613d86f6e76f4f66faa3f419072b21e9270204a2629a6b0eac96f20acbda29ea23755f2a6dd12d02084a9c696612c99e0ac59d7c8604ab6c3a6541cca7f01a99179afd66d6c2a151645ab93829d60e6ad0bde3a2b781f3ab00549b829c29dc31dd4cd72cc1da42df15013c4a7538c58f4099713508910036c91152708e926897e26c5c88cbdf0d81a077b5c3ab123cfb8c9a9ffc55483e5a88708d2c95dc4307ebc7986ebc1c013a7af071538fbda62e132c684be203d59273c98591dd93c21072bcb9228ec894a3b1663854400139d5a7171d4f64162f24ed154b320d45faef9f9b6ffd024480219b733e913aab5afffc29756aef689d4a4fd147e8e0a1e00b2c883354271956ca2e2e6db604dae4f53b88fc357234a49a63cf54b7f9985687553c2022b971fedf95beb4fc066af9757b96f200a639651618289ba35364468361505fa080baa5438619d05a9bec9e792144deb68c1010ce8651e5318b01f33f11004ba62c242d21d2697f8b3143c473dc156853699aec74332de95f3fe8797322ed586a3b97c36af1145c361467ebbd7dbd97c5b8346d53e46b7f8c60d3c025aaac7e4f23d8da5a9d60e26251de20553d922d97f950ffed3be882d8a2e4a39d7c54411e68808cd1b1ffbbe72c2041d8d8732b4afc18494263bf76d71245ec582c66a263abe1c0675fc278c62908bb8f907ef097791dec2ba4b698dfa1b159c8fff46c55a9e36ffa1cdc6b2717d535e0853b4c3269fbb1708d4bf76bb8ef6aca7dfbcb1de0a5bcae2b03003f10889fcf693e9cb370fb98cd949cb12ddc95e66ef767b2c1a8dc67173fc16aaa07d17bdeba9d7ffa5f3d77ca2e300d0976f14beb65b7a8120ec58ff5d8a443a8c1e6bfd7557e23171a2681bb20934da27689df62d0787300f8c2e5bcf460f994d64da5c3d25f932b08a290a27a4edd7c241a1217db61802", + "authTag":"c508bdca860cf209694198b5b428d7b0" + } + }, + { + "GCM":{ + "key":"e35bb38fb2dbfec75dbaaa740ec5bc65", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"8b151c7d66184e84f7464710698610390ba9ae14fad2827f5a5ee98eaeb4acd406585c663d2d62ff1547df2082b0dfaa659da810382a", + "aad":"null", + "encrypted":"e28238415d1ce875cea5753a9f4b28c760d652183bc4257ed8518dd4c23bda19455a1d3f1b8802948e8a6cb8ededd61e70e4297a4c50", + "authTag":"5a093a269a2a75a2c1bc75d43e07c2a6" + } + }, + { + "GCM":{ + "key":"427ef5e85911215917648bc7523614ff", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"7578be70c4169235987e4850fd2b8b71", + "aad":"null", + "encrypted":"0bf012f14ea3e9edb13e5338f12b7026", + "authTag":"f714567009c0bf5edbaf587b251f1dad" + } + }, + { + "GCM":{ + "key":"c9fc0bc90a49f23431590f2728d4400d", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"d242436dd016449b973d16d66c54d8125fe7164e6cdfb474c284a477f7880e69", + "aad":"null", + "encrypted":"076e511309a88171379e72c08f3c35b7ce28bcb6128e62865dc2fa09a923f1d6", + "authTag":"4b503c31cebd4f63b0639836e962a8fd" + } + }, + { + "GCM":{ + "key":"0b8c071149bc7664cb1f88179e01a55d", + "iv":"cb37304a176d67ed165d7a1a", + "plain":"bd317a3ca3068ef5206e766102b0ae357ead2438981504edd0a30a039fe1e9ff945fa53b1d62f00f3b57b9272462f4ac8becb64586a7533bccc3ed06f2f76ed01f082ac687e5533757c3a031db5ce27b433c19cbfec10a3185c44800d956bff0529897f71b69bba1ebac5a95441720e021f0421d99069a3ea630cbd21acb35c3c378450bb0cba292c8ea7063081c6960514721ac1d0d6fedebaab1583c4c8e5aed9c7fee19b4983f0329531686e3c65695de87e1dd9c3b67afa82d133a959b3be0244b0015d9e64452309b07fe93cb7fbad86ec16a249990f738f92c41db43b94410966f7e49de18d9b78b40a3b00b6c039ee8080377012f3f7cc0ec0e79cdd2", + "aad":"null", + "encrypted":"686930272b068d9a80f689d7b14e2a1245e96875917db3f259647f7147d233d774639b3febb7e490ac0483ea09dc463067efb7722cda9cce350e2971fe03fb528ac2115bc9ae53133b168fd35c819921600680d3e1f72692176ebc76ca2f72629f701a0f84eb92500bea1e0218ebeb3471ee088f0c010034b05f940ecd07cd8ed3b5c62204d03f617f55db69da2be6fc436aadc1295768461690730e9e515479e938f50b86d542c0ab273dd56a594022643e4c11f0cbfb6c3df4e2b412ce69c399bb3b035f07bf4383e4abff658988d4655e917f075875a6fdd6b6c6b5759bf00e5ca96dcd5a2605df77f95dc1b6b6bb009c7dc46bc27649c88b12f6a2e8f1a1", + "authTag":"af3cc9a7dbb5d32940f6d910abb088ed" + } + }, + { + "GCM":{ + "key":"64e0204441d35568c5c4f15405268ca429f00e3b281a91ba", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"f25558d7defdef660cb5e24c", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"eaf9ef2454df60e02f48b446", + "authTag":"8585a0fec96d63cec281582a2c7a6ab8" + } + }, + { + "GCM":{ + "key":"e429053df2596cb44c5ea87516d4b914d72b1cc31524aad5", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"99f64846b588788cda1010e6", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"e07efaa36ef49b3466ffef04", + "authTag":"edaaca4ea35aa6300f7a70892cc73a48" + } + }, + { + "GCM":{ + "key":"7ca47b650e1475e00731ac8ef66ae405c0749d9d338449e6", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"211318a74308c97b8bf60db72d916656c41b7d", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"79d4bee4855f61394a1c1f80ced28f7cb576fe", + "authTag":"7fb23530369b4991c5f60df74b7166a5" + } + }, + { + "GCM":{ + "key":"ce3359f8f57d748e81424eeecd7d55139c75a973a45be8fe", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"c0", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"bb", + "authTag":"a08b8c1296e89520d5e2272ff2ca8df6" + } + }, + { + "GCM":{ + "key":"04a93caac544f1316a8cf2ca7db94bb5993f88da01b53624", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"cf2d3af3e7e005302f94906a1debef17ac8fae364a22cdb384aed3e4e4aa43d01a22050379a60fcf4491f5113d9e7bc256990078830fcfb93f5166574b27da1262d13c7ccf3c1f2aec6bbcc3fcf68c1265f92bd8d8023f067d05ef2da2456d401dd8e771be87c911e9b2c6470651d3f16e642741196fbc03cfd1a50552c5008cc763b54dfb4cfa258618a48e645584e6e3d6104ad19c4a9d9196c7e58a9877f293d38c1bb6296abadfffab22778baec205a46f3cca278bd5f704b84e9a9f42a23722c512db531cee53b71b50ff634117dc4f0ce9d948d07c3f1cef4bf05c0cf2e13b15d34528a1e3ebfacc1c9c2f1825cd4e81e98ed17ec406d0243ab5b91392798d420b39844b2492a3283de61f542d2e8f72a7bed016b3fb9bb2ef39474cccc3b95d70780445b99428c3b2baf114cda427cf06dcf4b00cb3c624b9f63f9f0fdd30ff5f5dbad1b3a5662b47c7f9a4c22d3bf003637c1cd247d05ce1fcdaf4d18d663f09e0a81e5cffc108ff303d9f1202623908ddc8db0ef938d1a63ad1be252725c98084a8780283eb81dadacdc369bcad328159cf826e1d4dae6db4dc59727361fa55cdb6b664f1b19b96c4939ee9b4cb695ef4569ad547dba45bf437145f781e07b6b7ae9b9a3759375c8c2a7e17a118952b42007b8f068fcddff128101c8d65a271871cb986a9bc6e943c4d277565fce72e49b3854e5c18037ed6ef94b11101fc845a0c8e0ff905cc5a7052a4b4e36b213a885681aacb118203337a066cd31d10d7b442996d335cdf4d1276dd3c8a28f6147976be0dd78d5ca76515ec751a60c51c768c178541c76f2f369e6466734cec8cdd1b6beaa88b08e1f4c24f8e5616d384ee0c4baa5f83ee907a149f61acceec66fbd9abab328881a9a604f2dc67aca8ce713bb074e1416f7acaa388ed1a34501731a3837e94f94a66ad0be1751cc3b1097e1254dfe793b48762152e84ffa83ccdbd615798e0860012217479401ea341221145dcdfed8a8a110d031e45cf904169b281a3c2ce1f4d9c5245084016c9d0ad5f5f5e67fd31274704f3aea03f6b862713e5a52d7e175745e6d33964ae233e89a44006138d18e0af4b08e45868b119f32040620642fa7dceb9e2ebe401b4eb2fdc9dde2b77b0c4cb3dc920a9b0e0d1abdfd5334a46750b7c0dfdcf0e4520d82033bcadec360374cba2f5a25768cb1219b6b30bc9b721b43c46640aeb492557254853e61a5dfe14181154df1c1ff3bf4405d81724d48746b698ffdd11c6bb8194dbbdbc74099a0c14a2af602e95504e6441fdeae456b7fbecbf766dd78c75d9c5f2a20e71ecd768f8d94d68346907e099f9065cd0f564dc99f4c1a8d82049a3e8b18e192609ba2d1824350b349177357b38df3fa656aaa442fd7c969534b0f03ed2c80ef80086961ba45539b3f72d0a77f44c250a1b4a6fce3ea2f469a105e5fd5dc9ec48f1c6377cae0fd7e6ce2873807f726fc38af840eab5c9d55be9843249aed493163e27e8834c350398b229ee867fecbcfbabcc1004a8a7a16c44520d82d8ed569bba3c10e54ec0fcfbef3582d13fca8360248ed75344bbda43c67999efa8be767b7c90f51a94c8aa8d9dfc314d6e55910fb4fb5e4a1c46ece66cc01dd8640fb28716e332f3d1f9b2a27194b9b1087e5673173dfa91cb0b0b8ec26d076b5756a95ac2e1a6d7800393a586132adffc5cc12f0e08fcb403f3dc09e93e6318fdf46bcfeeb5ccd9dda21f767cf7", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"8eb59017c436576d6b56be98199c283808e24f620367fb6a536eac93db9fa0dd0ad35f95b7d91bb3484dad790f18fca0bb5b9f68467294f7ac8659735c8c133d2f1b612abfe3ca4e08bb779483178d3d9d8f581a4550120d76ee2c0d5de01851341165826b1d49946bdbee92a93a52e717ff72b9192b4bd3fa65f7ccc0b917a8f964907e5db20e34bb03dad4ff8784d8350ae0a347bb2753cd4ad0da5c66b23be4d693bc0e89331293c35e28ba8685a1f4eff898beb2489d830d484bac647e068bddc673dd9fa9c198e5e9f7bc002be846c07149eca1d55855a32de5dfe2c6506aadfe24a77ac4996960930b9210d4c1013117231e1f8923e6c95f1d05a3baf86735cef7108c82fe3cde2b48b9d1ce0a54488e69807ab921bf5df14b8758029a52a67543a0ba8438d6f1053c9881e7f21120d63e874e23b504a0cd3f72bbe0c168776015ed38875ca51c639524c4ba4f28dc02448ce390b90642c635159a468f217b976b8830e0df312972caed67fb17d28aab2b772700c56e0cbe6ab1598484087d69861fb485b6720863bf44f5cb4eafd2d2785bdbe3861547fdea026a9d36f40ab9d7547e235fdc192c9224ff3361864254158416f27650dc14867502291f0e9c2f29015f997a7b8c973b5d7236b1c5aa240c1091b926c3d318ff6ab6ff4362f082a0927c92fc0ca912c4243f94eb884b21d5ff46fbf8e08d750fb6bf085cc99f958d37033e6c9e44f2e55d1e65cd050f8ff65516ec9a43a370b9ecf4c741b7997d341cf8f8866e0a421002d77808de7d91dbf48609397adcd31e90efd87db9c42a445f502d3257c96f8c582a1c8a45d5911cf5807969a96856a05f9a9c5492b541af689812a11076e13a6bff59cbb631526c5eb4c583d174d2fa14636cf5404ee30be9ee88785c5559d244731514c3336f2a69fca9c29e569410a13517b720c30503fb645f2d1b0c894152ff52d3d223b5d5d3f2a89eecb53cdf274bfc0920855e79b164ce278f6adaea3366be4e89854a4ab55394aed9de252b46fc4a7d72e1943474d52db3ea374b67b0272978737b1a4841270f1474acf3e26f1767ee636bd143f4b9c4bc548a4d6af1f5c13a7759d25bcb30c4b6cf537436f1f6762d930337fb27f3970bb458435f909eacbf7dd1bc8c681f9a90a3e2460a0dd3152815adcd1461155945e013877c9d08906099c780249db42cdd7baf53647585f7e2ef644a8228e17fa5527178ef0850ed697b910e1de6a36ea7fa94f82f215483d72036b7b22de1d4e793853d1f3718eb27640b6cfc483364d89779b572d0c8915d3b74fbeec93e257f05367cbe395a1d4ab1a7ec1b77d673a8cc1e33d6f82360c47521e072a1846806e2035db472edbae71522902d0c6d286bbe02ad1d3a8f669940fb05975112ea59db189d51f92d58dd148889bcbb91f15bb5410ac1c25aee1fd4ee89e84825bf6a06c9727752200fdb9ccc0abeadbf04fe413841731417a10f1535213695535456447703cea5b69d778b3b1303e5fea1a807ff0c35e8f71bf1f6cb3c227f82ff8381b5c91b8b3976e33c0c3361950f446d4d21642eb864ad664aeb61d4b748be12c39b0f2a39e2c4d22e4c345528b517d81bb131b183ff801971469642937d02b54712f3078804d42b4b07fdafa1399d86c9e4b079871470b187d8889c050cb5529d3c8db1f7dfaa1d98ae4340ce15fdf73ee340a364ded65eda4579f7b827ed9a3303e3b707143f89b8f7", + "authTag":"d3872f5aeed2ff62f65f17ec5b943cb8" + } + }, + { + "GCM":{ + "key":"e389ffce8d48d535d0e7c3f2a8edf831ec25f43b2746eb0a", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"0634635bb21eec980b0c3a9a93102ccec02253635055b80f6ad9b6a96d1f5b7d4e6d0388dfc43cb467fe69ec102a6e8183ec7ca163d1", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"21b58a0305dc956e38a43ea89721d7010db377e1101952e1acd580771e8f00ec47e5ec41dcfad5ffca1d8bf1e0b14a11d65520877d10", + "authTag":"4432737a44b2e1313273c352570ac928" + } + }, + { + "GCM":{ + "key":"29b88f568dca24e25b2b20239e7ccd6355a47632c1e86944", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"f8ccb9af146ecc475206c598ed944898", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"506b50bbf9a7ef99b6d332b4b37f6fec", + "authTag":"8e785ad34fcaaf44a07a61fab4bb939e" + } + }, + { + "GCM":{ + "key":"c20df6f751c16eb5c31dd11aa40d2e943d3a2ed6223be307", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"616fa1cc8cd90ce1bf12a604c7e73cdccf76baac2d924e1f0d210d18eb5fb08f", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"6c5011db4cacd565601d4f06854de34a938cedef7fdbfaa1e5f2d1952d486438", + "authTag":"6374049e4d43d374d3814640fd97a389" + } + }, + { + "GCM":{ + "key":"c49d16b3b9897e905fc8d8dda54c9bf1bf597b44045914a0", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"dd2acb43db809fd9db6ffd9a7605f27b9aef67019abc4de305d349d2f59c8731b2afae0c3a70f2a03d4d240664f74c52f11a86943a67f6b7257a12274cb848b51e970354c54ab1182f79aaca7c119e0bbf8f7a64c86274229b4a6e3ef51ca0a18f37a7b52d67e07306fa4cf7d7b0ce104a1cee9b4d9f0a63747642f00b4333f0444345ea41135c27dd18d6853fdedee38f107ed0592501ba5fe07ad653985f6309a8b28d7956a58af49b3790d7c984cb6d69c0462a9e191d25b9957691815030b7332068b1df6589858d04b5582177de8d1118db24bb7e43f34befb81a7acf33c6a0743f31d81745505a067ea3aa11a8d9c7fcaa85fcac598d4d13b4c8cfa790", + "aad":"ae114534f0969ee04101ab36", + "encrypted":"13dee3777e78dd019d10940d19000f1341154cf96ac2480816b057d90fd29b4a943cbfd5139ace859560417147e84bad1b7361ace4550618a7ffc99ba2089e61c248fbde0a91eee5cc722d57939ca1c571b31d634c7314865476acb7d3de3f4b45b8ba333281e74e22d4eb0b24c1baa90db5e395beef3382a39ff9304e8c92b1170aa5e3187358ecda2acb82bd9a5c33e6661a53946cc23956d798f0a839e8269a1e0c2aafd41f1e0e1225ae8c0ff99ee17edb0c31daaa38fd41bb6916f410c39d3371d0e53fd9a9b1f6b3a5218bcd7464f199488b8c2a477e5b75512d4efeaa1a17a4ebcc013e7cc46d78559e9dbf1a74683d5a3b58d99fb63fa12c1da27528", + "authTag":"60970f889cadce70d5d8b7448427eef7" + } + }, + { + "GCM":{ + "key":"19d55823aeefaefe9f2525c42f8afc218bcecda45a791b9e", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"e6bba245578ad188bc9a5cda", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"98b2eacbc6591e60d670e6e3", + "authTag":"27c94a46789db814e1dd1d3a3d905d60" + } + }, + { + "GCM":{ + "key":"9e212e79858589b5f5173f8add3f95ee9e9da0663b75f604", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"b045792e67adf5447c4e03e2", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"785f7c35da158e00eabe20ea", + "authTag":"114de2d98393e203844fe5a661c7e42f" + } + }, + { + "GCM":{ + "key":"05708dfb36d3f175bf1cbc6bcadb264abd0aec29413494da", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"f633a785de1cc9d7fafb2c91cbb0480508eb69", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"97f139b5cae0719c119c39ba70dc311ddd05d4", + "authTag":"e6eaaeed49fb1c095e2498e86ed4d06a" + } + }, + { + "GCM":{ + "key":"af27396df5142ba5a5ecfef5811e3c01b7651af182b17b82", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"67", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"05", + "authTag":"ec1f021656391d838703949426854c76" + } + }, + { + "GCM":{ + "key":"9447df331e35b27ae408909ae5e9fcdf4567b06bdb951531", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"924cbe1d0c07aedbd8ef4bd2257b6da6a1dca308c6727787bf22b42ec8cbedb044cae7ab662e743c962ce6a9988639ae12ea08ad33b24385856d7f7c7302960056f05e359e85a1c9ac1e3a8bd41274bf79a1ebc70d898bfd90d3b83e3e713c67304c1e9b9038c9b2d1cd74e42a2930c6ce26d41e25985cfb2f28230fa1138cdacc596d2c1998bb7d1654719b3095a81175ed48d7d81020a6d578b3e656bae1feca78c4ca2d396006a470e5d447bb162a4e907c0064783c7fc115b43471425546502c0a20ede6ff68ad414dbd1b8c3705e541a890faa5c533001f33ace504e3acad725c90e5b056ef7041154def1f26fdb54a8dd474886240c3567a24e17c09ef748126450758b9552548f8b3b6e4a37ad3b746ff18cd35911c4fa1d37ae713ab9ea787b26be90792153fbd610940fdca76174af650a611cf3fe04bc01ac1f899bdb02086623447734f3ea24f07c6b6ad0fe918fc9a4963d98b1a5ad8b9cdcf6efc58663c2a408778516b181a510746b6ed1e5e70a02384b6b28ad3a9ff1dd544f4c20f3b4381970564eca036dc7020c5ef936979bde556ffacc18b321103f800c45f7e9167739f896af6ce5f88cf006b29d83be8e4723c3545e16163b17afbc8a65fa4607b1de85d747e690fe6f23bba581001efd0b853df6b539e52a9464820e38281efe2debc4858ddd7e002f9340152a2ca6de8cab1512ff30c20a1b6d398f2b45a2ea06693f7062c3c20310255bc82a80bf250fc34afbf0a4bc326717d701eb26b2536f6437d0227bd665fe531f377bb8690c5c9550077bf69e4f93cd26dfe2199268ab6cea1dc4e85bc06de7e2c8936fe7901479310f848ac1eec418932f6ab834b7bd1c994f0296e64a8a57c578c546a4696547d4d9193f78d9e4b98ff1f35d3177e6cc0ed9049c59054d8f7f319c6f2def06e2d3ba4966ebe023a3dd8b10a173e59f3f80067202044fe327aa4aeb2b9aeed651c7205fc27d4ce652e3724a9ff12b75b989e0fd756c513320e688ff92ed47fa048c2796b83bb5a98b228456ba159c83527636527f61116ffb77124485bc6a93c7502b925ab2d43d7d47fd1b7dc9254ff536825858e2e7c7f715176eb965b1466b63d9571aa14e7988d1e3b96163f575b5bcc97c6b1dedc39f2485d977d9874ee1c92bed76ccc13c8286c516c265a49df1dc517b49600ed96bef39e099c126abd774f90ab7578ba0149e365e9e67d35246c0a6150f0058e747d80aff90e1149db0e195bae98acff2b024cc7bd6cd8c3f6ad7ece6fff37f212b56d2aa33bf63fe315cd484eb64faed848058927f30793d247b7c3eed0d961971c8e70a4282ebd7a8a15a4030f94cd469657426228aaff06568b5bd4473d4f31d64413668cd2e83c4524920139e092a21fc207f168dba1d4c0d7c5fae681c7bbfc45373666c1f1515dbb998b3e22e66f67a36f6c6f0e137c98269fd8e71dcb67e87df380755cd8a5379450124af6c00aa5c3630f70043576a5aaaaadcf985061d36eb0b17478c3e9de25e70d3b929885805f023d29aef10a11e45f6038784d113019710cedef325c64a4343c8fe05e0a224208aba4b8253183efeeeb1513a615c5341c9b435d4a82f17a6b2b310e6e24c80e9e8d3bd6dd8a73b9723cc5de9717c9bcec11c82cb39098b2ad799dc87e20eba82277ef91addbc2380e8052aa7a3ca326eaeb4feb6ac8b540e14c96495e74146946306d30fd5a07c76896bd305b9ca5ca29e9", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"8ea9560623560abf7208e486b25ce3c8b9b35e7d2b451a1933d37a413d1d40c79594c6be84413071ba7a2764f71c69dac8ecdc9aadc883eed6cd5ae5c796544b31e153069ec979ef599dc882545cb86c1b3f66a7c8a0073e504928eb21cbbc9a2deb311f0beaef18e3eedc063244ecb094fe15e10cd73ee0ac7f09f7b1f83aad511561a61c409e36938ff2de4ffbf576271655c36d5a57e1b39255c3f59db6e85721f4bfc1a1231f33ab5ec926cedbc9cbbf857f8ce4c161202559df2aa21c39d2139220ef37708d687ae4eb5a5fd59a0b90d760f9da035d85b603d6ebb8648aca60797e6c8fa37701b1dab68e784c7fadbf1c57ca9d338d4d7e9a838f73bb409b3e1a89f43b6b531a6d19025af0ea814935da03a9dc9099ea409feacb78bb2155ffc85aac5a45a8f7dc8c0182f71d16b8e93ab917919cddc9434d2092ab003508fa100e0da569d9bbcab11359ff55cbccb266a7f1c533c290c6c26d8559e0223daea19ecef99f4288683fc765a1e0fba1d6c1401c6dd3d1fb0d51cc8167b74cbe80c55b7f13bbb74a33979dad4d4b2a4d23ac578b1b2aa3d2c63920b78607c1df83e6aae0b383e037e73a2230abce40154055c2a43bc0e27370d03e93d5a141b1fde57dd6a76691c8958e7711da6042560c4b2d5ea585801b1f97db52609e19de9add8bf7e51ae7ed73f6c331eaad40fd5c1eeceb15d25393c91f098bf8965db2212f4d122a65760b27d4158138ba00f336861a2efd3bd6b3df49c71217bcc210d728e59711109a21b97f7a7087227a5bf941e0ca73839fa272e03ba73599958e0e803ff01bd6002d5f0e4c754240d79e0cd9e1464e9ebcaed1d9fcb8da97737f7b024792f5e6a1fe17cc8a52c54884326c7771d9974988cde50a6bae64a1bbe1885e268fd680450bc18ff302c1c85320a710d792ca6e403bb5f5dc8a017fd57809e058d09651ba8173e3c39160cabdfc320bd1cc370e0391dc4c1d1f7174bb0000470dc46c2fb6fdbae9200ca1ed91cabdd403587c197a845f0deff56b1d0b82b35a7d5e8aa3d8519c14d0754fc49a74ce4a157592936bde74abcf5dc1a8897aa553b07a3fde1cdfc6d2e2839e519b1bd7cda751712592132dc71e8a5b64a5b2016e37e1c8cbb775e1290378e2fc9c3c926d001a0ff38e2448ddd0ca05ed83fdb1353cd65377a4dbde2cd4b91d1e7b855f113442e81d988a6b9214d6df626e80c699b1ffc81ec4eb6736eddc0e608acea5a9ab2e47b047ae01e57208ee7937857eab4f3591f8be844636c17a39e3cfa44ab555ee077d880d807faabdbff2bca04df83137f56a4e47d6592681578d0e5b928ac54c21bbc3d8910a7fa7a46c318290083316d669633a4d108c4835092a5d663ce1ddcf8c1b3677b67a7e336aaeac62eaa7bbc4995576c70b213ab7d9bd03e46229c571480891e554936c30ff02c999950b50267e13e2fecf7be7ab52d5bdf9558a2ec0d8c54d464d5e1769867eb5449843f02c09e380359df7857296ead926a5e9615234cd7e95afc0f23475c87a352bc5bee287aa3d76e5e3575ea4b9486cf32315fd840605b321908eba3bc10f0a75772bd3866a169ce80b0915d315555c32fcfe1e280508a4c3cb7e369370bbab0944e84116da393d7b849b2b5542d0f608d2bb7f1ea7d01e8940150e1cbb3a76440b94695b97c89591c2025bb0cb74289a6c4c765d9332fe00ee4be0d2fd8b3cacd456037e4e686059ddcdab8cbf691a", + "authTag":"59856ba269344246394ac1d140670532" + } + }, + { + "GCM":{ + "key":"77c592d4581357282995a5e5ef4f5b79dfa893c52d5dc866", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"95e804488f4cabf581366c0f06e1b50127f4e112b1208da69dd16b175dace0ca22137372bbaa4e9f9661521dc8760aeb00b5f6dc1be3", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"6d4398c18638fa01270d42d0240e7bb5c714403f54874ebfa92eb4746e6b82cde99ef81d2727b5c3460e30ce954e53b3e666208c3ece", + "authTag":"03b77641df882b2e7ae78e12cd853f9a" + } + }, + { + "GCM":{ + "key":"04d630b5331e5cebb98ef080c2d1a698c3f6f18e70b5aadf", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"e7efa57ea3dc3f7e36eefd843ffa5683", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"f8f6c895cfd7d7bd278211d2d1f92f19", + "authTag":"34c0b05350ed3fcdae1e47093a139fd1" + } + }, + { + "GCM":{ + "key":"20053a622ad699a921a9c9d210fc642c8e3196382b45a48b", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"b16f0b388a5247a6b5719375db83b060c2ebc549aece39719ae016d0ab2397f4", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"7370010c05dcc6dec008ff321894f4af66a914c4daf1efa9bbb6011c43874a1a", + "authTag":"c13b69288a55254cf56ae0470e1e34ea" + } + }, + { + "GCM":{ + "key":"bcd23f591506e553c4ab17302ccb463c060809d5595f686a", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"511ad047327be65c02de2767204cef3546b622b89ca01b8b74cfa1994ca7b256599d4c2ef3cd4b031d2e44bee4506147ba8fc01eee75698a0e20fcb5eb14f348e664ddf76e6fc726bab1b53ab4faef5ddd975e57ed00dc663733536505754f8a1eca90b5388becd9c16326c93150f4f53af9142b27f18aed65444a47e25a8eef2b79d890c96acfae85d0a7220d2791ecdfcc7f2d3ed745ec91308f097fc9d917a64f6cae4dc4af7fcea6d0e6b4fd6c68b27b6b1ebf15db414c2289c30f971d6cbcaef4dfd960796e6cd83735a1ce9c59fba3dfecc999be8d1bd196590c192a4ca749a5bfb91f2b72f2491a4b6b8628bf6897836bb9df665d70c7a437872e02cd", + "aad":"6b7c54bfa9ce1d17afa6f203", + "encrypted":"0797bf2c1f3f8fe2bddd6bd4781e838026ba229053a44626c59684a20d57f457a0f21872afa6dc89bb708b161ac0614d0283e54c4f40d1508417e1e2493251f0aa3a62b5e21ee1567a8d0956f55c8a256c661ca6891b08426054b8649eb26fed8e34d48c96b5afab0863bf9d4038589d757834503601c30e2b01e8e3e4488b33fd5559a00bd13fa49bba1d8bf815e6b5a70ec789372e40132ae7a6b426508af50966bd5a7fe729c114a06ba6baa63282a1b509a2d08704ba4285c85b8311fe1b7ab5f971484db15d72b6bb426125fd8d319a0915adefb4afe22152835c1013af86d8be102dcb53923137c0cf5728b401079f66fecae19427081d9a1774b21143", + "authTag":"e893825ccb672400cb12b632af6d6a33" + } + }, + { + "GCM":{ + "key":"4b99c3d655248a396326ef70f153b338b65c039fd91fc22c", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"206471ef630011dfd54ca8be", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"7b9c66927355a64f538d925d", + "authTag":"256490ccd580e076a9451ff04e04c940" + } + }, + { + "GCM":{ + "key":"8210a0199ebd44f69d3e5ba558938677796e6d4417af2ad8", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"422403538957d2768ef3596a", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"5a1951971c1b25d96edc418c", + "authTag":"54575f8532c380a5c7bc1e5dd81ff52b" + } + }, + { + "GCM":{ + "key":"ee17d26c23e473ac9174d235b3d5aadee1e1a496604de6a7", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"828c630b32ae210b5d10ef1048df1de9604097", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"e7866c56162d92c65b29972148cd62a61e1c58", + "authTag":"e2809d9c88c449f222b90f5c33771300" + } + }, + { + "GCM":{ + "key":"7e2266a60f83baaf55b7fc38d7e239aeef7649ebcddbdaff", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"10", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"54", + "authTag":"b85221edfc6d96fb9904bd4ed4117e54" + } + }, + { + "GCM":{ + "key":"4ab4c0e3e9ebff2186aa4e56e17335a516c8c96013c27914", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"52d664c74e3f42c3b0e0ee8941bc5497501eadae48f7c7a5c19b792b31595c9ed1a39ee2a051b1d045f3518e07402d9470d35fcf9703785fc03353308a3143f62d01d0bc24ea323e6b17d1496b8d788f4dfe63033b2c91be1d24af5512fc85ea791b397ba380776143597560564e631f69f7f906a0ea7778c3fd1041230e45bae9bc5c3673238c0ea1525e1b89c26d2e434c0ce641a094f75cb31419700b0dcd33df198969574e4ddfcd2c6470682bd6b9411ae916dd730cada14b85b4fe75deb3ef045193e6130eea1c5852fb92c8f54180f6f85e46661aaa2a55bf6f1f3f1311c5570b279191d677d1c48d6a8f66ae231513875c079fb35e658746df1da48f94cd454f3dd3ce0d4018079a067a1883678d38787a6941ab44ddfa1a30c78d6b664cc703822835cdb80c9507ce28eb8cef5434e223006e97953852e9590e07aaa2fb4434893853e435301cdf95d2185783fe46d6644b1c62c53c87f7af65cb5829c14565bfc34c7e9ea5bbd406486ef04a3d8130a54387cbdd3bae5022a219752041912211a83757489c12168c4d7b0e517fd07a08a2ce1b09eba5783d82996c1dbbbf2a538663505b55efc632cdfef4a652bddaa04721610f25be2b101c30b49a243065be1f2489723530a30060d1a546e3fb168fd0fc8d4efe5b6a6851d0b33b4454177d83146c63ef53a994fd8364682ccc419771a870ad55774316665ce32319c2f04f5cf43c6c03ad1e94f224207fab1b9f18746ea6902dd2cb9f87e93f295be0d50392272b38248e095f1bfd286320727b4e795c33b7590fa15914a781afa9cd6227b890091a03e10366f8a038f5140c0cbfed0f8710d438248e73adbd5126774eb4574a24aa5f5b7f76611edd8baee28a841f33af2a6bc6d88ed4a3ec95a82d656a72e7f5a739ca19ad4bd55d474806425299d861ee02d1e0d99182519f1dd648aebc1588c4772800023c1e11f13815fb97a95823febc36fe3f3dd59101092fbb4da18b072ad770be460d2fc55d8d2694d9673014156546db60b57ae02416e4e00c9b3a8978ee35077f3d238e2d36095b1560b2271ae3f07db59ed588123bbdc8cfcb5a8c408ed698587edbc359a60fb6fd6a6364edbc72250a8e8d51b4323f2e019628067ec0c25fa547a8af37143a6dfffa7a03b36e8644aa6b64f8e214e1b4112b1eda161c840b69e5296661083ad6a079558c93004db737b566ebe033e0c2b2909775c4f0d4222f1c9503beea773a481576edbdcea36f56546f80b747b9b81258d9b1c42b540a6c787b38b1f0e692664551d2487c45d18b2c59c8662dec7f9cd302091ea3a6e07cf3c666773f44614d555ee69f39b82306424a841d5588645e4c53d768b3bcede6eadf543ff32d9c64d2f24a09c934d883f8682bfbe5940aecd6d77a000d6bcef0c1775e8987f20537929e64a40b48e27de0e3d9aae7f9fc2e406d15c0212a092af2f8b6fe9a74f098fdaf61f903ae972399730a79db4442ce6342c8a2c98263b33affb23b182f0add3d834e37f5aec34d80be5995ed07004cd538fb4b4af21698a46362e75b08bf36df0d417bc727376acfd22718d409291f1e45d9e692068dfb1b5e34e4247457c9d8a146117da39d4a9097750beee72dfd368241817170cdad1b60d30757ab0b815666c72e0aa334b9eb167360da567d462cde2b3544caf0becd62a7bcaa5240b8de6eeab518e3779fd6a07406efac7f7f05b9581eb7e9140213716aec3d", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"c4d765e24afcb97014e4b561139277dc7101346d38dc1000e24f2497c2c920f727ee6b7f5b1c39f0f40711f0fcae6bfb5a4a1558b779d63d496999afbc9f3be4134dee6db08883e1240fdcaa78ae88e5339d565f2ffaf27ce20a005e6fa5f28066cc68cb653b8272eaac01a12435f7cd0fe3d0f749e5248cca668bcc88f5e737e0ff957e65f7a21030f03f7310b23e7d9dd542aee227160bfc2f7256e6e6e330ccb22f89abb96fea3e761b63a411f525e3c6c61f4eed31592d6c06950ec433f59d4b9cdac54b19d5f00e0ef2279a740db50c37a9269b89b2325e272fe9b8fd8cff1c47248937ccdd584ce8145cc833a0700ed0ad27cea7891fe33ce4d6eea27f04e4d3446692ff5f1d3436d9e6be719de90a1c4d348dd0429dfe1a5deaf5f9d78382a2a8caee2ea2bdd4751fb61eeee5f4091072de3406d4233845bca01b382ce58a37cae811fb41b02614d5164f5dec0f129ca466706b15ca1c93e6529b7257953490ba671a2228c62664e660d301ca446e58bb6a5481c8e256d1d281117cb063a3969f4ad69760fe57107ccb027b0256fc6f7bb2d72f8cc7dd217180555f02af9eda04f2cc228e85ff624e839f7808172daf702e944be29642d21aa4156d52375cf0c50eac97192b989220ee135eaae8f8eb61712964fefa314980053384ef7779f1ca17c86d0db451c1a5871d013202787cba0fbf53e82a26cda4268210c3b465d7f6db67ec759ca91e0cb7dea99a862a1144ea3f3c3b2ddbda62e8687cab7931c40114d0dbdd6952c753d5a06a145e87af10993d542e7f49341ada635c13ef4f74b9650159517d2bf42835cf8a09627e7bdc186770ed95664590c09c5a6b31ef6563e6a684f7eefbbc738a23167160ba163218887dedb337dfb96e8962e8bb4a299627daaff89a30f8e901e5ca0a189dd635cadde9759ca6dd23da5a81d766ac9f6b5b589fddb50e89e5e1092e114e90bea2478f158439f5503919ba0e34716aa3ca846e0ae2bacab06902c938b929a9ebb78337502bf2ffdb6bc060a2718596b919feb94eed367bdea9729e3d7426c3c1bd6950cd3ce867d79a24c3f8644d7cba0772e30badcd4f261034e0e48f36670a95c251c08b1b04ac1f43290da878d084b7218543bf5d9cf4fc651f57b3f8eb65d5f799af399f98a4ca31633c6be0fba2cd9cf915a57834d90925e5768664beda839a509d3cc49f09d3edb4060be2e86443c260b9842b9f7eb896495c9de34a90bafe4a632083d8219a1369cee60273d47895790839755a2dc1e300c2e6419dce3391f9b65f2f43737a3cd648628a13ea562d57140a1d6d291773c7e009b65399e7b727d3dd130b7f44be3afe849f6bbeaa00668378442736f38a6186e1bcdab181b141b7c144bc261c934c6dccbb26743a9726061ff7310dd577962430994b349271d50ad5255f108f98329a5dcdfd416099f89454652a213c7eaea21b44abb6e96e23b856cf60f4a2f71b898b6a8f5d801437f363cf3b5f432b313690240e71a87d741ce32e20183d2b77afea0e5f68de91b0720276fbb0bdf42bc8ff62a31bddaf7da5193a7d5270d50a75b2a2ee96a0cc083cd9a16661edca208611c5548a33ba08580e6014292dad9b9815636a172bb280dfb2b67b648e93cd758aeacd289610847d6e3ef9520cc53d2f8c07b2d34e117fbc1a8e11903c9b51a427e506286209fd83494e15f99cd258e8ad49e820fe14eceaf782b99af93ffb09f03bfb", + "authTag":"4181c1ff25f1f5896445a1e994452f5c" + } + }, + { + "GCM":{ + "key":"459d3c7b607dba4c62fdd378954744e54888cf8e4d3d432f", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"0964a6ef87487112f556a1815f870c5c0bd225889be55de1773308c56cfe887910e7303cf8f1bd9d47ebd6393088f0d0ce59e97a0108", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"70c32cc5e8f735fa008811c16080a730ec10bc2dd7ece0c31ddaa215b207f01a52e3b969c86034e9e3725dcb1170917d2551273d5507", + "authTag":"a29943fd3642ed43b5ee91adcf91d4c8" + } + }, + { + "GCM":{ + "key":"af8843c286bc21800ef74180121b8b2079add2d5f6f2994c", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"99eda4aa25ce51e654725a56aeba628b", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"b3c6b8380857588ca7bf959f4a81b6d6", + "authTag":"b762d332f277b08231325b5ddfdf2cbb" + } + }, + { + "GCM":{ + "key":"bcc5e66efecf3c1cb0a30ce1711b78ed584f716eb07edb23", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"233c115715d187e46775f66b513a5dccac1fa2f02b774aa238d52d543fd042b7", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"cb7efc778132c9760bc6475960824a3dad14f66b4ca4028021dea4fbc6413d53", + "authTag":"9b4a1f05ac04f937444c37bb87bc2675" + } + }, + { + "GCM":{ + "key":"46522cef110335901bcd9c12cbceac3e7a98c9f215e6501e", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"4178468a9654ab60c0f4f74d3938a45f1000b973fe21249a87b33bfaeb6db8eb90fd7ae5dcd2fa9649048f2a2ed43382dbd4c19f606534d3c39ebb015a0ef97a8356e86bc7745a2f769fbfdd75e15de5c1c6e200657b99ca1acb0aa418e03b839a4a18b1d2fb45b10311b4cd0b071e8aee2e27ea2a3f1235cd941609fad0d053b714b8f4e12ced26e9e655f98675c9920f016a66b16766783ff05c26a65cb767e61e53b3b0f0cd1e94ffe42471f7f173a5c4a176a07d6e9e3f193a393250f0698108f7a7120492f0dc0938be322aa788ca7d1e38fd3792c9417fade23bda11787b3fd3af909170646ea48cd11240ec4b446f5b9d72fc41c561a343e865de11ac", + "aad":"a88d54f947ff9a55e8d69adcb7f55fb3284423", + "encrypted":"361a14187b21230258ceb42d3cdd1b5a34787c8d45105084e8091d6771435aa98de7a69a2be724b3acc6a224d49fc596b0950466586271ae5df3c27cb9d4555c8a1e33ff6f7ff63f31e7fbbb12839b053fa21aa6f5b37e70ce95f80867f5a91494e30e6a1a530b5fe8b22371b6773ebe7abf51bbdc07eb54938aaf71dabdabaa2fd32954735ef430fa85bfcdfc818b9a1552ce8bbdaeb2b2f2dc37fd27e80ece75e9779404aaae2ba9b10ff8e7fdf1b04b079ad5a5f39562181578b974526e9abe13e60d07f801010fce0b3f23deba39a1831ea9dfe07701b2ee8a27926d0745d5ba092bf8a493c84414f80cec32dbff83f6b7af2524ac9b3c99aa78a1a50004", + "authTag":"edff77a30b815df76c8bc21d310e52f9" + } + }, + { + "GCM":{ + "key":"8b5d3171c790e109fde7f503019457749343a2d765a61c82", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"b5df9e80ef934a60261459f2", + "aad":"null", + "encrypted":"3d35c5314ca18c60cb3a2c44", + "authTag":"b11a3e8ba4a5f89e46d1921f38e1d774" + } + }, + { + "GCM":{ + "key":"4b06fdfc5625014ddc63f8ed5978df2a8966bf5ef4ede9f1", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"6e69a8551edb8ef213d97411", + "aad":"null", + "encrypted":"0634a480311e1a69f9b30a03", + "authTag":"fcdcc54aea477dc34456ca31adc752f6" + } + }, + { + "GCM":{ + "key":"acf36065cf830fa01f50ff18c84ea4547488cf45070b39d9", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"3ef485722f4e2b540682854cf3a3bb9f52b45e", + "aad":"null", + "encrypted":"949cbf986b3f7d7d19a0124584b83fb58d0cbc", + "authTag":"c24d6902474c22b6be184567e2279679" + } + }, + { + "GCM":{ + "key":"fe4d9c98b608c1e64523be7e185e97d1d7e6bbeec82180d1", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"c5", + "aad":"null", + "encrypted":"d6", + "authTag":"bead766766e49df7f52a10e00f87b9fb" + } + }, + { + "GCM":{ + "key":"e7e83a436f192b5834f7ecde003ccfad19bbc181171b4851", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"4fb80b3a29c8f1fba278182ae60383e111c519e61352cbf3c8758aa51894387b846eb37926248705cc29cd8e4e79c1f56ca2ff40e1c46654ac5d2aaad36cdb46fcd980619df2ac6153df3789bb67d65b08167cdf107cccd926901cc75195acd40dfe932fcf2eb2419befb5f861515f3848cd6cd267b99940151dcb60d694093b0aa4ffa15a53dc9cbdacc37a7ce4dfa86ed5ad1f0f06b40b253303811e6b950bd7e7224c80d70715f386b7e4863e0174aa47b4cfc0c7a57e8525b254702b4ccfa249de8b588b3bdf6ee96ae5d532229a946c61de2284a9fa11e041c4b4316081db897e135107c9568f898e36e9b2ca671e356830f18021f6fe0328465b838fd6444118b53393deffc66ba5bc8f55d589a744dcbab4cef32f5d83f6b09a9e0a30bd9520e55bb2e44fba4d539acd81622fd800010261bd981be0f6dade021e8951f4461d976ab58471e2c296a51d53ac55060c570e8991bf3ba2acb083cd7535daeec5c9783083a86294afe88d217d0f1f8d00d660b112950f886dba9204c99d0f5af362ec8eeec857b557e885320ac34e3f22ac74f445d88081090b6fbe570341be912d72feeef493071de98ace2631054fe744cd462c357fce8fd16b47c4357a98cb4fe2085e3dcb3c1d0063c27427e09d6c3f6edda326b5aa6ca655ffc4468f7a4c242db76965b8589045ef63b0f43824e2abb765c53e0f5d0629f8f2b2feebefca6c9d72eb9522a8b175938102e06f3d3417018fba0c223783243fa4dcfb1d85989097e6bb8dfbff8c4040913c20177b15e16efa9c7160dc96dd4f5a43fbfea1c51c26fd1f5641b0396ea93c3ff4d15c66f5f548571e184c4509b876872413122129082ab7f19f7ad501e56b429ddf45cbbe2754cbf84e8a0952f66ec957c8c27f9c9440f1f16d2dd21aa1a4611fe90181013c2a4de3fcfd9afbe2c1098e89f61586125b89536aaf29d02bb1a357852e1b1827db884e85a4e031c5bcee7ca1287b49348d2ab7d53aa66de2560aed4b92d624d7603f25b5025633544587e4e905625a8ecae82b89ca6ba3f27945cf7e245c1eaf8da1b7bc43e7c3c26a89d46459d0aa1ba1ae2601698aee5bc71767ae6a3e6e089ca9316e159530ab02b5b9693d1e1034862b395c7ace55e16708b405188742806b266acbc964862f23b2fd3f074761b319b6e08713c5238457afc25c9425d9a9a7109ccafce37846b15a98cc3d2af1cf871fac87e74c1589fa5e1bed69c5970abe4d4e901abba5be29ad56e85bff358ad5e17a14f5f6e1baf708251ec52805562935e71bec046dcf8b0982d7574485057cc03e0484322409dd446e1b0c3b0c59e3d43a153dac1bf39c40da4f8357da7a56f8cffe87ef4f61f5e67c05d4972670ead2ae5fa399aa284a6176796b99adf5a6b860499d3ab4084a495e85cb02a7b4ea407e45aed5394fa64babe26fb1bd3f9af2176ae7dbfeca51321413886783ac5f81f3175639cbe895b5b132c8392dac6b9a324b2e4e538197817891a679fba234274d856317f1e87a11f0ea652b40deef3dc091ae9ff51005fbb8682c2f34b236b22cafdb381f975e563ef75c0d7fa323c95ba5a6fc412168a6d6a38e28ee98b64639e3d77bb0c9511e6baea030d4e3cf22dfff6ddb08956ffdb16800d99e9a9936d2c77cad65d63c63a4bc96e5796ff5c44bdc05bd93980c8586faca49b1254d8db0c407a23a69ec1d1bd680e24778849a72fc013541429ee1ce113981", + "aad":"null", + "encrypted":"c2a13956c634c3520b4a926c162fbba768f4b19bf5dfb2f2b1617dd70ef36beae489af3b0134820d855949d0c4c54d3024fb7d3478068a8ff605c0ec8150ccc67d416f4392cbaf22a7fa45211748c4cc48e7d2a30cdd673101d35237d94741f6c5e88cffb79a5ead79841453aad4b61e27db1dc0ad7cd60fcf9888573d50baa5a717de89a46f82df2611569e5b0cf02f7ce9d6b1c1cdde565f2ecbeb1df27c8d94b16f8c1fa57b38fed20badee12743e1fa35b1496ffbd9c79782117f594a6ae427f7067bd7daf7af38ac312f233c47f37a7be525c3e84eb665e4b076f4960ca36f29f29b01c9a21a0dca0e57fa6ac45474cfa99100a24fd01f36d8a81a5942263afdbbbd9a0eea0b62e85c47fff57816afaf4dbc648b9839ff19423bc953fef6f893f351b6f5dcc7011d8b946f10e778dc6a4569d072614a3d3c0633ad13a1035a0183a14217d90c8a072735dcf3ebc2d120bd0405d3205935940c053018e327103484c29d5d9dc21dc874f8fd5de3629f24e8069177046405b389fc784864599c1afa3fc8a00f4b315442c94b15dc877fcd7bb23c6b77c4f46b807a75e803b09e5ac65cb10106a4efa51e83047e6120b608bc3750a3cef1fb398fd5f7c76fd3ea5eeb798a4ac84206807d7e5d9149ba14bfdf31aa857714b9579d33d93b5e5e0208ac84c0c410e4cb8613e7fa13f2e9d37e2806fdcb6e2dbb4f8d7fc763e42ae8127dfccc6448c6a5cef0803f46979f3384c5fda5bae803190086d425d176ddcbc1c7b5b0f715f68e3e85ef23f45ae08fc83c1a458c163b92812fdc24ffd1e8718cdd4a87d560db00f55951c88750792038e473fd0edd8b746829ba42e97ab57b278aa70beb869c9a78b2d20a1b08279f5919a17548a4067706369c9d150f8fcbf7b0d5740b561e3e5eeda7bfab4f5a4b0a790b064ae0e428587edbf7e7beb667e5b73c17721dd2e0aef247905d254200f4552320d7376f2d551f938c3d15d19ffb9bcde981d18846411bcb965677606c32b52044780a1f32a6b50a6c7c62855a9d6abd390d61074636b886b7a088872d7696f5445fa424f1db348f48bb94113fb08541b7fba7e407177756b340d984d1c5bb8bf47a907529ae030bf75f54115ce11b6498e82c082745d98b25b9afb0d10402db6a500a8fd66ea2d0c7eb44d24187af43231c201f293b75582bfc84cc635ab5e59172ee9f3d53267eecb4e1fcb502ed0dc945e828bfd91f7555d951969669c6d8f005432a2b34f0dca375cc9c68f2fc6cf27610c2e211eb4704da414ceda2d94f08ec2f597ed7a9e5b918176ec6668aeed52bfe72e7b8a26bd2a6b7ca5d7311ae5489368243e21a0d2005e3ef9060c724ae9030b018a817ebffbaf55e7d8d69a49f363ed3ab22d06b0bcf4824fba71ed57007799686200e402efc18538a75cb47dabb62c5a40cb8f8cc7dd694156966a8a36c68b13af5936f241f7cc657f30f719fa75e721979272b004ad2af3e6780848bfef05740f21d157486a7dcc4eb3a5146b34f0123e46c5be5318972193636a65e31c6a00adff4a244c393018f49cbc31e9926ef0683f716a53d425f66b71daf674b61f0d46d8b6843c9be7db4cdaa194ce7d73daa72a7cbddf0a57448fda77e570db07f5bd09634538f74b38568d874a8fe7abd2ecc47e7c93095f482c5fa6a6cf13bdd4bb62efda9e221d3207a6563c57d29a784574a47e00b65df92143c9a5cf3d8f60abe469d9bc1d228af5", + "authTag":"40b1546a6ecb0e76ae99fb0add04472b" + } + }, + { + "GCM":{ + "key":"0c967026f56bf8fae6caee43aa6497548ef9c0ccd214e1ae", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"357374e7ceac910483a923bb9bff5a348035c0ecbb389257d36d15afc4d1dfe224c9e2e28b289d2d450542d41557c50dcc973ddc19a1", + "aad":"null", + "encrypted":"7052a44830262c1c4396e4aa14dfb3527894e87a02d19b9d2bb38392cab4d3f8701ba41bb93199d05a874f573c2a5356b7b7a47d990d", + "authTag":"44247e9e109a8f2742ec3ad256d487c6" + } + }, + { + "GCM":{ + "key":"59c9594942d1a380263dfbc23396f1423eb4ad2890a38085", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"fd8cb94996d091571d763d773a21dc67", + "aad":"null", + "encrypted":"cd6a8f50396cfbc5e0f954cc49215927", + "authTag":"fbe0fe08ca2ff1b58e9731c5f66d614b" + } + }, + { + "GCM":{ + "key":"35ff5a1dfc3eed5c2629b24005b03b5a5e3f1876cfa0a8bc", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"33f52c3d79595da384aeb2b93348c65b51fba242b741746a044ed8878c184890", + "aad":"null", + "encrypted":"843bf4003cbbe4445ce31bea6050b7d0861155fe3370b3ba71691830ca00b279", + "authTag":"7bba1d8be207fd2f64733ebbc7d68952" + } + }, + { + "GCM":{ + "key":"7077b84b38a6f31d0c808ab0f1e843dd8af6b1558b00d73a", + "iv":"3973ec4c9e70596e9550a7cd", + "plain":"1f59e9c30e74c2a4ced685a532914a8ceaddd306cbae9e4af5f6560e2ae0f5eef2f0a4b13435cad68d70212e6855763f1aaeedc9fdc4f28fb8bffbc61b4bba14a757eb8cfb46aa7308a84af4f6c19e9ac2e2fb4eafeda2a66d2646cb6d4566dfa238dc539616d7b7a87bec4d273488c7f717729125ff70d2ee6a4027261682ec498c9bf2734c6550efe1a63234da9b66ec5eb6ebbd5c4ff5c88f15fb0cfa407cb2688e2af3eeabd855e5cbbfe73d0c09702104a53f8c64cb4c80d98391b66b6a732b5821121541f475f762ad94fd6a8491d5e3809d67d309833c01903ae96dc9b4558f57e99c473531ace1a3153bbce24470fba10b3b0c4fc96d5e7c02b8643d", + "aad":"null", + "encrypted":"d1ab2888b0b3b3602519dc31a331c7455944c382ef68ef3b88d796f219ad5718d1e246477f9dcd64af1791389e02a99a75a493608f0abee5f125188e901dd052328475d41d7fd43980e4f9c55d7380751b538d01e1d2c5c631ee7db54037e9555077e61f81376414739db22d1d873c2fe19c11354b55a469a457291bd052cb5012f287ad81ff6ba308b9649c03f79d037d6cb8fc406e12b349f970a38fd214f3448fbb5e0d184e13cd8dd96b22241609ef0e0c66ddcef6e7c977d8c4a699d78e5fe43bc05870d67f0d6a59081b6ec37819943a9d2bb5118c85566aa83222e910148346599cd9234e0bd426a03a3f42c564853ceb3b92895b7a8a2883edf6c3ad", + "authTag":"df1a33b39184f5d19ca776bb9ff8c03d" + } + }, + { + "GCM":{ + "key":"b1868202f3005890131b4ea55f5b0a18256641e49a5095ef", + "iv":"23bb948eb0a56240218bea21", + "plain":"53a5bf1a5ecb50bb94c3b4ea", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"af196981577f0dac86f9d7fd", + "authTag":"6800fe69d257208ade2172f955d5bace" + } + }, + { + "GCM":{ + "key":"a989512bc5fb7129f1cce2231d167ffe66d001b764a1345a", + "iv":"23bb948eb0a56240218bea21", + "plain":"3a25f55a8027411af1bb0deb", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"c78859233a64157c0c3bc683", + "authTag":"10deaa9c8dc1ba0e889ce4f6cf06d6fa" + } + }, + { + "GCM":{ + "key":"8cf6722a07db858ad1c89fedf7dc74084ea6fb35a86d0deb", + "iv":"23bb948eb0a56240218bea21", + "plain":"124dbeddf498d59881c9b43ae295382bb7c3dd", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"23fdbcaba462111f3846bbd4c96cee5d6ad06e", + "authTag":"50db40f17c6898047e76ea36258218d7" + } + }, + { + "GCM":{ + "key":"ee6272f679daf3715c7de0f43fd03afdc172141b6db5610c", + "iv":"23bb948eb0a56240218bea21", + "plain":"01", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"69", + "authTag":"d6eb133aa8e2e3df6637d2e76770204f" + } + }, + { + "GCM":{ + "key":"fe97720a4c01ab40d7471177cbf6ebf8fe9a2b98f9880bc0", + "iv":"23bb948eb0a56240218bea21", + "plain":"8438cf81ede0d2376543190c4953a40379ef67f36bb990d8929787eabb3f5f5bef6da9c63dcd0ec2d021cf829b6231ab6e866e0228465dfc06444e25146ae2e921da511ebfd954c2a765fb44c511885f2fe9610219325a616e269fb7fb1fc2e9c4be5a81569a85740b03f9019fdb8b34e881cc9be6c66fe3366d1429696452a742f69761d24b714c73584914d95ce0e3047ef1dfd105c514a7b8e086c9be238ae13344642814a043f9615d51d4d6ed26ceebffda05054efe995e64d07e9ab1f77ad47d5b6401d769efb7310234a46491905e0b04bacb1b96571c7f0c78a7d45558c6a2c7d606ec390016cc3a03ef7bc8f8924e19645eff29c5692c481338c83d33c8ca9c4a1318210c8dbd8ed570b91ab97ff8dd895f022347785e797461d96798f2924ffbeef81c6141a8ae4e0724b6dd67eddb7f6f4118d79e54327cc0c9e65d3ef164e5efe0ff981d05e2475de0fdba4e010b94768a052f56561d23a06bf6bb96d51c44ed92dd27ffd0d9cd027d8a5c267d0aa3b15c618456fb0046b5d672d17de391d4f0ad09504de0cb52399f641669d102cece19c038e5678c5c9f86ec1966e9d0a26f7d41387200370f3e8a4f00b3c5ebc94aa6b3b27e9036235ec06c202d0a2a9ec9234cfab39393b3bc7473f711f46a040234013477f19d1c081323b5971d0f7af96d5c64f7f2e6e64bdd5615019b723f1e3adf4404bdaed3d639584a8100d29b0f7bebad710d6186632fec7409680dca261ca445bc03869b5aec81cd2b03a17f230d63023173144814c90231e9f56c1f30528465d1fee921e744a7bd3e0ac5534f9e76b49318aaaea12c01b4f08c12ab027d944dad8997956aa97b0af964cd9c61ea46ca4d6b4989ceb1680acbbe28603773b0700a3448a36a1ac709a68166153c1bd2b09f9acd81d6aa0dd862b63a5ca246bfb8d5033601e6d359822869ddaca3cec403f228b0a53030edead3274848cb96b5222fbe1e18eaf67f7c1a41d612fd6ca68f1ab788755c0f44930c89de4f4fb34d5f3ca0f0fef2bd2e48a0946d32fb0ee1d2eb2739dbc0289d7aef037e04485a13e3467ce21f9ce4226a6737b0c6df6a68e9a9f04cfa52f1e1e2faf5117bf8ae16dc5120e19fb3d8693da7b1f7ecccee2ce18ffe6e8b576b85c803392089867512d9d22a6fad89b413c0a451b0cd32e337d30f8034fd1ef076d34b79320ab5f50733295ef5c04ab930826bd487c19f34f4cf652e476f0619e371f493eb01799c47ab2d53b50238f799db57e1f75d5bd37f4be03c5b76ad805bbfff4699c4fb795b0429e8e21216b906ef9f5adcc2cc7da7716dbe01916a662c1fb00a9a3e6cd1104b53b8287c2672a925ebcff8c393a249efe4bffaab2676bc6a410c7c6f92cc9f21cfd21c5362fac2a0085894361711929d69aa23921f3c2e50b29470161188dbc5238661e06970efb66d8a75495e9516ce204685a593ffdaea4844b869e9ea07c520d768e81d7e5a962b37a343214d2926b4312e8654efff839814d62bd61d41e2f2a7700ae2185d3102be19def5e154c6cb9de738b4be7408bbdb399e624e33032f80d2858a6dd07bebe646732ea4d36dde2b839e9854990226a979f5d9baee919e33e08f334bf6471fc95c0256df78e16173fc091274a881f8401bf4d534fddc5e2c32400c4af486014fbc19aedaa42a062b604d3f8a57b9df68233e715174c33d4b73b7fc3e7a4679f864b83ede1509d3d4fbce9532cdd458", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"35428fa6209d56f6346d28340af3e0ada0b85424f15d9df67a3cd9e10b42fda778d00e819ebb9378d3a9580edccf9c6ef0c12e1528dfd3e1016540b28626985e7c40b89cdeab9866affddbff8a15e4201dec2a1e725980df6dff2473bf0dda62bb7e63970906de385284d417eccda466d9844d20c641ecd8c7ba3692696bff51543aed4aae5de06c9670fc4fcc0d7dfa216282289b65ad30bed08ac6bbf77ed08fdbb69039aa5d332975aad56768f801c19e5e26e17c8db3d814ad7899fca5731cc22370997f89de59c7218d57fb78e75f4088bae8c3a48acf30f4a7000b533b5a33b43b4d78768da802165597a45dd0557a08f60472c74c480993448282f4bda608023871b568a106563ce3a1f13bf110d6bef88295479dda3d1b4ea8230019d9460347b40953579afff5307baeb35cda01bdc2ca099e358c11c2e924766e97288dc5009b653dc004b355ba86ba41e23b486fc5f3fa7818bcbf65684361c55b5bfdd6c236d9670b13c1fac0a4bfa9a89054390a2e9a3f9aa35562896be620471df7a39782aa2179a536afedf1ee4a3b5efffcf2359ecadba6c28106dcac7c67f6e9800aaf8cfdf30c43fe702ff239c98d0c3cb45a3e5f66dc0b1b9fdd66a2feb69bfaf37327fffd39565b7e37f7c17e4f75a312b92ac0bfbfdb0df84ab71caa9ec249376378a882176416303568762764b9eabf9d852e1ba4dd8aae6aeccf301e40e59f045da91e36688e428a2c7053b79ff95542a3d7df815f33e5bc540c4f24180da1c62f8611f1c09cfa51e8e115a2cec1ad11c5104b153b12579bc54c3b61040cd542b044c07fa373e06956347e835933be0c5f60cdc6ebd6874b46769866ef0bd5780dbadb12dfe0f9ea8b63847d3b96e7cb4783ad1227107202c4199e84e8f6f112b67e2ec2f70372bef9f59da1a42ffd782f1dbed29072764db6b26fc63cc4508297dfcd3de4103b143672ade89d088f79ed2b6902fa31f2787156818269a818031d36a47dd22d4b0c8c7169ea7a3d771c23eb04d529339aad89b600fe1bf73bed4b24f11469e8119cbe33910a93109d8752567b84c8aa3bc22778d86bd2ef84804abbbf5c7db8bb3acef2e435df5d035750ed122d5b8013526725970addb6654441068877edabce2c069db3c999c38b3bbd8fc2eb0a6c169ff75c6843cefc6898bb58294eabd65cc62372c892ee3912cd889e5f8c77d243835d03012eb523e5e43c57167f1a10669b41efb09cf1b7926cc712404406807ed67e1069748b644267e32f1c9e8a48220dba888ac35c01420d7e2a81b89048265c7e78a2c845b887e45adb61dc110e4e736a179983440808b8de9ab8a9fa7d1e46fe13ee4e89c439431d1d836ec41cba033f953279ff634a5746f62899d57c87220736d736bd39861f4de2c520934cd2e3afdd91c5b39a595a0eb7af9e15307a3cfa914d3c77e7eb3f7c2275e10d5d02ead9e4d4dd0ebfbb5a9818f12d9d9e2b9caa8e1bccf1b0fb8a1f4bd6c7df9cc5505adb7c2d4d7517681b775f5e5be7f7a1c3d4d9bf7c703df2625d9f8e0eda116dbaf29238708097b3b54cc1dc614b13953bae534130734746792f9c8a8dc1dad1268e1a92a726225a87a801a4ae4c2430e4bab317b7f19ad8e748e4f12794c919c440af01c20b3a64d859148d21f9d73a54c14b4afdc6c0f91d06cc132d68a5c9e4583af7f7852a0cfab66b62ad6099fb97641bdc02556a4a1f7f937b2cba97dc7af148ff20", + "authTag":"60a7c012c8ce6cfc6694c8893d484a05" + } + }, + { + "GCM":{ + "key":"f495d07ffd4ea676a2a779e5533d43c0cd1d844995258f0d", + "iv":"23bb948eb0a56240218bea21", + "plain":"153ec13572094557b4e4e9e6a4afbdda9d46b05799aa6851029d62486e761b74fc161707559849c02233d0b9ec5b0fa3c7a72bc8a251", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"4039c11c5cc41149e6e62b0d425b7321e1f55c8e23169acb4ca3a8e7255b615eb6adedb0c9483a4f9e7197a47b916745176d2d8f1fa7", + "authTag":"d1aac1b6f3361b923f539ca6269a2e6e" + } + }, + { + "GCM":{ + "key":"7e2c1e703ff3a0a20304b39c6dd053d56ae74f080f7f6042", + "iv":"23bb948eb0a56240218bea21", + "plain":"054bc6d893fc0d4e86987170ea615bd2", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"6051b1ae5d8abf1bf267cb8ec14ab968", + "authTag":"3c0e66b0a5eb864039d60ccb1a015f7a" + } + }, + { + "GCM":{ + "key":"398ab0011362243e855ea3d1cb1855faf06352c3e7cf8e89", + "iv":"23bb948eb0a56240218bea21", + "plain":"c28c801b13391dfb9c7a3010d8aba18bc748d20dcbb5f83b19f6dc5326070f66", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"d5c97b6d910cf112c38bc9df2bb1403566a2992daa99b16bdcfcea16a676d28a", + "authTag":"ae5694f1b00b4fe7adcec09e5208e66c" + } + }, + { + "GCM":{ + "key":"55144ba397479902ab7ee6deeb8d10c5523a6640fcbb5871", + "iv":"23bb948eb0a56240218bea21", + "plain":"04ae798bd5b442820e757cd376cfed375aa2060c573262c6e32e8f06408339fbabfa077560b13596fd15cc07a0e5eed97a398cc189a0135896aa0fc6ea0360a3e030f6fa5408c4c39c512125e65065f04e80d0674f36f4492af94e05a613278cefdca1a77bfae48bd523e46e3f1595b6793070d89446f9ff1c35f90a74bbe8e644b0ce9e5858266208057983a297406c50ccba34b3428312bfbc2d0917557eb10363adaca8d438da9d4fb65f5e77fe754cad24efbdab338b53a1582b7755ed0054c60ece2f137def372938416738e064dd675a8038ccbe9a444e5f39a1554bf390caddcc80eb9584ea946a8973a35bb04b8e01233b40e598dc66bf3dacdaf8b5", + "aad":"67c32e4ef98ddb38ed56dc19", + "encrypted":"68d366d1063cefadd86fdcc15811da76db16dc41ff449d71c2ea8d1c4d039d55ad0f75b0086cf1d747211935ddd44852faddf5f2f44721e6e817400a2773b3e6666d6bfe6d1b9004a701d95ac2e7301d8d0cd016d06f990fcbcaf21486d37e7ac9ad689a143fc262c3c7c8d3e5986df5c5a64a96eaa00104f3062eb1a6042b3a04dbad9514d0de8c2fe9c7c0c2106b8c9023e097d1d94b64563403ccbc695eaaeee7b526ba96290d93bc4eb5b657e40a4530f0a651923ec15f41d55a07e772c21047aaa0a9a8b47c3ac4e4210eaadd5c9c38837232661a273f21cb8bbdece152c67044c9d80c2632c55ebd353d72e7b260ef1b4c41b6eced5837d02d5799605f", + "authTag":"90d6e62ea7f07ea10d21480cf998a012" + } + }, + { + "GCM":{ + "key":"d7218617e644bd0fcfacd45d97dd634f31011c7f4123fb2c", + "iv":"23bb948eb0a56240218bea21", + "plain":"9d334bc48d40fa47cfe2e495", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"7fdf6b95bdb3021dc0086647", + "authTag":"f142daa81ba91b69f34b225d4e7774cf" + } + }, + { + "GCM":{ + "key":"9d236cf8c7a11c7a3be339f79685c6293b036406f80e13bc", + "iv":"23bb948eb0a56240218bea21", + "plain":"e426ac29f0a7325ae1f89813", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"ffb85d7da63208b5cc933a00", + "authTag":"5b65ca13942fdd33b6da18d48964403e" + } + }, + { + "GCM":{ + "key":"adeada8d64d1311e81578949a91ddfaa251e9edf76222c44", + "iv":"23bb948eb0a56240218bea21", + "plain":"cf11fe171595984ebb750648c2d05f48488dab", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"091f64f14447eb2f27422592992a16fbee691f", + "authTag":"7221c802047de31f0597f316872981c9" + } + }, + { + "GCM":{ + "key":"fb6294fc172f1d436a1b9ddc876c9368f494c40b6096e435", + "iv":"23bb948eb0a56240218bea21", + "plain":"2a", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"66", + "authTag":"439655831fd482931b22e8523ed6ace7" + } + }, + { + "GCM":{ + "key":"0c5591b634b2d53adc4b89d0e68759916cd2a83ef6d28ffb", + "iv":"23bb948eb0a56240218bea21", + "plain":"ef2e3c8663c6825067d5b9dc12e5005b3384ba2771c69f43f568204ad498594b16a910b4e340982c9ee2979821f30fb16553de122b6f33ba90dfe0d3a6952dcecd9618feff445cb992b8423ee90de47b1628daa3a53123de659acc8fbbf766474654dc9a2268dea53cb4c1927530b6deadf194ef96b583957de7c923c8ec5ac1cbf1e650813141fb0287043cbb01212bfbf39d99c8511d7ad9c9437016bb5c043ab86b8087522b443de00b0bd5607b76f2970c06fcaa808a1dd386edd13cc67d0319034559a7f2a57e495ca900fa04519610ed4eca761b59c9cdab806e78c2ab189c9029cca18050c97472e5485888d2450659e15087625d21cd9ffb2276aa16e89f03b6620f7bfa23c715ac5ab626a6cd8b487fa4d8e525c6131b3860bf1cb12933e868b08a1607627d3b4a6f2a15660385538f9919b62fe69565c2c2cafd336b9f5683868b0463bf7369c0c1ac077bb6871cd3eb78d2913ad3abd8d2aaa1eb8cb28d30cd7bbcf84ebdb7e004d76847e1fbab033b40b3e48230665b3cc451dd9ce4224853f6d7ff2b3194aba7f9515aab8ae5a751184547122cf9528a3784421f34943c380088329239f511a3b443d7f280557c83186656f7fa4ae6ec122c918a4f414165d02363a628a4f69f2f4323e390fa180afae01a784a5e6b38da2b34c431073562457124ac18ee5d2bfafe7acacbe995e164e866d3a3627fc747d43fe200b33c9b3615c31ed182a39724c7dd6c654206b93c9f5d4cf0e6bd31216f6e070b45a12699fba8b4c1b53cc3502e52be7341e7cdbc1f58c023e2597197570bccdf5e72a2a0b4050e83c0b619f5090d19ef9227de0b937affd024003d1934d2c9c49c9d1ce5264491c980b14592ba6b5fcf1cf6db3810b42fdbe3fb71a2b78bfb37610903835e45fca6a770e8812c2f0ddc104fce7a8c44758bdb39b12298c1f1d6c32a922be578530baf5846ec882ac9cfe2ada46dbace97d79a9a483705f8fabebde16f7d8d669f04f9f86de0a2008d29c454e3659e2af96e079890046aff38c811cfaa85da6fd1c237f0c7adfdf300048487d8c7cbd9a12b5a7f7c61be9601af70f5f8bd33ab8bd2441b5099be80798afc8d352d95f38a146095e38a5a9af9521648c0bb2d46d0d8704ff3d579caf2c367e79aa7c8815fbfd44613b98dd2255d656580f76411f3e22c4c1b22c893c7fe521472952d38ec9447ef6b0f0d8ab58b0cd7e0fa3245aff5b2b955987fabbbce16752093728d83be0d1627838741f7cd2d3427bfb1c2d3ef7d7ed7ed0d5113176ea60b6224ed97e49619c76792c5e7c9d393f1cd7502d0becd746205116871b2a3893dfae4685ae0ec031016d834e7195a8c4007db9290eeb7774944f0e0a0ea6f675f3ea979afa67b40d25130e8828d42f03b70ee22746646cd24f57e051137810b29dff629996adb00fc3a9b1d07d1195efc7c2cd42b3f0e6a7674b297c41fd23fddcc3f98bbfdfdf7bb423546b27cb6f547fcb91d0540ef1fad222732b2f5c56c18b947e27805236119b449723664bd2a0b1efaa4b5a281fd093a53b8cf5294e5f5fb31c97a0ab52e294718f40a9f0cad3229b13deecb7ea68da7261b1efdc92c034d2ee0d79704834bf5b020e4b421646950c931712eb0da5471c49d054f6ba67bdc46a03cef35663102e106f30410ae7900729914c265dccdc8c5f74f139ae12a1f26769c199860283ed4bbb098ca28a0e4378ac0697f22fa4e1fcd78ac", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"f09a88a82cdec8555d1b088567cee07535da5eefbd3858c9bb4b494ee4acf4809597b32d49cc7cdc4cbeaf2c6f3f84c78c611aba176b379e6aabd12a7f931c4fa5a3353a8de53c9ceffab412d959a9f840aee21e001f0ef6335d7b7afc8822796c7f0e97cf35bdf7ebf982cc85956e91aacb53a7866f4c2e9b3100b56bd4036dcb3aa3802c2a94e8785434a4fd0f011df1f3bd6a731342b3b1a60a0f3d6a6ede80c62c8f31677ffb3b92fb80a140f406a7fb941ab1850018942046deba467be0f13145d8ee2d9030dff267558f0bbb9ff9f5aa188b3ec671ff65de2b7ca4a9e81e7d4e35bf9b438e4094597944ae78c91cba27bb2edde0236cb461c65ea35de11876b82f783eec087db357df75350ebec72ee064218f4cbaf5c9265d4b00bdade42ff4b34de70b3f985f47cc0f14e1804fba46bb420e198981d2b1d1d1395ff163c60f0a915397b68c7dcbaa0d5242f3d8089a1dabde69208cf6f56f6934829eae0343d6af350afc3dd3ae8d84c9a8d39a5184ef53c2f4acf8c85f3f3767c60e71412dfc5dc2cdfa1d54fc0e4a7e4549fcf3b594a0427582d0ec2fcf30fbd90d05d729accddff6a4034b4e28d7c5b3abc154fcbd1b513b7c3c61049df8d51da5bffc6176401d6b63d9e4747de8174a58c24612520b0d56207571ec21bc7eb818c8f0e5a13ba44fb4aa2e72eff2dd14a613f1fea929e2efbadf08b1c8d32d93681d50e8fcefb9de2738ba155909b7d7899ffbfc2f3dbb524da03aba471a719b395665de71c441d709ea0d4652ae059121903295e542fe10646d62a816d3bde49a0384823b936a35e8418d0949273d94893d8cd6de3191c478ba1a8c76025778cc2bd87bd4953f9e6dbe2629f964e675d651371ec4b46784ae499b0fcb655347ef95864a80a397c6b467f4114c91eea1ac326842ed23fbe680a3e94ed572ff74fd0f20a8cdd079d0fc2018b67190668157954b8ffcb5ee0807610913bd98f6c3d1fa880bc005fce6d17c8ad60f3363b053f1f28c6b5b0bd8c87bc28d3bcfb5510b64a7889ce6789f95c2deb86f91a7e30a0954bb4ca7e767c0bd4f0890ba340ea8f9f28ceec1c695368b88975622503b0c288f1a3c62a0075dafa736d484a1d556956c150f258e0ee754ebaa6a3f2018e71cf3633ba30c371f4644165987f3ece52b56f5409ad8d825f274d6cd222b30f0d85cb4c4e5c7c6ab7756d9b7ccc3e7fb5f8002f854852adaefca34e7910a2abe931ad4fd2e7dd7da10da55508148b721ff971bb192b89128e6dba75358248462240d0e6c4fa89bfcda04e0ccae73b998ed1c7746cb56c0359ec5d37747fab5af965422d6cfe1ecdf55bde5b6d385b24a40e46010abae0287e8744264ccc6ec2b3e0373732397f9e17118ce3f78cc380782b5b473dc69a3925b5597d8c9a2b2f92db7119ff610bd071f4561b2260d235ae76ee98bb55bff8bcb1fea37c193574dc6dfb598794a234e8e3a0cb554bcdda6e7bb6b740540422294edef99c96ac334cd0257cbe4953a08ee696a2d66ff20548f71f743a288a065b72dd63e888be495c8bbd70eff5bdc996abac357ddf6e4ddb533b7714d0d270ec05da6a6a216a90c4ef2df9d09301c2dafca2324dfd365b07d2da2204c2d3f7f7cf4f1219d0e6fd3992aaee69a2748c0de9a18e54b2b9d14cdb4e09221ee0ab57da7ee5e544cc184096f49045a0bd0315a068035e52d221dac3514def0f095377bd945ae489e5df5b1c9", + "authTag":"2b34ec9ec902f69bba564c385a47fec2" + } + }, + { + "GCM":{ + "key":"cbc97cd41e7e0af1ed1a57f85d50db86ab1fbb5c572653c8", + "iv":"23bb948eb0a56240218bea21", + "plain":"41de6559bd2cdf1b2744c83494df6ae452287f1104cbce66366d25a3cd3aceeef9672877a7564fa0c42a15cd568baf22372fa56d71a1", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"1bd44a7b12a1221e1eaecc55ad4efb62adfb54ebd1ac9532aafc056b19d3fe1b316df0ef2bcb234ee75eb1d621baf46238690d995bd7", + "authTag":"dc53a1cdeba6b71b61863cfd6e26770c" + } + }, + { + "GCM":{ + "key":"6fc3be7682e4fc729bb3dd44677f8fb8ef9f526c115d4b0a", + "iv":"23bb948eb0a56240218bea21", + "plain":"899ec831abf5af546607c5078cef378b", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"9d6250568a00789be6eea2507266f42a", + "authTag":"b4a093d5135f3d595ee1dc0bd0065c6b" + } + }, + { + "GCM":{ + "key":"eb2d159d6258a37843fd92f14a781e26e1f5100178f2930d", + "iv":"23bb948eb0a56240218bea21", + "plain":"bac74c1bf3b6e5138119a69bd0fc9a7a962c42ae76a8e4f6d2447e09638b162b", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"613f81158ae9ad75244dc2d3de606cdfe87e2ae6e686ec6561db0ff406b784cf", + "authTag":"aa983ae77ed7c1996a3784f010b9a6b0" + } + }, + { + "GCM":{ + "key":"4202462181fe3477cfcb5021a70ccaf3a9aadf6e237f3d64", + "iv":"23bb948eb0a56240218bea21", + "plain":"38a3c7b2d94d5bcfe9ac28b4e32dfc9db620ae0d419d6d11a1f6d86146079adcd082e8a9a6adc47aea32bbe16ab52bfda3a768e97601082b485da055d6ce1c783fbfd2869f7a02b2edc0a04909cc0efbe222d9fed58de0d5773d242d0c8fd6588321e950fb62bb2299e1ca6dc197035977cb8547d5162a6621128520e385bc75c55198a2d5bc55678e3711f12d3fb79cb56c86c83781789cfefb12f9943658eab577576a1e4e65af232546371868ea2f53dd6284393642d89e0290e62fbd337e35fc7c80ea5081f9777de96d72e5db23b53583d99818a06639d9a0aec7cf4b4674cd7b408bfb73346cfe2594fc52e6cee6d230e059e76b4514d901e5335baa92", + "aad":"4a345850bf8fe21d36cb3fbe", + "encrypted":"c93da155545fea6beffad3870b9d9195037e0d5d881be5ccd7475eb18c54d7d0ee3fb4013193347cd1a3809b92520f479078e678e72502c509511bb4f124e199991888781a3e0449513d6ae3854fc790909213ebc8a8712a7a5cb1df19980d0d38e52cd363ba7cb274d6d562249bc8fdccb5eee46847876e3c7bc59ad736d7764ad39833c564cd795d740af26926951c4f5a6b896b0db64af8d2618ca59d6efe22e2c497f3d17cbb511987b2bf3ccc90c05e7838bdbbfde44d59048aa3edfac29b2191ad8a0a38ee08d9cad1e45f8ef4d29d6c0f9fdb0a734f8767fc177a0fbef9d663d480131be032a9d3dac5f0607e6341ba3916d2aa33d27899a409598da8", + "authTag":"7d195c13083e8b7f05f9d9cfff406545" + } + }, + { + "GCM":{ + "key":"814115dbd272cb39046f202fbe5c7637b13de000ad5da564", + "iv":"23bb948eb0a56240218bea21", + "plain":"018d2091e651a958f8e29ba0", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"f1d3889508ea12a1c38cba92", + "authTag":"d0e01906085c511f6ebb702dffb0ff78" + } + }, + { + "GCM":{ + "key":"4ce5fee8f01e692e4a5facd7e235a4322c33d37fb19338ca", + "iv":"23bb948eb0a56240218bea21", + "plain":"5f92734367aef18bca2a3886", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"1d004aa2d1e4f464d619b446", + "authTag":"fdd38a1cbdf97c28d9e611cdd94867b1" + } + }, + { + "GCM":{ + "key":"15933c451abba1259b58e728c81d6b214b1e38d470f409bc", + "iv":"23bb948eb0a56240218bea21", + "plain":"1935ba7fa2ff49b7cccee2211c936cc7ce7953", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"655e98aac48c0370b751d3cd72a04378a3f6f5", + "authTag":"b9f0ba468850ba8db24dd33adc312a8a" + } + }, + { + "GCM":{ + "key":"edfd843d3bcba73609c432659d95a73005e334dc3f0e946a", + "iv":"23bb948eb0a56240218bea21", + "plain":"a3", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"c1", + "authTag":"34fdd41b5ba6b2cdd4299e94cc764651" + } + }, + { + "GCM":{ + "key":"e1e154000172c63a39c4cd651c8b2c10f5abf914989592bf", + "iv":"23bb948eb0a56240218bea21", + "plain":"21a2ba5c2c7439889104a3dd09169971824e51885bc48d5092b89332954c6d69d5c1f4e3c02683545655db80828142f7e0d642b4cf96ac0aa99339f3f49e92ff0156eaf6e385c7ca08b1408a6176a1a57a9cb0ffbfbef1d945e76b28c475e53c76dc0d3fa8919c3d2bb4970fe9f7710118bd74427ee77adb3ca459ab086241d20a25cae71c57f9c7f91e22cf561d9db30a84b19636c353014bddb4dce2ec1f69e2187df4c353773519f963a976136654226c4b5fead41ab1183023e968deed208818b0e42b2cb8db4369eccc379f4643ea4cf26d55fac65a8cd8c87a1f2837f18af5d16caf90feef77388eb5b1f8a6eaccde02dcb64c4103ee558c5d09dc8afc644894a946d32164a5c569bb0eacf6856d276a05495f85aa87717792eec0d298bcf0ec2e6c23c1e437741221b3213cbd178215c7163364306554db0138923fd109e443c11babe0184374a5bc316eb145cbad42c6abda22cc1d91af1c06c62b0815effce7f68456fe54bf79167006c7ed7741b0f39c2f7b5060bd4963d6ffa7425417712b217ca33699770dea0d5e43c6fff47fb6cf56ea940f2a9789325d3596fe02ae79bfc279d0f5da743a678419d1386b4830a0cf7a5d6cf759b31a511dd107407e0254a7de16abacc040e6e615f28bae085c063a1de53dae21c384553966c9bd18488ec5ab08677d6db14972ad9153c7cde30dc3c85f3e011b02fee19ae392a386cb14e18577c2bf7d2b8b75749a8fc49a29f18c082da6e95e75fd79689c943d62f32c13816a3590cc59968cad8ded9c42c0428e7a68e3a499965407eef8b8c4710a1fda2b0894f3c519257aeff0afb61c31fee1ed3449c093b8c5337c33a51a26a76075553a5fcc502f3911ae518046266c83cb1187d6f084cc53d75ca36f7e9bfd5d47fa47e79223ba52e1811570fbf6553502441cc8725a580b927550383d00cba970e32c7b024c4a02310629a442cd01f4fce50809fd15b2899da4cd87dfe86d5766fb85318ec08cde69aa545a454e544ca25bf029ad161011dbbbaed1aa23c98957fb5274d48d2f64ac91cadba29e43a1afd1006f2c4eb17058c0475051da11c0c0509e97662d26e64c683e0d9b5a790d9134a6ca6300a75f53ee12a3d5563997887ddeab3217b24be7c8c72b7e32d6ee987da896dfb8424f588aaa1c0c394b11cf2e5a74b2eb507452c7dfa9981c6db0510bcbe89c9901579b42432bda26f4824fb68d38cb0c7d0cec27829bcee9c09738736449920c2b437e1f365bfc868aee7ee49b0d8558907270e412a13930a0151e3f3f0d4e9dd6ace653e00e860d40812e1b7967f21ee72d7c414ce6980ce4510754bd01b5986840e532faeaf942f463ac243d9083bd21613c2602da20905f0c805857f8c44bf53bd97caa9a03e939dfd2350961b517a92232557e57856d3b75cb85359eaa14bb5dc5edf19093f5aad9bcd71367423f24deed192412bfa6cc0f3bb0251ac648c95b2960f526cb2dcd6700000f5b4cba756da0f175e2a7b80de88f36757b982ba1265465f82cb41f6919438d0b7683001d9ba1da3dde1d41df80c1dc69f0662af82593ae6f42fe6d8264383da067a9b91c94c2c6006984938b6f5dccab613d3088022c7bcbc18e627642aa2d04ccb667087d75fbd0d16b0c41a3997507358d3d4c80ac5807e1652b3e30199be9bfeb1995e11dffd260333edb8b81326665c3a6e419626b58e631dae1b9bf9f62919e497eb08195ed0a13", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"dd0b5afefef4dc4504548f52ad14d140adf47cfbfee949020a6350747a5f92fa1b98a20dfba3e6031114e8ee71a06ab238dbdd2ae86f59fc7e92c85809cca833bf7c052fdc114fe15e6c21d528c1292032eed746a68026fc7637ad64165f0f4e16c46691b7ec352823f32cc36c667eeb482f886bf1ae4b9514397e292e5eb0a5e08488a5d3a3c1a0dd075cddb3d8e95e3bc3a8d92f68a53986b29e57ef4ed77d60798e9c0bb6536038f0137494fa6611c8c539e6ab9f73273d93ef1dc4be70f699882532258c0911d8d0b4f13568f380e5cda2ceb853275700f223aa40155b3cd2e1d9bcaff85c83bee9bd7dd94401ac950a56c242e864cea445d9c0cf6ffd4ec96a765ab7bef0fc2c0774ed0509a16dd08bced9e64edcfcea9d8421816bc34d4fbfdc3049a8579e4b82ac7ccaf91912aa5fbed7db3fe6a246ce655357d7a87821a4739a47b71ae41bff8a67bf7fa9d2545e7ddfb21e0bb7282656609611e3904e3f16be6ab594a78f6727b0e368dff5cb59b1a2bd7f407335ac31212e427e5f4504d76c3a22d084b682cc4e9fa46d2cc6455db9f5e1d9f26281edc39b197fda1b3617fccc31fecf422e4bfbbf2596285e842f625f477a2b40e29022c536d4db94b99bf1c3ab55c702b23c6033af61d4bcbca042f94f0ec73463bd1e48cfb3f1b905a86dadc114553ac1d5f7ab578b9ccda0a160cb871db3e4a02223cf0c471cc3704ad0d6f34d52950cb3af0ed5e2aa1f9b3416f20590447834532d470d9624ff990b5eb8f4d39e391b19a085c5bea5f900d600c9e6bc8c540795f8a8b51d71dcbc81fe74ae59a198b8be3f31ee59ce5e8e265f61080b070f02282efa0f00c2f3bb3f57fb4cce5622b407d070b6f7954a47902bf5f133e1f975cd97bf2cc29913e5f85a014bd0efb0b27af820deaf411f677f7cc9476fff6a12b7c96987c2fc85bf3aa9f2f72d827c6c0f8906ce233b0011ec4782793fd29c87155ca2d6c3e22de0924e257354628d1693f5349d40caddb68b2ae08e68570bc7a183919d6bdeb6c021b35ef99ef062fc4bc6110e4ffb0fde581c628bbc3ddf7d047cd79a5ea4969be0e6b82c5ec0fe57cdcc9196bb159a3e014f3cf9c9b241387a6abdeec1b730a0820028627050e313dfb07f47de325978118039c5e8679c00303be62e9064629924073e8e6aaba490e6d1da3207f7ef111cb82d7228bf5260092c82b42f7aec3ad3968b4a83e8add75422181796c8e9f67e9d6144a6f93d6f45e52dbb1f2c4beddef1e51517f803dca7eb6a1bc220b6dab996cd3d631cfa5a14feb061bcb7104939fc210787fbf9edbae57e07b90d5c1c9ee534cf4136f9df8d2a0cc3447958776ff6ac34a5792944cd7ed4283be4c338758cb91d36ec67d0ca6bf4a1ff0136fab3896eb63297cd325f885bf6c00a78e311afb1957ed83e78e2f9e00cff78dbbe21662397559a3e3892f3865898e7b1839dcf9958feb98ec0279355d15277c473d7b95e112825376e36491276f02ea89583400aab839aa2f4eec33d8a46ef869a07a078ee307e44b0d321401e8e6f4d34900a3516ec5c7e35f9cd5df6443d8e4ee7a7f6a0d3bc7d503319d954fd39e9cbb49f6c54cef2cfa569507524842dd23edbfb9fb47d8fe0ab39be862a872a15c47b151ebc0d00d5fd1271738a35b252214acc41b4e187306d2b1fbb5c49c64ae0f0b2ccc37c975ad0f63814a51540db48ece4dd44413fb57f0661559f1a1dd037", + "authTag":"a101735f00dfc36547c7adc0d938c189" + } + }, + { + "GCM":{ + "key":"688614675e150ca5dd1ea4c23dcda8b07a350e498975486d", + "iv":"23bb948eb0a56240218bea21", + "plain":"6d8d2f781e12b32ea48d423e43b753f3bf1a75e5af22596eef9a0b6e3b8255088c80ba5ebeb18eaf758ba8c3a0c4d2a428c2703b0e17", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"0abd1b23948f3cde195fba0b7cd4daab4ef7119858f6edd73b20e4f8da54a49ee0dc87906587b225611e0a4b5835029d683c021180bb", + "authTag":"ef43d0811b0982e2efbe030f373ea6f6" + } + }, + { + "GCM":{ + "key":"9af079261f2c5457efeea208c6726a5613755208fc0d5377", + "iv":"23bb948eb0a56240218bea21", + "plain":"cc31f2b784c82096ae05433f5bdaaf35", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"a5ef1402739ef7428e5d287ba7216440", + "authTag":"ca73425367be7035b43cf2f52115dbd0" + } + }, + { + "GCM":{ + "key":"69644bef4767db1a3fff397238dc9d5bf853d91b357a832a", + "iv":"23bb948eb0a56240218bea21", + "plain":"d28fbea7c14cb56292b62349fc5e2e90c5a3c364fe9754d4b61dd6734e636dcc", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"26abefb09e17a4cf72d0589891a4c6e2d574ed8ddd143d7f97826d6e6979b58e", + "authTag":"bb34bae6d439bb81894ac652293aa06f" + } + }, + { + "GCM":{ + "key":"045ecc4fe00d8bef4295f16acf9c2732f1d3808e6b0210e0", + "iv":"23bb948eb0a56240218bea21", + "plain":"801d7faf12b655971d6d5b6407e57961191fcde7b4e783390b1b9ccffc4f6c3e126d9c9235e7777848097bebc5635010689b3edafd332592b4088296b836b99130121c513ea3e39bb1ec920dc5b56a6e9b15f1ffd98dbea4962496903cd38424ec7cfad12a62ec631a7e8a51752e7d7b3ea876097bf4336dd2ad98552b08c92daf47d8d0ff4c1b45fb3c822631338f1bfd9fc6df7afea6f06d0bc890ba6816f9dbdbce7edf686e6e5c714d45470d2fc7f8631f742f6a0146b6cb8b7b73c3102f877cd1c0bb3cbdfb7ec4d317689a36c97e7b3df574ca20cf12f17bee4904e1519d2c238c3d9744473263f75e1672865677723528d3b8789b413228bb573beea1", + "aad":"45eff0b97afbe0ab2190b628c594b206862da7", + "encrypted":"8761fa314da52e4882f540a2ac70f7790c11ce2b88a6ff281a649cb071a41196f8a5b7481101f7c4f869303e276b320b5c4383901e418b84f9e0b34f8778fc047cf153ca4bbb76db8daa2280052a24a7f4fc872ad92dbe37552bd7b12689eea136cc419269b746b2a80107cc5454e93115122c6ead66525c765646adbcc502d0882cfd111ddf14112cca999e4c97e55587db1adb6d06ea9d28e34605b68a5733db47e254f66bdfe1e5429e64ee847df0a80a7e30382d3c877b5811a211e1711cbaf3c58ae0268e01249c09ee96885cfb15157b3b61ac61f4c4bf2e412c9604e8a58c4de8d6ea5960e72b509d1ce26f11567e77ce288b3364de3a5a6ac7186b30", + "authTag":"a8f61baa463a3cac76e21f2c6a1d2c21" + } + }, + { + "GCM":{ + "key":"0a312e9ba445a08e8cefdf0099709fb81a9d4123baa6de23", + "iv":"23bb948eb0a56240218bea21", + "plain":"f266e2031027e47940da9a31", + "aad":"null", + "encrypted":"a4cd008995c1fa7aa754d9c5", + "authTag":"00cd07ce3be48232dc5d39389cdcf827" + } + }, + { + "GCM":{ + "key":"ec5804899cfaeb2700b74be48aa9e2366e1d436ddade2ee9", + "iv":"23bb948eb0a56240218bea21", + "plain":"e198764dcd5cd9641ad0cda8", + "aad":"null", + "encrypted":"5efc1ef7ce4d529ff440b25a", + "authTag":"82ab6c60740842f424061e5433bf61e1" + } + }, + { + "GCM":{ + "key":"370a4a0db68db7dba4c306823156752f2a2e16d1bf088bfc", + "iv":"23bb948eb0a56240218bea21", + "plain":"a11ecfc3a8c28ac42117bc34055200ede3b32d", + "aad":"null", + "encrypted":"5c84799d1e6ffb43cb1006e9a0bb000fa42d2c", + "authTag":"290b2704072d05274c6d80d4a7217c00" + } + }, + { + "GCM":{ + "key":"8765f567a4d8b54ff7708ea5a323e511d450fd8168e1fccc", + "iv":"23bb948eb0a56240218bea21", + "plain":"7d", + "aad":"null", + "encrypted":"47", + "authTag":"e7bb4d46fb69e6a98a82beab36ee0e33" + } + }, + { + "GCM":{ + "key":"10258054572d5fdfd921753108e826a2ea182f22acc77015", + "iv":"23bb948eb0a56240218bea21", + "plain":"d9074ffcbdc31299e68eb45afb26ca3e1677287e557eb5fa12e25f7051bad4755dadd0ab6fe255d5ad84c9f8b0489d2334c5b93d82fbd269cee380591ab51b5d5984c60ec3bf4d46e66578cc11d295716d8855561bdb8542172e99b9812247218e3e676822a4900b4df1e29b1b9f6afc936fca7b96c3be044b66ed6674c6b83cac63d1bca2543bdd16f883ef3bdfa815946ee70ec14ef48c3333b89e48a349726dc988d4525ec31877fb3f55c1fd3b636d6781213f92b05d05a072828cb160b818711c054d1001360755e3ac2e2a4638cb22c3e649b735a73c44ccd0a60efb7312396f295d113427e456fb1882d11357d31cf3e3a294da3d328a9991d9d531aa672b52ac3aad6a8c3cb02993d622345b833039d0d222630a206f914e6619ade5709f2e7da8bd646a2985ef1bbd0e3e592668bdbe9281a291a9671106462853069e0cb76123fed114c7197a64716b1702fe4488c246fff95a832a5c851d1a624ba61c7bc9b87d183c131a0890027adc19bbb1697fc6a7aa3b25631a2a4dd7530e560dca8908af7eea758c4e263f6af4ce60a53ff2a4ad5af37c70ae73c6fd1e8be65d753129207f9d0e1354167af1bc947718654c2bc74ca0e84bc6ef1b86ef5a6abd75f08b01f62b6f4cf4a8fac73b2f03d03722cdd6a82856995ce296fad0fa099d92518930567723f27f95d0624bb58376a69754c1446d25fad6af9ca6981a3483bfee27a7f00f22f84e7e78d4341747d698dbfcbf6071bf410e2b8bfd375d08d02c400232360aba185f5f72b91d1dbbfa4b12cdde96fc92a4d570367cbeab514de0e25ef403ad7a125419202e5da7abc282b00ecdae5ddc5a9adb2d93bd78023fd6d47fd016bf4f9cb92f7c60ea9b855db653fde72c98ada636a79a2a58b2c255fbe22284871d0cd6c39abb92d24adff492e4e9cfb876e4135b619c3c0f26a599c676818e34c30d235f7e8f755cad285158dd9748144d4018f0af4959f69aa1cf1de8aa0afab7c3cadca96bc70366f5cc871c8e23e52d8e8f65b2d0c3e86e301f1f777f135984190cdce79410bb1ca9ea80dcbcda43bfd5252b26f0eca6345ce6743b6ba1a7fac2b46cccd19b266f68288001f9cd4f259357cc110607c08d0d9aa320ef375545d6658d8c793a5a9d72e21d9b5e0f1f2534ab2c2a680be53ccc3cf44cf0876fde4d3e106d55cf7d3663ab432544e10199b165df50a0b72a845f4c31f45d9533293d0f90f61e8f90ded19b520b7a0db834694ede9049227981232b066cf2c7c3c2d40263bd010c45d9dcaa24c89467e7cb119510728c319124ab2afce15551a98ca82cc554353cbdbe750f2ccbd88015f1c4e5e77d167a39d2d1640a218c7ff1ac7a556980a03edee36abb2e70f2f6ae4502cc0ad71b841cb8a752740cb5b8f36baa6c2da31f40b272180b8e0e531a63b615e07bd02bc7ef3024c20341a62eac76ca3f3a95b9b4b3950939b10528174f3fe4dcec2266f27de3d4e12c10ca366df177ea9acec5d4c3c9a19d0d83a1975c7deb554b93c54ecbfde34a02f165c7e8075a71a8c2549d03f68bfb9c69f39e854157692308a791c213c8d2e376faad47cc04bad3b1c104fffdac9d446f93ef34dbf340ddcc4efe210b9a728c68a9c2f4f8c7a8af0e690f71f01448fe0a9734e3c0997b2a9bce121e94b58ac102b4ddeddf92b0970c380775924dd65af1245fe29ae7a1ea77fd45292b77556c7b74d6d5dd9f6e7f99d3261a1bd372", + "aad":"null", + "encrypted":"cca549a7da95a5a88457691ac155310a979a914b491a914c66908ce624975477049a99d6aeffd1373c54218201fb641125c8c695ae9bd42555c61a29dead0fb9b011a6ccc30c517c06200d3583dadc8fcf7a642aef96af54a9c39dfb11424f7aff911b4f14fd96ef5ff2d3bee7ce16e1ba937283c99780620da64d6212cee6fe40dafea24dc050b5815852a1ccdc90ffa7c22a15f5dce442072069599aa440183796347e58b6e745bc25ba5aa491802af5c3d71b2ed536b5a5a21a98fb7a5f42288d3b7e2156ce845668da31a37790c9fb59856bfa4757d7212673ccf8d1995e62a4ccf1b5cd67b6999d27ea53b9f2d93819d87a48bb1ac819adc34b7e2fa4e6f8d43b81a01448914a8212223dae8fd352301935a7b0ba6e6baa8642316b7906eadeafacca282ba73786031c034eea700fae72acd900679cb49f9603869a2b47db5c4f9262384d00b5fc2e9c6dd100b475e470b84d737c71f7bc0ce87374a932a0e8716d9e78fcc44addbebcf71ad48d5fee1fa1bb9cd2eea99e049a3c322d0a7d65664b273aeeddc04f89b07214c00acbdd38bd435bae02f94a037efde843cd514a1dc5d96321b2769675527e4813e186a76f4378a9ed2e56d3de2a22fec6acf95176c873e678c9893ce94e03d2e2b719af30e2c606e36387a255233974f9cafc6f5c1b5da88ed1c5d560f30fa671cf3933037cf0779f047811a3e1961658bd4f4f90cd6822bf1f35e50c4e6030081e440358f7c0d897b2d3d3977a7e15754c0dbbe4317df039ffab60897f9dac1aec01c22cf9e4c04dff0ecea8c021c4c4ca9efde37f027dc4fd2b6290bf824d9b46de0b87a7fef938e7bb9cf9da2ff66a201dafe00228ea445f62b25f4c36602028a07a3a9f7e5b95cd03c4f8d0aa1c9d6088a289eaefb3cb04bf798f62ca7dca70111c5cae5a1f6966b26a687f5fd9050b041b0e46d0dd5da189ae691543a4d83368492c0b9eed2b1c6cbcce39b86338427864cb1afe6fc0f9fa043452ee9b4cdf746512ae0bdf466134e32fd7e9158e78ce3a44845da252544da43031e5b5aee6d7c64c598abeb84fdb0201791fb28f4720de4fa982ec83a6cec73c3e441d5971e81a12768726d38ac886fa87aaf123b4157e05dcddccd7dc7ce98c0fedeac7b57a4b9db4843e15f20229f0cb17ece7bf23a71e34a160e5efe2503448632e38ab7a1ac293bae08b390b3e5ad7cb978a281ad892ca6c3292ed8b7eae477cc0733575150231b01a1d97f9d79432330ffeafb97da4e7bd0a2c9b3aab6e36514abb564b0d4e9e3b6c27dd0ad266b3904b6d80479283d42a7e320ab78e9af88774ee0bc37e38992e9a8b6e29f5398b48b977d856289892f206b772b6d049c5d9dec08193e8918e716b9070e33b1d414373d33267b943c092dba652b6a314aa598c246e1331374fcd5cb2acf2c53bd0fd2ee86bd10c21e0342ebf29afba3d1f3ec08cc467d7e2b3fad400ebc0efd4a134e9ed1d9dbef50496b766b5ce710ccd59a72299945ac661639e13fb26e928b75c8a8dc1d2a89cca159088b008b81ec9e574d44b86aefdce9097dc61d93dd8132bb53f86dd24b41a94d73eb08893aa0b1832fac46e9281da3c7612a087ff2577e1e6d93f40b94f8c93dd05600e7fd88894c8681e3816e9a61adf3d5577dc07eb397a132ee35dff93cb4b6bd2efc314d4ecf877e814271808e0204e0a0717eece6d20da7a63012ba8ae60241b91ccd83108e4a3288601", + "authTag":"923dde8b71d794735201f73341889f70" + } + }, + { + "GCM":{ + "key":"c3288e8abe8dc097cb16916fb24ec62c7fb2f8befe8c487a", + "iv":"23bb948eb0a56240218bea21", + "plain":"70d1d03297c0c26fe83dd70068bdb2055bab34494664fe33f0fbdb50fc163591dbe2800f18b43d982097e5e5302725f457918f9e2cc7", + "aad":"null", + "encrypted":"209ebf86a0688ea3cb8b59f9b6a0509a3a2d24c784d9678cb12cf981ed612269a0a41ebbc88f98e62cea6cacc03c53089a22b523d56f", + "authTag":"5eecdb5f073cb518e53260d02b6a58fe" + } + }, + { + "GCM":{ + "key":"18417041c1810bb800658860f27f6e23fdb910e19817e41c", + "iv":"23bb948eb0a56240218bea21", + "plain":"c241b79a1b754b16e2c6709d42e27de1", + "aad":"null", + "encrypted":"161d1b38abeceab5db8f47b88a546ab1", + "authTag":"18bf048af77f4643195e09b636c97347" + } + }, + { + "GCM":{ + "key":"092f6b967fd30f4fc95d029e7fc59bb10c66b36a0a9e5f62", + "iv":"23bb948eb0a56240218bea21", + "plain":"1a831250de4b230379fd68fde6c6bbdf8604a6f42fb11c2768901e2ca0a3b7a0", + "aad":"null", + "encrypted":"43188620cc93abafa9be8adf5dc1b7472979a03cd8d2cfb34a6326233b6a232f", + "authTag":"b59606975c487103359c6c2f16197ffc" + } + }, + { + "GCM":{ + "key":"c12d3674ececd9b6c3c14d50777ab13f2ffc12b40bab157e", + "iv":"23bb948eb0a56240218bea21", + "plain":"6d719fde2065e9503b04834edc2f0accc4646ec8e77349cbb58ff0384c23427dd50b27ae633073c101e234786d1ae48fef9677196a3b7e0c514ccc412cfb511af82c26108beb9c9dd6fe84a5decd4607bf5519e4496284b2c581f99b7fa717d9ffb56680fcd687f1f933330d3010682262e1fa60172205338bc17bf9bff26c18e11c1dbf0814cba965671550cacdb58ca0f86ef24ff3b79aba64856db3f448d74dc109b40ab4b9223347291f648027c2a50dcc8bbb6b15bf6b51d72b62072e8193ce60540c8e7c10dde5d9bc80d0bba0363ec7b11d24c501d7f58f51e52c742ed602c3c412e5b48542ff0d86dbfbb67dd3f53cad98b5f7ef02493cc23ae09a1c", + "aad":"null", + "encrypted":"4e95b62e40d78c98703e3eaae2547dac636de6afbf5976302e2935260e63dc0ccb5efb0660b97c3dfbb7293956d868b110ba601b3d5f2fae447af492933b6dd4d41680db5a4824b7b8bc4932cf5ec0be49130517207ee53536dd8c8eb0e8646b76a990f58a1adc6f77647e948dd43079c17055fbbdde7ad1d936b0d597d661eec5bb53e90609506b669b3f4687de87d8c06ad9754b3acce24e4ca30ec3d4904ef4149c146a791043a32335b43b52ff91f02f0827744339f8c0c9812a713975743c96be3137c46caef56b5f8519bdf314dd5b5c7cbdf03e3e4ef0fb7cefc8473cc7c6a98c4abe815e103a4bdd94a189121e49519ba174bba91a0ef1b88039ea3f", + "authTag":"c11ff3a1a081a290945801174dd9483c" + } + }, + { + "GCM":{ + "key":"aec085f48146e68c44d9768d09221a804103adb57d9d8e5bae767aa03a7f0416", + "iv":"466a5422f6227108864a13f6", + "plain":"b3631c28f8f3a009f4ea06e2", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"a20c3a770fafaccf0f632e4c", + "authTag":"b5548badf6ba9d752b74791a1abe0ed0" + } + }, + { + "GCM":{ + "key":"a6e8c8065bc63e568e5506a75b0f87c9f0fa689b527f2763ef6bc02a00b2e3c0", + "iv":"466a5422f6227108864a13f6", + "plain":"221b73b5f0516bd079acb670", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"afba4071dc1a1aef5fb8e66b", + "authTag":"5bf6450b7f7b3139db862e44249bd2dd" + } + }, + { + "GCM":{ + "key":"bc276857fff2c80ccbaca451306301cd94aee05cd92cb6dbca4de3538cf80fbc", + "iv":"466a5422f6227108864a13f6", + "plain":"ac958e581d1b7f28a7f25f48e851eeabb30615", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"07c5536c4c89f331e69ee731eb2d915436f0c0", + "authTag":"c8a3a65d0b0a62bde51158baf219deee" + } + }, + { + "GCM":{ + "key":"3571041770cbdb6c7764f82a225d996d344a89b2c86b2e8f26b37bb7321cc91d", + "iv":"466a5422f6227108864a13f6", + "plain":"7e", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"25", + "authTag":"29769a13b5599758250f7615d2bb6cfc" + } + }, + { + "GCM":{ + "key":"27c6b914e2b900f832b1b852132ba307c986cb6dbaff64c6fec9d1ca9bd75c31", + "iv":"466a5422f6227108864a13f6", + "plain":"69bd997e7e4881fc489775bb5dbaf7dc2ad0d362739b2d6d55e1cdf0f4ab122624a3b164174f3057252d70934c542f9a5acbd973c4ceff3c3dea5c7325f1e434e60156606e95b496559180f87baea590a9d1e01f171e5b9fb524ad7443b5c7ec8c481a7d04328c06804ea186bccb62ed13a2ca565d9b6158c5b5e1991381d7fb09b028d9ae8b0291fea923439e4560a58f89ae586c1e5f38827fd845ffed3f131355b5c30d973b6f4ba170ab43d3d587b3c776304eb63069ceaf762737e10a1e41adc0a6c7d97f80dfc0c13f6d0072e04d37b183312320e00199ee81a9cb9902c17855847e5005f4326f6d04ec8bb5994aa38a6c865eede086975f952120e9a5495f750f1457fffa5d3c9d0741defaa0a7526eb053470c35326db0a21c9348b5e0b6a80a7317f6e74b9c4374c32cd1114aa00bfe083b5fe49f5b6177349e9c9f35e6a4f8ecc5cf16729822a221149a470545fbbb70cfa12049347e3bb2b9a156e29c85b368418c13d48e2cfc20a74a17e812e9f08cbcbdca95cbacd4ef4d8886ca8f2acebf609831f239b7f2c2a5a71316212bc22d3fd35acb1e2be451b45326a6e2a47d67390e4c4de69b124c033a954570b23be3b8eff4d865b1bcba4ff09d05d45ffb03e8936ff4fb89eba2af6df5e6c7d4ade0ac22d9fff84df4eb0f58406737e736e6aa6e55651b98bb183c3757154d0f9ae898c65f12b72e6ae06e0fef23df796c6f8ef6a8aded18e7dfe17514d87453df7dba903428a7840621a8a679e306a23965be9548b0300257c67f4e8392cd2e93fa8bce51268b12ddfa204319a53fe667d607ca92227fbc0c3e3808b2aedf1b633205de69691cb44aed36e58fb42c86a8991d4ade6a9012e98537b4e711d0d612b2a10a16515ac5d56ef42b047902ede89990bcd65845b66876f4165884f57d6de5ee7c1e4f3255e3dffdde1ee3ebd862b3d16cc3ed52ef2cb23dd48eefd6cce9437b95cd966a06887336ca8be81831b99f575bbdb64b06b87181d09eb44db726c90cc44b66d73f36f341a4ebf4b261103fbcb9bf07951041f0db701f7134f6ed2fff926370f975b798f55662ba0fef34d1af2896d05881ac4293b2463564fd2e40dc27cbecb8386ae859f633db787d7593b68fddc6eb0040b8a5c82d4f827fc08a2131181eeef4376eb143b55e7877db86de91282528fcc0000926134279cb433de881650f237c7d8290de922f418dbf418bdd979cac4804cffc6cd131ecf40d543b5139be95ec278d090a83205249df6c20bc44ce297d63c065ca8eb7620d7bd3006293d7b5a8495a2b7a01f658ecf3674b84d7d8e6cf8e7cc860c2e448ec07637a5424a784afd774d029215888275b3bd19b514dbedd65d049f6c7767dfcae11b583e05c7d1d21c962fe2d2cfd56fed6678fc9e171d1d5332009a71151ec0f32183bf739e28c11894d91459a3d3fc1b2271845ffedec34317054299b6bb78aeda3c9ef25d6e8a1503f01627c9aba4a869295ddbbf9d81fa04c8f632ffafa40ff0cc25d890d13cb6eb9a4ac3c36203f189837f55a6c95f56cdb3ddb20ecd2e154dbe67268467285fbee0a41d91425ba5058ee01c784794860d69f44f9ed6bd94eae1154a15b65d7fb39cf9f86c7d8432f408ecb226d98e88706eb5e3cfa94e62d48185080314e8b84ade9e40d19dff6ad72bcd3336f5b943896a1df666d33125e1ab0e89c5cf423d1b7cdd33d7ac51c4c76246146f01ae38d899b6a8cf3", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"e9dff02f26e15f681dcab1ccb77417b1b2c865c86f82b5232aa6e1a5d85bc086122ebb528946ebf218d7e7b03ba181ec71b499db97dc1b966d35a375ce247da749afb851a7dedc889d31df33b8bea4869b20ecfd694e6549e938c3881a590e759672748fe4f2971bb80a47c44b40faedbcdc51e35d851280a4bcf871674cff1cce6124e8cc872f76a0ed209bb227ef9ad19f02198e69d740e059cfc40ffc1bae675a2f1775f80be5c08826e25586ac732824fb18e4ca35bb6ab76f953151039c70e4b048a0858c8b189b5c3ce4c4ae3b59d03cc2ea70e4d1be05d2585ef25e6198054d1a4fc5a1a87dbc3555525f5e287acf6b95df94c978f33cd7413c9641832e40891ebe276e74ad82ee43c648eb6d00f92cfe4134bae1918b3a720757f2d5fe6c4362ad247ca8a1aee77090cba74a54386c19e1fa27294a277b4fedaafcaea3c0e6fd0706bb5c82390ad71bd15d8361b540ab50828b7616cf365678b72e34f17cc91b6b1023ed09a267c93f2b65d4fb475d818508db56dc808624a87f46e44c8359d0898ec5a83a21a167f358bf34729fc6fa912e713efe25ac3f0230797ea736edc5d858df28cdb5f6444878508ea6623a9a517067bf236203bf09d17b4231d08074917a1b284ef1027e4c9fec0ab9c0bf694188af3c2cb3c4059d90f885925b501336bd657bd1b7ecbe10cff3ec4d4500fa0474c9221fdf607ac4f840bc4b971d93eca95bd6b7b275ec57a2b45c0c5117baea683fd0a2b9715a64bf4fa18ff2953177f1e2151f0050e3f2bfcbe0e455de006cb1a597ad82bd43e40eeb5e5033601c8b84ba6dc6f13f473ad0bc86e877e3c9e3b8977565644ba925e7473405afa6a5baf0cf969a688afd746a4de9041557286c33ed4b114e5ddc5faf3ccd029cf883dc2729959e5dd148d86bdbc60d8446c6dd452c2e397d597ded4409266d35f919e8dc105ea69deed866b5ff26d415fec74dbf5abaea74b4087869f5a8762a692f72f1220d3e0c2dfa2d13221bf1eda56e76f42a08738c6a95402f3ae06691bea664104fc4b6b6873be26ac1920aa817cc99b509870c262df0fd32ff2523a923c6d73b5dbc203a19d0de0c4f0d2b15132fa5a3e85c77c44d0fe80fa17da1c4929ed558b5b8a0e2a3e7a533a98a304d4b065f2da7d99e30ae12cdd3bf31ab5c8a859afb979def90c6ca331d2fe0d4be7d3d735d87208e4ea5c86e85cbd4be856a3b5aa6f35b21a0893012e5fc8bd4bae1dfd580a9553c69d54673dfd781b5ae05ca4941031df2cf25b293b68d416df3c105dc8bd29f90fd5c57d54e873671a0e9b74b79b941992d6bc2ad8b5d09edce21194d85c957eb001bd18303806aae39c8d4b8a6d3d62ca5e7392f247e9b3036697b8c7505f80060889996b967a2c84cbe62712a1c4722aff6e44a6b1853a7cb325654dd8995f1c6092d1cfebb2654859400b8d8681027932b25541a9a05282419388553c1c01abe7418643457d4f2ec2f1f5189044dcf8710264daeb58687372f12e9e5b1f6133f7d3fbe1bea89474289ffc86513dd00cb49dff0ea0086fe229e7d7df24c887b4b187e20b7e255405784511c9754f6ac34708c86d37f2457b02ad9ac3ae9ad6ad0adf656f896507cf9a6be3989b59e05e06de77c02be9ed176ed58d3a2fe00cdcea20f87834553b2ab1687050a44f951772f4f1eb6d3625781af827d946b9fabf878163a234f3d0f6c4598a5b5618805d21576adcda55be994", + "authTag":"11464cc44c2e237a429c4849b1a3eae3" + } + }, + { + "GCM":{ + "key":"71bcb88f0d361f7ac4ba116fd3523a0e63bc78241dd31df2957f2609a4788b7b", + "iv":"466a5422f6227108864a13f6", + "plain":"d5f7e236a68d150fa03c0e547b6fd4ae2ac6c65be2960eb464e833565622587f2ef7edb1a0ec979faabdcbc98018c92c330eba79f52e", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"be6533c46ea43524a27a425dac8f4d378b032203538a0208f27df9313c4e3b51712cc0dbf616d5f0eb9b7dbf0ee20f7d5d9b6129ecce", + "authTag":"8125bd499bb461b337d0767f3cd05524" + } + }, + { + "GCM":{ + "key":"c34eff78702c81e15a2034ec3c003303f6cb272087c5b3a93cf958c38505be06", + "iv":"466a5422f6227108864a13f6", + "plain":"2fecabc5bbff3a326c74a07828037d2b", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"371043c70ecdd77558c98646eef5a6e3", + "authTag":"7932b3067b0c1b2a38c78ff51b061299" + } + }, + { + "GCM":{ + "key":"b64f081e9db4d62f5ffb50fa14eefaab029bb9f29b0aba010c77e0a6a86ee458", + "iv":"466a5422f6227108864a13f6", + "plain":"0b302b39234f6fddac9d2439ada8c7658d13a1536e733bca3379f32e79e195f3", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"a8620632adce856db8f1d83937920f78b3d226809187c61c278dd038b7f06322", + "authTag":"4f4c0194c197dcbec6f2504349dd892e" + } + }, + { + "GCM":{ + "key":"d39832b7551c1bb4a802299f1cb200319112d35c73495c01bf56bce6e4cfb087", + "iv":"466a5422f6227108864a13f6", + "plain":"c11ce525c64fdde56d6905f145c07181ee152b0c05d6be0aff1815d1c2a42cac0f2ad633ab68daa2f69eafec511044a6160a568af20c8461a230a7c5c319161aa5bdeae65e65f2f72511ccc583d3cc60660465ae32d3bbd041e148787077a648bb01745d0748f64cae277f693212e972e5baba4385b3285e276ff4e336314e068ec0764a7880540f1499a69f04b2d7322505651cf5538fac5b7e0c4e162581a0f72411af4e643c0dec61d9ab45bbbe34ab4e5948a788c00ebdb403050cf11e6187714374112891e6ff333ca0c19ca8f6ee7b149cdb845111fe203912ffd53f213f355f37c53102953fdbc61d249c22127b1fbe8fd1702de6b27fc1b492e7b193", + "aad":"f5ab0d87f5afe87a5005fa37", + "encrypted":"2c07183ed7b41457874f620da90b56027f930ead191e4e16752d5f788907198165a3511e1d26f73da12bbe603a41e357d28680ca5df6e6e975f7fcb565c5ad19de42e65fdec02946d0a3f78af17688340e2337d07cdd720142aceec2452dcdc911b0798c6c7b0a40d8cdafaddd4a5e83562e66afe139a3a42608d9e4a48b0a472dcb2f6ca9949364105b2d593f202004d2d506cdc4579fe121e4eaaef5f6bf281d55262a8dea29f25967698db8e9f4c1e68dedb94bdf2ebf4d5eb6507a549a5c3af4fa3a5b25c091d56ab3fa05071bab214f9f042ae36243ed38ed5276bea1ddc8abd344fa9e7b43e8d604f0571221a6d498185c67e1da8e3f9d6ed8d5261043", + "authTag":"a07cfe10b65dfc66a27f6e309b85326b" + } + }, + { + "GCM":{ + "key":"59b07ea97e3c1b5fdbc0549606bbe98c243a2669a8be70f402f0121e89cf513a", + "iv":"466a5422f6227108864a13f6", + "plain":"7a4ead6ddd6009194e82393d", + "aad":"ab9b858c21270a23225135db", + "encrypted":"6ef09aacc76850e794516004", + "authTag":"40d473dda79aeaec039ff7d3f0e8abcd" + } + }, + { + "GCM":{ + "key":"cc25873eaea86b06f9f1036cd5d99a076c3ffddec00f95775583af616949e5ab", + "iv":"466a5422f6227108864a13f6", + "plain":"4834133dc737b1d79ef0570d", + "aad":"ab9b858c21270a23225135db", + "encrypted":"bbdef4c43446ff48c8423c00", + "authTag":"9d31b7dd6a55e6ed569481e8fefc0afc" + } + }, + { + "GCM":{ + "key":"f231a67f08f84601fb5b09274c5a74d6952639c3f905b80ac537b1d5cc74bcaf", + "iv":"466a5422f6227108864a13f6", + "plain":"9f7777358d5582b39b30b7d22cb926710269d9", + "aad":"ab9b858c21270a23225135db", + "encrypted":"ce3cd221a579259d2b0cf3fe91cdf4139bfe2a", + "authTag":"f61d4ea86a969e62160ce3e3273f3553" + } + }, + { + "GCM":{ + "key":"879008c4b65da1ec8a4ef31884554e1a3596d0d8498316d1b8e1c4f0f96d0e8a", + "iv":"466a5422f6227108864a13f6", + "plain":"77", + "aad":"ab9b858c21270a23225135db", + "encrypted":"50", + "authTag":"ed3151acc9224133ede7f063dd794152" + } + }, + { + "GCM":{ + "key":"0c7d3eafa4852e471573e689bc413ee4874dd8b6fd0fc3830a5507a45ced3897", + "iv":"466a5422f6227108864a13f6", + "plain":"c2452f29c0ba80dca1d681b59275bdc06d9ca9249ffc74b2fc86a8f1f030ab8474c689f68e46a0e0a93f4e0a6fae090411760542af171a0c80b2c06b0fe2837ca0517208c517722852f61a947e8751441966a2edf4dd3a7c3020166a5bd6ec387e0abac4cabda3adb320b6b3e21ea582fe324b586739c7a9131a2cec70f527b5f4d728789a1ae7f4df669196166c3cf4d89d58979b496701b7802060faa1fa6ff255328c691f776b443772a9ce6e0d1850ff7f44f59c47fa4a7c671e5c03deb7275ab374d43610b3a087ecd0ce7e34c2841ec10ec104a86524174d1ec5319a62b80846284cb09b144a798a763cf2a28fa23b38606d9102423cc64468ace17b942fff6e51c1714687795b1c9ef7b632b2e3db2ad4b4eadbcdc7f0db750182feb41138ace38c75de044767f228fbc3202e52c89fb859fb0335687f31143ec4b82d8bb43f88792379d80b861f96f0248762b8c9ee971d2fcb6b64438c9ba8d26706adfb54e32a04fb8355e9654dc71c494b1c6bbfafbb3542831b16c4dd0e1dbfdc6824c7c21a2c7a8451668eb4c4ef7d00970f17e993ff74760ec8b640fe4a1a907c134c90b11c6c4ff6d9ab1fa2108d03cc46a8770134057c7b144d7fcae3eeadabd1e335a95d48bd472dd94b68722e205fbd9e43bcf91c8245b06c27a030eb632f91cea03c23aad0b2f8a3a1876bd9b92083082751b2fae917c0c8ec8bcd37c56aa37e4e529415a573043671cc8a9ee8e955cb758ed48776c051de485d784af74b14fcd8cc125c846b6fd9a9e655c09bb891f414f77d72255ca9973a78d875c41d5ae827d04783c5bad63f764a0da0ac2320683936edc18b3a26e3a7494c88a12c6803137023664f34bbc5b45c2860ecade138bd83ca0aa667b4772b39f62ff5e58b51802970205d8e51b9d0b82421d4bd955ee97d89832732e08e698734a4bf8d45968567fb40c9e23b83955f70f554b3afd8578e2753c2e9ae7cdc5d78c615cf964e169748e551706f99c44f0ebd5035d95efe5200b949c54accf48cd68552e9e1e697b0f55f6f7463f8456f14cf418f450d45c07cca4476aa98e676ee3094f9763bd053268300b39d3a32ea2949cc987d8fac68beede4288bf4ffa54e9d32297ca821faeb90f49aabff132324818c29095a1e68360d3bb1278873749bb5c9051628079db46182fa5477479b81c6ae036d6495b700b2b5bea88d41ac84415461957b51645ba076471cc7244a4ee355444be52ba07e9d7a87847e94c4a42017719ba9bc110c0aaaac8aea52ebc8c565d406ff757c3a68052d0396ddfa43590c8b34040e052410fdcda83ec1943dc0be2a738c193e2879ffe61376d46b156396211fb4c35d0574b8ac09bbba6a74e5f84b6d741c0c0469d657afc06c0c5bf4c47fb4ced809f55b314083218b713326ec5b60c0a6eea3e3a1e5d786cdf42448c6b7826b50e943638c4e5f3a9e8c6f1b8e023696aa467213cdf9a179282002600bcbb81d6470dfe94a19432f322e364d3c6eb3d8e30cdb9c81647fb079c48ec8e94a5f80cb9631e6f5c9c0ef334f4458c9e94facc4614c95d55034456b9ffbcc01bf6c9e70809edd4e2a687867be1e1f8233523e87adf939509f1db5b7ed6cf7b44a85b1345d7fc68d6b9e420bbb21cd8ad56a70876988218e697ec0d889723279abc7d88e74c5ddc2415e56acefd75e715bb5586ae366ff2434bb71d47294fd7dd1bcf1e856ee53c3c50d9272cc4e6808d474", + "aad":"ab9b858c21270a23225135db", + "encrypted":"0416a67190276846a375e5f0461418b1e370636ef61c3cde175be502947b0284b5d2f0841f328daa2fbd6d4bf743cafd92d523ccc45bc54670c67bec8d21b9a920c3beafebc6a37a4fc7b3901a51f9ba0c0e86b392515a756fde03f48c759a761d7d1b93e9b6831a9d6aeddb800da0f4f1e8392cc00561ae6b6604a3259428a3a4d8d10ed33dc63852a1fe78e165776e00ea1399dab44b7c4c0206a32687f012f753dbacbe433eeb69763e35b6da078b5c4a3e87e568045b708af2c761b3d4ebc297965dd769c7e505d8053e9a146d0f93ed479ce291d7d427d9b557abf7bcebaeb744e771fd8b9d436e7072e8cc7f769de6cbd10ef0d14bf4d18b15f964c3918b5bb8b519216cb8879ee8322bd0a0ce4ea1aab92b9661e1dc4b0b33bc086c55264a9c957d7fed4f6b6cede9c39dc4cec226d9a33271a6fae9410ec13a9c9ffddb644cf390098716d24b1d35c2d1554b4f0f632d8c34dabc6e4876dfd35507df0c28906ea5d0755718fdb3e1c3ee8df6b087347612ea28fc46c542a5d39188d7c1d5307a2b15150b663eaa1ac838743c2be1be7fcc15a6928bbae00bbeb4a8317452ab22179885023491800d6156f75a7476e99825411f74899916597b51990a2b7e13270607af892b3b2c26f72d2be398b8f7123e82966942f778f573a83ac4e7de040aa503b53dcd43911f6b84d9d9cb3a5bf0e2728591f1ea9c9265b02782df25af2f9a98d7b4663184e944ffcfd0879e96532bdb48414a99a366208927f37c51e942644272ed53587f38868cdea60658e4baa8639b5730352356116724dfc89caa2b7c99c2f69b1a40f16836583ad71841e2c56d37a9c4999363470b1b3c74c657fd35326865e73ad431b2a9e89d7d065b641b25a0383a3df5d6220b403090d6f0f86a0e666756635ec50613112e4bc2c94ca34323ae88b8cbc2e6f7834fe2a465abd961081682271a14a127eba4f0fb2bd80b3875adb4cc9e454090d044f1162ce43c435fee75f91e86629c1118a40e22784981f3f265133d71872f1908416da22411ef5272772d09633f1878acc52a012acc5fd18a16a5012a99760101bc6129a93a2143c79230b7d7e7a1435191c0a98840ef6a902c28da86e508e3fe27224fdf0343a7f8545a009fdd646f334830a7adfe7e5aa3dc0ded1dd994dffe603458ef576c4a6ff53638f200a097de69390f5ea7d6d9b969d9142d38f5fd784a31d1c8934af991d7dbb1eb76136e731d9272de4d80085e87f47ac739993fc4ffff7b9895107dfa4df9d076d43fdaaa815c9154a015a98817a7539c740a26831c83e0dc947d4c074746d11989aa827ba78dceafb2c4a09bd5de040b79e637267a9fe1376d00b2a86a1d3bfa2f38a46142afd0e007e384b57853567bb0d353967245551205937fd918bdcaaea4f13a1c5aaf2b4d2706c02dc37b03e3a4e06a45a6d551f084480af162bb2f3a365e516446619f7908458dc160c7330ed97025229ff262fad57b6d86317acd2d6a5a6613fd24307591cabd8107c67ca510b93ffeaeb8fe6144e4c1b06f8662ea35779ad205b438ce828ed07d44ceea45350fd7d3c59e59223dd6bc0db404f3f66704c118be30ab97c181179f5ab704a0d288be196869685cc74d0ad22a113d18650f2f576847df9716f56f28d5042d9a8377e9be2daa4bee0902ddf09f29ed81bb0d0969b2d9a79bfb602dbbf8c1015335b9cd2b6e38b0edb519bc2a0a899ed12c43a42ce156", + "authTag":"a4202d90a58cb441ca68b70dd6c6e3e8" + } + }, + { + "GCM":{ + "key":"406fb822d1e58e53110aeae68e74672b75df13b0e9158a3aacbe808c27294a4d", + "iv":"466a5422f6227108864a13f6", + "plain":"1f3555d092e503cd92e6844052377646d79a067b5dd30756eb5ca5f351cce62f98c3c611da7745067bee3b3555df669ea5febe4fb8ae", + "aad":"ab9b858c21270a23225135db", + "encrypted":"8dcc6481e8ee8036dbca48b432338cef76cdd2746f8337515461058f8bec0f942da35d9941996f94400cc27d2e947c4ca330b5afb753", + "authTag":"b83ddfb577720e01871df8ef1ae17c40" + } + }, + { + "GCM":{ + "key":"0e3411fc503fc559a9cd8b0c1d47ea06c65fa59d44c9d72fba1143c69df78284", + "iv":"466a5422f6227108864a13f6", + "plain":"e77d48f9e0ce923f8d1b7c4d26889b73", + "aad":"ab9b858c21270a23225135db", + "encrypted":"89fbc2da72a91d77e748baf748875dd8", + "authTag":"b5c388dbfc54d7e622360429b07b7655" + } + }, + { + "GCM":{ + "key":"a8d9e3038aa1b9f6d6b6ecb1a392e34fdb6bc570331bc96c54101fd5357032a5", + "iv":"466a5422f6227108864a13f6", + "plain":"431a2bbbc6d640f947a666215e5764e02a7b6d03048c5cc2d2232a3fb51562e4", + "aad":"ab9b858c21270a23225135db", + "encrypted":"98fa0701fce93f30cc4478c0c9fc47ee3c62f59e37122d2e25e536fb1c4ceb0d", + "authTag":"b9ab7f391637120728982e13ce495d67" + } + }, + { + "GCM":{ + "key":"fe9e6c4f2d42acf49d768b46b7ccbc1870fbaff524d554c19fdcb35f646bacb5", + "iv":"466a5422f6227108864a13f6", + "plain":"e0b24026ad744a1e6565fb1388842d5bdb07583e17d0871e34c7d4017aab8b6078332555df3bfc71d321adaf53a2b6b454537427cc7bd0f5b1a061aba2b91feaabfb8b93c26b0d390532d7dec48b425425f24f2dd38dce003a778f52207efc28a82a52ede00880b791970b675d5b1082969bad0a31a33d53a0ba9968a319f33d2204867614b8bbd019f7b71cbe0e4846aee0e440762bd1023f317d93c38162f4fb989e0ff2ab80118b88e69ccb308e49e72c441388a40ad18f47145a636673ca8d0ac5b424b98865d593f30f4eda6127682975af5b716e63beba5225ec1c1d6e62de7617dc676304c2c5144827f9189f74a61c729087ebf67955d3aa19810ef8", + "aad":"ab9b858c21270a23225135db", + "encrypted":"5a6727651130e79335bc11a4c159431f762a5f923da4c26ecb5c799f6dfefaecc94d6df73fe3d080c4309eb792d9beb63d2dd8fdd68a97605f123959225912615af3d209e23f33fc8760d19e22c1c79e8989a25e4591980b5354ca1d29eede44bf36d1c1ebec5dad149f93ae744e183550696b12d15dcb53b3b15c100585c8ef16024de877d80e3a46a1b697a23d79be1719d3716ab4882754f8ca4a7e2fc336ab6312b63588dffcbc6ee2604c0f2cd62a896d6b391b42c9f25d1ad62c66baff173b4d06783003b1a839cb048d11054c8224dbcc99cbf97be8a3474c6c4a05559f00e245c988c43be711e993cb6cb9d322b96541f89b5b0098dbf3f6d0d192ad", + "authTag":"3ef909a39c1c511eaa17d16efb65d0b1" + } + }, + { + "GCM":{ + "key":"aa36159f7e4eb2a704783622a9e0921db15f9c161900d69a36b374c3792737a5", + "iv":"466a5422f6227108864a13f6", + "plain":"3ce3957e318ddbacdd1bb0b5", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"0de1702fea1b9ccf7a258ccc", + "authTag":"752c1bb36a3d4886feb88aa9ba1981a1" + } + }, + { + "GCM":{ + "key":"3cd8e2bdb409f0e153e72b02c20a64f3d7ff2a9355fc6109bb80a2f0e6e9681c", + "iv":"466a5422f6227108864a13f6", + "plain":"4c1ecb9c6d291e831a0b97c3", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"8e66467d5f452285da4b874d", + "authTag":"e403263040baf1bfb099703c71ab8a28" + } + }, + { + "GCM":{ + "key":"a280b561acbc84b20abae3a030ef6711b0565bde54f81f4dc2822783302f0da5", + "iv":"466a5422f6227108864a13f6", + "plain":"ffd8912d74ed9c90cd0522d615cd79546e7e8e", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"a345e095729494f3559bdb9a3a5a7d88d0e7d4", + "authTag":"ade748585a9b3f5c6594b3d862c70439" + } + }, + { + "GCM":{ + "key":"01428f5c42c136e8dffbefaf8910b32b4c6021d9d27059f5bd5e425a10852f95", + "iv":"466a5422f6227108864a13f6", + "plain":"8f", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"98", + "authTag":"225063682d3b4e5fde99e824c87093c1" + } + }, + { + "GCM":{ + "key":"02e5ff3b380a4a9d87812b4e7335a41adcfe45e2bc6e8c009663d3e9d8b02e56", + "iv":"466a5422f6227108864a13f6", + "plain":"d9718117c800dcb84581c1ee6221c6483cd7dce215b1c130d4a13a1ca1448461a591017af45c1a5607fb3d49fe5586ae4624962c34dac7f1b04e9279a56633009f083eadf5cc313954e97b23503d65bf3eb861ad7dd87056f0f25133e8901aa15dbf502662f212dcc6641cbcb2f2521408b7b29ec4165a0b5c1a1e3a127b137aee3e68031baef86e041e7bfd70939945499b6e2c5f68b14c2728169fc6d7cdbdb2714fd66db7dc5a9db9f0173c64131f3132f2c192ff28accd124fd6fa6ec38700386e04247252035b722307b86f3904a92cc2c5ba30c99ef3116bc65ff36f414d86a2ae8d598371478a8bfe71cdb24711382707c693101b996bac2eabd56dcfa5204791dfd2718a1d4d621f7dbeadb87165bacd41a9104323bb54a16ddb87760dec88b2ec1119babcfa092093f07222364cfd392bc8304127c7339ce6846891e41fcb4dd8237683778ec691a1c23f063841fecbd43e8d5247707c89afaff02a16643f9fcdc53c460c2730c3be8ed9482dc2c4d7864eae5436050efcbc2f3e6be9ad2e0b5f2e2c3fc22b6fcb58866c6973001a48c701b4c2b5333a7e175d3a20c9f58ccfe59d7946938b83a67ad5aa22cbf7078923447e74c434fcd7af61e9ce509b733188411adcb3b4c2d97dcd1ff0ed19b8bc13f40b6e8b9239b93445c53641858321f2cbbcb07f6d829c4db069601ea36f0d7db6e2f73a0ec2fc690837945c44aa7410753bbada2dd613fd683350c30d7231ba6d606d687894d6196a2f3cf1097a2cb474f61759a96d7afa5338a99bf75a5495f098a10ff8f6465e61dfe27cea77a4f1356d8784fe99225989a389145c5e49d1e0947776e3de6eb0b21abee129e58924edc44e251a051daa603266ac1c9c0dd82c5b2f38b712e458f3475a30092b10c528c18d5b63b18cb70e99a74e6f903e1bc7913ac9d928e8e20716b12aa6b5cd8aadb4d9a384cd10f99d4f81dfe1e16999d715266b960f91f9b787f215c4f4244f5bc9aabbb423146d30c68905dca17ca201e173789db633c19d1869ef4f71995dbc7d3ad4d3be3c6f192631b7faef42feacb2aed15402bedef88206fcc151ffbc4c95b4e5d79db7d3ca4443972fc356095581ad6e1ef6cf97151358be61628e4dd8621f5f0e07e6b596d7f5d063bfa785a539c2315b6db9afd8b3fc43eecdc80cb4007ddbd57674e8c93dc0802752bf4cf66a20fce8d50b2e1a85af9e640df6465a7ccb8e0c9746695c3182b3ee02b3e7bbd59729ee3d0c2bb4441b39aafc1adb12ccea1e5dc60942fb4633071419a8f155d6a33071fe3a14309d021fda4309c356338c2bb47a28dbbff2fcdc99ce14ee0abd1547e2b2c7fc4b9ee8b73ff35946b574259e921e3743e5da5ba43ec79870db4a5cacf6f4f47931b5e5dd115ec01e60db7afade80936d100e96884ae6a22c20ff4e5080659235fb33bfee03ecf90177801e873459bdfb7f8c8496049cfc27c256721dbe5b1796dc4a9a712bdc09cfae88e7a2677c510810015628c1abcce31f5a75c1cf898cdc5fef86020a0f358ed86642bfe8a3a1947dd4394e6776252643dfe5cbbb74960f0e5c66e381b6ca54f52684d83d890d366a087eda4438b4bb95fb1aa7b38e4a430b19c9d4b319ec3aee9f498db198753fd2ac4debb5e8a68f44e84637c3c68c2766727c1351311b79376d3497d240073a67c77883d9803de9d8caf35d1dd193a382159d7e7e4bde3974083e9aee34fd1db10276bee5", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"1bc4dfc7e28c9189f98153da9fd5733d0afc0e3bdc5efca6be507f7e7eecf183ac8e2f8238368710f32aadb7052d7f4fdd85ddaa01c5d9dca916fc90ebada66ca38e8afe083c52417566ef59f0aa28591ade52360079e8315a66a7c9cd262c7b02243fc9de415161316ec4bd26e64c1a8c511655dbcd16f0c22f4fd8534fde611c7947255c7c4f9ebc236a5c27650fd4fcbf4ce55e6f4f83e5dd28f1c276d92a13225f6b1e23f58e60157b1357ad539e019265ca022381697d46055e0af748265730d75c75c82d14a6012f496040e67ee904b0740ad756ca069c01aa6a54ebac95e376981869f64b5a07e1d832c0d8e9edac0f53eb0e684bd003348bbf7bc4b36da318c5280c0bb015689c9df2525c6f0e903b551c35ddfbf0ea40b7bc58a7aa1d71852d44c9168890f7fc2eab49560501d018aee3522bd0cc98f3558c599998b005af2075f6999f58cee8652450261cead606c015243c13ecff01de36afd79383aaeabcdbf64439b574b7066872b54d2a7abbc8d12472262b21092760db562f89ed6997df8032914af384d4a368ef4361b9bb06b4a5935bf6a19028559bff377434c956f99d10a7991ce2cc0dea58e9946442d3342b4d15bcd660b05c2db98788c0eefdf4fd72ffbb184fd0205d2fbf865830d04e7b15dad269715d2a48b25e7bb4339586e1cb6c7a394dd04668e6d171578f6237bd8fc700cf49eac9442d5a01b705fd1a195c8959c02dfa553261ca28c04ba438b36ff3aa6a27774a36d5bd2f81601b501f2b246bc212af7f462dbdf02bcf02cfdea9f7849fe8a0a1f558adb2ae7b2d2fcab44aadd08838826de116ae2ecf441e72c358d95ec1868efee67253db855dd44303ef2f47b1a7c42edeb4631cd43fcf0ae5b0455c0a376e64786af451789730aa85229af1c14417b9f966c5e5b73a2ba159623a23945bc1512bf46e9ce7271a6bdf433f66bb6f6b8ac21ad8c9fce95d0a4fbc2dbf0aa3343ca99e9779503e9528d873c7724e160037c710dc285de6482ed65bcc23d9fb0494fd27067b7da2729ad64f0271c5779c31f0ccdadcc5c604302909495b4d761eae36641653681e871120474fefbcb1b77bb9f7e44c01d896292d78f3e626b732e69cab606f9361773099ee583257eb05c2db109be28e9b7fffb22571d85a2899050977889e8735b6a3c4b47f141d1cabb1bc46e98787b434bfe9cf04317b6ca1237f6ec41085c071139033739d1b84c5440f4eed64d3b887b8009d394049eb81803ef306115192a1d0535319ff3f58d3a80c763702cb963a6967c889c303968705442ea923fb34ae5ede1ead0283d8f88dd7f71a65383eccd0f1b2fe77a976c9691518cb3994bda043fe28074472b90859db35f731c97988505830b19c3a15924c61fa7278a759579b927290f1d012cbf049ed80d6d2049045b17bd2b183d1faa64929b626592ff3bf6b6eb3a53f92e2dd46634e72ca6f9464968a6524b927208698e7078517e6e9f3df4a836f2392be4b42fa73caad0765b6f1358ba9de29633c4a1a73ed09055fe119c6cf177d41872ba9d9a16c9539ea99febe849d6502b5a5f71a24e5f128cfcca5a92548faf166e0b148d9169f24bcf1b09edaeb4fcd91986de6b696eeba11429192fa228784380827de31350b1fbbfec296989bc0d0672e69a22b0c2e6c843a1cbdb371c0e1cc34ff6368e587f2738957120607533e0b4ca52a254ae956ce5439381087b93d4da1cb2f4d9f", + "authTag":"74d91435d6bf00dd4652a82a926955a5" + } + }, + { + "GCM":{ + "key":"8c1ac838dd79f7024a645f55ee2648051a6283e29c55c880a415e5f1f19e4478", + "iv":"466a5422f6227108864a13f6", + "plain":"fb223dd7858d6c287e2ee5df626661d024e6d7090e8bdf06cac4b06c2d5fce0814d78b744c2ea4e2918b207a5980a1628a46485b9bd2", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"00be9429ca36284b160d45e7938f0cad9f11e624492b1cc9cd0c8fcc92444390d078843f6431064e3815a2cf25734ba30ca9589aface", + "authTag":"204578c376fd51b4240e9eca9c5cd6f1" + } + }, + { + "GCM":{ + "key":"16fb0b567eab605c3868ec8b32d8b56095a75f866ee00a7df03f7992cdd90096", + "iv":"466a5422f6227108864a13f6", + "plain":"a30632ead69b7306e8330978b9b8d7ee", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"9219eff905bfcd5b62357519a0aa7ab7", + "authTag":"a63b7438387db254a246a9e389a45ca0" + } + }, + { + "GCM":{ + "key":"30c28b88b315b17cec35cae1a172df3cadab6dd7e359bfe36fac410295e130cb", + "iv":"466a5422f6227108864a13f6", + "plain":"6af40f815b9b43e8b0c7f452d3f875a7291a15dddc677e60e49bf8a971d66268", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"f88fb97459d735be406d10dfd8145eb17780e85d1f86f24118063f976ec7e148", + "authTag":"77f27755b4e670add4a5e356a6b9eee0" + } + }, + { + "GCM":{ + "key":"50f669bad47cecae2c0deb5fbcc29b9d3387309aaff4531537090dbe63e28a66", + "iv":"466a5422f6227108864a13f6", + "plain":"23196e9f9889d1f57a61c4dfe2c2b25c3e482d8890670fe13562f7d0dc9efe7d09740b8edbc13ed258b61ed9174a3b5c7febf0ff2e19d41a44a950c9f1b4ebf14b0ce5e0289028975a51e4f4536b1b0757a8b82f171818cfe6ec526c45189f8d7030d450cb22f2f9bc7666601566616f996ac174e3c1e8d847256a64df2f895e642b3e3f9474bd5b325740af8e27f23b1b327ae50c8a8607a4ad5af0fbb26d982c82774b64e6248fd5584535ee3bae977f602de4a8576ad41b7f929444365f3dff1b27fbce2e7559dce6ed85d458080ba99838e88066812373f4f5433d12cc9ec8e6f389ed84cb00645a41b54d4d0bda7ebb7b19179b14c5a541a19203255b85", + "aad":"df9922afb6f628875b3c8614da304f58ad9e71", + "encrypted":"2ce9cefc4119ad3edf58cad03e29e0d0295568aae6e944a5f5803a12fe213011033df3035968048402ea5b00218886517c940dd7273a30aeb52980db40450b6c9a38cab56fba9038dc8e37d02cb531e8c255de71a6464084156748887b343127aa6e2b57f0c04005f3bfc5ec808d5471f16cb6cc635276660de99886445b182e3fb8f5f504d0792ebce8ee34a538089a30025da5573ee2318f1f4f91b30bc685bc378025a75ced9442a601dcbfaabcfe7c2ff6a2a0355a773e76a0c22255cdc8ab39e3f289bf18c3ccbcded52498812e197c144485e7ef77d8411a0694a638aa5ddd287d4c8e6b7ad3a41dbe94b1430addc7eb6c152986664d780b5dba890807", + "authTag":"005a2c65f5d96b6b9ac3633fde8268a7" + } + }, + { + "GCM":{ + "key":"2e61ba7b58f9f61ac46c10c736409b45085fc11c69eefdf0a4a0bcb9d08607f6", + "iv":"466a5422f6227108864a13f6", + "plain":"5ffd2cd2daaaf5fa2e5793e7", + "aad":"null", + "encrypted":"54e06fbf5433997be14a569c", + "authTag":"bea5e70f4742233eac49a51b94225371" + } + }, + { + "GCM":{ + "key":"6a33be648ca69c47b9be4d1528f96ea5c3e593916c1fc3ed109be355398fafbd", + "iv":"466a5422f6227108864a13f6", + "plain":"c36c6eac7a79933bdb3c9640", + "aad":"null", + "encrypted":"ccd487d0af72b96c7a8bce5b", + "authTag":"3e12c06f53ac1abeb941c9ed97e00c4f" + } + }, + { + "GCM":{ + "key":"60f97f3e2a46ad4cba60369a93145ff60863ee8e66984b59a085ae3cb47a1949", + "iv":"466a5422f6227108864a13f6", + "plain":"2159a563fea69a93886da35c25258e4549f104", + "aad":"null", + "encrypted":"f3c595322345efb37489482905ecd31061fcd1", + "authTag":"a2605e6397f6329e579b085f088bd0ed" + } + }, + { + "GCM":{ + "key":"35c2717dd9628dd11e730975245f5b1b6f58c78f074f35aeb2728e20ed57e8ac", + "iv":"466a5422f6227108864a13f6", + "plain":"19", + "aad":"null", + "encrypted":"ce", + "authTag":"373d8669f3bfa68fb10a4688c6291146" + } + }, + { + "GCM":{ + "key":"ca89b4857645562bc2287a19daf0266814c08a2d3144a378bc16a9a2d0b0842e", + "iv":"466a5422f6227108864a13f6", + "plain":"637e64774a0f4f4c3c9ef90a3226d37b8044dd1430c9da06fc0487c07863ff56538b7392153637284f84389ff3bb47e6d2b6ca58c02347715753ec7257eb45ac58d18433be08b8986dc318494eeebf5e7b435cd9891adaa7cef222688c463ab0603e267557d749c6c3b90f75f49d76d582ea5f9cbb5f238b46f66503b0bcbb6b6b6fc9df6169baf2dfd5f96e066c9c702b3f4e9d5c9a8d7ab2685e159f4fda6ec26bd4808b2466ccae046b3b11fb2713acb4b138d8e4d14075c7d4c028c1c61a0cfbc232fa696fe4256de9ecf3f7372004d3a29f3ffe65f927405532b1c43e3dcb935fc8cee877f19ec457a79b5fff4406215c2df34a9da691db8a61c21a5b56252251f24a317b9150d6d9e5cf5bfaa97ba21eec9bc0fc52ecfb727dae8f51e5c8feef9a3b4b21e767d7f323daa0a9ba28571c8e632f465dc4064366e96d4755b1d1ed1f245a2806f4258b776c4eeafc4a134b1edfdcad6a204fbc3fc94685de9e20590777eac89851fe342f81864a22d80cff5c43fb57a6bd1af6e3d6cb50777868d742838433b74fced10430a082cf528ce1019968d3db729335bbf306543fc85d738cb317e49ab1e5b0356f477c1f10b716b28769506529066f13e6840e00743112905051f5bdd8d68d40481263eb5669298b48e59f4b1c5d1b393c87e6762ac93a7e51c6705059cfc0a2026f218497c084d7426c80765b4e5817a10c62211bf2705a356fa071b3741c9c8623d2842dabfa143f85f1945b5891cd0dfe9a1944caa4232151e85ccc756dd60a16077e19e352fcc1ee66ca7dc2a50922e31f19d9205f348265e5c5e7cb92227b50b2a5e5372e761cb1e1703bf3706cf47f2c0c84d12aeaeba417d784caefc0044dd6c09894b4da6f2e8c4b046047410ff6b95372479c46e1ab3336e994493f18e815567302526e9fd2877e6ee89fa0027c5ec46e62b4df15fedd20ac2dd24155d7df75877b30c4aa50706fac6486dcbcdae1cf602432e0a4b91111255ad14ab84b2ef931193328f05fc706a4dd13e249799bf048b1356ca676d3c755a9f6d8252342bd5c8f44d9f6c4b3eb0682140c04199ee2f376614a6956a5fd1b7789dbf5357aeb5cd7ae326d9a001719cba86ea6a4d9e80f9775d78c450e9fd608717bd06e20b0aad3fa78a6042f0f10703c634d2ba1de7498e32432056ed2c13cc5c60c2efe778f38ef5dbffdf73dcf396c6928b9b6b3689c456df1653c8df8f5b2072d1e1f329baee1dc3a670d5cc46ebbefc9c46fe347f9d5866a153cdc70b7b2169f16b37dacd88c6cc503e53abed78de16aa4b80d28fc4e5345ff91baf2bfa433956c31ea56c214c8c1a76818d661f030990f3f59e959ee1e0c251136dc0b11d492dcba2b00b7dc7c99dda8f87f4e34f492dbaba69b3dd574e72f82a22932008f97041dd9c57383fcfe5272b50ffee50e183f14a97da24a8fc73f96ab66e600978e25e218b19687d4e67e059128ff721e332c71c9b9834a96075c00151ad3e72ea23a6e2ea3729e4cdece0db7772ad7fc2f3f265084f742a3f7355858f720dca9b73ab72b08cb3c65529356695b543a05e74101343b88690cd6c2033af9f7f9c9f79ae833f5c6bade4c7f9da9fc05cf5989f6c8604518e56b1066b4142c7cc03cbf583195e60763aca2f60c9974e24030485a624df35b3a12f40f04eaeaa2d24f3d0da91a5cfe6b52b9a3548a103b054a4dfb2592dc05e195b3fda53b4f8ffe8b51fe1c356051", + "aad":"null", + "encrypted":"ce4e44f2d41e8ac46d7007af40ff2892f454c3f840eb1c2e9ebd44961c4b5a2cdeed4ea139cdc4d0c74a6ed135001e2e7e03095f732fd3306191ac1c347ebb5617ca6f66c3c7eb8410bc7537195b2a358ff547d64eaf2ec5950d2e20cb245878a9faf226617ac93d3ed532dac68d341f20538cf42ff33acdd92507fb74a5bbf272f97c5084686f8e61f2ceaedd8c3d346543c076bd36fe2ae60456031189f7cb0f35f5cc3ed8b2d2a0da4b815c3057d7ea42f811e19729d300c3a5a4257a543b04d3c0e6d371975deccf3855eb1f4bfb4f49a738ef5930c674b844da00db7ab29ae7af4047515749a0a0c4c54d3f78647e9986c996a0c2f010720f8e349e5d7a1ade6c77f15c6051ffcaa9fb56e22754d9db43556f7fd2691d3960d55afe0d7b27553c3b2368568ad7728d5b42c81db334130af6fd29ffc095a53a48127b836b3db3d2b917ef33b26635388e47ea19255cb470800161a21e3f3e388d7c83540e36496caedb3bd67ae4784ddeb793744be7dfc8dc00c063d053e03146208d964ac34e7b2c517acaaf8e2692b83203e809126835551898afd32e9e1f856dbfee100c6f2c6410f6fc9d6ffed07450cc394eb582b575edba70508fed46e8f767486535e56f55cecc9108ff89b4cbd5adabf080545b19628d06601e0991dbe61fa1ad94f906b3c7386ca1939603744b3eb041e826f61417ab64c484064b385bd184eb4d44feec3505c9f5e58fa4d37e8a31c696f3ca9f2a669481efb5f8ed7ad03b27caac67777c4f89c70eb256ddbd9d5a9264e7210d2a8c2cc3f57420751e73cae7b5e3beaac7b52f03bb72929f71ccca8d035326110ee0286a447079f448779e08f41b6cd50335901f32bd01019198c0757a220527aadd9eba1120723d8ee6d93a1ddf28165d32bc2318e981ed93c7a10ef7f1b57b0a9f6084b287215c0b98ecb63d326dbd13ee4386821867b098e3bf516e4004102fcb061556198c89641e8b8639bee603ece9c9d01234ee6339cf0e1c72d14e16faa9faeb679cd8c20fc704a5df011033e392f468f17315a8db5177696bf10aa68b13e0fa2077507d6cc31b7049cdb9c2a8cc43beac22851a76ce350c38ef7689dae886a6a33bcd1b2be7b6bbf6e0e0d79b2cddac2ea94e5dd4dd1ec4e45e224101fc99293b34ab797b95fb879d9dc266ee13c64dc4c5dedb213335d77e70e87ee46bc53327413cc86d9940140034ea8f679b076953aa93fcbf604eaaf7b532f659c6f049b92c6fe2d520fde704e79b0f3b0b9bc05ce95f5d079bf3e5863d9e95c401b0d9630ec08a26c8a85002b36b5b91897a514045a7fc30ada6c0ded74ec12b4906a87900108d93258806a7154032dca279dcb512218067de959d70c9b3c281b7ad201ae2091a482acce813ac96af48d3e710503832c0ea0f312d738aa7ede227cec82dfbfa2f06920f6a0b914ac35d0d983818b30103384128026a52a9bcd157cf460d97fbf12e69616c4ab0d5d02642777f21ec8f1db5453f2477d2df6769e41b4843d18e4564ba6a81d8292bc69c7e890affdb608bc94d8b5202813a38f16f38c4130bfa62680a345985083baddf8be33d121dc2f12ad21e64b775823f3cdbc0bf26dbfa034553507eb18c4006ed6acdeb870f77cbd381e526e407d2de0e83d7a7e8952d17a01323dd8fa055e2fd34f9d2bcc7f95c6769dc0d7f34c3d534f57874db099967d19158824ae2ad181b7c85c68e85a8eb9c43a30368a0", + "authTag":"540fa9dabb982515e5d14a07c9a8aa04" + } + }, + { + "GCM":{ + "key":"8d7fc1c9a607fd34a853816524ccaece89007c2196549ac2ef6c86163ff6babc", + "iv":"466a5422f6227108864a13f6", + "plain":"d70375810c4ff7aa78219c69a07e0d785828ada6740a7b8eb09022564b3f1bfc3d0368a74a56c4a8b50176d710bdfd3fe17650515e07", + "aad":"null", + "encrypted":"5d5fa02bf9eccab881ea38f92a2ce5d55fe16ac0075643c2399e29d2f794138bea9af308c715c0604a645fc4acd0bfeaf6fedbe9c399", + "authTag":"3fc79c3f5771623e892ccab51a385ec6" + } + }, + { + "GCM":{ + "key":"91576300d9cd56dc36ecc0776c0043a7eb56e3b304af51a18f39b3dc152f3aee", + "iv":"466a5422f6227108864a13f6", + "plain":"f29bb6b8b391857548f03d776b0c063e", + "aad":"null", + "encrypted":"6da591d471f4c3004f18ece49ad9bcbc", + "authTag":"58153913bb4e17f56bb31731737aa177" + } + }, + { + "GCM":{ + "key":"8ab7bf5ad3a40a2a9329e80752709820316216ded8d340401aae4c02d30fc60b", + "iv":"466a5422f6227108864a13f6", + "plain":"606e2ce4cf3aa2959734c08c0350372c66bda4c032fc242321097c529b9d0a6c", + "aad":"null", + "encrypted":"c7caef2ea0b229354fb22fe8ea55059bf67ac24038e464dd26b5ea4198be5ced", + "authTag":"164f2b7fcbfec266f6c102a6c5373796" + } + }, + { + "GCM":{ + "key":"bd28aa12cad32ae609c1ca737acc80864092e34bba8e67c7f4e1c0456c10ef5d", + "iv":"466a5422f6227108864a13f6", + "plain":"e8f6e8bad0b31e9b7b93168051982cdd12cc893f81c3cc7989b27d054aae0b20feec864b6560ad9fad34a730df2a8ba7a90145ceba3eab7d6576e8dded2bee76a133e3e7abe230a376fafda7e2ede274313347e185f7e3038e024ca8d322f8a739cb2110a26c5cd705a96ff07a0767558e639cd3c3980d68c48443df5d6cd442a1609c5f959dc028c11aa073eaffbd1a4f853d5a67d566cafacfe6f0b2a2d2fa2e64e4d1a8c37b280686114c6185521b35a8583973de8c38635fff6c68e6915dfdd6fc5450c0f899872060b9727ee7ddee19da2be4d3fec6c35724d4ec99f090533546a5417b8e1cc1d03d7ae4584ce40cd43909d89cddd194a77dac0ac2018e", + "aad":"null", + "encrypted":"2b2b2abf07af0edf36ec525aab8a9ab04f5aa6b8d7deddb430b505c73b32c74ab133b7c4f10e39abe47ea9c42eb85e8ef0b62db3aaa734492ea571f4078efeeac246651c79d5e8f5b3eaab85ca7d823859c87eb0d677ca20fac477dc1f1f524ad6d53260cdeacebfc7f348f2191979a5c9f3e0a74819ee911266dbb576236639ebc7204266c2b3c3fe1e9019ddcc774bece5164a4e52308937175e0bd8bffbf7493ffe3b2ee8d2f4e14b0b2be59660c19357a8e1b0800b7776029bbfe3e5a7666f3d0bf5e0c7ac6df23304b5db53d49ee5e18104b9f825c974e1365b5543002e0f74c4e8b80444900645fe8ba64075d16a267fdfa6050dd5b25c2e6132825209", + "authTag":"59e45697478138c41e52e76125308b0f" + } + }, + { + "GCM":{ + "key":"22bddc0b1ba302c44554553748c1017c874d5f90900db19ed2246022c88736b0", + "iv":"d0614b4848b7790fad695abc", + "plain":"cf4ac5c3883dd21343b6ba55", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"533cfbf8144f45a8708815d6", + "authTag":"b38cad5728353247af1f250c11026f26" + } + }, + { + "GCM":{ + "key":"0aa893dbaba934a04377a4a0a06e2fb95a24de7eb21db80b4df78c4c56cba83b", + "iv":"d0614b4848b7790fad695abc", + "plain":"db53ff62aabce1836b8299b0", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"c5ba51bd1b171f0132bbab51", + "authTag":"ce5ee9db7af36cd6061b7f89b5c818a0" + } + }, + { + "GCM":{ + "key":"f7f03ec05748ba650e1be329c1b323f57e5c4b7c946b57393083dd73c445f3c5", + "iv":"d0614b4848b7790fad695abc", + "plain":"687f48302ba9a781ad66dad50142e84b6a2fee", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"237092a468aca66a755c3b947bc84be61e77bc", + "authTag":"7952ff270537844624ef9e636a181a8d" + } + }, + { + "GCM":{ + "key":"fab9a032c8234105b3c62a49ee23a8ee4fd211af6073e95599821f849ca67114", + "iv":"d0614b4848b7790fad695abc", + "plain":"6b", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"f6", + "authTag":"32ffc435ea775d0df8fc45bc4ffc9d13" + } + }, + { + "GCM":{ + "key":"bbe46c36fb71cf6adebc9eb089c6d5d0d39a9a26b1b67478ee81748b12b1a3ea", + "iv":"d0614b4848b7790fad695abc", + "plain":"c069b298106d08a0d4bc7ad55b334c8ff88b42bfbc671af4ac5e65756ed12b0f33743bd0a9d8c5a96785bfeb0390bb724cf8e0ed9e3a84cb2f38aa92a0f2a3986b84e14d064e5156a0f343bdb1c65d2df60f767f387844fa7633da4d93205258b72664230b1186133c84e86a512698e16e6824e94fb8444b230c826c293bdcc2315ea9f2a589b098ade3ab6c7bba973294491eb039f0b19c137a5f8778398dcf4a22cdf26e9fe72d7b2605e9dc4deabd242e69244ff4731ad5e180d9256cebc126111279a8222b0d0f0263c80d46a0d40c0620b3aef623c02343a614ea22d04531b82f559605cfb8d57c78aa81569518a9e3793d9fa44bd79993988689ba6776200ab84d19a865fd8673aacc0e11413d6a7e3fa1f6ae07214d3826a7ed883940211b612f065f8f5b05a076e3962ce3bf335f050fd448563d2e30c240b464fdff3276f1f16b707aa3cef6dfe8e18d47a3c4931a155864b9fb3210bf0b8b968e02d168e3dd2e6267f61708c311e6aaeec82a57ad5de4e2897d875eaf2a0691f77deb50b319421ec2d81d6f5b0c812745b243b393121ba13eb756d9feb4a7ee107a11645bf80bac71579bc922e8d7447d84cbebbd07cca9450800fe8d7c93f9a7a8a03e403e4a359db7f76b058bdbde937c0e67268782b3ea1b126c21173df55ec02455061af3b768397e8413dfcf25093383074b34369bb03782bb32da84b52da768bb967424131b8dede65c6b66092de4ffe5530b56521ce92a2a6ef47eac1463537e6c5c3ef66aa058e6082881f8a03e6f450e80e42ed7250c0ea0b82907bfc42e95298224ec396f1ee2be4d99e8acc99422a4d91d22e37e3fe8e8b3a78ec1b91eb710ec95f5d1688cbd362940c589c46e858b8c4ad61aec30554b0d755f87e17b3c0ab7a22fce1d81b621fbc3d54fe52bf3d9e45ad3448adc4c35c1a3ea676687ca42e5a6b6eafe1a784f8860fee579bda8f607293624d3b0d899e44bae217537c84931f5ed659ce71f12f6e9617dd5a2694736810170ca61a5c567f486e4f26b419c783662783faed1eb886cc378da6a8c01bcbb42e67e67b63a1ef3e599af121fce2636d8c1fa08fd7ce50f74ed8d8c23b7c49c46219e83c4411c1d69ccec41016427d9ac82327ce2023c0177bd871d2b7fb883a1d1363cb2e677bdde4844aa4d2b3554ce54e6e4fd0567bd4937537c6f3ffed675d20471dbe772cfbf4380bafc14b9f39fab11129040978ad54e05c7906b2aeaa7bdfc3e225a3388f2252ed5b665e57d14f3053c4958056f6b65bc088339dbf42ff5e4cc369f71e1ee404b3e038752f7c2fb6e98a01cfa4542d8f5aeeb70c626e6e4e07e3724af22e7ab7f3718d8cb9ebae05442e529aeb94b70270eaf0385b07bd295ec1d7aab485425f7e23b94c9046a206c721456040cc405f3e1190b7cf5cfd7f44aeedde0d363e37594025b34101d25da88761e367b7f774d4c01704b20916edd747d0f9ae46a66d4a5dea9734ab7a38a901030845c7bc7d877ca92bf170aecd6a610f90936ba20a869c22bcadc8f7ebc3aa79b4e5c25832c2b420cdd68c30a6d0c9854d3ef86420627cf35504ceac0ec54ee1eb55ec827b4436fa50e737d0c69def288a1c4a5d3d594dafe7370132fa7c898b98aef1534771446751a73989915d42d3eab66a1295fed40543bf9f126918c0cf09e06718117ffc1d520e5d0068ff3083484b291a0580aad8e63daa1363b1838362b02d5dcd63158", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"aee229d98a6b11c75de246c9bd6e59b10669cec75c024369f0361c8e797def6b3f8ca8750ad9a7cc23b5ee7d271bc8357bdc3e4a30f1ff1b37524c6eb2e55e787a838fee72f3a789e2219f6579b34bf8ca807283c89cfdedd5023a6df2cf6c1b4c4744950cae032707a1cfca47db557b185a5cfa4d68a3830448e1b8c6b0e3ab5c8f641bcaefdef2eec37e12c4bf98481e4e21b56503e3f0c9508ab0773a355a31ad44036696b552f9032e63ebbc7f91add7c4b485a355448178ac4c8a00bccafc47fdc4af737565fee478b031ac296a6065c44698635049d2f9d9b39791e004d94f52810a0048a0139ebb853edf8ae519b9ecb8ea93f76ac82284695cf9418dfe74f78238fefec2e663582de8909f27e59cf1e319b9a75a6901f7bb5e4ab774750dff97f646e1763daeef102153069f8598e005c6c2a38aabd95eb34ca6787c8ef99dd21846560f9e730dc640dd0fdad6fcc2435e2fdb2e93780e81f9b7c87bb90ad6246224d6fed9f43372deeda2f53a217db9f322b074624fea9541926221f2cdf247588f7d62215b73676d27dd6b607eb0850a480dc9198a3e9cf02f6f27d9d15e44c6184730648a970e34d00011f31c1bd962145365ff3be3d2088dd7e9e733f677ba9dbc6899fbb6a1efda9e11f584ed19e37a99a119f012682da46c7ad181ae3af6de6bbe688360d0e5753d536aeaae80c7b74a99c89d7f6db12c0444109d1233e12345f2be52418233fb6b2dbf33bb0e0dc6e30e3a642c5b7a39094bf4924e0999a31faa4afbc04e24d9589c14f0c3a33ba830fa5df07e50a51fd83b1001fca82e7ce85bffa78dfb1a41b66c4af2fc64f7a2104ac2839da27bf2ec4947fc7fd8e8aa07c2d2efa2e899b3c0f4fea93256ae814ee3c8f22f40131e7768ab4958c29f4d2a628747665c4c21ba816ca799e733bbc5f9b20739787790dbb11226b97c86a1794e6f608ba323aa2c1138f65a23852e8c40e5861ee06612cf9fb424d1d28098019853eec1f34f1e0134928d021f5cbd091a6ea4dbf6ae124c29a400f0458ffd68dfbceccaecab9d66b138ebaf52f906ae984437cb511f94ac55b40bb57cd27936ccdd2ab7ed4b023fa1e8dd2070ddd54f5d6d1461773ced1f7770535f85002dbad92ccce9f6673d89a9d88d25b933c9d0ba36dd47bfbb515ff8ab23e8e0e87d389ca2f1d53c9bf5eaf7320df2525c1a0207f30a62e2e09998411d2696309c031126cce1ab29782d81e688c669bb7ff2dc791d7e4a29e134d5f4aabee91f0e0d2a7d9aa7b6bca2c107d4470836baeac0329ca7fdc8806ef7375a24441c9b7a9cb86e4b9aa86401375ba7b09dc60917d6f548683c4fbad9f0e02c665b84f311f24eccf6057b1bd44a67a915b2b7253ce63fd731ff02ea6c796cfe59bfc19f71af8d7c193f0ab3090280c5081e1dbca71f483621dd04324743e53fe53264d011d309fc13bf18ffd3191b3aa4d18ef7d74ae0967e4db62f5a551b3ba5e5bb632021792d07b39387e4237ae3164f74c2988a4fb8918cb50da0b62e68a6fc9245bb597210e17a5cb29b2742bee7f5d1af258ffd5b4809c8921a6762a94648fb354ec0f9879c79f3e2bb8dffe5574bdac8a83ed6c096b605fb3a9101c39a3ad6da06bb6c02d542d66622e512c8057099995433187d10f9423723471a068404a77591826ea244741be48f603b5808e31754ef53379aae6ffd241b2934c66aea58572aad4c48db8f09a7969fd9f27821", + "authTag":"0d27520a61b50baf704f4d130cb6f4f5" + } + }, + { + "GCM":{ + "key":"a76d86ef826507be38b6afce851b867bd5b5fa33ffc65bdba64d1098e16df681", + "iv":"d0614b4848b7790fad695abc", + "plain":"c9c60cf2e6fb2b5516ab9b4bd82c816ad3581bdff17086d65da91346d19ff1a40db25bb17df9151aba09139522289a61352ad0a49de6", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"5d6da74f45e5d547f82030af7f7a37ce1abf60743a464828477da1f24de1f47ac516deec287409786e98f71ef2999de69d2ef2013f97", + "authTag":"5c16e9898b5661b65a60fcdc9096c321" + } + }, + { + "GCM":{ + "key":"be8caf31a4d3d33ae8748081224a59b5baf13b70307b59c3be36f7864da33ca3", + "iv":"d0614b4848b7790fad695abc", + "plain":"9274a1655fd7e2a412224db05f143949", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"1785e8f9f007a786fecb18cd6cd066d2", + "authTag":"c8054afaf1876600ac3c49061715449f" + } + }, + { + "GCM":{ + "key":"c32b9e9a58a3223aa8a7c9d6dc0ebd0689eacc3a9974a63f8b5d23dfab2be3fd", + "iv":"d0614b4848b7790fad695abc", + "plain":"4d91eca3a8eb9723b416ab626fe21f8a85611fd0888b9333973df87a3b073150", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"d316aa4d874ca74583b65b31d73abc1dcbfca60daaada3d70eb26369d58ad57c", + "authTag":"31b10518ae0907c3b98235cb6eab2fb4" + } + }, + { + "GCM":{ + "key":"ba0d583033485dd7a53e1056646205dacd802f5c26e7dc925a79d14a08224130", + "iv":"d0614b4848b7790fad695abc", + "plain":"8993c7ac46b6a6a8845d5d3af07371facbf46370963b6fda3a3d40e7892fbae01b7d5798d032b0a9d673543aec4e7b4f0b722f8660dec50e1f83a65b69cd2fd785952962f5e131953e0b08449b0a8c49be857b9a311e77a6a1b84415966a116feadf4c08594166ccbe3186f1c5d1e938dd0a67baca1128c414991ed5940438e2bacb4c7c68954b64f7d6ddb8277bc8e18ebde7ba2b2ce37f8a3e0a3af188c984d59b2f005b17a03db003237549edba329aa5fede725c59aa831dc89a4d6c4930c87a9bdff1a93a93530ed480dc428d9362369988eae06e0497f807b97105515595ca995f7f15e4245927992b6911bdb6760796639be2e65388f9e73a5062cdb6", + "aad":"5e950e89d4578870bc43257b", + "encrypted":"42067c854b7cee199ea91e23d8736adf25c5f004ecbd11d3b658ef3b3b74df8a41a77395cb36f47aa165d69b6e9980c4808ae19dbc1ff1d55aabd2ac51767dbe0e977a58dcc020e6dd123d9f2459fe7571388efc003fb7dba57c0b35a24e1998e00e5a9209d41d1d33bdc31464f29968686484cd9490fa0aa8fbff4dfc800cca7e45abc4ddf01b83a0ac84bc511385a943866dff6e3962afaf12a0e5ab1d8cc892ca4b794e5b9f95974b80203ebfe35259bda9d0c5385678a6984102eaade6b7283e139af3aebb8d8775e2ccaf4bfce03a6c25159101c8fbd7cb3ff88b674e325016b7134df09f5bd0e97c02a5ce6a159db5372a076424c4b77abeadcccb67ce", + "authTag":"b0dbd2b99e58e16adbc98a5f9963b8ad" + } + }, + { + "GCM":{ + "key":"5745209261e4d81851a5ed540ed74fde38f49e1b40bf10229802c05d9e8d8741", + "iv":"d0614b4848b7790fad695abc", + "plain":"6c0c358b5d05dc278b30a7bb", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"b8f048dc8109941c548aa322", + "authTag":"d65a7c852b866a4b28b21f4d630fd6c3" + } + }, + { + "GCM":{ + "key":"f7816768ee486e70a7ffe1c402da8fd24371fc1933d595d30d794abc6cf7efe6", + "iv":"d0614b4848b7790fad695abc", + "plain":"427800fa05b0efde1b06ab18", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"665102363c3071f0a266adf3", + "authTag":"92cd115fe80732dd60f1903e044bcd4d" + } + }, + { + "GCM":{ + "key":"4d7f804e816137257494d187d4e75ec6fc8e020bcfb63c517adc84d878c0ae2f", + "iv":"d0614b4848b7790fad695abc", + "plain":"1f07a0d35205f6038f640070c90833b4235f55", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"2f34892139740d8da916c7a5ac6c4d333e9c90", + "authTag":"e75083c911667ff8de0942cc612a67c1" + } + }, + { + "GCM":{ + "key":"b807370e5e69be844a12ba4c7811ee66ba7e0e6caa569f58fac99525a0f04dd1", + "iv":"d0614b4848b7790fad695abc", + "plain":"ac", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"56", + "authTag":"a4b556a41dcd634bbc3f09c9af3374b1" + } + }, + { + "GCM":{ + "key":"415ee3de8de28dc2d430b148ea71630edd460c2e6915378cfb4129f7781355a4", + "iv":"d0614b4848b7790fad695abc", + "plain":"c98d61c2063852cccace489406a8b8ad7fd16dedf867adeaf4ea6d19d6eb4142af57f1fb6e7fae8a80ad9b0811edc8df271196b87c07c82b6f5f12a2d4f31ec0da04690df3455e0f511ae4456a8c8d3e478376c94892a5d35d943e54630298461056cd90b9f4eec5cefd7a0e44ad9927ac649ea98f44164ea5399ffdad42e78a1d05ae55ef31c91bf9189c3762f4a0caf2a69fffa4a1324f4bf0d9e7874fd9b09cd3f44cd9b687f9891b51623b5a2aedae87b52c05e09ef662c452b5cd8436bf55f4bc4a0868ecf04483089a3f996c80aa0dee3cb516d3f896d9d82b73f39b6fad0cd2d8f88371c1fc4dc10917f8b779c4d59400d32d547dc3b52553ed128201f2944ee32a71b984c466b2373cfb2544a562416517080f13afc7ed42befe7df96af11ace9507eeb135e1f0557bb83627040325b99feff6499e6f7b05eaadcb62e885513cb2e3eb8f3a71c207d1f6e388ec6d512f91ac896399d489577bfa6b36ea009ab19e4072f4ac8b22863046d91f6996782279180ddb6f9208e51918fa10e39f1d6a6db717824a1fd84ced39946de85b4b918ba7e344e656accbddabd8c27ce05798fe5c27f4d0f0a35d2957b31dd846599444fdceda2dcef8ff466c345bfda00d2124d65fa33eb2afdddf8821f9932f7afea602990cd1a689ad27499911539105479afae4e9d21b8c2d76c88604328dd7afa93ebd6a44772025ff997320c6972734294eef438bd52b3c41d0318ea667fcb097cca99be9d376cf6eb65b6a8442836635d9ed9186d03d777bf661bbdcf3f26ee8b1e2e042ccfd3df868ca6ac4bc33a1ba55b4b866e8d57aeb1df8038344297bfaa55e7150f2eb0050442b81daac49c963b01fd25792866052d1b3d52c27ef8ff08042bb6988200a8253b44357843188d7cf72ebe1d4afb648cf9ce92452e09c4170da7fbd95e0501967d8a5c923e9f2dcc1361484f0db71525490e0d43d94f57e2a6a7de17dbaeb26aaaa6f6eb3d4317c48c2bb1e24eff32cbc6f71a9a8e2f5fe2598500c7128c2c5b0a8a3ac48e36ea8de14ffa2247908f01610656363c1f5e883fd89ab0be6384654da1f1f9b234e1c35dba1d2ab27f8b5e3522d3089f6a61a98e442710745a4060a70d57a6062e48f15ca51dd3b4d4c0038a9ef30ba514c381fbb96b357d81f9419e397b4d01fa23a4825e05f6cc035acf61e88942eaab8b311bfae5f8472da67ea282bdcf49bb6bc2808e4eb074d2aeb11250520b45a36b2e316b81e53623140f5c7ed9f859549b7366c6734205802546c9d65228cbf583fdaa98915955011a19d1e564dbb1778b034ee92e494af036d502b49967d7e4a9854afcbcec56539d356f5b881f7152ffbd0c25bed38c8026fb54df6dcd0bdfc8491d85106a34ada989b9820b673d9cbafb5f7a613404d3a1cf2d8d2508a39546696bc2c1c1609bb22b0ffecb0c6a94323583aea5827f8abb9d2d6700e2fd11f8a6647abea090883cb8a1608dd475d01d7c61ec7e6567dbbcbd4a3f2c6814697ff490ac856304892aca88a3f327253fe3ed7b7e753f0e66756da3492ea89e3a008b18c6d7b7653cc19043e0389ad7f45e4a3b38d5a4a0543b8ed1bb1c1e1c372c25e833a3a421b5242113c07704a13c51c57d6a5b0e16933288d0de523784f451d47e3b5a5539a897996fe368c17761832fa3a268f8bc02e6dba50f53c17553b77edff9b94ea845f3ed614454bd5600888c51f023570377cf07b67bbb2ed", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"7a2ecfd12dce4f6a97ace46d3c96fe9803872146f813f667545f49734d4dc22c9e5c400ab70c6f9cf7e6abf5c2d7200d3ecb67f01726ade7208a3b136a94674be10bae8c6879a9bccf946d3d1e1085ffceae7a3d83d57883a59f0d6dbbe731289095867869bfb8e1ed56f2648e90c03587ef746b5243121d6c8690b6e1d5198a5e642a1f0cb8402f68227c7b149a6d674e69d9b581f5179a7ebba57278eb5c91ed6840bd4f5add6b3e631ea0f633f81b03b878235a13a8a2f05c662179666ddf33085c8da04feae0767677ecf3628792c45bd23924286d07f99d7c3b9f3a7d1cf296842c969c75536cb42874abf93174b67fe49cfc2cf77b0db14b0a8d0fa95a418e8d483661150359167b157787fa103454686ab7af7a6dacf6ffbbcc4fd04298305a9bba0d3414034b3a6b954629002a3a5677c0a350ec4dcbd4a93977b5be2ce242bb3fef2cf82ac3e084510f4224d70fd3e692151a360baf19813d5de5234e9e79ec2fc283889d9b82ffa807f4ad1f366e2916898ca45e658939e9090be75a8047945e8d6347d66957744a4fff12a39daa4bee62b1e6b261c456923e45598b0aec59f0c6fe4e9e2719f06feb904a6f8fcdec0b0741f7b120fa569854789d64d22160245ff0fc2f6eaa2a63eca9e41039d5a58619d1aec042651a3abea8f6db5384e5319101abb6683e95acfa01313c27b5b30eb7387a1f4f54381174165293cff503f2a870b59237e6187d0179590043f15dfd6864f0cc5e355c7a542d3fc78b18f6bbc50c3fef7c2f17c0e659b1e4460edc2d2c85528247c0cf1c5f48ea17673a38e5260781227787fab6ba65bc321643d2552e43982ca5e525fca2e1f8300e562cfd62288204c1a74c5d245325169000b50532484ff77ca0f03cd60bce1df9e67ee91955307acaeb7544be64e330904f0dbd8324b70bdde4a749b1e30f7548ec5b4169ec9bfd1463175d03d0cd8faf5046a6419d849ccddcd87c59bd8faba11e3fc36c999b2af0154f69c1f6c27590028227f7a0e5af696bcdb50dacbb34d5b57a2992f4c50ded2a5ee3e7e0a271776d5343eb83be62e28c83a5d7e0032dbeb8ff89af62b7324610fa6f7b28dc783f51a3832b36180e4f625ff92389229d21edf65b500c7021a86a0bdb8e9094f4898800674ab34f983ab5ca38b706d9b43bf66ef5d3ed3fad3550b74c4720bc190795bb6afd8903c65eef0e5124f027d530fa6a12359fb1a5dd4f19155419863f200f34e4066b7b42a292d947b91b7496c3f53b32d8b72f912d35fe146a51389f1a3a7e79d46d97328265a38c90fbaa8f397640c43f5547e2db8c4deb9c4a7d17b9f85998b5380af717bb214813f98b3ac36107b921f5b6796f820117afa28bf8555a42735982f026fb927f99a49be59173bfa5fc0cd2ccfb2c4e0397bea7996da3533b2a0b89b9d03a58586dff104020013774a3fd189651ad09aa594836f0d49229dd41ceddb511eeb729e5e3a63e7ffae9008e5d4862f865e1469e3350d67ea58bf26568698c89b246ebbceef4bcec5c62ac1d587f39b8b55070b876bcf4256e4f4b23d15882d6471a3ff6a8c3845fcf8b0fd60074c89075c5424c391ccde84c5a40643b2476943dc216955a8267f79c5e04b74a05b9ee210edc8b47480a79d0e6ff3b704d8543a026a661751a55876e1efff6794072290cc82d91e41b71ec8acc976941d5d1a92fbe1e92be4b97a80540e0775ae554f9e8c2a105a7f14069b6", + "authTag":"176ea0fd6855ff24340a9f8aec1ad53b" + } + }, + { + "GCM":{ + "key":"232a6237578cd787d30d64d45df02c57c29e33425cfbc40eaa0a45890ccc3a00", + "iv":"d0614b4848b7790fad695abc", + "plain":"cfc32ac02c00a0e459a3257bcdbd4165e4950f706c0b688bddfe866e932de128a3948f648ad930393c4fe052909f143ab7c8afdfa11b", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"077a141844c14bdae0c4a222c61d27d618d22d36cb45c212b2d048d2b6549d0ec7c1427a84302e589bf5ab41f06b85e857bf23a9cdeb", + "authTag":"07e1404ab67ac9043523ffe89f668144" + } + }, + { + "GCM":{ + "key":"0f47f42c821ab75aa7a72d8d35fb524520bf5aa46349cec28ad521ec546daf82", + "iv":"d0614b4848b7790fad695abc", + "plain":"b9a58986c3dff0c45a8c4390474038a8", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"83ee1f52b61df4340cdc5a9cd5a299f6", + "authTag":"22c9e6c1b7fa097415a6c13825295d2e" + } + }, + { + "GCM":{ + "key":"cc0d866da74f1a4f16f73b21c9fe8f5012151fa3b599ce851f14d0541b989d20", + "iv":"d0614b4848b7790fad695abc", + "plain":"253365db76bfe60d4aeb12f4a0eb6dab79cd16548124ae2ce3b541f1121269a3", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"01713b8393686afe53b6254a9bb728be674032c588fcb3dfe858a9818cef5532", + "authTag":"2763a38eebdeff624653ff504b9b8867" + } + }, + { + "GCM":{ + "key":"b3a0a064b06ba6a62cd5524110f124e21253e5d6ec9490f192737edf2b777f1c", + "iv":"d0614b4848b7790fad695abc", + "plain":"2a346bd162d48bd91e43c91581559bf6459a8728c4fa25183be71d8ee3789257b0360955a41d811b2182de9d0fc2395aec000f5bb854e51b23f6db8bc5a4df404bdb91f42d887790116279d2f24c193ea78b5757cbcc22dfa1fc3807ba1416c93ee7aa8c7e9482daef96bcf97d2c3ade568ce1e480f41ee410ce70c3d03a6793be75d747d14615a0c05c291f49614c0353f5b9a02ea8d78ccfc86e0dd4dcdae2f23de92351851da79557a32eb36b7d7839484cb442a4190119ecd8e7f3792e698aea8bb3aee7a88dba478e4e9d91eb5c00be9fa39c862c96cff83b1d9be93e4b103d336b5b07fc8bfc010b0e8ec0537d801e9460f046bdc7935f4ba673c18fde", + "aad":"9de737f27f90d284b01f6bd1", + "encrypted":"b66789daf0870b4670a459c98953ccd7fb79306c3f93dba126761233b079f79125fcd9f003f578652f62efefb260016a8942f04984502a7ba88af1f90f6b2919b6a114d3a28f15521436556b6dab0d5bff7dcdf79800262000ef13598bf1e252adf085bdc2b80a74909bfe439a31b84e6ee7bd8449507d29adbfbade524fe4a8906f85a37e3ef3c11ba18f0788700a02ebc01481593395b667e86b64e7b76b25d899b7b69973f962a972827c19918178ef8f1375a7afb3aa187178d8fe4dbfc9a4097fa9fe678f13213847b927f4e8e949fece29a68890b947dd72cf2ab41f2b72518d1db5059c4becfd1555b4632dd0c1c04f870306ccb5326c23e42ea193bd", + "authTag":"e4d7c1ec456a9a9839d056bac1b39a4b" + } + }, + { + "GCM":{ + "key":"4801fea42083fb16333617fe30ec5c0d5184370cfbae0875eeaf40676ddce4ff", + "iv":"d0614b4848b7790fad695abc", + "plain":"fe582407c6a96ec53281934f", + "aad":"7a22618160caa825cc42aa9cdbc0676fff5729", + "encrypted":"dd3f3c7ce2c27ba592a28560", + "authTag":"2d3a6136135940153d329a1a7250d9bb" + } + }, + { + "GCM":{ + "key":"b6506102756fec3c33754c75a2c76e964c46b907599e4ef2bde9d692de543e89", + "iv":"d0614b4848b7790fad695abc", + "plain":"742bf2e82d44452bdc5f5faa", + "aad":"7a22618160caa825cc42aa9cdbc0676fff5729", + "encrypted":"bdfaec4bee9ac4637a2cdcf4", + "authTag":"03eab7ca9b8e8e0f0a2323082832ef30" + } + }, + { + "CHACHA":{ + "key":"59ec3f28d554f14909a6757bf2ab67fee5a69ce662696db84da552d408dea24d", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"7019141998b5c015b14c0cc0929b1b92f8fb4a", + "aad":"c6fa93e82aaac7d1d50fb0529340d78efe4374", + "encrypted":"f9c953c05070a985b369f79824a2c4c8911aef", + "authTag":"3d48999867e8c262b2a0a2cde004fed6" + } + }, + { + "CHACHA":{ + "key":"db412a70e5ae0b45730bd06961c8e65ebfae54b75c276c47db62a8b8e20ed741", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"28", + "aad":"c6fa93e82aaac7d1d50fb0529340d78efe4374", + "encrypted":"16", + "authTag":"4a16a2eb8869e3a8158ad853a86f620e" + } + }, + { + "CHACHA":{ + "key":"5e74a548cbedb24b91ecbe825a0eb2e406d4b2bdd062c08e8cb7c072ecbe1691", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"", + "aad":"c6fa93e82aaac7d1d50fb0529340d78efe4374", + "encrypted":"", + "authTag":"2102bb63e590edcd532e60b3e58e822b" + } + }, + { + "CHACHA":{ + "key":"1586bf8f9edd6cc6ec78619a9d94c08f4f1e31d4042636a259304c5878b426bc", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"986363df3e4714953fee615afc73dcd787b26c6aa4ce07874f324da83eaf4058105740cdbdce2283463e86b51e1ecacc5b111169fc5dbcd697e88e5a6ccacde4332e01108ddd90fac5acc94864c2aeec58e4cf302c7e1094fb2a2192123e8f9a3fdb8d1a4f1b9f37945a19d6da212e62b8067a9e8206649cebf721ccec1b58fdadc7c5231f5210d938225f7d9da367431f71d64bf6ff572b2f5540bc1915ecf06c3160ae09b800a24df97762fe9e186527fda83a268c83bec00e9b7fdff7328b14ce2bfc7ad77e99f0070a294d6aa3847b590f977525a09cfd0956a2800bfe068c88fd0eb4e0eef37d12acf556ee94e7c28eefe86ef0086d129b5e1729607e7f78d193c7884a0636e00da15cfe102b532fc37c434eeb7a0df8c3ee75fec89f3be04d720ed1bcff7980de3d473eba03a19a9c6600040a154b7870efd115c6ce745a3a277b6d9c1abedb18efcde04cd41e923ab988627786ae57e9590445b89eb9d129033143f80d6b4f6cbd4a456bf142db4c6033118ff7c0a89fbcbe6001524eefc814f423051cf9bcab4abf3044a59c8f2cb082bc0ef09bce8a7889a6865c8628df1c6725f32605cee1d3f18ec9d0deebcaee0049d6960432868692834f4d2cf110010bba6b7e2bd5b157b0ee9f9a697c669f713aecc2c2c3d6e550466210d9aab2f308fee616c278660a6f0b621a318fa9e8560558d946354c701133ce8e3435e4bfdb7a742873b8b1da86700a5791bd8e222ec4a1b1fe77d73ecc87a5825b6f4b883df22a732923ccecc64ab56137a85987809663220688317a49325d329a1b6c8faae2c4194e45d41e069f6e295c20f759c4fc43de83bd0278ebc63f1bcea6b59aa3ed8674bede092f0db766f9d9278d3fab6f0c7e38ed2ce365cfcadb48172f12849bf29d40b3cd107341ca8de9527bebf1e25cf57f7e8384604b91194d4745adc444ff0154f2c7e4db4968f90e5ac99828f4a9ee6b0f648ee1b7bc40664f52a9ba54e7e39e0bb443ca1c485d94faf4d5bf7f7e8949e716c9c4b46813ea39a1c3f7cddfe28412afcb7cd2d32c6e12145a4969e59d566a5e4cdfd63abd9d1d751e1fb566cec0660e6a8e315106ffcdaa8e51ecff7326ba1a0a8a997016ebfc1d964ea8aefa933638a2d7dc9139e4d02eb448334c1eef8980f414e465587b227a65e540f48a2f37cb0d0f810d057ffbfc56a2c49fbe79db0162d5b52dc417a232c74ae9dc3d68b8c856f153cff1fa9f010796e0c9ce6684281ca3576f5fdaee1e4c87bc11142650f40a4b5df44efde6916733ad95c1bd2d65638af0abe29cb63e5588595b10b5c8724372353edfd743225f1621eb1f43b44e8bb34c062a675ab00f7559ad2ca1af0761db6be191eca72378a3da53b6af55d17c2d47d1d28eefbb5e36d0e9a9d6049fa9c158c3ccb7b907bed0d8c60c2e556b9cf9d86cd3f1330ff2c62c379aa132cd2195d6019793382ef921d316245b3236032a37baa5fef20413e1fd17eb04ab07a30b33edfab27c6bc396639c5a86b8dc663bedfc6190455193011cf4e690cc38e33a52ff46d066273d3a9e5739f912bc6746e4f3e5d9379a1661c450f2f8b235409c2e48964909adadebd11a3860d0fee8633f15cfb651d5aeb649a43c8c5c5295b69218b65ab2f5cf74cdf9e9316b18a27bddbdda3f5749da8c1d969ae7bce978c5cccd49bb4c3dc7346bde08075891ac2d4703eeb89ff523ff230448b66915b784a9dd8f16edcd", + "aad":"c6fa93e82aaac7d1d50fb0529340d78efe4374", + "encrypted":"ccbc48a106dc67f8b2eaa91a6b42a63f43c13ac3ada2f94b87c0c3a055f976be51bcaa95c9695d306e494671e4051e0f559f0eb374af3170dcf479b11c2f2796ddb7f7493926efcaddc2a6747e7419ae4e7289c00fd8b7e10c4cb75cb984560b5d42ce3e98930116b086c6907988eaa1c2173edda2bd2b78663422dba4d04a7b59c15ff322966df2dcb1e45aa5664b488665873b42790daed15abc400fa5626e56341cc07c20b68f6f0600f24755d78c0b103dbac82aa2897166011a4513b402e7ac5c52500f631866752a6b76db575fc93d0fd6bfdb356526d79dec51434e5717817d7e33312fa55498db437248d77ffdd1f9daf2ff0146f7c1cfa9d59719627ee6b7405158a519ff017a30ee87b0f01957241f3b73f89d52f74ab596798b7f13803a199bbc795a4ab6eedd4aadd12e1e9a45aa33abdf08eb19903722efdc29471ddf4acb63250ef41077370b2b26e93c7f497bc1dfc9c327583a907a93758d57d6e59c832cce28acc752fd82978297e8aa4c9a091b5a171856884e4deb1aeac72e696d47d6af268f038de108ade2195e62c6ce98ff46183f126c7da51f9f385fef76a8891560c767a1bb5b4e50a84386c000dab27604da0f42d91c7f01ecaea2cf3a40b544bf1123b8f3ed9c81b44bd6b42d3ef3f010b136f8d43543ccbe9e0c5f9449970b8e880879ffaa5f47c8b6655e62e496d9c0ac2d4f6aaa6cf05fab013a3669971c8771569349346ad8b280f63a1c62028997f7cfd8342e10b28c0581ff01cfe73fdd7a53cc4c6d10abffa517e35fe26339b956974658e4bbb542fe49af9c25dae29b1563666dab6989fa7bf22250330e811dfb46dbabfa0fa5c84ebc8e48e201209ebb6270bc929474e9ca120617a2a41ffc44ea6ff75a4bc81336ec359c507863467d3bbf74abeeb86b0a71da4a83085d451f08c6f5c2c576898dac1a0f027776c540aea0821d4ca5f6e531a3f12242e9e050f5fa5d5162a55e88f97ade862ae419c6f7b8fe24e8d76d4f903e468e3ef67fea51eeff3fd328609e46d1ed03a4730776f5d91c443932e62204d6fd4342a30424cef0a9d0de75878dbe4cda2e55eba2989cdc4ce03e30b65d15bbbec44717a2eb93146dc1873d0bd4fe4b35fafa7463c4db6449330c8512912f202baef17172b4e558c3882fc20644cb9a833082f0bda4551dfb077b3a3d2098ed6b4743665827f2ab0190e0aa5420b42be919ba7e94e6958b36bbf9028e597ee916f4e04fa6a3a8c4a35c7d8bef0ea47e945825fef19f675abef7d6493531a9b9043af9699e77a10a220555ccb10cd98699fee5bd3dcd87728a4f83299ce710712ebf55ca81f809fc523c81b787efe6250cfe37dd0407b93e4bc5a10dc6faa300ed95cd6a1d4dbfd5a165d9721236adf1f2293b040cc031428c63cb531c4bcbb1dbaae9e4cb2addd1d0a904cf9f213f5f4e7051b33bac318bd0ad3d290d42e36f8b8725e60de49989852161f7863e7ba48233c38ed32d1984e679a3f8be44fd87a41806e30c55cfacbdad34242d25ba036d85aab529a265af1dc7213bc77455fd6ebaae56fbb8a1f9e446c2dd53903d5360f0a02132190402b2b680682c3b48120c126d4eea8f113938713778fb4acc3cd498dd815f6b8cd5f661ad5feaf7ffe13bc5cc9f9858942cbb3efd3ecabbb56ce65d2005bfd66c43ce77557024275da710b0004d2e50cf56c25c91a07aecab74a1af7a575ef80521bdcafc5976df5f51", + "authTag":"d5f1aaef20e1220fa0099276f838e0f8" + } + }, + { + "CHACHA":{ + "key":"f48b0fe1bfbc876423044fd9ffe2eb5b91ecfa443f7ee13f1a5c80554d1d67f0", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"953aebd4b1f36aef6eb0c0a699e9fed0d3972c6b71df7482e8aa522b5a62af2f022ef023759859f875a00275f3178371937d7979e9c3", + "aad":"c6fa93e82aaac7d1d50fb0529340d78efe4374", + "encrypted":"13877fc339e8276c9df00f3076055e47d0112ea694e2eed284818b7ddfb6968cd2d3e80884c12fc8219b71172a2815d9455d17c34bf7", + "authTag":"0b4ac7c2a5475ae772cc5b85daa48b3b" + } + }, + { + "CHACHA":{ + "key":"06ca3376de423c833a233b092cad0a34877f7b4950e9f96e6ca004ba871a424a", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"337d5c5f4eab828d97a9d21b0fd240d5", + "aad":"c6fa93e82aaac7d1d50fb0529340d78efe4374", + "encrypted":"82c57e8e202b86df58399b56219d18a6", + "authTag":"0caf141c1d78371c8da56fca7a1f4d38" + } + }, + { + "CHACHA":{ + "key":"9d4d8789419efdfafbf65939bd0718da2216a3cee6d6032d79f531118a4b0e87", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"c04715e8295116def13442507230d196a1c02c8f3ab3f6a6ee37ebb22e212ffb", + "aad":"c6fa93e82aaac7d1d50fb0529340d78efe4374", + "encrypted":"6258187d7229bc430a758034a10076892f218f4e802d9825ad9b5be3e658f6c6", + "authTag":"fbb631329ca0fb07d17e19f75a94c61f" + } + }, + { + "CHACHA":{ + "key":"52378a146817702403a35192bc7bd2d2ab4d689e48a4e0ba500f3dd73bbcd47a", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"2d7bff9b3e2777ad38a8da3be62f952768e5b2c71223daf722fcfa7ea4bc1f6f0f1eb466d2f259207797d620ab8ccd72641aaf10d7b5e0a3f2bf5758163a6a2e739fe9950ccdb5d7b6340d2e5febf2c94bfcb1c4458b092960f7e6d58bd92a27a72ba099eefc60dec3539f821bd41a9151cb8df8d96fd5da2910e2a0a95ee4f99e3b99c96a400ec8ed2ec9310c3869357ee44925456a06126aaf15f1a6fef5222812d5caa4089a3667e286586ef2afb11bcc67136e8545732490a1c536ee9cf2a1dec4af11334651038b168505612221cc3db03a42b5c5ebd03bd2e774634f212d4973c267b3fc9d704800cfcc64029cb640e8019a35327ca41b892bfa15b994", + "aad":"c6fa93e82aaac7d1d50fb0529340d78efe4374", + "encrypted":"9d0d907129003f2ef3251b01f8e4d9faa65a543688daf56ababce2a0233a9e280a4e246a3bf025a7d9cce657a63901ec1df3b23ee83333d8748defee673d2d25112fb542bdde010dc961199ab65aa7b47609b155229362911b59b3ca596c22066e4371ae2b3057931c2d7d93dae46a7fe1bb34b50e69ce273a550a804c6ff8a418dcb004c1f642f6b1c20a0f42819739e8c6d48e2f8eee507e0a7f79f06565ec9ffcfeb013d67852dd370510a9dfe227a1d3caede1f482d8427c57d31edb375b1ff1289d56972d23373cf5d553e111e44992f19449ce21d7203ea6720e21102b38e47fe289171192aae9335ddac3edbfa29fea487529964140d9be57200c4bc7", + "authTag":"2ee78745e90d0258303e4e598b9a0642" + } + }, + { + "CHACHA":{ + "key":"9fb5d87ebe769cf14757213a06880d22b6d225eb6a9add39c056982143a3ef50", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"7e6701316c9b5df8f510756a70b6c726f0824f", + "aad":"null", + "encrypted":"c85454a70c3b852b4400b9fc6a5905ad77bd18", + "authTag":"9648b1096a183c2cfa5846b0b1784e20" + } + }, + { + "CHACHA":{ + "key":"21e360c6cc3cd686f2ae93bfda7978b8047855cd45cd16b662d52153086be703", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"68", + "aad":"null", + "encrypted":"38", + "authTag":"f66d14ecf08fd85c9118a64cf0514aa4" + } + }, + { + "CHACHA":{ + "key":"37dbaabdfdbafcc355265eb13e14e2cc4157cd6b7d6bcef21c663b22e0b4ba35", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"", + "aad":"null", + "encrypted":"", + "authTag":"42228c13be13ba52d3e751156867f2c8" + } + }, + { + "CHACHA":{ + "key":"cf312ee658c588645058d4c6e359b0c55462275f0c2a5556d20813ec0cf2f88f", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"69b52f06129a99ca57578278bb5af05cc15bd8a07e147bcb9b898d902337f91c3fa02b16507861be8b38e97874bee4250a3ec8bc8f1df3676029a0c9ae5546d3d51ca3dd6b08e6f9ae604a69c9e8e8a3ce6007132968993a3963bcbc0b4f1b81507fce5f73d24c54dd0e4d5dce17ed674722a1b61124bc5ae33840fcd0093a3eca08706beaae5c25ecc742b1d6cbb7959a735716943892e31846bf9aea813e4cbe71483263d210fd2e16b3aa0d0b71d1216ee6f504164fbff0b33c4a7bdde4296011a7e66023bead0897a55729a45ab18f3db6da55b2eaf16c8490dfb3d1767b022fe462475633ca4cc8f8c89c0f121919ad0af6e2bf8ed29fa14587c214e983a43cd9b60de1d52bbca1c6930e0912c773d42a9ce8b95f158f9f8fc551bd461495505849d140def6acaf6c4398855ba73082796f39819459e8c561f66de27d4bc3a24f90373d7a555d9e7ba5063e82f18a753842295b0f43ba837d804d39367006cd17092a93f40d9acd31883c3625bba423e69ab5740d3bcf2c295b6f0d163069aa438e6463bb93ad3af782cafd0a674f543557189f82597abc20d140df0929982b5bb3f45573d8143864c53d11f8ce4d64f0fb39f604ed0c9a7b3769d5c94c56f739f0c3ba60164b14fa5f99492f719959c13952b68bc745044dbd1247b37ef52f5b91f090f88e6e57a5ef14105cd87f1ffb31543a72edce5add651ff001b5040b6cdaea26c549be6cd265db7bad4c6084054592f39c1dbfeedf75cbc5ea52199b15654c40e6e253f80ee757f0f6223ff3065e92789b22f2bcd32ce8f4dea0f9aa10986adad209d39997d3035eb664a98305ad3fc71bad06682da8d610e2664e3a5f41096a6247f46ed37f2102efeffcdff3626690550946d5f008bed017f35232bb2bd9ec52bcf0b24f63ce0cb3c1d68017586c8082fe914966e5fcc3728e1dab9f3226a3796a80a7681c65ac773eba29ed4a13f7111d1bfffd03d90494bfa0f41c7b63ed969b87b0a27c6bc97c79a90679d6e3921be321ba7d96a45b4fb9b658198c464a5c6fef455d3609d5607daba0f359009854554a9909c463cf0b7d35a0766d65c38f4e5d8ddc73bccb3b09e6e3f81962faa88181117c0f883a6c01eb5c2bdb5a49e39c07723023cc5c99c406b10b82bd5d4dd8ab1a9e8dea9f95bcab2076078cd1217add3bb74ff2ccb18926169232645bd634138863fede8f174e73de32902f53466084911dfab226757e2ad7d359f614e06c64817d2a7e477f4b7dadbab94268d51852c7a4871c582e9f4209dbd6e316ac45e42b7f097ad99219add95801cd5331efd7c7e0bd0a5d81f554c1393351f6bc02d4b049b31b31ffeeb67a1737e5147b6197d3f218b59f3e1dab85d92114984527a280903b80b0f9990ff860caa9479ed937b83870c2cdc538dda05e27f1398fc403b4c971a1fec99c82bc8a8b62aed0577fca21554cbb89172be574bd167f4b803e47fab4ecd01f2611d1a0b95f40b24b98ccbe2355de741329f256e2e95bd10876457932d523aeeed977db68dc774e9c0b0d5eb2b86f0c8db4e818bbeed5c8e80a84ae6009a3c160b5ec1104058ae88fde97da91bb75b78e907e48c9572bde870fa36c9fa9206bad37f62f1a56929c22fd9b92d1f972cfef76c69d2324d7e9978a2da9d0bd9fefe5e898b9f93df01968bb44499ed4679a99380aa178193dd5dc50ad4a3fbb22c588e369b415f190373439a81448067720913a96", + "aad":"null", + "encrypted":"c292049b7ffcbeb0dabe2c6ec411c9cd4f99629f6f6e04339f19ea113a439466bd5cb17acb1d4df84b3c48a715868bb29710c588b648f35280df5ffb36936e03fc7c60fe8febe097258db0f3b8fd21ff04ed75e41a9087e07630c670674b1a44e08683f111185a693dc817af5687ad19cc15fbe0855aca59221407117b06403fd27398b9a8bcad7bece89e8168e0ccf494bd39f6f0f15b137ebdb11e7dff39eadc9a9605b55d9c40bd77899156024dd11eca44c6d254245b0fc2f6d55d115b726a00389e1e3e20e492b4be88e2cf81bc531c4c8b4d74c3bf2eb22c00ff46a01cf12e28d6920c91d74ada45b025bb9ae1321ce0939a2c88bb67177f062e1ebc1d68429ecbdffd2a524eaaaf8f9f9795402378e345e2ae65c40eba7885bc41bbeba4cbe3122c2c4c39bfa0a7d8f4e2b90fb91d06f9c7090edb1f33327c528ff3a3cbd15e154755085fa78d49dad0cf687230fb97e6c7b95de41c452529fd9f78b4c49e07ca744b3659bb4df05226f58f1766ee4be640584ff047182314308c8ac2bd47c655a3e77d9ab02aa45ac25a5f56669f464ec7af013d130e207e2a70c8cc47c0eb959b294b017922cf00d8c759a11d9ee97f0965158902b6f9566bfd61c9f1d8f32857c0c8bf1679916f1e7272d8130e2935b983403048a3e2915863e0d6b153ac36563648fcadfcc3070a6426255fce53fe385047192bef8923dff9d92aed30deb96e6078053492d07f3a09e004a9b3d6a1f432478330fcdf0ad704ace184ef57389037de3351742640c5264293e144f9be6dd940ca3384ebf7f2efa8b128a7018d9c6786a83dae9b12daa1181b3eec69776ae4bf6b34fc69161503ca1248445c7a6bdf91c4ddcf2a89211f16c57fd32c5492b077637411706adcc85455b5aa59460d3da7bfb6a64be21673762c26124153cb5fb2db4e9e5a92364cdee97496e23f1c8afb934ccbf7367ddb9343fd98b909e17d8056ca1a186946a36d1ecec3352029b6230017469fda895cf010d355979488da0e27c72f12864ed96c3f88dbaa89b6c2a787b2bb9c29b1e6bb8ea1000a6ed1631768c55cda66fbc607179795dbcc01ec584d14557672e10085770ab52d5f31baf5cfd23263c1f84b2f5fd165f8e8ad6f955be2237f84d247d4b49211ae9249a8aea7ecc9e70da751226fb8116589c7469e4027da59d57587245e778bdbcd49769e9e1d8aec3d40ce6e87ffbb068af64cbd6811f1ddafc3d7aaf11b98207896fc3a5f4fc2f5452f181dab47801c5a77cbe7e1dff445b7dec8b216a0f87b943ca8a485a680aff04fceea47cd738b06958fe2629761edc4233eaa15e6f38bda63d400d62e3df4f70252ef36394b6849dcfab8dd595d8bab18ab8885c229363d8bca9f90b772b79278befcb8db44e93a6dd49b4499b4f4276921ed9f143a2326f0d426be01954e7c5da27f26b4093d947891939776a01d25e153ba8bb14f9ecf06ab9c7431b17d016834ee433c3183dfbd845de208ce132722953053743335a34da600efe4390e59b96e29e841f93b59eee2da8da29911e314d4f4f1d5797c0c3becca93e01eba335d5b311d760eaac21dab4115942657ee9e75547df4f5c5aef908ecac8f1905b6eb41a4e9ecadcdf111c05b6f2cc481fef33795082fd6f4f489b3f7b3f266cea99c681b74b4fea3822d8032ed2e1450f893219e8212b0c0ce41207209b171479821bff43007cac28f07f0419b0b0821c3d2ba2169b176", + "authTag":"1330f15f83e790dfd68aa55b0520b409" + } + }, + { + "CHACHA":{ + "key":"81253255cedd7ea75b24028a579a54ad11956f407af53a1fc7f6bc7da02a4d92", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"dd72b076f8bfab28915f958722b7e97816894a11244d692cd85df48f6ee81eed6e7db267e604b9d3ca18f7257bab157c8dab17effb33", + "aad":"null", + "encrypted":"20d7077b321f237da4d3c0da943b00d670ebd18eac7efac505e8db2f98974d853062791edd90eb6428ade92c4079565668e01cd48be0", + "authTag":"eb7a29cf61166ca5abcddf119fd59181" + } + }, + { + "CHACHA":{ + "key":"4befdc03c6b0a11c17c194c8798606b009ccccca9e7317283d82ff78c14605bf", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"00bc7f77b4dc6a934d70051dfa03634d", + "aad":"null", + "encrypted":"499adb7b153073b87135238ef78d965e", + "authTag":"da151683ade8a31114a1622b04871df8" + } + }, + { + "CHACHA":{ + "key":"025289218dc4bf2386187fa8651dba0ece8221543cca8ac97515eb443626e5a6", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"6c509611f7c380c8b58d3b806c614d2e601b30e85564f9de43941626f6789523", + "aad":"null", + "encrypted":"4aabbd6c3c1420f49408d18883c2ef70488ffe2c24893482c7de48146ad9feab", + "authTag":"20f290760c088354fabad9f3b076ea29" + } + }, + { + "CHACHA":{ + "key":"9b378d9bb24f1c327a57f6da1a19e1edd314b07631d949caa373c5aa3f23ccd5", + "iv":"4bf62e0416a719a1cc7b8378", + "plain":"d3fc081795bb60001857466e19ff77eed280950b88f5a4f3840e15fff912d9b3f196e2fc084d9d305937f710551a3b1e20c4dbf144d81534d8ef0a41f1147ee493cfd2d70c83875b327c650d4b1bb608551c6f9b70cb5edc0e4b4be8d1d9b4840cb0892c3ed1c5eecf7589d7343545a1a403e060c0bcc5341930f472af8684edf36f762633e0200c459fde52d555e68b2edbcf7cdc26f4b61a858fdcbded03f2080cf1ebbd7466c05585863fd71ca16e682f0369f4b559da470b53114087efee56826afce37c4ae0d034be12fa3ab8de117c05b6a8af9d7141cd998d2553f5aa5a9726b34bf446b2e3cbfd442f07e309922ef4a3e11d87e0be1b59fb0952ab06", + "aad":"null", + "encrypted":"6078a36d3e777cb09580e6afe51773e7f00c99020a437d58dad1c35abd7da328b62786a7b4d6ecf88414e12858900ee8e5d924eb1c1b0455e6d72291f8e6974c0323aab6bfc57405ad31aec2e43242a1f89aac04ac8ab9876bc64c81a1389061d1e8fc6cc196003e56b670592d21dbfe0512d47ae37c6f7ea1218a00786dfae621c5cc6799f53145964a5d95be3578d6f4b4797ced9c1cbd6b49f46f392b7e8fb75144b685894335df648443362b14e2d137d82d56dd940b2296e9103707b3f92461867c7b72e4fb55a05c4d939a51dcb50ec7c0c9d12141b3f681c150ec5111c972623878b10d9c42903941e996469169a59c6e1890258810b5f6cb7e7370dd", + "authTag":"ca227f7f41ac4ccd280ae2f2323319df" + } + }, + { + "CHACHA":{ + "key":"3274ef8143494c686ec0c3f2331f92714c291ff89619522378ebe1ebf67d3a09", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"0c5f324918a9cecfdb6d34cac5db5bda9313aa", + "aad":"3bc454e088b708b5c146963753e0e288580249", + "encrypted":"34845c31c01f81169c49779f34f809e033c982", + "authTag":"af8266ad5205368843570d292f213409" + } + }, + { + "CHACHA":{ + "key":"2dc37cc58ec53f2375e8c427c5ca757175afc695c905ae86100cf7a71f77b36e", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"45", + "aad":"3bc454e088b708b5c146963753e0e288580249", + "encrypted":"10", + "authTag":"3ae99ad025698a25451327bf5c1c09b6" + } + }, + { + "CHACHA":{ + "key":"6dd300fbf68fa62c8170bb4221e0d97f5876837ce86d0aee4c1b270d619ba904", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"", + "aad":"3bc454e088b708b5c146963753e0e288580249", + "encrypted":"", + "authTag":"fe2be3e7d11972c3179a8fa5d3ff752e" + } + }, + { + "CHACHA":{ + "key":"9dd6ad95f96653b81c22da52f6f3ecb9ef8e37973b445b9bcf515d7c3781233c", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"626bbfb649a8ce860c260d343a294acc000ccff36c2d3a4f02f61ccff6b2b664281b04af3a06d2296e5d4e0c4fa43ba88a73e2940c52164077bf836080771efefcb615ece8ff4c0d6a95ec63884c5747652cdb7520f6cfdbfc4ce58e22068cd6db39a9ee3b1f7ec682df1a44c0fe03a9172a134657753efe34712e9fbbc64695840689ac7b8e867388f9bd61aa4ce890117e26649f1b0e4ec96c2a714b56322537451e10801b1c220bdabaddb60dce90c5d726bfb1af5da654210ddddaca12ce7bc24f97364054d7925e135c637d6f380d9395a1eddf3b2de74d978f7833eda477f32518c475a2544adfa58b0f2bef3e4e80f11483e8434ccd70aecd09a7dfc2fc0fa1725632ad10b35b508e7d50d1fa434779cd5718b7ce8dfbf05d4664947f943ec18f3ae9c9c237115ff84854e343757ad95527d8981e3ff3e1f45ea02139903af039adf50fe11dba62d1764f276716018b3f5369f3a518f37934439a05bd2e1eede6534801b9c37054a43a2f1288d7275bef6e8fcc09b25620c9429221f488f2b76ab61b79d8e011c2385115da64710f7ab5c152015380b24463fad0fb2d330d79e82b4d5805ed71d63bd308c40accdd0fad0b1b5cda0ebb6fd50a938ff0027851ada4f6f3086cae4de8607da7f08afb543623fefe2303da342371a8f42d5a6855afb9a4bd3bb441b64365f38ee4cfdd0e59ba35e2487ba47fad447a1cfff46facbdce2e90d6f7a60d5d54531241e94832058f99530e6a984cb41a264784354b00754409d7565723102cbeaa8265991dd5fe1a76adbfa757688afef70f5c224cf3bd573bc9c365be31d83481f4a9bdbc511c95ee9e1aa54cc8193080f6627ff44088e57016e21f6812161d39e46bf03a0dd922e92b384245cc07b46bbd1e9da4269e2f8ab793bcc1479438d8251e3a8b0bde6bd420c5a400f32f8272967a47d3909d77e4d887659f0188c48cfc26f45443cca63f50871c82652205fc2f878388980cd80472ec0447d9f570a506cdd712de607533354c420aaeea109505fa34ff20d7f70c4c2229b803294c7779a970ae3b52ba8f622ad9564cae7057a18039ba8c227831bd5213037e537dc260e448431a6ac1fd8a6128e3eb4b6ec5e1b1afb7cc73d14dd4a1e53ccbbd04bd32330852d672fa24d2d0124ee4f08f46cc3c82f3037668073a747be3335e1a39e02e9a8c462e1325fb4dc95aab3301677b5f276d24be6c0666c852415d3de0497d34576cfe78492d4f7f8139326d7a941bd6925ca2d5d38db352b2c36f6e3f6d263d38c84e4bd7160520ee2cffc98c0f8ebf14eb0330a89adf83ffbbcefe3313ba3ba3e17738eb675ce901132bcfb149234d05b0f7fb884c4272ea97ee450427116ac840ccb7ea30687a5166c4e1ad8d8e91f3bd5d946a623d36be2b8182f6000dcb28d59e20dd37fc01e8e57c79c9607c8f6666c055b9a09903518a307767cc2807a3f70a4bb7067a5e23c632625c54dbe62368e7973782d2ec0a8ccd17b9d5b7e0a55a699c8c1be908319944d8e334541af6ba95d879eba2e9db6c364ef6de5ed39397341e05be23614c9cb8c8d7e6162d12eddd5d976472afc7112a859a6e5e997557d055dfcc8ac659046f4b38b529650e12c606bec508d24f45c1810320438d099db8e3e0b29420d6838e5c15a6bfebd46e538afed85ff2a12eead79a7da8b938b27fdac72892adcd175e72d49d3c01b557865208b0baa7ee29e3a4033591db5c2f", + "aad":"3bc454e088b708b5c146963753e0e288580249", + "encrypted":"7995bdb76cc42aeff10e29cefbba83fc6313077b82045c61b7844f15d892c472ff7582bdb5cd79a15dcb4ab2c261edbcda5289d3b394fc3e50b8f749135193b432e5f252551b7c0defc1e36eb52caf10b5fb7ad1e28dea6a3fae7ed33929793dd86644aea263e7dac9bbaf21977217acb7b91230b55a5488096e69e737f05a55ab55e30ab299de893b7b0dca6be5587b1760c50dfbf51a21184027bae6b6f5abc01e97d6b98a9b93b7d9f7fc00e94d98b8d2495876fe8c634f3518d7a26aebb48b890b1c40b96ea4dc3d90b26adec8ed693610337eceeeeece1daa8e17c93fb6ee66921b5a0b3e3ca53aef5a53a18ab706b143734486b556b9dad90a26837ffbcdc3a752e215b3ca82830af69923069f22c4d30cfca7dd81ac296015cf1417cd3f288dbe0df5608771d710802f6ff43b055c3947ac3ab84ff8cbb9e384558ca70970b325f22ab414e55f3dcadef9fd54ba90df0958d67b555cc0456c4a026f3c145edd683cb0a3ed27e9b854a0551cee7261da17f331bb39e07726b276a475770ccda86eba60d1a86bfea2e7d17d9fe3995419267f45023302e443b3c5e929413c9ebedac4eeaf645d5bb5a9357b820fac9fd42d2b92be5349bab9bdb7da41611e0830376902d6b6226d88dc266bef9e165c65644c7be283fe50266337ee3992442d400d844f3a098d66d99710730fd4f53442232e3a6b6c61ef98c95db3f6e84f45fddd3c103ad68451b2d1cb8c2e1920a5c6681ebd054306d555607537c532c7ad6ff98c00fe15823fa1322c1f5faa3e5420c80b75bdd70d2125d5d78796288f5bc00116797a543d9012175b1366ef7fd1332606ada201c59121983495957aa2e81da2682ee3d8c725714d0b6abdef1d649d348c959d782b150ae15b00864993fc860017ca4b2100003fd14c53f95f32fd2c471085b5a4029046e5a2096f647b228911cd3252e8d3ea92777431da5547b7f3abf118ea876fe36b54187cba3835da99885c421b2e323345d1cc69385748beae043deb665107f8abfa1a636fb811b52d93908a8c49cadac6e0c94d8ffe4a35972558fb39dedfb17bcc3401641adc7419f3ee316675cfeecb7f91817e58cfa203208201f38f371ed0a06f0c5adf6badc83dc47b1cc6d0c0754cc4623af936d526fec87c98353528769364086729d049eaeca1c1a97e553d330014c5fbbd5643cce9e32b5c89fa85479adf8c1f65771ef16f6a5e2c7da562abefd39ad88b4568e3463175cc065ed2ca3390786ebe950d53ad67a377d4c777f18e1a3d4db6cd28e80248b7c8f36d134fe7d174a91362c01792c8536b05d85187626a3c3cc8ec4c769b535a44b0630007241171d8c960237a4912aad2b0d648328295ff2e1e1060d075b481da9222c98be7efa8a3ea7b3ae2286dd704ff71162470acce7d64b498707078183073c757b30fa6d4749ccd35cfad4206dae214e91e7fbf560483622bdb074a32d7de2a554c645ea6680f29d25e84ff0cfc405c0403203aaa2779741c3d24e2ba1b86dd8540fad81bf832436e950d3fa212b8609d5c4beae76c49431f24dc2cffe93faf80c95001480d1e1c8c2261c6d1b5a3e3696ed7b5a3abeecdeb88645cafe2e063ee49da35e1ea7104a84539e2fbd4d308ff92c523bd821a6f4abbdbd8bb36e2c2176158006f98580e2d2d7f511f5f3a208ffa165f84799451130694b57406f249fb15125dd00371caf25b7357a41d7e9abce807c4c72fb68613", + "authTag":"873484eadc09f111c984fd93f29426a8" + } + }, + { + "CHACHA":{ + "key":"abd8f4efc31917a53c1726df418f2a29dc537d24b86d3f39de6ef61043a00096", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"87f4928dd9b4f39bd9080469219b638db626b28a297830e6a9b05c88ca1be3d69e25ed55841555cba92b7ecb2403831c5dc89e99d1c8", + "aad":"3bc454e088b708b5c146963753e0e288580249", + "encrypted":"02048e4ae646b4e9597e073a3387b2f1bdfb208c7bbb82ecc103b6a554c525eead368074ba9f8e8416afdb3f914be7a95c019abeead9", + "authTag":"2c5bfabe23fc7e44353f983575dfd8be" + } + }, + { + "CHACHA":{ + "key":"f9a70c3c7974f3bd96b7167b6d3e635193815e83af63d275c5bd4f3661d0e25c", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"61c454c009c5adea63e8d74c8744f7b0", + "aad":"3bc454e088b708b5c146963753e0e288580249", + "encrypted":"193b82998740449b49e537de6cfe3404", + "authTag":"8b6c25241c19e3aab149e46e1567f45d" + } + }, + { + "CHACHA":{ + "key":"e64f5465e9db13f1d88f6043a9212e5cb5a7a30866ead6858992c5f792770263", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"e765856e278e528bd2d48d9d7cccf87ea5a6de843c165f4a6e76377aeca18683", + "aad":"3bc454e088b708b5c146963753e0e288580249", + "encrypted":"ec9b71bb672a03e4971a50bc5c876bfb28f4135c8c2e7edb1155b89cd35e76ca", + "authTag":"2714f29b2ed28955aeeff3b7be35dd27" + } + }, + { + "CHACHA":{ + "key":"ee4dcc1ca2a8d4711ab02aa9e74f937df6c82ea3f23fcf07d58a63cce50febe5", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"478d9bc5a59c975d2ac379348c853f99b7838cf6751b71dd6c8ca370e3987f93fa0a6609ed319a705418722c7f2e960ca38ec72dc2da1cfb3c7c2c495eb166b5adc3891e0950f68c31c31a238781d3f7e6940bd8e693ff4be8c921ec3e3aa75752e4b2e00123c6adf2ca8c7da1910a644a805d5458689f37295b9ea650fe2d572c5c5f3be649be49d6cfb89f5982a5406f8375aaf3cd4f60c8426a8c9f3630ec5646970d96c1de485820d8f021fe6e9cab7921b847f224c1c09f8064e1d436d5a94fb3ccc15ec5aff2cc113067f4407b8a4ba6b8d34bf06aca18ccceaa6f1eacb254ccac59b74d9f4200a9f32c1dc99afdcb67381f6a423dba9c46d730f1ab90", + "aad":"3bc454e088b708b5c146963753e0e288580249", + "encrypted":"84f2eced983f31341253f9fbb0d564d2cda1d0007f4b683be3109d86c6b127129189d4877caa6d9ea9e78577fd75aba06ddfde028392f388b73428916c31dfc84f09bcedc95f39efd323fe9be058751de5a8b1a2f76bc16c148712a7d504d168c0450b1d9990120a620357f8184acfd77db7371d306c6bc42bdd620ea7097bb73a34c4ab07a383f58ed589d066f2937c1a11de538bd06924f4cb73271520dc58d3adbcf5bad93c9e7bd567005511a2933ff383f23e4714259f7ad5fb8b6362799296e5fe4c0bfd17ba67a6ba9d991b5c2d0a6cb2e8b19125a1a16f29e82da83350345bdac785b418074f4444c13de9505c4967f584f043145f6438f31b6cef0c", + "authTag":"6b70c84e511ad5b589436e7b0246a286" + } + }, + { + "CHACHA":{ + "key":"e2ac1c985d9fe543ded43958b6527127a900d372c3f8d0dc87a72e1378539adf", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"b4a639e94f54816edd9ea69fc00d2c47589719", + "aad":"null", + "encrypted":"a50735e209eceef9fcc53b89c3613842c2795b", + "authTag":"16be9c1f946ca690411cbe92897b9cf1" + } + }, + { + "CHACHA":{ + "key":"7617e78639202ced973bbf335829e3aa175ecd7e4378b61688658b58f776bc5b", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"00", + "aad":"null", + "encrypted":"4d", + "authTag":"421b125d968dfe1a1773f00afc6f75f4" + } + }, + { + "CHACHA":{ + "key":"973ac1e88b2594ab21906e335c685e8a71235b095e4d7e02da99c303d9fb89c0", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"", + "aad":"null", + "encrypted":"", + "authTag":"e4edc3bb0e1dd57103d8cffc9d93ebba" + } + }, + { + "CHACHA":{ + "key":"c0ad8d1010e67c7973302a26a541c5397158f4446b55e3ac6bb9183e754ef1d8", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"eaa740de65e3675231b9db2035c4a9bc128e9672f215bb7847fe83676e6d54245498e8eae434c7745587c8ca4007f4679b22c0f1b6d9b79adbfcb7bdd686fb3bae7630765ef85b04b598549a6ab6585c701fb1ce3f0026f4e90f6af79c03712a34bc04a14e183cac07b9ccfbd8972562a49ddb1ca6ff059400dded1b879c954bd812c9c187c4550d30d49a1cfc76850e6b87976548d2fe9c20c1ba5c2ab1f9fdf85276a2b7f91bdb2be907fb4f9c58cee4542bf65096cf57104428bd5653083bdb832802983319dc4ee814a4da087432a43ba820f8373f5ff410aa693eca425c57d3152872b445877f32493dd094c7f567c137b6a662d779ce681b084d791b8bde095cb005f7728ebc14e907ddf69d4bb18a3459e1b582a2dc8be5ae131d6467d72687fd0e67fb9b91102186dc7034aea8a17e4668a20448d58b6d76cbee01f3b1b6224aa819bf5c3f54c801d5c174a13e74b40565b9402571ec8fdc6a14244d2874510af55d44d83c95a7996d19e3eb9192e6c67347efa3033055c519c82d7524dfdcd542e6a7fd680f306a54b55cb70e97c1897d7251cbcbf081cf3dbcf2c35976a1870d4945c17c0ac1f60c3b6b22fa4b82cf375e881bbe392cba8fe01d3db12c3f86c8b8a1d49cac3a9f8adc5a51357d19d037f17e5f83d5890e93e79c576e79ec578a845d269333e3ac73cee7e01cbf35b21e2031591da0bd1b5eb45e9c709e0e40edeb25e7a53271983ded2e13142478866aa533bba212f60d6faf3088c298aac94f1a71f97f219a42db62900e48e2695c4adc98cea9ab5f058ed3d019ec72437cdd47c5474adb3e638287bed8d02fd879c83f0e89f3613169197d29b35606bc9ded4c509bb3c3e5ac5a37e17bba650d9bdbf7c89cba704d02bb0b31319751650a3c2bccfdbb4febc000225c74950bcdf9eeaf29598d5414489e030dba65cc4d94229d6d7b5f25a246db31b08d13d23e9609e56432e867fa4a4ab30767e85b89fd94e855cc447d04f2bf31b072f974458bfe6a5364ca0b2058b9f7d91d6bfc55f759c1a459c209b2d8bbbcc402dae3c2011dfd8fa432a1401798509e92e3e569046af50dd9167a0c764f2bf5fcbde3e56969c2761a8d3a9e3dc399f872cf1940a9fb4548cae30449ae3d26552966f19e7fa20db352ad6e1b0d8253236bb7692fe206e21b200bb9d98a6dcfc96e1182ff03c3b23210ae9f96da8d51084de7f3e1e6ea6a792a013f32557fbb4a38b5b3e977011e812175dae27e2dd99fbcf837d46b0ee393b871fcae36d3edc4198d86ec52e5ecb0b29b2b7d7b576255494fe76882603ef0b205d8e28f47b239da903f4e08bf73150b34e60554c044dc69c33593f3714e05319d47bde39c2cebf46e35609264fd3870034b9373cac6adba1e4e8ee02b991774921115a48807230cccc7dd647086f737a8da49888ba4b3e467e185e63a0b3dfa3c5b42d2797b647b9e1c88f3d2f1293c24fc10aad9a4251d0193e9c260afe32262f99153fab56b02599be69da36d79288a0214ed3f66accb4d34ea62f55fe438d9e47ef041a103f4d523ba3c2b45fc593ba92eaebeed6134063f15e23f87e0b1b639b582adbe417d02ee5da989080472dff109a0896ac8c696786eb9feea2ab4a6a6d0a9bf920fc84c857de8db30f44e33ecf404ba7406ea1c4440d0ba29b65ea917478e51b12ca6f581f7739e3cbabe51886294adef98e0d831ddda7d2ee3d1ff381b9469dc8369197a", + "aad":"null", + "encrypted":"209812052723008d607c4569d2cd5563fc7b10f43fc02fcb24983994a18e82c82c2849b90ecac9bca051191a9963c26696d0d1444bd906744dd90facd70101342777482272cec5781898e11081d27d378b6157dd75d80f05e850c2749a4abab8ff61bc016d3467d58a8abd979d5c490286ff800f19aa09f0d227c11de5c1ad4e614ef6b8c71b1ffbe619fc452be004a61f5d5f744954f32d7731a99518793ea13cd9ea0ea918231f5be14ea0f1c6d1429012d23e29437c61440e24676834e121706f4a46958505714eb3297196df57f59e2013c7f57b4d2a91e64876274219f0b7d5ec35ad26e755039fd73ccaba590f9bf78b00c7e87a9ff8b026c00e945e16ed35dbeb762dc91a4b7e4ee04e10b46d1b84151f25d9a001e11720e0c238092f1157cbe81c43d744b2d19eb188e5e3b32dba58105883c465a07c8cba5cefb91c8f5c080912ca47bd55a2fc24a827b2815f27c5baf0d5e61138209d5f6ebe6a2e44102def2c9decbeede701aaca815ec1aba2c2431bc1f7c14636cff49c37e325ba6bac3d16fbaf610af44367c2b0e6dc448230b1f3f73eec34047f3c833df3d3e1ed42553e7fa08c6ef556617608ac11af8fdaef0d16f9da629bd98a34fda049994b6fe96d6e1e59894e310928371eb6bbc9e1c9b8a1f58747dbdf7ae553b86feea9485f118ef6a0e424d18c69a64aa877dcee631d1ae6df7137fba17acf9f01841877ec5504a799e3208e225a1dfb5ee79286ca9298928a2224bf3283aab0878524c820087ed768df80dc20f31b079d47e80762ac5c7e3f67cfe2946be72f5df7ac3de1223dc6f22208ceca01d741fa7c32137d7536ceff8af7621151806508e8a3a956048dcb64f54b59e07f9b5895c635e62e441d2e66e81426c30390dbd673cb1ca12ea024199e508227186519511d9239ca5dcc37cf29ceee6589016be3fcc0824e4160534fa34df46a345172dd64131232bbb659e7bc0002af26aa796f8df9d2f4fbd3e7928875ffe79d8e60cac63dfc4f64644195fea4b83df714cbe0407a6767734a53da45f8c495df58daef43283ceb52889b57e8655b3a9f3a96c93c4f509573f772d2e767edd389287a59b54daf6d6a848c50abdbf92007fd3e57ff16d584f5df38d4806d64151d07250489769b68e89c08a0d0e34c5dd77b9f191d4fbe1fde4be2e8f5cb3e461fac173b850e79f31e15fe6792b370701eaba5a3a8bc3c6a4cf136aab600b786049adc04b3df9104da025799e05f16d0d47ec2cb9a8f666b2451304394b1b914247fddcceaf88d02bcb543f83c1fdc467bf437a71359937ae2457f028eb01d6765bf0fe0a50a3d60bf7a34ee13be7fe709baa053729be415e7e023a69d56b8b9e537930ba128fa3f1d40dff2534609533f234d4a1f64b23bafde857c6160c90356be841d05eb7c0c9a8136bd199143eba7f74649620520ce5eff33743c005049b6978a40a72c088b2bc856e0dccf8849f84a1b430030dff3a25d63f84efcba2ff4ad8f79990d1e8e40352d352bd86c3082690e9df90bc4be4fb58304880aa265c1671767e68bf1b2613fc86762d9f8aef61e003fbd7dcbdc44d2fd211e93919ebc939271622c70a420277e140266edc681665143e5d8101780ed177f41655b7fdf527b2d30ab8c2b691d895b087316428c39da0d78d0faf1bc18ac3e1b479dcfe9c2139ead677f8da555136838924912cab146d063a47f84e4b23f68e085a9e3dffeed91e928", + "authTag":"35618f367588f7a6253df0ad6c17d6fd" + } + }, + { + "CHACHA":{ + "key":"fd567353a490530f8e34748d9fb8dfd7f25ada0bac9af06a0ad890a2a508a331", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"bfa2e781e11b8379243b6be488300ac3d25154d303b9acb9c95d257101a6107048e203db5f23af63254b883a2ab0e2cb59f1fb4369fb", + "aad":"null", + "encrypted":"5e7026780d8af415f9c1cc54482858fbae8dc2d5365716c929694f6108af82acade12a524a88869a6321c83b72219ec7abce1822bfdb", + "authTag":"005626a3dfd76a9eb944a3974e1cb6b8" + } + }, + { + "CHACHA":{ + "key":"3d1d21e69360ac32165f6fdc8fa3ed93de8131915fbeb177b4dc839e3bed6f3c", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"ed50e7628571d7a0953d47c7936c9f0b", + "aad":"null", + "encrypted":"156e233190692a160248299b6ab8eef5", + "authTag":"fe7d88e01e1280e827b9ceb7091db782" + } + }, + { + "CHACHA":{ + "key":"0a3c464ceeda089ed5ae8ad6312abe05a39142eac199a881cc9cfedf4c72bc75", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"3d4ca6cda22ed0cc00b48133ab57fda1b60d9307b070f0b88af9363a2a1875a6", + "aad":"null", + "encrypted":"f130afbc30fe4e50016c887191513ee28c2781f7c5710398201ceb8e077ffa64", + "authTag":"4b231ebf3fd4176ea572195b11d84e7d" + } + }, + { + "CHACHA":{ + "key":"fd825d24f8005616313572fb6d487b00e1be8e43713a3b7cef48f078fc549232", + "iv":"8d026d9f1b1afcd5a3d2ddd4", + "plain":"43fb24f6f516582b31c37cb73c16eaa2dda1f1a3af142c96ee94cd8a130c2b12eb6e75c627f81af8e778308aff79c135c8c3c8a168f83cc22153446449b33577e0e46ac934ed07603a094ee436405a10bcaf605ff695c889b05d70ed1c65cab0a7556d527407af99d9b083741a96ff0dd1dbb6f7763e68869b2649fdc2075b1b9ee05e0a541bf91ed918f8aad3189b8b93aa19f575ebec10f3a08440d9998d9b024cc6c0920eda8d674809ffac13a802e9664e3662f1181ba5ef84da7e7a59197847fbda8e6011fce44135d76635d3440001853adb26dca612d2ec48214d4ac15f02401b1a326e736fd2538a79db23527a4f316fff04dcf7eb7dce9423a6fd9f", + "aad":"null", + "encrypted":"e1495b81b707e7a764844937d0a53852df8724a2823bf39bad70dd995b8f647a8cc3e2a4c5a935407da3322acef9451b8f30c01721406311e29cdfd3ba9b52e0d8553e3a0853aa854087ca3908a371af458b9f179e3cf02f5eaff238d5b86691582e9cd5e35a1786198604c53f29ce5cdea35ea517a91c999eca477973b687081232f955f212c4486eabd055605efcff89c58ab965e875eaf0ebf7d9a04720a8f2f5dd8b6571dab4d41f1a9841a70f2addbbbc27ed120b7a4ea105530de2149d9f28c0f39980d70f1ec45bd677aca1dbcdbce4f45abb997cdfd1e526dcd5ec0288d4cce54d157b45e9a6b010e2d040f9d47a3902cdf6441d9bedc0702516b896", + "authTag":"3a771254ad7b287727e77ded59fbe9da" + } + }, + { + "CHACHA":{ + "key":"f49888e7a8b171584e7880904397b1935d6bdb44134c9864f6b9fe58b012d819", + "iv":"b9291257a94b8a9933d2806a", + "plain":"6d3b1dbe8ab06cdb6d1e3f35518c9d216d5233", + "aad":"6289ce2425a3f4d4755f1b828b132e4ee3ff80", + "encrypted":"b52c650d7589f57513e092ef9af5a94ebea812", + "authTag":"c0f3106f026c10f725292ccef9773297" + } + }, + { + "CHACHA":{ + "key":"9e6fbc117008ccdef07a964ad748a391518bb4c2388ab5a169789530a665b1bf", + "iv":"0859c51b2648c1d8a09f1430", + "plain":"bf", + "aad":"6289ce2425a3f4d4755f1b828b132e4ee3ff80", + "encrypted":"3b", + "authTag":"df7cc4506b716e17ef84de2f7173d0b1" + } + }, + { + "CHACHA":{ + "key":"55e7372d0a4736dbcea4b99dac7112b3d9e7663b1b941990f0e9878d0dd8e4f8", + "iv":"36a42a96bf15ccb7d9387416", + "plain":"", + "aad":"6289ce2425a3f4d4755f1b828b132e4ee3ff80", + "encrypted":"", + "authTag":"03bafc38e001d091ff1e9c7ab5d3f44b" + } + }, + { + "CHACHA":{ + "key":"c30cbf58f3184d8d0976c9a71e82a815bf800adf632fdf9887f22ca9a5ff708a", + "iv":"3e1083eca37c3792ddd18df7", + "plain":"5f9c6ada892df89775dae7e3d57b25114465dab59970c76239555e2c3523df98ef4b76dbcf59992beb8ff9d01149f7f7b3e34c469c76233f23f634926086b7fac8f518fdbe964e4c657123dcd2843ac576513428f9facbc3a5cc4d9bcd81fc0390e875de69392ab67a6717c228c4c8837f55902c0341f506cbd279c7418a61c26303ce1ca2964104169249d3bc1dbf24fe390f16660e048cdb7a229a2224af58c13478a7989105bfb9ff4b7238c35b4fff0c5477a049d418067d4237f13fb481c9bf824b85cdade3e89a91136f263730122756369b7615750cc0d1ddf32d46389167109e03f5d02c7e0ea9dbfdf12938e631025a92b45e72754a4128b84601e3c3b4c8b8c68265510522f5841c859dc7fe25766d0f6ba0c943f9ad75453aa490b3b3fc0ac9ba83c814e388e05569fe782c86cb706bfec7d86fb81861353bfe69295c6b83d915f1d5e110f5b9e615e1b9945e1581fefecff6f58478f5b9c73517932daa96ba21e1cb637c4c92704cf61d76735d7785656a87da8d44103b25daa5d759ece8a1ab6ea546530cede5e85d04a6c87a6430d245645e01947019d225e0005aa7cabd41b13044fd6672ee217b1c737f1d807465b62c881b85b799a531de5a76f54eb4045f95084bb1f447c3a2380ff428cd67f565891216cf14907c0615f6bf765c8e4f8bf07e36507df1794b94210662cd13aaf77c7754bae4572a9e0c7e17acd269aca8f8d53ed2d1b60395b3ef175f07789f275d32839a07221d33208767e8e00924c92744e26fd3f19e0da311c0fa5bf10bc40e53a2434d8959603cbe2cdc890a8908d8fefafd4a158955e4e19a41bf0e5d82e301dc943f2eecd414ee77f0b45ae7447a20494e43442370cca55b2bc5ea99897e42cd5351c1849f70f0e6cf5365378f9e10b37c306e04d238326ece86223d527024086a59ae2bd4fa4a851546f5bfb50156b909e687149588daee8499870c17ef95128f5ae5066785ce9fcd4a657bd869181cd68afd53f864a40825046257e7a001be15925d6d4518d8dfbb12b6cabcaade19c2b57af8d98585fd2a9d55829423b31c8bc88e4904db7d47bfb82085f1bffee004ef8d4a13a898d074456749827e6810a3ada3f0b47688279df6d8d1ae18273c9843bd90c9db00c4f33cf56645b25ff6f73c1cbcee86523b6cf08af9689dd6f03ee13e16b18dc5a8533fdd22b51cc78b686303f7a0a855a64725df4dbe8d3c990e9ff92567cd714c923d212b3210b497ba58efed06bda57ac98a7fcda83fbb5b766619527c4d5454c40c9c58f276e9376bf2e75f1085d349813301485ed3e7dbdb2ef861d63af6f5e1715185106922b870dd0a2b718ff9a1d06a1e364c2579836829c21e1f75c61bcccb44f95a1a53adf8d8b9ae44192b1957c026fa0563f3e3f270e5b80b412bfd479ec8cce1869ba31cc9c133a7abc725ef85d7e5fe3f2313b8819e546c5aeaaec814cee029398be01c0c2e125bc5e22eba8f0c138ab17136056688b90f0b4dd58229b110b59507a104b7176d4a10e93fcd0431be96a3ad5b23ecdcfe69697e6d001d4be3ce0ae1fce86946cf1f70222a12601a9f1e3a343f6b10a6a4fcf029166c71fd3fc7df8ec0d9a614bd4ac6ff5b28c5f5481d293a1e4bb5eac290f69be07afb4d600c8b7bc0a3efd0338687368774b05dfbd75a4b9a1196426e2beb770734f153595d341e746cdb5fefd0d2586a412a98800b5937730e4ea4e1fe6209b6", + "aad":"6289ce2425a3f4d4755f1b828b132e4ee3ff80", + "encrypted":"75c7b1f7f1fbfe991e7a8de11e31699fb415b07ca3df6df709351f585fa66acce6b8cd84562e6030b6ccb23e0d4623e6be057f7d7ccd91d556f60c8085507a897816151fb181a06cbd77b4292017f066d4420befcec768043cebaeaacedf460af18bc9f1359fd7057f7c0059212421759123df8c4a4287cd1a18ae1550bff59c42b04e7d5bd3ba8eb1044d22c06691b06878b963a99f1bab25d70e1178508de003de8c3ce72b7b0a31f025ef8013a3be648a421033291a009e2c6b9f6483b1ba8eaf2ee81399c55cc40ca16b46ce693cc3753839d8488711c9bfa7518e5f0bc489b6c8bd65b019f2d66325de6420e154a08c1327891b618b97104cd70463238aa0eb8ece71b013220f708669b3e527b449c08409b4d40af5332e26cb708e200422e39285346a7b83e60e11f90abc6d1b96dfcdfd98ff84c6601652eca70fda14e9d17457103fffb767a6322a0599717f481e4ac53a045516046f945302fafb1903b7d9140fe4324dd29e02fa5a41eeeafb409fdb2c1bab552ef1c004ad7418664694329627d649cc93e8f70a4cfb677a1f7a9fcd75d8f63dd96853e3a1dbb41d354ecda6f24a65a786643ba1ae159830cf9b11bbca649282c72e76f33a36fc8aad3cbbd5b3e5d91b25b9d6bc544e530b566fa8e124b95f70e59481095d1fd62ce0ce9373e405f94e9d27caeec26ffc238ebc3733f5385fb55716824c5c3154a2cfe96c00b95b5292ffd5bbec1b9bae556330c07f6f99a6a6eadf5cad912c76ef7c5953c21fa6e14ddc80101eb33b0a40780d9bd51fbcd4910b3dd723c0ccdba05be820fadc32f4c055017976461f0e665660324d2504c99338847a565d0b2f6f808d6e259be0c737389ff4ecdbe2d2d4b6b72cf7a1615c70df9ded10e94ac68222939a06c06d465050151234f8a4150b0e8becd1b93ecee4b925744c626767a78a0d7f453094a540ac6cf9ff0f4ac7b9027a7a8cf270a6d93d780f06b45b2a91937e916a457dbdf349e7efc4ac3c4206cd9fc8c72049d8fd3a35e4e948dcd0d33f03ef50fc97fa31db8301352ee22d9d14cd76a5228a31c0cbdc1215ae5253aed782229711c2ab0d2bec802212db9010830ab2fae1f5f9259e697f7ab9afc8c2a8fb829db26af9c57df920127810d6fdc80266fb85faf12b9323b1eb4482cd5a980802522abb232eaf141839f81ec44835c75d83de16bf91becd63cff3fbaaba6346d84e264df048855fefbc8ae43058aace922c749c91c3c94252858afcae513901cb777da34135a9c25a8f847bbbb889978e0a3d80d5e68d75f58d52c8348959b59a51433c85bcde9c621fdc38aec962eccc7570bc818e6af4b2edb9c3e7a675cb27bfdff7a65cfd9bd8483ba3d449a5dfd8a835fbddc69551e29f578a19a122f32f7d944e7e98789adcf8bfea12c7c1476f21db89bc061efe7a9f7a1d651a4dcca6556164493e5032923e38393ba34d2ae01908c9bda395a81e8b8f3a956d1e9a3c25a3d4fe99bf15ad61d1044bada5acb9079f235f33d8e88b4398b25ecf0aa46391453e8a031452f753f7e68eb3fb36144cba9147c73a087a54b61b182f27800017c7e893f16e9a9df3948dca7163c8c0d5eb115018354f5d810ac867f1b28f45b0277c8feae3d600b5a78e4a268609132fae47e19ee66da073bbf5a5be94beb926a12d38c9567ad5c252703361783e474cbf2f00b271da5de0d4770eed1e594f1761dc61eb829ea4b9a2932357d6e1", + "authTag":"aa339843efdbc71605d99349ca4cf2b5" + } + }, + { + "CHACHA":{ + "key":"8a3f0e3b54ee7a503e574a54fcf82078a458407f155d51f3545514c7360b51e0", + "iv":"359f25066fc7705f3c033bb8", + "plain":"e5373d313841980a8e08fa8c2cd535ae205118840901be39d4cfb67322d0e5ec27d6070a52c1777b34bd754e2d816e22b9155b54b3e5", + "aad":"6289ce2425a3f4d4755f1b828b132e4ee3ff80", + "encrypted":"7d5dab732a9e6dde5999e296baf002182b2845f61b0234cdd3c4b1d622995dc7c2dcab37cc08071a6b3f73e7c9267c14a955dbf2e73c", + "authTag":"46c88bd5f6640f64f9f923dc4a6e2b97" + } + }, + { + "CHACHA":{ + "key":"087697705e987be0a7f8b23c0723e42081a11b5b0a2b04f5972c7ac4915ce6a1", + "iv":"9c5681d280ffab921dc03267", + "plain":"3b5def0e0ba9d595193eeda07597a467", + "aad":"6289ce2425a3f4d4755f1b828b132e4ee3ff80", + "encrypted":"3f175441de6be433ef7b41af1c83057b", + "authTag":"c5259aa5b02a595eded70d04ccb3995a" + } + }, + { + "CHACHA":{ + "key":"c2a16579cb453b989b0355d2666c652ac668daf13f16c7d7e343dd0f175cdd56", + "iv":"5d04bd561aca61964f3a3c0c", + "plain":"00ec9f9d053339635ebf507aca393586ebf11721474ef8794e7f8c9557a1587f", + "aad":"6289ce2425a3f4d4755f1b828b132e4ee3ff80", + "encrypted":"43a7b6bad10575d9afefaf550f7c5865ca38166d7bf1f35d672c1c317ab24bc1", + "authTag":"aebce23c5293fee0ebace766971ba087" + } + }, + { + "CHACHA":{ + "key":"1f6df1a0114a194fe820fd31213bc026e4295cb462bb075b742186235224692d", + "iv":"5fce8671c6ae9357bdc22e2e", + "plain":"7daa8f65744e1207544cba845ffe0207cf146a08f3cd2223bd41203b4fb33ffb024755275b78041fee5191e2d5c5339af4417f6ce851e3bd2fcc7aa436c71f609c372387788b3e28146f4a45d28d7e29b86dc60c2c58a592ad39bff05bb407c2c2688d4ff2189ac2dd41059a78564119e95fc4e6cdb16475e81360f9979bef918d03899e2c8e760f658481dc82c4a1bd71954ca3145023cc81244e3af94701789e084f295d1a9d79615339f7a92cd302064e185d96cc78e98ccf285132ddabd18177f3602f0c9a457cc3a960a0f0c3ef45df82b597a577002861f0d0aeb25b60c08491bbd1d203559f1bf294bce383cd65b38249fdfefec759532040331b7485", + "aad":"6289ce2425a3f4d4755f1b828b132e4ee3ff80", + "encrypted":"e277f02773e2f164858dfe58e4a07910ad75c705e87fb74c93fe1d8722224967109aa24e1f0f160dda22fc96c1dcb2e0b4cb0833a6ae075b18c1fc00ca45d8b28a027517a4b4bfe9fda778ddc965e973b2bff1f22ae6ee0c555d3626720be7267e2182953365b0cceaa354014b145adc18d648e009f2952ab638912c3ca0d36591938c80fda2916b9fea199898027a5cd3904d936175248a5d0f18192e47346f949050571673524142a252fb10bc61bcb8b81747b603e285f2caf468267eb58b248a14fb4aa3c49683c886608bc1676453423901b377b80a7b42e570b7038a9cc9962bc856596727f5df321201863beccd31af2fbada316e90ada97c1ef6a538", + "authTag":"a5951cf90d451e321d364aace4b886ff" + } + }, + { + "CHACHA":{ + "key":"0ec1fafba16e62151f61601ff7bc379bcb9ee18aed94e3353ae047726ae5af70", + "iv":"d2a8ab68bb037a0fd91cdc81", + "plain":"8bc135ad8ae749c8237f57ddfcc835fb32c12c", + "aad":"null", + "encrypted":"d7446a9a361ff6e5e782db5d883fe6bd9058ef", + "authTag":"e5dfb918ab814fed46335e12979684f0" + } + }, + { + "CHACHA":{ + "key":"c612eaae87c569ee2edf4f1345e877f5d2c882e933c6e61c3fa2cc72d842023c", + "iv":"a6549c6627de4aa8fb780272", + "plain":"d7", + "aad":"null", + "encrypted":"4e", + "authTag":"c778232e73ac4daa97f4dbe28080bf48" + } + }, + { + "CHACHA":{ + "key":"8b6c7b3ef5325deb74efbf17bd9c9342a6f847dcf93c3fdc44c0d9e145031167", + "iv":"1466af042c682f71b584c615", + "plain":"", + "aad":"null", + "encrypted":"", + "authTag":"88e96ef69149050ebe731ded6e501edb" + } + }, + { + "CHACHA":{ + "key":"9432f84fed0ae752479afe3167fec1254bdbe74d925544f8e2bf8a43ac9b7f79", + "iv":"01e510c5f4c3c1e8b41cf2d8", + "plain":"08bbd7414cb7468bfa4a03783c19c2a519f899c75ed1f2ce088e96da274ea69c7ff54ebe4a7d2a12d5f6fbaa78898196d82137c506008dcecf41f778cb3f0a747720ac42e20faea1c5acebb4ee1b3c9bb3753c565048f23abdbee352d771c29da70dcc2c8379fb1758b9a5a9d49119c9d25339f69684802c83262f4bac842814b05d3341e51c59cb8465e106502f7d84b0f1a0cce1d23656db6b0974df23f716b8e2182bad793c6ff71b8a626e3c57bacbbc31ee4138ba35199bb7dd29fe08c21fc5615ebd6a516eb42c6e4e5ee93dad00f4a8b4a315fe219aedb84d6ebb2cf8cee8f8e6fc624e7c3913e11a0bc7b7445b60a4ef6b6328a899de9c86872722f99d73060a467cb95bafb0033de0023ef7dc6b36eae4356977483a0de62aa60930f5bde230b7a5bc1f588528802438e58b73d737c50d6135ef9a204928cdeda5abcdfca85eb0b64c01c192dbd06c4b81307749689120ca7c7829d0c0333a71c9b0bf3d6601e9fd4273129d480f51bb39d9b30985edcd619813e5533b0343db90772e1205ba9070793efc8b5f2fda04d908637612641e4e7fa7913fab8243870c23b96467543e8b838500ac7a10ad389d044ba5bacdc9c2caafc974a8a98f3f78d3ad021fe65feeaeb5aa031836fd508dad33bad3d62ac1b1bfbd47f0ac835033a2426566a992e8921f9e5d199f227655d26ae2b5b846adcdb935d5b2c6d8cb79f110bf1a371701fdd94e14a1a2f007fb98757666e793db43d9175915767b019c26a6fbeb765ded932173d730dc58c75773d6f0a9eca63eb637fe4aeaa295bb2854bf4200896895caa0a7dc832178a30f4091e1967b7e3011eadec67f4043c1f959108b6135ba70a469db07097d9d608aa14aa1ba514ec8cf402dcb90c78bafd93feb1e38861df3ecf3a97dd78c77e2392782c4458f3a67c50aa833c31e284f9eb987fb6f8a30ffaed7dcd179991b79b15a7d9ceb9942d81d33e74d0fb964b3c91915f0dcad00ba17ba64d34d4eab774ce9aab1b98b0eea3660eddd6b78cad0be67fd78cb27cf4108bbed1bf8cb62b84713b500addb83e3664f149844735c71840c0221045ba16b8a5c6992540d04c6e1dbcb834b4a654c9193877ee4cc6a79a19faff87c06cf9377c7efcda3447329697e1a6f99f3790b046944682db137a933df20fe1b0ccffd2a98ce64ee5f3e52d073e8e242e01908c07adf8dea155db24ad41f3573df569f8683a99d70161fe1798577c1475dc92cf034d0eb81151213d80191fc931bd498a1a16035188ecdd8267b7bd1f287f82e5048ff9628759430abe82bdc9d32713e5131e35bb1f7610c35e127c9db216415c2d66c41933994d911c0b0d5402e626f6d6afacb9a25a130ea947b0f9016ed29f11f9be3db2d440313a2a1c0b0f5ae476057e87834d7bd5d13676d104730eb1b776fc3fe5c540a54e4b4ae1a66241a770ec39104efc8cb7ab54268276915e8baa533ab0b970a004db593f15834342a6a0a56438e9c1b91fd6112ba79caaccd71fb2876e2233958fb0207e7facb8cc64db2188055ea4ff320338a535a09f3b7a56fbb4fbf67aaea2b93e1ef40d6aae4e419bc6a6e77985098dd7f8e5261a6274d27ff0256cb3031fa342d706d71e6820c8a88aab97fc839121f724d9da5c339eeb81a8f664021d309d9eb028f716b17ca771a452a89043f9c49d669b144251b1466fc8c0f472c4d84f604f8f9136bcaef6bc3355723ed89150f0ef4f3", + "aad":"null", + "encrypted":"5a6bbe216f007ab450c1b6a6add01baff907eb158aef83a2527ddc7b4edce36bd8fc08cfffe530fd79cbdce299d62cd48ef1754d537ca983b49bd3234da3b03f9650bce7075ef0aa553f0c6ed3e4d3039f30ec92ad358e942a54a9c82f8cab999cbc0ef1f9ab29ff209bd557df595fbf54d3728254b8986e19d7c53379ae4e14b6c7886fc1fe74efc7dbb7bac9b0847f63713863894ef9ce3d8dfb22d849529cc69834984f8b75d10cad945e503a21ed1db1c0ad6a0ea51ce4b06b094290ce174dfc480c64aa7e5d9720274a6b96db52da2a27d66e014deb3074f0a34ea2cfbd12a4a36a7dcabea2f6ac35e9de33f355830bdd2ca9252a759c5bff33ab859a19740bb31536cf78432ded7b7f7863bc4e019133af1e3a59a36283aaa7a56d824eefd2d95de0fc7e895d1cde2383135a0278eaeeac3063fb09377b80d098e660c99a1770ed7ea0cb8aaad0833e569867a09686740d901dd24d94da9fa750470e597d6d65c9c16fe49b1781baf5cf716dbad9b699d784667b925bcd83b668a0ac879f4b405529214d0208f67abfb472962f39f1063698bf5c89fe804e9ef417eadddf85aa0adac47c59b3bf98e5942b41f9236d10485d682d4d172ba90f1eec0afbb2c48ad26e6383433f2f1de49126604c21e495953cca807830bbbd1f5486fea2db45bfcc4004acd38996b6a3e364f5b02ae81c033f6d0193a5d0994b401572b6d1e49d1ecc4b6c58e32a3bc76f5c487006e699f68138f61d90bc74ad751f4d5ca19e9ebc1a3f38e9cfb4bcb3666c0e48926e985ce7a792b7470cdb8455a22ac60530e0f880a96f1876e9b575a7bd124c776bd568d10c63460097d95512ff4caf70103a62c735607b6c325ccfc4961417ba81b6ec1ca679aef9beea78a6224b158e10daca3db40f0399db81aff5165349c3f8a8bce0185b829d27816e737f03d3eeb9fe5f1f5ce579683a2256cb586c08865317aa1860ffcbfd923989e32ed4ae4aed3a2c16b6eb83b5c92fc9eb5c643475bbe88d627a881270625874e7b2c9bc7f6a7c809897975fe584eb61e469f6344daeade309b2cd055bf576fa1e5695fc8a774ef3216371cf4a590d02a2ab6ba956a3b68fcc5fb4c2333ced7e56f3ce52505a9d8393cc4233378159ad57b5fbc486b87d3836030e6be37fa33b8179414e67eac34f48823f7a0e58dc08d926dccecb44d4b8dd10728df92bc10c359e9e4b4a889de41a646040d329ceadf8551d407223179651d09d006e624e027782f49e6cdd865768bf03a09f59282d03ec4a4984721d1f14280f4fc0decbeff89a5eb7f145a2d44e87094242e0cfe594aa573e615da8da9b3acf99a22941cb7d55d5235814bd68c5b316830943a5d935ea93b9002270702c671a0d291d02f955d35f31ee4f07b50fb800a3a56cb900b06d08d5152859aeb52f6545bf5d2609b80248d3280a5363d272e3de0ecaa43601907c4734529d54aabc903000b2984425be7842742975c9b7b92024824d749d18c48f314621975b3decad37bcfdc12d5cf51b634b5da6a9dc3179304fd6f68140ede41628f8e8de8be36adcfba324d07b190a8ea032c2f19b228403b2eaca04295a67e9a7c10118f4d643493a9929744705bbf7a46a4fe9b0e10346f464e79dd273a42e67eefe0e493c6a3ce0565b0c8876947e6ce92bc2b46cb9618ad57ee44fffba38ee133caefc3c26ba4cdaae6312daabaae25f3316270ab2c8423e5d5d23c3c4505598", + "authTag":"152c6c2990839dad3e3427a4dd2bda20" + } + }, + { + "CHACHA":{ + "key":"ca2496bb00a788de22b6eed460a9637e5c1e4a69e6912c50d532f2f4861772a9", + "iv":"d4803178e766d3178287a4bb", + "plain":"bb15320d730918af4649ddbb8dece5ee1253acbeb7613f89e8f7965f454b8e4e4a380383e207bb405f1712f83b59c295b7be5dbeb2b4", + "aad":"null", + "encrypted":"ad8fc0b3702b880303285df3ffc35a5abb6d738a82628cb2205b882cf054040c7bc689162938a8232d67a0b6a68338744212fdcd2d12", + "authTag":"9ebd04380eca234084ca2a92bbabe2d5" + } + }, + { + "CHACHA":{ + "key":"b3efba049bb8997ed34bfad89656cc65f2fc48120873c8928ba79aa4105f9bc0", + "iv":"14de5b6bcfe205d347c8d67a", + "plain":"cafe47e3861e1ff7251a758b76d56e88", + "aad":"null", + "encrypted":"7be2db41b30beaad708017aacc555984", + "authTag":"8e6a917295275f911ae1b23d94596410" + } + }, + { + "CHACHA":{ + "key":"8d84ea0e3666257820ebafdbef3b93741674199292fa0cd34f9b50293425e9cf", + "iv":"2c1b3cb726c15a81902f4603", + "plain":"8382e8384b100f8e644140c33cac35f7b35bfbe9ac4ca5aae8a8948569cfceaa", + "aad":"null", + "encrypted":"a4033c2cf53f5ca1502f2094f9404b2be2063896e9fb58e2e82837aec010ed86", + "authTag":"61b6a3b347a61caeb483dc245d6d88da" + } + }, + { + "CHACHA":{ + "key":"46ca82b4ca5aef7de510a192116a6ed40d00cd95ea562218357783aceaf5275b", + "iv":"e7a555c9da8508fc9c310215", + "plain":"d2a8db83a576bf70d5476c74e7ea1683dd2fd9cbde8b2322dbbc6434fda1d0b78f8442d71850c95daa52e67f4fdc9955c22455eed1267fad2bd6c68bbb82446125b0bfd68b2afd8c367844e9671d3dac23b7d00c21b1316acb2680f827c92251515bf00b85831e3cb81e3adfe23bc06d94f33a470856b52362820de612f55d83b8082c6567ed1a4066b47b0036ad89cf690b39538b383c46959c3bb0925ea9adf2b4871e0ba90eca8122bf401e3e52cffb26a5ba1f2e61cd3ea3160b581fd0eaad005412d9228890e89724a9c2e2d1f4564aaf97674cb90eb9d8f39c3779a7e0e5bb01904437ec29fd955fda2ab21cf5649d7e37616a991de9c0159d3253f182", + "aad":"null", + "encrypted":"b137a284c6c61743010577ffe5adfb629af4cd4450f9f00792835ceee1f7df97164a8f82cf0ea3cf26df84cb0c684a045692f3a9950d86daf742e06c250e044ca4ed9ac5d43489e84f677e2bd61cc96d01318891ee09beaa2d1cefe74e576d1af6829f2a52920f9abc955f237b73676d17a9d1cc27055f3b9dc75da875bb41800276463402c4e2f41543e6ca9d412bbc5c3728d6d1b5acbc96c0d57ea4ea86c7d466316975c87d2a6352d39531083a325008a3adbcb5c424a4df533cc4c895d8d1c04e8ea66f11ca54b6f296a9aff94c4d76a45400e36447dd1de5bf63ccf8a6605500769c2faa68a53fea2d0ae483bfef67ab473e69dc6d4e22d20f3484b979", + "authTag":"6d60b9998267dd630eda5f42bcd6ece1" + } + } +] +""".trimIndent() + +val ecb = + """ +[ + { + "key":"ea218c789a3e3c66d63631987c5eb30c", + "plain":"59a5057e282a7c5e8df6603f19d074e90be34a", + "encrypted":"891c17fa5d605efa80960675b45fbe302ac7a8edde206392e03f54112953e574" + }, + { + "key":"6203d1cb9b5c27742a7423d6877cc5c6", + "plain":"22", + "encrypted":"44ab161422b2f47b80da9d627653bbce" + }, + { + "key":"0eccc17137c63e472db5904e722a7095", + "plain":"a25e39c2bbf1b663c346b0432a75e9947284bca02e67112b19c3ebad6edc2d909edf3e6b595a5c1bad70bc2ae9a73cd506cc54fd5af1749f0d219964dda01ad80f8ffe5fcca85c24b288cc5a5b2f4785722d4e1a399f0ddcc1de3132d7e36dfdea31e7f5a121c586b71955d6cc9f573907a957ddc402e4827e32d193c257580b93b47aab0bd850234c9bd9a4c4b67d44da3edf4f99e229681c0eb91d38135fc05a70e7bb91dc3df989c2c4873d8f1e05fedca587712c5fc5d93f3f48949f61b589129018ed13fc92f8f076d9efeea9cbee39798211c29b11d7b30ce2c5c18e29b7987232475bd104cee6cfc8cc4d194f8a56a277fa42e12a00ddc61f41fa18cd615df859e00cc693ebf8851aef5d417206d17b55c0fda1073ddb18807c1e235e12e65da43495e730c40ad09123129c4ff220f92e2c170ad80dfab9313c7a5f272af5237651ddd5f544a79af6fa23a38350165175a9f3b89d37e9b969820029735e9a9429f9a1afbd415b289c1ed6761f29447098bb6e04d09dc59e47a02668245685dc223756f80870f395c4dd7c2836a79f35d28e4405e239a79982f5786a885e04e1dcb654be2b985a4d799aa2978b11d4f7bb96995e19f566e1c7da6883377e544545487a0b0d5d7a4a0f6020332ad8459ed4d08dc4a30bcfed1ea4ad70f7c1c46a7dfe1bc7431fcf4fed594be5b9b9749f93dbee8d2719957cff837056ed796310461ff7086b212b72cde113591f116e100b18a3f3360c2e6037c70b7326bcbae836aebeb6965f647470c2c3a7059375e46471d0c4f6ccdaf5218d811af86d8ca7fa2bd37d0e2e65e9f8f3a6fbffe16c84118e5b1ce03f192640dfd4b68d7d2c88206dd0c7f8d97898f16aee26b06e7577ff6ace6db2ef324ef837bd41835a437c7dd4953c414faeff0bd9e386b5df7316e4a482cf56c64ac7379b1f7d487681c658e58cf9a595306fdac1d8937f27ffaca6e2fbdac700b66f5905661e8ad3d583b79d2852d619e44820f0008c1a6d9726fcf7f2c99234b6d3c6fcc7bafc674234ef49e611eb918e9de82bc2af3871dc45b4424cf6747ab340a15df97568ebd4cb779c26b8ce74706e721f7eb67153d608ba237b7a2f6a7f315b1e02c5ba666e77be80f906dec4b2707cacba8dbf88d4a35b19063f2dcdfea0d71c1a111a4000c5e15b79b866a25c3c00d0d296eca23cbb99942f5dc22f149f115deef0dcdc6089d392b043addb0593a6e1411d742a6e64eaf5d48713727e6ef9dadc0fdd3bc8c5acfdcb032bdc095fac45afbaff53b401be225d7bacb1bf9daad43c105ced2d5ac0007afdd5520cb2ec631e1c5e265bec273776b67876395d2ba12dd6038ec7d0193393a379bec19a1bdbf6c3e29f7c8329cd8e5f1773a1e0043004ac24e702e16d70f9f37cb11660521f5d8ec11b097f106a254a8855740520839ee9adc51f2a4fc22af979fc1dc65d0c942f3d29523ea442d349b929d4f9b1f6e03e04df94f5799a9a01e44412e90b8c78c0612c156b8495e7a5e464bc75249d20f5d8b62a6eb0945267e5da0255a0fa02a7cdee1ea2cbb38fdf6620032a4a0e343d2d497c08cc452db0472b3c67b2262fad74f2e4b95a8cf9f170eda0059af5b041f8700ff5e35cdbff3d61e4fd60fb766ede629ed082cb8aa92d98bd92c4ba3f1cf9a19aa4793576d99b626d294b2dbee87fad423c870d60cba037f50736bd05449d339a31ea43cada9d14be9bd3ba8f571ed030", + "encrypted":"60a6e560e33a505b4ff74010da2c8bbfeb2147777786a044091564a143eb9349ee007d1878f1bbc2c0ff2bf4e8360aff19187b2bd6d9c6147e46e720f25992e818219644419918965e87796110f14539f91f0d62305f1c3da357bdc846a410c92771a07ed548ae86f1d26726a5c9cdf3099600fab8fbc42c7ab4c881e125cff382d6cf1fe926a58249f1015ec9654d9cc657205bef95379d66e7ba4f2dccd5ad0962edd917927d7d6b14475fe435d37c44ae9fabd3862a88094bc7ad4057c6ea79c58fa9a99d5c8bc1201e2aeb80dfc918839efb489a631a04189e02596b2b3138fce449aaaa5e40d641d466c55a9957fb78d4c7b52ab97d63adeabe004b772c6e5bec2b81c325d146718c52e48bce019781ee7c5338cacd9ef449eed9276ed087176e60836e4f48bffb2dfca851f754a30bcf5f4f230e0eba80f40504c428800c8da416d949b7e139753c0b396cdc67c7bfad9d34a7621c14569096b9b0e4e1feaecaf977cf86f8b98ad26d4f8ca80cc78f6787859347d40dba2b22970d3d7384d1fe9dfb0ecfdbbe8e34b684f80f9170e7e28d7cac55c27ecc13d74a440dd42dbdd7d0ee583b5b9efc547f05059920b36d2a1fab6684af6d8ff0cfe912a2797544b8d6609824ce23f4b3a4f857481324159ea24147215e114673b0abf1fe3b849d359b4e3321a8ebd84ee8c5b60afa04d51ef33d1e1f12ceb4da8853a53d720ff731880d8ce68ccf8b63b4d6f2c73be326af0138eaf33bbd5a18605f211f14e9b16f38a3f03c7277e347e22eca138f2adee1e82471432065c0220d51bf999b09c55b77ed3452a91c383ba3d5f96ba089a29ccff6ed8f8dc19df1dbe3805d35230f90533a1c1c5f6c216b87625aa3e606530f0939f51b2d7f300e5a543189f9df77f91cf21cd89efcb5a520006763feac992540edf649af309aba459780ccd35f3022f66faafb1040e313b05dfe11e2eef49518797453549604f706c315010c8aff6abd3a2c2b2ab33330357ed13226b23138528eb06f21a098273892fb0d4f43a8054853e38cada9494a95047f448dcba01163dbb3ae75028632ff2a3ef01b0db661622681e4994a0b103b4ed07468dbbc3a35bea74e8015e8e9a17fed286c85bd15ca896765e737e5c1ead0c62cda431fe12c968d6ca18db0d3bd249a411fcc670884c7afdf8533b5bc46fd9b530dd640710d598d40f642ea041afc4473ea87cdae51d67c4e346dd1c83c7221c12a68de6e49259f86e7e3c02ba025f9c7c49b81c7928990e30ea0107935f689290caf6888527941b8b700980be5ff4bb8d2127e999a31e45e6186d20b2f8c95bfe67c9863f30bbf076f9f6de6f5c59200428b615254b0633fd80d67df9671ad310e8c9d25bf7ca3525767ed2c6d67c72780bd24ed30abcd80f1bdf9a70e2bc3527c9e3f950a6ceb35d9db181035afc3ff972828069ccfb6cf9494d4b7828c0ef6fc3fbdb7e85f4662d9eff239d496738f199200ace92a662b722caed803322cef7c6433de1266d451f721096f138c755aefcb4901ea7c13827c310949322666d921a11cf8819515dab1b6f268a515e8d4de85faabd52c40b7494c8954ac45461df19be3d530870ab3ae310a0303003400f2e8013c0e42e935771373a2ee4a85c8ddb9df04d253c95e61e71bd86d48a82b21e5e4a899e5a3c55d6eb1408ea7b16b9426709f5b84628ac58f3370d257f8c836b3d6ef7d2432f992bc0e0293bab3447bb662dffafcd94c34c0258ec4b6be65b8" + }, + { + "key":"5cbd188b60a0b158563b9fdc2e32e0da", + "plain":"0a797c9a7301ff42f9b2dcecf97c429d29e1d83d91eabbd4bbfda3474f2ce60d7dc9b62026e303b2308c20fe48112819386798936e55", + "encrypted":"b8603ea95b680b4c1ee3481ab87bec27d9ba623933f281b33afddcc12e144b79f3fb4b2abc0c67a1c0b585263f658f54740530702acaf657846d6814f71cc368" + }, + { + "key":"dae266d0c11d773a019efb6b9d820314", + "plain":"7b7286074b074b4f25fb61d1a26c8cec", + "encrypted":"635fd879ccfdb8fca1474487b08d5d303f71a43dc78e5228cec716d066f4c940" + }, + { + "key":"caf19cfed605ac3824694d2a594d1ad0", + "plain":"3a6eeb7e040310b4134f05509495ead56a7b0e4c543cb29ace5e6d2cfa6c3939", + "encrypted":"bd63f5279b3ad95fd69ce7068570381f23a79baa179cda842e38070f944cdd060f3bc2cde11ed3645acbb567465f329a" + }, + { + "key":"934ebb73a66b0fe57dbc0068664270f2", + "plain":"b1221ec1fb88bbd44d19770a0e9d5aca302b13e830543a8fc1445c819d6668543f91f8f6255dd4ba232e6e9adb14fd9662e2318d40eb05fd040562c9faa581ae7029b36bda817f5d74f7a4c7136dfc99433835d26b96ff53f73050723f3df703f5b21fc082dabeca6c2c016ece2af47b67b99334f2b6a4ddd0db1d7e55bed56db79cd4004b7b74ed7d4f608514f21914ba28d5eb8655b9a5e9fdb64f45f529948c24b4d97749e31743793adc637dac6c18f7429bfea0d001ecc516d62d87feec182835f7e8ec5de5f834f0fa22f045abfc812443355269d0e510e0be68ec945c2b88da49ab9fe20252cb020a877ea7444898d50a6c747b72a5094bd9efefec1b", + "encrypted":"e0c4f2625cf819e01c643cde9f0ce6b9161d344b15a37aa65f5e51ffef30004beb05681a9d71e8dfa7bea39d2e63c8b33ceb44d1927113ce64dc0be80eec9648afc29b9b522c857a116740525c4be2a93ade2fa14dc33ca36f75c40caed171705cd30783be3a64f3182029054e43339c82ca21cb3e8f4dad233c77cc6d95eca59973453ca1aca8ce4a3cd791fe30b69f22ff4f56b4a1fff929e77b7024386885d0d9bef8b15b2f2aa52ddedd68dd7de55202637aee549a23697f820eb1fe9aac4190b1a1b04cd3358b31d25531059f9f02f1235571947abd8b65217a7cd34f9be161ad802558eaf6006378a817c6d19735f0b32082229cf26367e9883c87c03d06156d5cc9604a7270350919c55c9e0c" + }, + { + "key":"bf8a39a9f90468529220af403a20c611eb2679c006532b3e", + "plain":"ac03419b37e9b38d83f00ed235536860599f0a", + "encrypted":"8564ca530b282e065185bb752a2091d2703cf7d9d9de8ade29a6eed94a878365" + }, + { + "key":"c65f4cf603221c30b6c8e41031b5c2eaf8546a165c1a5494", + "plain":"ad", + "encrypted":"14fe3ef574a35933ded7076d11cd4882" + }, + { + "key":"6c600cad463804839db469c88b2a2dbd3659dd9bd6b4cbc4", + "plain":"2bebc473e975470be026915a26631c0b652ff7ddb8cccd795d6554a9fc1ad070089834c2bc3141c15cc13abbcb4299238a16dcaf0b426715c84c5b935cf6a89b946dbf9026b0eedd0557a1c3bb4ea395107140e4ff2421668a71c5f58ed391c7ec3721d79bcd2f42caad261fbcff08d39d19bc8bcfa3d4dffd5acc4fd6e149bf434274cd816809b31a94047ce6a0a1c4e6bd9d4bbd47e2c8027e066d457efb7de2d0d29a0087087d09b1548ace23e9896dcdd45a54744dbba6ecd70e1f9e277ac4bf621510f77e23b18b3b09b3a44441c771b2cd6369c06c547a8a44b74b880ef5f7576881346fc5f5214f157a23d7f6b1ed0167fde25825b4138e10559afd76af5bf686e2c7701b35c5f0094326c1f9c7920ba8937e1a41c5e6b6a336187e28acdfb687b6574230bf5c71dd1cc1f9698b9f0d6879f013200e70d1e42b9b41ad9efb697fbcaf316784b92419628ac620799828d94888a78b583ea4507cda5c429c2c8f9c2d3a93a282071104ede227917ee9cb78113eb1e66855092bc6498193d964e9a79c02c7aad99618f2533f1808301bdd77aaec3d0b2ecff879d392fbb001a1bca980b87c670d7697f9b721d7c0b143ffcfb2540e2b850bc26e99eb3a4e605fa9aa23660dbeb76292b09e673a0e59c7b2b5790d504d3a177e2e41b24f66550da76dea6f1d3b1b28c4be8d5765e3e7c30745c902a1d1e38e6a92cae056f6802b2527fb0df33ef7c1f8604373d2d2e625bd85946526c5f810e6d892425755b7da907b78c41d4b8381ab4b3de35d319597792d7b02210aca03de88cbef86f0fd47d9d05c184c43ce16be61a49a5dec39e13f502bb28a907b60a6a4054fffbe5f9217a98df69122de8d71fd6c87f59cc22a6fd9539972d85657077aee896c0cba786eea82f3d8fba75cfb6b32fcf53af50e7dc222499cc4297a9be70662dd6d01745212fd7d5e4b5d5f345624e53ab718bd5c336e53d67a73ca4d4cde3a9a2eb822cfbbf936c597c1c1a18e3d402252719cc80407d26e7bd9b9d48e49ab70f72bf3cbfd11b4fcb660489df4e14e3f663240b0a0ec5e4a533cc001e34798f12681de18de10d67350ce4c094a10c535983565d8a9f3e907fded1e806fd340712687846e2cded697f5e1293e94805743add2c18a81d67ca3ebf665ed5ec6e1f0986b50e531cd18a78ec383ae9e40900b31f5bea5820b727ef39e78f5dc650799b4b7d10b7ddda4ee0e3b61455c16231fa718c96b13e04dc1f592ba5a9420121bc85100ba7c87c2344d7fa9a159bc933a9b570d16c84877711fc4240b3e8f8f66d655fd5c72c27aebc98204cd5a9d515ef888a0549faccb28fe3049fd1b0969e97931c6ddc9b8664a0bf8a2938d842c26fa8c54f433ae88a38240a5834b0404b276e2c7e387c773d1451f4b5f57355f9e22fe7da52d2a9c765a42c0ec6a7c540ed6edb346b8fa2e9a3a92c3159c70b549c33e0c5523858b3183ac3d8072015eed7dde0f100c4c412dab3bae41816858673335370f479697d1ae8a632a8eb7017a0a96486210a670c1dba31d0a1f371a47d66de42036b93ea557ee2100832c6df5cdb4e8470a1d64536d833fae356ce543df9759dde5e9113879b5b3cc86f4b321f1661130fa0841a7442bbd3530984fcbd37cad44a06351311db41ab83c8fb9a0afac1021744d5fe6d8d7628572b04200a48665e1c848c9ab7e86ba6f28a5c1fcd25c25961cf2aa512b80ba09521e7f50b80e4c", + "encrypted":"e959ec4004dfd07bdb8967e7d32aa7b4b47ee2f17afc68b4f6e6e2301219db8d4e52b80cde22b4eff7975771222bb48e5697c22b36593af1986bfa883ab9ce7fea14e69611579b86105bf856f9d066fadf5818e03b00ba50cc9626c56358a70bc3666ed1435532aa196886fff98627d8749041093235fca8aa872c8e2ea8b86f038bdba83d8ed180bd3b68a176ce9d495e817029ae15c8e2ef70ff1d490454fbc7158853acfe429dfd904bc93d99f6445a8e58eae09f13ebfe3aa3317065c7526166d7762c05ce44df7114f330a81f95d987091002bad961baca5b4a836fc975462ac47e0102bc0b2e2a5acf5fa2d90082012e3ce1d79631f9b2174c3d92ea0665190eed2a06db8a25b73d58ee232167342e8014841a36bbc2443d4ecef781805b1aa5f0d1d508b27bc2ee6f5b1801398c3156acc964d118c7b2c4150c5eb5e455dfa43f4ae3dc7c177cc0af4c9db3a03b2b4298bd4751bee727c517631ad30954cf75618df50b25698c56330663a2293c007f2d2475b969ee816a6b2c8a7ae40e0e01c84b2844c19ed4cdca39167741b61edb008ac7f951a6b4e921b4f7a4e826df6be9ffa6e18478d3fc7739bdd82a4c7644dc9f45c1cd78420f1eaaf65324f32a074c05df56e1bbd2262296f23b285da531031889ef9d8c6f34c666fdc23ae5259aea59fcd19f3a8676836ca5b3ad92e94cc7518a748063e39eaca558d14caf15195f3e23de8877de8688ef016d177b758445ed2ebc9fda1b01a35fade08aae7054d3a049bc5b16e03cae930f12b42722930b903f533996383726e7ca4c261135a03e1041c99b63b0a6fcecd0e435c57ebcba90422c316186120e38526fa6b73308a7455b4e6082dd418f4e0293fc62de981d32384551ba3bbcb1ad43423c84fdaef3d9c7d2a48b88fb584d1095ee872535c98a9194ce8d830c1312727c2790ff5538d64c2f030122ee898f3b98d97fa048a7b33e7656ff4754dc53a5e4a34aeae64732bf9593a0305adfcb6cea7cc302484f1dd5ddf01adfa64cfc4e1be567b5cbcf67cc59b7b607fdad886c4ac9c72af3fe654185fa4e5f0dfab5e1051699ed30c05d918115d30fd34960e26e8004549846cebed4f38432e81d430e7d8af33f194cc0d44ee0b0a200a7a84cafb318afa2ae635255d58d936dff0bb2b9fb8fcbe96e8ab18fe79f48e4bac23f0a996eef96a4808ee061dc02cae9e337deb1a42ae07229ea0b928daab9c0c9e198e8c07557434f0f7cb43402077de0d53cbe156f8da3b56ec783905c780704553467ffee585a44be953b77f4914bd334e43c4fab2314831047c68dd038b896b230faec2624d094c5ee6c17349cb9f261b7c752c68703081cc5f25842a07c8f94f727a12eca6b738cd8fb4e5b9253e40c6acf1cd50b7a3460066a59f79e05fddc33578968fb9205315db1564a261efec5c66cab9f28cde52c81fe6a9b7468db821f10bb682c0458d52a204e69b2667cc7eb75549b32e0dbad390231e0a0a49a106461ec0920bb5448bb46fdcc14feb64af4ee574db77984ffc0c457298748a87fc982ff55785c0b91c4787918a18230c20135ea672d6aed900ce61e811c556d8fad42bac3b5fc54ed5d543388121457e614097bde5b59566962830bd1e4a4af86657039f16b5482657ffd97d3c8070cb384261ad0ddf3d6047ee9004b2ce4d9f421049e2e71538e122cddbdd2a06ddf1a1ab0a7608ac8136803d3c11d82ccbb3b123e3f543be2de7368d35033b6c8896b18d7" + }, + { + "key":"20a0a07c6a1b12e85581acbbf0237867c25b7940dd1a640c", + "plain":"5899b01dbd20f071fc09141309b7eaf1a3a8c26fbd85e2abb7c3a86a6322bca44d18b2f0c8c39c2024c03b8b2c749078e867beb4559e", + "encrypted":"8a97e02de575d79776c6ffc356e3a1589b59fd1a0fa2175e2da9b17c64e0cb8ff27dff1f18d0de115a06bc1b6484db236ee33f1003a83474b58e0c141e1483c1" + }, + { + "key":"218e3c7df772f036431802e4671c1b3bcfc019be4258c15f", + "plain":"4fabfc2bc5620e3d8bbb5df2c4742a06", + "encrypted":"712d8b9a072b730151ae82fe6691f269a66ad815e55af8b5a6961ed5b23fea4d" + }, + { + "key":"d242d8e2b870048e0d8ace56e1b0de78a87b32cdaaf4f761", + "plain":"483b3623102a98ed0fb0e2c2909f83ccf762f390b1d21fc1d49daed674784adf", + "encrypted":"0a8c3d42cb3dfc7d640aa5f4407287081a8aecf37ca5cc96f1671aebff00926ccb4f8e23a0de78fb25ef0fec96241a76" + }, + { + "key":"3e9c9fbe17af59f163ff19d6c14aff92713ce1e39a8f33a1", + "plain":"b432c1f61b78a49b800041e9de7d1f6c808e152fa91d6a8d0bc6521dbb034e6e34dea2923570ef671a9b6032f4382942995d0d4189d288f23eddd0868c0569ac62d84b958ad9364fadc9ce6d006920921ced873676bc401531989ce5e2e624602df9545617fcfb03ef15db72ca50deee406cf0219f7ef322f8dc18c6c29d29bef1f37292ba3e925eaf32a3309cbee81c484fa00c082866bbbe0b324f47fe82d3c4879934e2d82aa8c2250c3112e56a3041589662045f9a5d2549ad4184503a101a717716ebb6be4ae78afbd2666706793498db8a93545c24dadde9e2ceab20cf0373345529a494e395e30b2c0a359aec796f4a05c4a79b063dda73758d55e2e5", + "encrypted":"21fa01ecb91b9dec8e421cd70768de55b5260b6afa3a30e87f708a585bf9d050de5329e61dad73cd4e4084546a9da4317ad293c1546808446bd8a73a7fca23ec146716095dd85e2bb84f85bc2537daf2e43bf1c01bf6ce825b9786cadfc8c6d203372b6baaeae17a13f36c708075783c75aa4d067fe3b56ffc50d207bef7e3a8b3c57f402add17efc4dde4f68503921ebabec09130732a5087f1b7f4ddd4bace8807971cdb153ab43ffdd06d45898a0d357d3bab0ba7809087bf1873450326ac499c1cba3629b7c6249eb728fdafe4a8078f62ee27309c81aea5c7d203e86dc195f2fdf71aa0e99d6ea7d4a56d077bbc32901a966959ea28a9ebca9f14c24d58f2a1e285cf9b5eea55a370f10f733c8a" + }, + { + "key":"ba2acf5bf021ddc3e4310f3eb216f047bdc69919b407108790f08141c48761d0", + "plain":"1d90d568b2e18f2f4b98aed148285afe766f27", + "encrypted":"9549d559953dd7420617993a7d1d029c8b52ebc21a27fdf9e828e045be8e172d" + }, + { + "key":"f196044a8737f9fc43d1a8fc785d8277b3cff07cf0f258cb4c2511bc604d48bc", + "plain":"6a", + "encrypted":"b8507b842324d392a642e80f0d23a407" + }, + { + "key":"d6dc1e3dd9d3fc58ae662e54a84c071182ad9504e1e4ac4626beab59f69e3f5f", + "plain":"3a83bbdb709394c7f18de97a2d81fec6110b8a1584785f3800485f33b4a85b8f74f8939cf6983edf5e936ea38d6b032734724cf3f74ffa15ba9b60f387d57ba729304112c0b12ec1e506874f9bb6524d4ecb0130e777f938f6105a384184a2818ddff26895c60065c0a8d2b360fa0c84bced72707680201a4f7aad36762405a99857050cc453b6eaffc707c97c19549d25cd8604d38c4cbcf87bd3647f5600478af47db70d405ee9f1b5490fe173531ab63d20915b1d210d95eb25fc73b522311007283967874509b779b06bbbc03369a50136be155459d49a3162825932a12dc3f95502ea53f3ff620f7aa9fc5218e8045a94f4a0a09e8b390f8993cb1beccd29fc5bc8a42180e92df0a674748dc51d91e3f58764092cdcda2857c2894431952ffe7a34c27941f2af20dedf084c1659b8a20a92e4816ab1036f8c0a71564da332c1e87794c966ecc38c2cdd0d1e0a237ed4e75cb87f881f569e3230246a8236b652cd5a36c48e0fe56993bbef88a6e6687d4f87fb2283e8631934aa62074cade3775737cf4ee096f0e2193615653ff6edaed7af05ac7cfbaccf482a812d0c807c1c6215dd348a061d6b38df9820fbb3e6b638eb83558031e5e02291a0c8df6f134d432bf8a1608aa95fcbfbc00ed20ef02f9a0673b6005e2389a27f5fd1c3bc709d2628b9976f068bb2f38bf8a7e2c211fa2da6190662cf44c4c432fe2fbb720fdb165c7cccbdba1ee1e3c15845732aaa5cfb48d5ae186ee025b9a55bf5d3f2968cb3022f0fdef57587473c8ad1a9f8f78e06b887245fb1993d212c7c91fa772d79ba217108ddd0b2da33769a7d567c56c9ab065fdc038e60b6d24b56ede962f1146bdb85e62f30d5980098359bdc3e3f80106bf9dd39ba8c8470b1bf2728a97742bf029dfaa9d227c858b3c40986850e69f2667eb9de56c54c31e32e98f3e3009db3cd8fe7834c76ca0d2694a7d2faa85fd9e9d4a059c5583c24784b99fe4c817acb67489e1a621684f3e46e1c15acfab66f397228898cf8a2598250c4b08a37bd565da13ea3ef43d99eeca159d379a8c06f221c0fe463a00bda74236b9cee2e8c2d505bab9661f2c9accf94e03d0aa2c0275ff0976e5306e85c1c652871347b5875543582165f5bd6920e050dfb2348e3aa2e8012546574216739f9da255486b0c3e63efa23cc1cd77367da3317b32754fbf0ffbe9fc7cfe27a016d055b61e84b44cb1f569595e255b28d6121d9cc5acda12a9b4cba7f71adb7a3aa652c9b7eab969d59fed736175037fa26b7c692d7b270560240a9b4b04dff35adfd6f2d1d99db3c0cc90b2b32c5f40f42ac4a418b1bf21e9d2705d6a06692f8cc31f9edb743af342578f62b0d736a27ed98b7af314cff3bf009b616adcbefa1ae0e11e472a2c53d7f6c64c3ad90027db7004ae0726866ff2754a1856fe5cebee462ec001fc1e00e926bd31a99e2ea601473e7031a85c8a1aab3d27da10fa6b81d26d69374ddb9175a3095e59b47e0febdbe3bf50995cd979924a3575275b144051306ebd1e36df5608d043ee06be2e17c6e43ad9c3f84d6ba44a6cf5e844abf9c197838c4a57382326f7814dd2ebafaed504efcec5ac01a062e1e3dd2298c46d09e2fb6c9187f7f2e9622d1f41fa6f0c83a3045b3b3f41eb31da9a60b25ec8ba51aa52c1fb14a55a6226c87ef61b8a043f47db5fcfada92786c1edc8265681fd14e71db47c3c28f2479319ee6c74ed16520641c109d", + "encrypted":"cbee027ee006b9cfb5983b7d8373575d03919fe3728522c7820a7828b5b6069c06831b6fac8a62a576976d86123e9a92e14c63c61cc486134736da313cff5212daa7b00461468fb7a74b0e1b3216997881c33cf502065e21ca6161eea2be47a516e06474f6bb13d4e38d164355a7ab5ba100b6e3d03692e636e389447a391a0214eff4a3985bb8bb8d8a911f552171767b7d3cf9c3fecf13c4e17b0e7ea5cb4c7d1912347efe2e7438a3cfad7be681dbe16e9570bf33556913a68785d1dfc8d7276bdcec6a726f0cc4dd9f8d203d3624a35a8270da6f519922c50611a0938984d56cc1abf5a4e730d137d4a6a8431e3768ddd7316e71f85053e85e116153ed13ce550432373db58300bea9ee7ee5def47e12036bdbbf6d256f7ef2ba255a5ea4ffeb191821fca6cfd6ecfc46474289b9b8e14f1084fef7aae76886303437115d09be27660d5aecad41ded120d7589aa565ec4980d0ef592bcf649323f8c7fb8d901ba599b20ff24fc4227e8724e3ab8f197ec2d68168beb19c37a31e6a2474cad57e82ba8ecdafa45a03824d0fd095d8335ab6e4faba2e73e2e13a157ab2ffd1f263c56516791315f483595ea8ee2e383169594005db5c3709389d9e26986bb57e21221723d03366e74e8c6442048a8f790bc6e15c66177c69d4311887911aa029b758364e731177440c27b52c6ba17801af8c1aead492126d36078dfdca468cffa72d24710874e13392952aa0349fd401886087badbead8f9ac09c90fb687ff53cb463b26f2903e983504393840e211b68deb3d1a8392890d856b73c398633125d183b515d617666d7d0643ab3461eacd811175b88d7d429ac25beb4286122ad043228f21729832fdd18ccde5ed38b15878dcbc4e4924b779f4259389325b810fbfa4f0651787b0daeab78acabbe24d7a247ee3411aae9d05c34c316eaf7875483a72fa765254a744ea54ba70b81fe6098e9b60f6898363dabc037f1a0c74ce24b3f7d29a7fb679a3b4714393e79a892a261e6cb4f82df7f0934c277402258cf262ad4422a7faa5d6b9c3c4bb00b7f24f9c8cca2ffeeec48c0bcb560c6cf13331ae9cd09b7376749d042a3ede13487c22c1f5a6f52271b074bb9cbcf91733872ae976ba0df50260e55a2619258bdb9429b7ce4248642f11233e172a0c7688c1842c54f6d54a3c0f84566c697405ec4d34c3eb314ca9870d77bca53b8e76931965dec6a08970768e16c598f2331fc5eb4038b972029ff4b1a7fe24c685d084d67fb1df342383a2c40857cd1470f9fc405abddba4a69d44f119160783315cee7c40c96e49d19170257061424292901a9ccb9b0f0f13a303d5d28af75c58d2eebf5306a965ab964efafa36c02857609dca20f5a15bcaf0324d089f1854ec2f3f2739d55cdb26a602614bcd14d66cad118bd0c892b65f24fe6c3646b4e8b8fff5ee95c79336f5931dfc9ad6dacde01628a5c319f0ce64adad68c24ee267e70088f8cc29b9f8043d900a9de359f1bc09e830893368c835a649677fdc1ee77770d0fa624847059e0900e7604100cca3c50bd916203140746bae66a8a8bb46da376eab792417866a7c479235350a0d31193c06c900a6016d598a32c126152db0225c07f170f94a6352df8826b55c774ebdc2509f7119eda114c7bd5b5380a1c96796babeceed4c05b25f10575e7919aa11a9c74cb4ab871879cbefa8e6b83e94d9fcaf23b1f0e10a5d160ea74c2f49d562321d7eca68c018f69a8c6552ccb6beb67726" + }, + { + "key":"59ab99b8ccd6469046580c1414cb82421151acf81b12c2d0497942423e3e419d", + "plain":"fdf3fb8579d63001a3c8635b85cc04146a15e0723724ecc715e65fd0138d7aa828f1f5226448c8385c0ea7d193bec62bad0fb99276ff", + "encrypted":"3843e4e85e62d11e5d918e675160c2745e0b5cb78f48c2d41a507d3815a12293e5a664d6a455d42334b3bd35768226df40d4d8becb7525b0eab27c58092b84f9" + }, + { + "key":"5249b788ab3f3bd450e5d468e8ae5635f645eb2503e16b4966eae6b555eb1646", + "plain":"873be78e514d3cb10c8135bdc906e6e1", + "encrypted":"d8254a8e420bb12684f00278dc0c1fd866dc1df8231ffe3922e80106cc42252a" + }, + { + "key":"1d6ef9923840e8c0e3e52f2c2f64af452f47c20e57d410ba5e17295d4ddc57d3", + "plain":"eafdf3276b6ccdfd56d34e932c9a18e029bf6df39d94f3fb3593ee55a367f225", + "encrypted":"ed90fca138f2856d268e8dd8602df0a9c5ac60ea3c5cbe4562395ae2854189e1e745c091bd8476128bacb8277dd62aac" + }, + { + "key":"cdb28f572bdd81ac6bd2ca67dde8a375599d234a5eb4cea49811a4d5f835435e", + "plain":"acc893a3cdfdc0fd539da932bbdaef9c3465c07d4fcfd9848f860d030caad0795db40c56d9704c3b1f5e472405ea9962b349af89d57bd378b73ac945919d739df2a2c877fd586d7c3fd74c366df7b22a5e929b2a910fed8d42615f0988e91755f25a902b32277e335850e1faf9992402332e5df6856b6c9f2daa2039dd4ebda11fa55491f60b0374b60093205ab16b12398e5e0b8e48889fc418d0f2dde6a35b9accf2530d48fce81532eac2f66bc52c45df27c6d3f5aafc177ea8130650cb036354e6e90c967ea426f22e335f091535666f8b634c4f8df22a0eca7b9f666589f54e7a56493a89e8e1293183de82c347540d86ba8c2046503c7af007e838f7ec", + "encrypted":"d5e6ee5ef14f4caa55c51ef4b7b3351b01239f9cdf253e21f34d4d11714ada4084506e6684550700edc1e860faa5237bee31abf6489b81f0b6d305cba4f5da370f81fd534709be49002043851e360cd39350520c5b6b43740cc3623ae46046fdb647e25a5c461afb8e04eb20900da8c0b7765a1ec57f9da57730315cd992fe2a51c9d9458210cc432ac0bd2428a6e652e33086e4fc480d77ec9e9c30c1e5a8a432a817f0b1a60cd12c7600ea5228bd361634bcfc86dfa78a02f61032109e367841c9aa7fd91a89a47893cfdd558ed3ea4c2593e3150885229ba10f504b6ef3a918262d0b4500dfe4c8f2be568da7bc7b88b71945bd68be17a47de6d8235e7e818ade29fb3d9e47f0e2f7f10bed97f1d9" + } +] + +""".trimIndent() + +val wrap=""" +[ + { + "key":"85ca1d468f4436f8f0e9ae2d780264d2", + "plain":"7c2eb39a40fe58fe93d5750647016f9c", + "encrypted":"6b39f2609170a8bc577eeab761db3fe30d23dd523c0713a6" + }, + { + "key":"b048b9b6e171a21420fba08f623afdda", + "plain":"ae7cddf52d98e4681794e8f606752cdbb4c085c1444e04c8254cf3533e706829", + "encrypted":"b976a69fc897b795081eff88e4b681510a2d50c76ebb2877be4b8fbfce7e25e52b6eca3ce4f63137" + }, + { + "key":"bad070971eff554b319aa83897330ef1", + "plain":"67ad3e700d8eceb9ca68c64fa751ff1c8342d2f5fcda50d69b18e9c2d790ec81e7dd9f023d1fd5653619fb12150169798222a8701de335c9ae3bf739c3f74c7241e6855e83c5bcaa0a0826abc3d36bb45e3f505aa08f600a4888847f2afbc02ca8cff465e495d1cba53118c9ceeeea9f1a90097c1267c98f51cc2542b002f6d0ac33b71b1c69aad43a5055e82bc2db07ceac53fecef5208e0f780b385fad92b5b5b9966eebf5eb39b1345da08e30e23c7224c4c9a955809016211171027573b30ef7404f8356c36f7bfec9fdbc6f8eb205bdfdd1251a890d12ca7667d62e40b827312eeec319b61eabc6641c40e86ee84d9e009abfdfa1bc60234acd8643e179", + "encrypted":"2263efb85899f094145940227971582ff2fd4b113f8a7db20723d81f801a4287ce3b7df9158122170d606a02dca324ffe9aeda35b4ebb099f529291e982581ab6f4d2ce3a37737379aafa4346db958236bd182699ff68b9b5a17e779473cc09175dff32be351ea970df4de5b2bb940c625a8056cb61e375e243d2cf9f7327f363e1825e35d0f11422fbf1e7f0dd151e37df4256a362537f72adaa6a09ff16a2be2b75e1b02a208a941269edb4f60e1b88b7fb56ca96e8228580ef7a534e1d3dcaf767ab136732a1eab9af2d43b03a6bfc59d40d1e5222aedb1941c987f22dfeb762afb066b1d849e60fa67be88d0bab942b1b9b8053364b7a0da2e73165e0f2d8a6d559bc04ea51a" + }, + { + "key":"603fc3b3c5672a19832cdbcbf48ed059", + "plain":"3b0664b1b9f3bd27f7f6e1ed4b82b6327c148b5b63113f8dfe1804ad39f5bce73951ec6ace189394bdce37fccb4e779722c67e668086854bca12c1e0e9c41ffed0a44ae416a33ef4d5a583e5f8d34ab155bcd930834817b8dccb2eee87d7c28381b5aad1989deec2f0922cd6205d834217d3d17d6acf95746296426869bdda3babe8b4faf9975253364d85613cae6d8a9829541f02e3fcad4fbc8c77e23f1270bec2a267545fe59bd5edc42e9066519ccab9c2049c67bcddb24f0c8ad9582521abc62c8f4ecbc8a88c6587f3e2aceb9fae7ec8a5e0b848045ab2f592d006b4edbcbb3154b56d88732fdbf138db8347afc7da7f7ef2786b1b9331a51a015a2a60ebd433e16988d278362d0b0bbac33e6a2ef7b15b34df020e184849490d2f2792b48754febdf6cd0ba76ee0fe2b31da70457db9f0546d380b6815468fb0af08d90f713db5ec72766c33f3e2f2c6cc86a558409b9b08a396af176b810d023bf8cb7127440333a144306b93b85d31460fc152d945dad62573c6aca2622a3a978c06b3a55072ecc2d9e2c4133627670650812853d3835d3d7d1766225add5f2f795844a5b0d1256951e997c3b868538efc18191a02b122ca606a99fcecd69332a857cce17d7720739a3f09a9a44cb64d251264807757bdf5be3637e0751dbabb319075b723da594a4e656f10596b442ba3ba4fdf3b20a32abf5723232693451c7a5b", + "encrypted":"ac742a71974a53146133884f62500b998c0fbc4c9838c9c01a533297da3aaa742c4fc87cdcfb3477145bfece2232c4ba9a40e9a0534b8067366b2793b1170341d60cc1b1d6d7d10e8e9eeb440ffc2a697ae39c369f4afef6d7e2cb54dadc84cacd1fb45b2c2949dc3cac223b4b4a0b4aa04749f2170be5d9ee6c7f46ee4916c1005726d66e43418839bdef8f05825b5216a39f3c25ba53f3a6f9e1f09b78b900b174137108e84e4dfee9748247b77e6d6664368a265159441146072c02eadf5b2b6e8d8299ac743b57e1e3af897ad9e2ace1eccdd5e7c1c85e36e32bdf3a2dac56b2b5132ef45e390a3cbfc230301ac8e2382f6f6ac4c42870e9b1c91ca5ba12250f490c3d0c1e23a2a3b02f74b0ae29a051fb4576142bc49ca62799f34b3ccff608f916928ef2e2ea334211effeb0ab8cb42b886bae389dd89d8e4d82fca0ebbc5e7a2d7e1f891e96bf1ab515303d8c381f3010c61c4f023eacedc1bfe0878674e23cdfe7dac55748690fc5c8e7c30bb19b118625e5c6fa373846f2ae2238cd218a69d97978657bd5a78e20db0533d64c3282dc21bd6ad1dd8b0984c2ba206ce68f449128422dec31cc8ecf3402abb8eee25bde6e14754265def6800a9fe6716896b29c220c728f2ef214f191e169f7f6f9abdd43cb0197501143e8e9ec7fd4ad1bc473f80209470f943b129a88743aa4cd7b8e22eb514c2c9e2c420982ca9a836f8951e676f1f7" + }, + { + "key":"1db5eeec66618d321766464e6e76460b", + "plain":"313433bc430e4fb670ff685820ea1338f706569fadb8eaa80cfd79e1f7523ec1c3e4d5d3dc5f90cee09c4999798b43b7ded3170154c12566b853902b3a8330d19adab43f570d63b7d81ad61a237e678e24333349dfd2332944c764a462f014f8fa8d7bec1840d912652473251caee3fc1d8c1ba807e1621d8aeaa08d614fbd36a648f1c5d9fe5f9593ed86c29a3653ace962f7b88b90bd90b84f16b030ce50e2de324121559c1f3f4e620b525e86c5c3c919ae79b60c221ff97c2571b300a3b6785c5c77b21f9808da9c155a21d3e7f276d402ffe67d151c66f33e643265da134497f5d8938380ae2807ac29d08eaf8d9604106fc087be72d39f799e68f150f189d0ab40fbcf1124d726a708667dd2b6d01866a783cedd4e8963e81c5bb376f2aec8dbf3e0463f39b04ddb1250892c87933e6c61fbef37bc4c33d241090f9ff4fd36f7c719583d9b0d49cc504c47b4ea0c0f25fa7b3dea353dc323609d5e800939a72084d50ec34c1c5cadf5b2f0f9c2856b91e97d26ed36ffec967752b34f6a2d2823eaad1ef271f6ba61fe9dab165d78683c4df512fdac0d8306b6c2083c3b59c2ba0b7870c6136febebb208fcad851559f1758a8751714cb63c39c8aff83bdbe7d8c7efff6392d1739b7844342916f570eedd8f7cebf7eed4f2ed18add6e6bf705e9b4c3c1f185917e29ee96680318caf5181f95eb6955015379d05036cae1dea38f60d4897d755c368b07d2dfd9da2964b1697d8d56bd0174806d71ec5baac8a4d29e6cd1557b573696ec50799aadc5df09bd853efbf458f96cb8ebed6296f626a76aa2bdb65d9f92e4491733591a487320370692d264fd039c6456e086649d2a147777048ddba16cc8f4d0632af1330fa72bbe49000b1728f58b0e1145c76f94f02c78e34296fd53cfa29dfd4a8bc1aac43b7c267d4796ae81bf3250c361fed57dde0d7c2ef023f30d519da4e8d05560cce7beb383969879c2a446e4d555af201cebd37f1b4262e1c9bfa1845f48f8de3d6234f3059d891bafc256e13e06c7ebde00a4defd628aa21522cb97266bdfe416db96da57863992613594e5f609e04ae41b8c7436997d934689c8a6111dd9baf917138f2cb6a081bcb8131ff263ed4c1a8ac7fbcc821c3fa136faf5321e9170ebf5d5f66af0fd927d70a1fec0ad7ee8315b15f90cda978755d60a431d4c89922e27a21cf52b57cfc21b8aad999c8cfe4de6c315d39aef62c6b17a925884fe86494278393ec4492f8d6c222fab86598a3262c30a01694016e5a923a310469eb743c8dcdeb1696d7835370b97144ab2bacdf768841a1ae295d7a032aa0bc8a6456e30f02b801707bbbe433ffe0f53e12d34b7d87167406c44b1e9f5910077268c600ea182813f9ac52ed0ed027fc0c43d2b452a169e01170cd863fabbfaeffdd5424ec3d33f74f9c7af312ef3913", + "encrypted":"9c3e25c9b77d7ce8e52cfc556e62a426eff63516a5038d980bcfb3ceb30fabeb5cfc912503e5628104b04607079063bebd20f872c2fbbe5e7dcbf30c5e7181bc4de3da876259336f6b4905cf5e877bbb33816df9d6165658a64b1cddbbbc1109c19f520923fb1c9fcba5362f2fbcfb66496041ca462e203eea3d2e257d57f6f11d8f53b4153aefc563f4185ccbd11f6d1e19210f1beebd4433faf61d5b0a7870b7e7211a7ddfa8fd23728c860934a3c5f2dbfcada2e329ac2140ed757417860e8c4f88f5d82956a7aafe714d15903a3c1afaa08c4a7a9cc5179fe1d004d869f48588968908eb26683a5870330101cc73f8468e6d2551b09aec14f5ff8c992257a49c193d690ae2386ffb5d3f5d5301801a891fd2d9c9963c07ad115b8f80e725a475382e6109ebb4e7f4c8a0d11235bb0502e4bb775c70c9cefed260bd6f64593af4a0dee276d8a861f84f2a1cacc8656f7d3bc604b682d3c6c45f1803f5a6c8a11b4175ad32843a16437d3037e575ffadecfb54d9a6cc74b052281e3716409d284cb3ef0053343ea075e76b85e4fd2b4a6db58577a5fe3cf5c7d13b1d6b6deb1b7a57409d9e9fdc5f0b1ef48ec4110c9d450f16ee7d81267167be65ecd6ba574c2e8536ef595b1611c6b84346eba497dbf5d02d170d838dbf68b3373df25ddb4dea6591e6de3ea687f09718b492f2f34d82d65804002d5eb28765b908c04bd219f91faee5f025c6e5726d3e03a52c3db55610bd64340fbc23fc02399792580d2ec6e3b1132cd550bdf1a0219da3d806c28607d167adbb9c51497d4a99f2c91c28097eca1d60dc9daf3bfb36150916b734b5f61a2a7420f905e20117384e9b9ece7c6693a8897513bcd14d438089751e60164983d04bc768f27e1382c7355009428b1989a6045926ba274aa3023e58d480ed6ce399db25a1c857ea09a0662c54bc5bfcbc2827cca47c33c0e7b8032a0e930830062657d78e009b6eee77f31f6bc53133aad3ed5122b0497f3ba07d6b37f94027605455a4c137669ca4cbc74b474462fe1a84781c7f8309ae6eb7d875ba8788f21b3d4f8c2f56f143c82efe42e9726dfd1d3b5d2068d00e2e1615896c2f467691936c149e3d99b779c73a3c6357f5ab0873de28d5d6f2c8d581f68a5f643ba83bc0f5e56a191e381fbf9f6d227fa3cc0bd487e994e73579bdfa5c49cc5521f47abdd9ce69764ea534ffbddae98730353d54af7bc3bb6236fd16256810f531bc440e210fd8e2905f3f08dd80b9c7685621b5bcc725b576fffa757c66f7123ed4ad675c999516c18605eed11cb3097c4325e9ec8c3d10ac5178d917d8143627f64ef74822c62d6f951879bb058b8d0194674aa172cef6bdb2a76b61263147725c922a81bb3b880a0f4597e0d39ce1a3762c4386697cb8051f1f6c3e541ae65a25d147a477e090768a24d18d03a99612460e979dd38a91" + }, + { + "key":"0540f7f3636637f476a76a7f221b4ba4", + "plain":"c1f08a364901f843fa944d8bf10098e8", + "encrypted":"c4e7e643d87fe445cede50dbfa648f0d59ce48642321c7e3" + }, + { + "key":"3768fa1ef9d5875ae0cb5fc470a051ae", + "plain":"874bb683ad14cefb0036755b0a5a72be38e99b11bf8119e5e20d378c3cb82817a16d8b8a4e91515a245a49465cb8dc82", + "encrypted":"9e1e73ff97a3cbc224b361538878f0a86c83167c6d8ba43714580d5c01205630e88a5facddf672c63dde9535e2b4c50c926d7a6c9a4956d4" + }, + { + "key":"c5e017f5cf9606ec12ba3c272df528fe", + "plain":"ad51886e7fca7ed8be8949baa8a149219bb5fa40d1de60cd", + "encrypted":"1885a6d848bbe86f3b7098384cff0372389525db83ae4e41681209727843b378" + }, + { + "key":"e9514dc23239797337946953c4adfa39", + "plain":"b8ad2274933c2fa4ac90784fcd11675ba19b74e6ea5e0d95d31e0f4578fad41eb9ec858ae9d39bea3baf968b6202b3556f3b31e67183d96cd64b4eba0de687b367bd2027ed69eda9", + "encrypted":"9284df55d14cb67251a9b49b16949cd45ef51e3a42b6cd7cc81877f05f72d23bec616daad1549e62341d1976b297a0b824070e045c016a09dea025fc1e021fae5e09ffcce96c74708e0dfd5d4319fb25" + }, + { + "key":"6ff5110664ebe6a6459c178ed9b2c4615b284d1fbe6d5fc3", + "plain":"c2963f105ff1b4a8084bb0d15bb94001", + "encrypted":"9ea3a575daf8d2bd39429e9e496673c7e860b2c7f002c9e0" + }, + { + "key":"791bace3117ccd722f113797d55bdba80326fa69727c3665", + "plain":"e149f6de7aac84c4e6b4b1bed063e7f42cfa478057042497dcff6fb96797aad1", + "encrypted":"16eb0fd7d9f4052275a6f9c766944ea9979f27aa0ec926df4ea38daad3e963df764fa5d4f8d4cddb" + }, + { + "key":"e9d748bfdb6c06a6ff5ad27b7935c5530b4c1735556e6dd1", + "plain":"bb81c7e1124a11edcce8a7829d6abcc2a81084a24d86e1fdd5b3627e471946c7f7ce14a3f47b06a3a6e9adc71fcb761d6f5d579b538766bc100f05ce7e1790da62c4d70630cc649f0a439dce6fa9463124c190df746feb1e54b5f92b40c86589b46c6a16560fba53277310cbed88d61bc4a1b71b1df0fe6cb8955a13c0a417faa1eb97557819c432f63595fde4c594e8cf235ec5859253fef974c22a3da4604b0ebdf19d0ee38e5fd19645018f729c8231f37a821c77779530be982d1ea17bc6b2becd5934143d2340b4f4485dcdca404186a4078d6a7f332eff85c1ceb51b83da37eb1141e68cc436a309c291cde5499a90d2316fcd52d07bff8dd589f382ee", + "encrypted":"ceaeedeb38d500bf1049d86fa023ecd1d2360aea591ce0e220c4bb46af4f1337cef298d56a92cadefe4228448f98b1382a7b51a352155619b126aeef7bacfed2b952a1e6c18b81e704fe914db4c45d03b6813995e87ba9b34d8a93a21170360381985974ebda607beb7c26a3266c711aa2b5df23b5fddbb95362735110048f4d834c98baed26e61ae18328cd7ef56198ff7eac3bf541eabcee97a4dc70898fb0940c84cf04ecf6c7bb44d58cefd2718be2f33861c11fa17bdcdbdd6b23449d2a470824e5abf2efd155f64650f3f83a380a3e27c34cc16a6390b81c8d23485545996593c292437602e7530f2e65023a22671d7f93624161801c90e436e1f187fd0818f6ef3182bbd6" + }, + { + "key":"e2775da90c676815cdca72ab2df8f1079e6e53b769d3cec9", + "plain":"8abe1b65713180b10f728b00e7e5f2674097ba20382a9ef6e5b8d772765d56685e26bcf1c7acd5ad95ed061d1af1e8873773571a440f879c090460d03a7852bcef0001d1ae53584df144c6e3335280daab83fbf51d4fb4c9ef0501546a8dffbcd7b9b53cdec0b8cf3eb97aed62db05af559774b542441575b2536fc8751a12801c8b8d003c30d1d0630222e95a7aba0dee4a3bee97488318300aaffdc0dafb6bf80b8a7ea22c20873ab994af57e5711713d903cb877896f2246794985ff080250dda4e2c8c5cd9690fab1d8013cca83a0c2f41dad5572a0da22cab2a3d72fe7e6865b23c9bc6f13873442f7e955694235332b7ab940ef7683eb11094b7e80ee2d5764dbb6ba5cd61a015117797fab733fb09055bd6ad2ddbd12a415b182387d48c1ee3cde22201825a3d81cc30f954c1736fd8d47f2e3ff987e9d5c62f36aee10db4c84f1d630c988ecf1f5560ae73e64b567b4620e2a843ae7545670d7006a78e4296e49ee62532b259e88ca7414fe7748e01fbcd1cd2e6a67b0c2ccfc2116bf54e26952cfa8295dfff23f18ed22d28948a843f9656cd5a0196fea1a51c64f4632325e68bd783b72f7767085bbd6edf5fbd136f8f974220e717ec3651ad8b3b51dec8812a9f58ccca32bf162680637ac3c5a058bd5bcda3d9f22c96c61d0e89f9c0037196cfedb422e18abf7484bde521f7cc3450f9b1ba56bf4d21f7e43746", + "encrypted":"e666c1397276cd795596c3a08a4cd9d29952ac4025004de4b43543ceb723b386ebd9cab4c5fa21d59ad439cb180869df3328252c70454e27a56fc44d99fe1f17c08a57ac8cf06893ce2fc797cd1a4eee5b4a0f2263d51b4ac1dfb71cd0edb82003b59aa27ff9133e29c68e0649c1d347ef081ac4e2232bcf3cbbca951ac532e5b459502834afd9dfd13474da409941a1fd45fc9657164d6c58457157e1120fb162835e82d9b08c55b9794731be494b6ae954be59fb8e2ba5158225f3a5c07528e333ee7e272f7c696918b8932ebb487a3bec1ecffdcbe07abb06706b07a430c93c7cc2eaf5ff7509f222a1776c5c5e532d6c2698442cf2bdfeb48751d15d8d518505b14dd5277828e2943a50e5b6a239971a07f6af7b8371789cdc2398b929739a88e7976b12918cca232cee0fe0e5674895712b0279382d83999b8e054ca16a3b007547eb92a75c2f22e22841e55e6e9a708f0325c9256c9b613bd02970ac87c2723b1cc98abf785db73291e8a8535b7e01d260aca936a2bfe0099d3527698187610df5d1cb88d65ccf7c438c8e973477fdc801537b726b4debd2974d8ce8b1bfc4af7599f5656ed8d44a718b2aa3210fef98f32ae116f77d56cd1442b4a00e94ade0016e6ce5eeb8602f0c26e1a632204951a964e4ee42f19c9807065a2467500aa8b4dfca46dbc50ef0b414f099eb579a74d6e732be0bd132983f0155b596eb152ade488087b5" + }, + { + "key":"13251bdfb3daea70079ed09f95234f1f356a0f535d3f1c9d", + "plain":"62053bee494764153f6035f2d3781c37f8d15b7d9b6ebeb3f0bae2a43db01b2af416e75b83de3a2c461416cbdcd4fdad5a1156da20b57328f8691c81fff2ada74656a23d363c65fccfbc6457009c03154234759f193b2321f81d029f5a03d2d0a39de542f97adb5d119e9c444866b5d7332c5c751887acf8bcc896ce2f578edf2d645e1718ce3873b55a4e3bd37e7a4e610a529010ca1b30ee27ae60ff80b04388600125134ceca86a28ed86c581f2414169833f02c168dc8046d72a25cab09bbef1476553032f2774207e5f51e56b9649828769ecfd793f676563783b4285725b33b1b9b48caee200a8ce3f4c5990fa86b2b24f98e32c7f30d0ef99ac00de1eed6c0ef98e17953a387630a38af34362bd8a99d52a6369e2385d7c82916d32836025350cb807caa8664e958c83f65ad3fc357f80359be6491f0127289c0e54cf7f8075745ee553c1c4de07500d6f358051f2f31bb30fb4a7f27d4858a03b515cc514909f70144d7c7f16c0038cc2577c1c6dfb52963596f702e759883d4865613a3dfdf74df3fe73ebee7b36cb9b2c901b411a51fdac0cb65ea75848649a7a18015f1ec73f91cca8fb890742811ac20987145fdc11f88861a2c3c9eae8fcdeab748d7f948c243fbeb1b07440a480d19aef55748b8417aab483b1072582c37c08534058737ce6a9010d198fc4a54138ba4d1fd7ab802dcdb479efd5aacd5645621c1d1229f267248dce2b01650ccf8d070cd735c3577e4688c94e50774eb983ecca4766f76054a39f5c05194d1b345d50638a9b7ce0731743c7d21ac85a94a405d17fd2a7fab52f0b5b9331ce58b3de58fd2139502e4b876792f720757e5d8dfa22781056139ce9758101b3a8ae44986cb1fcabeeaaac1d4f5e4dc15b07caf3dfa5150dd22e5d61a9e9e42ea3c11e6e6770f3db5298d886bb8d3c90676bb4c7a62f83bd3e6d7ae31d1d414b8e76c100e33a056f2d066d49fbc5ba0eae1f70d909d53799a793ca4d456248cc4b10f9d9e2c7cd552be5038920b5d7005535fc1e37a149fa28cc5f73010304800d64ee615cfb7722c9320807a974751f5d10ae041bf7491f26a9dbb8490c5f7f449441379cb4c653743b09bd82d2219cf0b3f6666d29661c9ad4e56d195fd99e38c37dd3628b4ff298d359df72259ed90d15670a5d01b4ba0ac212b09b3b5272d719cfebc98b4baa6ae05f0d7fa74268b030510c6cb36e60190c4cc239133e3360cee866cb136bf39ab9a23b7d91ea9579a4f1894a566e768fd208ac0061fe6ceffebba21543e00120423e36cabc41e69b12023c4ac48ee77152fb4a9e21ed4bd467295e9f55edda5e9832c84d143f2d34082dd9db1d101041d36fd925173fd6738b9ccbfa090791591cc06c7d70f549584af951de0ceedb334e9c505fe6dcd5a88cee88e9cf5ab356c385202997d9914daa3cae10", + "encrypted":"b6d76646469f52db8dbe6fd9bf5718d75490a03d0556284d1f54e03cddc18239707a4c9213fe7d69f3705c0c39ff814ea270d495bda75a591529bf33b0def2deff5ebace42bae99b9c611ae252a4202fe603bf92471366e570185f27e470f058babc48ce58f5e1cea40d6caff37fdd7ccee3429d6100a5cfba8e0f7e11a78dd1a528d3b0adce33ce27f74f4a8410df547a3dd1b240b4088f6df0416acafdf2acd69fd2a1adad23f1e71b499b42c779e0eb5aa7b7485ecbde2986722d24d3839283b615b9f29de4b509f3be600166f9db04ae4f948a514a4ba265b6ce53d29b78dc59203c42053032ebc5c93cc43d7587d2d3264b26aa8c7abf3d8512b188013717a3e31ecf12ee64688b7a4f295017bc5a6bdd34e1986201a254866f46a9913f073f8e02d840faecda4e1509df5b56419c51687c0ecc23454578633f6bef73843aa7453a215a17b9f2fcb322559ba871f11f15bb2b08cfee75ca2ea0ae445a9e57e504423a95654d514e57951a7215dde550e88e12d8460537d1d3fa95bc0e8ab12819588a1946ae77d51806ce8449cdd66a204793ce4d4f9b7927712b078ce5f7d0f0a8d36eec7cd8e639e025f6fb175c08b78b4913518004957d9289d5f505839dffdfa029a6c8b1456aa7f02182ea695c6a1948b4da0fd0e9cfd11ec207505d78e7298709c4ae1370e66397800a8b037d3304f258661a3b6d0fcf9b1230dc5409f6e36009fd4c293cb816f8afc234af6fe4f6b53daceb1ef8c51cf966d2ed0b306d70bc4fca53e7be9b35916c62a52a19c46a4183eb73ad7294a2b7f41d0969d93f70e21c8b5b6289c5cfb88bf2e37351fb5ca0314434c3be7cee031c86e16038d0ad21f84b083e2a6bd1779d443eb2e93b9bad59917cf6a87311ae23dd63d779ae5562486533359193f611d58fc23d80c493fb7f6cc31d4f0f72d3bea0c847c9413ccaec55cee3e8ef052c53b7277f2dd99935ee0f893d805a7a70309f82347e7ea36ba791354994e7a5f1e6ce719b8e655019aea926291446a33d1294d64576cbfc58f4de481051e34a3d447a82bc115042ec5159be99aab7ffd4f3643341b0a102349dc288eeefe235d30bd1e60c5a91337ccbd7717c72a725e468d15cb59687a0b19be89cecd7d64f57e2028f639527d4d70f75c27675dc5b14b8841ad2ca5d71f36b5619e61f512ef8e3b390b5876237351815881ed04df35b9736521c5551badc303d7d9f0d9ebe87678f8184d40a2aed6d1c8a053fa967bf60734a94097b0ff799cd22c6ace6bac10df2f4a8592c49bd4aa7590bf0cda307bb22614a5879ec1c4e4189e8c297fb0140847f3dc514447a36b14bf144586288b5b5de296b444b8cb80599f3bbe8ff1780b7b4fe394d280ca6ad7042bc6b6080562db1dee974206b7db2e4a39b41494851198fc21ad0acec79d354e77e1479dcfce708fec6f995effc5fa6" + }, + { + "key":"0d0cd8cc9c2d2c418aaa68e3366f9c7658f4679dfba84a1f", + "plain":"2f044d6cf26595efeff579a307a6bc5d", + "encrypted":"a89ce30bb9d0ec19340a43dab721ed5c17b88a12e63b8af8" + }, + { + "key":"00c7f8699b9a85a7bc1900480f99d823839b8432550a3b87", + "plain":"67fd0b051d1f8cd0ed0293d54b37335c999d36ab76b12404577f4e3842fa0e35e0b590b2b3223dbee720577c223d56e6", + "encrypted":"cb1304f6b8c8d99064c936d0cd5e0ba9af59b6c3ce100e28293d40b27ea53d332db125700c6c3c5322949546e1019f9d4236353600802cb3" + }, + { + "key":"f2fc30ad0d6d8114fc94dce83ff05cee80809c2311b46d0f", + "plain":"d91b72bb2d1bd5e3763543b2a42b4452f0060aa802ebd9ff", + "encrypted":"6b2c5342968f62542fbd17cfe4ba91824702a432ba21f76c3083f22600fef2ea" + }, + { + "key":"2e401a16929f939dab19dc6aa9fa2a8324f2f5f3b6766952", + "plain":"819afd2ba61ad26d1aba2c7e4ef2ac34a4d29f8b14d4052a5e9035cf8e42d7f8abb94acc1ec329f103820cc17f63997b2fcea16f9d900f5416a187db41bd7ac26a7a1f0da795c3ee", + "encrypted":"4697e334a662c2aeafe1a1925daee2d513187e2f01dffa20a3e86aff10af6f94f9fafa7a92d70c09820c1ede68e093eb529a58a2eab2b4c865f411e4e34791839dc226f2d013702fd7f09f09ea892f82" + }, + { + "key":"473bfe0b99b6a766add875bbb3c229fc59e46c5fc0197733ce128a493328ff06", + "plain":"de9dafdc3bf2ae37d0af6dcdc2bc7c1c", + "encrypted":"907cbe97ac2357ef89ae755e4d8a610dabdedead33b2eece" + }, + { + "key":"bea4c37fe3e2cb274f7616f5ca84360800f8c589d0e9200219c694b965869665", + "plain":"f7c5be17421b91cb35b5483a25b8bc5272acdbaa235a32645d7ed8224f328f40", + "encrypted":"8b94fcc48862331c180cea55d71966d37940b41d445a8d2ebec4b0c8c4ab35528dbcc429c45347d4" + }, + { + "key":"0d5e8d8b7831c2171bdf20816da695fbd3fe54860dd74faf34a0dbd614076490", + "plain":"82144a99650b455fc992e65939618105adbfd31dac5ac9018d2f602d9d0eee9bfbde11936d0031556126f9f80912f45ef74270ff71a91afa54fabdc8018435e2d9fff447b793fe79cac0c9d08166ca07c26bf95030a542d5c376f8de061ed48e4de8771fcbee78bad37663647d70f8215febc7b9bc45943b5b67ec191590338686c9ec4923b692b5d0d08eb223d3371f2b3e073c1a89c85304ba9186faec95d839a65c2c3816332096c123fa87d60c92375c222e0cbf0df07aaed35a85be3960e2fab68c4c12cb235f205b6ca8011c73436d3ccc1cfa18adf716695e44289de97fda9f8476b3b3eb236976ff3dd22935747b76596e889c5016ddeb718eb96f2e", + "encrypted":"9b4848c37a33a0a0aced9995d2e82cb0acb2d9dd454798cf6ff7c7a832cec51b7b9ff029cba461e1605ee53f2e3ae46d6e5da3bccebfc1b1ffd4d4acfa108c2178a3223e5778f63c74767781982d82fd0c5762d905b8e8ed8df02c7bf643d1db54039ea58d81b4eade6bd5890f317038b7f534c18d5dcc9dd7a1a7cb0e2ca52dd9f98659a0ac92533fecc91cdc1c43d4553484702cdb9e1b4fd3a4058f4dbd6d93dc59c5262a4c77ba4640a52e2faadd835e20c47a52bf308cff6f6c7c030afeff7799a0ae0753b495cc693e18c2d0ebcc78072ad70520e0f86bb2b4462fa7201f6c49ed3e70a1d8d414fe96e593270ed499422a738e9c1e54f547facffeb5861863d4d0bf1ab660" + }, + { + "key":"e35e8e3aff6f9be63d605cffea63e0f26ccac1619af4af3fc74567f15de0524f", + "plain":"4359309a40aef6210be7a782c193f4709856b772f796ba3b749b2d824c231ee2a09b59d4ba35950da91331891586159dad2fc977b89224ed097c466dd917f784b567fe4d780810b9b1e144066bc46a2d6f8d529dc9177e4ffe38e46368f4ff37c488af709d287a373285bcf12e6568254cb4fbaccc9b0f12ee02773fc323f76c6f5f7621a7fcd4e6641aa2b6d4302fe37c1e8b11619f64d50728a6b903ad8cfbd62a8e402dbb2ef45ef66166a95ea4f261a4b169178157c2ccf3e5c6687bd7b72310c6df6dab833394029826fb5966462b602abf2739c7d47aeb131942c865c9f915eb038cdaa678c705ebcab70505aa43f4106d276f1a34d5af06e4428c86e53b9e234dea2cd388148fb55649c7a1f9dcb231f9a29bbf709bdb48044db2ac6a656c7b87aa889c7a25936ad4a23be50dfdd01f36400dba1c5531bc0c9ce8ec0e64657c1f12b4bd386f64ac260febd4f08dab15fb4be9242cf0d5585c6d8861ccf7ee99b3e7bc820901e3c54bc28f2d05738bb79b8689185ccc3faf189d963bf5d28b560c6ef830b31ca27d42d8a15eca311a812cd8e0c8f2f9af8ebd23298500a4a94dd0528b47b6a1ee4de2cfc65ea3e6955c1dd09ba26215ef167c48707385ce3109aa74b557d6edc07f9071a65a12b5485f51c92f2946f20d0f040bf99c51ffcd66e7cfdef4ec41cd7463f0993f6bf769a23ff4b079f756c49359f413b048", + "encrypted":"e616a85a0a7ad3b839b17aceae367e8bf7156bf5a1096b5a70d3df6b86506f7fe0e73c460d9ad12f78ea62f4bb621f70d4a548024b8fc95f619066f0a36fa3708aa91839824f55e32dc0f97683744d93ad14b3f0d58eda20ac60c0fdc45f204165df7db8607dcde602863c28ad2d974cd173329d3be76617fbf50db4b319c02f6dfabaf9eedc298c1da0cb86da1b443d14c8f533dc542121449f918cc6b88cdb4755565aff6e30d602c81254a73a323e6439a4c51d2fec9f3e642cbf3ff4f0c4cdb7a5f59fea15137d81f948d6d03916db473ab2c23403637ef2a896fcc357f9f0175f2bb3d9e1a6bdec2b6c0c1a91306bd78c1a28e586ed1a96500be05440fcc2169d8ebdc1564016b8fa948c27e184aede9bd6325303e1340acdc81a874f54e3ade10ba712b6d6e649358d457f3336928fc0e4715aad97fc1dd2ce1d43864cda8d12c99bf28009c84e0677544d66ba14d2aea829b9afe2a2eb51e978ca428c6d0794e70cd2fc4a9b90944229011cd77bb6d7fff8445d39a89f8068365989564d32eb9af649f59604501b067398f4c3dd463ee177ccf5f88779791694aba7dace1cb1859c2ebb3aaa75d1b1cb67e07a7baefff9bf3083c93b83e1ef679a6d23594e44b9efcbb809f843f3ad57ff4240206dd39a122a543b809789403cdcdf9909acd69d322716c5806cc27768ff48ee0ebb5372143c7eb8677a63afbfd73047272a68d667c93452" + }, + { + "key":"fdf97a745e659eb454c6377a49883968544e9de8bea9cf6b835845d0fb23e753", + "plain":"074fe55feaeb90a4c008110982285b789b50ec2ead827d860721b47f0120379988dcf8065f7884dc34bad1ad396e2a7f6a5afdd56a7b2125992f8fa0c79c8aec0330e3bae623ebe94e9c3ce1cf16f4a783b18a1706432faa5b9dcd0f31324a172b70eb82bb96f4115494d3ca8627c2c41f2539c4ad036c324cd4850a7b8517f21efc408abe9aae227b75d68eb9dfe0e38cef3244e4c06075a2585834885f310cbcbf79165a9d2b7bdecf4198e454e8c1a4129c03ef7ec89d53fdd4192b9b57988ca2d455b0260ce5c47a01e06fcd15c773fb34219ea6328fc741fd6d046a4a740b7055c227f2f27b8fd26fdb7bfb435f366517b400d831a13bbdeebada3261c1960cbad5aac0bcc6de7c5fc46a513026503ff5239b2f76789f07dd767404e4c49e039a59babb7d7d10a8f2387164ddb8ef2aa135af06aecc672e096b631012ed73b2ad8278305034ff8623f2f3f916ad148840ab8bf8395a14dcc174fb50bc0aae5f116036b36e69230370a2a93aa4f52929db9bfee52bb52aeef51f4c1bb68810d6fdc29dc492130c42529cb425c87e779626e517d5892cf4fe3937648ec59568e4d274db988f901cea2c839333bf51ce520eec5f6ea17338ef49ab341972e8769582f2046ff92a597ca4ed79c127098d1a43343f5627913a0c1c4ad9f6d92aac9fc463f3823cd8ce917a7bc73a351936985fc0ab6434af02b9c1fb4acead046192b6bf7df1f2364f4ec165ab5f2df7df9446c8d6459cc5e789454a521927d528f0ef7775d84a0da420a140064b3b878df12e43ef7271eb6845673b372abd355fe7672d6b739edeb8a0c1342a3f25cc72df7256f4bd2bb704a28b0d38b0327822d59fa26b1bd488d0b242f8a49607f16fb87bc405c560710386858d20e3529f1a69acfc40a6bb5ae3dd1466b536ed89ec710f623e43875d2017dfc10febbc13f2a3b50b25a7f4eb7780e86983352f9bb4d564980339930edf2c0dc1b7f6459f465683096e680f224f2ad65d9631fc01dddb787fef7e1c6d7a5d92b43ccc6ecec0d6a70dda22fd07c49782dd40da2a63abf30efbcb88664b81741eb343209f2f703ca9bbda9a41146efc657720cc04a6bfa3afbfa251ee0b09dc07bb94f62cc1bdb94d076e081e76171925c477f1607712b0d8342c84edc8853ab982afee3515517a689112b880b376960a6d7a42d781bbe25d8b30330aae035f350f41af58ba1c5e927efc24b387a8f1180fff9dfbeab2189a675909c621ea6e3ed84e1e03da3f5e54976d23e875ed4e10d6fe9489a05a889c199dd64b9c7a8f563c52a9bd2b2195ddd43a1f45a27c789b697b76422a5b2f883bed13b0f5f2b40975ef9f6f300bbe0f9a0fa0592100d104ff4112c2edd44a5d10422efeb9243bd7c6655c0c30196dfaa6abdfe98d63f67286f383b3939a7bdaa7ddef17437b6860b6f59d1034", + "encrypted":"04155da6319a0fbaf6e6993867e65803a5b788aa6fa584731baf6b1bd13a42b9708ed5570ba58595476063ee731896e22d8d8be91e1c7f36eb07ad14886fd6706103a7a725bad4dd78e4cf22156ef77a8816c23c2933372e636d47726f77ac6f2d6389de0737cb7f2a62355dfcb48453c4c875674ab0dc9f16477858dd3dfe63f21461a850f7cfaac5536a4f96edde65b497e2db8c0f22698cf3b94404fc4daf3850a700897892c23847ad4ce584274f6d795fef02a715452d92ea567bb949b1b06ac7ea8e6b0d21f6f2d3d31c275e32b7cfa4ee286c3f60c905ec55770edc577f9b78a04711a2deb57537cd5eac46fca662d0242c9bc290d7809bbd06f12b8cd78426771e2ed2eb877345514f704935362f79d54faf26899e0beb6a17c417ad4e725518ec52fd972063c8fe01af4fe6f3369e7d800828df4b96220d2678bb5c9869b5ce59a98f2514832fa4e8e552a0b650c6f313c1778360810126e8317b05ad3cec0adb02d48dd57f6946bcd318759a6afd8dae9f74821f6e9171d0fcd1042845da9f5d61dcebeeac8d72d97d57dfdecfa1a62100840f6a4c4d78c47b7bb03711268b2925c5d965ad63cccb21c18f4fb284b58f87f76082892f00dc1c222a14889ae330e0bad1705f904743b1a863a7508db641ab572d5c39f5691f440477c3ac49703dca5bcca11d287770fc5ca48feddc49ed98313bd12a399f8da5721dc85b547c7dc5a2db35c2946dd49e17a24a3017a25859e3dfbb92f1b8d588269a0ca4563a7d2bb595104cfe09707d30f1a61952f0a60566da71101cef2161d420c7b398d00e19c22367ba201e0eb9d4209d56bbd48bfff0e23387a2940f27688ebd062f7ba6373bb40e357d1e63e94075abe33cb39bdf2753b0f7664744c29ebc9a83746fcedd154cf507abe5013456e2c82a93f501079ab9a0635017f5d0fe267a5965dd99c704c82dba4c40c1f7fcaf6c1333ce544ff4ed111f1f71b4ae7104041b5367833edff62953b97e693beb11000a94ddd19307f751fcc3f39ed3d8b143ef055a31941372593138d9195ea3c6c8644330e48bee74ef785a251ecc680229954715978c443514f077e5e1ee0ed1c6ee830ee335b9d3d40f0c1225e52535af23e6696a5123b06537367958ee19d4c9f4d89ac0eef4fdf9bd155112f32dcc89a2cf88ed20b0c911efc567074fd0df85c82160c678f1f70e6f68ceb5648885108eb223e5f4739bbf09e971880731f0e04132e6abdea260c2ff28548bc8e1f6db46a0c823087fe15be9f1c9c8cfc8ca0a0e48137fbf9f09e7691ccd16af93f25d42dab3ced791f725e1ff247b9a17f61826a7b5c5926844ecba9ba404447ac63ba829d698d1deaaf2373475833f38bf4783fbe6b839723d501ce365a2b40e058b45f4f83f3ad4e9b11876504892f2b20d31f614d5d859cdf8bfbf1a2e4a5843285f6a920037c4bc" + }, + { + "key":"d9d82e9c94b1d9244b30d67aa82aaf40af86b3cce2c965708d465b2a55fcbb75", + "plain":"dfd7aa6d2c2f31e434dceda79c41981c", + "encrypted":"4675e986b1395fd5e498c2d06295c7fe053108bc26b521fe" + }, + { + "key":"71c8e51df1b2c3d17fbbbdd6643eeb746001f000fde6cd2511961310bd28fa61", + "plain":"b299df2859412087d482a206b51936005d584161cc8557bcfdd369ce842b8bfca7ebddf2693b0c7bf1ffa4fdd6f17b88", + "encrypted":"76fbcbd86702f9b6068358b291bef37b8fded9509545b5f1b0e029b3196fcec539b90bbbaca61d8f2254e09cb7c547af8c3c4ffe3c7d787d" + }, + { + "key":"856379c2e86fe8e4192da6dba4376ec208235d11327254058c9c67818af4faa9", + "plain":"acbdc5d69c173fdbba544fdc2214fbde122da0a292f9355c", + "encrypted":"9ab6629da0a95c5bb56c4684dd1d973651eae434873123e4fd795110fa499e49" + }, + { + "key":"1cc560be330375cf162fd56de9119afa76ae462b000dd720b587a198e9b5dd62", + "plain":"ef5e07c33fe77c85a4c8262db02624693023bf6883b826c416b01c1103850c4dbc4eba192bbacfc3336b50f66d79b1537c8c6cb02b6ea4c4fa2d6944a4093f8b5192eacd4ee83308", + "encrypted":"8660b2da83dc496f5ca0c86318ba64ed364577ea566fd196be33e6499271b08cea45d50ad0dafaab5a7366c5db8e8af6c835adca2149bb9d6969591d8a5419edf4a36b0511be61309081cc2e24343cab" + } +] +""".trimIndent() \ No newline at end of file diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt new file mode 100644 index 000000000..5659baa2f --- /dev/null +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -0,0 +1,976 @@ +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.asn1.encoding.encodeTo4Bytes +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.misc.bit +import at.asitplus.signum.indispensable.misc.bytes +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.supreme.succeed +import at.asitplus.signum.supreme.symmetric.* +import at.asitplus.signum.supreme.symmetric.discouraged.andPredefinedNonce +import at.asitplus.signum.supreme.symmetric.discouraged.encrypt +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FreeSpec +import io.kotest.datatest.withData +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.should +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNot +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.shouldBeInstanceOf +import kotlinx.datetime.Clock +import org.kotlincrypto.SecureRandom +import kotlin.random.Random +import kotlin.random.nextUInt + +@OptIn(HazardousMaterials::class) +@ExperimentalStdlibApi +class `00SymmetricTest` : FreeSpec({ + + + "README" { + val secureRandom = SecureRandom() + + val payload = "More matter, with less art!".encodeToByteArray() + + //define algorithm parameters + val algorithm = SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512 + //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*/) + + //The sealed box object is correctly typed: + // * It is a SealedBox.WithIV + // * The generic type arguments indicate that + // * the ciphertext is authenticated + // * Using a dedicated MAC function atop an unauthenticated cipher + // * we can hence access `authenticatedCiphertext` for: + // * authTag + // * authenticatedData + sealedBox.authenticatedData shouldBe aad + + //because everything is structured, decryption is simple + val recovered = sealedBox.decrypt(key).getOrThrow(/*handle error*/) + + recovered shouldBe payload //success! + + //we can also manually construct the sealed box, if we know the algorithm: + val reconstructed = algorithm.sealedBoxFrom( + sealedBox.nonce, + encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/ + authTag = sealedBox.authTag, + authenticatedData = sealedBox.authenticatedData + ).getOrThrow() + + val manuallyRecovered = reconstructed.decrypt( + key + ).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*/), + ).getOrThrow(/*handle error*/) shouldBe payload //greatest success! + } + + + "Illegal IV Size" - { + withData( + SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_192.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_256.CBC.PLAIN, + + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_1, + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_384, + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_512, + + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_1, + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_384, + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512, + + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_1, + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_384, + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_512, + + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.AES_192.GCM, + SymmetricEncryptionAlgorithm.AES_256.GCM, + + SymmetricEncryptionAlgorithm.ChaCha20Poly1305, + + ) { alg -> + + withData( + nameFn = { "${it?.size} Bytes" }, + Random.nextBytes(1), + Random.nextBytes(17), + Random.nextBytes(18), + Random.nextBytes(33), + Random.nextBytes(256), + null + ) { iv -> + + val key = alg.randomKey() + if (iv != null) key.andPredefinedNonce(iv) shouldNot succeed + else key.encrypt(Random.nextBytes(32)) should succeed + key.andPredefinedNonce(alg.randomNonce()).getOrThrow().encrypt(Random.nextBytes(32)) should succeed + key.encrypt(Random.nextBytes(32)) should succeed + + if (alg.authCapability is AuthCapability.Authenticated) + key.encrypt(Random.nextBytes(32)) + .getOrThrow().algorithm.isAuthenticated() shouldBe true + else if (alg.authCapability is AuthCapability.Unauthenticated) + key.encrypt(Random.nextBytes(32)) + .getOrThrow().algorithm.isAuthenticated() shouldBe false + } + } + } + + + "Illegal Key Size" - { + withData( + SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_192.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_256.CBC.PLAIN, + + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_1, + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_384, + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_512, + + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_1, + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_384, + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512, + + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_1, + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_384, + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_512, + + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.AES_192.GCM, + SymmetricEncryptionAlgorithm.AES_256.GCM, + + SymmetricEncryptionAlgorithm.ChaCha20Poly1305 + + ) { alg -> + + withData( + nameFn = { "${it.size} Bytes" }, + Random.nextBytes(0), + Random.nextBytes(1), + Random.nextBytes(17), + Random.nextBytes(18), + Random.nextBytes(33), //cannot use 16, 24, or 32 + Random.nextBytes(256), + + ) { keyBytes -> + + when (alg.hasDedicatedMac()) { + true -> alg.keyFrom(keyBytes, keyBytes) //never do this in production! + false -> alg.keyFrom(keyBytes) + } shouldNot succeed + + val key = when (alg.hasDedicatedMac()) { + true -> alg.keyFrom(alg.randomKey().encryptionKey, alg.randomKey().encryptionKey) + false -> alg.keyFrom(alg.randomKey().secretKey) + }.getOrThrow() + + + + key.encrypt(Random.nextBytes(32)) should succeed + key.andPredefinedNonce(alg.randomNonce()).getOrThrow() + .encrypt(data = Random.nextBytes(32)) should succeed + + if (alg.authCapability is AuthCapability.Authenticated) + alg.randomKey().encrypt( + Random.nextBytes(32) + ).let { + it should succeed + it.getOrThrow().algorithm.isAuthenticated() shouldBe true + } + else if (alg.authCapability is AuthCapability.Unauthenticated) + alg.randomKey().encrypt( + Random.nextBytes(32) + ).let { + it should succeed + it.getOrThrow().algorithm.isAuthenticated() shouldBe false + } + } + } + } + + "CBC.PLAIN" - { + + withData( + SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_192.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_256.CBC.PLAIN, + ) { + withData( + nameFn = { "${it.size} Bytes" }, + Random.Default.nextBytes(5), + Random.Default.nextBytes(15), + Random.Default.nextBytes(16), + Random.Default.nextBytes(17), + Random.Default.nextBytes(31), + Random.Default.nextBytes(32), + Random.Default.nextBytes(33), + Random.Default.nextBytes(256), + Random.Default.nextBytes(257), + Random.Default.nextBytes(1257), + Random.Default.nextBytes(21257), + ) { plaintext -> + + val key = it.randomKey() + + withData( + nameFn = { "IV: " + it?.toHexString()?.substring(0..8) }, + it.randomNonce(), + it.randomNonce(), + null + ) { iv -> + + val ciphertext = + if (iv != null) key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext).getOrThrow() + else key.encrypt(plaintext).getOrThrow() + + ciphertext.nonce.shouldNotBeNull() + if (iv != null) ciphertext.nonce.size shouldBe iv.size + ciphertext.nonce.size shouldBe it.nonceTrait.length.bytes.toInt() + iv?.let { ciphertext.nonce shouldBe iv } + ciphertext.algorithm.isAuthenticated() shouldBe false + + + val decrypted = ciphertext.decrypt(key).getOrThrow() + decrypted shouldBe plaintext + + + val wrongDecrypted = ciphertext.decrypt(it.randomKey()) + //We're not authenticated, so from time to time, we won't run into a padding error for specific plaintext sizes + wrongDecrypted.onSuccess { value -> value shouldNotBe plaintext } + + val wrongCiphertext = + ciphertext.algorithm.sealedBoxFrom( + ciphertext.nonce, + Random.Default.nextBytes(ciphertext.encryptedData.size) + ).getOrThrow() + + val wrongWrongDecrypted = wrongCiphertext.decrypt(it.randomKey()) + withClue("KEY: ${key.secretKey.toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { + //we're not authenticated, so from time to time, this succeeds + //wrongWrongDecrypted shouldNot succeed + //instead, we test differently: + wrongWrongDecrypted.onSuccess { value -> value shouldNotBe plaintext } + } + val wrongRightDecrypted = wrongCiphertext.decrypt(key) + withClue("KEY: ${key.secretKey.toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { + //we're not authenticated, so from time to time, this succeeds + //wrongRightDecrypted shouldNot succeed + //instead, we test differently: + wrongRightDecrypted.onSuccess { value -> value shouldNotBe plaintext } + } + val wrongIV = + ciphertext.algorithm.sealedBoxFrom( + nonce = ciphertext.nonce.asList().shuffled().toByteArray(), + encryptedData = ciphertext.encryptedData + ).getOrThrow() + + + if (plaintext.size > it.blockSize.bytes.toInt()) { //cannot test like that for ciphertexts shorter than IV + val wrongIVDecrypted = wrongIV.decrypt(key) + wrongIVDecrypted should succeed //no padding errors! + wrongIVDecrypted shouldNotBe plaintext + } + + } + } + } + } + + "GCM + ChaCha-Poly1503" - { + withData( + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.AES_192.GCM, + SymmetricEncryptionAlgorithm.AES_256.GCM, + + SymmetricEncryptionAlgorithm.ChaCha20Poly1305 + ) { alg -> + + withData( + nameFn = { "${it.size} Bytes" }, + Random.Default.nextBytes(5), + Random.Default.nextBytes(15), + Random.Default.nextBytes(16), + Random.Default.nextBytes(17), + Random.Default.nextBytes(31), + Random.Default.nextBytes(32), + Random.Default.nextBytes(33), + Random.Default.nextBytes(256), + Random.Default.nextBytes(257), + Random.Default.nextBytes(1257), + Random.Default.nextBytes(21257), + ) { plaintext -> + val key = alg.randomKey() + withData( + nameFn = { "IV: " + it?.toHexString()?.substring(0..8) }, + alg.randomNonce(), + alg.randomNonce(), + null + ) { iv -> + + withData( + nameFn = { "AAD: " + it?.toHexString() }, + Random.Default.nextBytes(32), + null + ) { aad -> + key.encrypt(plaintext, aad) + val ciphertext = + if (iv != null) key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext, aad).getOrThrow() + else key.encrypt(plaintext, aad).getOrThrow() + + ciphertext.nonce.shouldNotBeNull() + ciphertext.nonce.size shouldBe alg.nonceTrait.length.bytes.toInt() + if (iv != null) ciphertext.nonce shouldBe iv + ciphertext.algorithm.authCapability.shouldBeInstanceOf>() + ciphertext.authenticatedData shouldBe aad + + val decrypted = ciphertext.decrypt(key).getOrThrow() + decrypted shouldBe plaintext + + + val wrongDecrypted = ciphertext.decrypt(alg.randomKey()) + wrongDecrypted shouldNot succeed + + val wrongCiphertext = alg.sealedBoxFrom( + ciphertext.nonce, + Random.Default.nextBytes(ciphertext.encryptedData.size), + authTag = ciphertext.authTag, + authenticatedData = ciphertext.authenticatedData + ).getOrThrow() + + + val wrongWrongDecrypted = wrongCiphertext.decrypt(alg.randomKey()) + wrongWrongDecrypted shouldNot succeed + + val wrongRightDecrypted = wrongCiphertext.decrypt(key) + wrongRightDecrypted shouldNot succeed + + val wrongIV = alg.sealedBoxFrom( + nonce = ciphertext.nonce.asList().shuffled().toByteArray(), + ciphertext.encryptedData, + authTag = ciphertext.authTag, + authenticatedData = ciphertext.authenticatedData + ).getOrThrow() + + val wrongIVDecrypted = wrongIV.decrypt(key) + wrongIVDecrypted shouldNot succeed + + + if (aad != null) { + //missing aad + alg.sealedBoxFrom( + nonce = ciphertext.nonce, + encryptedData = ciphertext.encryptedData, + authTag = ciphertext.authTag, + authenticatedData = null + ).getOrThrow().decrypt(key) shouldNot succeed + + } + //shuffled auth tag + alg.sealedBoxFrom( + nonce = ciphertext.nonce, + ciphertext.encryptedData, + authTag = ciphertext.authTag.asList().shuffled().toByteArray(), + authenticatedData = ciphertext.authenticatedData, + ).getOrThrow().decrypt(key) shouldNot succeed + } + } + } + } + } + + "CBC+HMAC" - { + withData( + nameFn = { it.first }, + "Default" to DefaultDedicatedMacInputCalculation, + "Oklahoma MAC" to fun MAC.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = + "Oklahoma".encodeToByteArray() + (iv ?: byteArrayOf()) + (aad + ?: byteArrayOf()) + ciphertext) { (_, macInputFun) -> + withData( + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_1.Custom( + HMAC.SHA1.outputLength, + DefaultDedicatedMacAuthTagTransformation, + macInputFun + ), + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_1.Custom( + HMAC.SHA1.outputLength, + DefaultDedicatedMacAuthTagTransformation, + macInputFun + ), + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_1.Custom( + HMAC.SHA1.outputLength, + DefaultDedicatedMacAuthTagTransformation, + macInputFun + ), + + + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_256.Custom( + HMAC.SHA256.outputLength, + macInputFun + ), + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_256.Custom( + HMAC.SHA256.outputLength, + macInputFun + ), + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_256.Custom( + HMAC.SHA256.outputLength, + macInputFun + ), + + + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_384.Custom( + HMAC.SHA384.outputLength, + macInputFun + ), + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_384.Custom( + HMAC.SHA384.outputLength, + macInputFun + ), + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_384.Custom( + HMAC.SHA384.outputLength, + macInputFun + ), + + + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_512.Custom( + HMAC.SHA512.outputLength, + macInputFun, + ), + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512.Custom( + HMAC.SHA512.outputLength, + macInputFun, + ), + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_512.Custom( + HMAC.SHA512.outputLength, + macInputFun, + ), + ) { + withData( + nameFn = { "${it.size} Bytes" }, + Random.Default.nextBytes(16), + byteArrayOf(), + Random.Default.nextBytes(5), + Random.Default.nextBytes(15), + Random.Default.nextBytes(17), + Random.Default.nextBytes(31), + Random.Default.nextBytes(32), + Random.Default.nextBytes(33), + Random.Default.nextBytes(256), + Random.Default.nextBytes(257), + Random.Default.nextBytes(1257), + Random.Default.nextBytes(21257), + ) { plaintext -> + + val secretKey = it.randomKey().encryptionKey + + withData( + nameFn = { "MAC KEY $it" }, + 16, 32, 64, 128, secretKey.size + ) { macKeyLen -> + + val key = it.randomKey(macKeyLen.bytes) + + withData( + nameFn = { "IV: " + it?.toHexString()?.substring(0..8) }, + Random.Default.nextBytes((it.nonceTrait.length.bytes).toInt()), + Random.Default.nextBytes((it.nonceTrait.length.bytes).toInt()), + null + ) { iv -> + withData( + nameFn = { "AAD: " + it?.toHexString()?.substring(0..8) }, + Random.Default.nextBytes(32), + null + ) { aad -> + val ciphertext = + if (iv != null) key.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext, aad) + .getOrThrow() + else key.encrypt(plaintext, aad).getOrThrow() + val manilaAlg = it.Custom(ciphertext.authTag.size.bytes) + { _, _, _ -> "Manila".encodeToByteArray() } + + val manilaKey = SymmetricKey.WithDedicatedMac.RequiringNonce( + manilaAlg, + key.encryptionKey, + key.macKey + ) + if (iv != null) manilaKey.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext, aad) + .getOrThrow() shouldNotBe ciphertext + manilaKey.encrypt(plaintext, aad).getOrThrow() shouldNotBe ciphertext + + //no randomness. must be equal + val randomIV = it.randomNonce() + manilaKey.andPredefinedNonce(randomIV).getOrThrow().encrypt(plaintext, aad) + .getOrThrow() shouldBe + manilaKey.andPredefinedNonce(randomIV).getOrThrow().encrypt(plaintext, aad) + .getOrThrow() + + if (iv != null) ciphertext.nonce shouldBe iv + ciphertext.nonce.shouldNotBeNull() + ciphertext.nonce.size shouldBe it.nonceTrait.length.bytes.toInt() + ciphertext.algorithm.authCapability.shouldBeInstanceOf>() + ciphertext.authenticatedData shouldBe aad + + val decrypted = ciphertext.decrypt(key).getOrThrow() + decrypted shouldBe plaintext + + val wrongDecrypted = ciphertext.decrypt(it.randomKey()) + wrongDecrypted shouldNot succeed + + val wrongCiphertext = + ciphertext.algorithm.sealedBoxFrom( + ciphertext.nonce, + Random.Default.nextBytes(ciphertext.encryptedData.size), + authTag = ciphertext.authTag, + authenticatedData = ciphertext.authenticatedData + ).getOrThrow() + + val wrongWrongDecrypted = wrongCiphertext.decrypt(it.randomKey()) + wrongWrongDecrypted shouldNot succeed + + val wrongRightDecrypted = + wrongCiphertext.decrypt(key) + wrongRightDecrypted shouldNot succeed + + val wrongIV = + ciphertext.algorithm.sealedBoxFrom( + nonce = ciphertext.nonce.asList().shuffled().toByteArray(), + ciphertext.encryptedData, + ciphertext.authTag, + ciphertext.authenticatedData + ).getOrThrow() + + val wrongIVDecrypted = wrongIV.decrypt(key) + wrongIVDecrypted shouldNot succeed + ciphertext.algorithm.sealedBoxFrom( + nonce = ciphertext.nonce.asList().shuffled().toByteArray(), + ciphertext.encryptedData, + authTag = ciphertext.authTag, + authenticatedData = ciphertext.authenticatedData, + ).getOrThrow().decrypt(key) shouldNot succeed + + ciphertext.algorithm.sealedBoxFrom( + nonce = ciphertext.nonce, + ciphertext.encryptedData, + authTag = ciphertext.authTag, + authenticatedData = ciphertext.authenticatedData, + ).getOrThrow().decrypt( + SymmetricKey.WithDedicatedMac.RequiringNonce( + ciphertext.algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, + key.encryptionKey, + dedicatedMacKey = key.macKey.asList().shuffled().toByteArray() + ) + ) shouldNot succeed + + if (aad != null) { + ciphertext.algorithm.sealedBoxFrom( + ciphertext.nonce, + ciphertext.encryptedData, + ciphertext.authTag, + null + ).getOrThrow().decrypt(key) shouldNot succeed + } + + ciphertext.algorithm.sealedBoxFrom( + ciphertext.nonce, + ciphertext.encryptedData, + ciphertext.authTag.asList().shuffled().toByteArray(), + ciphertext.authenticatedData + ).getOrThrow().decrypt(key) shouldNot succeed + ciphertext.algorithm.sealedBoxFrom( + ciphertext.nonce, + ciphertext.encryptedData, + ciphertext.authTag.asList().shuffled().toByteArray(), + ciphertext.authenticatedData + ).getOrThrow().decrypt(it.Custom(ciphertext.authTag.size.bytes) { _, _, _ -> + "Szombathely".encodeToByteArray() + }.let { + SymmetricKey.WithDedicatedMac.RequiringNonce( + it, + key.encryptionKey, + key.macKey + ) + }) shouldNot succeed + } + + } + } + } + } + } + } + + "ECB + WRAP" - { + withData( + + SymmetricEncryptionAlgorithm.AES_128.ECB, + SymmetricEncryptionAlgorithm.AES_192.ECB, + SymmetricEncryptionAlgorithm.AES_256.ECB, + SymmetricEncryptionAlgorithm.AES_128.WRAP.RFC3394, + SymmetricEncryptionAlgorithm.AES_192.WRAP.RFC3394, + SymmetricEncryptionAlgorithm.AES_256.WRAP.RFC3394, + + ) { alg -> + + withData( + nameFn = { "data: ${it.size} bytes" }, + Random.nextBytes(19), + Random.nextBytes(1), + Random.nextBytes(1234), + Random.nextBytes(54), + Random.nextBytes(16), + Random.nextBytes(32), + Random.nextBytes(256), + Random.nextBytes(512), + Random.nextBytes(1024), + Random.nextBytes(8), + Random.nextBytes(16), + Random.nextBytes(48), + Random.nextBytes(24), + Random.nextBytes(72), + ) { data -> + + val secretKey = alg.randomKey() + + //CBC + if (alg !is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394) { + + val own = secretKey.encrypt(data).getOrThrow() + + + own.decrypt(secretKey).getOrThrow() shouldBe data + + //we might get lucky here + own.decrypt(own.algorithm.randomKey()).onSuccess { + it shouldNotBe data + } + + alg.sealedBoxFrom(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed + } else { + + + val shouldSucceed = (data.size >= 16) && (data.size % 8 == 0) + val trial = secretKey.encrypt(data) + + if (shouldSucceed) + trial should succeed + else trial shouldNot succeed + + + + + if (shouldSucceed) { + val own = trial.getOrThrow() + + + own.decrypt(secretKey).getOrThrow() shouldBe data + + //we might get lucky here + own.decrypt(own.algorithm.randomKey()).onSuccess { + it shouldNotBe data + } + + alg.sealedBoxFrom(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed + } + } + } + } + } + + + val allAlgorithms = listOf( + SymmetricEncryptionAlgorithm.AES_128.ECB, + SymmetricEncryptionAlgorithm.AES_192.ECB, + SymmetricEncryptionAlgorithm.AES_256.ECB, + SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_192.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_256.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.AES_192.GCM, + SymmetricEncryptionAlgorithm.AES_256.GCM, + + SymmetricEncryptionAlgorithm.AES_128.WRAP.RFC3394, + SymmetricEncryptionAlgorithm.AES_192.WRAP.RFC3394, + SymmetricEncryptionAlgorithm.AES_256.WRAP.RFC3394, + + SymmetricEncryptionAlgorithm.ChaCha20Poly1305, + ) + + + "Equality" - { + withData(allAlgorithms) { alg -> + withData( + nameFn = { "data: ${it.size} bytes" }, + //multiples of 8, so AES-KW works + Random.nextBytes(192), + Random.nextBytes(24), + Random.nextBytes(32), + Random.nextBytes(56), + Random.nextBytes(64), + Random.nextBytes(256), + Random.nextBytes(1024), + Random.nextBytes(4096), + ) { plaintext -> + alg.randomKey().also { key -> + when (alg.hasDedicatedMac()) { + true -> { + key shouldBe alg.keyFrom( + (key as SymmetricKey.WithDedicatedMac).encryptionKey, + (key as SymmetricKey.WithDedicatedMac<*>).macKey + ).getOrThrow() + + key shouldNotBe alg.keyFrom( + key.encryptionKey, + (key as SymmetricKey.WithDedicatedMac<*>).macKey.asList().shuffled() + .toByteArray() + ).getOrThrow() + key shouldNotBe alg.keyFrom( + key.encryptionKey.asList().shuffled().toByteArray(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey + ).getOrThrow() + key shouldNotBe alg.keyFrom( + key.encryptionKey.asList().shuffled().toByteArray(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.asList().shuffled() + .toByteArray() + ).getOrThrow() + } + + false -> key shouldBe alg.keyFrom((key as SymmetricKey.Integrated).secretKey).getOrThrow() + } + } + + + if (alg.isAuthenticated()) { + val aad = plaintext.asList().shuffled().toByteArray() + if (!alg.requiresNonce()) alg.randomKey().let { key -> + key.encrypt(plaintext, aad).getOrThrow() shouldBe key.encrypt(plaintext, aad).getOrThrow() + key.encrypt(plaintext, plaintext).getOrThrow() shouldNotBe key.encrypt(plaintext, aad) + .getOrThrow() + } + else alg.randomKey().let { key -> + + key.encrypt(plaintext, aad).getOrThrow() shouldNotBe key.encrypt(plaintext, aad).getOrThrow() + + val nonce = alg.randomNonce() + key.andPredefinedNonce(nonce).getOrThrow() + .encrypt(plaintext).getOrThrow() shouldBe key.andPredefinedNonce(nonce).getOrThrow() + .encrypt(plaintext).getOrThrow() + + key.andPredefinedNonce(nonce).getOrThrow() + .encrypt(plaintext).getOrThrow() shouldNotBe key.andPredefinedNonce(alg.randomNonce()) + .getOrThrow() + .encrypt(plaintext).getOrThrow() + } + } else { + if (!alg.requiresNonce()) alg.randomKey().also { key -> + key.encrypt(plaintext).getOrThrow() shouldBe key.encrypt(plaintext).getOrThrow() + } else alg.randomKey().let { key -> + + key.encrypt(plaintext).getOrThrow() shouldNotBe key.encrypt(plaintext).getOrThrow() + + val nonce = alg.randomNonce() + key.andPredefinedNonce(nonce).getOrThrow() + .encrypt(plaintext).getOrThrow() shouldBe key.andPredefinedNonce(nonce).getOrThrow() + .encrypt(plaintext).getOrThrow() + + key.andPredefinedNonce(nonce).getOrThrow() + .encrypt(plaintext).getOrThrow() shouldNotBe key.andPredefinedNonce(alg.randomNonce()) + .getOrThrow() + .encrypt(plaintext).getOrThrow() + } + } + + withData(allAlgorithms.filterNot { it /*check for same instance*/ === alg }) { wrongAlg -> + alg shouldNotBe wrongAlg + alg.randomKey() shouldNotBe wrongAlg.randomKey() + if (alg.keySize == wrongAlg.keySize) { + if (alg.isAuthenticated() && wrongAlg.isAuthenticated()) { + if (alg.hasDedicatedMac() && wrongAlg.hasDedicatedMac()) { + alg.randomKey().let { key -> + wrongAlg.keyFrom( + key.encryptionKey, + key.macKey /*size will not match, but it will get us a valid key*/ + ).getOrThrow() shouldNotBe key + } + } else if (!wrongAlg.hasDedicatedMac() && !alg.hasDedicatedMac()) { + alg.randomKey().let { key -> + wrongAlg.keyFrom( + key.secretKey, + ).getOrThrow() shouldNotBe key + } + } + } + } + + val box = when (alg.requiresNonce()) { + true -> when (alg.isAuthenticated()) { + true -> alg.sealedBoxFrom( + alg.randomNonce(), + plaintext, + Random.nextBytes(alg.authTagLength.bytes.toInt()), + plaintext + ) + + false -> alg.sealedBoxFrom(alg.randomNonce(), plaintext) + } + + false -> when (alg.isAuthenticated()) { + true -> alg.sealedBoxFrom( + plaintext, + Random.nextBytes(alg.authTagLength.bytes.toInt()), + plaintext + ) + + false -> alg.sealedBoxFrom(plaintext) + } + }.getOrThrow() + + val box2 = when (wrongAlg.requiresNonce()) { + true -> when (wrongAlg.isAuthenticated()) { + true -> wrongAlg.sealedBoxFrom( + if (box.hasNonce() && box.nonce.size == wrongAlg.nonceLength.bytes.toInt()) box.nonce else + wrongAlg.randomNonce(), + plaintext, + if (box.isAuthenticated() && box.authTag.size == wrongAlg.authTagLength.bytes.toInt()) box.authTag else + Random.nextBytes(wrongAlg.authTagLength.bytes.toInt()), + plaintext + ) + + false -> wrongAlg.sealedBoxFrom( + if (box.hasNonce() && box.nonce.size == wrongAlg.nonceLength.bytes.toInt()) box.nonce else + wrongAlg.randomNonce(), plaintext + ) + } + + false -> when (wrongAlg.isAuthenticated()) { + true -> wrongAlg.sealedBoxFrom( + plaintext, + if (box.isAuthenticated() && box.authTag.size == wrongAlg.authTagLength.bytes.toInt()) box.authTag else + Random.nextBytes(wrongAlg.authTagLength.bytes.toInt()), + plaintext + ) + + false -> wrongAlg.sealedBoxFrom(plaintext) + } + }.getOrThrow() + + box shouldNotBe box2 + } + } + } + } + + + "Edge Cases " - { + "all good" - { + withData( + + SymmetricEncryptionAlgorithm.AES_128.ECB, + SymmetricEncryptionAlgorithm.AES_192.ECB, + SymmetricEncryptionAlgorithm.AES_256.ECB, + SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_192.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_256.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_256, + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.AES_192.GCM, + SymmetricEncryptionAlgorithm.AES_256.GCM, + /*NO WRAP, because it has constraints on input size*/ + SymmetricEncryptionAlgorithm.ChaCha20Poly1305, + + ) { alg -> + + withData(0, 1, 4096) { sz -> + val data = Random.nextBytes(sz) + val key = alg.randomKey() + key.encrypt(data).getOrThrow().decrypt(key).getOrThrow() shouldBe data + } + } + } + + "algorithm mismatch" - { + withData(allAlgorithms) { alg -> + withData(allAlgorithms.filterNot { it == alg }) { wrongAlg -> + val encrypted = alg.randomKey().encrypt(Random.nextBytes(64)/*works with wrapping*/).getOrThrow() + val wrongKey = wrongAlg.randomKey() + + encrypted.decrypt(wrongKey) shouldNot succeed + + } + } + } + "illegal key sizes" - { + withData(allAlgorithms) { alg -> + val wrongSized = mutableListOf() + while (wrongSized.size < 100) { + val wrong = Random.nextUInt(until = 1025u).toInt() + if (wrong != alg.keySize.bytes.toInt()) + wrongSized += wrong + } + withData(wrongSized) { sz -> + when (alg.hasDedicatedMac()) { + true -> { + alg.keyFrom( + Random.nextBytes(sz), + alg.randomKey().encryptionKey /*mac key should not trigger, as it is unconstrained*/ + ) + } + + false -> { + alg.keyFrom(Random.nextBytes(sz)) + } + } shouldNot succeed + } + } + } + + "illegal nonce sizes" - { + withData(allAlgorithms.filter { it.requiresNonce() }) { alg -> + alg as SymmetricEncryptionAlgorithm.RequiringNonce<*, *> + val wrongSized = mutableListOf() + while (wrongSized.size < 100) { + val wrong = Random.nextUInt(until = 1025u).toInt() + if (wrong != alg.nonceTrait.length.bytes.toInt()) + wrongSized += wrong + } + withData(wrongSized) { sz -> + alg.randomKey().andPredefinedNonce(Random.nextBytes(sz)) shouldNot succeed + } + + } + } + } + + +}) + diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt index 3d34885d0..75b81da6e 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt @@ -2,7 +2,7 @@ package at.asitplus.signum.supreme.hazmat import at.asitplus.signum.internals.OwnedCFValue -import at.asitplus.signum.supreme.HazardousMaterials +import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.supreme.os.IosSigner import at.asitplus.signum.supreme.os.IosSignerSigningConfiguration import at.asitplus.signum.supreme.sign.EphemeralKey diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt index 720eea3b9..a52afedc0 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt @@ -4,6 +4,8 @@ package at.asitplus.signum.supreme.os import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.* +import at.asitplus.signum.indispensable.RSAPadding +import at.asitplus.signum.indispensable.SignatureAlgorithm import at.asitplus.signum.internals.* import at.asitplus.signum.supreme.* import at.asitplus.signum.supreme.dsl.* diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 528335c94..b52756985 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -4,6 +4,7 @@ package at.asitplus.signum.supreme.sign import at.asitplus.catching import at.asitplus.signum.indispensable.* +import at.asitplus.signum.indispensable.SignatureAlgorithm import at.asitplus.signum.internals.* import at.asitplus.signum.supreme.* import kotlinx.cinterop.* diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt new file mode 100644 index 000000000..c44350d39 --- /dev/null +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt @@ -0,0 +1,180 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.ImplementationError +import at.asitplus.signum.indispensable.symmetric.BlockCipher +import at.asitplus.signum.indispensable.symmetric.KeyType +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES +import at.asitplus.signum.indispensable.symmetric.sealedBoxFrom +import at.asitplus.signum.internals.swiftcall +import at.asitplus.signum.internals.toByteArray +import at.asitplus.signum.internals.toNSData +import at.asitplus.signum.supreme.symmetric.ios.GCM +import kotlinx.cinterop.* +import platform.CoreCrypto.* + +private fun BlockCipher<*, *, *>.addPKCS7Padding(plain: ByteArray): ByteArray { + val blockBytes = blockSize.bytes.toInt() + val diff = blockBytes - (plain.size % blockBytes) + return if (diff == 0) + plain + ByteArray(blockBytes) { blockBytes.toByte() } + else plain + ByteArray(diff) { diff.toByte() } +} + + +private fun BlockCipher<*, *, *>.removePKCS7Padding(plainWithPadding: ByteArray): ByteArray { + val paddingBytes = plainWithPadding.last().toInt() + require(paddingBytes > 0) { "Illegal padding: $paddingBytes" } + require(plainWithPadding.takeLast(paddingBytes).all { it.toInt() == paddingBytes }) { "Padding not consistent" } + require(plainWithPadding.size - paddingBytes >= 0) { "Too much padding: data ${plainWithPadding.joinToString()}" } + return plainWithPadding.sliceArray(0.., + data: ByteArray, + key: ByteArray, + nonce: ByteArray?, + aad: ByteArray? + ) = when (alg) { + is AES.CBC.Unauthenticated -> { + val bytes = cbcEcbCrypt(alg, encrypt = true, key, nonce, data, pad = true) + alg.sealedBoxFrom(nonce!!, bytes).getOrThrow() + } + + is AES.ECB -> { + val bytes = cbcEcbCrypt(alg, encrypt = true, key, nonce, data, pad = true) + alg.sealedBoxFrom(bytes).getOrThrow() + } + + is AES.WRAP.RFC3394 -> { + val bytes = cbcEcbCrypt(alg, encrypt = true, key, nonce, data, pad = false) + alg.sealedBoxFrom(bytes).getOrThrow() + } + + is AES.GCM -> { + val ciphertext = GCM.encrypt(data.toNSData(), key.toNSData(), nonce?.toNSData(), aad?.toNSData()) + if (ciphertext == null) throw IllegalStateException("Error from swift code!") + alg.sealedBoxFrom( + ciphertext.iv().toByteArray(), + ciphertext.ciphertext().toByteArray(), + ciphertext.authTag().toByteArray(), + aad + ).getOrThrow() + } + + else -> TODO("ALGORITHM UNSUPPORTED") + } + + internal fun gcmDecrypt( + encryptedData: ByteArray, + secretKey: ByteArray, + nonce: ByteArray, + authTag: ByteArray, + authenticatedData: ByteArray? + ): ByteArray = swiftcall { + @OptIn(ExperimentalForeignApi::class) + GCM.decrypt( + encryptedData.toNSData(), + secretKey.toNSData(), + nonce.toNSData(), + authTag.toNSData(), + authenticatedData?.toNSData(), + error + ) + }.toByteArray() + + @OptIn(ExperimentalForeignApi::class, HazardousMaterials::class) + fun cbcEcbCrypt( + algorithm: SymmetricEncryptionAlgorithm.AES<*, KeyType.Integrated, *>, + encrypt: Boolean, + secretKey: ByteArray, + nonce: ByteArray?, + data: ByteArray, + pad: Boolean + ): ByteArray { + //better safe than sorry + val keySize = when (secretKey.size) { + SymmetricEncryptionAlgorithm.AES_128.keySize.bytes.toInt() -> kCCKeySizeAES128 + SymmetricEncryptionAlgorithm.AES_192.keySize.bytes.toInt() -> kCCKeySizeAES192 + SymmetricEncryptionAlgorithm.AES_256.keySize.bytes.toInt() -> kCCKeySizeAES256 + else -> throw ImplementationError("Illegal AES key size: ${secretKey.size}") + } + + //must be CBC + nonce?.let { nonce -> + if (nonce.size != kCCBlockSizeAES128.toInt()) + throw ImplementationError("Illegal AES nonce length: ${nonce.size}") + } + + if (Int.MAX_VALUE - kCCBlockSizeAES128.toInt() < data.size) + throw ImplementationError("Input data too large: ${data.size}") + + val bytesEncrypted = ULongArray(1) + //account for padding + val destination = ByteArray(kCCBlockSizeAES128.toInt() + data.size) + val result = destination.usePinned { output -> + secretKey.usePinned { secretKey -> + data.let { if (encrypt && pad) algorithm.addPKCS7Padding(it) else it }.usePinned { input -> + bytesEncrypted.usePinned { bytesEncrypted -> + when (algorithm) { + is AES.CBC.Unauthenticated, is AES.ECB -> { + CCCrypt( + (if (encrypt) kCCEncrypt else kCCDecrypt), + (kCCAlgorithmAES), + algorithm.iosOptions, + secretKey.addressOf(0), keySize.toULong(), + nonce?.refTo(0), + input.addressOf(0), input.get().size.toULong(), + output.addressOf(0), output.get().size.toULong(), + bytesEncrypted.addressOf(0) + ) + } + + is AES.WRAP.RFC3394 -> { + //Why Apple, why??? + bytesEncrypted.get()[0] = output.get().size.toULong() + //Why, Apple, Why are these separate operations and not parameterized as others??? + if (encrypt) @Suppress("UNCHECKED_CAST") CCSymmetricKeyWrap( + kCCWRAPAES, + CCrfc3394_iv, CCrfc3394_ivLen, + //Why, Apple, Why is it ubyte for wrap and byte for others??? + secretKey.addressOf(0) as CValuesRef>, keySize.toULong(), + input.addressOf(0) as CValuesRef>, input.get().size.toULong(), + output.addressOf(0) as CValuesRef>, bytesEncrypted.addressOf(0) + ) + else @Suppress("UNCHECKED_CAST") CCSymmetricKeyUnwrap( + kCCWRAPAES, + CCrfc3394_iv, CCrfc3394_ivLen, + //why is it ubyte for wrap and byte for others??? + secretKey.addressOf(0) as CValuesRef>, keySize.toULong(), + input.addressOf(0) as CValuesRef>, input.get().size.toULong(), + output.addressOf(0) as CValuesRef>, bytesEncrypted.addressOf(0) + ) + } + + else -> throw ImplementationError("Illegal State in AES ${if (encrypt) "encryption" else "decryption"}.") + } + } + } + } + } + return when { + (result != kCCSuccess) -> throw IllegalStateException("Invalid state returned by Core Foundation call: $result") + else -> destination.sliceArray(0...iosOptions: UInt + get() = @OptIn(HazardousMaterials::class) when (this) { + is AES.CBC.Unauthenticated, is AES.WRAP.RFC3394 -> 0u //no options (=manual padding). + is AES.ECB -> kCCOptionECBMode + else -> throw ImplementationError() + } \ No newline at end of file diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt new file mode 100644 index 000000000..a0fa4e0c0 --- /dev/null +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt @@ -0,0 +1,55 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.indispensable.symmetric.AuthCapability +import at.asitplus.signum.indispensable.symmetric.KeyType +import at.asitplus.signum.indispensable.symmetric.NonceTrait +import at.asitplus.signum.indispensable.symmetric.SealedBox +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm +import at.asitplus.signum.indispensable.symmetric.sealedBoxFrom +import at.asitplus.signum.internals.swiftcall +import at.asitplus.signum.internals.toByteArray +import at.asitplus.signum.internals.toNSData +import at.asitplus.signum.supreme.symmetric.ios.ChaCha +import kotlinx.cinterop.ExperimentalForeignApi + + +internal object ChaChaIOS { + @OptIn(ExperimentalForeignApi::class) + fun encrypt( + data: ByteArray, + key: ByteArray, + nonce: ByteArray, + aad: ByteArray? + ): SealedBox { + val ciphertext = ChaCha.encrypt(data.toNSData(), key.toNSData(), nonce.toNSData(), aad?.toNSData()) + if (ciphertext == null) throw UnsupportedOperationException("Error from swift code!") + @Suppress("UNCHECKED_CAST") + return SymmetricEncryptionAlgorithm.ChaCha20Poly1305.sealedBoxFrom( + ciphertext.iv().toByteArray(), + ciphertext.ciphertext().toByteArray(), + ciphertext.authTag().toByteArray(), + aad + ).getOrThrow() as SealedBox + + } + + internal fun decrypt( + encryptedData: ByteArray, + secretKey: ByteArray, + nonce: ByteArray, + authTag: ByteArray, + authenticatedData: ByteArray? + ): ByteArray = swiftcall { + @OptIn(ExperimentalForeignApi::class) + ChaCha.decrypt( + encryptedData.toNSData(), + secretKey.toNSData(), + nonce.toNSData(), + authTag.toNSData(), + authenticatedData?.toNSData(), + error + ) + }.toByteArray() + +} + diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt new file mode 100644 index 000000000..06dafc025 --- /dev/null +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt @@ -0,0 +1,80 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.ImplementationError +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES +import kotlinx.cinterop.ExperimentalForeignApi + + +internal actual fun , I : NonceTrait, K : KeyType> initCipher( + algorithm: SymmetricEncryptionAlgorithm, + key: ByteArray, + nonce: ByteArray?, + aad: ByteArray? +): CipherParam { + + @OptIn(HazardousMaterials::class) + val nonce = if (algorithm.requiresNonce()) nonce ?: algorithm.randomNonce() else null + + @Suppress("UNCHECKED_CAST") + return CipherParam, KeyType>( + algorithm, key, nonce, aad + ) as CipherParam +} + +@OptIn(ExperimentalForeignApi::class) +internal actual fun , I : NonceTrait, K : KeyType> CipherParam<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { + @Suppress("UNCHECKED_CAST") (this as CipherParam) + + @Suppress("UNCHECKED_CAST") + return when (alg) { + is AES<*, *, *> -> AESIOS.encrypt(alg, data, platformData, nonce, aad) + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.encrypt(data, platformData, nonce!!, aad) + else -> TODO("ALGORITHM $alg UNSUPPORTED") + } as SealedBox +} + + +@OptIn(ExperimentalForeignApi::class) +internal actual fun SealedBox.doDecryptAEAD( + secretKey: ByteArray +): ByteArray { + if (algorithm.nonceTrait !is NonceTrait.Required) TODO("ALGORITHM $algorithm UNSUPPORTED") + this as SealedBox.WithNonce + return when (algorithm) { + is AES<*, *, *> -> AESIOS.gcmDecrypt(encryptedData, secretKey, nonce, authTag, authenticatedData) + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.decrypt( + encryptedData, + secretKey, + nonce, + authTag, + authenticatedData + ) + + else -> TODO("ALGORITHM UNSUPPORTED") + } +} + +@OptIn(ExperimentalForeignApi::class, HazardousMaterials::class) +internal actual fun SealedBox.doDecrypt( + secretKey: ByteArray +): ByteArray { + require(algorithm is AES<*, *, *>) { "Only AES is supported" } + + return AESIOS.cbcEcbCrypt( + algorithm as AES<*, KeyType.Integrated, *>, + encrypt = false, + secretKey, + if (this is SealedBox.WithNonce) nonce else null, + encryptedData, + pad = when (algorithm) { + is AES.CBC.Unauthenticated, is AES.ECB -> true + is AES.WRAP.RFC3394 -> false + else -> throw ImplementationError("Illegal AES encryption state.") + + } + ) + +} diff --git a/supreme/src/iosMain/swift/AEAD.swift b/supreme/src/iosMain/swift/AEAD.swift new file mode 100644 index 000000000..0cc8c7c5e --- /dev/null +++ b/supreme/src/iosMain/swift/AEAD.swift @@ -0,0 +1,110 @@ +import CommonCrypto +import CryptoKit +import Foundation + +@objc public class ChaCha: NSObject { + + @objc public class func encrypt(_ plain: NSData, key: NSData, iv: NSData?, aad: NSData?) + -> AuthenticatedCiphertext? + { + let data = (key as Data) + + guard let nonce = if iv != nil { try! ChaChaPoly.Nonce(data: iv!) } else { ChaChaPoly.Nonce() } + else { return nil } + + let symmKey = SymmetricKey(data: data) + + let sealedBox = + if aad != nil { + try! ChaChaPoly.seal(plain, using: symmKey, nonce: nonce, authenticating: aad!) + } else { try! ChaChaPoly.seal(plain, using: symmKey, nonce: nonce) } + + return AuthenticatedCiphertext( + ciphertext: sealedBox.ciphertext, + authTag: sealedBox.tag, + iv: sealedBox.nonce.withUnsafeBytes { Data($0) }) + } + + @objc public class func decrypt( + _ ciphertext: NSData, key: NSData, iv: NSData, tag: NSData, aad: NSData? + ) throws -> Data { + let data = (key as Data) + let nonce = try! ChaChaPoly.Nonce(data: iv) + + let symmKey = SymmetricKey(data: data) + + let sealedBox = try ChaChaPoly.SealedBox( + nonce: nonce, ciphertext: ciphertext as Data, tag: tag as Data) + + let decrypted = + if aad != nil { + try ChaChaPoly.open(sealedBox, using: symmKey, authenticating: aad!) + } else { + + try ChaChaPoly.open(sealedBox, using: symmKey) + } + return decrypted + + } +} + +@objc public class GCM: NSObject { + + @objc public class func encrypt(_ plain: NSData, key: NSData, iv: NSData?, aad: NSData?) + -> AuthenticatedCiphertext? + { + let data = (key as Data) + + guard let nonce = if iv != nil { try! AES.GCM.Nonce(data: iv!) } else { AES.GCM.Nonce() } + else { return nil } + + let symmKey = SymmetricKey(data: data) + + let sealedBox = + if aad != nil { + try! AES.GCM.seal(plain, using: symmKey, nonce: nonce, authenticating: aad!) + } else { try! AES.GCM.seal(plain, using: symmKey, nonce: nonce) } + + return AuthenticatedCiphertext( + ciphertext: sealedBox.ciphertext, + authTag: sealedBox.tag, + iv: sealedBox.nonce.withUnsafeBytes { Data($0) }) + } + + @objc public class func decrypt( + _ ciphertext: NSData, key: NSData, iv: NSData, tag: NSData, aad: NSData? + ) throws -> Data { + let data = (key as Data) + let nonce = try! AES.GCM.Nonce(data: iv) + + let symmKey = SymmetricKey(data: data) + + let sealedBox = try AES.GCM.SealedBox( + nonce: nonce, ciphertext: ciphertext as Data, tag: tag as Data) + + let decrypted = + if aad != nil { + try AES.GCM.open(sealedBox, using: symmKey, authenticating: aad!) + } else { + + try AES.GCM.open(sealedBox, using: symmKey) + } + return decrypted + + } +} + +@objc public class AuthenticatedCiphertext: NSObject { + @objc public let ciphertext: Data + + @objc public let authTag: Data + + @objc public let iv: Data + + init(ciphertext: Data, authTag: Data, iv: Data) { + self.ciphertext = ciphertext + self.authTag = authTag + self.iv = iv + } + +} \ No newline at end of file diff --git a/supreme/src/iosTest/kotlin/Test.kt b/supreme/src/iosTest/kotlin/Test.kt index 1d201ad7f..9ce3c6234 100644 --- a/supreme/src/iosTest/kotlin/Test.kt +++ b/supreme/src/iosTest/kotlin/Test.kt @@ -1,11 +1,11 @@ import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldNotBe -import kotlinx.cinterop.ExperimentalForeignApi -@OptIn(ExperimentalForeignApi::class) +@ExperimentalStdlibApi class ProviderTest : FreeSpec({ "This dummy test" { "is just making sure" shouldNotBe "that iOS tests are indeed running" } + }) \ No newline at end of file diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt index ba944973b..fd9fd2af5 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/hazmat/InternalsAccessors.kt @@ -1,6 +1,6 @@ package at.asitplus.signum.supreme.hazmat -import at.asitplus.signum.supreme.HazardousMaterials +import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.supreme.sign.EphemeralKey import at.asitplus.signum.supreme.sign.EphemeralKeyBase import at.asitplus.signum.supreme.sign.EphemeralSigner diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt index 5a7ed044c..ccf5f016b 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt @@ -1,7 +1,7 @@ package at.asitplus.signum.supreme.os import at.asitplus.signum.indispensable.* -import at.asitplus.signum.supreme.UnsupportedCryptoException +import at.asitplus.signum.indispensable.SignatureAlgorithm import at.asitplus.signum.supreme.sign.* import at.asitplus.signum.supreme.signature import at.asitplus.signum.supreme.succeed diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/sign/VerifierTests.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/sign/VerifierTests.kt index 8a9cd00dc..9327d6bdc 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/sign/VerifierTests.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/sign/VerifierTests.kt @@ -10,12 +10,6 @@ import at.asitplus.signum.indispensable.fromJcaPublicKey import at.asitplus.signum.indispensable.jcaAlgorithmComponent import at.asitplus.signum.indispensable.jcaName import at.asitplus.signum.supreme.succeed -import at.asitplus.signum.supreme.sign.KotlinECDSAVerifier -import at.asitplus.signum.supreme.sign.PlatformECDSAVerifier -import at.asitplus.signum.supreme.sign.SignatureInputFormat -import at.asitplus.signum.supreme.sign.Verifier -import at.asitplus.signum.supreme.sign.of -import at.asitplus.signum.supreme.sign.verify import io.kotest.core.spec.style.FreeSpec import io.kotest.datatest.withData import io.kotest.matchers.should diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt new file mode 100644 index 000000000..5cada8a98 --- /dev/null +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -0,0 +1,298 @@ +import at.asitplus.catching +import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.supreme.succeed +import at.asitplus.signum.supreme.symmetric.decrypt +import at.asitplus.signum.supreme.symmetric.discouraged.andPredefinedNonce +import at.asitplus.signum.supreme.symmetric.discouraged.encrypt +import at.asitplus.signum.supreme.symmetric.encrypt +import at.asitplus.signum.supreme.symmetric.randomKey +import at.asitplus.signum.supreme.symmetric.randomNonce +import io.kotest.core.spec.style.FreeSpec +import io.kotest.datatest.withData +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.should +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNot +import io.kotest.matchers.shouldNotBe +import javax.crypto.Cipher +import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec +import kotlin.random.Random + +@OptIn(HazardousMaterials::class, ExperimentalStdlibApi::class) +class JvmSymmetricTest : FreeSpec({ + + "Against JCA" - { + "AES" - { + withData( + SymmetricEncryptionAlgorithm.AES_128.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_192.CBC.PLAIN, + SymmetricEncryptionAlgorithm.AES_256.CBC.PLAIN, + + //JVM knows no AES-CBC-HMAC + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.AES_192.GCM, + SymmetricEncryptionAlgorithm.AES_256.GCM, + + ) { alg -> + withData( + nameFn = { "iv: ${it.size} bytes" }, alg.randomNonce(), alg.randomNonce() + ) { iv -> + withData( + nameFn = { "aad: ${it?.size} bytes" }, alg.randomNonce(), alg.randomNonce(), + + Random.nextBytes(19), null + ) { aad -> + withData( + nameFn = { "data: ${it.size} bytes" }, alg.randomNonce(), alg.randomNonce(), + Random.nextBytes(19), + Random.nextBytes(1), + Random.nextBytes(1234), + Random.nextBytes(54), + Random.nextBytes(16), + Random.nextBytes(32), + Random.nextBytes(256), + ) { data -> + + + val jcaCipher = + Cipher.getInstance(if (alg.authCapability is AuthCapability.Unauthenticated) "AES/CBC/PKCS5PADDING" else "AES/GCM/NoPadding") + + if (alg is SymmetricEncryptionAlgorithm.AES.GCM) { + val secretKey = alg.randomKey() + //GCM need to cast key, because alg is AES with no mode of ops, since we mix CBC and GCM in the test input + val own = + (secretKey as SymmetricKey, NonceTrait.Required, KeyType.Integrated>).andPredefinedNonce( + iv + ).getOrThrow().encrypt(data = data, aad) + .getOrThrow() + own.isAuthenticated() shouldBe true + jcaCipher.init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "AES"), + GCMParameterSpec( + alg.authCapability.tagLength.bits.toInt(), + own.nonce/*use our own auto-generated IV*/ + ) + ) + if (aad != null) jcaCipher.updateAAD(aad) + + val encrypted = jcaCipher.doFinal(data) + + (own.encryptedData + own.authTag) shouldBe encrypted + + jcaCipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "AES"), + GCMParameterSpec( + alg.authCapability.tagLength.bits.toInt(), + own.nonce/*use our own auto-generated IV*/ + ) + ) + if (aad != null) jcaCipher.updateAAD(aad) + + + + own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) + + val wrongKey = own.algorithm.randomKey() + own.decrypt(wrongKey) shouldNot succeed + + own.algorithm.sealedBoxFrom( + own.algorithm.randomNonce(), + own.encryptedData, + own.authTag, + own.authenticatedData + ).getOrThrow().decrypt(secretKey) shouldNot succeed + + + } else { + alg as SymmetricEncryptionAlgorithm.AES.CBC.Unauthenticated + val secretKey = alg.randomKey() + //CBC + val own = secretKey.encrypt(data).getOrThrow() + jcaCipher.init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "AES"), + IvParameterSpec(own.nonce)/*use our own auto-generated IV, if null iv was provided*/ + ) + val encrypted = jcaCipher.doFinal(data) + + own.encryptedData shouldBe encrypted + + jcaCipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "AES"), + IvParameterSpec(own.nonce)/*use our own auto-generated IV, if null iv was provided*/ + ) + own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) + + //this could succeed if we're lucky and padding works out + own.decrypt(own.algorithm.randomKey()).onSuccess { + it shouldNotBe data + } + + if (data.size < alg.blockSize.bytes.toInt()) + alg.sealedBoxFrom( + own.algorithm.randomNonce(), + own.encryptedData + ).getOrThrow().decrypt(secretKey) shouldNot succeed + + } + } + } + } + } + "ECB + WRAP" - { + withData( + + SymmetricEncryptionAlgorithm.AES_128.ECB, + SymmetricEncryptionAlgorithm.AES_192.ECB, + SymmetricEncryptionAlgorithm.AES_256.ECB, + SymmetricEncryptionAlgorithm.AES_128.WRAP.RFC3394, + SymmetricEncryptionAlgorithm.AES_192.WRAP.RFC3394, + SymmetricEncryptionAlgorithm.AES_256.WRAP.RFC3394, + + ) { alg -> + + withData( + nameFn = { "data: ${it.size} bytes" }, + Random.nextBytes(19), + Random.nextBytes(1), + Random.nextBytes(1234), + Random.nextBytes(54), + Random.nextBytes(16), + Random.nextBytes(32), + Random.nextBytes(256), + Random.nextBytes(512), + Random.nextBytes(1024), + Random.nextBytes(8), + Random.nextBytes(16), + Random.nextBytes(48), + Random.nextBytes(24), + Random.nextBytes(72), + ) { data -> + + val secretKey = alg.randomKey() + + //CBC + if (alg !is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394) { + val jcaCipher = + Cipher.getInstance("AES/ECB/PKCS5PADDING") + + val own = secretKey.encrypt(data).getOrThrow() + jcaCipher.init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "AES"), + ) + val encrypted = jcaCipher.doFinal(data) + + own.encryptedData shouldBe encrypted + + jcaCipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "AES"), + ) + own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) + + //we might get lucky here + own.decrypt(own.algorithm.randomKey()).onSuccess { + it shouldNotBe data + } + + alg.sealedBoxFrom(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed + } else { + + + val shouldSucceed = (data.size >= 16) && (data.size % 8 == 0) + val jcaCipher = + Cipher.getInstance("AESWrap") + val trial = secretKey.encrypt(data) + + if (shouldSucceed) + trial should succeed + else trial shouldNot succeed + + jcaCipher.init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "AES"), + ) + val jcaTrail = catching { + jcaCipher.doFinal(data) + } + if (shouldSucceed) + jcaTrail should succeed + else jcaTrail shouldNot succeed + + if (shouldSucceed) { + val own = trial.getOrThrow() + val encrypted = jcaTrail.getOrThrow() + own.encryptedData shouldBe encrypted + + jcaCipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "AES"), + ) + own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) + + //we might get lucky here + own.decrypt(own.algorithm.randomKey()).onSuccess { + it shouldNotBe data + } + + alg.sealedBoxFrom(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed + } + } + } + } + } + } + } + + "ChaCha20-Poly1305" - { + val alg = SymmetricEncryptionAlgorithm.ChaCha20Poly1305 + withData( + nameFn = { "iv: ${it?.size} bytes" }, alg.randomNonce(), alg.randomNonce(), null + ) { nonce -> + withData(Random.nextBytes(19), null) { aad -> + withData( + Random.nextBytes(19), + Random.nextBytes(1), + Random.nextBytes(0), + Random.nextBytes(1234), + Random.nextBytes(54), + Random.nextBytes(16), + Random.nextBytes(32), + Random.nextBytes(256), + ) { data -> + val secretKey = alg.randomKey() + val jcaCipher = Cipher.getInstance("ChaCha20-Poly1305"); + + val box = if (nonce != null) secretKey.andPredefinedNonce(nonce).getOrThrow().encrypt(data, aad) + .getOrThrow() + else secretKey.encrypt(data, aad).getOrThrow() + + + jcaCipher.init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(secretKey.secretKey, "ChaCha"), + IvParameterSpec(box.nonce) /*need to do this, otherwise we get a random nonce*/ + ) + + if (aad != null) jcaCipher.updateAAD(aad) + + val fromJCA = jcaCipher.doFinal(data) + + box.nonce.shouldNotBeNull() + box.nonce.size shouldBe alg.nonceTrait.length.bytes.toInt() + box.isAuthenticated() shouldBe true + (box.encryptedData + box.authTag) shouldBe fromJCA + box.decrypt(secretKey).getOrThrow() shouldBe data + + } + } + } + } +}) \ No newline at end of file From d5ee5d7a8b97ae162f7711cd7211694adc60e896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Wed, 12 Feb 2025 21:05:48 +0100 Subject: [PATCH 02/28] add note to changelog diff --git a/CHANGELOG.md b/CHANGELOG.md index 130aa1a4..6e4f65a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,15 @@ ## 3.0 ### NEXT -* Rename `ObjectIdentifier.parse` -> `ObjectIdentifier.decodeFromAsn1ContentBytes` in accordance with other similar functions -* Add dedicated Android targets (SDK 30 /JDK 1.8) to all modules - +* **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 @@ -20,6 +26,8 @@ * `ivLength` and `encryptionKeyLength` now return `BitLength` instead of `Int` * `text` is now properly called `identifier` * Moved `HazardousMaterials` annotation from `supreme` to `indispensable` +* Rename `ObjectIdentifier.parse` -> `ObjectIdentifier.decodeFromAsn1ContentBytes` in accordance with other similar functions +* Add dedicated Android targets (SDK 30 /JDK 1.8) to all modules ### 3.14.0 (Supreme 0.7.0) --- CHANGELOG.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 130aa1a48..6e4f65a05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,15 @@ ## 3.0 ### NEXT -* Rename `ObjectIdentifier.parse` -> `ObjectIdentifier.decodeFromAsn1ContentBytes` in accordance with other similar functions -* Add dedicated Android targets (SDK 30 /JDK 1.8) to all modules - +* **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 @@ -20,6 +26,8 @@ * `ivLength` and `encryptionKeyLength` now return `BitLength` instead of `Int` * `text` is now properly called `identifier` * Moved `HazardousMaterials` annotation from `supreme` to `indispensable` +* Rename `ObjectIdentifier.parse` -> `ObjectIdentifier.decodeFromAsn1ContentBytes` in accordance with other similar functions +* Add dedicated Android targets (SDK 30 /JDK 1.8) to all modules ### 3.14.0 (Supreme 0.7.0) From 043280a2894800aafdb55732daf964bfcfaa5350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Wed, 12 Feb 2025 23:40:50 +0100 Subject: [PATCH 03/28] Fix RSAorHMAC mess MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git a/.github/workflows/test-jvm.yml b/.github/workflows/test-jvm.yml index 8bbf7620..239a9a7b 100644 --- a/.github/workflows/test-jvm.yml +++ b/.github/workflows/test-jvm.yml @@ -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-cosef/build/test-results/**/TEST*.xml,indispensable-josef/build/test-results/**/TEST*.xml,supreme/build/test-results/**/TEST*.xml reporter: java-junit diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4f65a0..69f7e813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,14 @@ * 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 + * **Clean up `RSAorHMAC` mess** + * Introduce `DataAuthenticationAlgorithm` encompassing Signatures and MACs + * Rename `RSAorHMAC` to `RSA` and introduce dedicated `MessageAuthenticationCode` and `HMAC` classes + * This caused **some breaking shifts and cleanups** in conversion methods between indispensable, josef, cosef, and JCA types + * There is no direct mapping between `SignatureAlgorithm` and COSE/JOSE algorithms anymore, because JOSE/COSE also incorporate MAC + * Hence, for both `JwsAlgorithm` and `CoseAlgorithm`, the following properties have been introduced on the companions: + * `signatureAlgorithms` + * `messageAuthenticationCodes` * Symmetric Encryption * Supported Algorithms * AES diff --git a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt index 6bff9f32..201ab745 100644 --- a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt +++ b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size @@ -46,28 +45,26 @@ import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.ECCurve import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.nativeDigest +import at.asitplus.signum.indispensable.pki.X509Certificate import at.asitplus.signum.supreme.dsl.PREFERRED import at.asitplus.signum.supreme.sign.Signer import at.asitplus.signum.supreme.sign.makeVerifier import at.asitplus.signum.supreme.sign.verify import at.asitplus.cryptotest.theme.AppTheme 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.supreme.agree.Ephemeral import at.asitplus.signum.supreme.asKmmResult import at.asitplus.signum.supreme.os.PlatformSignerConfigurationBase +import at.asitplus.signum.supreme.os.PlatformSigningKeyConfigurationBase +import at.asitplus.signum.supreme.os.PlatformSigningProvider import at.asitplus.signum.supreme.os.SignerConfiguration import at.asitplus.signum.supreme.os.SigningProvider import at.asitplus.signum.supreme.sign.Verifier import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier -import io.ktor.util.encodeBase64 +import io.ktor.util.decodeBase64Bytes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -100,10 +97,7 @@ private class getter(private val fn: () -> T) { operator fun getValue(nothing: Nothing?, property: KProperty<*>): T = fn() } -@OptIn( - ExperimentalCoroutinesApi::class, - SecretExposure::class -) +@OptIn(ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class) @Composable internal fun App() { @@ -120,7 +114,7 @@ internal fun App() { X509SignatureAlgorithm.RS512 ) var keyAlgorithm by remember { - mutableStateOf( + mutableStateOf( X509SignatureAlgorithm.ES256 ) } @@ -143,12 +137,6 @@ internal fun App() { } val signingPossible by getter { currentKey?.isSuccess == true } var signatureData by remember { mutableStateOf?>(null) } - var ephemeralKey by remember { - mutableStateOf( - null - ) - } - var agreedKey by remember { mutableStateOf?>(null) } val signatureDataStr by getter { signatureData?.fold(onSuccess = Any::toString) { Napier.e("Signature failed", it) @@ -169,14 +157,6 @@ internal fun App() { var genTextOverride by remember { mutableStateOf(null) } val genText by getter { genTextOverride ?: "Generate" } - fun genEphemeralKey(){ - ephemeralKey = if (currentKey?.getOrNull() is CryptoPublicKey.EC) - KeyAgreementPrivateValue.ECDH.Ephemeral((currentKey!!.getOrThrow() as CryptoPublicKey.EC).curve).getOrNull() - else null - agreedKey=null - } - - Column( modifier = Modifier.fillMaxSize().verticalScroll(ScrollState(0), enabled = true) .windowInsetsPadding(WindowInsets.safeDrawing) @@ -327,16 +307,12 @@ internal fun App() { canGenerate = false genTextOverride = "Creating…" currentSigner = Provider.createSigningKey(ALIAS) { - when (val alg = keyAlgorithm.algorithm) { is SignatureAlgorithm.ECDSA -> { this@createSigningKey.ec { - curve = alg.requiredCurve - ?: ECCurve.entries.find { it.nativeDigest == alg.digest }!! + curve = alg.requiredCurve ?: + ECCurve.entries.find { it.nativeDigest == alg.digest }!! digests = setOf(alg.digest) - purposes { - keyAgreement = true - } } } @@ -351,27 +327,29 @@ internal fun App() { else -> error("unreachable") } - signer(SIGNER_CONFIG) + if (this is PlatformSigningKeyConfigurationBase) { + signer(SIGNER_CONFIG) - val timeout = runCatching { - biometricAuth.substringBefore("s").trim().toInt() - }.getOrNull() + val timeout = runCatching { + biometricAuth.substringBefore("s").trim().toInt() + }.getOrNull() - if (attestation || timeout != null) { - hardware { - backing = PREFERRED - if (attestation) { - attestation { - challenge = Random.nextBytes(16) + if (attestation || timeout != null) { + hardware { + backing = PREFERRED + if (attestation) { + attestation { + challenge = Random.nextBytes(16) + } } - } - if (timeout != null) { - protection { - this.timeout = timeout.seconds - factors { - biometry = true - deviceLock = true + if (timeout != null) { + protection { + this.timeout = timeout.seconds + factors { + biometry = true + deviceLock = true + } } } } @@ -384,8 +362,6 @@ internal fun App() { Napier.w { "Signing possible: ${currentKey?.isSuccess}" } canGenerate = true genTextOverride = null - - genEphemeralKey() } }, modifier = Modifier.padding(start = 16.dp) @@ -409,7 +385,6 @@ internal fun App() { //loadPubKey().let { Napier.w { "PubKey retrieved from native: $it" } } canGenerate = true genTextOverride = null - genEphemeralKey() } }, modifier = Modifier.padding(start = 16.dp, end = 16.dp) @@ -457,6 +432,7 @@ internal fun App() { Button( onClick = { + Napier.w { "input: $inputData" } Napier.w { "signingKey: $currentKey" } CoroutineScope(context).launch { @@ -465,61 +441,13 @@ internal fun App() { .transform { it.sign(data).asKmmResult() } .also { signatureData = it; verifyState = null } } + }, - modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), + + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), enabled = signingPossible - ) { Text("Sign") } - - if (signingPossible && (currentKey?.getOrNull() is CryptoPublicKey.EC)) { - Napier.i { "Ephemeral key: $ephemeralKey" } - Spacer(Modifier.height(8.dp)) - ephemeralKey?.let { ephemeralKey -> - OutlinedTextField(value = ephemeralKey.publicValue.toString(), - modifier = Modifier.fillMaxWidth().padding(16.dp), - minLines = 1, - textStyle = TextStyle.Default.copy(fontSize = 10.sp), - readOnly = true, - onValueChange = {}, - label = { Text("Random faux-external key for ECDH") }) - Button( - onClick = { - Napier.w { "input: $inputData" } - Napier.w { "signingKey: $currentKey" } - CoroutineScope(context).launch { - - agreedKey = - (currentSigner!!.getOrThrow() as Signer.ECDSA).keyAgreement( - ephemeralKey.publicValue - ) - Napier.i { - "ECDH Key of ext_piv + hardware-backed_pub: ${ - agreedKey?.getOrNull()?.encodeBase64() - }" - } - - Napier.i { - "ECDH Key of ext_pub + hardware-backed_priv: ${ - agreedKey?.getOrNull()?.encodeBase64() - }" - } - } - - }, - modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), - ) { Text("Perform ECDH key agreement") } - } - - if (agreedKey != null) { - OutlinedTextField(value = - "Computed from ext_pub + hardware-backed_priv:\n" + agreedKey?.map { it.encodeBase64() } - ?.getOrElse { it.message ?: it::class.simpleName ?: "" }, - modifier = Modifier.fillMaxWidth().padding(16.dp), - minLines = 1, - textStyle = TextStyle.Default.copy(fontSize = 10.sp), - readOnly = true, - onValueChange = {}, - label = { Text("Agreed-upon secret") }) - } + ) { + Text("Sign") } if (signatureData != null) { diff --git a/docs/docs/indispensable.md b/docs/docs/indispensable.md index 4376b599..d9c3aa60 100644 --- a/docs/docs/indispensable.md +++ b/docs/docs/indispensable.md @@ -104,9 +104,9 @@ The following functions provide interop functionality with platform types. ### JVM/Android * `SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm -* `SpecializedSignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm +* `X509SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm * `SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm -* `SpecializedSignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm +* `X509SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm
@@ -148,7 +148,7 @@ The following functions provide interop functionality with platform types. * `CryptoSignature.jcaSignatureBytes` returns the JCA-native encoded representation of a signature * `CryptoSignature.parseFromJca()` returns a signature object form a JCA-native encoded representation of a signature * `CryptoSignature.EC.parseFromJca()` returns an EC signature object form a JCA-native encoded representation of a signature -* `CryptoSignature.RSAorHMAC.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature +* `CryptoSignature.RSA.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature * `CryptoSignature.EC.parseFromJcaP1363` parses a signature produced by the JCA digestWithECDSAinP1363Format algorithm. * `X509Certificate.toJcaCertificate()` converts the certificate to a JCA-native `X509Certificate` * `java.security.cert.X509Certificate.toKmpCertificate()` converts a JCA-native certificate to a Signum `X509Certificate` diff --git a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt index b8f2d98b..b6837c26 100644 --- a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt +++ b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt @@ -13,7 +13,9 @@ class Asn1TagMismatchException(val expected: Asn1Element.Tag, val actual: Asn1El class Asn1StructuralException(message: String, cause: Throwable? = null) : Asn1Exception(message, cause) -class Asn1OidException(message: String, val oid: ObjectIdentifier) : Asn1Exception(message) +class Asn1OidException(message: String, val oid: ObjectIdentifier) : Asn1Exception(message){ + constructor(oid: ObjectIdentifier):this("Unknown oid $oid", oid) +} /** * Runs [block] inside [catching] and encapsulates any thrown exception in an [Asn1Exception] unless it already is one diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt index 979cb602..94bfe3f1 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt @@ -1,11 +1,10 @@ package at.asitplus.signum.indispensable.cosef +import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.ECCurve -import at.asitplus.signum.indispensable.RSAPadding -import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm +import at.asitplus.signum.indispensable.* +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -18,7 +17,7 @@ import kotlinx.serialization.encoding.Encoder * See [COSE Algorithm Registry](https://www.iana.org/assignments/cose/cose.xhtml) */ @Serializable(with = CoseAlgorithmSerializer::class) -enum class CoseAlgorithm(val value: Int): SpecializedSignatureAlgorithm { +enum class CoseAlgorithm(val value: Int) : SpecializedDataIntegrityAlgorithm { // ECDSA with SHA-size ES256(-7), @@ -43,22 +42,52 @@ enum class CoseAlgorithm(val value: Int): SpecializedSignatureAlgorithm { // RSASSA-PKCS1-v1_5 using SHA-1 RS1(-65535); - val digest: Digest get() = when(this) { - RS1 -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 + + companion object { + /** + * encompasses only signature algorithms, filtering out MACs + */ + val signatureAlgorithms = listOf( + ES256, + ES384, + ES512, + + PS256, + PS384, + PS512, + + RS256, + RS384, + RS512, + RS1 + ).map { it.algorithm as SignatureAlgorithm } + + /** + * encompasses only MACs, filtering out signature algorithms + */ + val messageAuthenticationCodes = listOf(HS256, HS384, HS512).map { it.algorithm as MessageAuthenticationCode } + + } - override val algorithm: SignatureAlgorithm get() = when (this) { - ES256 -> SignatureAlgorithm.ECDSA(Digest.SHA256, ECCurve.SECP_256_R_1) - ES384 -> SignatureAlgorithm.ECDSA(Digest.SHA384, ECCurve.SECP_384_R_1) - ES512 -> SignatureAlgorithm.ECDSA(Digest.SHA512, ECCurve.SECP_521_R_1) + val digest: Digest + get() = when (this) { + RS1 -> Digest.SHA1 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 + } - HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) - PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this. digest, RSAPadding.PSS) - RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) - } + override val algorithm: DataIntegrityAlgorithm + get() = when (this) { + ES256 -> SignatureAlgorithm.ECDSA(Digest.SHA256, ECCurve.SECP_256_R_1) + ES384 -> SignatureAlgorithm.ECDSA(Digest.SHA384, ECCurve.SECP_384_R_1) + ES512 -> SignatureAlgorithm.ECDSA(Digest.SHA512, ECCurve.SECP_521_R_1) + + HS256, HS384, HS512 -> HMAC.byDigest(digest) + PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) + RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + } } object CoseAlgorithmSerializer : KSerializer { @@ -77,8 +106,9 @@ object CoseAlgorithmSerializer : KSerializer { } + /** Tries to find a matching COSE algorithm. Note that COSE imposes curve restrictions on ECDSA based on the digest. */ -fun SignatureAlgorithm.toCoseAlgorithm() = catching { +fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catching { when (this) { is SignatureAlgorithm.ECDSA -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.ES256 @@ -86,6 +116,7 @@ fun SignatureAlgorithm.toCoseAlgorithm() = catching { Digest.SHA512 -> CoseAlgorithm.ES512 else -> throw IllegalArgumentException("ECDSA with ${this.digest} is unsupported by COSE") } + is SignatureAlgorithm.RSA -> when (this.padding) { RSAPadding.PKCS1 -> when (this.digest) { Digest.SHA1 -> CoseAlgorithm.RS1 @@ -93,6 +124,7 @@ fun SignatureAlgorithm.toCoseAlgorithm() = catching { Digest.SHA384 -> CoseAlgorithm.RS384 Digest.SHA512 -> CoseAlgorithm.RS512 } + RSAPadding.PSS -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.PS256 Digest.SHA384 -> CoseAlgorithm.PS384 @@ -100,15 +132,18 @@ fun SignatureAlgorithm.toCoseAlgorithm() = catching { else -> throw IllegalArgumentException("RSA-PSS with ${this.digest} is unsupported by COSE") } } - is SignatureAlgorithm.HMAC -> when (this.digest) { + + is HMAC -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.HS256 Digest.SHA384 -> CoseAlgorithm.HS384 Digest.SHA512 -> CoseAlgorithm.HS512 else -> throw IllegalArgumentException("HMAC with ${this.digest} is unsupported by COSE") } + + else -> throw IllegalArgumentException("UnsupportedAlgorithm $this") } } /** Tries to find a matching COSE algorithm. Note that COSE imposes curve restrictions on ECDSA based on the digest. */ -fun SpecializedSignatureAlgorithm.toCoseAlgorithm() = +fun SpecializedDataIntegrityAlgorithm.toCoseAlgorithm() = this.algorithm.toCoseAlgorithm() diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt index c7f98eeb..e571328e 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt @@ -66,7 +66,7 @@ class CoseSignedSerializer

( ): CryptoSignature.RawByteEncodable = if (protectedHeader.usesEC() ?: unprotectedHeader?.usesEC() ?: (size < 2048)) CryptoSignature.EC.fromRawBytes(this) - else CryptoSignature.RSAorHMAC(this) + else CryptoSignature.RSA(this) private fun ByteArray?.toNullablePayload(): P? = when (this) { null -> null @@ -80,7 +80,7 @@ class CoseSignedSerializer

( } else { runCatching { fromBytes() } .getOrElse { fromByteStringWrapper() } - // if it still fails, the input is not valid + // if it still fails, the input not valid } private fun ByteArray.fromBytes(): P = diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt index dc47dd22..d9d737c7 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt @@ -21,14 +21,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = bytes, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = ByteArraySerializer(), ) val bytesSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = bytes, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = ByteArraySerializer(), ) @@ -42,14 +42,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSAorHMAC(reversed), + signature = CryptoSignature.RSA(reversed), payloadSerializer = ByteArraySerializer(), ) val reversedSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSAorHMAC(reversed), + signature = CryptoSignature.RSA(reversed), payloadSerializer = ByteArraySerializer(), ) @@ -80,14 +80,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = DataClass.serializer(), ) val bytesSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = DataClass.serializer(), ) @@ -102,14 +102,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = DataClass.serializer(), ) val reversedSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = DataClass.serializer(), ) diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt index bc0c9cb1..dd25e09c 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt @@ -30,7 +30,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC(byteArrayOf()), + signature = CryptoSignature.RSA(byteArrayOf()), payloadSerializer = ByteStringWrapper.serializer(String.serializer()) ) } @@ -42,7 +42,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), + signature = CryptoSignature.RSA("bar".encodeToByteArray()), payloadSerializer = ByteArraySerializer(), ) @@ -55,7 +55,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), + signature = CryptoSignature.RSA("bar".encodeToByteArray()), payloadSerializer = DataClass.serializer(), ) @@ -68,7 +68,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple + signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple payloadSerializer = ByteArraySerializer(), ) val serialized = cose.serialize(ByteArraySerializer()) @@ -86,7 +86,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = null, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple + signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple payloadSerializer = ByteArraySerializer(), ) val serialized = cose.serialize(ByteArraySerializer()) @@ -129,7 +129,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple + signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple payloadSerializer = DataClass.serializer(), ) val serialized = cose.serialize(DataClass.serializer()) @@ -195,7 +195,7 @@ class CoseSerializationTest : FreeSpec({ val inputObject = CoseSigned.create( protectedHeader = header, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), + signature = CryptoSignature.RSA("bar".encodeToByteArray()), payloadSerializer = ByteArraySerializer(), ).prepareCoseSignatureInput(byteArrayOf()) .encodeToString(Base16()) @@ -217,7 +217,7 @@ class CoseSerializationTest : FreeSpec({ val inputObject = CoseSigned.create( protectedHeader = header, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), + signature = CryptoSignature.RSA("bar".encodeToByteArray()), payloadSerializer = DataClass.serializer(), ).prepareCoseSignatureInput(byteArrayOf()) .encodeToString(Base16()) diff --git a/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt b/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt index 02270b0d..54737142 100644 --- a/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt +++ b/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt @@ -23,9 +23,9 @@ class ConversionTests : FreeSpec({ } } "COSE -> X509 -> COSE is stable" - { - withData(CoseAlgorithm.entries) { + withData(CoseAlgorithm.signatureAlgorithms/*here we filter by using only sig alg*/) { it.toX509SignatureAlgorithm().getOrNull()?.let { x509 -> - x509.toCoseAlgorithm() shouldSucceedWith it + x509.toCoseAlgorithm().onSuccess { it.algorithm } shouldSucceedWith it.toCoseAlgorithm().getOrThrow() //here we go back to cose, after initially filtering, because cose does not discriminate between MAC and sig } } } diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt index 2f8ecd28..9ec4e38e 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt @@ -1,11 +1,10 @@ package at.asitplus.signum.indispensable.josef +import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.ECCurve -import at.asitplus.signum.indispensable.RSAPadding -import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm +import at.asitplus.signum.indispensable.* +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -19,7 +18,7 @@ import kotlinx.serialization.encoding.Encoder * Since we support only JWS algorithms (with one exception), this class is called what it's called. */ @Serializable(with = JwsAlgorithmSerializer::class) -enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, SpecializedSignatureAlgorithm { +enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, SpecializedDataIntegrityAlgorithm { ES256("ES256"), ES384("ES384"), @@ -42,28 +41,58 @@ enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, Spe */ NON_JWS_SHA1_WITH_RSA("RS1"); - val digest: Digest get() = when(this) { - NON_JWS_SHA1_WITH_RSA -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 + + companion object { + /** + * encompasses only signature algorithms, filtering out MACs + */ + val signatureAlgorithms = listOf( + ES256, + ES384, + ES512, + + PS256, + PS384, + PS512, + + RS256, + RS384, + RS512, + NON_JWS_SHA1_WITH_RSA + ).map { it.algorithm as SignatureAlgorithm } + + /** + * Encompasses only MACs, filtering our signature algorithms + */ + val messageAuthenticationCodes = listOf(HS256, HS384, HS512).map { it.algorithm as MessageAuthenticationCode } + } - override val algorithm: SignatureAlgorithm get() = when (this) { - ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, this.ecCurve!!) - HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) - PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this. digest, RSAPadding.PSS) - NON_JWS_SHA1_WITH_RSA, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) - } + val digest: Digest + get() = when (this) { + NON_JWS_SHA1_WITH_RSA -> Digest.SHA1 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 + } + + override val algorithm: DataIntegrityAlgorithm + get() = when (this) { + ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, this.ecCurve!!) + HS256, HS384, HS512 -> HMAC.byDigest(this.digest) + PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) + NON_JWS_SHA1_WITH_RSA, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + } /** The curve to create signatures on. * This is fixed by RFC7518, as opposed to X.509 where other combinations are possible. */ - val ecCurve: ECCurve? get() = when (this) { - ES256 -> ECCurve.SECP_256_R_1 - ES384 -> ECCurve.SECP_384_R_1 - ES512 -> ECCurve.SECP_521_R_1 - else -> null - } + val ecCurve: ECCurve? + get() = when (this) { + ES256 -> ECCurve.SECP_256_R_1 + ES384 -> ECCurve.SECP_384_R_1 + ES512 -> ECCurve.SECP_521_R_1 + else -> null + } } object JwsAlgorithmSerializer : KSerializer { @@ -80,8 +109,12 @@ object JwsAlgorithmSerializer : KSerializer { } } + +fun SpecializedDataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = + algorithm.toJwsAlgorithm() + /** Tries to find a matching JWS algorithm. Note that JWS imposes curve restrictions on ECDSA based on the digest. */ -fun SignatureAlgorithm.toJwsAlgorithm() = catching { +fun DataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = catching { when (this) { is SignatureAlgorithm.ECDSA -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.ES256 @@ -89,6 +122,7 @@ fun SignatureAlgorithm.toJwsAlgorithm() = catching { Digest.SHA512 -> JwsAlgorithm.ES512 else -> throw IllegalArgumentException("ECDSA with ${this.digest} is unsupported by JWS") } + is SignatureAlgorithm.RSA -> when (this.padding) { RSAPadding.PKCS1 -> when (this.digest) { Digest.SHA1 -> JwsAlgorithm.NON_JWS_SHA1_WITH_RSA @@ -96,24 +130,26 @@ fun SignatureAlgorithm.toJwsAlgorithm() = catching { Digest.SHA384 -> JwsAlgorithm.RS384 Digest.SHA512 -> JwsAlgorithm.RS512 } + RSAPadding.PSS -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.PS256 Digest.SHA384 -> JwsAlgorithm.PS384 Digest.SHA512 -> JwsAlgorithm.PS512 else -> throw IllegalArgumentException("RSA-PSS with ${this.digest} is unsupported by JWS") } + } - is SignatureAlgorithm.HMAC -> when (this.digest) { + + is HMAC -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.HS256 Digest.SHA384 -> JwsAlgorithm.HS384 Digest.SHA512 -> JwsAlgorithm.HS512 else -> throw IllegalArgumentException("HMAC with ${this.digest} is unsupported by JWS") } + + else -> throw IllegalArgumentException("UnsupportedAlgorithm $this") + } } -/** Tries to find a matching JWS algorithm. Note that JWS imposes curve restrictions on ECDSA based on the digest. */ -fun SpecializedSignatureAlgorithm.toJwsAlgorithm() = - this.algorithm.toJwsAlgorithm() - diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt index 0bc1838e..6c31b6e5 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt @@ -74,7 +74,7 @@ data class JwsSigned( val payload = inputParts[1] val signature = with(inputParts[2]) { when (val curve = header.algorithm.ecCurve) { - null -> CryptoSignature.RSAorHMAC(this) + null -> CryptoSignature.RSA(this) else -> CryptoSignature.EC.fromRawBytes(curve, this) } } diff --git a/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt b/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt index 69b0c60f..d8f2610c 100644 --- a/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt +++ b/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt @@ -17,9 +17,9 @@ class ConversionTest : FreeSpec({ } } "JWS -> X509 -> JWS is stable" - { - withData(JwsAlgorithm.entries) { + withData(JwsAlgorithm.signatureAlgorithms /*here we filter s.t. only sig alg are fed into the test*/) { it.toX509SignatureAlgorithm().getOrNull()?.let { x509 -> - x509.toJwsAlgorithm() shouldSucceedWith it + x509.toJwsAlgorithm() shouldSucceedWith it.toJwsAlgorithm().getOrThrow() /* and here we convert the sig algs back to JWS alg*/ } } } diff --git a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt index 90e17c91..273e91aa 100644 --- a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt +++ b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt @@ -4,6 +4,7 @@ import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.asn1.toAsn1Integer import at.asitplus.signum.indispensable.asn1.toJavaBigInteger +import at.asitplus.signum.indispensable.mac.HMAC import at.asitplus.signum.internals.isAndroid import at.asitplus.signum.indispensable.pki.X509Certificate import com.ionspin.kotlin.bignum.integer.base63.toJavaBigInteger @@ -186,7 +187,7 @@ fun CryptoPublicKey.Companion.fromJcaPublicKey(publicKey: PublicKey): KmmResult< val CryptoSignature.jcaSignatureBytes: ByteArray get() = when (this) { is CryptoSignature.EC -> encodeToDer() - is CryptoSignature.RSAorHMAC -> rawByteArray + is CryptoSignature.RSA -> rawByteArray } /** @@ -199,7 +200,7 @@ fun CryptoSignature.Companion.parseFromJca( if (algorithm is SignatureAlgorithm.ECDSA) CryptoSignature.EC.parseFromJca(input) else - CryptoSignature.RSAorHMAC.parseFromJca(input) + CryptoSignature.RSA.parseFromJca(input) fun CryptoSignature.Companion.parseFromJca( input: ByteArray, @@ -218,8 +219,8 @@ fun CryptoSignature.EC.Companion.parseFromJca(input: ByteArray) = fun CryptoSignature.EC.Companion.parseFromJcaP1363(input: ByteArray) = CryptoSignature.EC.fromRawBytes(input) -fun CryptoSignature.RSAorHMAC.Companion.parseFromJca(input: ByteArray) = - CryptoSignature.RSAorHMAC(input) +fun CryptoSignature.RSA.Companion.parseFromJca(input: ByteArray) = + CryptoSignature.RSA(input) /** * Converts this [X509Certificate] to a [java.security.cert.X509Certificate]. diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt index d8f59e9d..b5033a80 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt @@ -217,7 +217,7 @@ sealed interface CryptoSignature : Asn1Encodable { } - class RSAorHMAC private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { + class RSA private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { constructor(rawBytes: ByteArray) : this(rawBytes, null) constructor(x509Element: Asn1Primitive) : this(null, x509Element) @@ -241,16 +241,16 @@ sealed interface CryptoSignature : Asn1Encodable { if (this === other) return true if (other == null || this::class != other::class) return false - other as RSAorHMAC + other as RSA return signature == other.signature } - companion object : Asn1Decodable { + companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun doDecode(src: Asn1Element): RSAorHMAC { + override fun doDecode(src: Asn1Element): RSA { src as Asn1Primitive - return RSAorHMAC(src) + return RSA(src) } } } @@ -259,7 +259,7 @@ sealed interface CryptoSignature : Asn1Encodable { @Throws(Asn1Exception::class) override fun doDecode(src: Asn1Element): CryptoSignature = runRethrowing { when (src.tag) { - Asn1Element.Tag.BIT_STRING -> RSAorHMAC.decodeFromTlv(src) + Asn1Element.Tag.BIT_STRING -> RSA.decodeFromTlv(src) Asn1Element.Tag.SEQUENCE -> EC.decodeFromTlv(src) else -> throw Asn1Exception("Unknown Signature Format") diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt new file mode 100644 index 00000000..d85fb548 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt @@ -0,0 +1,14 @@ +package at.asitplus.signum.indispensable + +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode + +/** + * Umbrella interface encompassing _data integrity algorithms_: + * * Message Authentication Codes ([MessageAuthenticationCode]) + * * Digital Signatures ([SignatureAlgorithm]) + */ +interface DataIntegrityAlgorithm{ + companion object { + val entries: Iterable = MessageAuthenticationCode.entries + SignatureAlgorithm.entries + } +} \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt index 1659a602..84739935 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt @@ -5,11 +5,7 @@ enum class RSAPadding { PSS; } -sealed interface SignatureAlgorithm { - data class HMAC( - /** The digest to use */ - val digest: Digest - ) : SignatureAlgorithm +sealed interface SignatureAlgorithm : DataIntegrityAlgorithm { data class ECDSA( /** The digest to apply to the data, or `null` to directly process the raw data. */ @@ -38,15 +34,22 @@ sealed interface SignatureAlgorithm { val RSAwithSHA384andPSSPadding = RSA(Digest.SHA384, RSAPadding.PSS) val RSAwithSHA512andPSSPadding = RSA(Digest.SHA512, RSAPadding.PSS) - @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) - val HMACwithSHA256 = HMAC(Digest.SHA256) - @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) - val HMACwithSHA384 = HMAC(Digest.SHA384) - @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) - val HMACwithSHA512 = HMAC(Digest.SHA512) + val entries: Iterable = listOf( + ECDSAwithSHA256, + ECDSAwithSHA384, + ECDSAwithSHA512, + + RSAwithSHA256andPSSPadding, + RSAwithSHA384andPSSPadding, + RSAwithSHA512andPSSPadding, + + RSAwithSHA256andPKCS1Padding, + RSAwithSHA384andPKCS1Padding, + RSAwithSHA512andPKCS1Padding + ) } } -interface SpecializedSignatureAlgorithm { - val algorithm: SignatureAlgorithm +interface SpecializedDataIntegrityAlgorithm { + val algorithm: DataIntegrityAlgorithm } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt index 43acfc3d..e3c4d9f5 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt @@ -18,18 +18,13 @@ import kotlinx.serialization.encoding.Encoder enum class X509SignatureAlgorithm( override val oid: ObjectIdentifier, val isEc: Boolean = false -) : Asn1Encodable, Identifiable, SpecializedSignatureAlgorithm { +) : Asn1Encodable, Identifiable, SpecializedDataIntegrityAlgorithm { // ECDSA with SHA-size ES256(KnownOIDs.ecdsaWithSHA256, true), ES384(KnownOIDs.ecdsaWithSHA384, true), ES512(KnownOIDs.ecdsaWithSHA512, true), - // HMAC-size with SHA-size - HS256(KnownOIDs.hmacWithSHA256), - HS384(KnownOIDs.hmacWithSHA384), - HS512(KnownOIDs.hmacWithSHA512), - // RSASSA-PSS with SHA-size PS256(KnownOIDs.rsaPSS), PS384(KnownOIDs.rsaPSS), @@ -84,7 +79,6 @@ enum class X509SignatureAlgorithm( PS512 -> encodePSSParams(512) - HS256, HS384, HS512, RS256, RS384, RS512, RS1 -> Asn1.Sequence { +oid +Null() @@ -93,15 +87,14 @@ enum class X509SignatureAlgorithm( val digest: Digest get() = when(this) { RS1 -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 + ES256, PS256, RS256 -> Digest.SHA256 + ES384, PS384, RS384 -> Digest.SHA384 + ES512, PS512, RS512 -> Digest.SHA512 } override val algorithm: SignatureAlgorithm get() = when(this) { ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, null) - HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) } @@ -119,8 +112,7 @@ enum class X509SignatureAlgorithm( ES512.oid, ES384.oid, ES256.oid -> fromOid(oid) RS1.oid -> RS1 - RS256.oid, RS384.oid, RS512.oid, - HS256.oid, HS384.oid, HS512.oid -> fromOid(oid).also { + RS256.oid, RS384.oid, RS512.oid-> fromOid(oid).also { val tag = src.nextChild().tag if (tag != Asn1Element.Tag.NULL) throw Asn1TagMismatchException(Asn1Element.Tag.NULL, tag, "RSA Params not allowed.") @@ -192,17 +184,8 @@ fun SignatureAlgorithm.toX509SignatureAlgorithm() = catching { else -> throw IllegalArgumentException("Digest ${this.digest} is unsupported by X.509 RSA-PSS") } } - is SignatureAlgorithm.HMAC -> when (this.digest) { - Digest.SHA256 -> X509SignatureAlgorithm.HS256 - Digest.SHA384 -> X509SignatureAlgorithm.HS384 - Digest.SHA512 -> X509SignatureAlgorithm.HS512 - else -> throw IllegalArgumentException("Digest ${this.digest} is unsupported by X.509 HMAC") - } } } -/** Finds a X.509 signature algorithm matching this algorithm. Curve restrictions are not preserved. */ -fun SpecializedSignatureAlgorithm.toX509SignatureAlgorithm() = - this.algorithm.toX509SignatureAlgorithm() object X509SignatureAlgorithmSerializer : KSerializer { diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt deleted file mode 100644 index 6c7810b8..00000000 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt +++ /dev/null @@ -1,40 +0,0 @@ -package at.asitplus.signum.indispensable.mac - -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.asn1.Identifiable -import at.asitplus.signum.indispensable.asn1.KnownOIDs -import at.asitplus.signum.indispensable.asn1.ObjectIdentifier -import at.asitplus.signum.indispensable.misc.BitLength - -sealed interface MAC { - /** output size of MAC */ - val outputLength: BitLength - - companion object { - val entries: Iterable = HMAC.entries - } -} - -/** - * RFC 2104 HMAC - */ -enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MAC, Identifiable { - SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), - SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), - SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), - SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), - ; - - override fun toString() = "HMAC-$digest" - - companion object { - operator fun invoke(digest: Digest) = when (digest) { - Digest.SHA1 -> SHA1 - Digest.SHA256 -> SHA256 - Digest.SHA384 -> SHA384 - Digest.SHA512 -> SHA512 - } - } - - override val outputLength: BitLength get() = digest.outputLength -} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt new file mode 100644 index 00000000..cc507420 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt @@ -0,0 +1,61 @@ +package at.asitplus.signum.indispensable.mac + +import at.asitplus.signum.indispensable.DataIntegrityAlgorithm +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.asn1.* +import at.asitplus.signum.indispensable.asn1.encoding.Asn1 +import at.asitplus.signum.indispensable.asn1.encoding.Asn1.Null +import at.asitplus.signum.indispensable.asn1.encoding.readNull +import at.asitplus.signum.indispensable.misc.BitLength + +sealed interface MessageAuthenticationCode : DataIntegrityAlgorithm { + /** output size of MAC */ + val outputLength: BitLength + + companion object { + val entries: Iterable = HMAC.entries + } +} + +/** + * RFC 2104 HMAC + */ +enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MessageAuthenticationCode, Identifiable, + Asn1Encodable { + SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), + SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), + SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), + SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), + ; + + override fun toString() = "HMAC-$digest" + + override fun encodeToTlv(): Asn1Sequence = Asn1.Sequence { + +oid + +Null() + } + + + companion object : Asn1Decodable { + + fun byOID(oid: ObjectIdentifier): HMAC? = entries.find { it.oid == oid } + + fun byDigest(digest: Digest): HMAC = entries.find { it.digest == digest }!! + + operator fun invoke(digest: Digest) = when (digest) { + Digest.SHA1 -> SHA1 + Digest.SHA256 -> SHA256 + Digest.SHA384 -> SHA384 + Digest.SHA512 -> SHA512 + } + + override fun doDecode(src: Asn1Sequence): HMAC { + val oid = src.nextChild().asPrimitive().readOid() + src.nextChild().asPrimitive().readNull() + require(!src.hasMoreChildren()) { "Superfluous ANS.1 data in HMAC" } + return byOID(oid) ?: throw Asn1OidException(oid) + } + } + + override val outputLength: BitLength get() = digest.outputLength +} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt index e9f38b49..147e9c9a 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt @@ -224,7 +224,7 @@ constructor( val CryptoSignature.x509Encoded get() = when (this) { is CryptoSignature.EC -> encodeToDer().encodeToAsn1BitStringPrimitive() - is CryptoSignature.RSAorHMAC -> encodeToTlv() + is CryptoSignature.RSA -> encodeToTlv() } /** @@ -235,7 +235,7 @@ val CryptoSignature.x509Encoded fun CryptoSignature.Companion.fromX509Encoded(alg: X509SignatureAlgorithm, it: Asn1Primitive) = when (alg.isEc) { true -> CryptoSignature.EC.decodeFromDer(it.asAsn1BitString().rawBytes) - false -> CryptoSignature.RSAorHMAC.decodeFromTlv(it) + false -> CryptoSignature.RSA.decodeFromTlv(it) } /** diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt index 2ba35669..b7314d76 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt @@ -7,7 +7,7 @@ import at.asitplus.signum.indispensable.asn1.KnownOIDs import at.asitplus.signum.indispensable.asn1.ObjectIdentifier import at.asitplus.signum.indispensable.asn1.encoding.encodeTo8Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.misc.BitLength import at.asitplus.signum.indispensable.misc.bit import kotlin.contracts.ExperimentalContracts @@ -110,7 +110,7 @@ sealed interface SymmetricEncryptionAlgorithm, out interface Integrated : Authenticated - interface WithDedicatedMac : + interface WithDedicatedMac : Authenticated, I, KeyType.WithDedicatedMacKey> } @@ -311,7 +311,7 @@ sealed interface AuthCapability { * An authenticated cipher construction based on an unauthenticated cipher with a dedicated MAC function, requiring a dedicated MAC key. * _Encrypt-then-MAC_ */ - class WithDedicatedMac( + class WithDedicatedMac( /** * The inner unauthenticated cipher */ @@ -406,14 +406,14 @@ val DefaultDedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation /** * Typealias defining the signature of the lambda for defining a custom MAC input calculation scheme. */ -typealias DedicatedMacInputCalculation = MAC.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray +typealias DedicatedMacInputCalculation = MessageAuthenticationCode.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray /** * The default dedicated mac input calculation as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1), authenticating all inputs: * `AAD || IV || Ciphertext || AAD Length`, where AAD_length is a 64 bit big-endian representation of the aad length in bits */ val DefaultDedicatedMacInputCalculation: DedicatedMacInputCalculation = - fun MAC.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = + fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = aad + iv + ciphertext + (aad.size.toLong()*8L).encodeTo8Bytes() /** diff --git a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt index a17391bb..0b3c51f5 100644 --- a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt +++ b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt @@ -20,9 +20,9 @@ class CryptoSignatureTest : FreeSpec({ val ec1 = CryptoSignature.EC.fromRS(first.toBigInteger(), second.toBigInteger()) val ec2 = CryptoSignature.EC.fromRS(first.toBigInteger(), second.toBigInteger()) val ec3 = CryptoSignature.EC.fromRS(second.toBigInteger(), first.toBigInteger()) - val rsa1 = CryptoSignature.RSAorHMAC(first.toTwosComplementByteArray()) - val rsa2 = CryptoSignature.RSAorHMAC(first.toTwosComplementByteArray()) - val rsa3 = CryptoSignature.RSAorHMAC(second.toTwosComplementByteArray()) + val rsa1 = CryptoSignature.RSA(first.toTwosComplementByteArray()) + val rsa2 = CryptoSignature.RSA(first.toTwosComplementByteArray()) + val rsa3 = CryptoSignature.RSA(second.toTwosComplementByteArray()) ec1 shouldBe ec1 ec1 shouldBe ec2 diff --git a/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt b/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt index b1806733..f5bbcab4 100644 --- a/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt +++ b/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt @@ -40,13 +40,8 @@ val SignatureAlgorithm.secKeyAlgorithm: SecKeyAlgorithm } } - is SignatureAlgorithm.HMAC -> TODO("HMAC is unsupported") }!! -val SpecializedSignatureAlgorithm.secKeyAlgorithm - get() = - this.algorithm.secKeyAlgorithm - val SignatureAlgorithm.secKeyAlgorithmPreHashed: SecKeyAlgorithm get() = when (this) { is SignatureAlgorithm.ECDSA -> { @@ -77,17 +72,16 @@ val SignatureAlgorithm.secKeyAlgorithmPreHashed: SecKeyAlgorithm } } - is SignatureAlgorithm.HMAC -> TODO("HMAC is unsupported") }!! -val SpecializedSignatureAlgorithm.secKeyAlgorithmPreHashed +val X509SignatureAlgorithm.secKeyAlgorithmPreHashed get() = this.algorithm.secKeyAlgorithmPreHashed val CryptoSignature.iosEncoded get() = when (this) { is CryptoSignature.EC -> this.encodeToDer() - is CryptoSignature.RSAorHMAC -> this.rawByteArray + is CryptoSignature.RSA -> this.rawByteArray } fun CryptoPublicKey.toSecKey() = catching { diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt index fb045341..4aa6e241 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt @@ -1,6 +1,6 @@ package at.asitplus.signum.indispensable -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -40,5 +40,5 @@ inline fun FreeSpec.enumConsistencyTest() { } class EnumConsistencyTests : FreeSpec({ - enumConsistencyTest() + enumConsistencyTest() }) \ No newline at end of file diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt index d018fa5f..9923e11b 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt @@ -72,7 +72,7 @@ class SignatureCodecTest : FreeSpec({ sign() } - CryptoSignature.RSAorHMAC.parseFromJca(sig).jcaSignatureBytes shouldBe sig + CryptoSignature.RSA.parseFromJca(sig).jcaSignatureBytes shouldBe sig CryptoSignature.parseFromJca( sig, SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) @@ -99,7 +99,7 @@ class SignatureCodecTest : FreeSpec({ val bcSig = (ASN1Sequence.fromByteArray(certificateHolder.encoded) as DLSequence).elementAt(2) .toASN1Primitive().encoded - CryptoSignature.RSAorHMAC.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig + CryptoSignature.RSA.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig CryptoSignature.parseFromJca( certificateHolder.signature, SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt index f9a2e13d..8365699d 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt @@ -12,7 +12,6 @@ infix fun KmmResult.shouldSucceedWith(b: T) : T = class X509ConversionTests : FreeSpec({ "X509 -> Alg -> X509 is stable" - { withData(X509SignatureAlgorithm.entries) { - it.toX509SignatureAlgorithm() shouldSucceedWith it it.algorithm.toX509SignatureAlgorithm() shouldSucceedWith it } } diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt index 0e1631c8..34fe376d 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt @@ -390,7 +390,7 @@ sealed class AndroidKeystoreSigner private constructor( return@signCatching when (this@AndroidKeystoreSigner) { is ECDSA -> CryptoSignature.EC.parseFromJca(jcaSig).withCurve(publicKey.curve) - is RSA -> CryptoSignature.RSAorHMAC.parseFromJca(jcaSig) + is RSA -> CryptoSignature.RSA.parseFromJca(jcaSig) } }} diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 2707500b..1677a67f 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -63,7 +63,7 @@ sealed class AndroidEphemeralSigner (internal val privateKey: PrivateKey) : Sign override val publicKey: CryptoPublicKey.RSA, override val signatureAlgorithm: SignatureAlgorithm.RSA) : AndroidEphemeralSigner(privateKey), Signer.RSA { - override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSAorHMAC.parseFromJca(bytes) + override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSA.parseFromJca(bytes) @SecretExposure override fun exportPrivateKey() = diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index 1019415b..c45d95ab 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -79,7 +79,7 @@ internal actual fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier @JvmSynthetic internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSAorHMAC, + data: SignatureInput, signature: CryptoSignature.RSA, config: PlatformVerifierConfiguration) { getRSAInstance(signatureAlgorithm, config).run { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt index bb733132..6dd8b911 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt @@ -3,19 +3,19 @@ package at.asitplus.signum.supreme.mac import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.internals.xor import at.asitplus.signum.supreme.hash.digest -fun MAC.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) -fun MAC.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) +fun MessageAuthenticationCode.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) +fun MessageAuthenticationCode.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) private val HMAC.blockLength get() = digest.inputBlockSize.bytes.toInt() private val HMAC.innerPad get() = ByteArray(blockLength) { 0x36 } private val HMAC.outerPad get() = ByteArray(blockLength) { 0x5C } -fun MAC.mac(key: ByteArray, msg: Sequence): KmmResult = catching { +fun MessageAuthenticationCode.mac(key: ByteArray, msg: Sequence): KmmResult = catching { when (this@mac) { is HMAC -> hmac(key, msg) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt index d12ce4e4..df38051b 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt @@ -154,7 +154,6 @@ fun SignatureAlgorithm.signerFor(privateKey: CryptoPrivateKey.WithPublicKey<*>): (this is SignatureAlgorithm.RSA && privateKey is CryptoPrivateKey.RSA)) { when (this) { is SignatureAlgorithm.ECDSA -> this.signerFor(privateKey as CryptoPrivateKey.EC.WithPublicKey) - is SignatureAlgorithm.HMAC -> KmmResult.failure(UnsupportedOperationException("HMAC is not yet supported!")) is SignatureAlgorithm.RSA -> this.signerFor(privateKey as CryptoPrivateKey.RSA) } } else { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt index b0a8cd79..12b18541 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt @@ -6,8 +6,9 @@ import at.asitplus.recoverCatching import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm +import at.asitplus.signum.indispensable.SpecializedDataIntegrityAlgorithm import at.asitplus.signum.ecmath.straussShamir +import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.supreme.dsl.DSL import at.asitplus.signum.supreme.UnsupportedCryptoException import at.asitplus.signum.supreme.dsl.DSLConfigureFn @@ -96,7 +97,7 @@ internal expect fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier /** data is guaranteed to be in RAW_BYTES format. failure should throw. */ internal expect fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSAorHMAC, + data: SignatureInput, signature: CryptoSignature.RSA, config: PlatformVerifierConfiguration) class PlatformRSAVerifier @@ -109,7 +110,7 @@ class PlatformRSAVerifier checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier(signatureAlgorithm, publicKey, config) } override fun verify(data: SignatureInput, sig: CryptoSignature) = catching { - require (sig is CryptoSignature.RSAorHMAC) + require (sig is CryptoSignature.RSA) { "Attempted to validate non-RSA signature using RSA public key" } if (data.format != null) throw UnsupportedOperationException("RSA with pre-hashed input is unsupported") @@ -191,8 +192,6 @@ private fun SignatureAlgorithm.verifierForImpl else verifierForImpl(publicKey, configure, allowKotlin) } - is SignatureAlgorithm.HMAC -> - KmmResult.failure(IllegalArgumentException("HMAC is unsupported")) } /** @@ -265,11 +264,11 @@ private fun SignatureAlgorithm.RSA.verifierForImpl catching { PlatformRSAVerifier(this, publicKey, configure) } /** @see [SignatureAlgorithm.verifierFor] */ -fun SpecializedSignatureAlgorithm.verifierFor +fun X509SignatureAlgorithm.verifierFor (publicKey: CryptoPublicKey, configure: ConfigurePlatformVerifier = null) = this.algorithm.verifierFor(publicKey, configure) /** @see [SignatureAlgorithm.platformVerifierFor] */ -fun SpecializedSignatureAlgorithm.platformVerifierFor +fun X509SignatureAlgorithm.platformVerifierFor (publicKey: CryptoPublicKey, configure: ConfigurePlatformVerifier = null) = this.algorithm.platformVerifierFor(publicKey, configure) diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt index 35030483..cacf5b12 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt @@ -30,7 +30,7 @@ open class RSAVerifierCommonTests : FreeSpec({ val key = CryptoPublicKey.decodeFromDer(Base64.decode(test.key)) as CryptoPublicKey.RSA val b64msg = test.msg val msg = Base64.decode(b64msg) - val sig = CryptoSignature.RSAorHMAC(Base64.decode(test.sig)) + val sig = CryptoSignature.RSA(Base64.decode(test.sig)) } /* diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 5659baa2..61dbca4f 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -1,7 +1,7 @@ import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.asn1.encoding.encodeTo4Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.misc.bit import at.asitplus.signum.indispensable.misc.bytes import at.asitplus.signum.indispensable.symmetric.* @@ -409,7 +409,7 @@ class `00SymmetricTest` : FreeSpec({ withData( nameFn = { it.first }, "Default" to DefaultDedicatedMacInputCalculation, - "Oklahoma MAC" to fun MAC.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = + "Oklahoma MAC" to fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = "Oklahoma".encodeToByteArray() + (iv ?: byteArrayOf()) + (aad ?: byteArrayOf()) + ciphertext) { (_, macInputFun) -> withData( diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt index a52afedc..567271a8 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt @@ -279,7 +279,7 @@ sealed class IosSigner(final override val alias: String, ) } override fun bytesToSignature(sigBytes: ByteArray) = - CryptoSignature.RSAorHMAC(sigBytes) + CryptoSignature.RSA(sigBytes) } } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index b5275698..cc60839f 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -37,7 +37,7 @@ sealed class EphemeralSigner(internal val privateKey: OwnedCFValue) : }.takeFromCF().toByteArray() return@signCatching when (val pubkey = publicKey) { is CryptoPublicKey.EC -> CryptoSignature.EC.decodeFromDer(signatureBytes).withCurve(pubkey.curve) - is CryptoPublicKey.RSA -> CryptoSignature.RSAorHMAC(signatureBytes) + is CryptoPublicKey.RSA -> CryptoSignature.RSA(signatureBytes) } } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index 3e79fa84..8a2e8a5f 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -77,6 +77,6 @@ internal actual fun verifyECDSAImpl internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSAorHMAC, + data: SignatureInput, signature: CryptoSignature.RSA, config: PlatformVerifierConfiguration) = verifyImpl(signatureAlgorithm, publicKey, data, signature, config) diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 84cfb311..1af30bea 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -68,7 +68,7 @@ sealed class EphemeralSigner (internal val privateKey: PrivateKey, private val p override val publicKey: CryptoPublicKey.RSA, override val signatureAlgorithm: SignatureAlgorithm.RSA) : EphemeralSigner(privateKey, config.provider), Signer.RSA { - override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSAorHMAC.parseFromJca(bytes) + override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSA.parseFromJca(bytes) @SecretExposure final override fun exportPrivateKey() = (privateKey as RSAPrivateKey).toCryptoPrivateKey() diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt index 8b2d2921..cc2ebf11 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt @@ -56,7 +56,6 @@ fun SignatureAlgorithm.signerFor( signatureAlgorithm = this ) - is SignatureAlgorithm.HMAC -> throw UnsupportedOperationException("HMAC is not yet supported!") is SignatureAlgorithm.RSA -> EphemeralSigner.RSA( config = DSL.resolve( ::EphemeralSignerConfiguration, diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index 2447e582..a70d555b 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -82,7 +82,7 @@ internal actual fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier @JvmSynthetic internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSAorHMAC, + data: SignatureInput, signature: CryptoSignature.RSA, config: PlatformVerifierConfiguration) { getRSAInstance(signatureAlgorithm, config).run { diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt index ccf5f016..0c32d0f5 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt @@ -114,8 +114,8 @@ class JKSProviderTest : FreeSpec({ } CryptoSignature.parseFromJca(signature.jcaSignatureBytes, signer.signatureAlgorithm) shouldBe signature when (signer.signatureAlgorithm) { - is SignatureAlgorithm.RSA, is SignatureAlgorithm.HMAC -> - CryptoSignature.RSAorHMAC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature + is SignatureAlgorithm.RSA -> + CryptoSignature.RSA.parseFromJca(signature.jcaSignatureBytes) shouldBe signature is SignatureAlgorithm.ECDSA -> CryptoSignature.EC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature } diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index 5cada8a9..87bcd090 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -138,7 +138,9 @@ class JvmSymmetricTest : FreeSpec({ alg.sealedBoxFrom( own.algorithm.randomNonce(), own.encryptedData - ).getOrThrow().decrypt(secretKey) shouldNot succeed + ).getOrThrow().decrypt(secretKey).onSuccess { + it shouldNotBe data + } } } --- .github/workflows/test-jvm.yml | 2 +- CHANGELOG.md | 8 + .../kotlin/at/asitplus/cryptotest/App.kt | 138 +++++------------- docs/docs/indispensable.md | 6 +- .../indispensable/asn1/Asn1Exception.kt | 4 +- .../indispensable/cosef/CoseAlgorithm.kt | 79 +++++++--- .../cosef/CoseSignedSerializer.kt | 4 +- .../indispensable/cosef/CoseEqualsTest.kt | 16 +- .../cosef/CoseSerializationTest.kt | 16 +- .../src/jvmTest/kotlin/ConversionTests.kt | 4 +- .../indispensable/josef/JwsAlgorithm.kt | 94 ++++++++---- .../signum/indispensable/josef/JwsSigned.kt | 2 +- .../indispensable/josef/ConversionTest.kt | 4 +- .../signum/indispensable/JcaExtensions.kt | 9 +- .../signum/indispensable/CryptoSignature.kt | 12 +- .../indispensable/DataIntegrityAlgorithm.kt | 14 ++ .../indispensable/SignatureAlgorithm.kt | 29 ++-- .../indispensable/X509SignatureAlgorithm.kt | 27 +--- .../asitplus/signum/indispensable/mac/MAC.kt | 40 ----- .../mac/MessageAuthenticationCode.kt | 61 ++++++++ .../indispensable/pki/X509Certificate.kt | 4 +- .../symmetric/SymmetricEncryptionAlgorithm.kt | 10 +- .../commonTest/kotlin/CryptoSignatureTest.kt | 6 +- .../iosMain/kotlin/CommonCryptoExtensions.kt | 10 +- .../indispensable/EnumConsistencyTests.kt | 4 +- .../indispensable/SignatureCodecTest.kt | 4 +- .../indispensable/pki/X509ConversionTests.kt | 1 - .../supreme/os/AndroidKeyStoreProvider.kt | 2 +- .../signum/supreme/sign/EphemeralKeysImpl.kt | 2 +- .../signum/supreme/sign/VerifierImpl.kt | 2 +- .../at/asitplus/signum/supreme/mac/MAC.kt | 8 +- .../at/asitplus/signum/supreme/sign/Signer.kt | 1 - .../asitplus/signum/supreme/sign/Verifier.kt | 13 +- .../supreme/sign/RSAVerifierCommonTests.kt | 2 +- .../supreme/symmetric/00SymmetricTest.kt | 4 +- .../signum/supreme/os/IosKeychainProvider.kt | 2 +- .../signum/supreme/sign/EphemeralKeysImpl.kt | 2 +- .../signum/supreme/sign/VerifierImpl.kt | 2 +- .../signum/supreme/sign/EphemeralKeysImpl.kt | 2 +- .../supreme/sign/PrivateKeySignerImpl.kt | 1 - .../signum/supreme/sign/VerifierImpl.kt | 2 +- .../signum/supreme/os/JKSProviderTest.kt | 4 +- .../asitplus/signum/supreme/symmetric/Test.kt | 4 +- 43 files changed, 342 insertions(+), 319 deletions(-) create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt delete mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt diff --git a/.github/workflows/test-jvm.yml b/.github/workflows/test-jvm.yml index 8bbf76206..239a9a7b5 100644 --- a/.github/workflows/test-jvm.yml +++ b/.github/workflows/test-jvm.yml @@ -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-cosef/build/test-results/**/TEST*.xml,indispensable-josef/build/test-results/**/TEST*.xml,supreme/build/test-results/**/TEST*.xml reporter: java-junit diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4f65a05..69f7e8134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,14 @@ * 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 + * **Clean up `RSAorHMAC` mess** + * Introduce `DataAuthenticationAlgorithm` encompassing Signatures and MACs + * Rename `RSAorHMAC` to `RSA` and introduce dedicated `MessageAuthenticationCode` and `HMAC` classes + * This caused **some breaking shifts and cleanups** in conversion methods between indispensable, josef, cosef, and JCA types + * There is no direct mapping between `SignatureAlgorithm` and COSE/JOSE algorithms anymore, because JOSE/COSE also incorporate MAC + * Hence, for both `JwsAlgorithm` and `CoseAlgorithm`, the following properties have been introduced on the companions: + * `signatureAlgorithms` + * `messageAuthenticationCodes` * Symmetric Encryption * Supported Algorithms * AES diff --git a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt index 6bff9f32e..201ab7452 100644 --- a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt +++ b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size @@ -46,28 +45,26 @@ import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.ECCurve import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.nativeDigest +import at.asitplus.signum.indispensable.pki.X509Certificate import at.asitplus.signum.supreme.dsl.PREFERRED import at.asitplus.signum.supreme.sign.Signer import at.asitplus.signum.supreme.sign.makeVerifier import at.asitplus.signum.supreme.sign.verify import at.asitplus.cryptotest.theme.AppTheme 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.supreme.agree.Ephemeral import at.asitplus.signum.supreme.asKmmResult import at.asitplus.signum.supreme.os.PlatformSignerConfigurationBase +import at.asitplus.signum.supreme.os.PlatformSigningKeyConfigurationBase +import at.asitplus.signum.supreme.os.PlatformSigningProvider import at.asitplus.signum.supreme.os.SignerConfiguration import at.asitplus.signum.supreme.os.SigningProvider import at.asitplus.signum.supreme.sign.Verifier import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier -import io.ktor.util.encodeBase64 +import io.ktor.util.decodeBase64Bytes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -100,10 +97,7 @@ private class getter(private val fn: () -> T) { operator fun getValue(nothing: Nothing?, property: KProperty<*>): T = fn() } -@OptIn( - ExperimentalCoroutinesApi::class, - SecretExposure::class -) +@OptIn(ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class) @Composable internal fun App() { @@ -120,7 +114,7 @@ internal fun App() { X509SignatureAlgorithm.RS512 ) var keyAlgorithm by remember { - mutableStateOf( + mutableStateOf( X509SignatureAlgorithm.ES256 ) } @@ -143,12 +137,6 @@ internal fun App() { } val signingPossible by getter { currentKey?.isSuccess == true } var signatureData by remember { mutableStateOf?>(null) } - var ephemeralKey by remember { - mutableStateOf( - null - ) - } - var agreedKey by remember { mutableStateOf?>(null) } val signatureDataStr by getter { signatureData?.fold(onSuccess = Any::toString) { Napier.e("Signature failed", it) @@ -169,14 +157,6 @@ internal fun App() { var genTextOverride by remember { mutableStateOf(null) } val genText by getter { genTextOverride ?: "Generate" } - fun genEphemeralKey(){ - ephemeralKey = if (currentKey?.getOrNull() is CryptoPublicKey.EC) - KeyAgreementPrivateValue.ECDH.Ephemeral((currentKey!!.getOrThrow() as CryptoPublicKey.EC).curve).getOrNull() - else null - agreedKey=null - } - - Column( modifier = Modifier.fillMaxSize().verticalScroll(ScrollState(0), enabled = true) .windowInsetsPadding(WindowInsets.safeDrawing) @@ -327,16 +307,12 @@ internal fun App() { canGenerate = false genTextOverride = "Creating…" currentSigner = Provider.createSigningKey(ALIAS) { - when (val alg = keyAlgorithm.algorithm) { is SignatureAlgorithm.ECDSA -> { this@createSigningKey.ec { - curve = alg.requiredCurve - ?: ECCurve.entries.find { it.nativeDigest == alg.digest }!! + curve = alg.requiredCurve ?: + ECCurve.entries.find { it.nativeDigest == alg.digest }!! digests = setOf(alg.digest) - purposes { - keyAgreement = true - } } } @@ -351,27 +327,29 @@ internal fun App() { else -> error("unreachable") } - signer(SIGNER_CONFIG) + if (this is PlatformSigningKeyConfigurationBase) { + signer(SIGNER_CONFIG) - val timeout = runCatching { - biometricAuth.substringBefore("s").trim().toInt() - }.getOrNull() + val timeout = runCatching { + biometricAuth.substringBefore("s").trim().toInt() + }.getOrNull() - if (attestation || timeout != null) { - hardware { - backing = PREFERRED - if (attestation) { - attestation { - challenge = Random.nextBytes(16) + if (attestation || timeout != null) { + hardware { + backing = PREFERRED + if (attestation) { + attestation { + challenge = Random.nextBytes(16) + } } - } - if (timeout != null) { - protection { - this.timeout = timeout.seconds - factors { - biometry = true - deviceLock = true + if (timeout != null) { + protection { + this.timeout = timeout.seconds + factors { + biometry = true + deviceLock = true + } } } } @@ -384,8 +362,6 @@ internal fun App() { Napier.w { "Signing possible: ${currentKey?.isSuccess}" } canGenerate = true genTextOverride = null - - genEphemeralKey() } }, modifier = Modifier.padding(start = 16.dp) @@ -409,7 +385,6 @@ internal fun App() { //loadPubKey().let { Napier.w { "PubKey retrieved from native: $it" } } canGenerate = true genTextOverride = null - genEphemeralKey() } }, modifier = Modifier.padding(start = 16.dp, end = 16.dp) @@ -457,6 +432,7 @@ internal fun App() { Button( onClick = { + Napier.w { "input: $inputData" } Napier.w { "signingKey: $currentKey" } CoroutineScope(context).launch { @@ -465,61 +441,13 @@ internal fun App() { .transform { it.sign(data).asKmmResult() } .also { signatureData = it; verifyState = null } } - }, - modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), - enabled = signingPossible - ) { Text("Sign") } - - if (signingPossible && (currentKey?.getOrNull() is CryptoPublicKey.EC)) { - Napier.i { "Ephemeral key: $ephemeralKey" } - Spacer(Modifier.height(8.dp)) - ephemeralKey?.let { ephemeralKey -> - OutlinedTextField(value = ephemeralKey.publicValue.toString(), - modifier = Modifier.fillMaxWidth().padding(16.dp), - minLines = 1, - textStyle = TextStyle.Default.copy(fontSize = 10.sp), - readOnly = true, - onValueChange = {}, - label = { Text("Random faux-external key for ECDH") }) - Button( - onClick = { - Napier.w { "input: $inputData" } - Napier.w { "signingKey: $currentKey" } - CoroutineScope(context).launch { - - agreedKey = - (currentSigner!!.getOrThrow() as Signer.ECDSA).keyAgreement( - ephemeralKey.publicValue - ) - Napier.i { - "ECDH Key of ext_piv + hardware-backed_pub: ${ - agreedKey?.getOrNull()?.encodeBase64() - }" - } - - Napier.i { - "ECDH Key of ext_pub + hardware-backed_priv: ${ - agreedKey?.getOrNull()?.encodeBase64() - }" - } - } - }, - modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), - ) { Text("Perform ECDH key agreement") } - } + }, - if (agreedKey != null) { - OutlinedTextField(value = - "Computed from ext_pub + hardware-backed_priv:\n" + agreedKey?.map { it.encodeBase64() } - ?.getOrElse { it.message ?: it::class.simpleName ?: "" }, - modifier = Modifier.fillMaxWidth().padding(16.dp), - minLines = 1, - textStyle = TextStyle.Default.copy(fontSize = 10.sp), - readOnly = true, - onValueChange = {}, - label = { Text("Agreed-upon secret") }) - } + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + enabled = signingPossible + ) { + Text("Sign") } if (signatureData != null) { diff --git a/docs/docs/indispensable.md b/docs/docs/indispensable.md index 4376b599b..d9c3aa602 100644 --- a/docs/docs/indispensable.md +++ b/docs/docs/indispensable.md @@ -104,9 +104,9 @@ The following functions provide interop functionality with platform types. ### JVM/Android * `SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm -* `SpecializedSignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm +* `X509SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm * `SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm -* `SpecializedSignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm +* `X509SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm
@@ -148,7 +148,7 @@ The following functions provide interop functionality with platform types. * `CryptoSignature.jcaSignatureBytes` returns the JCA-native encoded representation of a signature * `CryptoSignature.parseFromJca()` returns a signature object form a JCA-native encoded representation of a signature * `CryptoSignature.EC.parseFromJca()` returns an EC signature object form a JCA-native encoded representation of a signature -* `CryptoSignature.RSAorHMAC.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature +* `CryptoSignature.RSA.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature * `CryptoSignature.EC.parseFromJcaP1363` parses a signature produced by the JCA digestWithECDSAinP1363Format algorithm. * `X509Certificate.toJcaCertificate()` converts the certificate to a JCA-native `X509Certificate` * `java.security.cert.X509Certificate.toKmpCertificate()` converts a JCA-native certificate to a Signum `X509Certificate` diff --git a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt index b8f2d98bc..b6837c261 100644 --- a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt +++ b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt @@ -13,7 +13,9 @@ class Asn1TagMismatchException(val expected: Asn1Element.Tag, val actual: Asn1El class Asn1StructuralException(message: String, cause: Throwable? = null) : Asn1Exception(message, cause) -class Asn1OidException(message: String, val oid: ObjectIdentifier) : Asn1Exception(message) +class Asn1OidException(message: String, val oid: ObjectIdentifier) : Asn1Exception(message){ + constructor(oid: ObjectIdentifier):this("Unknown oid $oid", oid) +} /** * Runs [block] inside [catching] and encapsulates any thrown exception in an [Asn1Exception] unless it already is one diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt index 979cb6023..94bfe3f18 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt @@ -1,11 +1,10 @@ package at.asitplus.signum.indispensable.cosef +import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.ECCurve -import at.asitplus.signum.indispensable.RSAPadding -import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm +import at.asitplus.signum.indispensable.* +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -18,7 +17,7 @@ import kotlinx.serialization.encoding.Encoder * See [COSE Algorithm Registry](https://www.iana.org/assignments/cose/cose.xhtml) */ @Serializable(with = CoseAlgorithmSerializer::class) -enum class CoseAlgorithm(val value: Int): SpecializedSignatureAlgorithm { +enum class CoseAlgorithm(val value: Int) : SpecializedDataIntegrityAlgorithm { // ECDSA with SHA-size ES256(-7), @@ -43,22 +42,52 @@ enum class CoseAlgorithm(val value: Int): SpecializedSignatureAlgorithm { // RSASSA-PKCS1-v1_5 using SHA-1 RS1(-65535); - val digest: Digest get() = when(this) { - RS1 -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 - } - override val algorithm: SignatureAlgorithm get() = when (this) { - ES256 -> SignatureAlgorithm.ECDSA(Digest.SHA256, ECCurve.SECP_256_R_1) - ES384 -> SignatureAlgorithm.ECDSA(Digest.SHA384, ECCurve.SECP_384_R_1) - ES512 -> SignatureAlgorithm.ECDSA(Digest.SHA512, ECCurve.SECP_521_R_1) + companion object { + /** + * encompasses only signature algorithms, filtering out MACs + */ + val signatureAlgorithms = listOf( + ES256, + ES384, + ES512, + + PS256, + PS384, + PS512, + + RS256, + RS384, + RS512, + RS1 + ).map { it.algorithm as SignatureAlgorithm } + + /** + * encompasses only MACs, filtering out signature algorithms + */ + val messageAuthenticationCodes = listOf(HS256, HS384, HS512).map { it.algorithm as MessageAuthenticationCode } + - HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) - PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this. digest, RSAPadding.PSS) - RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) } + + val digest: Digest + get() = when (this) { + RS1 -> Digest.SHA1 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 + } + + override val algorithm: DataIntegrityAlgorithm + get() = when (this) { + ES256 -> SignatureAlgorithm.ECDSA(Digest.SHA256, ECCurve.SECP_256_R_1) + ES384 -> SignatureAlgorithm.ECDSA(Digest.SHA384, ECCurve.SECP_384_R_1) + ES512 -> SignatureAlgorithm.ECDSA(Digest.SHA512, ECCurve.SECP_521_R_1) + + HS256, HS384, HS512 -> HMAC.byDigest(digest) + PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) + RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + } } object CoseAlgorithmSerializer : KSerializer { @@ -77,8 +106,9 @@ object CoseAlgorithmSerializer : KSerializer { } + /** Tries to find a matching COSE algorithm. Note that COSE imposes curve restrictions on ECDSA based on the digest. */ -fun SignatureAlgorithm.toCoseAlgorithm() = catching { +fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catching { when (this) { is SignatureAlgorithm.ECDSA -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.ES256 @@ -86,6 +116,7 @@ fun SignatureAlgorithm.toCoseAlgorithm() = catching { Digest.SHA512 -> CoseAlgorithm.ES512 else -> throw IllegalArgumentException("ECDSA with ${this.digest} is unsupported by COSE") } + is SignatureAlgorithm.RSA -> when (this.padding) { RSAPadding.PKCS1 -> when (this.digest) { Digest.SHA1 -> CoseAlgorithm.RS1 @@ -93,6 +124,7 @@ fun SignatureAlgorithm.toCoseAlgorithm() = catching { Digest.SHA384 -> CoseAlgorithm.RS384 Digest.SHA512 -> CoseAlgorithm.RS512 } + RSAPadding.PSS -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.PS256 Digest.SHA384 -> CoseAlgorithm.PS384 @@ -100,15 +132,18 @@ fun SignatureAlgorithm.toCoseAlgorithm() = catching { else -> throw IllegalArgumentException("RSA-PSS with ${this.digest} is unsupported by COSE") } } - is SignatureAlgorithm.HMAC -> when (this.digest) { + + is HMAC -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.HS256 Digest.SHA384 -> CoseAlgorithm.HS384 Digest.SHA512 -> CoseAlgorithm.HS512 else -> throw IllegalArgumentException("HMAC with ${this.digest} is unsupported by COSE") } + + else -> throw IllegalArgumentException("UnsupportedAlgorithm $this") } } /** Tries to find a matching COSE algorithm. Note that COSE imposes curve restrictions on ECDSA based on the digest. */ -fun SpecializedSignatureAlgorithm.toCoseAlgorithm() = +fun SpecializedDataIntegrityAlgorithm.toCoseAlgorithm() = this.algorithm.toCoseAlgorithm() diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt index c7f98eeb7..e571328e9 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt @@ -66,7 +66,7 @@ class CoseSignedSerializer

( ): CryptoSignature.RawByteEncodable = if (protectedHeader.usesEC() ?: unprotectedHeader?.usesEC() ?: (size < 2048)) CryptoSignature.EC.fromRawBytes(this) - else CryptoSignature.RSAorHMAC(this) + else CryptoSignature.RSA(this) private fun ByteArray?.toNullablePayload(): P? = when (this) { null -> null @@ -80,7 +80,7 @@ class CoseSignedSerializer

( } else { runCatching { fromBytes() } .getOrElse { fromByteStringWrapper() } - // if it still fails, the input is not valid + // if it still fails, the input not valid } private fun ByteArray.fromBytes(): P = diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt index dc47dd22f..d9d737c71 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt @@ -21,14 +21,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = bytes, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = ByteArraySerializer(), ) val bytesSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = bytes, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = ByteArraySerializer(), ) @@ -42,14 +42,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSAorHMAC(reversed), + signature = CryptoSignature.RSA(reversed), payloadSerializer = ByteArraySerializer(), ) val reversedSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSAorHMAC(reversed), + signature = CryptoSignature.RSA(reversed), payloadSerializer = ByteArraySerializer(), ) @@ -80,14 +80,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = DataClass.serializer(), ) val bytesSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = DataClass.serializer(), ) @@ -102,14 +102,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = DataClass.serializer(), ) val reversedSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSAorHMAC(bytes), + signature = CryptoSignature.RSA(bytes), payloadSerializer = DataClass.serializer(), ) diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt index bc0c9cb1c..dd25e09c6 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt @@ -30,7 +30,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC(byteArrayOf()), + signature = CryptoSignature.RSA(byteArrayOf()), payloadSerializer = ByteStringWrapper.serializer(String.serializer()) ) } @@ -42,7 +42,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), + signature = CryptoSignature.RSA("bar".encodeToByteArray()), payloadSerializer = ByteArraySerializer(), ) @@ -55,7 +55,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), + signature = CryptoSignature.RSA("bar".encodeToByteArray()), payloadSerializer = DataClass.serializer(), ) @@ -68,7 +68,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple + signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple payloadSerializer = ByteArraySerializer(), ) val serialized = cose.serialize(ByteArraySerializer()) @@ -86,7 +86,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = null, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple + signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple payloadSerializer = ByteArraySerializer(), ) val serialized = cose.serialize(ByteArraySerializer()) @@ -129,7 +129,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple + signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple payloadSerializer = DataClass.serializer(), ) val serialized = cose.serialize(DataClass.serializer()) @@ -195,7 +195,7 @@ class CoseSerializationTest : FreeSpec({ val inputObject = CoseSigned.create( protectedHeader = header, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), + signature = CryptoSignature.RSA("bar".encodeToByteArray()), payloadSerializer = ByteArraySerializer(), ).prepareCoseSignatureInput(byteArrayOf()) .encodeToString(Base16()) @@ -217,7 +217,7 @@ class CoseSerializationTest : FreeSpec({ val inputObject = CoseSigned.create( protectedHeader = header, payload = payload, - signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), + signature = CryptoSignature.RSA("bar".encodeToByteArray()), payloadSerializer = DataClass.serializer(), ).prepareCoseSignatureInput(byteArrayOf()) .encodeToString(Base16()) diff --git a/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt b/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt index 02270b0de..547371422 100644 --- a/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt +++ b/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt @@ -23,9 +23,9 @@ class ConversionTests : FreeSpec({ } } "COSE -> X509 -> COSE is stable" - { - withData(CoseAlgorithm.entries) { + withData(CoseAlgorithm.signatureAlgorithms/*here we filter by using only sig alg*/) { it.toX509SignatureAlgorithm().getOrNull()?.let { x509 -> - x509.toCoseAlgorithm() shouldSucceedWith it + x509.toCoseAlgorithm().onSuccess { it.algorithm } shouldSucceedWith it.toCoseAlgorithm().getOrThrow() //here we go back to cose, after initially filtering, because cose does not discriminate between MAC and sig } } } diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt index 2f8ecd289..9ec4e38ee 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt @@ -1,11 +1,10 @@ package at.asitplus.signum.indispensable.josef +import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.ECCurve -import at.asitplus.signum.indispensable.RSAPadding -import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm +import at.asitplus.signum.indispensable.* +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -19,7 +18,7 @@ import kotlinx.serialization.encoding.Encoder * Since we support only JWS algorithms (with one exception), this class is called what it's called. */ @Serializable(with = JwsAlgorithmSerializer::class) -enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, SpecializedSignatureAlgorithm { +enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, SpecializedDataIntegrityAlgorithm { ES256("ES256"), ES384("ES384"), @@ -42,28 +41,58 @@ enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, Spe */ NON_JWS_SHA1_WITH_RSA("RS1"); - val digest: Digest get() = when(this) { - NON_JWS_SHA1_WITH_RSA -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 - } - override val algorithm: SignatureAlgorithm get() = when (this) { - ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, this.ecCurve!!) - HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) - PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this. digest, RSAPadding.PSS) - NON_JWS_SHA1_WITH_RSA, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + companion object { + /** + * encompasses only signature algorithms, filtering out MACs + */ + val signatureAlgorithms = listOf( + ES256, + ES384, + ES512, + + PS256, + PS384, + PS512, + + RS256, + RS384, + RS512, + NON_JWS_SHA1_WITH_RSA + ).map { it.algorithm as SignatureAlgorithm } + + /** + * Encompasses only MACs, filtering our signature algorithms + */ + val messageAuthenticationCodes = listOf(HS256, HS384, HS512).map { it.algorithm as MessageAuthenticationCode } + } + val digest: Digest + get() = when (this) { + NON_JWS_SHA1_WITH_RSA -> Digest.SHA1 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 + } + + override val algorithm: DataIntegrityAlgorithm + get() = when (this) { + ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, this.ecCurve!!) + HS256, HS384, HS512 -> HMAC.byDigest(this.digest) + PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) + NON_JWS_SHA1_WITH_RSA, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + } + /** The curve to create signatures on. * This is fixed by RFC7518, as opposed to X.509 where other combinations are possible. */ - val ecCurve: ECCurve? get() = when (this) { - ES256 -> ECCurve.SECP_256_R_1 - ES384 -> ECCurve.SECP_384_R_1 - ES512 -> ECCurve.SECP_521_R_1 - else -> null - } + val ecCurve: ECCurve? + get() = when (this) { + ES256 -> ECCurve.SECP_256_R_1 + ES384 -> ECCurve.SECP_384_R_1 + ES512 -> ECCurve.SECP_521_R_1 + else -> null + } } object JwsAlgorithmSerializer : KSerializer { @@ -80,8 +109,12 @@ object JwsAlgorithmSerializer : KSerializer { } } + +fun SpecializedDataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = + algorithm.toJwsAlgorithm() + /** Tries to find a matching JWS algorithm. Note that JWS imposes curve restrictions on ECDSA based on the digest. */ -fun SignatureAlgorithm.toJwsAlgorithm() = catching { +fun DataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = catching { when (this) { is SignatureAlgorithm.ECDSA -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.ES256 @@ -89,6 +122,7 @@ fun SignatureAlgorithm.toJwsAlgorithm() = catching { Digest.SHA512 -> JwsAlgorithm.ES512 else -> throw IllegalArgumentException("ECDSA with ${this.digest} is unsupported by JWS") } + is SignatureAlgorithm.RSA -> when (this.padding) { RSAPadding.PKCS1 -> when (this.digest) { Digest.SHA1 -> JwsAlgorithm.NON_JWS_SHA1_WITH_RSA @@ -96,24 +130,26 @@ fun SignatureAlgorithm.toJwsAlgorithm() = catching { Digest.SHA384 -> JwsAlgorithm.RS384 Digest.SHA512 -> JwsAlgorithm.RS512 } + RSAPadding.PSS -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.PS256 Digest.SHA384 -> JwsAlgorithm.PS384 Digest.SHA512 -> JwsAlgorithm.PS512 else -> throw IllegalArgumentException("RSA-PSS with ${this.digest} is unsupported by JWS") } + } - is SignatureAlgorithm.HMAC -> when (this.digest) { + + is HMAC -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.HS256 Digest.SHA384 -> JwsAlgorithm.HS384 Digest.SHA512 -> JwsAlgorithm.HS512 else -> throw IllegalArgumentException("HMAC with ${this.digest} is unsupported by JWS") } + + else -> throw IllegalArgumentException("UnsupportedAlgorithm $this") + } } -/** Tries to find a matching JWS algorithm. Note that JWS imposes curve restrictions on ECDSA based on the digest. */ -fun SpecializedSignatureAlgorithm.toJwsAlgorithm() = - this.algorithm.toJwsAlgorithm() - diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt index 0bc1838ec..6c31b6e5d 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt @@ -74,7 +74,7 @@ data class JwsSigned( val payload = inputParts[1] val signature = with(inputParts[2]) { when (val curve = header.algorithm.ecCurve) { - null -> CryptoSignature.RSAorHMAC(this) + null -> CryptoSignature.RSA(this) else -> CryptoSignature.EC.fromRawBytes(curve, this) } } diff --git a/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt b/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt index 69b0c60f2..d8f2610c1 100644 --- a/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt +++ b/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt @@ -17,9 +17,9 @@ class ConversionTest : FreeSpec({ } } "JWS -> X509 -> JWS is stable" - { - withData(JwsAlgorithm.entries) { + withData(JwsAlgorithm.signatureAlgorithms /*here we filter s.t. only sig alg are fed into the test*/) { it.toX509SignatureAlgorithm().getOrNull()?.let { x509 -> - x509.toJwsAlgorithm() shouldSucceedWith it + x509.toJwsAlgorithm() shouldSucceedWith it.toJwsAlgorithm().getOrThrow() /* and here we convert the sig algs back to JWS alg*/ } } } diff --git a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt index 90e17c91b..273e91aa5 100644 --- a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt +++ b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt @@ -4,6 +4,7 @@ import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.asn1.toAsn1Integer import at.asitplus.signum.indispensable.asn1.toJavaBigInteger +import at.asitplus.signum.indispensable.mac.HMAC import at.asitplus.signum.internals.isAndroid import at.asitplus.signum.indispensable.pki.X509Certificate import com.ionspin.kotlin.bignum.integer.base63.toJavaBigInteger @@ -186,7 +187,7 @@ fun CryptoPublicKey.Companion.fromJcaPublicKey(publicKey: PublicKey): KmmResult< val CryptoSignature.jcaSignatureBytes: ByteArray get() = when (this) { is CryptoSignature.EC -> encodeToDer() - is CryptoSignature.RSAorHMAC -> rawByteArray + is CryptoSignature.RSA -> rawByteArray } /** @@ -199,7 +200,7 @@ fun CryptoSignature.Companion.parseFromJca( if (algorithm is SignatureAlgorithm.ECDSA) CryptoSignature.EC.parseFromJca(input) else - CryptoSignature.RSAorHMAC.parseFromJca(input) + CryptoSignature.RSA.parseFromJca(input) fun CryptoSignature.Companion.parseFromJca( input: ByteArray, @@ -218,8 +219,8 @@ fun CryptoSignature.EC.Companion.parseFromJca(input: ByteArray) = fun CryptoSignature.EC.Companion.parseFromJcaP1363(input: ByteArray) = CryptoSignature.EC.fromRawBytes(input) -fun CryptoSignature.RSAorHMAC.Companion.parseFromJca(input: ByteArray) = - CryptoSignature.RSAorHMAC(input) +fun CryptoSignature.RSA.Companion.parseFromJca(input: ByteArray) = + CryptoSignature.RSA(input) /** * Converts this [X509Certificate] to a [java.security.cert.X509Certificate]. diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt index d8f59e9d6..b5033a80e 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt @@ -217,7 +217,7 @@ sealed interface CryptoSignature : Asn1Encodable { } - class RSAorHMAC private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { + class RSA private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { constructor(rawBytes: ByteArray) : this(rawBytes, null) constructor(x509Element: Asn1Primitive) : this(null, x509Element) @@ -241,16 +241,16 @@ sealed interface CryptoSignature : Asn1Encodable { if (this === other) return true if (other == null || this::class != other::class) return false - other as RSAorHMAC + other as RSA return signature == other.signature } - companion object : Asn1Decodable { + companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun doDecode(src: Asn1Element): RSAorHMAC { + override fun doDecode(src: Asn1Element): RSA { src as Asn1Primitive - return RSAorHMAC(src) + return RSA(src) } } } @@ -259,7 +259,7 @@ sealed interface CryptoSignature : Asn1Encodable { @Throws(Asn1Exception::class) override fun doDecode(src: Asn1Element): CryptoSignature = runRethrowing { when (src.tag) { - Asn1Element.Tag.BIT_STRING -> RSAorHMAC.decodeFromTlv(src) + Asn1Element.Tag.BIT_STRING -> RSA.decodeFromTlv(src) Asn1Element.Tag.SEQUENCE -> EC.decodeFromTlv(src) else -> throw Asn1Exception("Unknown Signature Format") diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt new file mode 100644 index 000000000..d85fb5485 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt @@ -0,0 +1,14 @@ +package at.asitplus.signum.indispensable + +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode + +/** + * Umbrella interface encompassing _data integrity algorithms_: + * * Message Authentication Codes ([MessageAuthenticationCode]) + * * Digital Signatures ([SignatureAlgorithm]) + */ +interface DataIntegrityAlgorithm{ + companion object { + val entries: Iterable = MessageAuthenticationCode.entries + SignatureAlgorithm.entries + } +} \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt index 1659a602c..84739935c 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt @@ -5,11 +5,7 @@ enum class RSAPadding { PSS; } -sealed interface SignatureAlgorithm { - data class HMAC( - /** The digest to use */ - val digest: Digest - ) : SignatureAlgorithm +sealed interface SignatureAlgorithm : DataIntegrityAlgorithm { data class ECDSA( /** The digest to apply to the data, or `null` to directly process the raw data. */ @@ -38,15 +34,22 @@ sealed interface SignatureAlgorithm { val RSAwithSHA384andPSSPadding = RSA(Digest.SHA384, RSAPadding.PSS) val RSAwithSHA512andPSSPadding = RSA(Digest.SHA512, RSAPadding.PSS) - @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) - val HMACwithSHA256 = HMAC(Digest.SHA256) - @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) - val HMACwithSHA384 = HMAC(Digest.SHA384) - @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) - val HMACwithSHA512 = HMAC(Digest.SHA512) + val entries: Iterable = listOf( + ECDSAwithSHA256, + ECDSAwithSHA384, + ECDSAwithSHA512, + + RSAwithSHA256andPSSPadding, + RSAwithSHA384andPSSPadding, + RSAwithSHA512andPSSPadding, + + RSAwithSHA256andPKCS1Padding, + RSAwithSHA384andPKCS1Padding, + RSAwithSHA512andPKCS1Padding + ) } } -interface SpecializedSignatureAlgorithm { - val algorithm: SignatureAlgorithm +interface SpecializedDataIntegrityAlgorithm { + val algorithm: DataIntegrityAlgorithm } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt index 43acfc3d3..e3c4d9f5e 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt @@ -18,18 +18,13 @@ import kotlinx.serialization.encoding.Encoder enum class X509SignatureAlgorithm( override val oid: ObjectIdentifier, val isEc: Boolean = false -) : Asn1Encodable, Identifiable, SpecializedSignatureAlgorithm { +) : Asn1Encodable, Identifiable, SpecializedDataIntegrityAlgorithm { // ECDSA with SHA-size ES256(KnownOIDs.ecdsaWithSHA256, true), ES384(KnownOIDs.ecdsaWithSHA384, true), ES512(KnownOIDs.ecdsaWithSHA512, true), - // HMAC-size with SHA-size - HS256(KnownOIDs.hmacWithSHA256), - HS384(KnownOIDs.hmacWithSHA384), - HS512(KnownOIDs.hmacWithSHA512), - // RSASSA-PSS with SHA-size PS256(KnownOIDs.rsaPSS), PS384(KnownOIDs.rsaPSS), @@ -84,7 +79,6 @@ enum class X509SignatureAlgorithm( PS512 -> encodePSSParams(512) - HS256, HS384, HS512, RS256, RS384, RS512, RS1 -> Asn1.Sequence { +oid +Null() @@ -93,15 +87,14 @@ enum class X509SignatureAlgorithm( val digest: Digest get() = when(this) { RS1 -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 + ES256, PS256, RS256 -> Digest.SHA256 + ES384, PS384, RS384 -> Digest.SHA384 + ES512, PS512, RS512 -> Digest.SHA512 } override val algorithm: SignatureAlgorithm get() = when(this) { ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, null) - HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) } @@ -119,8 +112,7 @@ enum class X509SignatureAlgorithm( ES512.oid, ES384.oid, ES256.oid -> fromOid(oid) RS1.oid -> RS1 - RS256.oid, RS384.oid, RS512.oid, - HS256.oid, HS384.oid, HS512.oid -> fromOid(oid).also { + RS256.oid, RS384.oid, RS512.oid-> fromOid(oid).also { val tag = src.nextChild().tag if (tag != Asn1Element.Tag.NULL) throw Asn1TagMismatchException(Asn1Element.Tag.NULL, tag, "RSA Params not allowed.") @@ -192,17 +184,8 @@ fun SignatureAlgorithm.toX509SignatureAlgorithm() = catching { else -> throw IllegalArgumentException("Digest ${this.digest} is unsupported by X.509 RSA-PSS") } } - is SignatureAlgorithm.HMAC -> when (this.digest) { - Digest.SHA256 -> X509SignatureAlgorithm.HS256 - Digest.SHA384 -> X509SignatureAlgorithm.HS384 - Digest.SHA512 -> X509SignatureAlgorithm.HS512 - else -> throw IllegalArgumentException("Digest ${this.digest} is unsupported by X.509 HMAC") - } } } -/** Finds a X.509 signature algorithm matching this algorithm. Curve restrictions are not preserved. */ -fun SpecializedSignatureAlgorithm.toX509SignatureAlgorithm() = - this.algorithm.toX509SignatureAlgorithm() object X509SignatureAlgorithmSerializer : KSerializer { diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt deleted file mode 100644 index 6c7810b84..000000000 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt +++ /dev/null @@ -1,40 +0,0 @@ -package at.asitplus.signum.indispensable.mac - -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.asn1.Identifiable -import at.asitplus.signum.indispensable.asn1.KnownOIDs -import at.asitplus.signum.indispensable.asn1.ObjectIdentifier -import at.asitplus.signum.indispensable.misc.BitLength - -sealed interface MAC { - /** output size of MAC */ - val outputLength: BitLength - - companion object { - val entries: Iterable = HMAC.entries - } -} - -/** - * RFC 2104 HMAC - */ -enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MAC, Identifiable { - SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), - SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), - SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), - SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), - ; - - override fun toString() = "HMAC-$digest" - - companion object { - operator fun invoke(digest: Digest) = when (digest) { - Digest.SHA1 -> SHA1 - Digest.SHA256 -> SHA256 - Digest.SHA384 -> SHA384 - Digest.SHA512 -> SHA512 - } - } - - override val outputLength: BitLength get() = digest.outputLength -} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt new file mode 100644 index 000000000..cc5074209 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt @@ -0,0 +1,61 @@ +package at.asitplus.signum.indispensable.mac + +import at.asitplus.signum.indispensable.DataIntegrityAlgorithm +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.asn1.* +import at.asitplus.signum.indispensable.asn1.encoding.Asn1 +import at.asitplus.signum.indispensable.asn1.encoding.Asn1.Null +import at.asitplus.signum.indispensable.asn1.encoding.readNull +import at.asitplus.signum.indispensable.misc.BitLength + +sealed interface MessageAuthenticationCode : DataIntegrityAlgorithm { + /** output size of MAC */ + val outputLength: BitLength + + companion object { + val entries: Iterable = HMAC.entries + } +} + +/** + * RFC 2104 HMAC + */ +enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MessageAuthenticationCode, Identifiable, + Asn1Encodable { + SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), + SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), + SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), + SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), + ; + + override fun toString() = "HMAC-$digest" + + override fun encodeToTlv(): Asn1Sequence = Asn1.Sequence { + +oid + +Null() + } + + + companion object : Asn1Decodable { + + fun byOID(oid: ObjectIdentifier): HMAC? = entries.find { it.oid == oid } + + fun byDigest(digest: Digest): HMAC = entries.find { it.digest == digest }!! + + operator fun invoke(digest: Digest) = when (digest) { + Digest.SHA1 -> SHA1 + Digest.SHA256 -> SHA256 + Digest.SHA384 -> SHA384 + Digest.SHA512 -> SHA512 + } + + override fun doDecode(src: Asn1Sequence): HMAC { + val oid = src.nextChild().asPrimitive().readOid() + src.nextChild().asPrimitive().readNull() + require(!src.hasMoreChildren()) { "Superfluous ANS.1 data in HMAC" } + return byOID(oid) ?: throw Asn1OidException(oid) + } + } + + override val outputLength: BitLength get() = digest.outputLength +} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt index e9f38b493..147e9c9a4 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt @@ -224,7 +224,7 @@ constructor( val CryptoSignature.x509Encoded get() = when (this) { is CryptoSignature.EC -> encodeToDer().encodeToAsn1BitStringPrimitive() - is CryptoSignature.RSAorHMAC -> encodeToTlv() + is CryptoSignature.RSA -> encodeToTlv() } /** @@ -235,7 +235,7 @@ val CryptoSignature.x509Encoded fun CryptoSignature.Companion.fromX509Encoded(alg: X509SignatureAlgorithm, it: Asn1Primitive) = when (alg.isEc) { true -> CryptoSignature.EC.decodeFromDer(it.asAsn1BitString().rawBytes) - false -> CryptoSignature.RSAorHMAC.decodeFromTlv(it) + false -> CryptoSignature.RSA.decodeFromTlv(it) } /** diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt index 2ba356695..b7314d766 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt @@ -7,7 +7,7 @@ import at.asitplus.signum.indispensable.asn1.KnownOIDs import at.asitplus.signum.indispensable.asn1.ObjectIdentifier import at.asitplus.signum.indispensable.asn1.encoding.encodeTo8Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.misc.BitLength import at.asitplus.signum.indispensable.misc.bit import kotlin.contracts.ExperimentalContracts @@ -110,7 +110,7 @@ sealed interface SymmetricEncryptionAlgorithm, out interface Integrated : Authenticated - interface WithDedicatedMac : + interface WithDedicatedMac : Authenticated, I, KeyType.WithDedicatedMacKey> } @@ -311,7 +311,7 @@ sealed interface AuthCapability { * An authenticated cipher construction based on an unauthenticated cipher with a dedicated MAC function, requiring a dedicated MAC key. * _Encrypt-then-MAC_ */ - class WithDedicatedMac( + class WithDedicatedMac( /** * The inner unauthenticated cipher */ @@ -406,14 +406,14 @@ val DefaultDedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation /** * Typealias defining the signature of the lambda for defining a custom MAC input calculation scheme. */ -typealias DedicatedMacInputCalculation = MAC.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray +typealias DedicatedMacInputCalculation = MessageAuthenticationCode.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray /** * The default dedicated mac input calculation as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1), authenticating all inputs: * `AAD || IV || Ciphertext || AAD Length`, where AAD_length is a 64 bit big-endian representation of the aad length in bits */ val DefaultDedicatedMacInputCalculation: DedicatedMacInputCalculation = - fun MAC.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = + fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = aad + iv + ciphertext + (aad.size.toLong()*8L).encodeTo8Bytes() /** diff --git a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt index a17391bbb..0b3c51f58 100644 --- a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt +++ b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt @@ -20,9 +20,9 @@ class CryptoSignatureTest : FreeSpec({ val ec1 = CryptoSignature.EC.fromRS(first.toBigInteger(), second.toBigInteger()) val ec2 = CryptoSignature.EC.fromRS(first.toBigInteger(), second.toBigInteger()) val ec3 = CryptoSignature.EC.fromRS(second.toBigInteger(), first.toBigInteger()) - val rsa1 = CryptoSignature.RSAorHMAC(first.toTwosComplementByteArray()) - val rsa2 = CryptoSignature.RSAorHMAC(first.toTwosComplementByteArray()) - val rsa3 = CryptoSignature.RSAorHMAC(second.toTwosComplementByteArray()) + val rsa1 = CryptoSignature.RSA(first.toTwosComplementByteArray()) + val rsa2 = CryptoSignature.RSA(first.toTwosComplementByteArray()) + val rsa3 = CryptoSignature.RSA(second.toTwosComplementByteArray()) ec1 shouldBe ec1 ec1 shouldBe ec2 diff --git a/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt b/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt index b18067339..f5bbcab42 100644 --- a/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt +++ b/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt @@ -40,13 +40,8 @@ val SignatureAlgorithm.secKeyAlgorithm: SecKeyAlgorithm } } - is SignatureAlgorithm.HMAC -> TODO("HMAC is unsupported") }!! -val SpecializedSignatureAlgorithm.secKeyAlgorithm - get() = - this.algorithm.secKeyAlgorithm - val SignatureAlgorithm.secKeyAlgorithmPreHashed: SecKeyAlgorithm get() = when (this) { is SignatureAlgorithm.ECDSA -> { @@ -77,17 +72,16 @@ val SignatureAlgorithm.secKeyAlgorithmPreHashed: SecKeyAlgorithm } } - is SignatureAlgorithm.HMAC -> TODO("HMAC is unsupported") }!! -val SpecializedSignatureAlgorithm.secKeyAlgorithmPreHashed +val X509SignatureAlgorithm.secKeyAlgorithmPreHashed get() = this.algorithm.secKeyAlgorithmPreHashed val CryptoSignature.iosEncoded get() = when (this) { is CryptoSignature.EC -> this.encodeToDer() - is CryptoSignature.RSAorHMAC -> this.rawByteArray + is CryptoSignature.RSA -> this.rawByteArray } fun CryptoPublicKey.toSecKey() = catching { diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt index fb0453414..4aa6e241e 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt @@ -1,6 +1,6 @@ package at.asitplus.signum.indispensable -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -40,5 +40,5 @@ inline fun FreeSpec.enumConsistencyTest() { } class EnumConsistencyTests : FreeSpec({ - enumConsistencyTest() + enumConsistencyTest() }) \ No newline at end of file diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt index d018fa5f0..9923e11b1 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt @@ -72,7 +72,7 @@ class SignatureCodecTest : FreeSpec({ sign() } - CryptoSignature.RSAorHMAC.parseFromJca(sig).jcaSignatureBytes shouldBe sig + CryptoSignature.RSA.parseFromJca(sig).jcaSignatureBytes shouldBe sig CryptoSignature.parseFromJca( sig, SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) @@ -99,7 +99,7 @@ class SignatureCodecTest : FreeSpec({ val bcSig = (ASN1Sequence.fromByteArray(certificateHolder.encoded) as DLSequence).elementAt(2) .toASN1Primitive().encoded - CryptoSignature.RSAorHMAC.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig + CryptoSignature.RSA.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig CryptoSignature.parseFromJca( certificateHolder.signature, SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt index f9a2e13d1..8365699d3 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt @@ -12,7 +12,6 @@ infix fun KmmResult.shouldSucceedWith(b: T) : T = class X509ConversionTests : FreeSpec({ "X509 -> Alg -> X509 is stable" - { withData(X509SignatureAlgorithm.entries) { - it.toX509SignatureAlgorithm() shouldSucceedWith it it.algorithm.toX509SignatureAlgorithm() shouldSucceedWith it } } diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt index 0e1631c87..34fe376d1 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt @@ -390,7 +390,7 @@ sealed class AndroidKeystoreSigner private constructor( return@signCatching when (this@AndroidKeystoreSigner) { is ECDSA -> CryptoSignature.EC.parseFromJca(jcaSig).withCurve(publicKey.curve) - is RSA -> CryptoSignature.RSAorHMAC.parseFromJca(jcaSig) + is RSA -> CryptoSignature.RSA.parseFromJca(jcaSig) } }} diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 2707500be..1677a67fa 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -63,7 +63,7 @@ sealed class AndroidEphemeralSigner (internal val privateKey: PrivateKey) : Sign override val publicKey: CryptoPublicKey.RSA, override val signatureAlgorithm: SignatureAlgorithm.RSA) : AndroidEphemeralSigner(privateKey), Signer.RSA { - override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSAorHMAC.parseFromJca(bytes) + override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSA.parseFromJca(bytes) @SecretExposure override fun exportPrivateKey() = diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index 1019415b0..c45d95ab1 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -79,7 +79,7 @@ internal actual fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier @JvmSynthetic internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSAorHMAC, + data: SignatureInput, signature: CryptoSignature.RSA, config: PlatformVerifierConfiguration) { getRSAInstance(signatureAlgorithm, config).run { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt index bb733132c..6dd8b9117 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt @@ -3,19 +3,19 @@ package at.asitplus.signum.supreme.mac import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.internals.xor import at.asitplus.signum.supreme.hash.digest -fun MAC.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) -fun MAC.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) +fun MessageAuthenticationCode.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) +fun MessageAuthenticationCode.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) private val HMAC.blockLength get() = digest.inputBlockSize.bytes.toInt() private val HMAC.innerPad get() = ByteArray(blockLength) { 0x36 } private val HMAC.outerPad get() = ByteArray(blockLength) { 0x5C } -fun MAC.mac(key: ByteArray, msg: Sequence): KmmResult = catching { +fun MessageAuthenticationCode.mac(key: ByteArray, msg: Sequence): KmmResult = catching { when (this@mac) { is HMAC -> hmac(key, msg) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt index d12ce4e43..df38051bf 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt @@ -154,7 +154,6 @@ fun SignatureAlgorithm.signerFor(privateKey: CryptoPrivateKey.WithPublicKey<*>): (this is SignatureAlgorithm.RSA && privateKey is CryptoPrivateKey.RSA)) { when (this) { is SignatureAlgorithm.ECDSA -> this.signerFor(privateKey as CryptoPrivateKey.EC.WithPublicKey) - is SignatureAlgorithm.HMAC -> KmmResult.failure(UnsupportedOperationException("HMAC is not yet supported!")) is SignatureAlgorithm.RSA -> this.signerFor(privateKey as CryptoPrivateKey.RSA) } } else { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt index b0a8cd796..12b185415 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt @@ -6,8 +6,9 @@ import at.asitplus.recoverCatching import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm +import at.asitplus.signum.indispensable.SpecializedDataIntegrityAlgorithm import at.asitplus.signum.ecmath.straussShamir +import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.supreme.dsl.DSL import at.asitplus.signum.supreme.UnsupportedCryptoException import at.asitplus.signum.supreme.dsl.DSLConfigureFn @@ -96,7 +97,7 @@ internal expect fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier /** data is guaranteed to be in RAW_BYTES format. failure should throw. */ internal expect fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSAorHMAC, + data: SignatureInput, signature: CryptoSignature.RSA, config: PlatformVerifierConfiguration) class PlatformRSAVerifier @@ -109,7 +110,7 @@ class PlatformRSAVerifier checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier(signatureAlgorithm, publicKey, config) } override fun verify(data: SignatureInput, sig: CryptoSignature) = catching { - require (sig is CryptoSignature.RSAorHMAC) + require (sig is CryptoSignature.RSA) { "Attempted to validate non-RSA signature using RSA public key" } if (data.format != null) throw UnsupportedOperationException("RSA with pre-hashed input is unsupported") @@ -191,8 +192,6 @@ private fun SignatureAlgorithm.verifierForImpl else verifierForImpl(publicKey, configure, allowKotlin) } - is SignatureAlgorithm.HMAC -> - KmmResult.failure(IllegalArgumentException("HMAC is unsupported")) } /** @@ -265,11 +264,11 @@ private fun SignatureAlgorithm.RSA.verifierForImpl catching { PlatformRSAVerifier(this, publicKey, configure) } /** @see [SignatureAlgorithm.verifierFor] */ -fun SpecializedSignatureAlgorithm.verifierFor +fun X509SignatureAlgorithm.verifierFor (publicKey: CryptoPublicKey, configure: ConfigurePlatformVerifier = null) = this.algorithm.verifierFor(publicKey, configure) /** @see [SignatureAlgorithm.platformVerifierFor] */ -fun SpecializedSignatureAlgorithm.platformVerifierFor +fun X509SignatureAlgorithm.platformVerifierFor (publicKey: CryptoPublicKey, configure: ConfigurePlatformVerifier = null) = this.algorithm.platformVerifierFor(publicKey, configure) diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt index 350304831..cacf5b124 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt @@ -30,7 +30,7 @@ open class RSAVerifierCommonTests : FreeSpec({ val key = CryptoPublicKey.decodeFromDer(Base64.decode(test.key)) as CryptoPublicKey.RSA val b64msg = test.msg val msg = Base64.decode(b64msg) - val sig = CryptoSignature.RSAorHMAC(Base64.decode(test.sig)) + val sig = CryptoSignature.RSA(Base64.decode(test.sig)) } /* diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 5659baa2f..61dbca4ff 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -1,7 +1,7 @@ import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.asn1.encoding.encodeTo4Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.misc.bit import at.asitplus.signum.indispensable.misc.bytes import at.asitplus.signum.indispensable.symmetric.* @@ -409,7 +409,7 @@ class `00SymmetricTest` : FreeSpec({ withData( nameFn = { it.first }, "Default" to DefaultDedicatedMacInputCalculation, - "Oklahoma MAC" to fun MAC.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = + "Oklahoma MAC" to fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = "Oklahoma".encodeToByteArray() + (iv ?: byteArrayOf()) + (aad ?: byteArrayOf()) + ciphertext) { (_, macInputFun) -> withData( diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt index a52afedc0..567271a82 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt @@ -279,7 +279,7 @@ sealed class IosSigner(final override val alias: String, ) } override fun bytesToSignature(sigBytes: ByteArray) = - CryptoSignature.RSAorHMAC(sigBytes) + CryptoSignature.RSA(sigBytes) } } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index b52756985..cc60839f3 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -37,7 +37,7 @@ sealed class EphemeralSigner(internal val privateKey: OwnedCFValue) : }.takeFromCF().toByteArray() return@signCatching when (val pubkey = publicKey) { is CryptoPublicKey.EC -> CryptoSignature.EC.decodeFromDer(signatureBytes).withCurve(pubkey.curve) - is CryptoPublicKey.RSA -> CryptoSignature.RSAorHMAC(signatureBytes) + is CryptoPublicKey.RSA -> CryptoSignature.RSA(signatureBytes) } } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index 3e79fa843..8a2e8a5f2 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -77,6 +77,6 @@ internal actual fun verifyECDSAImpl internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSAorHMAC, + data: SignatureInput, signature: CryptoSignature.RSA, config: PlatformVerifierConfiguration) = verifyImpl(signatureAlgorithm, publicKey, data, signature, config) diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 84cfb3112..1af30bea0 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -68,7 +68,7 @@ sealed class EphemeralSigner (internal val privateKey: PrivateKey, private val p override val publicKey: CryptoPublicKey.RSA, override val signatureAlgorithm: SignatureAlgorithm.RSA) : EphemeralSigner(privateKey, config.provider), Signer.RSA { - override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSAorHMAC.parseFromJca(bytes) + override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSA.parseFromJca(bytes) @SecretExposure final override fun exportPrivateKey() = (privateKey as RSAPrivateKey).toCryptoPrivateKey() diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt index 8b2d2921c..cc2ebf11d 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt @@ -56,7 +56,6 @@ fun SignatureAlgorithm.signerFor( signatureAlgorithm = this ) - is SignatureAlgorithm.HMAC -> throw UnsupportedOperationException("HMAC is not yet supported!") is SignatureAlgorithm.RSA -> EphemeralSigner.RSA( config = DSL.resolve( ::EphemeralSignerConfiguration, diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index 2447e582d..a70d555b7 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -82,7 +82,7 @@ internal actual fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier @JvmSynthetic internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSAorHMAC, + data: SignatureInput, signature: CryptoSignature.RSA, config: PlatformVerifierConfiguration) { getRSAInstance(signatureAlgorithm, config).run { diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt index ccf5f016b..0c32d0f51 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt @@ -114,8 +114,8 @@ class JKSProviderTest : FreeSpec({ } CryptoSignature.parseFromJca(signature.jcaSignatureBytes, signer.signatureAlgorithm) shouldBe signature when (signer.signatureAlgorithm) { - is SignatureAlgorithm.RSA, is SignatureAlgorithm.HMAC -> - CryptoSignature.RSAorHMAC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature + is SignatureAlgorithm.RSA -> + CryptoSignature.RSA.parseFromJca(signature.jcaSignatureBytes) shouldBe signature is SignatureAlgorithm.ECDSA -> CryptoSignature.EC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature } diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index 5cada8a98..87bcd090a 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -138,7 +138,9 @@ class JvmSymmetricTest : FreeSpec({ alg.sealedBoxFrom( own.algorithm.randomNonce(), own.encryptedData - ).getOrThrow().decrypt(secretKey) shouldNot succeed + ).getOrThrow().decrypt(secretKey).onSuccess { + it shouldNotBe data + } } } From 19fe6a0f9d96ca41a1df0202e2fbd7bcacf623a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Thu, 13 Feb 2025 16:56:23 +0100 Subject: [PATCH 04/28] Revert "Fix RSAorHMAC mess" and move it to its own branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 4cb60b8f8e28a1faec8c13d8456b117c1b6d2b15. diff --git a/.github/workflows/test-jvm.yml b/.github/workflows/test-jvm.yml index 239a9a7b..8bbf7620 100644 --- a/.github/workflows/test-jvm.yml +++ b/.github/workflows/test-jvm.yml @@ -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-cosef/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-josef/build/test-results/**/TEST*.xml,supreme/build/test-results/**/TEST*.xml reporter: java-junit diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f7e813..6e4f65a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,14 +13,6 @@ * 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 - * **Clean up `RSAorHMAC` mess** - * Introduce `DataAuthenticationAlgorithm` encompassing Signatures and MACs - * Rename `RSAorHMAC` to `RSA` and introduce dedicated `MessageAuthenticationCode` and `HMAC` classes - * This caused **some breaking shifts and cleanups** in conversion methods between indispensable, josef, cosef, and JCA types - * There is no direct mapping between `SignatureAlgorithm` and COSE/JOSE algorithms anymore, because JOSE/COSE also incorporate MAC - * Hence, for both `JwsAlgorithm` and `CoseAlgorithm`, the following properties have been introduced on the companions: - * `signatureAlgorithms` - * `messageAuthenticationCodes` * Symmetric Encryption * Supported Algorithms * AES diff --git a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt index 201ab745..6bff9f32 100644 --- a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt +++ b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size @@ -45,26 +46,28 @@ import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.ECCurve import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm +import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.nativeDigest -import at.asitplus.signum.indispensable.pki.X509Certificate import at.asitplus.signum.supreme.dsl.PREFERRED import at.asitplus.signum.supreme.sign.Signer import at.asitplus.signum.supreme.sign.makeVerifier import at.asitplus.signum.supreme.sign.verify import at.asitplus.cryptotest.theme.AppTheme 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.supreme.agree.Ephemeral import at.asitplus.signum.supreme.asKmmResult import at.asitplus.signum.supreme.os.PlatformSignerConfigurationBase -import at.asitplus.signum.supreme.os.PlatformSigningKeyConfigurationBase -import at.asitplus.signum.supreme.os.PlatformSigningProvider import at.asitplus.signum.supreme.os.SignerConfiguration import at.asitplus.signum.supreme.os.SigningProvider import at.asitplus.signum.supreme.sign.Verifier import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier -import io.ktor.util.decodeBase64Bytes +import io.ktor.util.encodeBase64 import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -97,7 +100,10 @@ private class getter(private val fn: () -> T) { operator fun getValue(nothing: Nothing?, property: KProperty<*>): T = fn() } -@OptIn(ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class) +@OptIn( + ExperimentalCoroutinesApi::class, + SecretExposure::class +) @Composable internal fun App() { @@ -114,7 +120,7 @@ internal fun App() { X509SignatureAlgorithm.RS512 ) var keyAlgorithm by remember { - mutableStateOf( + mutableStateOf( X509SignatureAlgorithm.ES256 ) } @@ -137,6 +143,12 @@ internal fun App() { } val signingPossible by getter { currentKey?.isSuccess == true } var signatureData by remember { mutableStateOf?>(null) } + var ephemeralKey by remember { + mutableStateOf( + null + ) + } + var agreedKey by remember { mutableStateOf?>(null) } val signatureDataStr by getter { signatureData?.fold(onSuccess = Any::toString) { Napier.e("Signature failed", it) @@ -157,6 +169,14 @@ internal fun App() { var genTextOverride by remember { mutableStateOf(null) } val genText by getter { genTextOverride ?: "Generate" } + fun genEphemeralKey(){ + ephemeralKey = if (currentKey?.getOrNull() is CryptoPublicKey.EC) + KeyAgreementPrivateValue.ECDH.Ephemeral((currentKey!!.getOrThrow() as CryptoPublicKey.EC).curve).getOrNull() + else null + agreedKey=null + } + + Column( modifier = Modifier.fillMaxSize().verticalScroll(ScrollState(0), enabled = true) .windowInsetsPadding(WindowInsets.safeDrawing) @@ -307,12 +327,16 @@ internal fun App() { canGenerate = false genTextOverride = "Creating…" currentSigner = Provider.createSigningKey(ALIAS) { + when (val alg = keyAlgorithm.algorithm) { is SignatureAlgorithm.ECDSA -> { this@createSigningKey.ec { - curve = alg.requiredCurve ?: - ECCurve.entries.find { it.nativeDigest == alg.digest }!! + curve = alg.requiredCurve + ?: ECCurve.entries.find { it.nativeDigest == alg.digest }!! digests = setOf(alg.digest) + purposes { + keyAgreement = true + } } } @@ -327,29 +351,27 @@ internal fun App() { else -> error("unreachable") } - if (this is PlatformSigningKeyConfigurationBase) { - signer(SIGNER_CONFIG) + signer(SIGNER_CONFIG) - val timeout = runCatching { - biometricAuth.substringBefore("s").trim().toInt() - }.getOrNull() + val timeout = runCatching { + biometricAuth.substringBefore("s").trim().toInt() + }.getOrNull() - if (attestation || timeout != null) { - hardware { - backing = PREFERRED - if (attestation) { - attestation { - challenge = Random.nextBytes(16) - } + if (attestation || timeout != null) { + hardware { + backing = PREFERRED + if (attestation) { + attestation { + challenge = Random.nextBytes(16) } + } - if (timeout != null) { - protection { - this.timeout = timeout.seconds - factors { - biometry = true - deviceLock = true - } + if (timeout != null) { + protection { + this.timeout = timeout.seconds + factors { + biometry = true + deviceLock = true } } } @@ -362,6 +384,8 @@ internal fun App() { Napier.w { "Signing possible: ${currentKey?.isSuccess}" } canGenerate = true genTextOverride = null + + genEphemeralKey() } }, modifier = Modifier.padding(start = 16.dp) @@ -385,6 +409,7 @@ internal fun App() { //loadPubKey().let { Napier.w { "PubKey retrieved from native: $it" } } canGenerate = true genTextOverride = null + genEphemeralKey() } }, modifier = Modifier.padding(start = 16.dp, end = 16.dp) @@ -432,7 +457,6 @@ internal fun App() { Button( onClick = { - Napier.w { "input: $inputData" } Napier.w { "signingKey: $currentKey" } CoroutineScope(context).launch { @@ -441,13 +465,61 @@ internal fun App() { .transform { it.sign(data).asKmmResult() } .also { signatureData = it; verifyState = null } } - }, - - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), enabled = signingPossible - ) { - Text("Sign") + ) { Text("Sign") } + + if (signingPossible && (currentKey?.getOrNull() is CryptoPublicKey.EC)) { + Napier.i { "Ephemeral key: $ephemeralKey" } + Spacer(Modifier.height(8.dp)) + ephemeralKey?.let { ephemeralKey -> + OutlinedTextField(value = ephemeralKey.publicValue.toString(), + modifier = Modifier.fillMaxWidth().padding(16.dp), + minLines = 1, + textStyle = TextStyle.Default.copy(fontSize = 10.sp), + readOnly = true, + onValueChange = {}, + label = { Text("Random faux-external key for ECDH") }) + Button( + onClick = { + Napier.w { "input: $inputData" } + Napier.w { "signingKey: $currentKey" } + CoroutineScope(context).launch { + + agreedKey = + (currentSigner!!.getOrThrow() as Signer.ECDSA).keyAgreement( + ephemeralKey.publicValue + ) + Napier.i { + "ECDH Key of ext_piv + hardware-backed_pub: ${ + agreedKey?.getOrNull()?.encodeBase64() + }" + } + + Napier.i { + "ECDH Key of ext_pub + hardware-backed_priv: ${ + agreedKey?.getOrNull()?.encodeBase64() + }" + } + } + + }, + modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), + ) { Text("Perform ECDH key agreement") } + } + + if (agreedKey != null) { + OutlinedTextField(value = + "Computed from ext_pub + hardware-backed_priv:\n" + agreedKey?.map { it.encodeBase64() } + ?.getOrElse { it.message ?: it::class.simpleName ?: "" }, + modifier = Modifier.fillMaxWidth().padding(16.dp), + minLines = 1, + textStyle = TextStyle.Default.copy(fontSize = 10.sp), + readOnly = true, + onValueChange = {}, + label = { Text("Agreed-upon secret") }) + } } if (signatureData != null) { diff --git a/docs/docs/indispensable.md b/docs/docs/indispensable.md index d9c3aa60..4376b599 100644 --- a/docs/docs/indispensable.md +++ b/docs/docs/indispensable.md @@ -104,9 +104,9 @@ The following functions provide interop functionality with platform types. ### JVM/Android * `SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm -* `X509SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm +* `SpecializedSignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm * `SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm -* `X509SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm +* `SpecializedSignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm
@@ -148,7 +148,7 @@ The following functions provide interop functionality with platform types. * `CryptoSignature.jcaSignatureBytes` returns the JCA-native encoded representation of a signature * `CryptoSignature.parseFromJca()` returns a signature object form a JCA-native encoded representation of a signature * `CryptoSignature.EC.parseFromJca()` returns an EC signature object form a JCA-native encoded representation of a signature -* `CryptoSignature.RSA.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature +* `CryptoSignature.RSAorHMAC.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature * `CryptoSignature.EC.parseFromJcaP1363` parses a signature produced by the JCA digestWithECDSAinP1363Format algorithm. * `X509Certificate.toJcaCertificate()` converts the certificate to a JCA-native `X509Certificate` * `java.security.cert.X509Certificate.toKmpCertificate()` converts a JCA-native certificate to a Signum `X509Certificate` diff --git a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt index b6837c26..b8f2d98b 100644 --- a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt +++ b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt @@ -13,9 +13,7 @@ class Asn1TagMismatchException(val expected: Asn1Element.Tag, val actual: Asn1El class Asn1StructuralException(message: String, cause: Throwable? = null) : Asn1Exception(message, cause) -class Asn1OidException(message: String, val oid: ObjectIdentifier) : Asn1Exception(message){ - constructor(oid: ObjectIdentifier):this("Unknown oid $oid", oid) -} +class Asn1OidException(message: String, val oid: ObjectIdentifier) : Asn1Exception(message) /** * Runs [block] inside [catching] and encapsulates any thrown exception in an [Asn1Exception] unless it already is one diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt index 94bfe3f1..979cb602 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt @@ -1,10 +1,11 @@ package at.asitplus.signum.indispensable.cosef -import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.* -import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.ECCurve +import at.asitplus.signum.indispensable.RSAPadding +import at.asitplus.signum.indispensable.SignatureAlgorithm +import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -17,7 +18,7 @@ import kotlinx.serialization.encoding.Encoder * See [COSE Algorithm Registry](https://www.iana.org/assignments/cose/cose.xhtml) */ @Serializable(with = CoseAlgorithmSerializer::class) -enum class CoseAlgorithm(val value: Int) : SpecializedDataIntegrityAlgorithm { +enum class CoseAlgorithm(val value: Int): SpecializedSignatureAlgorithm { // ECDSA with SHA-size ES256(-7), @@ -42,52 +43,22 @@ enum class CoseAlgorithm(val value: Int) : SpecializedDataIntegrityAlgorithm { // RSASSA-PKCS1-v1_5 using SHA-1 RS1(-65535); - - companion object { - /** - * encompasses only signature algorithms, filtering out MACs - */ - val signatureAlgorithms = listOf( - ES256, - ES384, - ES512, - - PS256, - PS384, - PS512, - - RS256, - RS384, - RS512, - RS1 - ).map { it.algorithm as SignatureAlgorithm } - - /** - * encompasses only MACs, filtering out signature algorithms - */ - val messageAuthenticationCodes = listOf(HS256, HS384, HS512).map { it.algorithm as MessageAuthenticationCode } - - + val digest: Digest get() = when(this) { + RS1 -> Digest.SHA1 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 } - val digest: Digest - get() = when (this) { - RS1 -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 - } + override val algorithm: SignatureAlgorithm get() = when (this) { + ES256 -> SignatureAlgorithm.ECDSA(Digest.SHA256, ECCurve.SECP_256_R_1) + ES384 -> SignatureAlgorithm.ECDSA(Digest.SHA384, ECCurve.SECP_384_R_1) + ES512 -> SignatureAlgorithm.ECDSA(Digest.SHA512, ECCurve.SECP_521_R_1) - override val algorithm: DataIntegrityAlgorithm - get() = when (this) { - ES256 -> SignatureAlgorithm.ECDSA(Digest.SHA256, ECCurve.SECP_256_R_1) - ES384 -> SignatureAlgorithm.ECDSA(Digest.SHA384, ECCurve.SECP_384_R_1) - ES512 -> SignatureAlgorithm.ECDSA(Digest.SHA512, ECCurve.SECP_521_R_1) - - HS256, HS384, HS512 -> HMAC.byDigest(digest) - PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) - RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) - } + HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) + PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this. digest, RSAPadding.PSS) + RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + } } object CoseAlgorithmSerializer : KSerializer { @@ -106,9 +77,8 @@ object CoseAlgorithmSerializer : KSerializer { } - /** Tries to find a matching COSE algorithm. Note that COSE imposes curve restrictions on ECDSA based on the digest. */ -fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catching { +fun SignatureAlgorithm.toCoseAlgorithm() = catching { when (this) { is SignatureAlgorithm.ECDSA -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.ES256 @@ -116,7 +86,6 @@ fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catchin Digest.SHA512 -> CoseAlgorithm.ES512 else -> throw IllegalArgumentException("ECDSA with ${this.digest} is unsupported by COSE") } - is SignatureAlgorithm.RSA -> when (this.padding) { RSAPadding.PKCS1 -> when (this.digest) { Digest.SHA1 -> CoseAlgorithm.RS1 @@ -124,7 +93,6 @@ fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catchin Digest.SHA384 -> CoseAlgorithm.RS384 Digest.SHA512 -> CoseAlgorithm.RS512 } - RSAPadding.PSS -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.PS256 Digest.SHA384 -> CoseAlgorithm.PS384 @@ -132,18 +100,15 @@ fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catchin else -> throw IllegalArgumentException("RSA-PSS with ${this.digest} is unsupported by COSE") } } - - is HMAC -> when (this.digest) { + is SignatureAlgorithm.HMAC -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.HS256 Digest.SHA384 -> CoseAlgorithm.HS384 Digest.SHA512 -> CoseAlgorithm.HS512 else -> throw IllegalArgumentException("HMAC with ${this.digest} is unsupported by COSE") } - - else -> throw IllegalArgumentException("UnsupportedAlgorithm $this") } } /** Tries to find a matching COSE algorithm. Note that COSE imposes curve restrictions on ECDSA based on the digest. */ -fun SpecializedDataIntegrityAlgorithm.toCoseAlgorithm() = +fun SpecializedSignatureAlgorithm.toCoseAlgorithm() = this.algorithm.toCoseAlgorithm() diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt index e571328e..c7f98eeb 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt @@ -66,7 +66,7 @@ class CoseSignedSerializer

( ): CryptoSignature.RawByteEncodable = if (protectedHeader.usesEC() ?: unprotectedHeader?.usesEC() ?: (size < 2048)) CryptoSignature.EC.fromRawBytes(this) - else CryptoSignature.RSA(this) + else CryptoSignature.RSAorHMAC(this) private fun ByteArray?.toNullablePayload(): P? = when (this) { null -> null @@ -80,7 +80,7 @@ class CoseSignedSerializer

( } else { runCatching { fromBytes() } .getOrElse { fromByteStringWrapper() } - // if it still fails, the input not valid + // if it still fails, the input is not valid } private fun ByteArray.fromBytes(): P = diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt index d9d737c7..dc47dd22 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt @@ -21,14 +21,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = bytes, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = ByteArraySerializer(), ) val bytesSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = bytes, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = ByteArraySerializer(), ) @@ -42,14 +42,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSA(reversed), + signature = CryptoSignature.RSAorHMAC(reversed), payloadSerializer = ByteArraySerializer(), ) val reversedSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSA(reversed), + signature = CryptoSignature.RSAorHMAC(reversed), payloadSerializer = ByteArraySerializer(), ) @@ -80,14 +80,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = DataClass.serializer(), ) val bytesSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = DataClass.serializer(), ) @@ -102,14 +102,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = DataClass.serializer(), ) val reversedSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = DataClass.serializer(), ) diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt index dd25e09c..bc0c9cb1 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt @@ -30,7 +30,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA(byteArrayOf()), + signature = CryptoSignature.RSAorHMAC(byteArrayOf()), payloadSerializer = ByteStringWrapper.serializer(String.serializer()) ) } @@ -42,7 +42,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), payloadSerializer = ByteArraySerializer(), ) @@ -55,7 +55,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), payloadSerializer = DataClass.serializer(), ) @@ -68,7 +68,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple payloadSerializer = ByteArraySerializer(), ) val serialized = cose.serialize(ByteArraySerializer()) @@ -86,7 +86,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = null, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple payloadSerializer = ByteArraySerializer(), ) val serialized = cose.serialize(ByteArraySerializer()) @@ -129,7 +129,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple payloadSerializer = DataClass.serializer(), ) val serialized = cose.serialize(DataClass.serializer()) @@ -195,7 +195,7 @@ class CoseSerializationTest : FreeSpec({ val inputObject = CoseSigned.create( protectedHeader = header, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), payloadSerializer = ByteArraySerializer(), ).prepareCoseSignatureInput(byteArrayOf()) .encodeToString(Base16()) @@ -217,7 +217,7 @@ class CoseSerializationTest : FreeSpec({ val inputObject = CoseSigned.create( protectedHeader = header, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), payloadSerializer = DataClass.serializer(), ).prepareCoseSignatureInput(byteArrayOf()) .encodeToString(Base16()) diff --git a/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt b/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt index 54737142..02270b0d 100644 --- a/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt +++ b/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt @@ -23,9 +23,9 @@ class ConversionTests : FreeSpec({ } } "COSE -> X509 -> COSE is stable" - { - withData(CoseAlgorithm.signatureAlgorithms/*here we filter by using only sig alg*/) { + withData(CoseAlgorithm.entries) { it.toX509SignatureAlgorithm().getOrNull()?.let { x509 -> - x509.toCoseAlgorithm().onSuccess { it.algorithm } shouldSucceedWith it.toCoseAlgorithm().getOrThrow() //here we go back to cose, after initially filtering, because cose does not discriminate between MAC and sig + x509.toCoseAlgorithm() shouldSucceedWith it } } } diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt index 9ec4e38e..2f8ecd28 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt @@ -1,10 +1,11 @@ package at.asitplus.signum.indispensable.josef -import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.* -import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.ECCurve +import at.asitplus.signum.indispensable.RSAPadding +import at.asitplus.signum.indispensable.SignatureAlgorithm +import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -18,7 +19,7 @@ import kotlinx.serialization.encoding.Encoder * Since we support only JWS algorithms (with one exception), this class is called what it's called. */ @Serializable(with = JwsAlgorithmSerializer::class) -enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, SpecializedDataIntegrityAlgorithm { +enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, SpecializedSignatureAlgorithm { ES256("ES256"), ES384("ES384"), @@ -41,58 +42,28 @@ enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, Spe */ NON_JWS_SHA1_WITH_RSA("RS1"); - - companion object { - /** - * encompasses only signature algorithms, filtering out MACs - */ - val signatureAlgorithms = listOf( - ES256, - ES384, - ES512, - - PS256, - PS384, - PS512, - - RS256, - RS384, - RS512, - NON_JWS_SHA1_WITH_RSA - ).map { it.algorithm as SignatureAlgorithm } - - /** - * Encompasses only MACs, filtering our signature algorithms - */ - val messageAuthenticationCodes = listOf(HS256, HS384, HS512).map { it.algorithm as MessageAuthenticationCode } - + val digest: Digest get() = when(this) { + NON_JWS_SHA1_WITH_RSA -> Digest.SHA1 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 } - val digest: Digest - get() = when (this) { - NON_JWS_SHA1_WITH_RSA -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 - } - - override val algorithm: DataIntegrityAlgorithm - get() = when (this) { - ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, this.ecCurve!!) - HS256, HS384, HS512 -> HMAC.byDigest(this.digest) - PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) - NON_JWS_SHA1_WITH_RSA, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) - } + override val algorithm: SignatureAlgorithm get() = when (this) { + ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, this.ecCurve!!) + HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) + PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this. digest, RSAPadding.PSS) + NON_JWS_SHA1_WITH_RSA, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + } /** The curve to create signatures on. * This is fixed by RFC7518, as opposed to X.509 where other combinations are possible. */ - val ecCurve: ECCurve? - get() = when (this) { - ES256 -> ECCurve.SECP_256_R_1 - ES384 -> ECCurve.SECP_384_R_1 - ES512 -> ECCurve.SECP_521_R_1 - else -> null - } + val ecCurve: ECCurve? get() = when (this) { + ES256 -> ECCurve.SECP_256_R_1 + ES384 -> ECCurve.SECP_384_R_1 + ES512 -> ECCurve.SECP_521_R_1 + else -> null + } } object JwsAlgorithmSerializer : KSerializer { @@ -109,12 +80,8 @@ object JwsAlgorithmSerializer : KSerializer { } } - -fun SpecializedDataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = - algorithm.toJwsAlgorithm() - /** Tries to find a matching JWS algorithm. Note that JWS imposes curve restrictions on ECDSA based on the digest. */ -fun DataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = catching { +fun SignatureAlgorithm.toJwsAlgorithm() = catching { when (this) { is SignatureAlgorithm.ECDSA -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.ES256 @@ -122,7 +89,6 @@ fun DataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = catching Digest.SHA512 -> JwsAlgorithm.ES512 else -> throw IllegalArgumentException("ECDSA with ${this.digest} is unsupported by JWS") } - is SignatureAlgorithm.RSA -> when (this.padding) { RSAPadding.PKCS1 -> when (this.digest) { Digest.SHA1 -> JwsAlgorithm.NON_JWS_SHA1_WITH_RSA @@ -130,26 +96,24 @@ fun DataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = catching Digest.SHA384 -> JwsAlgorithm.RS384 Digest.SHA512 -> JwsAlgorithm.RS512 } - RSAPadding.PSS -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.PS256 Digest.SHA384 -> JwsAlgorithm.PS384 Digest.SHA512 -> JwsAlgorithm.PS512 else -> throw IllegalArgumentException("RSA-PSS with ${this.digest} is unsupported by JWS") } - } - - is HMAC -> when (this.digest) { + is SignatureAlgorithm.HMAC -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.HS256 Digest.SHA384 -> JwsAlgorithm.HS384 Digest.SHA512 -> JwsAlgorithm.HS512 else -> throw IllegalArgumentException("HMAC with ${this.digest} is unsupported by JWS") } - - else -> throw IllegalArgumentException("UnsupportedAlgorithm $this") - } } +/** Tries to find a matching JWS algorithm. Note that JWS imposes curve restrictions on ECDSA based on the digest. */ +fun SpecializedSignatureAlgorithm.toJwsAlgorithm() = + this.algorithm.toJwsAlgorithm() + diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt index 6c31b6e5..0bc1838e 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt @@ -74,7 +74,7 @@ data class JwsSigned( val payload = inputParts[1] val signature = with(inputParts[2]) { when (val curve = header.algorithm.ecCurve) { - null -> CryptoSignature.RSA(this) + null -> CryptoSignature.RSAorHMAC(this) else -> CryptoSignature.EC.fromRawBytes(curve, this) } } diff --git a/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt b/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt index d8f2610c..69b0c60f 100644 --- a/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt +++ b/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt @@ -17,9 +17,9 @@ class ConversionTest : FreeSpec({ } } "JWS -> X509 -> JWS is stable" - { - withData(JwsAlgorithm.signatureAlgorithms /*here we filter s.t. only sig alg are fed into the test*/) { + withData(JwsAlgorithm.entries) { it.toX509SignatureAlgorithm().getOrNull()?.let { x509 -> - x509.toJwsAlgorithm() shouldSucceedWith it.toJwsAlgorithm().getOrThrow() /* and here we convert the sig algs back to JWS alg*/ + x509.toJwsAlgorithm() shouldSucceedWith it } } } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt index b5033a80..d8f59e9d 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt @@ -217,7 +217,7 @@ sealed interface CryptoSignature : Asn1Encodable { } - class RSA private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { + class RSAorHMAC private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { constructor(rawBytes: ByteArray) : this(rawBytes, null) constructor(x509Element: Asn1Primitive) : this(null, x509Element) @@ -241,16 +241,16 @@ sealed interface CryptoSignature : Asn1Encodable { if (this === other) return true if (other == null || this::class != other::class) return false - other as RSA + other as RSAorHMAC return signature == other.signature } - companion object : Asn1Decodable { + companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun doDecode(src: Asn1Element): RSA { + override fun doDecode(src: Asn1Element): RSAorHMAC { src as Asn1Primitive - return RSA(src) + return RSAorHMAC(src) } } } @@ -259,7 +259,7 @@ sealed interface CryptoSignature : Asn1Encodable { @Throws(Asn1Exception::class) override fun doDecode(src: Asn1Element): CryptoSignature = runRethrowing { when (src.tag) { - Asn1Element.Tag.BIT_STRING -> RSA.decodeFromTlv(src) + Asn1Element.Tag.BIT_STRING -> RSAorHMAC.decodeFromTlv(src) Asn1Element.Tag.SEQUENCE -> EC.decodeFromTlv(src) else -> throw Asn1Exception("Unknown Signature Format") diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt deleted file mode 100644 index d85fb548..00000000 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt +++ /dev/null @@ -1,14 +0,0 @@ -package at.asitplus.signum.indispensable - -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode - -/** - * Umbrella interface encompassing _data integrity algorithms_: - * * Message Authentication Codes ([MessageAuthenticationCode]) - * * Digital Signatures ([SignatureAlgorithm]) - */ -interface DataIntegrityAlgorithm{ - companion object { - val entries: Iterable = MessageAuthenticationCode.entries + SignatureAlgorithm.entries - } -} \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt index 84739935..1659a602 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt @@ -5,7 +5,11 @@ enum class RSAPadding { PSS; } -sealed interface SignatureAlgorithm : DataIntegrityAlgorithm { +sealed interface SignatureAlgorithm { + data class HMAC( + /** The digest to use */ + val digest: Digest + ) : SignatureAlgorithm data class ECDSA( /** The digest to apply to the data, or `null` to directly process the raw data. */ @@ -34,22 +38,15 @@ sealed interface SignatureAlgorithm : DataIntegrityAlgorithm { val RSAwithSHA384andPSSPadding = RSA(Digest.SHA384, RSAPadding.PSS) val RSAwithSHA512andPSSPadding = RSA(Digest.SHA512, RSAPadding.PSS) - val entries: Iterable = listOf( - ECDSAwithSHA256, - ECDSAwithSHA384, - ECDSAwithSHA512, - - RSAwithSHA256andPSSPadding, - RSAwithSHA384andPSSPadding, - RSAwithSHA512andPSSPadding, - - RSAwithSHA256andPKCS1Padding, - RSAwithSHA384andPKCS1Padding, - RSAwithSHA512andPKCS1Padding - ) + @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) + val HMACwithSHA256 = HMAC(Digest.SHA256) + @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) + val HMACwithSHA384 = HMAC(Digest.SHA384) + @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) + val HMACwithSHA512 = HMAC(Digest.SHA512) } } -interface SpecializedDataIntegrityAlgorithm { - val algorithm: DataIntegrityAlgorithm +interface SpecializedSignatureAlgorithm { + val algorithm: SignatureAlgorithm } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt index e3c4d9f5..43acfc3d 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt @@ -18,13 +18,18 @@ import kotlinx.serialization.encoding.Encoder enum class X509SignatureAlgorithm( override val oid: ObjectIdentifier, val isEc: Boolean = false -) : Asn1Encodable, Identifiable, SpecializedDataIntegrityAlgorithm { +) : Asn1Encodable, Identifiable, SpecializedSignatureAlgorithm { // ECDSA with SHA-size ES256(KnownOIDs.ecdsaWithSHA256, true), ES384(KnownOIDs.ecdsaWithSHA384, true), ES512(KnownOIDs.ecdsaWithSHA512, true), + // HMAC-size with SHA-size + HS256(KnownOIDs.hmacWithSHA256), + HS384(KnownOIDs.hmacWithSHA384), + HS512(KnownOIDs.hmacWithSHA512), + // RSASSA-PSS with SHA-size PS256(KnownOIDs.rsaPSS), PS384(KnownOIDs.rsaPSS), @@ -79,6 +84,7 @@ enum class X509SignatureAlgorithm( PS512 -> encodePSSParams(512) + HS256, HS384, HS512, RS256, RS384, RS512, RS1 -> Asn1.Sequence { +oid +Null() @@ -87,14 +93,15 @@ enum class X509SignatureAlgorithm( val digest: Digest get() = when(this) { RS1 -> Digest.SHA1 - ES256, PS256, RS256 -> Digest.SHA256 - ES384, PS384, RS384 -> Digest.SHA384 - ES512, PS512, RS512 -> Digest.SHA512 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 } override val algorithm: SignatureAlgorithm get() = when(this) { ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, null) + HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) } @@ -112,7 +119,8 @@ enum class X509SignatureAlgorithm( ES512.oid, ES384.oid, ES256.oid -> fromOid(oid) RS1.oid -> RS1 - RS256.oid, RS384.oid, RS512.oid-> fromOid(oid).also { + RS256.oid, RS384.oid, RS512.oid, + HS256.oid, HS384.oid, HS512.oid -> fromOid(oid).also { val tag = src.nextChild().tag if (tag != Asn1Element.Tag.NULL) throw Asn1TagMismatchException(Asn1Element.Tag.NULL, tag, "RSA Params not allowed.") @@ -184,8 +192,17 @@ fun SignatureAlgorithm.toX509SignatureAlgorithm() = catching { else -> throw IllegalArgumentException("Digest ${this.digest} is unsupported by X.509 RSA-PSS") } } + is SignatureAlgorithm.HMAC -> when (this.digest) { + Digest.SHA256 -> X509SignatureAlgorithm.HS256 + Digest.SHA384 -> X509SignatureAlgorithm.HS384 + Digest.SHA512 -> X509SignatureAlgorithm.HS512 + else -> throw IllegalArgumentException("Digest ${this.digest} is unsupported by X.509 HMAC") + } } } +/** Finds a X.509 signature algorithm matching this algorithm. Curve restrictions are not preserved. */ +fun SpecializedSignatureAlgorithm.toX509SignatureAlgorithm() = + this.algorithm.toX509SignatureAlgorithm() object X509SignatureAlgorithmSerializer : KSerializer { diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt new file mode 100644 index 00000000..6c7810b8 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt @@ -0,0 +1,40 @@ +package at.asitplus.signum.indispensable.mac + +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.asn1.Identifiable +import at.asitplus.signum.indispensable.asn1.KnownOIDs +import at.asitplus.signum.indispensable.asn1.ObjectIdentifier +import at.asitplus.signum.indispensable.misc.BitLength + +sealed interface MAC { + /** output size of MAC */ + val outputLength: BitLength + + companion object { + val entries: Iterable = HMAC.entries + } +} + +/** + * RFC 2104 HMAC + */ +enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MAC, Identifiable { + SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), + SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), + SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), + SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), + ; + + override fun toString() = "HMAC-$digest" + + companion object { + operator fun invoke(digest: Digest) = when (digest) { + Digest.SHA1 -> SHA1 + Digest.SHA256 -> SHA256 + Digest.SHA384 -> SHA384 + Digest.SHA512 -> SHA512 + } + } + + override val outputLength: BitLength get() = digest.outputLength +} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt deleted file mode 100644 index cc507420..00000000 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt +++ /dev/null @@ -1,61 +0,0 @@ -package at.asitplus.signum.indispensable.mac - -import at.asitplus.signum.indispensable.DataIntegrityAlgorithm -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.asn1.* -import at.asitplus.signum.indispensable.asn1.encoding.Asn1 -import at.asitplus.signum.indispensable.asn1.encoding.Asn1.Null -import at.asitplus.signum.indispensable.asn1.encoding.readNull -import at.asitplus.signum.indispensable.misc.BitLength - -sealed interface MessageAuthenticationCode : DataIntegrityAlgorithm { - /** output size of MAC */ - val outputLength: BitLength - - companion object { - val entries: Iterable = HMAC.entries - } -} - -/** - * RFC 2104 HMAC - */ -enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MessageAuthenticationCode, Identifiable, - Asn1Encodable { - SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), - SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), - SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), - SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), - ; - - override fun toString() = "HMAC-$digest" - - override fun encodeToTlv(): Asn1Sequence = Asn1.Sequence { - +oid - +Null() - } - - - companion object : Asn1Decodable { - - fun byOID(oid: ObjectIdentifier): HMAC? = entries.find { it.oid == oid } - - fun byDigest(digest: Digest): HMAC = entries.find { it.digest == digest }!! - - operator fun invoke(digest: Digest) = when (digest) { - Digest.SHA1 -> SHA1 - Digest.SHA256 -> SHA256 - Digest.SHA384 -> SHA384 - Digest.SHA512 -> SHA512 - } - - override fun doDecode(src: Asn1Sequence): HMAC { - val oid = src.nextChild().asPrimitive().readOid() - src.nextChild().asPrimitive().readNull() - require(!src.hasMoreChildren()) { "Superfluous ANS.1 data in HMAC" } - return byOID(oid) ?: throw Asn1OidException(oid) - } - } - - override val outputLength: BitLength get() = digest.outputLength -} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt index 147e9c9a..e9f38b49 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt @@ -224,7 +224,7 @@ constructor( val CryptoSignature.x509Encoded get() = when (this) { is CryptoSignature.EC -> encodeToDer().encodeToAsn1BitStringPrimitive() - is CryptoSignature.RSA -> encodeToTlv() + is CryptoSignature.RSAorHMAC -> encodeToTlv() } /** @@ -235,7 +235,7 @@ val CryptoSignature.x509Encoded fun CryptoSignature.Companion.fromX509Encoded(alg: X509SignatureAlgorithm, it: Asn1Primitive) = when (alg.isEc) { true -> CryptoSignature.EC.decodeFromDer(it.asAsn1BitString().rawBytes) - false -> CryptoSignature.RSA.decodeFromTlv(it) + false -> CryptoSignature.RSAorHMAC.decodeFromTlv(it) } /** diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt index b7314d76..2ba35669 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt @@ -7,7 +7,7 @@ import at.asitplus.signum.indispensable.asn1.KnownOIDs import at.asitplus.signum.indispensable.asn1.ObjectIdentifier import at.asitplus.signum.indispensable.asn1.encoding.encodeTo8Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.mac.MAC import at.asitplus.signum.indispensable.misc.BitLength import at.asitplus.signum.indispensable.misc.bit import kotlin.contracts.ExperimentalContracts @@ -110,7 +110,7 @@ sealed interface SymmetricEncryptionAlgorithm, out interface Integrated : Authenticated - interface WithDedicatedMac : + interface WithDedicatedMac : Authenticated, I, KeyType.WithDedicatedMacKey> } @@ -311,7 +311,7 @@ sealed interface AuthCapability { * An authenticated cipher construction based on an unauthenticated cipher with a dedicated MAC function, requiring a dedicated MAC key. * _Encrypt-then-MAC_ */ - class WithDedicatedMac( + class WithDedicatedMac( /** * The inner unauthenticated cipher */ @@ -406,14 +406,14 @@ val DefaultDedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation /** * Typealias defining the signature of the lambda for defining a custom MAC input calculation scheme. */ -typealias DedicatedMacInputCalculation = MessageAuthenticationCode.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray +typealias DedicatedMacInputCalculation = MAC.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray /** * The default dedicated mac input calculation as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1), authenticating all inputs: * `AAD || IV || Ciphertext || AAD Length`, where AAD_length is a 64 bit big-endian representation of the aad length in bits */ val DefaultDedicatedMacInputCalculation: DedicatedMacInputCalculation = - fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = + fun MAC.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = aad + iv + ciphertext + (aad.size.toLong()*8L).encodeTo8Bytes() /** diff --git a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt index 0b3c51f5..a17391bb 100644 --- a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt +++ b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt @@ -20,9 +20,9 @@ class CryptoSignatureTest : FreeSpec({ val ec1 = CryptoSignature.EC.fromRS(first.toBigInteger(), second.toBigInteger()) val ec2 = CryptoSignature.EC.fromRS(first.toBigInteger(), second.toBigInteger()) val ec3 = CryptoSignature.EC.fromRS(second.toBigInteger(), first.toBigInteger()) - val rsa1 = CryptoSignature.RSA(first.toTwosComplementByteArray()) - val rsa2 = CryptoSignature.RSA(first.toTwosComplementByteArray()) - val rsa3 = CryptoSignature.RSA(second.toTwosComplementByteArray()) + val rsa1 = CryptoSignature.RSAorHMAC(first.toTwosComplementByteArray()) + val rsa2 = CryptoSignature.RSAorHMAC(first.toTwosComplementByteArray()) + val rsa3 = CryptoSignature.RSAorHMAC(second.toTwosComplementByteArray()) ec1 shouldBe ec1 ec1 shouldBe ec2 diff --git a/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt b/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt index f5bbcab4..b1806733 100644 --- a/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt +++ b/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt @@ -40,8 +40,13 @@ val SignatureAlgorithm.secKeyAlgorithm: SecKeyAlgorithm } } + is SignatureAlgorithm.HMAC -> TODO("HMAC is unsupported") }!! +val SpecializedSignatureAlgorithm.secKeyAlgorithm + get() = + this.algorithm.secKeyAlgorithm + val SignatureAlgorithm.secKeyAlgorithmPreHashed: SecKeyAlgorithm get() = when (this) { is SignatureAlgorithm.ECDSA -> { @@ -72,16 +77,17 @@ val SignatureAlgorithm.secKeyAlgorithmPreHashed: SecKeyAlgorithm } } + is SignatureAlgorithm.HMAC -> TODO("HMAC is unsupported") }!! -val X509SignatureAlgorithm.secKeyAlgorithmPreHashed +val SpecializedSignatureAlgorithm.secKeyAlgorithmPreHashed get() = this.algorithm.secKeyAlgorithmPreHashed val CryptoSignature.iosEncoded get() = when (this) { is CryptoSignature.EC -> this.encodeToDer() - is CryptoSignature.RSA -> this.rawByteArray + is CryptoSignature.RSAorHMAC -> this.rawByteArray } fun CryptoPublicKey.toSecKey() = catching { diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt index 4aa6e241..fb045341 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt @@ -1,6 +1,6 @@ package at.asitplus.signum.indispensable -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.mac.MAC import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -40,5 +40,5 @@ inline fun FreeSpec.enumConsistencyTest() { } class EnumConsistencyTests : FreeSpec({ - enumConsistencyTest() + enumConsistencyTest() }) \ No newline at end of file diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt index 9923e11b..d018fa5f 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt @@ -72,7 +72,7 @@ class SignatureCodecTest : FreeSpec({ sign() } - CryptoSignature.RSA.parseFromJca(sig).jcaSignatureBytes shouldBe sig + CryptoSignature.RSAorHMAC.parseFromJca(sig).jcaSignatureBytes shouldBe sig CryptoSignature.parseFromJca( sig, SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) @@ -99,7 +99,7 @@ class SignatureCodecTest : FreeSpec({ val bcSig = (ASN1Sequence.fromByteArray(certificateHolder.encoded) as DLSequence).elementAt(2) .toASN1Primitive().encoded - CryptoSignature.RSA.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig + CryptoSignature.RSAorHMAC.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig CryptoSignature.parseFromJca( certificateHolder.signature, SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt index 8365699d..f9a2e13d 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt @@ -12,6 +12,7 @@ infix fun KmmResult.shouldSucceedWith(b: T) : T = class X509ConversionTests : FreeSpec({ "X509 -> Alg -> X509 is stable" - { withData(X509SignatureAlgorithm.entries) { + it.toX509SignatureAlgorithm() shouldSucceedWith it it.algorithm.toX509SignatureAlgorithm() shouldSucceedWith it } } diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt index 34fe376d..0e1631c8 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt @@ -390,7 +390,7 @@ sealed class AndroidKeystoreSigner private constructor( return@signCatching when (this@AndroidKeystoreSigner) { is ECDSA -> CryptoSignature.EC.parseFromJca(jcaSig).withCurve(publicKey.curve) - is RSA -> CryptoSignature.RSA.parseFromJca(jcaSig) + is RSA -> CryptoSignature.RSAorHMAC.parseFromJca(jcaSig) } }} diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 1677a67f..2707500b 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -63,7 +63,7 @@ sealed class AndroidEphemeralSigner (internal val privateKey: PrivateKey) : Sign override val publicKey: CryptoPublicKey.RSA, override val signatureAlgorithm: SignatureAlgorithm.RSA) : AndroidEphemeralSigner(privateKey), Signer.RSA { - override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSA.parseFromJca(bytes) + override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSAorHMAC.parseFromJca(bytes) @SecretExposure override fun exportPrivateKey() = diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index c45d95ab..1019415b 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -79,7 +79,7 @@ internal actual fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier @JvmSynthetic internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSA, + data: SignatureInput, signature: CryptoSignature.RSAorHMAC, config: PlatformVerifierConfiguration) { getRSAInstance(signatureAlgorithm, config).run { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt index 6dd8b911..bb733132 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt @@ -3,19 +3,19 @@ package at.asitplus.signum.supreme.mac import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.mac.MAC import at.asitplus.signum.internals.xor import at.asitplus.signum.supreme.hash.digest -fun MessageAuthenticationCode.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) -fun MessageAuthenticationCode.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) +fun MAC.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) +fun MAC.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) private val HMAC.blockLength get() = digest.inputBlockSize.bytes.toInt() private val HMAC.innerPad get() = ByteArray(blockLength) { 0x36 } private val HMAC.outerPad get() = ByteArray(blockLength) { 0x5C } -fun MessageAuthenticationCode.mac(key: ByteArray, msg: Sequence): KmmResult = catching { +fun MAC.mac(key: ByteArray, msg: Sequence): KmmResult = catching { when (this@mac) { is HMAC -> hmac(key, msg) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt index df38051b..d12ce4e4 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt @@ -154,6 +154,7 @@ fun SignatureAlgorithm.signerFor(privateKey: CryptoPrivateKey.WithPublicKey<*>): (this is SignatureAlgorithm.RSA && privateKey is CryptoPrivateKey.RSA)) { when (this) { is SignatureAlgorithm.ECDSA -> this.signerFor(privateKey as CryptoPrivateKey.EC.WithPublicKey) + is SignatureAlgorithm.HMAC -> KmmResult.failure(UnsupportedOperationException("HMAC is not yet supported!")) is SignatureAlgorithm.RSA -> this.signerFor(privateKey as CryptoPrivateKey.RSA) } } else { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt index 12b18541..b0a8cd79 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt @@ -6,9 +6,8 @@ import at.asitplus.recoverCatching import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedDataIntegrityAlgorithm +import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import at.asitplus.signum.ecmath.straussShamir -import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.supreme.dsl.DSL import at.asitplus.signum.supreme.UnsupportedCryptoException import at.asitplus.signum.supreme.dsl.DSLConfigureFn @@ -97,7 +96,7 @@ internal expect fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier /** data is guaranteed to be in RAW_BYTES format. failure should throw. */ internal expect fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSA, + data: SignatureInput, signature: CryptoSignature.RSAorHMAC, config: PlatformVerifierConfiguration) class PlatformRSAVerifier @@ -110,7 +109,7 @@ class PlatformRSAVerifier checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier(signatureAlgorithm, publicKey, config) } override fun verify(data: SignatureInput, sig: CryptoSignature) = catching { - require (sig is CryptoSignature.RSA) + require (sig is CryptoSignature.RSAorHMAC) { "Attempted to validate non-RSA signature using RSA public key" } if (data.format != null) throw UnsupportedOperationException("RSA with pre-hashed input is unsupported") @@ -192,6 +191,8 @@ private fun SignatureAlgorithm.verifierForImpl else verifierForImpl(publicKey, configure, allowKotlin) } + is SignatureAlgorithm.HMAC -> + KmmResult.failure(IllegalArgumentException("HMAC is unsupported")) } /** @@ -264,11 +265,11 @@ private fun SignatureAlgorithm.RSA.verifierForImpl catching { PlatformRSAVerifier(this, publicKey, configure) } /** @see [SignatureAlgorithm.verifierFor] */ -fun X509SignatureAlgorithm.verifierFor +fun SpecializedSignatureAlgorithm.verifierFor (publicKey: CryptoPublicKey, configure: ConfigurePlatformVerifier = null) = this.algorithm.verifierFor(publicKey, configure) /** @see [SignatureAlgorithm.platformVerifierFor] */ -fun X509SignatureAlgorithm.platformVerifierFor +fun SpecializedSignatureAlgorithm.platformVerifierFor (publicKey: CryptoPublicKey, configure: ConfigurePlatformVerifier = null) = this.algorithm.platformVerifierFor(publicKey, configure) diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt index cacf5b12..35030483 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt @@ -30,7 +30,7 @@ open class RSAVerifierCommonTests : FreeSpec({ val key = CryptoPublicKey.decodeFromDer(Base64.decode(test.key)) as CryptoPublicKey.RSA val b64msg = test.msg val msg = Base64.decode(b64msg) - val sig = CryptoSignature.RSA(Base64.decode(test.sig)) + val sig = CryptoSignature.RSAorHMAC(Base64.decode(test.sig)) } /* diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 61dbca4f..5659baa2 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -1,7 +1,7 @@ import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.asn1.encoding.encodeTo4Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.mac.MAC import at.asitplus.signum.indispensable.misc.bit import at.asitplus.signum.indispensable.misc.bytes import at.asitplus.signum.indispensable.symmetric.* @@ -409,7 +409,7 @@ class `00SymmetricTest` : FreeSpec({ withData( nameFn = { it.first }, "Default" to DefaultDedicatedMacInputCalculation, - "Oklahoma MAC" to fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = + "Oklahoma MAC" to fun MAC.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = "Oklahoma".encodeToByteArray() + (iv ?: byteArrayOf()) + (aad ?: byteArrayOf()) + ciphertext) { (_, macInputFun) -> withData( diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt index 567271a8..a52afedc 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt @@ -279,7 +279,7 @@ sealed class IosSigner(final override val alias: String, ) } override fun bytesToSignature(sigBytes: ByteArray) = - CryptoSignature.RSA(sigBytes) + CryptoSignature.RSAorHMAC(sigBytes) } } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index cc60839f..b5275698 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -37,7 +37,7 @@ sealed class EphemeralSigner(internal val privateKey: OwnedCFValue) : }.takeFromCF().toByteArray() return@signCatching when (val pubkey = publicKey) { is CryptoPublicKey.EC -> CryptoSignature.EC.decodeFromDer(signatureBytes).withCurve(pubkey.curve) - is CryptoPublicKey.RSA -> CryptoSignature.RSA(signatureBytes) + is CryptoPublicKey.RSA -> CryptoSignature.RSAorHMAC(signatureBytes) } } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index 8a2e8a5f..3e79fa84 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -77,6 +77,6 @@ internal actual fun verifyECDSAImpl internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSA, + data: SignatureInput, signature: CryptoSignature.RSAorHMAC, config: PlatformVerifierConfiguration) = verifyImpl(signatureAlgorithm, publicKey, data, signature, config) diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 1af30bea..84cfb311 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -68,7 +68,7 @@ sealed class EphemeralSigner (internal val privateKey: PrivateKey, private val p override val publicKey: CryptoPublicKey.RSA, override val signatureAlgorithm: SignatureAlgorithm.RSA) : EphemeralSigner(privateKey, config.provider), Signer.RSA { - override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSA.parseFromJca(bytes) + override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSAorHMAC.parseFromJca(bytes) @SecretExposure final override fun exportPrivateKey() = (privateKey as RSAPrivateKey).toCryptoPrivateKey() diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt index cc2ebf11..8b2d2921 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt @@ -56,6 +56,7 @@ fun SignatureAlgorithm.signerFor( signatureAlgorithm = this ) + is SignatureAlgorithm.HMAC -> throw UnsupportedOperationException("HMAC is not yet supported!") is SignatureAlgorithm.RSA -> EphemeralSigner.RSA( config = DSL.resolve( ::EphemeralSignerConfiguration, diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index a70d555b..2447e582 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -82,7 +82,7 @@ internal actual fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier @JvmSynthetic internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSA, + data: SignatureInput, signature: CryptoSignature.RSAorHMAC, config: PlatformVerifierConfiguration) { getRSAInstance(signatureAlgorithm, config).run { diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt index 0c32d0f5..ccf5f016 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt @@ -114,8 +114,8 @@ class JKSProviderTest : FreeSpec({ } CryptoSignature.parseFromJca(signature.jcaSignatureBytes, signer.signatureAlgorithm) shouldBe signature when (signer.signatureAlgorithm) { - is SignatureAlgorithm.RSA -> - CryptoSignature.RSA.parseFromJca(signature.jcaSignatureBytes) shouldBe signature + is SignatureAlgorithm.RSA, is SignatureAlgorithm.HMAC -> + CryptoSignature.RSAorHMAC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature is SignatureAlgorithm.ECDSA -> CryptoSignature.EC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature } diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index 87bcd090..5cada8a9 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -138,9 +138,7 @@ class JvmSymmetricTest : FreeSpec({ alg.sealedBoxFrom( own.algorithm.randomNonce(), own.encryptedData - ).getOrThrow().decrypt(secretKey).onSuccess { - it shouldNotBe data - } + ).getOrThrow().decrypt(secretKey) shouldNot succeed } } --- .github/workflows/test-jvm.yml | 2 +- CHANGELOG.md | 8 - .../kotlin/at/asitplus/cryptotest/App.kt | 138 +++++++++++++----- docs/docs/indispensable.md | 6 +- .../indispensable/asn1/Asn1Exception.kt | 4 +- .../indispensable/cosef/CoseAlgorithm.kt | 79 +++------- .../cosef/CoseSignedSerializer.kt | 4 +- .../indispensable/cosef/CoseEqualsTest.kt | 16 +- .../cosef/CoseSerializationTest.kt | 16 +- .../src/jvmTest/kotlin/ConversionTests.kt | 4 +- .../indispensable/josef/JwsAlgorithm.kt | 94 ++++-------- .../signum/indispensable/josef/JwsSigned.kt | 2 +- .../indispensable/josef/ConversionTest.kt | 4 +- .../signum/indispensable/CryptoSignature.kt | 12 +- .../indispensable/DataIntegrityAlgorithm.kt | 14 -- .../indispensable/SignatureAlgorithm.kt | 29 ++-- .../indispensable/X509SignatureAlgorithm.kt | 27 +++- .../asitplus/signum/indispensable/mac/MAC.kt | 40 +++++ .../mac/MessageAuthenticationCode.kt | 61 -------- .../indispensable/pki/X509Certificate.kt | 4 +- .../symmetric/SymmetricEncryptionAlgorithm.kt | 10 +- .../commonTest/kotlin/CryptoSignatureTest.kt | 6 +- .../iosMain/kotlin/CommonCryptoExtensions.kt | 10 +- .../indispensable/EnumConsistencyTests.kt | 4 +- .../indispensable/SignatureCodecTest.kt | 4 +- .../indispensable/pki/X509ConversionTests.kt | 1 + .../supreme/os/AndroidKeyStoreProvider.kt | 2 +- .../signum/supreme/sign/EphemeralKeysImpl.kt | 2 +- .../signum/supreme/sign/VerifierImpl.kt | 2 +- .../at/asitplus/signum/supreme/mac/MAC.kt | 8 +- .../at/asitplus/signum/supreme/sign/Signer.kt | 1 + .../asitplus/signum/supreme/sign/Verifier.kt | 13 +- .../supreme/sign/RSAVerifierCommonTests.kt | 2 +- .../supreme/symmetric/00SymmetricTest.kt | 4 +- .../signum/supreme/os/IosKeychainProvider.kt | 2 +- .../signum/supreme/sign/EphemeralKeysImpl.kt | 2 +- .../signum/supreme/sign/VerifierImpl.kt | 2 +- .../signum/supreme/sign/EphemeralKeysImpl.kt | 2 +- .../supreme/sign/PrivateKeySignerImpl.kt | 1 + .../signum/supreme/sign/VerifierImpl.kt | 2 +- .../signum/supreme/os/JKSProviderTest.kt | 4 +- .../asitplus/signum/supreme/symmetric/Test.kt | 4 +- 42 files changed, 315 insertions(+), 337 deletions(-) delete mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt delete mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt diff --git a/.github/workflows/test-jvm.yml b/.github/workflows/test-jvm.yml index 239a9a7b5..8bbf76206 100644 --- a/.github/workflows/test-jvm.yml +++ b/.github/workflows/test-jvm.yml @@ -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-cosef/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-josef/build/test-results/**/TEST*.xml,supreme/build/test-results/**/TEST*.xml reporter: java-junit diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f7e8134..6e4f65a05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,14 +13,6 @@ * 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 - * **Clean up `RSAorHMAC` mess** - * Introduce `DataAuthenticationAlgorithm` encompassing Signatures and MACs - * Rename `RSAorHMAC` to `RSA` and introduce dedicated `MessageAuthenticationCode` and `HMAC` classes - * This caused **some breaking shifts and cleanups** in conversion methods between indispensable, josef, cosef, and JCA types - * There is no direct mapping between `SignatureAlgorithm` and COSE/JOSE algorithms anymore, because JOSE/COSE also incorporate MAC - * Hence, for both `JwsAlgorithm` and `CoseAlgorithm`, the following properties have been introduced on the companions: - * `signatureAlgorithms` - * `messageAuthenticationCodes` * Symmetric Encryption * Supported Algorithms * AES diff --git a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt index 201ab7452..6bff9f32e 100644 --- a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt +++ b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size @@ -45,26 +46,28 @@ import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.ECCurve import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm +import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.nativeDigest -import at.asitplus.signum.indispensable.pki.X509Certificate import at.asitplus.signum.supreme.dsl.PREFERRED import at.asitplus.signum.supreme.sign.Signer import at.asitplus.signum.supreme.sign.makeVerifier import at.asitplus.signum.supreme.sign.verify import at.asitplus.cryptotest.theme.AppTheme 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.supreme.agree.Ephemeral import at.asitplus.signum.supreme.asKmmResult import at.asitplus.signum.supreme.os.PlatformSignerConfigurationBase -import at.asitplus.signum.supreme.os.PlatformSigningKeyConfigurationBase -import at.asitplus.signum.supreme.os.PlatformSigningProvider import at.asitplus.signum.supreme.os.SignerConfiguration import at.asitplus.signum.supreme.os.SigningProvider import at.asitplus.signum.supreme.sign.Verifier import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier -import io.ktor.util.decodeBase64Bytes +import io.ktor.util.encodeBase64 import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -97,7 +100,10 @@ private class getter(private val fn: () -> T) { operator fun getValue(nothing: Nothing?, property: KProperty<*>): T = fn() } -@OptIn(ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class) +@OptIn( + ExperimentalCoroutinesApi::class, + SecretExposure::class +) @Composable internal fun App() { @@ -114,7 +120,7 @@ internal fun App() { X509SignatureAlgorithm.RS512 ) var keyAlgorithm by remember { - mutableStateOf( + mutableStateOf( X509SignatureAlgorithm.ES256 ) } @@ -137,6 +143,12 @@ internal fun App() { } val signingPossible by getter { currentKey?.isSuccess == true } var signatureData by remember { mutableStateOf?>(null) } + var ephemeralKey by remember { + mutableStateOf( + null + ) + } + var agreedKey by remember { mutableStateOf?>(null) } val signatureDataStr by getter { signatureData?.fold(onSuccess = Any::toString) { Napier.e("Signature failed", it) @@ -157,6 +169,14 @@ internal fun App() { var genTextOverride by remember { mutableStateOf(null) } val genText by getter { genTextOverride ?: "Generate" } + fun genEphemeralKey(){ + ephemeralKey = if (currentKey?.getOrNull() is CryptoPublicKey.EC) + KeyAgreementPrivateValue.ECDH.Ephemeral((currentKey!!.getOrThrow() as CryptoPublicKey.EC).curve).getOrNull() + else null + agreedKey=null + } + + Column( modifier = Modifier.fillMaxSize().verticalScroll(ScrollState(0), enabled = true) .windowInsetsPadding(WindowInsets.safeDrawing) @@ -307,12 +327,16 @@ internal fun App() { canGenerate = false genTextOverride = "Creating…" currentSigner = Provider.createSigningKey(ALIAS) { + when (val alg = keyAlgorithm.algorithm) { is SignatureAlgorithm.ECDSA -> { this@createSigningKey.ec { - curve = alg.requiredCurve ?: - ECCurve.entries.find { it.nativeDigest == alg.digest }!! + curve = alg.requiredCurve + ?: ECCurve.entries.find { it.nativeDigest == alg.digest }!! digests = setOf(alg.digest) + purposes { + keyAgreement = true + } } } @@ -327,29 +351,27 @@ internal fun App() { else -> error("unreachable") } - if (this is PlatformSigningKeyConfigurationBase) { - signer(SIGNER_CONFIG) + signer(SIGNER_CONFIG) - val timeout = runCatching { - biometricAuth.substringBefore("s").trim().toInt() - }.getOrNull() + val timeout = runCatching { + biometricAuth.substringBefore("s").trim().toInt() + }.getOrNull() - if (attestation || timeout != null) { - hardware { - backing = PREFERRED - if (attestation) { - attestation { - challenge = Random.nextBytes(16) - } + if (attestation || timeout != null) { + hardware { + backing = PREFERRED + if (attestation) { + attestation { + challenge = Random.nextBytes(16) } + } - if (timeout != null) { - protection { - this.timeout = timeout.seconds - factors { - biometry = true - deviceLock = true - } + if (timeout != null) { + protection { + this.timeout = timeout.seconds + factors { + biometry = true + deviceLock = true } } } @@ -362,6 +384,8 @@ internal fun App() { Napier.w { "Signing possible: ${currentKey?.isSuccess}" } canGenerate = true genTextOverride = null + + genEphemeralKey() } }, modifier = Modifier.padding(start = 16.dp) @@ -385,6 +409,7 @@ internal fun App() { //loadPubKey().let { Napier.w { "PubKey retrieved from native: $it" } } canGenerate = true genTextOverride = null + genEphemeralKey() } }, modifier = Modifier.padding(start = 16.dp, end = 16.dp) @@ -432,7 +457,6 @@ internal fun App() { Button( onClick = { - Napier.w { "input: $inputData" } Napier.w { "signingKey: $currentKey" } CoroutineScope(context).launch { @@ -441,13 +465,61 @@ internal fun App() { .transform { it.sign(data).asKmmResult() } .also { signatureData = it; verifyState = null } } - }, - - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), enabled = signingPossible - ) { - Text("Sign") + ) { Text("Sign") } + + if (signingPossible && (currentKey?.getOrNull() is CryptoPublicKey.EC)) { + Napier.i { "Ephemeral key: $ephemeralKey" } + Spacer(Modifier.height(8.dp)) + ephemeralKey?.let { ephemeralKey -> + OutlinedTextField(value = ephemeralKey.publicValue.toString(), + modifier = Modifier.fillMaxWidth().padding(16.dp), + minLines = 1, + textStyle = TextStyle.Default.copy(fontSize = 10.sp), + readOnly = true, + onValueChange = {}, + label = { Text("Random faux-external key for ECDH") }) + Button( + onClick = { + Napier.w { "input: $inputData" } + Napier.w { "signingKey: $currentKey" } + CoroutineScope(context).launch { + + agreedKey = + (currentSigner!!.getOrThrow() as Signer.ECDSA).keyAgreement( + ephemeralKey.publicValue + ) + Napier.i { + "ECDH Key of ext_piv + hardware-backed_pub: ${ + agreedKey?.getOrNull()?.encodeBase64() + }" + } + + Napier.i { + "ECDH Key of ext_pub + hardware-backed_priv: ${ + agreedKey?.getOrNull()?.encodeBase64() + }" + } + } + + }, + modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), + ) { Text("Perform ECDH key agreement") } + } + + if (agreedKey != null) { + OutlinedTextField(value = + "Computed from ext_pub + hardware-backed_priv:\n" + agreedKey?.map { it.encodeBase64() } + ?.getOrElse { it.message ?: it::class.simpleName ?: "" }, + modifier = Modifier.fillMaxWidth().padding(16.dp), + minLines = 1, + textStyle = TextStyle.Default.copy(fontSize = 10.sp), + readOnly = true, + onValueChange = {}, + label = { Text("Agreed-upon secret") }) + } } if (signatureData != null) { diff --git a/docs/docs/indispensable.md b/docs/docs/indispensable.md index d9c3aa602..4376b599b 100644 --- a/docs/docs/indispensable.md +++ b/docs/docs/indispensable.md @@ -104,9 +104,9 @@ The following functions provide interop functionality with platform types. ### JVM/Android * `SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm -* `X509SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm +* `SpecializedSignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm * `SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm -* `X509SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm +* `SpecializedSignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm
@@ -148,7 +148,7 @@ The following functions provide interop functionality with platform types. * `CryptoSignature.jcaSignatureBytes` returns the JCA-native encoded representation of a signature * `CryptoSignature.parseFromJca()` returns a signature object form a JCA-native encoded representation of a signature * `CryptoSignature.EC.parseFromJca()` returns an EC signature object form a JCA-native encoded representation of a signature -* `CryptoSignature.RSA.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature +* `CryptoSignature.RSAorHMAC.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature * `CryptoSignature.EC.parseFromJcaP1363` parses a signature produced by the JCA digestWithECDSAinP1363Format algorithm. * `X509Certificate.toJcaCertificate()` converts the certificate to a JCA-native `X509Certificate` * `java.security.cert.X509Certificate.toKmpCertificate()` converts a JCA-native certificate to a Signum `X509Certificate` diff --git a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt index b6837c261..b8f2d98bc 100644 --- a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt +++ b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Exception.kt @@ -13,9 +13,7 @@ class Asn1TagMismatchException(val expected: Asn1Element.Tag, val actual: Asn1El class Asn1StructuralException(message: String, cause: Throwable? = null) : Asn1Exception(message, cause) -class Asn1OidException(message: String, val oid: ObjectIdentifier) : Asn1Exception(message){ - constructor(oid: ObjectIdentifier):this("Unknown oid $oid", oid) -} +class Asn1OidException(message: String, val oid: ObjectIdentifier) : Asn1Exception(message) /** * Runs [block] inside [catching] and encapsulates any thrown exception in an [Asn1Exception] unless it already is one diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt index 94bfe3f18..979cb6023 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseAlgorithm.kt @@ -1,10 +1,11 @@ package at.asitplus.signum.indispensable.cosef -import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.* -import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.ECCurve +import at.asitplus.signum.indispensable.RSAPadding +import at.asitplus.signum.indispensable.SignatureAlgorithm +import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -17,7 +18,7 @@ import kotlinx.serialization.encoding.Encoder * See [COSE Algorithm Registry](https://www.iana.org/assignments/cose/cose.xhtml) */ @Serializable(with = CoseAlgorithmSerializer::class) -enum class CoseAlgorithm(val value: Int) : SpecializedDataIntegrityAlgorithm { +enum class CoseAlgorithm(val value: Int): SpecializedSignatureAlgorithm { // ECDSA with SHA-size ES256(-7), @@ -42,52 +43,22 @@ enum class CoseAlgorithm(val value: Int) : SpecializedDataIntegrityAlgorithm { // RSASSA-PKCS1-v1_5 using SHA-1 RS1(-65535); - - companion object { - /** - * encompasses only signature algorithms, filtering out MACs - */ - val signatureAlgorithms = listOf( - ES256, - ES384, - ES512, - - PS256, - PS384, - PS512, - - RS256, - RS384, - RS512, - RS1 - ).map { it.algorithm as SignatureAlgorithm } - - /** - * encompasses only MACs, filtering out signature algorithms - */ - val messageAuthenticationCodes = listOf(HS256, HS384, HS512).map { it.algorithm as MessageAuthenticationCode } - - + val digest: Digest get() = when(this) { + RS1 -> Digest.SHA1 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 } - val digest: Digest - get() = when (this) { - RS1 -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 - } - - override val algorithm: DataIntegrityAlgorithm - get() = when (this) { - ES256 -> SignatureAlgorithm.ECDSA(Digest.SHA256, ECCurve.SECP_256_R_1) - ES384 -> SignatureAlgorithm.ECDSA(Digest.SHA384, ECCurve.SECP_384_R_1) - ES512 -> SignatureAlgorithm.ECDSA(Digest.SHA512, ECCurve.SECP_521_R_1) + override val algorithm: SignatureAlgorithm get() = when (this) { + ES256 -> SignatureAlgorithm.ECDSA(Digest.SHA256, ECCurve.SECP_256_R_1) + ES384 -> SignatureAlgorithm.ECDSA(Digest.SHA384, ECCurve.SECP_384_R_1) + ES512 -> SignatureAlgorithm.ECDSA(Digest.SHA512, ECCurve.SECP_521_R_1) - HS256, HS384, HS512 -> HMAC.byDigest(digest) - PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) - RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) - } + HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) + PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this. digest, RSAPadding.PSS) + RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + } } object CoseAlgorithmSerializer : KSerializer { @@ -106,9 +77,8 @@ object CoseAlgorithmSerializer : KSerializer { } - /** Tries to find a matching COSE algorithm. Note that COSE imposes curve restrictions on ECDSA based on the digest. */ -fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catching { +fun SignatureAlgorithm.toCoseAlgorithm() = catching { when (this) { is SignatureAlgorithm.ECDSA -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.ES256 @@ -116,7 +86,6 @@ fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catchin Digest.SHA512 -> CoseAlgorithm.ES512 else -> throw IllegalArgumentException("ECDSA with ${this.digest} is unsupported by COSE") } - is SignatureAlgorithm.RSA -> when (this.padding) { RSAPadding.PKCS1 -> when (this.digest) { Digest.SHA1 -> CoseAlgorithm.RS1 @@ -124,7 +93,6 @@ fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catchin Digest.SHA384 -> CoseAlgorithm.RS384 Digest.SHA512 -> CoseAlgorithm.RS512 } - RSAPadding.PSS -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.PS256 Digest.SHA384 -> CoseAlgorithm.PS384 @@ -132,18 +100,15 @@ fun DataIntegrityAlgorithm.toCoseAlgorithm(): KmmResult = catchin else -> throw IllegalArgumentException("RSA-PSS with ${this.digest} is unsupported by COSE") } } - - is HMAC -> when (this.digest) { + is SignatureAlgorithm.HMAC -> when (this.digest) { Digest.SHA256 -> CoseAlgorithm.HS256 Digest.SHA384 -> CoseAlgorithm.HS384 Digest.SHA512 -> CoseAlgorithm.HS512 else -> throw IllegalArgumentException("HMAC with ${this.digest} is unsupported by COSE") } - - else -> throw IllegalArgumentException("UnsupportedAlgorithm $this") } } /** Tries to find a matching COSE algorithm. Note that COSE imposes curve restrictions on ECDSA based on the digest. */ -fun SpecializedDataIntegrityAlgorithm.toCoseAlgorithm() = +fun SpecializedSignatureAlgorithm.toCoseAlgorithm() = this.algorithm.toCoseAlgorithm() diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt index e571328e9..c7f98eeb7 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt @@ -66,7 +66,7 @@ class CoseSignedSerializer

( ): CryptoSignature.RawByteEncodable = if (protectedHeader.usesEC() ?: unprotectedHeader?.usesEC() ?: (size < 2048)) CryptoSignature.EC.fromRawBytes(this) - else CryptoSignature.RSA(this) + else CryptoSignature.RSAorHMAC(this) private fun ByteArray?.toNullablePayload(): P? = when (this) { null -> null @@ -80,7 +80,7 @@ class CoseSignedSerializer

( } else { runCatching { fromBytes() } .getOrElse { fromByteStringWrapper() } - // if it still fails, the input not valid + // if it still fails, the input is not valid } private fun ByteArray.fromBytes(): P = diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt index d9d737c71..dc47dd22f 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt @@ -21,14 +21,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = bytes, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = ByteArraySerializer(), ) val bytesSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = bytes, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = ByteArraySerializer(), ) @@ -42,14 +42,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSA(reversed), + signature = CryptoSignature.RSAorHMAC(reversed), payloadSerializer = ByteArraySerializer(), ) val reversedSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSA(reversed), + signature = CryptoSignature.RSAorHMAC(reversed), payloadSerializer = ByteArraySerializer(), ) @@ -80,14 +80,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = DataClass.serializer(), ) val bytesSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = DataClass.serializer(), ) @@ -102,14 +102,14 @@ class CoseEqualsTest : FreeSpec({ protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = DataClass.serializer(), ) val reversedSigned2 = CoseSigned.create( protectedHeader = CoseHeader(), unprotectedHeader = null, payload = reversed, - signature = CryptoSignature.RSA(bytes), + signature = CryptoSignature.RSAorHMAC(bytes), payloadSerializer = DataClass.serializer(), ) diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt index dd25e09c6..bc0c9cb1c 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt @@ -30,7 +30,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA(byteArrayOf()), + signature = CryptoSignature.RSAorHMAC(byteArrayOf()), payloadSerializer = ByteStringWrapper.serializer(String.serializer()) ) } @@ -42,7 +42,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), payloadSerializer = ByteArraySerializer(), ) @@ -55,7 +55,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), payloadSerializer = DataClass.serializer(), ) @@ -68,7 +68,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple payloadSerializer = ByteArraySerializer(), ) val serialized = cose.serialize(ByteArraySerializer()) @@ -86,7 +86,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = null, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple payloadSerializer = ByteArraySerializer(), ) val serialized = cose.serialize(ByteArraySerializer()) @@ -129,7 +129,7 @@ class CoseSerializationTest : FreeSpec({ protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256), unprotectedHeader = null, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), //RSA because EC expects tuple + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), //RSAorHMAC because EC expects tuple payloadSerializer = DataClass.serializer(), ) val serialized = cose.serialize(DataClass.serializer()) @@ -195,7 +195,7 @@ class CoseSerializationTest : FreeSpec({ val inputObject = CoseSigned.create( protectedHeader = header, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), payloadSerializer = ByteArraySerializer(), ).prepareCoseSignatureInput(byteArrayOf()) .encodeToString(Base16()) @@ -217,7 +217,7 @@ class CoseSerializationTest : FreeSpec({ val inputObject = CoseSigned.create( protectedHeader = header, payload = payload, - signature = CryptoSignature.RSA("bar".encodeToByteArray()), + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()), payloadSerializer = DataClass.serializer(), ).prepareCoseSignatureInput(byteArrayOf()) .encodeToString(Base16()) diff --git a/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt b/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt index 547371422..02270b0de 100644 --- a/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt +++ b/indispensable-cosef/src/jvmTest/kotlin/ConversionTests.kt @@ -23,9 +23,9 @@ class ConversionTests : FreeSpec({ } } "COSE -> X509 -> COSE is stable" - { - withData(CoseAlgorithm.signatureAlgorithms/*here we filter by using only sig alg*/) { + withData(CoseAlgorithm.entries) { it.toX509SignatureAlgorithm().getOrNull()?.let { x509 -> - x509.toCoseAlgorithm().onSuccess { it.algorithm } shouldSucceedWith it.toCoseAlgorithm().getOrThrow() //here we go back to cose, after initially filtering, because cose does not discriminate between MAC and sig + x509.toCoseAlgorithm() shouldSucceedWith it } } } diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt index 9ec4e38ee..2f8ecd289 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsAlgorithm.kt @@ -1,10 +1,11 @@ package at.asitplus.signum.indispensable.josef -import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.* -import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.ECCurve +import at.asitplus.signum.indispensable.RSAPadding +import at.asitplus.signum.indispensable.SignatureAlgorithm +import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -18,7 +19,7 @@ import kotlinx.serialization.encoding.Encoder * Since we support only JWS algorithms (with one exception), this class is called what it's called. */ @Serializable(with = JwsAlgorithmSerializer::class) -enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, SpecializedDataIntegrityAlgorithm { +enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, SpecializedSignatureAlgorithm { ES256("ES256"), ES384("ES384"), @@ -41,58 +42,28 @@ enum class JwsAlgorithm(override val identifier: String) : JsonWebAlgorithm, Spe */ NON_JWS_SHA1_WITH_RSA("RS1"); - - companion object { - /** - * encompasses only signature algorithms, filtering out MACs - */ - val signatureAlgorithms = listOf( - ES256, - ES384, - ES512, - - PS256, - PS384, - PS512, - - RS256, - RS384, - RS512, - NON_JWS_SHA1_WITH_RSA - ).map { it.algorithm as SignatureAlgorithm } - - /** - * Encompasses only MACs, filtering our signature algorithms - */ - val messageAuthenticationCodes = listOf(HS256, HS384, HS512).map { it.algorithm as MessageAuthenticationCode } - + val digest: Digest get() = when(this) { + NON_JWS_SHA1_WITH_RSA -> Digest.SHA1 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 } - val digest: Digest - get() = when (this) { - NON_JWS_SHA1_WITH_RSA -> Digest.SHA1 - ES256, HS256, PS256, RS256 -> Digest.SHA256 - ES384, HS384, PS384, RS384 -> Digest.SHA384 - ES512, HS512, PS512, RS512 -> Digest.SHA512 - } - - override val algorithm: DataIntegrityAlgorithm - get() = when (this) { - ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, this.ecCurve!!) - HS256, HS384, HS512 -> HMAC.byDigest(this.digest) - PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) - NON_JWS_SHA1_WITH_RSA, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) - } + override val algorithm: SignatureAlgorithm get() = when (this) { + ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, this.ecCurve!!) + HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) + PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this. digest, RSAPadding.PSS) + NON_JWS_SHA1_WITH_RSA, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) + } /** The curve to create signatures on. * This is fixed by RFC7518, as opposed to X.509 where other combinations are possible. */ - val ecCurve: ECCurve? - get() = when (this) { - ES256 -> ECCurve.SECP_256_R_1 - ES384 -> ECCurve.SECP_384_R_1 - ES512 -> ECCurve.SECP_521_R_1 - else -> null - } + val ecCurve: ECCurve? get() = when (this) { + ES256 -> ECCurve.SECP_256_R_1 + ES384 -> ECCurve.SECP_384_R_1 + ES512 -> ECCurve.SECP_521_R_1 + else -> null + } } object JwsAlgorithmSerializer : KSerializer { @@ -109,12 +80,8 @@ object JwsAlgorithmSerializer : KSerializer { } } - -fun SpecializedDataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = - algorithm.toJwsAlgorithm() - /** Tries to find a matching JWS algorithm. Note that JWS imposes curve restrictions on ECDSA based on the digest. */ -fun DataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = catching { +fun SignatureAlgorithm.toJwsAlgorithm() = catching { when (this) { is SignatureAlgorithm.ECDSA -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.ES256 @@ -122,7 +89,6 @@ fun DataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = catching Digest.SHA512 -> JwsAlgorithm.ES512 else -> throw IllegalArgumentException("ECDSA with ${this.digest} is unsupported by JWS") } - is SignatureAlgorithm.RSA -> when (this.padding) { RSAPadding.PKCS1 -> when (this.digest) { Digest.SHA1 -> JwsAlgorithm.NON_JWS_SHA1_WITH_RSA @@ -130,26 +96,24 @@ fun DataIntegrityAlgorithm.toJwsAlgorithm(): KmmResult = catching Digest.SHA384 -> JwsAlgorithm.RS384 Digest.SHA512 -> JwsAlgorithm.RS512 } - RSAPadding.PSS -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.PS256 Digest.SHA384 -> JwsAlgorithm.PS384 Digest.SHA512 -> JwsAlgorithm.PS512 else -> throw IllegalArgumentException("RSA-PSS with ${this.digest} is unsupported by JWS") } - } - - is HMAC -> when (this.digest) { + is SignatureAlgorithm.HMAC -> when (this.digest) { Digest.SHA256 -> JwsAlgorithm.HS256 Digest.SHA384 -> JwsAlgorithm.HS384 Digest.SHA512 -> JwsAlgorithm.HS512 else -> throw IllegalArgumentException("HMAC with ${this.digest} is unsupported by JWS") } - - else -> throw IllegalArgumentException("UnsupportedAlgorithm $this") - } } +/** Tries to find a matching JWS algorithm. Note that JWS imposes curve restrictions on ECDSA based on the digest. */ +fun SpecializedSignatureAlgorithm.toJwsAlgorithm() = + this.algorithm.toJwsAlgorithm() + diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt index 6c31b6e5d..0bc1838ec 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JwsSigned.kt @@ -74,7 +74,7 @@ data class JwsSigned( val payload = inputParts[1] val signature = with(inputParts[2]) { when (val curve = header.algorithm.ecCurve) { - null -> CryptoSignature.RSA(this) + null -> CryptoSignature.RSAorHMAC(this) else -> CryptoSignature.EC.fromRawBytes(curve, this) } } diff --git a/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt b/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt index d8f2610c1..69b0c60f2 100644 --- a/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt +++ b/indispensable-josef/src/jvmTest/kotlin/at/asitplus/signum/indispensable/josef/ConversionTest.kt @@ -17,9 +17,9 @@ class ConversionTest : FreeSpec({ } } "JWS -> X509 -> JWS is stable" - { - withData(JwsAlgorithm.signatureAlgorithms /*here we filter s.t. only sig alg are fed into the test*/) { + withData(JwsAlgorithm.entries) { it.toX509SignatureAlgorithm().getOrNull()?.let { x509 -> - x509.toJwsAlgorithm() shouldSucceedWith it.toJwsAlgorithm().getOrThrow() /* and here we convert the sig algs back to JWS alg*/ + x509.toJwsAlgorithm() shouldSucceedWith it } } } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt index b5033a80e..d8f59e9d6 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt @@ -217,7 +217,7 @@ sealed interface CryptoSignature : Asn1Encodable { } - class RSA private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { + class RSAorHMAC private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { constructor(rawBytes: ByteArray) : this(rawBytes, null) constructor(x509Element: Asn1Primitive) : this(null, x509Element) @@ -241,16 +241,16 @@ sealed interface CryptoSignature : Asn1Encodable { if (this === other) return true if (other == null || this::class != other::class) return false - other as RSA + other as RSAorHMAC return signature == other.signature } - companion object : Asn1Decodable { + companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun doDecode(src: Asn1Element): RSA { + override fun doDecode(src: Asn1Element): RSAorHMAC { src as Asn1Primitive - return RSA(src) + return RSAorHMAC(src) } } } @@ -259,7 +259,7 @@ sealed interface CryptoSignature : Asn1Encodable { @Throws(Asn1Exception::class) override fun doDecode(src: Asn1Element): CryptoSignature = runRethrowing { when (src.tag) { - Asn1Element.Tag.BIT_STRING -> RSA.decodeFromTlv(src) + Asn1Element.Tag.BIT_STRING -> RSAorHMAC.decodeFromTlv(src) Asn1Element.Tag.SEQUENCE -> EC.decodeFromTlv(src) else -> throw Asn1Exception("Unknown Signature Format") diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt deleted file mode 100644 index d85fb5485..000000000 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/DataIntegrityAlgorithm.kt +++ /dev/null @@ -1,14 +0,0 @@ -package at.asitplus.signum.indispensable - -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode - -/** - * Umbrella interface encompassing _data integrity algorithms_: - * * Message Authentication Codes ([MessageAuthenticationCode]) - * * Digital Signatures ([SignatureAlgorithm]) - */ -interface DataIntegrityAlgorithm{ - companion object { - val entries: Iterable = MessageAuthenticationCode.entries + SignatureAlgorithm.entries - } -} \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt index 84739935c..1659a602c 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SignatureAlgorithm.kt @@ -5,7 +5,11 @@ enum class RSAPadding { PSS; } -sealed interface SignatureAlgorithm : DataIntegrityAlgorithm { +sealed interface SignatureAlgorithm { + data class HMAC( + /** The digest to use */ + val digest: Digest + ) : SignatureAlgorithm data class ECDSA( /** The digest to apply to the data, or `null` to directly process the raw data. */ @@ -34,22 +38,15 @@ sealed interface SignatureAlgorithm : DataIntegrityAlgorithm { val RSAwithSHA384andPSSPadding = RSA(Digest.SHA384, RSAPadding.PSS) val RSAwithSHA512andPSSPadding = RSA(Digest.SHA512, RSAPadding.PSS) - val entries: Iterable = listOf( - ECDSAwithSHA256, - ECDSAwithSHA384, - ECDSAwithSHA512, - - RSAwithSHA256andPSSPadding, - RSAwithSHA384andPSSPadding, - RSAwithSHA512andPSSPadding, - - RSAwithSHA256andPKCS1Padding, - RSAwithSHA384andPKCS1Padding, - RSAwithSHA512andPKCS1Padding - ) + @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) + val HMACwithSHA256 = HMAC(Digest.SHA256) + @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) + val HMACwithSHA384 = HMAC(Digest.SHA384) + @Deprecated("Not yet implemented", level = DeprecationLevel.ERROR) + val HMACwithSHA512 = HMAC(Digest.SHA512) } } -interface SpecializedDataIntegrityAlgorithm { - val algorithm: DataIntegrityAlgorithm +interface SpecializedSignatureAlgorithm { + val algorithm: SignatureAlgorithm } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt index e3c4d9f5e..43acfc3d3 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt @@ -18,13 +18,18 @@ import kotlinx.serialization.encoding.Encoder enum class X509SignatureAlgorithm( override val oid: ObjectIdentifier, val isEc: Boolean = false -) : Asn1Encodable, Identifiable, SpecializedDataIntegrityAlgorithm { +) : Asn1Encodable, Identifiable, SpecializedSignatureAlgorithm { // ECDSA with SHA-size ES256(KnownOIDs.ecdsaWithSHA256, true), ES384(KnownOIDs.ecdsaWithSHA384, true), ES512(KnownOIDs.ecdsaWithSHA512, true), + // HMAC-size with SHA-size + HS256(KnownOIDs.hmacWithSHA256), + HS384(KnownOIDs.hmacWithSHA384), + HS512(KnownOIDs.hmacWithSHA512), + // RSASSA-PSS with SHA-size PS256(KnownOIDs.rsaPSS), PS384(KnownOIDs.rsaPSS), @@ -79,6 +84,7 @@ enum class X509SignatureAlgorithm( PS512 -> encodePSSParams(512) + HS256, HS384, HS512, RS256, RS384, RS512, RS1 -> Asn1.Sequence { +oid +Null() @@ -87,14 +93,15 @@ enum class X509SignatureAlgorithm( val digest: Digest get() = when(this) { RS1 -> Digest.SHA1 - ES256, PS256, RS256 -> Digest.SHA256 - ES384, PS384, RS384 -> Digest.SHA384 - ES512, PS512, RS512 -> Digest.SHA512 + ES256, HS256, PS256, RS256 -> Digest.SHA256 + ES384, HS384, PS384, RS384 -> Digest.SHA384 + ES512, HS512, PS512, RS512 -> Digest.SHA512 } override val algorithm: SignatureAlgorithm get() = when(this) { ES256, ES384, ES512 -> SignatureAlgorithm.ECDSA(this.digest, null) + HS256, HS384, HS512 -> SignatureAlgorithm.HMAC(this.digest) PS256, PS384, PS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PSS) RS1, RS256, RS384, RS512 -> SignatureAlgorithm.RSA(this.digest, RSAPadding.PKCS1) } @@ -112,7 +119,8 @@ enum class X509SignatureAlgorithm( ES512.oid, ES384.oid, ES256.oid -> fromOid(oid) RS1.oid -> RS1 - RS256.oid, RS384.oid, RS512.oid-> fromOid(oid).also { + RS256.oid, RS384.oid, RS512.oid, + HS256.oid, HS384.oid, HS512.oid -> fromOid(oid).also { val tag = src.nextChild().tag if (tag != Asn1Element.Tag.NULL) throw Asn1TagMismatchException(Asn1Element.Tag.NULL, tag, "RSA Params not allowed.") @@ -184,8 +192,17 @@ fun SignatureAlgorithm.toX509SignatureAlgorithm() = catching { else -> throw IllegalArgumentException("Digest ${this.digest} is unsupported by X.509 RSA-PSS") } } + is SignatureAlgorithm.HMAC -> when (this.digest) { + Digest.SHA256 -> X509SignatureAlgorithm.HS256 + Digest.SHA384 -> X509SignatureAlgorithm.HS384 + Digest.SHA512 -> X509SignatureAlgorithm.HS512 + else -> throw IllegalArgumentException("Digest ${this.digest} is unsupported by X.509 HMAC") + } } } +/** Finds a X.509 signature algorithm matching this algorithm. Curve restrictions are not preserved. */ +fun SpecializedSignatureAlgorithm.toX509SignatureAlgorithm() = + this.algorithm.toX509SignatureAlgorithm() object X509SignatureAlgorithmSerializer : KSerializer { diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt new file mode 100644 index 000000000..6c7810b84 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt @@ -0,0 +1,40 @@ +package at.asitplus.signum.indispensable.mac + +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.asn1.Identifiable +import at.asitplus.signum.indispensable.asn1.KnownOIDs +import at.asitplus.signum.indispensable.asn1.ObjectIdentifier +import at.asitplus.signum.indispensable.misc.BitLength + +sealed interface MAC { + /** output size of MAC */ + val outputLength: BitLength + + companion object { + val entries: Iterable = HMAC.entries + } +} + +/** + * RFC 2104 HMAC + */ +enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MAC, Identifiable { + SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), + SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), + SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), + SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), + ; + + override fun toString() = "HMAC-$digest" + + companion object { + operator fun invoke(digest: Digest) = when (digest) { + Digest.SHA1 -> SHA1 + Digest.SHA256 -> SHA256 + Digest.SHA384 -> SHA384 + Digest.SHA512 -> SHA512 + } + } + + override val outputLength: BitLength get() = digest.outputLength +} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt deleted file mode 100644 index cc5074209..000000000 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt +++ /dev/null @@ -1,61 +0,0 @@ -package at.asitplus.signum.indispensable.mac - -import at.asitplus.signum.indispensable.DataIntegrityAlgorithm -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.asn1.* -import at.asitplus.signum.indispensable.asn1.encoding.Asn1 -import at.asitplus.signum.indispensable.asn1.encoding.Asn1.Null -import at.asitplus.signum.indispensable.asn1.encoding.readNull -import at.asitplus.signum.indispensable.misc.BitLength - -sealed interface MessageAuthenticationCode : DataIntegrityAlgorithm { - /** output size of MAC */ - val outputLength: BitLength - - companion object { - val entries: Iterable = HMAC.entries - } -} - -/** - * RFC 2104 HMAC - */ -enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MessageAuthenticationCode, Identifiable, - Asn1Encodable { - SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), - SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), - SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), - SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), - ; - - override fun toString() = "HMAC-$digest" - - override fun encodeToTlv(): Asn1Sequence = Asn1.Sequence { - +oid - +Null() - } - - - companion object : Asn1Decodable { - - fun byOID(oid: ObjectIdentifier): HMAC? = entries.find { it.oid == oid } - - fun byDigest(digest: Digest): HMAC = entries.find { it.digest == digest }!! - - operator fun invoke(digest: Digest) = when (digest) { - Digest.SHA1 -> SHA1 - Digest.SHA256 -> SHA256 - Digest.SHA384 -> SHA384 - Digest.SHA512 -> SHA512 - } - - override fun doDecode(src: Asn1Sequence): HMAC { - val oid = src.nextChild().asPrimitive().readOid() - src.nextChild().asPrimitive().readNull() - require(!src.hasMoreChildren()) { "Superfluous ANS.1 data in HMAC" } - return byOID(oid) ?: throw Asn1OidException(oid) - } - } - - override val outputLength: BitLength get() = digest.outputLength -} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt index 147e9c9a4..e9f38b493 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt @@ -224,7 +224,7 @@ constructor( val CryptoSignature.x509Encoded get() = when (this) { is CryptoSignature.EC -> encodeToDer().encodeToAsn1BitStringPrimitive() - is CryptoSignature.RSA -> encodeToTlv() + is CryptoSignature.RSAorHMAC -> encodeToTlv() } /** @@ -235,7 +235,7 @@ val CryptoSignature.x509Encoded fun CryptoSignature.Companion.fromX509Encoded(alg: X509SignatureAlgorithm, it: Asn1Primitive) = when (alg.isEc) { true -> CryptoSignature.EC.decodeFromDer(it.asAsn1BitString().rawBytes) - false -> CryptoSignature.RSA.decodeFromTlv(it) + false -> CryptoSignature.RSAorHMAC.decodeFromTlv(it) } /** diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt index b7314d766..2ba356695 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt @@ -7,7 +7,7 @@ import at.asitplus.signum.indispensable.asn1.KnownOIDs import at.asitplus.signum.indispensable.asn1.ObjectIdentifier import at.asitplus.signum.indispensable.asn1.encoding.encodeTo8Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.mac.MAC import at.asitplus.signum.indispensable.misc.BitLength import at.asitplus.signum.indispensable.misc.bit import kotlin.contracts.ExperimentalContracts @@ -110,7 +110,7 @@ sealed interface SymmetricEncryptionAlgorithm, out interface Integrated : Authenticated - interface WithDedicatedMac : + interface WithDedicatedMac : Authenticated, I, KeyType.WithDedicatedMacKey> } @@ -311,7 +311,7 @@ sealed interface AuthCapability { * An authenticated cipher construction based on an unauthenticated cipher with a dedicated MAC function, requiring a dedicated MAC key. * _Encrypt-then-MAC_ */ - class WithDedicatedMac( + class WithDedicatedMac( /** * The inner unauthenticated cipher */ @@ -406,14 +406,14 @@ val DefaultDedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation /** * Typealias defining the signature of the lambda for defining a custom MAC input calculation scheme. */ -typealias DedicatedMacInputCalculation = MessageAuthenticationCode.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray +typealias DedicatedMacInputCalculation = MAC.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray /** * The default dedicated mac input calculation as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1), authenticating all inputs: * `AAD || IV || Ciphertext || AAD Length`, where AAD_length is a 64 bit big-endian representation of the aad length in bits */ val DefaultDedicatedMacInputCalculation: DedicatedMacInputCalculation = - fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = + fun MAC.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = aad + iv + ciphertext + (aad.size.toLong()*8L).encodeTo8Bytes() /** diff --git a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt index 0b3c51f58..a17391bbb 100644 --- a/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt +++ b/indispensable/src/commonTest/kotlin/CryptoSignatureTest.kt @@ -20,9 +20,9 @@ class CryptoSignatureTest : FreeSpec({ val ec1 = CryptoSignature.EC.fromRS(first.toBigInteger(), second.toBigInteger()) val ec2 = CryptoSignature.EC.fromRS(first.toBigInteger(), second.toBigInteger()) val ec3 = CryptoSignature.EC.fromRS(second.toBigInteger(), first.toBigInteger()) - val rsa1 = CryptoSignature.RSA(first.toTwosComplementByteArray()) - val rsa2 = CryptoSignature.RSA(first.toTwosComplementByteArray()) - val rsa3 = CryptoSignature.RSA(second.toTwosComplementByteArray()) + val rsa1 = CryptoSignature.RSAorHMAC(first.toTwosComplementByteArray()) + val rsa2 = CryptoSignature.RSAorHMAC(first.toTwosComplementByteArray()) + val rsa3 = CryptoSignature.RSAorHMAC(second.toTwosComplementByteArray()) ec1 shouldBe ec1 ec1 shouldBe ec2 diff --git a/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt b/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt index f5bbcab42..b18067339 100644 --- a/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt +++ b/indispensable/src/iosMain/kotlin/CommonCryptoExtensions.kt @@ -40,8 +40,13 @@ val SignatureAlgorithm.secKeyAlgorithm: SecKeyAlgorithm } } + is SignatureAlgorithm.HMAC -> TODO("HMAC is unsupported") }!! +val SpecializedSignatureAlgorithm.secKeyAlgorithm + get() = + this.algorithm.secKeyAlgorithm + val SignatureAlgorithm.secKeyAlgorithmPreHashed: SecKeyAlgorithm get() = when (this) { is SignatureAlgorithm.ECDSA -> { @@ -72,16 +77,17 @@ val SignatureAlgorithm.secKeyAlgorithmPreHashed: SecKeyAlgorithm } } + is SignatureAlgorithm.HMAC -> TODO("HMAC is unsupported") }!! -val X509SignatureAlgorithm.secKeyAlgorithmPreHashed +val SpecializedSignatureAlgorithm.secKeyAlgorithmPreHashed get() = this.algorithm.secKeyAlgorithmPreHashed val CryptoSignature.iosEncoded get() = when (this) { is CryptoSignature.EC -> this.encodeToDer() - is CryptoSignature.RSA -> this.rawByteArray + is CryptoSignature.RSAorHMAC -> this.rawByteArray } fun CryptoPublicKey.toSecKey() = catching { diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt index 4aa6e241e..fb0453414 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt @@ -1,6 +1,6 @@ package at.asitplus.signum.indispensable -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.mac.MAC import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -40,5 +40,5 @@ inline fun FreeSpec.enumConsistencyTest() { } class EnumConsistencyTests : FreeSpec({ - enumConsistencyTest() + enumConsistencyTest() }) \ No newline at end of file diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt index 9923e11b1..d018fa5f0 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt @@ -72,7 +72,7 @@ class SignatureCodecTest : FreeSpec({ sign() } - CryptoSignature.RSA.parseFromJca(sig).jcaSignatureBytes shouldBe sig + CryptoSignature.RSAorHMAC.parseFromJca(sig).jcaSignatureBytes shouldBe sig CryptoSignature.parseFromJca( sig, SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) @@ -99,7 +99,7 @@ class SignatureCodecTest : FreeSpec({ val bcSig = (ASN1Sequence.fromByteArray(certificateHolder.encoded) as DLSequence).elementAt(2) .toASN1Primitive().encoded - CryptoSignature.RSA.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig + CryptoSignature.RSAorHMAC.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig CryptoSignature.parseFromJca( certificateHolder.signature, SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt index 8365699d3..f9a2e13d1 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/pki/X509ConversionTests.kt @@ -12,6 +12,7 @@ infix fun KmmResult.shouldSucceedWith(b: T) : T = class X509ConversionTests : FreeSpec({ "X509 -> Alg -> X509 is stable" - { withData(X509SignatureAlgorithm.entries) { + it.toX509SignatureAlgorithm() shouldSucceedWith it it.algorithm.toX509SignatureAlgorithm() shouldSucceedWith it } } diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt index 34fe376d1..0e1631c87 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt @@ -390,7 +390,7 @@ sealed class AndroidKeystoreSigner private constructor( return@signCatching when (this@AndroidKeystoreSigner) { is ECDSA -> CryptoSignature.EC.parseFromJca(jcaSig).withCurve(publicKey.curve) - is RSA -> CryptoSignature.RSA.parseFromJca(jcaSig) + is RSA -> CryptoSignature.RSAorHMAC.parseFromJca(jcaSig) } }} diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 1677a67fa..2707500be 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -63,7 +63,7 @@ sealed class AndroidEphemeralSigner (internal val privateKey: PrivateKey) : Sign override val publicKey: CryptoPublicKey.RSA, override val signatureAlgorithm: SignatureAlgorithm.RSA) : AndroidEphemeralSigner(privateKey), Signer.RSA { - override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSA.parseFromJca(bytes) + override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSAorHMAC.parseFromJca(bytes) @SecretExposure override fun exportPrivateKey() = diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index c45d95ab1..1019415b0 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -79,7 +79,7 @@ internal actual fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier @JvmSynthetic internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSA, + data: SignatureInput, signature: CryptoSignature.RSAorHMAC, config: PlatformVerifierConfiguration) { getRSAInstance(signatureAlgorithm, config).run { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt index 6dd8b9117..bb733132c 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt @@ -3,19 +3,19 @@ package at.asitplus.signum.supreme.mac import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.mac.MAC import at.asitplus.signum.internals.xor import at.asitplus.signum.supreme.hash.digest -fun MessageAuthenticationCode.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) -fun MessageAuthenticationCode.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) +fun MAC.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) +fun MAC.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) private val HMAC.blockLength get() = digest.inputBlockSize.bytes.toInt() private val HMAC.innerPad get() = ByteArray(blockLength) { 0x36 } private val HMAC.outerPad get() = ByteArray(blockLength) { 0x5C } -fun MessageAuthenticationCode.mac(key: ByteArray, msg: Sequence): KmmResult = catching { +fun MAC.mac(key: ByteArray, msg: Sequence): KmmResult = catching { when (this@mac) { is HMAC -> hmac(key, msg) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt index df38051bf..d12ce4e43 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt @@ -154,6 +154,7 @@ fun SignatureAlgorithm.signerFor(privateKey: CryptoPrivateKey.WithPublicKey<*>): (this is SignatureAlgorithm.RSA && privateKey is CryptoPrivateKey.RSA)) { when (this) { is SignatureAlgorithm.ECDSA -> this.signerFor(privateKey as CryptoPrivateKey.EC.WithPublicKey) + is SignatureAlgorithm.HMAC -> KmmResult.failure(UnsupportedOperationException("HMAC is not yet supported!")) is SignatureAlgorithm.RSA -> this.signerFor(privateKey as CryptoPrivateKey.RSA) } } else { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt index 12b185415..b0a8cd796 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Verifier.kt @@ -6,9 +6,8 @@ import at.asitplus.recoverCatching import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.indispensable.SpecializedDataIntegrityAlgorithm +import at.asitplus.signum.indispensable.SpecializedSignatureAlgorithm import at.asitplus.signum.ecmath.straussShamir -import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.supreme.dsl.DSL import at.asitplus.signum.supreme.UnsupportedCryptoException import at.asitplus.signum.supreme.dsl.DSLConfigureFn @@ -97,7 +96,7 @@ internal expect fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier /** data is guaranteed to be in RAW_BYTES format. failure should throw. */ internal expect fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSA, + data: SignatureInput, signature: CryptoSignature.RSAorHMAC, config: PlatformVerifierConfiguration) class PlatformRSAVerifier @@ -110,7 +109,7 @@ class PlatformRSAVerifier checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier(signatureAlgorithm, publicKey, config) } override fun verify(data: SignatureInput, sig: CryptoSignature) = catching { - require (sig is CryptoSignature.RSA) + require (sig is CryptoSignature.RSAorHMAC) { "Attempted to validate non-RSA signature using RSA public key" } if (data.format != null) throw UnsupportedOperationException("RSA with pre-hashed input is unsupported") @@ -192,6 +191,8 @@ private fun SignatureAlgorithm.verifierForImpl else verifierForImpl(publicKey, configure, allowKotlin) } + is SignatureAlgorithm.HMAC -> + KmmResult.failure(IllegalArgumentException("HMAC is unsupported")) } /** @@ -264,11 +265,11 @@ private fun SignatureAlgorithm.RSA.verifierForImpl catching { PlatformRSAVerifier(this, publicKey, configure) } /** @see [SignatureAlgorithm.verifierFor] */ -fun X509SignatureAlgorithm.verifierFor +fun SpecializedSignatureAlgorithm.verifierFor (publicKey: CryptoPublicKey, configure: ConfigurePlatformVerifier = null) = this.algorithm.verifierFor(publicKey, configure) /** @see [SignatureAlgorithm.platformVerifierFor] */ -fun X509SignatureAlgorithm.platformVerifierFor +fun SpecializedSignatureAlgorithm.platformVerifierFor (publicKey: CryptoPublicKey, configure: ConfigurePlatformVerifier = null) = this.algorithm.platformVerifierFor(publicKey, configure) diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt index cacf5b124..350304831 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/RSAVerifierCommonTests.kt @@ -30,7 +30,7 @@ open class RSAVerifierCommonTests : FreeSpec({ val key = CryptoPublicKey.decodeFromDer(Base64.decode(test.key)) as CryptoPublicKey.RSA val b64msg = test.msg val msg = Base64.decode(b64msg) - val sig = CryptoSignature.RSA(Base64.decode(test.sig)) + val sig = CryptoSignature.RSAorHMAC(Base64.decode(test.sig)) } /* diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 61dbca4ff..5659baa2f 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -1,7 +1,7 @@ import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.asn1.encoding.encodeTo4Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode +import at.asitplus.signum.indispensable.mac.MAC import at.asitplus.signum.indispensable.misc.bit import at.asitplus.signum.indispensable.misc.bytes import at.asitplus.signum.indispensable.symmetric.* @@ -409,7 +409,7 @@ class `00SymmetricTest` : FreeSpec({ withData( nameFn = { it.first }, "Default" to DefaultDedicatedMacInputCalculation, - "Oklahoma MAC" to fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = + "Oklahoma MAC" to fun MAC.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = "Oklahoma".encodeToByteArray() + (iv ?: byteArrayOf()) + (aad ?: byteArrayOf()) + ciphertext) { (_, macInputFun) -> withData( diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt index 567271a82..a52afedc0 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/os/IosKeychainProvider.kt @@ -279,7 +279,7 @@ sealed class IosSigner(final override val alias: String, ) } override fun bytesToSignature(sigBytes: ByteArray) = - CryptoSignature.RSA(sigBytes) + CryptoSignature.RSAorHMAC(sigBytes) } } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index cc60839f3..b52756985 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -37,7 +37,7 @@ sealed class EphemeralSigner(internal val privateKey: OwnedCFValue) : }.takeFromCF().toByteArray() return@signCatching when (val pubkey = publicKey) { is CryptoPublicKey.EC -> CryptoSignature.EC.decodeFromDer(signatureBytes).withCurve(pubkey.curve) - is CryptoPublicKey.RSA -> CryptoSignature.RSA(signatureBytes) + is CryptoPublicKey.RSA -> CryptoSignature.RSAorHMAC(signatureBytes) } } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index 8a2e8a5f2..3e79fa843 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -77,6 +77,6 @@ internal actual fun verifyECDSAImpl internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSA, + data: SignatureInput, signature: CryptoSignature.RSAorHMAC, config: PlatformVerifierConfiguration) = verifyImpl(signatureAlgorithm, publicKey, data, signature, config) diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 1af30bea0..84cfb3112 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -68,7 +68,7 @@ sealed class EphemeralSigner (internal val privateKey: PrivateKey, private val p override val publicKey: CryptoPublicKey.RSA, override val signatureAlgorithm: SignatureAlgorithm.RSA) : EphemeralSigner(privateKey, config.provider), Signer.RSA { - override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSA.parseFromJca(bytes) + override fun parseFromJca(bytes: ByteArray) = CryptoSignature.RSAorHMAC.parseFromJca(bytes) @SecretExposure final override fun exportPrivateKey() = (privateKey as RSAPrivateKey).toCryptoPrivateKey() diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt index cc2ebf11d..8b2d2921c 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/PrivateKeySignerImpl.kt @@ -56,6 +56,7 @@ fun SignatureAlgorithm.signerFor( signatureAlgorithm = this ) + is SignatureAlgorithm.HMAC -> throw UnsupportedOperationException("HMAC is not yet supported!") is SignatureAlgorithm.RSA -> EphemeralSigner.RSA( config = DSL.resolve( ::EphemeralSignerConfiguration, diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt index a70d555b7..2447e582d 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/VerifierImpl.kt @@ -82,7 +82,7 @@ internal actual fun checkAlgorithmKeyCombinationSupportedByRSAPlatformVerifier @JvmSynthetic internal actual fun verifyRSAImpl (signatureAlgorithm: SignatureAlgorithm.RSA, publicKey: CryptoPublicKey.RSA, - data: SignatureInput, signature: CryptoSignature.RSA, + data: SignatureInput, signature: CryptoSignature.RSAorHMAC, config: PlatformVerifierConfiguration) { getRSAInstance(signatureAlgorithm, config).run { diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt index 0c32d0f51..ccf5f016b 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt @@ -114,8 +114,8 @@ class JKSProviderTest : FreeSpec({ } CryptoSignature.parseFromJca(signature.jcaSignatureBytes, signer.signatureAlgorithm) shouldBe signature when (signer.signatureAlgorithm) { - is SignatureAlgorithm.RSA -> - CryptoSignature.RSA.parseFromJca(signature.jcaSignatureBytes) shouldBe signature + is SignatureAlgorithm.RSA, is SignatureAlgorithm.HMAC -> + CryptoSignature.RSAorHMAC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature is SignatureAlgorithm.ECDSA -> CryptoSignature.EC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature } diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index 87bcd090a..5cada8a98 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -138,9 +138,7 @@ class JvmSymmetricTest : FreeSpec({ alg.sealedBoxFrom( own.algorithm.randomNonce(), own.encryptedData - ).getOrThrow().decrypt(secretKey).onSuccess { - it shouldNotBe data - } + ).getOrThrow().decrypt(secretKey) shouldNot succeed } } From 72ce79047df471aa770f810f9a5b723c03fb125b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Wed, 19 Feb 2025 20:05:29 +0100 Subject: [PATCH 05/28] rid sealedbox of aad --- .../indispensable/symmetric/SealedBox.kt | 34 +++---- .../symmetric/SealedBoxCreation.kt | 24 ++--- .../indispensable/symmetric/SymmetricKey.kt | 2 +- .../signum/supreme/symmetric/Encryptor.jca.kt | 7 +- .../signum/supreme/symmetric/Encryptor.kt | 9 +- .../signum/supreme/symmetric/KeyGen.kt | 6 +- .../signum/supreme/symmetric/decrypt.kt | 95 +++++++++++++------ .../supreme/symmetric/discouraged/encrypt.kt | 4 +- .../signum/supreme/symmetric/00ApiTest.kt | 26 ++--- .../symmetric/00SymmetricAgainstReference.kt | 10 +- .../supreme/symmetric/00SymmetricTest.kt | 57 +++-------- .../signum/supreme/symmetric/AES.ios.kt | 3 +- .../signum/supreme/symmetric/ChaCha.ios.kt | 3 +- .../signum/supreme/symmetric/Encryptor.ios.kt | 3 +- .../asitplus/signum/supreme/symmetric/Test.kt | 10 +- 15 files changed, 138 insertions(+), 155 deletions(-) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt index 27f885af4..725ce6683 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt @@ -5,8 +5,6 @@ import kotlin.contracts.contract val SealedBox, I, *>.authTag get() = (this as SealedBox.Authenticated<*, *>).authTag -val SealedBox, I, *>.authenticatedData - get() = (this as SealedBox.Authenticated<*, *>).authenticatedData val SealedBox<*, NonceTrait.Required, *>.nonce get() = (this as SealedBox.WithNonce<*, *>).nonce @@ -14,14 +12,13 @@ val SealedBox<*, NonceTrait.Required, *>.nonce get() = (this as SealedBox.WithNo * Represents symmetrically encrypted data. This is a separate class to more easily enforce type safety wrt. presence of * Construct using [SymmetricEncryptionAlgorithm.sealedBoxFrom] */ -sealed interface SealedBox, I : NonceTrait, K : KeyType> { +sealed interface SealedBox, I : NonceTrait, K : KeyType> { val algorithm: SymmetricEncryptionAlgorithm val encryptedData: ByteArray interface Authenticated : - SealedBox, I, K> { + SealedBox, I, K> { val authTag: ByteArray - val authenticatedData: ByteArray? } interface Unauthenticated : @@ -31,7 +28,7 @@ sealed interface SealedBox, I : NonceTrait, K : KeyType> { * A sealed box without an IV/nonce. * Construct using [SymmetricEncryptionAlgorithm.sealedBoxFrom] */ - sealed class WithoutNonce, K : KeyType>( + sealed class WithoutNonce, K : KeyType>( private val ciphertext: Ciphertext, K> ) : SealedBox { @@ -63,11 +60,10 @@ sealed interface SealedBox, I : NonceTrait, K : KeyType> { SealedBox.Unauthenticated class Authenticated - internal constructor(ciphertext: Ciphertext.Authenticated, NonceTrait.Without, SymmetricEncryptionAlgorithm, NonceTrait.Without, K>, K>) : - WithoutNonce, K>(ciphertext), + internal constructor(ciphertext: Ciphertext.Authenticated, NonceTrait.Without, SymmetricEncryptionAlgorithm, NonceTrait.Without, K>, K>) : + WithoutNonce, K>(ciphertext), SealedBox.Authenticated { override val authTag = ciphertext.authTag - override val authenticatedData = ciphertext.authenticatedData } } @@ -75,7 +71,7 @@ sealed interface SealedBox, I : NonceTrait, K : KeyType> { * A sealed box consisting of an [nonce] and the actual [ciphertext]. * Construct using [SymmetricEncryptionAlgorithm.sealedBoxFrom] */ - sealed class WithNonce, K : KeyType>( + sealed class WithNonce, K : KeyType>( val nonce: ByteArray, private val ciphertext: Ciphertext, K> ) : SealedBox { @@ -117,12 +113,11 @@ sealed interface SealedBox, I : NonceTrait, K : KeyType> { class Authenticated internal constructor( nonce: ByteArray, - ciphertext: Ciphertext.Authenticated, NonceTrait.Required, SymmetricEncryptionAlgorithm, NonceTrait.Required, K>, K> + ciphertext: Ciphertext.Authenticated, NonceTrait.Required, SymmetricEncryptionAlgorithm, NonceTrait.Required, K>, K> ) : - WithNonce, K>(nonce, ciphertext), + WithNonce, K>(nonce, ciphertext), SealedBox.Authenticated { override val authTag = ciphertext.authTag - override val authenticatedData = ciphertext.authenticatedData } } } @@ -131,25 +126,24 @@ sealed interface SealedBox, I : NonceTrait, K : KeyType> { /** * A generic ciphertext object, referencing the algorithm it was created by. */ -sealed interface Ciphertext, I : NonceTrait, E : SymmetricEncryptionAlgorithm, K : KeyType> { +sealed interface Ciphertext, I : NonceTrait, E : SymmetricEncryptionAlgorithm, K : KeyType> { val algorithm: E val encryptedData: ByteArray /** * An authenticated ciphertext, i.e. containing an [authTag], and, optionally [authenticatedData] (_Additional Authenticated Data_) */ - class Authenticated, I : NonceTrait, E : SymmetricEncryptionAlgorithm, K : KeyType> internal constructor( + class Authenticated, I : NonceTrait, E : SymmetricEncryptionAlgorithm, K : KeyType> internal constructor( override val algorithm: E, override val encryptedData: ByteArray, val authTag: ByteArray, - val authenticatedData: ByteArray? ) : Ciphertext { @OptIn(ExperimentalStdlibApi::class) override fun toString(): String = "$algorithm Authenticated Ciphertext(encryptedData=${this.encryptedData.toHexString(HexFormat.UpperCase)}, authTag=${ authTag.toHexString(HexFormat.UpperCase) - }, aad=${authenticatedData?.toHexString(HexFormat.UpperCase)})" + })" override fun equals(other: Any?): Boolean { if (this === other) return true @@ -158,7 +152,6 @@ sealed interface Ciphertext, I : NonceTrait, E : Symmetric if (algorithm != other.algorithm) return false if (!encryptedData.contentEquals(other.encryptedData)) return false if (!authTag.contentEquals(other.authTag)) return false - if (!authenticatedData.contentEquals(other.authenticatedData)) return false return true } @@ -167,7 +160,6 @@ sealed interface Ciphertext, I : NonceTrait, E : Symmetric var result = algorithm.hashCode() result = 31 * result + encryptedData.contentHashCode() result = 31 * result + authTag.contentHashCode() - result = 31 * result + (authenticatedData?.contentHashCode() ?: 0) return result } @@ -204,7 +196,7 @@ sealed interface Ciphertext, I : NonceTrait, E : Symmetric /**Use to smart-cast this sealed box*/ @OptIn(ExperimentalContracts::class) -fun , K : KeyType, I : NonceTrait> SealedBox.isAuthenticated(): Boolean { +fun , K : KeyType, I : NonceTrait> SealedBox.isAuthenticated(): Boolean { contract { returns(true) implies (this@isAuthenticated is SealedBox.Authenticated) returns(false) implies (this@isAuthenticated is SealedBox.Unauthenticated) @@ -214,7 +206,7 @@ fun , K : KeyType, I : NonceTrait> SealedBox.isAu /**Use to smart-cast this sealed box*/ @OptIn(ExperimentalContracts::class) -fun , K : KeyType, I : NonceTrait> SealedBox.hasNonce(): Boolean { +fun , K : KeyType, I : NonceTrait> SealedBox.hasNonce(): Boolean { contract { returns(true) implies (this@hasNonce is SealedBox.WithNonce) returns(false) implies (this@hasNonce is SealedBox.WithoutNonce) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt index f45ff3c4a..5fc879e25 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt @@ -1,5 +1,6 @@ package at.asitplus.signum.indispensable.symmetric +import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.misc.bytes import kotlin.jvm.JvmName @@ -49,24 +50,23 @@ fun SymmetricEncryptionAlgorithm, NonceTrait.Required, *>.sealedBoxFrom( +fun, K: KeyType> SymmetricEncryptionAlgorithm.sealedBoxFrom( nonce: ByteArray, encryptedData: ByteArray, authTag: ByteArray, - authenticatedData: ByteArray? = null -) = catching { +) : KmmResult> =catching { require(authTag.size.bytes == this.authCapability.tagLength) { "Illegal auth tag length! expected: ${authTag.size * 8}, actual: ${this.authCapability.tagLength.bits}" } when (isIntegrated()) { false -> SealedBox.WithNonce.Authenticated( nonce, - authenticatedCipherText(encryptedData, authTag, authenticatedData) + authenticatedCipherText(encryptedData, authTag) ) true -> SealedBox.WithNonce.Authenticated( nonce, - authenticatedCipherText(encryptedData, authTag, authenticatedData) + authenticatedCipherText(encryptedData, authTag) ) - } + } as SealedBox } /** @@ -79,12 +79,11 @@ fun SymmetricEncryptionAlgorithm, NonceTrait.Req fun SymmetricEncryptionAlgorithm, NonceTrait.Without, *>.sealedBoxFrom( encryptedData: ByteArray, authTag: ByteArray, - authenticatedData: ByteArray? = null -) = catching { +) : KmmResult, NonceTrait.Without,*>> = catching { require(authTag.size == this.authCapability.tagLength.bytes.toInt()) { "Illegal auth tag length! expected: ${authTag.size * 8}, actual: ${this.authCapability.tagLength.bits}" } when (hasDedicatedMac()) { true -> SealedBox.WithoutNonce.Authenticated( - authenticatedCipherText(encryptedData, authTag, authenticatedData) + authenticatedCipherText(encryptedData, authTag) ) false -> SealedBox.WithoutNonce.Authenticated( @@ -92,19 +91,16 @@ fun SymmetricEncryptionAlgorithm, NonceTrait.Wit (this as SymmetricEncryptionAlgorithm).authenticatedCipherText( encryptedData, authTag, - authenticatedData ) ) - } + }as SealedBox, NonceTrait.Without,*> } -private inline fun , reified I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.authenticatedCipherText( +private inline fun , reified I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.authenticatedCipherText( encryptedData: ByteArray, authTag: ByteArray, - authenticatedData: ByteArray? = null ) = Ciphertext.Authenticated, K>( this, encryptedData, authTag, - authenticatedData ) \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index 071bc75d8..bb4083d08 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -3,6 +3,7 @@ package at.asitplus.signum.indispensable.symmetric import at.asitplus.signum.HazardousMaterials import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract +import kotlin.jvm.JvmName sealed interface KeyType { object Integrated : KeyType @@ -208,4 +209,3 @@ fun , K : KeyType> SymmetricKey.requiresNonce(): * The actual encryption key bytes */ val , I : NonceTrait> SymmetricKey.secretKey get() = (this as SymmetricKey.Integrated).secretKey -val SymmetricKey, I, out KeyType.WithDedicatedMacKey >.encryptionKey get() = (this as SymmetricKey.WithDedicatedMac).encryptionKey \ No newline at end of file diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt index b81dc4013..0606c094e 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt @@ -55,7 +55,7 @@ internal actual fun , I : NonceTrait, K : KeyType> Cip alg.requiresNonce() -> when { alg.isAuthenticated() -> { (alg as SymmetricEncryptionAlgorithm, NonceTrait.Required, *>) - alg.sealedBoxFrom(nonce!!, ciphertext, authTag!!, aad) + alg.sealedBoxFrom(nonce!!, ciphertext, authTag!!) } else -> alg.sealedBoxFrom(nonce!!, ciphertext) @@ -64,7 +64,7 @@ internal actual fun , I : NonceTrait, K : KeyType> Cip else -> when { alg.isAuthenticated() -> { (alg as SymmetricEncryptionAlgorithm, NonceTrait.Without, *>) - alg.sealedBoxFrom(ciphertext, authTag!!, aad) + alg.sealedBoxFrom(ciphertext, authTag!!) } else -> alg.sealedBoxFrom(ciphertext) @@ -93,7 +93,8 @@ val SymmetricEncryptionAlgorithm<*, *, *>.jcaKeySpec: String @JvmName("doDecryptAuthenticated") internal actual fun SealedBox.doDecryptAEAD( - secretKey: ByteArray + secretKey: ByteArray, + authenticatedData: ByteArray ): ByteArray { if (!this.hasNonce()) TODO("AEAD algorithm $algorithm is UNSUPPORTED") diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt index 17a73c117..f30db1791 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt @@ -60,13 +60,11 @@ internal class Encryptor, I : NonceTrait, out K : KeyT (algorithm).sealedBoxFrom( (encrypted as SealedBox.WithNonce<*, *>).nonce, encrypted.encryptedData, - authTag, - aad + authTag ) } else (algorithm).sealedBoxFrom( encrypted.encryptedData, - authTag, - aad + authTag )).getOrThrow() as SealedBox } else platformCipher.doEncrypt(data)) @@ -80,7 +78,8 @@ internal class CipherParam, K : KeyType>( ) internal expect fun SealedBox.doDecryptAEAD( - secretKey: ByteArray + secretKey: ByteArray, + authenticatedData: ByteArray ): ByteArray internal expect fun SealedBox.doDecrypt( diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt index 4f60c8b12..23f7d9ff1 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt @@ -109,10 +109,10 @@ fun SymmetricEncryptionAlgorithm SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>.keyFrom( +fun , I : NonceTrait> SymmetricEncryptionAlgorithm.keyFrom( encryptionKey: ByteArray, macKey: ByteArray -): KmmResult, I, KeyType.WithDedicatedMacKey>> = +): KmmResult> = catching { (this as SymmetricEncryptionAlgorithm<*, *, *>).keyFromInternal(encryptionKey, macKey) - } as KmmResult, I, KeyType.WithDedicatedMacKey>> + } as KmmResult> diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index ac28d4667..32fb09062 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -2,6 +2,9 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.KmmResult import at.asitplus.catching +import at.asitplus.signum.ImplementationError +import at.asitplus.signum.indispensable.mac.HMAC +import at.asitplus.signum.indispensable.mac.MAC import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES @@ -16,18 +19,18 @@ import kotlin.jvm.JvmName * [key] and [SealedBox].** */ @JvmName("decryptGeneric") -fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>) = catching { +fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult = catching { require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } @Suppress("UNCHECKED_CAST") when (algorithm.authCapability) { is Authenticated.Integrated -> (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey + (key as SymmetricKey.Integrated).secretKey, byteArrayOf() ) is Authenticated.WithDedicatedMac<*, *> -> { key as SymmetricKey.WithDedicatedMac (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( - key.encryptionKey, key.macKey + key.encryptionKey, key.macKey, byteArrayOf() ) } @@ -37,74 +40,110 @@ fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>) = catching { } } + + + +//required because we don't store MAC info all the way +@JvmName("decryptAuthenticatedIntegrated") +fun SealedBox,I, KeyType.WithDedicatedMacKey>.decrypt( + key: SymmetricKey.WithDedicatedMac<*>, + authenticatedData: ByteArray = byteArrayOf() +) = catching { + (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( + key.encryptionKey, + key.macKey, + authenticatedData + ) +} + /** - * Attempts to decrypt this ciphertext (which may also hold an IV/nonce, and in case of an authenticated ciphertext, authenticated data and auth tag) using the provided [key]. - * This constrains the [key]'s characteristics to the characteristics of the [SealedBox] to decrypt. - * It does not, however, prevent maxing up different encryption algorithms with the same characteristics. I.e., it is possible to feed a [AES.ECB] key into - * a [AES.CBC] [SealedBox]. - * In such cases, this function will immediately return a [KmmResult.failure]. + * Attempts to decrypt this ciphertext (which may also hold an IV/nonce) using the provided [key]. + * Do provide [authenticatedData] if required, or else decryption will fail! + * This is the generic, untyped decryption function for convenience. + * **Compared to its narrower-typed cousins is possible to mismatch the characteristics of + * [key] and [SealedBox].** */ -fun , I : NonceTrait, K : KeyType> SealedBox.decrypt( - key: SymmetricKey +@JvmName("decryptAuthenticatedGeneric") +fun , K : KeyType> SealedBox.decrypt( + key: SymmetricKey, + authenticatedData: ByteArray = byteArrayOf() ): KmmResult = catching { require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } @Suppress("UNCHECKED_CAST") - when (algorithm.authCapability as AuthCapability<*>) { + when (algorithm.authCapability) { is Authenticated.Integrated -> (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey + (key as SymmetricKey.Integrated).secretKey, authenticatedData ) is Authenticated.WithDedicatedMac<*, *> -> { key as SymmetricKey.WithDedicatedMac (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( - key.encryptionKey, key.macKey + key.encryptionKey, key.macKey, authenticatedData ) } - is AuthCapability.Unauthenticated -> (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey - ) + else -> throw ImplementationError("Authenticated Decryption") } } +/** + * Attempts to decrypt this ciphertext (which may also hold an IV/nonce) using the provided [key]. + * This constrains the [key]'s characteristics to the characteristics of the [SealedBox] to decrypt. + * It does not, however, prevent mixing up different encryption algorithms with the same characteristics. I.e., it is possible to feed a [AES.ECB] key into + * a [AES.CBC] [SealedBox]. + * In such cases, this function will immediately return a [KmmResult.failure]. + */ +fun SealedBox.decrypt( + key: SymmetricKey +): KmmResult = catching { + require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } + @Suppress("UNCHECKED_CAST") + (this as SealedBox).decryptInternal( + (key as SymmetricKey.Integrated).secretKey + ) + +} + + /** * Attempts to decrypt this ciphertext (which may also hold an IV/nonce, and in case of an authenticated ciphertext, authenticated data and auth tag) using the provided [key]. * This constrains the [key]'s characteristics to the characteristics of the [SealedBox] to decrypt. - * It does not, however, prevent maxing up different encryption algorithms with the same characteristics. I.e., it is possible to feed a [SymmetricEncryptionAlgorithm.ChaCha20Poly1305] key into + * It does not, however, prevent mixing up different encryption algorithms with the same characteristics. I.e., it is possible to feed a [SymmetricEncryptionAlgorithm.ChaCha20Poly1305] key into * a [AES.GCM] [SealedBox]. * In such cases, this function will immediately return a [KmmResult.failure]. */ @JvmName("decryptRawAuthenticated") -private fun SealedBox.decryptInternal( - secretKey: ByteArray +private fun SealedBox.decryptInternal( + secretKey: ByteArray, + authenticatedData: ByteArray ): ByteArray { require(secretKey.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } - return doDecryptAEAD(secretKey) + return doDecryptAEAD(secretKey, authenticatedData) } @JvmName("decryptRaw") -private fun SealedBox.decryptInternal( +private fun SealedBox.decryptInternal( secretKey: ByteArray ): ByteArray { require(secretKey.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } return doDecrypt(secretKey) } -private fun SealedBox, *, KeyType.WithDedicatedMacKey>.decryptInternal( +private fun SealedBox, *, out KeyType.WithDedicatedMacKey>.decryptInternal( secretKey: ByteArray, macKey: ByteArray = secretKey, + authenticatedData: ByteArray ): ByteArray { require(this.isAuthenticated()) val iv: ByteArray? = if (this is SealedBox.WithNonce<*, *>) nonce else null - val aad = authenticatedData val authTag = authTag val algorithm = algorithm val innerCipher = algorithm.authCapability.innerCipher val mac = algorithm.authCapability.mac val dedicatedMacInputCalculation = algorithm.authCapability.dedicatedMacInputCalculation - val hmacInput = mac.dedicatedMacInputCalculation(encryptedData, iv ?: byteArrayOf(), aad ?: byteArrayOf()) + val hmacInput = mac.dedicatedMacInputCalculation(encryptedData, iv ?: byteArrayOf(), authenticatedData) val transform = algorithm.authCapability.dedicatedMacAuthTagTransform if (!algorithm.authCapability.transform(mac.mac(macKey, hmacInput).getOrThrow()).contentEquals(authTag)) throw IllegalArgumentException("Auth Tag mismatch!") @@ -151,9 +190,9 @@ fun SymmetricKey, NonceTrait.Required, *>.decryp nonce: ByteArray, encryptedData: ByteArray, authTag: ByteArray, - authenticatedData: ByteArray? = null + authenticatedData: ByteArray = byteArrayOf() ): KmmResult = - algorithm.sealedBoxFrom(nonce, encryptedData, authTag, authenticatedData).transform { it.decrypt(this) } + algorithm.sealedBoxFrom(nonce, encryptedData, authTag).transform { it.decrypt(this, authenticatedData) } /** * Directly decrypts raw [encryptedData], feeding [authTag], and [authenticatedData] into the decryption process. @@ -163,6 +202,6 @@ fun SymmetricKey, NonceTrait.Required, *>.decryp fun SymmetricKey, NonceTrait.Without, *>.decrypt( encryptedData: ByteArray, authTag: ByteArray, - authenticatedData: ByteArray? = null + authenticatedData: ByteArray = byteArrayOf() ): KmmResult = - algorithm.sealedBoxFrom(encryptedData, authTag, authenticatedData).transform { it.decrypt(this) } \ No newline at end of file + algorithm.sealedBoxFrom(encryptedData, authTag).transform { it.decrypt(this, authenticatedData) } \ No newline at end of file diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt index 01a23ed6e..873677bc5 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt @@ -24,14 +24,14 @@ import kotlin.jvm.JvmName fun > KeyWithNonceAuthenticating.encrypt( data: ByteArray, authenticatedData: ByteArray? = null -): KmmResult> = catching { +): KmmResult> = catching { Encryptor( second.algorithm, if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).encryptionKey else (second as SymmetricKey.Integrated).secretKey, if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).macKey else (second as SymmetricKey.Integrated).secretKey, first, authenticatedData, - ).encrypt(data) as SealedBox.WithNonce.Authenticated + ).encrypt(data) as SealedBox } /** diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt index 576840f57..d52aada71 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt @@ -55,14 +55,9 @@ class `00ApiTest` : FreeSpec({ algorithm.sealedBoxFrom( byteArrayOf(), //nonce byteArrayOf(), //encrypted - byteArrayOf(), //nonce - ) - algorithm.sealedBoxFrom( - byteArrayOf(), - byteArrayOf(), - byteArrayOf(), - byteArrayOf(), + byteArrayOf(), //auth tag ) + } false -> { @@ -83,10 +78,8 @@ class `00ApiTest` : FreeSpec({ //compile error //algorithm.sealedBox(byteArrayOf()) algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) - //why ambiguous?? - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) //compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf(), byteArrayOf(), byteArrayOf()) + //algorithm.sealedBox(byteArrayOf(), byteArrayOf(), byteArrayOf()) } false -> { @@ -115,13 +108,7 @@ class `00ApiTest` : FreeSpec({ algorithm.sealedBoxFrom( byteArrayOf(), //nonce byteArrayOf(), //encrypted - byteArrayOf(), //nonce - ) - algorithm.sealedBoxFrom( - byteArrayOf(), //nonce - byteArrayOf(), //nonce - byteArrayOf(), //nonce - byteArrayOf(), //nonce + byteArrayOf(), //authTag ) } @@ -129,9 +116,8 @@ class `00ApiTest` : FreeSpec({ //Compile error //algorithm.sealedBox(byteArrayOf()) algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) //Compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf(),byteArrayOf(), byteArrayOf()) + //algorithm.sealedBox(byteArrayOf(), byteArrayOf(),byteArrayOf()) } @@ -198,7 +184,7 @@ class `00ApiTest` : FreeSpec({ algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) //correct - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf(), byteArrayOf()) + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) } } diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricAgainstReference.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricAgainstReference.kt index e1326d8dc..21b77bf12 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricAgainstReference.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricAgainstReference.kt @@ -3,7 +3,7 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm import at.asitplus.signum.indispensable.symmetric.authTag -import at.asitplus.signum.indispensable.symmetric.authenticatedData +import at.asitplus.signum.indispensable.symmetric.nonce import at.asitplus.signum.supreme.symmetric.discouraged.andPredefinedNonce import at.asitplus.signum.supreme.symmetric.discouraged.encrypt import io.kotest.assertions.withClue @@ -54,10 +54,9 @@ class `00SymmetricAgainstReference` : FreeSpec({ withClue(encrypted.toHexString()) { box.encryptedData shouldBe encrypted box.authTag shouldBe authTag - box.authenticatedData shouldBe aad box.nonce shouldBe iv } - box.decrypt(key).getOrThrow() shouldBe plaintext + box.decrypt(key, aad?:byteArrayOf()).getOrThrow() shouldBe plaintext } 192 / 8 -> SymmetricEncryptionAlgorithm.AES_192.GCM.apply { @@ -110,10 +109,9 @@ class `00SymmetricAgainstReference` : FreeSpec({ withClue(encrypted.toHexString()) { box.encryptedData shouldBe encrypted box.authTag shouldBe authTag - box.authenticatedData shouldBe aad box.nonce shouldBe iv } - box.decrypt(key).getOrThrow() shouldBe plaintext + box.decrypt(key, aad?:byteArrayOf()).getOrThrow() shouldBe plaintext } } @@ -4543,7 +4541,7 @@ val ecb = """.trimIndent() -val wrap=""" +val wrap = """ [ { "key":"85ca1d468f4436f8f0e9ae2d780264d2", diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 5659baa2f..6855eb98d 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -49,18 +49,8 @@ class `00SymmetricTest` : FreeSpec({ authenticatedData = aad, ).getOrThrow(/*handle error*/) - //The sealed box object is correctly typed: - // * It is a SealedBox.WithIV - // * The generic type arguments indicate that - // * the ciphertext is authenticated - // * Using a dedicated MAC function atop an unauthenticated cipher - // * we can hence access `authenticatedCiphertext` for: - // * authTag - // * authenticatedData - sealedBox.authenticatedData shouldBe aad - //because everything is structured, decryption is simple - val recovered = sealedBox.decrypt(key).getOrThrow(/*handle error*/) + val recovered = sealedBox.decrypt(key, aad).getOrThrow(/*handle error*/) recovered shouldBe payload //success! @@ -69,11 +59,11 @@ class `00SymmetricTest` : FreeSpec({ sealedBox.nonce, encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/ authTag = sealedBox.authTag, - authenticatedData = sealedBox.authenticatedData ).getOrThrow() val manuallyRecovered = reconstructed.decrypt( - key + key, + authenticatedData = aad, ).getOrThrow(/*handle error*/) manuallyRecovered shouldBe payload //great success! @@ -81,6 +71,7 @@ class `00SymmetricTest` : FreeSpec({ //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! } @@ -348,9 +339,7 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce.size shouldBe alg.nonceTrait.length.bytes.toInt() if (iv != null) ciphertext.nonce shouldBe iv ciphertext.algorithm.authCapability.shouldBeInstanceOf>() - ciphertext.authenticatedData shouldBe aad - - val decrypted = ciphertext.decrypt(key).getOrThrow() + val decrypted = ciphertext.decrypt(key, aad?:byteArrayOf()).getOrThrow() decrypted shouldBe plaintext @@ -361,11 +350,10 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce, Random.Default.nextBytes(ciphertext.encryptedData.size), authTag = ciphertext.authTag, - authenticatedData = ciphertext.authenticatedData ).getOrThrow() - val wrongWrongDecrypted = wrongCiphertext.decrypt(alg.randomKey()) + val wrongWrongDecrypted = wrongCiphertext.decrypt(alg.randomKey(), aad?:byteArrayOf()) wrongWrongDecrypted shouldNot succeed val wrongRightDecrypted = wrongCiphertext.decrypt(key) @@ -375,10 +363,9 @@ class `00SymmetricTest` : FreeSpec({ nonce = ciphertext.nonce.asList().shuffled().toByteArray(), ciphertext.encryptedData, authTag = ciphertext.authTag, - authenticatedData = ciphertext.authenticatedData ).getOrThrow() - val wrongIVDecrypted = wrongIV.decrypt(key) + val wrongIVDecrypted = wrongIV.decrypt(key,aad?:byteArrayOf()) wrongIVDecrypted shouldNot succeed @@ -388,7 +375,6 @@ class `00SymmetricTest` : FreeSpec({ nonce = ciphertext.nonce, encryptedData = ciphertext.encryptedData, authTag = ciphertext.authTag, - authenticatedData = null ).getOrThrow().decrypt(key) shouldNot succeed } @@ -397,8 +383,7 @@ class `00SymmetricTest` : FreeSpec({ nonce = ciphertext.nonce, ciphertext.encryptedData, authTag = ciphertext.authTag.asList().shuffled().toByteArray(), - authenticatedData = ciphertext.authenticatedData, - ).getOrThrow().decrypt(key) shouldNot succeed + ).getOrThrow().decrypt(key, aad?:byteArrayOf()) shouldNot succeed } } } @@ -534,9 +519,8 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce.shouldNotBeNull() ciphertext.nonce.size shouldBe it.nonceTrait.length.bytes.toInt() ciphertext.algorithm.authCapability.shouldBeInstanceOf>() - ciphertext.authenticatedData shouldBe aad - val decrypted = ciphertext.decrypt(key).getOrThrow() + val decrypted = ciphertext.decrypt(key, aad?:byteArrayOf()).getOrThrow() decrypted shouldBe plaintext val wrongDecrypted = ciphertext.decrypt(it.randomKey()) @@ -547,10 +531,9 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce, Random.Default.nextBytes(ciphertext.encryptedData.size), authTag = ciphertext.authTag, - authenticatedData = ciphertext.authenticatedData ).getOrThrow() - val wrongWrongDecrypted = wrongCiphertext.decrypt(it.randomKey()) + val wrongWrongDecrypted = wrongCiphertext.decrypt(it.randomKey(), aad?:byteArrayOf()) wrongWrongDecrypted shouldNot succeed val wrongRightDecrypted = @@ -562,29 +545,26 @@ class `00SymmetricTest` : FreeSpec({ nonce = ciphertext.nonce.asList().shuffled().toByteArray(), ciphertext.encryptedData, ciphertext.authTag, - ciphertext.authenticatedData ).getOrThrow() - val wrongIVDecrypted = wrongIV.decrypt(key) + val wrongIVDecrypted = wrongIV.decrypt(key, aad?:byteArrayOf()) wrongIVDecrypted shouldNot succeed ciphertext.algorithm.sealedBoxFrom( nonce = ciphertext.nonce.asList().shuffled().toByteArray(), ciphertext.encryptedData, authTag = ciphertext.authTag, - authenticatedData = ciphertext.authenticatedData, - ).getOrThrow().decrypt(key) shouldNot succeed + ).getOrThrow().decrypt(key, aad?:byteArrayOf()) shouldNot succeed ciphertext.algorithm.sealedBoxFrom( nonce = ciphertext.nonce, ciphertext.encryptedData, authTag = ciphertext.authTag, - authenticatedData = ciphertext.authenticatedData, ).getOrThrow().decrypt( SymmetricKey.WithDedicatedMac.RequiringNonce( ciphertext.algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, key.encryptionKey, dedicatedMacKey = key.macKey.asList().shuffled().toByteArray() - ) + ), aad?:byteArrayOf() ) shouldNot succeed if (aad != null) { @@ -592,7 +572,6 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce, ciphertext.encryptedData, ciphertext.authTag, - null ).getOrThrow().decrypt(key) shouldNot succeed } @@ -600,13 +579,11 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce, ciphertext.encryptedData, ciphertext.authTag.asList().shuffled().toByteArray(), - ciphertext.authenticatedData - ).getOrThrow().decrypt(key) shouldNot succeed + ).getOrThrow().decrypt(key, aad?:byteArrayOf()) shouldNot succeed ciphertext.algorithm.sealedBoxFrom( ciphertext.nonce, ciphertext.encryptedData, ciphertext.authTag.asList().shuffled().toByteArray(), - ciphertext.authenticatedData ).getOrThrow().decrypt(it.Custom(ciphertext.authTag.size.bytes) { _, _, _ -> "Szombathely".encodeToByteArray() }.let { @@ -615,7 +592,7 @@ class `00SymmetricTest` : FreeSpec({ key.encryptionKey, key.macKey ) - }) shouldNot succeed + }, aad?:byteArrayOf()) shouldNot succeed } } @@ -836,7 +813,6 @@ class `00SymmetricTest` : FreeSpec({ alg.randomNonce(), plaintext, Random.nextBytes(alg.authTagLength.bytes.toInt()), - plaintext ) false -> alg.sealedBoxFrom(alg.randomNonce(), plaintext) @@ -846,7 +822,6 @@ class `00SymmetricTest` : FreeSpec({ true -> alg.sealedBoxFrom( plaintext, Random.nextBytes(alg.authTagLength.bytes.toInt()), - plaintext ) false -> alg.sealedBoxFrom(plaintext) @@ -861,7 +836,6 @@ class `00SymmetricTest` : FreeSpec({ plaintext, if (box.isAuthenticated() && box.authTag.size == wrongAlg.authTagLength.bytes.toInt()) box.authTag else Random.nextBytes(wrongAlg.authTagLength.bytes.toInt()), - plaintext ) false -> wrongAlg.sealedBoxFrom( @@ -875,7 +849,6 @@ class `00SymmetricTest` : FreeSpec({ plaintext, if (box.isAuthenticated() && box.authTag.size == wrongAlg.authTagLength.bytes.toInt()) box.authTag else Random.nextBytes(wrongAlg.authTagLength.bytes.toInt()), - plaintext ) false -> wrongAlg.sealedBoxFrom(plaintext) diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt index c44350d39..465f2eb73 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt @@ -62,8 +62,7 @@ internal object AESIOS { alg.sealedBoxFrom( ciphertext.iv().toByteArray(), ciphertext.ciphertext().toByteArray(), - ciphertext.authTag().toByteArray(), - aad + ciphertext.authTag().toByteArray() ).getOrThrow() } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt index a0fa4e0c0..1beb3454a 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt @@ -27,8 +27,7 @@ internal object ChaChaIOS { return SymmetricEncryptionAlgorithm.ChaCha20Poly1305.sealedBoxFrom( ciphertext.iv().toByteArray(), ciphertext.ciphertext().toByteArray(), - ciphertext.authTag().toByteArray(), - aad + ciphertext.authTag().toByteArray() ).getOrThrow() as SealedBox } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt index 06dafc025..7fbf1986b 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt @@ -39,7 +39,8 @@ internal actual fun , I : NonceTrait, K : KeyType> Cip @OptIn(ExperimentalForeignApi::class) internal actual fun SealedBox.doDecryptAEAD( - secretKey: ByteArray + secretKey: ByteArray, + authenticatedData: ByteArray ): ByteArray { if (algorithm.nonceTrait !is NonceTrait.Required) TODO("ALGORITHM $algorithm UNSUPPORTED") this as SealedBox.WithNonce diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index 5cada8a98..de909f3cc 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -95,17 +95,17 @@ class JvmSymmetricTest : FreeSpec({ - own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) + own.decrypt(secretKey, aad?:byteArrayOf()).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) val wrongKey = own.algorithm.randomKey() own.decrypt(wrongKey) shouldNot succeed - own.algorithm.sealedBoxFrom( + val box = own.algorithm.sealedBoxFrom( own.algorithm.randomNonce(), own.encryptedData, own.authTag, - own.authenticatedData - ).getOrThrow().decrypt(secretKey) shouldNot succeed + ).getOrThrow() + box.decrypt(secretKey, aad?:byteArrayOf()) shouldNot succeed } else { @@ -289,7 +289,7 @@ class JvmSymmetricTest : FreeSpec({ box.nonce.size shouldBe alg.nonceTrait.length.bytes.toInt() box.isAuthenticated() shouldBe true (box.encryptedData + box.authTag) shouldBe fromJCA - box.decrypt(secretKey).getOrThrow() shouldBe data + box.decrypt(secretKey, aad?:byteArrayOf()).getOrThrow() shouldBe data } } From 7766f160ac284c37738ea417a93d93f9985cb5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Thu, 20 Feb 2025 12:38:22 +0100 Subject: [PATCH 06/28] address PR Feedback --- README.md | 77 ++++++++++++------- docs/docs/index.md | 5 +- docs/docs/supreme.md | 28 +++++++ .../indispensable/symmetric/SymmetricKey.kt | 2 +- .../asitplus/signum/internals/InteropUtils.kt | 12 +-- .../signum/supreme/symmetric/decrypt.kt | 7 ++ .../signum/supreme/symmetric/00ApiTest.kt | 48 +++++------- .../supreme/symmetric/00SymmetricTest.kt | 45 +++++++---- 8 files changed, 147 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index c4f22adec..41299d1c3 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,14 @@ ## Kotlin Multiplatform Crypto/PKI Library with ASN1 Parser + Encoder * **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** +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) @@ -113,7 +113,8 @@ certificates, CSRs, as well as COSE and JOSE data. Hence, we needed a fully-feat 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. +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.
@@ -128,7 +129,7 @@ We also needed platform-native attestation capabilities (and so will you sooner 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 — -**because they are the same** under the hood. Most notably: private keys never leave the platform and **hardware-backed private keys +**because they are the same** under the hood. Most notably: **hardware-backed private keys never even leave the hardware crypto modules**!
This tight integration and our focus on mobile comes at the cost of the **Supreme KMP crypto provider only supporting JVM, Android, and iOS**. @@ -295,7 +296,37 @@ println("Is it trustworthy? $isValid") ``` ## Symmetric Encryption -We currently support ChaCha20-Poly1503, AES-CBC, AES-GCM, and a very flexible flavour of AES-CBC-HMAC. +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.sealedBoxFrom(nonce, ciphertext, authTag).getOrThrow(/*handle error*/) +box.decrypt(preSharedKey, /*also pass AAD*/ 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() @@ -307,8 +338,7 @@ val algorithm = SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512 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 +//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() @@ -317,36 +347,29 @@ val sealedBox = key.encrypt( authenticatedData = aad, ).getOrThrow(/*handle error*/) -//The sealed box object is correctly typed: -// * It is a SealedBox.WithIV -// * The generic type arguments indicate that -// * the ciphertext is authenticated -// * Using a dedicated MAC function atop an unauthenticated cipher -// * we can hence access `authenticatedCiphertext` for: -// * authTag -// * authenticatedData -sealedBox.authenticatedData shouldBe aad - //because everything is structured, decryption is simple -val recovered = sealedBox.decrypt(key).getOrThrow(/*handle error*/) +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( +val reconstructed = algorithm.sealedBoxFrom( sealedBox.nonce, encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/ authTag = sealedBox.authTag, - authenticatedData = sealedBox.authenticatedData ).getOrThrow() -val manuallyRecovered = reconstructed.decrypt(key).getOrThrow(/*handle error*/) +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! ``` diff --git a/docs/docs/index.md b/docs/docs/index.md index 24a1d23aa..db392bc3e 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -100,7 +100,8 @@ and comprehensive ASN.1, JOSE, and COSE support. 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. + 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.
@@ -115,7 +116,7 @@ and comprehensive ASN.1, JOSE, and COSE support. 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 — - **because they are the same** under the hood. Most notably: private keys never leave the platform and **hardware-backed private keys + **because they are the same** under the hood. Most notably: **hardware-backed private keys never even leave the hardware crypto modules**!
This tight integration and our focus on mobile comes at the cost of the **Supreme KMP crypto provider only supporting JVM, Android, and iOS**. diff --git a/docs/docs/supreme.md b/docs/docs/supreme.md index e225a8c95..f2cd69c0e 100644 --- a/docs/docs/supreme.md +++ b/docs/docs/supreme.md @@ -474,6 +474,34 @@ this space. As of 01-2025, the following algorithms are implemented: * `SymmetricEncryptionAlgorithm.AES_192.ECB` * `SymmetricEncryptionAlgorithm.AES_256.ECB` +### 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.sealedBoxFrom(nonce, ciphertext, authTag).getOrThrow(/*handle error*/) +box.decrypt(preSharedKey, /*also pass AAD*/ 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: In addition, it is possible to customise AES-CBC-HMAC by freely defining which data gets fed into the MAC. There are also no constraints on the MAC key length, except that it must not be empty: diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index bb4083d08..f176cf7e0 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -119,7 +119,7 @@ sealed interface SymmetricKey
, I : NonceTrait, K : Key */ val encryptionKey: ByteArray, /** - * The actual dedicated MAX key bytes + * The actual dedicated MAC key bytes */ val macKey: ByteArray ) : SymmetricKey, I, KeyType.WithDedicatedMacKey>, diff --git a/internals/src/iosMain/kotlin/at/asitplus/signum/internals/InteropUtils.kt b/internals/src/iosMain/kotlin/at/asitplus/signum/internals/InteropUtils.kt index e4cc25cb9..e7713c4c6 100644 --- a/internals/src/iosMain/kotlin/at/asitplus/signum/internals/InteropUtils.kt +++ b/internals/src/iosMain/kotlin/at/asitplus/signum/internals/InteropUtils.kt @@ -9,12 +9,14 @@ import platform.posix.memcpy import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner -fun NSData.toByteArray(): ByteArray = ByteArray(length.toInt()).apply { +fun NSData.toByteArray(): ByteArray { if(length>Int.MAX_VALUE.toULong()) throw IndexOutOfBoundsException("length is too large") - if (length > 0uL) - usePinned { - memcpy(it.addressOf(0), bytes, length) - } + return ByteArray(length.toInt()).apply { + if (length > 0uL) + usePinned { + memcpy(it.addressOf(0), bytes, length) + } + } } @OptIn(BetaInteropApi::class) diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index 32fb09062..b73c85f89 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -44,6 +44,13 @@ fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult //required because we don't store MAC info all the way +/** + * Attempts to decrypt this ciphertext (which may also hold an IV/nonce) using the provided [key]. + * Do provide [authenticatedData] if required, or else decryption will fail! + * This is the generic, untyped decryption function for convenience. + * **Compared to its narrower-typed cousins is possible to mismatch the characteristics of + * [key] and [SealedBox].** + */ @JvmName("decryptAuthenticatedIntegrated") fun SealedBox,I, KeyType.WithDedicatedMacKey>.decrypt( key: SymmetricKey.WithDedicatedMac<*>, diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt index d52aada71..7d96da023 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt @@ -49,47 +49,40 @@ class `00ApiTest` : FreeSpec({ true -> { algorithm //compile error - //algorithm.sealedBox(byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf()) //compile error - //algorithm.sealedBox(byteArrayOf(),byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf()) algorithm.sealedBoxFrom( byteArrayOf(), //nonce byteArrayOf(), //encrypted byteArrayOf(), //auth tag ) - } false -> { //Compile error - //algorithm.sealedBox(byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf()) algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) //Compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf(),byteArrayOf()) - //Compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf(),byteArrayOf(), byteArrayOf()) - - + //algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(),byteArrayOf()) } } false -> when (algorithm.isAuthenticated()) { true -> { //compile error - //algorithm.sealedBox(byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf()) algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) - //compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf(), byteArrayOf()) + //TODO @Jakob, I need help. this should not compile, but it does. + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) } false -> { algorithm.sealedBoxFrom(byteArrayOf()) //compile error - //algorithm.sealedBox(byteArrayOf(),byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf()) //compile error - //algorithm.sealedBox(byteArrayOf(),byteArrayOf(),byteArrayOf()) - //compile error - //algorithm.sealedBox(byteArrayOf(),byteArrayOf(),byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf(),byteArrayOf()) } } } @@ -101,9 +94,9 @@ class `00ApiTest` : FreeSpec({ true -> { algorithm //compile error - //algorithm.sealedBox(byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf()) //compile error - //algorithm.sealedBox(byteArrayOf(),byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf()) //why ambiguous? algorithm.sealedBoxFrom( byteArrayOf(), //nonce @@ -114,10 +107,10 @@ class `00ApiTest` : FreeSpec({ false -> { //Compile error - //algorithm.sealedBox(byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf()) algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) - //Compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf(),byteArrayOf()) + //TODO @Jakob, I need help. this should not compile, but it does. + algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(),byteArrayOf()) } @@ -126,22 +119,19 @@ class `00ApiTest` : FreeSpec({ false -> when (algorithm.requiresNonce()) { true -> { //compile error - //algorithm.sealedBox(byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf()) algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) //compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf(), byteArrayOf()) - //compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf(), byteArrayOf(), byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) } false -> { algorithm.sealedBoxFrom(byteArrayOf()) //compile error - //algorithm.sealedBox(byteArrayOf(),byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf()) //compile error - //algorithm.sealedBox(byteArrayOf(),byteArrayOf(),byteArrayOf()) - //compile error - //algorithm.sealedBox(byteArrayOf(),byteArrayOf(),byteArrayOf()) + //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf(),byteArrayOf()) + } } } diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 6855eb98d..30c0f8871 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -19,7 +19,6 @@ import io.kotest.matchers.shouldNot import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf import kotlinx.datetime.Clock -import org.kotlincrypto.SecureRandom import kotlin.random.Random import kotlin.random.nextUInt @@ -29,7 +28,27 @@ class `00SymmetricTest` : FreeSpec({ "README" { - val secureRandom = SecureRandom() + + //base case + 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 + + //getting data from external + + val algo = SymmetricEncryptionAlgorithm.ChaCha20Poly1305 + val nonce = encrypted.nonce + val ciphertext = encrypted.encryptedData + val authTag = encrypted.authTag + val externalAAD = authenticatedData + val keyBytes = secretKey.secretKey + + val preSharedKey = algo.keyFrom(keyBytes).getOrThrow() + + val box = algo.sealedBoxFrom(nonce, ciphertext, authTag).getOrThrow(/*handle error*/) + box.decrypt(preSharedKey, /*also pass AAD*/ externalAAD).getOrThrow(/*handle error*/) shouldBe secret val payload = "More matter, with less art!".encodeToByteArray() @@ -339,7 +358,7 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce.size shouldBe alg.nonceTrait.length.bytes.toInt() if (iv != null) ciphertext.nonce shouldBe iv ciphertext.algorithm.authCapability.shouldBeInstanceOf>() - val decrypted = ciphertext.decrypt(key, aad?:byteArrayOf()).getOrThrow() + val decrypted = ciphertext.decrypt(key, aad ?: byteArrayOf()).getOrThrow() decrypted shouldBe plaintext @@ -353,7 +372,7 @@ class `00SymmetricTest` : FreeSpec({ ).getOrThrow() - val wrongWrongDecrypted = wrongCiphertext.decrypt(alg.randomKey(), aad?:byteArrayOf()) + val wrongWrongDecrypted = wrongCiphertext.decrypt(alg.randomKey(), aad ?: byteArrayOf()) wrongWrongDecrypted shouldNot succeed val wrongRightDecrypted = wrongCiphertext.decrypt(key) @@ -365,7 +384,7 @@ class `00SymmetricTest` : FreeSpec({ authTag = ciphertext.authTag, ).getOrThrow() - val wrongIVDecrypted = wrongIV.decrypt(key,aad?:byteArrayOf()) + val wrongIVDecrypted = wrongIV.decrypt(key, aad ?: byteArrayOf()) wrongIVDecrypted shouldNot succeed @@ -383,7 +402,7 @@ class `00SymmetricTest` : FreeSpec({ nonce = ciphertext.nonce, ciphertext.encryptedData, authTag = ciphertext.authTag.asList().shuffled().toByteArray(), - ).getOrThrow().decrypt(key, aad?:byteArrayOf()) shouldNot succeed + ).getOrThrow().decrypt(key, aad ?: byteArrayOf()) shouldNot succeed } } } @@ -520,7 +539,7 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce.size shouldBe it.nonceTrait.length.bytes.toInt() ciphertext.algorithm.authCapability.shouldBeInstanceOf>() - val decrypted = ciphertext.decrypt(key, aad?:byteArrayOf()).getOrThrow() + val decrypted = ciphertext.decrypt(key, aad ?: byteArrayOf()).getOrThrow() decrypted shouldBe plaintext val wrongDecrypted = ciphertext.decrypt(it.randomKey()) @@ -533,7 +552,7 @@ class `00SymmetricTest` : FreeSpec({ authTag = ciphertext.authTag, ).getOrThrow() - val wrongWrongDecrypted = wrongCiphertext.decrypt(it.randomKey(), aad?:byteArrayOf()) + val wrongWrongDecrypted = wrongCiphertext.decrypt(it.randomKey(), aad ?: byteArrayOf()) wrongWrongDecrypted shouldNot succeed val wrongRightDecrypted = @@ -547,13 +566,13 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.authTag, ).getOrThrow() - val wrongIVDecrypted = wrongIV.decrypt(key, aad?:byteArrayOf()) + val wrongIVDecrypted = wrongIV.decrypt(key, aad ?: byteArrayOf()) wrongIVDecrypted shouldNot succeed ciphertext.algorithm.sealedBoxFrom( nonce = ciphertext.nonce.asList().shuffled().toByteArray(), ciphertext.encryptedData, authTag = ciphertext.authTag, - ).getOrThrow().decrypt(key, aad?:byteArrayOf()) shouldNot succeed + ).getOrThrow().decrypt(key, aad ?: byteArrayOf()) shouldNot succeed ciphertext.algorithm.sealedBoxFrom( nonce = ciphertext.nonce, @@ -564,7 +583,7 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, key.encryptionKey, dedicatedMacKey = key.macKey.asList().shuffled().toByteArray() - ), aad?:byteArrayOf() + ), aad ?: byteArrayOf() ) shouldNot succeed if (aad != null) { @@ -579,7 +598,7 @@ class `00SymmetricTest` : FreeSpec({ ciphertext.nonce, ciphertext.encryptedData, ciphertext.authTag.asList().shuffled().toByteArray(), - ).getOrThrow().decrypt(key, aad?:byteArrayOf()) shouldNot succeed + ).getOrThrow().decrypt(key, aad ?: byteArrayOf()) shouldNot succeed ciphertext.algorithm.sealedBoxFrom( ciphertext.nonce, ciphertext.encryptedData, @@ -592,7 +611,7 @@ class `00SymmetricTest` : FreeSpec({ key.encryptionKey, key.macKey ) - }, aad?:byteArrayOf()) shouldNot succeed + }, aad ?: byteArrayOf()) shouldNot succeed } } From 122de00c53943e9c132e45e4fb34bd43850c2378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Thu, 20 Feb 2025 13:00:09 +0100 Subject: [PATCH 07/28] better docs --- README.md | 3 +++ docs/docs/supreme.md | 12 ++++++++++++ .../signum/supreme/symmetric/decrypt.kt | 17 ++++++++++------- .../signum/supreme/symmetric/00SymmetricTest.kt | 4 ++++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 41299d1c3..58c4ce42b 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,9 @@ Decrypting data received from external sources is also straight-forward: ```kotlin val box = algo.sealedBoxFrom(nonce, 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 diff --git a/docs/docs/supreme.md b/docs/docs/supreme.md index f2cd69c0e..288801f09 100644 --- a/docs/docs/supreme.md +++ b/docs/docs/supreme.md @@ -461,9 +461,18 @@ this space. As of 01-2025, the following algorithms are implemented: * `SymmetricEncryptionAlgorithm.AES_128.GCM` * `SymmetricEncryptionAlgorithm.AES_192.GCM` * `SymmetricEncryptionAlgorithm.AES_256.GCM` +* `SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_1` * `SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_256` +* `SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_384` +* `SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_512` +* `SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_1` * `SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_256` +* `SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_384` +* `SymmetricEncryptionAlgorithm.AES_192.CBC.HMAC.SHA_512` +* `SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_1` * `SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_256` +* `SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_384` +* `SymmetricEncryptionAlgorithm.AES_256.CBC.HMAC.SHA_512` * `SymmetricEncryptionAlgorithm.AES_128.WRAP.RFC3394` * `SymmetricEncryptionAlgorithm.AES_192.WRAP.RFC3394` * `SymmetricEncryptionAlgorithm.AES_256.WRAP.RFC3394` @@ -497,6 +506,9 @@ Decrypting data received from external sources is also straight-forward: ```kotlin val box = algo.sealedBoxFrom(nonce, 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 diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index b73c85f89..9f17e9a86 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -3,7 +3,6 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.ImplementationError -import at.asitplus.signum.indispensable.mac.HMAC import at.asitplus.signum.indispensable.mac.MAC import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated @@ -41,8 +40,6 @@ fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult } - - //required because we don't store MAC info all the way /** * Attempts to decrypt this ciphertext (which may also hold an IV/nonce) using the provided [key]. @@ -52,7 +49,7 @@ fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult * [key] and [SealedBox].** */ @JvmName("decryptAuthenticatedIntegrated") -fun SealedBox,I, KeyType.WithDedicatedMacKey>.decrypt( +fun SealedBox, I, KeyType.WithDedicatedMacKey>.decrypt( key: SymmetricKey.WithDedicatedMac<*>, authenticatedData: ByteArray = byteArrayOf() ) = catching { @@ -193,7 +190,7 @@ fun SymmetricKey, NonceTrait.Required, *>.decrypt( +fun > SymmetricKey.decrypt( nonce: ByteArray, encryptedData: ByteArray, authTag: ByteArray, @@ -206,9 +203,15 @@ fun SymmetricKey, NonceTrait.Required, *>.decryp * @return [at.asitplus.KmmResult.failure] on illegal auth tag length */ @JvmName("decryptRawAuthedNoNonce") -fun SymmetricKey, NonceTrait.Without, *>.decrypt( +fun > SymmetricKey.decrypt( encryptedData: ByteArray, authTag: ByteArray, authenticatedData: ByteArray = byteArrayOf() ): KmmResult = - algorithm.sealedBoxFrom(encryptedData, authTag).transform { it.decrypt(this, authenticatedData) } \ No newline at end of file + algorithm.sealedBoxFrom(encryptedData, authTag).transform { + it.decrypt( + @Suppress("UNCHECKED_CAST") + this as SymmetricKey, NonceTrait.Without, *>, + authenticatedData + ) + } \ No newline at end of file diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 30c0f8871..64a5bc662 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -21,6 +21,7 @@ import io.kotest.matchers.types.shouldBeInstanceOf import kotlinx.datetime.Clock import kotlin.random.Random import kotlin.random.nextUInt +import at.asitplus.signum.supreme.symmetric.decrypt @OptIn(HazardousMaterials::class) @ExperimentalStdlibApi @@ -50,6 +51,9 @@ class `00SymmetricTest` : FreeSpec({ val box = algo.sealedBoxFrom(nonce, ciphertext, authTag).getOrThrow(/*handle error*/) box.decrypt(preSharedKey, /*also pass AAD*/ externalAAD).getOrThrow(/*handle error*/) shouldBe secret + //direct decryption + preSharedKey.decrypt(nonce, ciphertext,authTag, externalAAD).getOrThrow(/*handle error*/) shouldBe secret + val payload = "More matter, with less art!".encodeToByteArray() //define algorithm parameters From 781390e35bc643163f76d026f1a99b9ccb48ce88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Thu, 20 Feb 2025 15:08:23 +0100 Subject: [PATCH 08/28] Update docs/docs/supreme.md Co-authored-by: Jakob Heher --- docs/docs/supreme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/supreme.md b/docs/docs/supreme.md index 288801f09..3e7537bcc 100644 --- a/docs/docs/supreme.md +++ b/docs/docs/supreme.md @@ -384,7 +384,7 @@ Hence, a sealed box containing an authenticated ciphertext will only ever accept Additional runtime checks ensure that mo mixups can happen. ### On Type Safety -The API tries to be as type-safe as possible, e.g., it is impossible to specify a dedicated MAC key, or dedicated MAC function for AES-GCM, +The API tries to be as type-safe as possible, e.g., it is impossible to specify a dedicated MAC key (or function) for AES-GCM, and non-authenticated AES-CBC does not even support passing additional authenticated data to the encryption process. The same constraints apply to the resulting ciphertexts, making it much harder to accidentally confuse an authenticated encryption algorithm with a non-authenticated one. From 3be2f602ca451e5cb40d36debeefb721ed406e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Thu, 20 Feb 2025 16:25:14 +0100 Subject: [PATCH 09/28] Update indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt Co-authored-by: Jakob Heher --- .../indispensable/symmetric/SymmetricEncryptionAlgorithm.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt index 2ba356695..fe3955e7b 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt @@ -91,7 +91,7 @@ sealed interface SymmetricEncryptionAlgorithm, out } } - /**Humanly-readable name**/ + /**Human-readable name**/ val name: String /** From 503206447bbc1d230662a6fb3d786ea4db758efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Thu, 20 Feb 2025 16:35:27 +0100 Subject: [PATCH 10/28] address more PR feedback --- docs/docs/indispensable.md | 2 +- docs/docs/supreme.md | 2 +- .../signum/indispensable/misc/BitLength.kt | 2 +- .../indispensable/symmetric/SealedBox.kt | 11 ++--- .../symmetric/SymmetricEncryptionAlgorithm.kt | 40 +++++++++++++------ .../indispensable/symmetric/SymmetricKey.kt | 11 +---- .../signum/supreme/symmetric/AES.jca.kt | 2 +- 7 files changed, 37 insertions(+), 33 deletions(-) diff --git a/docs/docs/indispensable.md b/docs/docs/indispensable.md index 4376b599b..f686a7060 100644 --- a/docs/docs/indispensable.md +++ b/docs/docs/indispensable.md @@ -77,7 +77,7 @@ which is implemented by `CryptoPublicKey.EC` * `HMAC` defines HMAC for all supported `Digest` algorithms. The [Supreme](supreme.md) KMP crypto provider implements the actual HMAC functionality. * `SymmetricEncryptionAlgorithm` represents symmetric encryption algorithms. _Indispensable_ currently ships with definitions for AES-CBC, a flexible AES-CBC-HMAC, and AES-GCM, while the [Supreme](supreme.md) KMP crypto provider implements the actual AES functionality. * `BlockCipher` denotes a BlockCipher - * `WithIV` denotes a Cipher requiring or supporting an initialization vector + * `WithIV` denotes a Cipher requiring an initialization vector * `Unauthenticated` denotes a non-authenticated encryption algorithm * `Authenticated` denotes an authenticated encryption algorithm * `Authenticated.WithDedicatedMac` describes an encryption authenticated encryption algorithm based on a non-authenticated one and a dedicated `MAC`, to achieve authenticated encryption diff --git a/docs/docs/supreme.md b/docs/docs/supreme.md index 3e7537bcc..e79a53f30 100644 --- a/docs/docs/supreme.md +++ b/docs/docs/supreme.md @@ -436,7 +436,7 @@ All contracts can be combined, meaning it is possible to steadily narrow down th In addition, there's `isIntegrated()`, which is only defined for objects having the `Authenticated.Integrated` characteristic: * if `true`, smart-casts the object's - * AuthCapability to `SymmetricEncryptionalgorithm` * KeyType to `KeyType.Integrated` * if `false`, smart-casts the object's * KeyType to `KeyType.WithDedicatedMac` diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/BitLength.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/BitLength.kt index 79c494215..2f204dc6c 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/BitLength.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/BitLength.kt @@ -30,7 +30,7 @@ data class BitLength(val bits: UInt) : Comparable { @Suppress("NOTHING_TO_INLINE", "OVERRIDE_BY_INLINE") override inline fun compareTo(other: BitLength): Int = bits.compareTo(other.bits) - operator fun plus(other: BitLength) = BitLength(bits + other.bits) + operator fun plus(other: BitLength) = BitLength(bits + other.bits) } @Suppress("NOTHING_TO_INLINE") diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt index 725ce6683..462c175f8 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBox.kt @@ -3,13 +3,13 @@ package at.asitplus.signum.indispensable.symmetric import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract -val SealedBox, I, *>.authTag +val SealedBox, I, *>.authTag get() = (this as SealedBox.Authenticated<*, *>).authTag val SealedBox<*, NonceTrait.Required, *>.nonce get() = (this as SealedBox.WithNonce<*, *>).nonce /** - * Represents symmetrically encrypted data. This is a separate class to more easily enforce type safety wrt. presence of + * Represents symmetrically encrypted data in a structured manner. * Construct using [SymmetricEncryptionAlgorithm.sealedBoxFrom] */ sealed interface SealedBox, I : NonceTrait, K : KeyType> { @@ -42,7 +42,6 @@ sealed interface SealedBox, I : NonceTrait, K : KeyTyp if (ciphertext != other.ciphertext) return false if (algorithm != other.algorithm) return false - if (!encryptedData.contentEquals(other.encryptedData)) return false return true } @@ -50,7 +49,6 @@ sealed interface SealedBox, I : NonceTrait, K : KeyTyp override fun hashCode(): Int { var result = ciphertext.hashCode() result = 31 * result + algorithm.hashCode() - result = 31 * result + encryptedData.contentHashCode() return result } @@ -71,12 +69,11 @@ sealed interface SealedBox, I : NonceTrait, K : KeyTyp * A sealed box consisting of an [nonce] and the actual [ciphertext]. * Construct using [SymmetricEncryptionAlgorithm.sealedBoxFrom] */ - sealed class WithNonce, K : KeyType>( + sealed class WithNonce, K : KeyType>( val nonce: ByteArray, private val ciphertext: Ciphertext, K> ) : SealedBox { - override val algorithm: SymmetricEncryptionAlgorithm = ciphertext.algorithm override val encryptedData: ByteArray = ciphertext.encryptedData @@ -88,7 +85,6 @@ sealed interface SealedBox, I : NonceTrait, K : KeyTyp if (!nonce.contentEquals(other.nonce)) return false if (ciphertext != other.ciphertext) return false if (algorithm != other.algorithm) return false - if (!encryptedData.contentEquals(other.encryptedData)) return false return true } @@ -97,7 +93,6 @@ sealed interface SealedBox, I : NonceTrait, K : KeyTyp var result = nonce.contentHashCode() result = 31 * result + ciphertext.hashCode() result = 31 * result + algorithm.hashCode() - result = 31 * result + encryptedData.contentHashCode() return result } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt index fe3955e7b..789432fbc 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt @@ -43,12 +43,25 @@ sealed interface SymmetricEncryptionAlgorithm, out */ class AESDefinition(val keySize: BitLength) { + /** + * AES in Galois Counter Mode + */ val GCM = AES.GCM(keySize) + + /** + * AES in Cipher Block Chaining Mode + */ val CBC = CbcDefinition(keySize) + /** + * AES in Electronic Codebook Mode. You almost certainly don't want to use this + */ @HazardousMaterials("ECB is almost always insecure!") val ECB = AES.ECB(keySize) + /** + * AES Key Wrapping as per [RFC 3394](https://www.rfc-editor.org/rfc/rfc3394) + */ val WRAP = WrapDefinition(keySize) class WrapDefinition(keySize: BitLength) { @@ -56,6 +69,10 @@ sealed interface SymmetricEncryptionAlgorithm, out } class CbcDefinition(keySize: BitLength) { + /** + * Plain, Unauthenticated AES in Cipher Block Chaining mode. + * You almost certainly don't want to use this as is, but rather some [HMAC]-authenticated variant + */ @HazardousMaterials("Unauthenticated!") val PLAIN = AES.CBC.Unauthenticated(keySize) @@ -248,7 +265,7 @@ sealed interface SymmetricEncryptionAlgorithm, out fun Custom( tagLength: BitLength, dedicatedMacInputCalculation: DedicatedMacInputCalculation - ) = Custom (tagLength,DefaultDedicatedMacAuthTagTransformation, dedicatedMacInputCalculation) + ) = Custom(tagLength, DefaultDedicatedMacAuthTagTransformation, dedicatedMacInputCalculation) /** * Instantiates a new [CBC.HMAC] instance with @@ -260,14 +277,13 @@ sealed interface SymmetricEncryptionAlgorithm, out tagLength: BitLength, dedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation, dedicatedMacInputCalculation: DedicatedMacInputCalculation - ) = - CBC.HMAC( - authCapability.innerCipher as Unauthenticated, - authCapability.mac, - dedicatedMacInputCalculation, - dedicatedMacAuthTagTransformation, - tagLength - ) + ) = CBC.HMAC( + authCapability.innerCipher as Unauthenticated, + authCapability.mac, + dedicatedMacInputCalculation, + dedicatedMacAuthTagTransformation, + tagLength + ) } } } @@ -414,14 +430,14 @@ typealias DedicatedMacInputCalculation = MAC.(ciphertext: ByteArray, nonce: Byte */ val DefaultDedicatedMacInputCalculation: DedicatedMacInputCalculation = fun MAC.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = - aad + iv + ciphertext + (aad.size.toLong()*8L).encodeTo8Bytes() + aad + iv + ciphertext + (aad.size.toLong() * 8L).encodeTo8Bytes() /** * Marker, indicating whether a symmetric encryption algorithms requires or prohibits the use of a nonce/IV */ sealed interface NonceTrait { /** - * Indicates that a cipher requires an initialization vector + * Indicates that a cipher requires a nonce/IV */ class Required(val length: BitLength) : NonceTrait @@ -439,7 +455,7 @@ abstract class BlockCipher, I : NonceTrait, K : KeyType>( enum class ModeOfOperation(val friendlyName: String, val acronym: String) { GCM("Galois Counter Mode", "GCM"), - CBC("Cipherblock Chaining Mode", "CBC"), + CBC("Cipher Block Chaining Mode", "CBC"), ECB("Electronic Codebook Mode", "ECB"), } } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index f176cf7e0..841c50909 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -34,9 +34,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key * Self-Contained encryption key, i.e. a single byte array is sufficient */ sealed class Integrated, I : NonceTrait> - @HazardousMaterials("Does not check whether key size matched algorithm! Useful for testing, but not production!") - - constructor( + protected constructor( override val algorithm: SymmetricEncryptionAlgorithm, /** * The actual encryption key bytes @@ -107,12 +105,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key * bolt on AEAD capabilities, such as [SymmetricEncryptionAlgorithm.AES.GCM] */ sealed class WithDedicatedMac - @HazardousMaterials("Does not check whether key size matched algorithm! Useful for testing, but not production!") - /** - * Do not invoke directly! use Supreme's `SymmetricEncryptionAlgorithm.randomKey()` and `SymmetricEncryptionAlgorithm.encryptionKeyFrom(bytes)` - * This constructor does not check for matching key sizes to allow for testing error cases! - */ - constructor( + protected constructor( override val algorithm: SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>, /** * The actual encryption key bytes diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt index 5454c4797..4aad5e533 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt @@ -25,7 +25,7 @@ internal object AESJCA { SecretKeySpec(key, algorithm.jcaKeySpec), GCMParameterSpec(cipher.tagLength.bits.toInt(), nonce) ) - else if (algorithm is SymmetricEncryptionAlgorithm.AES.CBC<*, *>) //covers Plain and CBC, because CBC will delegate to here + else if (algorithm is SymmetricEncryptionAlgorithm.AES.CBC<*, *>) //covers unauthenticated and CBC-HMAC, because CBC will always delegate to here init( Cipher.ENCRYPT_MODE, SecretKeySpec(key, algorithm.jcaKeySpec), From 73e1da423ed8585b9ea47a4cd0a3f7fb5ec12f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Thu, 20 Feb 2025 20:40:56 +0100 Subject: [PATCH 11/28] fixes --- docs/docs/supreme.md | 11 +++--- .../signum/indispensable/josef/JsonWebKey.kt | 14 ++------ .../indispensable/symmetric/SymmetricKey.kt | 36 ++++++++++--------- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/docs/docs/supreme.md b/docs/docs/supreme.md index e79a53f30..1f4163916 100644 --- a/docs/docs/supreme.md +++ b/docs/docs/supreme.md @@ -363,9 +363,6 @@ For a list of supported algorithms, check out the [feature matrix](features.md#s ## Symmetric Encryption -!!! warning inline end - **NEVER** re-use an IV! Let the Supreme KMP crypto provider auto-generate them! - Symmetric encryption is implemented both flexible and type-safe. At the same time, the public interface is also rather lean: * Reference an algorithm such as `SymmetricEncryptionAlgorithm.ChaCha20Poly1305`. @@ -381,7 +378,7 @@ Simply call `decrypt(key)` on a `SealedBox` to recover the plaintext. To minimise the potential for error, everything (algorithms, keys, sealed boxes) makes heavy use of generics. Hence, a sealed box containing an authenticated ciphertext will only ever accept a symmetric key that is usable for AEAD. -Additional runtime checks ensure that mo mixups can happen. +Additional runtime checks ensure that no mixups can happen. ### On Type Safety The API tries to be as type-safe as possible, e.g., it is impossible to specify a dedicated MAC key (or function) for AES-GCM, @@ -408,6 +405,10 @@ These are: * `Integrated`: The key consists of a single byte array, from which encryption key and (if required by the algorithm) a mac key is derived. * `WithDedicatedMac`: The key consists of an encryption key and a dedicated MAC key to compute the auth tag. + +!!! warning inline end + **NEVER** re-use an IV! Let the Supreme KMP crypto provider auto-generate them! + In addition to runtime checks for matching algorithms and parameters, algorithms, keys and sealed boxes need matching characteristics to be used with each other. This approach does come with one caveat: It forces you to know what you are dealing with. @@ -496,7 +497,7 @@ encrypted.decrypt(secretKey, authenticatedData).getOrThrow(/*handle error*/) sho Encrypted data is always structured and the individual components are easily accessible: ```kotlin - val nonce = encrypted.nonce +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*/ diff --git a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JsonWebKey.kt b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JsonWebKey.kt index 9c6bbdbcd..dadcd9346 100644 --- a/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JsonWebKey.kt +++ b/indispensable-josef/src/commonMain/kotlin/at/asitplus/signum/indispensable/josef/JsonWebKey.kt @@ -311,12 +311,7 @@ data class JsonWebKey( ).apply { jwkId = keyId } } - JwkType.SYM -> { - require(k!=null){"Missing symmetric key k"} - algorithm - TODO() - } - null -> throw IllegalArgumentException("Illegal key type") + else -> throw IllegalArgumentException("Illegal key type") } } @@ -374,12 +369,7 @@ fun CryptoPublicKey.toJsonWebKey(keyId: String? = this.jwkId): JsonWebKey = e = e.magnitude ) } -/** - * Converts a [at.asitplus.signum.indispensable.symmetric.SymmetricKey] to a [JsonWebKey] - */ -fun SymmetricKey<*,*,*>.toJsonWebKey(keyId: String? = this.jwkId): JsonWebKey? { - TODO("Define algorithms an map where possible") -} + private const val JWK_ID = "jwkIdentifier" diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index 841c50909..c4f24eba6 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -1,9 +1,7 @@ package at.asitplus.signum.indispensable.symmetric -import at.asitplus.signum.HazardousMaterials import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract -import kotlin.jvm.JvmName sealed interface KeyType { object Integrated : KeyType @@ -36,6 +34,8 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key sealed class Integrated, I : NonceTrait> protected constructor( override val algorithm: SymmetricEncryptionAlgorithm, + + override val additionalProperties: MutableMap = mutableMapOf(), /** * The actual encryption key bytes */ @@ -44,42 +44,45 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key SymmetricKey { sealed class Authenticating( algorithm: SymmetricEncryptionAlgorithm, + additionalProperties: MutableMap = mutableMapOf(), secretKey: ByteArray - ) : Integrated(algorithm, secretKey), + ) : Integrated(algorithm, additionalProperties, secretKey), SymmetricKey.Authenticating { class RequiringNonce( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, - override val additionalProperties: MutableMap = mutableMapOf() + additionalProperties: MutableMap = mutableMapOf() ) : Authenticating( - algorithm, secretKey + algorithm, additionalProperties, secretKey ), SymmetricKey.RequiringNonce class WithoutNonce( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, - override val additionalProperties: MutableMap = mutableMapOf() + additionalProperties: MutableMap = mutableMapOf() ) : Authenticating( - algorithm, secretKey + algorithm, additionalProperties, secretKey ), SymmetricKey.WithoutNonce } sealed class NonAuthenticating( algorithm: SymmetricEncryptionAlgorithm, + additionalProperties: MutableMap = mutableMapOf(), secretKey: ByteArray - ) : Integrated(algorithm, secretKey), SymmetricKey.NonAuthenticating { + ) : Integrated(algorithm, additionalProperties, secretKey), + SymmetricKey.NonAuthenticating { class RequiringNonce( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, - override val additionalProperties: MutableMap = mutableMapOf() - ) : NonAuthenticating(algorithm, secretKey) + additionalProperties: MutableMap = mutableMapOf() + ) : NonAuthenticating(algorithm, additionalProperties, secretKey) class WithoutNonce( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, - override val additionalProperties: MutableMap = mutableMapOf() - ) : NonAuthenticating(algorithm, secretKey) + additionalProperties: MutableMap = mutableMapOf() + ) : NonAuthenticating(algorithm, additionalProperties, secretKey) } override fun equals(other: Any?): Boolean { @@ -107,6 +110,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key sealed class WithDedicatedMac protected constructor( override val algorithm: SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>, + override val additionalProperties: MutableMap = mutableMapOf() /** * The actual encryption key bytes */ @@ -121,9 +125,9 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, secretKey: ByteArray, dedicatedMacKey: ByteArray, - override val additionalProperties: MutableMap = mutableMapOf() + additionalProperties: MutableMap = mutableMapOf() ) : WithDedicatedMac( - algorithm, secretKey, dedicatedMacKey + algorithm, additionalProperties, secretKey, dedicatedMacKey ), SymmetricKey.RequiringNonce, KeyType.WithDedicatedMacKey> @@ -131,9 +135,9 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>, secretKey: ByteArray, dedicatedMacKey: ByteArray, - override val additionalProperties: MutableMap = mutableMapOf() + additionalProperties: MutableMap = mutableMapOf() ) : WithDedicatedMac( - algorithm, secretKey, dedicatedMacKey + algorithm, additionalProperties, secretKey, dedicatedMacKey ), SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> From cb784e9002de179bc270e0f0784448b77c4cfd69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Thu, 20 Feb 2025 23:47:05 +0100 Subject: [PATCH 12/28] fix missing comma --- .../at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index c4f24eba6..019eab05d 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -110,7 +110,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key sealed class WithDedicatedMac protected constructor( override val algorithm: SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>, - override val additionalProperties: MutableMap = mutableMapOf() + override val additionalProperties: MutableMap = mutableMapOf(), /** * The actual encryption key bytes */ From 295ce2ac6194fe82803a83491d198888602477b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Fri, 21 Feb 2025 00:04:11 +0100 Subject: [PATCH 13/28] rename CipherParam to PlatformCipher --- .../at/asitplus/signum/supreme/symmetric/AES.jca.kt | 2 +- .../at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt | 4 ++-- .../asitplus/signum/supreme/symmetric/Encryptor.jca.kt | 10 +++++----- .../at/asitplus/signum/supreme/symmetric/Encryptor.kt | 8 ++++---- .../asitplus/signum/supreme/symmetric/Encryptor.ios.kt | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt index 4aad5e533..e3634a3fe 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt @@ -42,7 +42,7 @@ internal object AESJCA { aad?.let { if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) updateAAD(it) /*CBC-HMAC we do ourselves*/ } }.let { @Suppress("UNCHECKED_CAST") - CipherParam, KeyType>( + PlatformCipher, KeyType>( algorithm as SymmetricEncryptionAlgorithm,*, KeyType>, it, nonce, diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt index be504e75d..0a0c2f5e1 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt @@ -8,7 +8,7 @@ import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec internal object ChaChaJVM { - fun initCipher(key: ByteArray, nonce: ByteArray, aad: ByteArray?): CipherParam = + fun initCipher(key: ByteArray, nonce: ByteArray, aad: ByteArray?): PlatformCipher = Cipher.getInstance(SymmetricEncryptionAlgorithm.ChaCha20Poly1305.jcaName).apply { init( Cipher.ENCRYPT_MODE, @@ -16,5 +16,5 @@ internal object ChaChaJVM { IvParameterSpec(nonce) ) aad?.let { updateAAD(it) } - }.let { CipherParam(SymmetricEncryptionAlgorithm.ChaCha20Poly1305, it, nonce, aad) } + }.let { PlatformCipher(SymmetricEncryptionAlgorithm.ChaCha20Poly1305, it, nonce, aad) } } \ No newline at end of file diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt index 0606c094e..c39372ab3 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt @@ -13,7 +13,7 @@ internal actual fun , I : NonceTrait, K : KeyType> key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): CipherParam = when { +): PlatformCipher = when { algorithm.requiresNonce() -> { @OptIn(HazardousMaterials::class) val nonce = nonce ?: algorithm.randomNonce() @@ -22,7 +22,7 @@ internal actual fun , I : NonceTrait, K : KeyType> when (algorithm) { is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaJVM.initCipher(key, nonce, aad) is SymmetricEncryptionAlgorithm.AES<*, *, *> -> AESJCA.initCipher(algorithm, key, nonce, aad) - } as CipherParam + } as PlatformCipher } else -> { @@ -30,12 +30,12 @@ internal actual fun , I : NonceTrait, K : KeyType> if ((algorithm !is SymmetricEncryptionAlgorithm.AES.ECB) && (algorithm !is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) TODO("$algorithm is UNSUPPORTED") - AESJCA.initCipher(algorithm, key, nonce, aad) as CipherParam + AESJCA.initCipher(algorithm, key, nonce, aad) as PlatformCipher } } -internal actual fun , I : NonceTrait, K : KeyType> CipherParam<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { - @Suppress("UNCHECKED_CAST") (this as CipherParam) +internal actual fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { + @Suppress("UNCHECKED_CAST") (this as PlatformCipher) val jcaCiphertext = platformData.doFinal(data) //JCA simply concatenates ciphertext and authtag, so we need to split diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt index f30db1791..4ec14bf37 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt @@ -21,7 +21,7 @@ internal class Encryptor, I : NonceTrait, out K : KeyT require(key.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } } - private val platformCipher: CipherParam<*, A, out K> = initCipher(algorithm, key, iv, aad) + private val platformCipher: PlatformCipher<*, A, out K> = initCipher(algorithm, key, iv, aad) /** * Encrypts [data] and returns a [at.asitplus.signum.indispensable.symmetric.Ciphertext] matching the algorithm type that was used to create this [Encryptor] object. @@ -70,7 +70,7 @@ internal class Encryptor, I : NonceTrait, out K : KeyT } else platformCipher.doEncrypt(data)) } -internal class CipherParam, K : KeyType>( +internal class PlatformCipher, K : KeyType>( val alg: SymmetricEncryptionAlgorithm, val platformData: T, val nonce: ByteArray?, @@ -92,6 +92,6 @@ internal expect fun , I : NonceTrait, K : KeyType> key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): CipherParam +): PlatformCipher -internal expect fun , I : NonceTrait, K : KeyType> CipherParam<*, A, out K>.doEncrypt(data: ByteArray): SealedBox +internal expect fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt index 7fbf1986b..1c8d6a8d6 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt @@ -13,20 +13,20 @@ internal actual fun , I : NonceTrait, K : KeyType> key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): CipherParam { +): PlatformCipher { @OptIn(HazardousMaterials::class) val nonce = if (algorithm.requiresNonce()) nonce ?: algorithm.randomNonce() else null @Suppress("UNCHECKED_CAST") - return CipherParam, KeyType>( + return PlatformCipher, KeyType>( algorithm, key, nonce, aad - ) as CipherParam + ) as PlatformCipher } @OptIn(ExperimentalForeignApi::class) -internal actual fun , I : NonceTrait, K : KeyType> CipherParam<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { - @Suppress("UNCHECKED_CAST") (this as CipherParam) +internal actual fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { + @Suppress("UNCHECKED_CAST") (this as PlatformCipher) @Suppress("UNCHECKED_CAST") return when (alg) { From 95ef085419bd56e9af11a38ba00ada2e08bb2aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Fri, 21 Feb 2025 08:31:32 +0100 Subject: [PATCH 14/28] mv MAC -> MessageAuthenticationCode --- .github/workflows/test-jvm.yml | 2 +- .../asitplus/signum/indispensable/mac/MAC.kt | 40 ------------ .../mac/MessageAuthenticationCode.kt | 61 +++++++++++++++++++ .../symmetric/SymmetricEncryptionAlgorithm.kt | 10 +-- .../indispensable/EnumConsistencyTests.kt | 4 +- .../at/asitplus/signum/supreme/mac/MAC.kt | 8 +-- .../signum/supreme/symmetric/decrypt.kt | 4 +- .../supreme/symmetric/00SymmetricTest.kt | 4 +- 8 files changed, 77 insertions(+), 56 deletions(-) delete mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt diff --git a/.github/workflows/test-jvm.yml b/.github/workflows/test-jvm.yml index 8bbf76206..675ea170a 100644 --- a/.github/workflows/test-jvm.yml +++ b/.github/workflows/test-jvm.yml @@ -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 diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt deleted file mode 100644 index 6c7810b84..000000000 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MAC.kt +++ /dev/null @@ -1,40 +0,0 @@ -package at.asitplus.signum.indispensable.mac - -import at.asitplus.signum.indispensable.Digest -import at.asitplus.signum.indispensable.asn1.Identifiable -import at.asitplus.signum.indispensable.asn1.KnownOIDs -import at.asitplus.signum.indispensable.asn1.ObjectIdentifier -import at.asitplus.signum.indispensable.misc.BitLength - -sealed interface MAC { - /** output size of MAC */ - val outputLength: BitLength - - companion object { - val entries: Iterable = HMAC.entries - } -} - -/** - * RFC 2104 HMAC - */ -enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MAC, Identifiable { - SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), - SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), - SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), - SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), - ; - - override fun toString() = "HMAC-$digest" - - companion object { - operator fun invoke(digest: Digest) = when (digest) { - Digest.SHA1 -> SHA1 - Digest.SHA256 -> SHA256 - Digest.SHA384 -> SHA384 - Digest.SHA512 -> SHA512 - } - } - - override val outputLength: BitLength get() = digest.outputLength -} diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt new file mode 100644 index 000000000..488f30bb4 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt @@ -0,0 +1,61 @@ +package at.asitplus.signum.indispensable.mac + +import at.asitplus.signum.indispensable.DataIntegrityAlgorithm +import at.asitplus.signum.indispensable.Digest +import at.asitplus.signum.indispensable.asn1.* +import at.asitplus.signum.indispensable.asn1.encoding.Asn1 +import at.asitplus.signum.indispensable.asn1.encoding.Asn1.Null +import at.asitplus.signum.indispensable.asn1.encoding.readNull +import at.asitplus.signum.indispensable.misc.BitLength + +sealed interface MessageAuthenticationCode { + /** output size of MAC */ + val outputLength: BitLength + + companion object { + val entries: Iterable = HMAC.entries + } +} + +/** + * RFC 2104 HMAC + */ +enum class HMAC(val digest: Digest, override val oid: ObjectIdentifier) : MessageAuthenticationCode, Identifiable, + Asn1Encodable { + SHA1(Digest.SHA1, KnownOIDs.hmacWithSHA1), + SHA256(Digest.SHA256, KnownOIDs.hmacWithSHA256), + SHA384(Digest.SHA384, KnownOIDs.hmacWithSHA384), + SHA512(Digest.SHA512, KnownOIDs.hmacWithSHA512), + ; + + override fun toString() = "HMAC-$digest" + + override fun encodeToTlv(): Asn1Sequence = Asn1.Sequence { + +oid + +Null() + } + + + companion object : Asn1Decodable { + + fun byOID(oid: ObjectIdentifier): HMAC? = entries.find { it.oid == oid } + + fun byDigest(digest: Digest): HMAC = entries.find { it.digest == digest }!! + + operator fun invoke(digest: Digest) = when (digest) { + Digest.SHA1 -> SHA1 + Digest.SHA256 -> SHA256 + Digest.SHA384 -> SHA384 + Digest.SHA512 -> SHA512 + } + + override fun doDecode(src: Asn1Sequence): HMAC { + val oid = src.nextChild().asPrimitive().readOid() + src.nextChild().asPrimitive().readNull() + require(!src.hasMoreChildren()) { "Superfluous ANS.1 data in HMAC" } + return byOID(oid) ?: throw Asn1OidException("Unknown OID", oid) + } + } + + override val outputLength: BitLength get() = digest.outputLength +} \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt index 789432fbc..621b02d35 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricEncryptionAlgorithm.kt @@ -7,7 +7,7 @@ import at.asitplus.signum.indispensable.asn1.KnownOIDs import at.asitplus.signum.indispensable.asn1.ObjectIdentifier import at.asitplus.signum.indispensable.asn1.encoding.encodeTo8Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.misc.BitLength import at.asitplus.signum.indispensable.misc.bit import kotlin.contracts.ExperimentalContracts @@ -127,7 +127,7 @@ sealed interface SymmetricEncryptionAlgorithm, out interface Integrated : Authenticated - interface WithDedicatedMac : + interface WithDedicatedMac : Authenticated, I, KeyType.WithDedicatedMacKey> } @@ -327,7 +327,7 @@ sealed interface AuthCapability { * An authenticated cipher construction based on an unauthenticated cipher with a dedicated MAC function, requiring a dedicated MAC key. * _Encrypt-then-MAC_ */ - class WithDedicatedMac( + class WithDedicatedMac( /** * The inner unauthenticated cipher */ @@ -422,14 +422,14 @@ val DefaultDedicatedMacAuthTagTransformation: DedicatedMacAuthTagTransformation /** * Typealias defining the signature of the lambda for defining a custom MAC input calculation scheme. */ -typealias DedicatedMacInputCalculation = MAC.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray +typealias DedicatedMacInputCalculation = MessageAuthenticationCode.(ciphertext: ByteArray, nonce: ByteArray, aad: ByteArray) -> ByteArray /** * The default dedicated mac input calculation as per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1), authenticating all inputs: * `AAD || IV || Ciphertext || AAD Length`, where AAD_length is a 64 bit big-endian representation of the aad length in bits */ val DefaultDedicatedMacInputCalculation: DedicatedMacInputCalculation = - fun MAC.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = + fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray, aad: ByteArray): ByteArray = aad + iv + ciphertext + (aad.size.toLong() * 8L).encodeTo8Bytes() /** diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt index fb0453414..4aa6e241e 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/EnumConsistencyTests.kt @@ -1,6 +1,6 @@ package at.asitplus.signum.indispensable -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -40,5 +40,5 @@ inline fun FreeSpec.enumConsistencyTest() { } class EnumConsistencyTests : FreeSpec({ - enumConsistencyTest() + enumConsistencyTest() }) \ No newline at end of file diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt index bb733132c..6dd8b9117 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/mac/MAC.kt @@ -3,19 +3,19 @@ package at.asitplus.signum.supreme.mac import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.internals.xor import at.asitplus.signum.supreme.hash.digest -fun MAC.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) -fun MAC.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) +fun MessageAuthenticationCode.mac(key: ByteArray, msg: ByteArray) = mac(key, sequenceOf(msg)) +fun MessageAuthenticationCode.mac(key: ByteArray, msg: Iterable) = mac(key, msg.asSequence()) private val HMAC.blockLength get() = digest.inputBlockSize.bytes.toInt() private val HMAC.innerPad get() = ByteArray(blockLength) { 0x36 } private val HMAC.outerPad get() = ByteArray(blockLength) { 0x5C } -fun MAC.mac(key: ByteArray, msg: Sequence): KmmResult = catching { +fun MessageAuthenticationCode.mac(key: ByteArray, msg: Sequence): KmmResult = catching { when (this@mac) { is HMAC -> hmac(key, msg) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index 9f17e9a86..023839260 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -3,7 +3,7 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.ImplementationError -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES @@ -49,7 +49,7 @@ fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult * [key] and [SealedBox].** */ @JvmName("decryptAuthenticatedIntegrated") -fun SealedBox, I, KeyType.WithDedicatedMacKey>.decrypt( +fun SealedBox, I, KeyType.WithDedicatedMacKey>.decrypt( key: SymmetricKey.WithDedicatedMac<*>, authenticatedData: ByteArray = byteArrayOf() ) = catching { diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 64a5bc662..d63b51ea4 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -1,7 +1,7 @@ import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.asn1.encoding.encodeTo4Bytes import at.asitplus.signum.indispensable.mac.HMAC -import at.asitplus.signum.indispensable.mac.MAC +import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.misc.bit import at.asitplus.signum.indispensable.misc.bytes import at.asitplus.signum.indispensable.symmetric.* @@ -417,7 +417,7 @@ class `00SymmetricTest` : FreeSpec({ withData( nameFn = { it.first }, "Default" to DefaultDedicatedMacInputCalculation, - "Oklahoma MAC" to fun MAC.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = + "Oklahoma MAC" to fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = "Oklahoma".encodeToByteArray() + (iv ?: byteArrayOf()) + (aad ?: byteArrayOf()) + ciphertext) { (_, macInputFun) -> withData( From cf4165778ae53ac6d9e69c8aadac06b0b992590f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Mon, 24 Feb 2025 06:53:13 +0100 Subject: [PATCH 15/28] tmp refactor platform code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt index 488f30bb..cc91a84c 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt @@ -1,6 +1,5 @@ package at.asitplus.signum.indispensable.mac -import at.asitplus.signum.indispensable.DataIntegrityAlgorithm import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.asn1.encoding.Asn1 diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt index e3634a3f..7761b116 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt @@ -2,7 +2,6 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.symmetric.AuthCapability -import at.asitplus.signum.indispensable.symmetric.KeyType import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm import javax.crypto.Cipher import javax.crypto.spec.GCMParameterSpec @@ -12,41 +11,31 @@ import javax.crypto.spec.SecretKeySpec @OptIn(HazardousMaterials::class) internal object AESJCA { fun initCipher( + mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm.AES<*, *, *>, key: ByteArray, nonce: ByteArray?, aad: ByteArray? - ) = - Cipher.getInstance(algorithm.jcaName).apply { - val cipher = algorithm.authCapability - if (cipher is AuthCapability.Authenticated.Integrated) - init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpec), - GCMParameterSpec(cipher.tagLength.bits.toInt(), nonce) - ) - else if (algorithm is SymmetricEncryptionAlgorithm.AES.CBC<*, *>) //covers unauthenticated and CBC-HMAC, because CBC will always delegate to here - init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpec), - IvParameterSpec(nonce) - ) - else if ((algorithm is SymmetricEncryptionAlgorithm.AES.ECB) || algorithm is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394) { - init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpec), - ) - } - - else TODO("Algorithm $algorithm is unsupported ") - aad?.let { if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) updateAAD(it) /*CBC-HMAC we do ourselves*/ } - }.let { - @Suppress("UNCHECKED_CAST") - PlatformCipher, KeyType>( - algorithm as SymmetricEncryptionAlgorithm,*, KeyType>, - it, - nonce, - aad + ) = Cipher.getInstance(algorithm.jcaName).apply { + val cipher = algorithm.authCapability + if (cipher is AuthCapability.Authenticated.Integrated) + init( + mode.jcaCipherMode, + SecretKeySpec(key, algorithm.jcaKeySpec), + GCMParameterSpec(cipher.tagLength.bits.toInt(), nonce) ) - } + else if (algorithm is SymmetricEncryptionAlgorithm.AES.CBC<*, *>) //covers unauthenticated and CBC-HMAC, because CBC will always delegate to here + init( + mode.jcaCipherMode, + SecretKeySpec(key, algorithm.jcaKeySpec), + IvParameterSpec(nonce) + ) + else if ((algorithm is SymmetricEncryptionAlgorithm.AES.ECB) || algorithm is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394) { + init( + mode.jcaCipherMode, + SecretKeySpec(key, algorithm.jcaKeySpec), + ) + } else TODO("Algorithm $algorithm is unsupported ") + aad?.let { if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) updateAAD(it) /*CBC-HMAC we do ourselves*/ } + } } diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt index 0a0c2f5e..20376fb1 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt @@ -1,20 +1,18 @@ package at.asitplus.signum.supreme.symmetric -import at.asitplus.signum.indispensable.symmetric.AuthCapability -import at.asitplus.signum.indispensable.symmetric.KeyType import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec internal object ChaChaJVM { - fun initCipher(key: ByteArray, nonce: ByteArray, aad: ByteArray?): PlatformCipher = + fun initCipher(mode: PlatformCipher.Mode, key: ByteArray, nonce: ByteArray, aad: ByteArray?): Cipher = Cipher.getInstance(SymmetricEncryptionAlgorithm.ChaCha20Poly1305.jcaName).apply { init( - Cipher.ENCRYPT_MODE, + mode.jcaCipherMode, SecretKeySpec(key, SymmetricEncryptionAlgorithm.ChaCha20Poly1305.jcaKeySpec), IvParameterSpec(nonce) ) - aad?.let { updateAAD(it) } - }.let { PlatformCipher(SymmetricEncryptionAlgorithm.ChaCha20Poly1305, it, nonce, aad) } + aad?.let { updateAAD(it) } + } } \ No newline at end of file diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt index c39372ab..31f1bcdf 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt @@ -2,75 +2,89 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.symmetric.* -import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated import javax.crypto.Cipher -import javax.crypto.spec.GCMParameterSpec -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec -internal actual fun , I : NonceTrait, K : KeyType> initCipher( +internal actual suspend fun , I : NonceTrait, K : KeyType> initCipher( + mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm, key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): PlatformCipher = when { - algorithm.requiresNonce() -> { - @OptIn(HazardousMaterials::class) - val nonce = nonce ?: algorithm.randomNonce() +): PlatformCipher = JcaPlatformCipher(mode, algorithm, key, nonce, aad) + +internal class JcaPlatformCipher, I : NonceTrait, K : KeyType>( + override val mode: PlatformCipher.Mode, + override val algorithm: SymmetricEncryptionAlgorithm, + override val key: ByteArray, + override val nonce: ByteArray?, + override val aad: ByteArray?, +) : PlatformCipher { + + + internal val cipher: Cipher = + when { + algorithm.requiresNonce() -> { + + + @Suppress("UNCHECKED_CAST") + when (algorithm) { + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaJVM.initCipher(mode, key, nonce!!, aad) + is SymmetricEncryptionAlgorithm.AES<*, *, *> -> AESJCA.initCipher(mode, algorithm, key, nonce, aad) + } + } + + else -> { + @OptIn(HazardousMaterials::class) + if ((algorithm !is SymmetricEncryptionAlgorithm.AES.ECB) && (algorithm !is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) + TODO("$algorithm is UNSUPPORTED") + AESJCA.initCipher(mode, algorithm, key, nonce, aad) + } + } + + override suspend fun doEncrypt(data: ByteArray): SealedBox { + require(mode == PlatformCipher.Mode.ENCRYPT) { "Cipher not in ENCRYPT mode!" } + val jcaCiphertext = cipher.doFinal(data) + //JCA simply concatenates ciphertext and authtag, so we need to split + val ciphertext = + if (algorithm.authCapability is AuthCapability.Authenticated<*>) + jcaCiphertext.dropLast(((algorithm.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) + .toByteArray() + else jcaCiphertext + val authTag = + if (algorithm.authCapability is AuthCapability.Authenticated<*>) + jcaCiphertext.takeLast(((algorithm.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) + .toByteArray() else null @Suppress("UNCHECKED_CAST") - when (algorithm) { - is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaJVM.initCipher(key, nonce, aad) - is SymmetricEncryptionAlgorithm.AES<*, *, *> -> AESJCA.initCipher(algorithm, key, nonce, aad) - } as PlatformCipher - } + return when { + algorithm.requiresNonce() -> when { + algorithm.isAuthenticated() -> { + (algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, *>) + algorithm.sealedBoxFrom(nonce!!, ciphertext, authTag!!) + } - else -> { - @OptIn(HazardousMaterials::class) - if ((algorithm !is SymmetricEncryptionAlgorithm.AES.ECB) && (algorithm !is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) - TODO("$algorithm is UNSUPPORTED") - - AESJCA.initCipher(algorithm, key, nonce, aad) as PlatformCipher - } -} - -internal actual fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { - @Suppress("UNCHECKED_CAST") (this as PlatformCipher) - val jcaCiphertext = platformData.doFinal(data) - - //JCA simply concatenates ciphertext and authtag, so we need to split - val ciphertext = - if (alg.authCapability is AuthCapability.Authenticated<*>) - jcaCiphertext.dropLast(((alg.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) - .toByteArray() - else jcaCiphertext - val authTag = - if (alg.authCapability is AuthCapability.Authenticated<*>) - jcaCiphertext.takeLast(((alg.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) - .toByteArray() else null - - - @Suppress("UNCHECKED_CAST") - return when { - alg.requiresNonce() -> when { - alg.isAuthenticated() -> { - (alg as SymmetricEncryptionAlgorithm, NonceTrait.Required, *>) - alg.sealedBoxFrom(nonce!!, ciphertext, authTag!!) + else -> algorithm.sealedBoxFrom(nonce!!, ciphertext) } - else -> alg.sealedBoxFrom(nonce!!, ciphertext) - } + else -> when { + algorithm.isAuthenticated() -> { + (algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Without, *>) + algorithm.sealedBoxFrom(ciphertext, authTag!!) + } - else -> when { - alg.isAuthenticated() -> { - (alg as SymmetricEncryptionAlgorithm, NonceTrait.Without, *>) - alg.sealedBoxFrom(ciphertext, authTag!!) + else -> algorithm.sealedBoxFrom(ciphertext) } - else -> alg.sealedBoxFrom(ciphertext) - } + }.getOrThrow() as SealedBox + } - }.getOrThrow() as SealedBox + override suspend fun doDecrypt(data: ByteArray, authTag: ByteArray?): ByteArray { + require(mode == PlatformCipher.Mode.DECRYPT) { "Cipher not in DECRYPT mode!" } + return when (algorithm.isAuthenticated()) { + true -> cipher.doFinal(data + authTag!!) + false -> cipher.doFinal(data) + } + } } val SymmetricEncryptionAlgorithm<*, *, *>.jcaName: String @@ -91,68 +105,9 @@ val SymmetricEncryptionAlgorithm<*, *, *>.jcaKeySpec: String else -> TODO("$this keyspec is unsupported UNSUPPORTED") } -@JvmName("doDecryptAuthenticated") -internal actual fun SealedBox.doDecryptAEAD( - secretKey: ByteArray, - authenticatedData: ByteArray -): ByteArray { - if (!this.hasNonce()) TODO("AEAD algorithm $algorithm is UNSUPPORTED") - - if ((algorithm !is SymmetricEncryptionAlgorithm.ChaCha20Poly1305) && (algorithm !is SymmetricEncryptionAlgorithm.AES.GCM)) - TODO("AEAD algorithm $algorithm is UNSUPPORTED") - - return aeadDecrypt( - algorithm, - secretKey, - nonce, - encryptedData, - authTag, - authenticatedData - ) - -} - -internal actual fun SealedBox.doDecrypt( - secretKey: ByteArray -): ByteArray { - if (algorithm !is SymmetricEncryptionAlgorithm.AES<*, *, *>) - TODO("unauthenticated algorithm $algorithm is UNSUPPORTED") - - @OptIn(HazardousMaterials::class) - if ((algorithm is SymmetricEncryptionAlgorithm.AES.ECB) || (algorithm is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) { - return Cipher.getInstance(algorithm.jcaName).also { cipher -> - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey, algorithm.jcaKeySpec), - ) - }.doFinal(encryptedData) +internal val PlatformCipher.Mode.jcaCipherMode + get() = when (this) { + PlatformCipher.Mode.ENCRYPT -> Cipher.ENCRYPT_MODE + PlatformCipher.Mode.DECRYPT -> Cipher.DECRYPT_MODE } - this as SealedBox.WithNonce - return Cipher.getInstance(algorithm.jcaName).also { cipher -> - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey, algorithm.jcaKeySpec), - IvParameterSpec(this@doDecrypt.nonce) - ) - }.doFinal(encryptedData) -} - -internal fun aeadDecrypt( - algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.Integrated>, - secretKey: ByteArray, - nonce: ByteArray, - encryptedData: ByteArray, - authTag: ByteArray, - aad: ByteArray? -): ByteArray = Cipher.getInstance(algorithm.jcaName).also { cipher -> - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey, algorithm.jcaKeySpec), - if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) - GCMParameterSpec(authTag.size * 8, nonce) - else IvParameterSpec(nonce) - ) - aad?.let { cipher.updateAAD(it) } -}.doFinal(encryptedData + authTag) - diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt index 4ec14bf3..1395a2a7 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt @@ -1,5 +1,6 @@ package at.asitplus.signum.supreme.symmetric +import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.ImplementationError import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated @@ -10,88 +11,130 @@ internal class Encryptor, I : NonceTrait, out K : KeyT private val algorithm: SymmetricEncryptionAlgorithm, private val key: ByteArray, private val macKey: ByteArray?, - private val iv: ByteArray?, + @OptIn(HazardousMaterials::class) + private val nonce: ByteArray? = if (algorithm.requiresNonce()) algorithm.randomNonce() else null, private val aad: ByteArray?, ) { init { - if (algorithm.nonceTrait is NonceTrait.Required) iv?.let { + + + if (algorithm.nonceTrait is NonceTrait.Required) nonce?.let { require(it.size.toUInt() == (algorithm.nonceTrait as NonceTrait.Required).length.bytes) { "IV must be exactly ${(algorithm.nonceTrait as NonceTrait.Required).length} bits long" } } require(key.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } } - private val platformCipher: PlatformCipher<*, A, out K> = initCipher(algorithm, key, iv, aad) - /** * Encrypts [data] and returns a [at.asitplus.signum.indispensable.symmetric.Ciphertext] matching the algorithm type that was used to create this [Encryptor] object. * E.g., an authenticated encryption algorithm causes this function to return a [at.asitplus.signum.indispensable.symmetric.Ciphertext.Authenticated]. */ - internal fun encrypt(data: ByteArray): SealedBox = ( - //Our own, flexible construction to make any unauthenticated cipher into an authenticated cipher - if (algorithm.hasDedicatedMac()) { - val aMac = algorithm.authCapability - aMac.innerCipher - val innerCipher = - initCipher( - aMac.innerCipher, - key, - iv, - aad - ) + internal suspend fun encrypt(data: ByteArray): SealedBox { + //Our own, flexible construction to make any unauthenticated cipher into an authenticated cipher + if (algorithm.hasDedicatedMac()) { + val aMac = algorithm.authCapability + aMac.innerCipher + val innerCipher = + initCipher( + PlatformCipher.Mode.ENCRYPT, + aMac.innerCipher, + key, + nonce, + aad + ) - if (!aMac.innerCipher.requiresNonce()) throw ImplementationError("AES-CBC-HMAC Nonce inconsistency") - if (macKey == null) throw ImplementationError("AES-CBC-HMAC MAC key is null") + if (!aMac.innerCipher.requiresNonce()) throw ImplementationError("AES-CBC-HMAC Nonce inconsistency") + if (macKey == null) throw ImplementationError("AES-CBC-HMAC MAC key is null") - val encrypted = innerCipher.doEncrypt(data) - val macInputCalculation = aMac.dedicatedMacInputCalculation - val hmacInput: ByteArray = - aMac.mac.macInputCalculation( - encrypted.encryptedData, - innerCipher.nonce ?: byteArrayOf(), - aad ?: byteArrayOf() - ) + val encrypted = innerCipher.doEncrypt(data) + val macInputCalculation = aMac.dedicatedMacInputCalculation + val hmacInput: ByteArray = + aMac.mac.macInputCalculation( + encrypted.encryptedData, + innerCipher.nonce ?: byteArrayOf(), + aad ?: byteArrayOf() + ) - val outputTransform = aMac.dedicatedMacAuthTagTransform - val authTag = aMac.outputTransform(aMac.mac.mac(macKey, hmacInput).getOrThrow()) + val outputTransform = aMac.dedicatedMacAuthTagTransform + val authTag = aMac.outputTransform(aMac.mac.mac(macKey, hmacInput).getOrThrow()) - @Suppress("UNCHECKED_CAST") - (if (algorithm.requiresNonce()) { - (algorithm).sealedBoxFrom( - (encrypted as SealedBox.WithNonce<*, *>).nonce, - encrypted.encryptedData, - authTag - ) - } else (algorithm).sealedBoxFrom( + @Suppress("UNCHECKED_CAST") + return (if (algorithm.requiresNonce()) { + (algorithm).sealedBoxFrom( + (encrypted as SealedBox.WithNonce<*, *>).nonce, encrypted.encryptedData, authTag - )).getOrThrow() as SealedBox + ) + } else (algorithm).sealedBoxFrom( + encrypted.encryptedData, + authTag + )).getOrThrow() as SealedBox - } else platformCipher.doEncrypt(data)) + } else @Suppress("UNCHECKED_CAST") return initCipher( + PlatformCipher.Mode.ENCRYPT, + algorithm, + key, + nonce, + aad + ).doEncrypt(data) as SealedBox + } } -internal class PlatformCipher, K : KeyType>( - val alg: SymmetricEncryptionAlgorithm, - val platformData: T, - val nonce: ByteArray?, +/** + * Platform cipher abstraction. + */ +internal interface PlatformCipher, I : NonceTrait, K : KeyType> { + /** + * We could do away with the encrypt/decrypt state and add subclassses, etc. but then we'd just have double the glue + * code, because every platform cipher works that way. + */ + val mode: Mode + val algorithm: SymmetricEncryptionAlgorithm + val key: ByteArray + val nonce: ByteArray? val aad: ByteArray? -) + //for later use, we could add val oneshot:Boolean =true here and add update() and doFinal() -internal expect fun SealedBox.doDecryptAEAD( + abstract suspend fun doDecrypt(data: ByteArray, authTag: ByteArray?): ByteArray + + abstract suspend fun doEncrypt(data: ByteArray): SealedBox + + enum class Mode { + ENCRYPT, + DECRYPT, + ; + } +} + + +internal suspend fun SealedBox.doDecryptAEAD( secretKey: ByteArray, authenticatedData: ByteArray -): ByteArray +): ByteArray { + val authTag = if (isAuthenticated()) authTag else null + val nonce = if (hasNonce()) nonce else null -internal expect fun SealedBox.doDecrypt( + return initCipher(PlatformCipher.Mode.DECRYPT, algorithm, secretKey, nonce, authenticatedData).doDecrypt( + encryptedData, + authTag + ) +} + +internal suspend fun SealedBox.doDecrypt( secretKey: ByteArray -): ByteArray +): ByteArray { + val nonce = if (hasNonce()) nonce else null + return initCipher(PlatformCipher.Mode.DECRYPT, algorithm, secretKey, nonce, null).doDecrypt(encryptedData, null) +} - -internal expect fun , I : NonceTrait, K : KeyType> initCipher( +internal expect suspend fun , I : NonceTrait, K : KeyType> initCipher( + mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm, key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): PlatformCipher +): PlatformCipher -internal expect fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox + +internal suspend fun SealedBox<*, *, *>.initDecrypt(key: ByteArray, aad: ByteArray?): PlatformCipher<*, *, *> = + initCipher(PlatformCipher.Mode.DECRYPT, algorithm, key, if (hasNonce()) nonce else null, aad) \ No newline at end of file diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt index 23f7d9ff..f75db909 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt @@ -15,7 +15,7 @@ private val secureRandom = SecureRandom() * Generates a fresh random key for this algorithm. */ @Suppress("UNCHECKED_CAST") -fun , I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.randomKey(): SymmetricKey = +suspend fun , I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.randomKey(): SymmetricKey = keyFromInternal( secureRandom.nextBytesOf(keySize.bytes.toInt()), if (authCapability.keyType is KeyType.WithDedicatedMacKey) secureRandom.nextBytesOf(keySize.bytes.toInt()) @@ -28,7 +28,7 @@ fun , I : NonceTrait, K : KeyType> SymmetricEncryption */ @JvmName("randomKeyAndMacKey") @Suppress("UNCHECKED_CAST") -fun SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>.randomKey( +suspend fun SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>.randomKey( macKeyLength: BitLength = preferredMacKeyLength ): SymmetricKey.WithDedicatedMac = keyFromInternal( diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index 02383926..03597908 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -18,7 +18,7 @@ import kotlin.jvm.JvmName * [key] and [SealedBox].** */ @JvmName("decryptGeneric") -fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult = catching { +suspend fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult = catching { require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } @Suppress("UNCHECKED_CAST") when (algorithm.authCapability) { @@ -49,7 +49,7 @@ fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult * [key] and [SealedBox].** */ @JvmName("decryptAuthenticatedIntegrated") -fun SealedBox, I, KeyType.WithDedicatedMacKey>.decrypt( +suspend fun SealedBox, I, KeyType.WithDedicatedMacKey>.decrypt( key: SymmetricKey.WithDedicatedMac<*>, authenticatedData: ByteArray = byteArrayOf() ) = catching { @@ -68,7 +68,7 @@ fun SealedBox, K : KeyType> SealedBox.decrypt( +suspend fun , K : KeyType> SealedBox.decrypt( key: SymmetricKey, authenticatedData: ByteArray = byteArrayOf() ): KmmResult = catching { @@ -98,7 +98,7 @@ fun , K : KeyType> SealedBox SealedBox.decrypt( +suspend fun SealedBox.decrypt( key: SymmetricKey ): KmmResult = catching { require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } @@ -118,23 +118,23 @@ fun SealedBox.decryptInternal( +private suspend fun SealedBox.decryptInternal( secretKey: ByteArray, authenticatedData: ByteArray ): ByteArray { require(secretKey.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } - return doDecryptAEAD(secretKey, authenticatedData) + return initDecrypt(secretKey, authenticatedData).doDecrypt(encryptedData, authTag) } @JvmName("decryptRaw") -private fun SealedBox.decryptInternal( +private suspend fun SealedBox.decryptInternal( secretKey: ByteArray ): ByteArray { require(secretKey.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } - return doDecrypt(secretKey) + return initDecrypt(secretKey, null).doDecrypt(encryptedData, null) } -private fun SealedBox, *, out KeyType.WithDedicatedMacKey>.decryptInternal( +private suspend fun SealedBox, *, out KeyType.WithDedicatedMacKey>.decryptInternal( secretKey: ByteArray, macKey: ByteArray = secretKey, authenticatedData: ByteArray @@ -159,7 +159,7 @@ private fun SealedBox, *, out KeyType.WithD ) else (innerCipher as SymmetricEncryptionAlgorithm).sealedBoxFrom( encryptedData )).getOrThrow() as SealedBox - return box.doDecrypt(secretKey) + return box.initDecrypt(secretKey, null).doDecrypt(box.encryptedData, null) } @@ -170,7 +170,7 @@ private fun SealedBox, *, out KeyType.WithD * Directly decrypts raw [encryptedData], feeding [nonce] into the decryption process. */ @JvmName("decryptRawUnauthedWithNonce") -fun SymmetricKey.decrypt( +suspend fun SymmetricKey.decrypt( nonce: ByteArray, encryptedData: ByteArray ): KmmResult = algorithm.sealedBoxFrom(nonce, encryptedData).transform { it.decrypt(this) } @@ -180,7 +180,7 @@ fun SymmetricKey.decrypt( +suspend fun SymmetricKey.decrypt( encryptedData: ByteArray ): KmmResult = algorithm.sealedBoxFrom(encryptedData).transform { it.decrypt(this) } @@ -190,7 +190,7 @@ fun SymmetricKey> SymmetricKey.decrypt( +suspend fun > SymmetricKey.decrypt( nonce: ByteArray, encryptedData: ByteArray, authTag: ByteArray, @@ -203,7 +203,7 @@ fun > SymmetricKey> SymmetricKey.decrypt( +suspend fun > SymmetricKey.decrypt( encryptedData: ByteArray, authTag: ByteArray, authenticatedData: ByteArray = byteArrayOf() diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt index 873677bc..714b45d2 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt @@ -21,7 +21,7 @@ import kotlin.jvm.JvmName */ @HazardousMaterials("NEVER re-use a nonce/IV! Have them auto-generated instead!") @JvmName("encryptAuthenticatedWithNonce") -fun > KeyWithNonceAuthenticating.encrypt( +suspend fun > KeyWithNonceAuthenticating.encrypt( data: ByteArray, authenticatedData: ByteArray? = null ): KmmResult> = catching { @@ -45,7 +45,7 @@ fun > KeyWithNonceAuthentic */ @HazardousMaterials("NEVER re-use a nonce/IV! Have them auto-generated instead!") @JvmName("encryptWithNonce") -fun > KeyWithNonce.encrypt( +suspend fun > KeyWithNonce.encrypt( data: ByteArray ): KmmResult> = catching { Encryptor( diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt index c45d3700..2c6a4453 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt @@ -12,15 +12,14 @@ import kotlin.jvm.JvmName * invalid parameters (e.g., algorithm mismatch, key length, …) */ @JvmName("encryptWithAutoGenIV") -fun , I : NonceTrait> SymmetricKey.encrypt( +suspend fun , I : NonceTrait> SymmetricKey.encrypt( data: ByteArray ): KmmResult> = catching { Encryptor( algorithm, if (this.hasDedicatedMacKey()) encryptionKey else secretKey, if (this.hasDedicatedMacKey()) macKey else null, - null, - null, + aad = null ).encrypt(data) } @@ -38,7 +37,7 @@ fun , I : NonceTrait> SymmetricKey, I : NonceTrait> SymmetricKey.encrypt( +suspend fun , I : NonceTrait> SymmetricKey.encrypt( data: ByteArray, authenticatedData: ByteArray? = null ): KmmResult> = catching { @@ -46,7 +45,6 @@ fun , I : NonceTrait> Symme algorithm, if (this.hasDedicatedMacKey()) encryptionKey else secretKey, if (this.hasDedicatedMacKey()) macKey else null, - null, - authenticatedData, + aad = authenticatedData, ).encrypt(data) } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt index 1c8d6a8d..5999f282 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt @@ -3,79 +3,79 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.ImplementationError import at.asitplus.signum.indispensable.symmetric.* -import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES import kotlinx.cinterop.ExperimentalForeignApi -internal actual fun , I : NonceTrait, K : KeyType> initCipher( +internal actual suspend fun , I : NonceTrait, K : KeyType> initCipher( + mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm, key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): PlatformCipher { +): PlatformCipher { + + @Suppress("UNCHECKED_CAST") + return IosPlatformCipher(mode, algorithm, key, nonce, aad) +} + + +private class IosPlatformCipher, I : NonceTrait, K : KeyType>( + override val mode: PlatformCipher.Mode, + override val algorithm: SymmetricEncryptionAlgorithm, + override val key: ByteArray, + override val nonce: ByteArray?, + override val aad: ByteArray?, +) : PlatformCipher { + + //the oneshot ccrypt is fully stateless. no init, no update, no final, so we are stateless here too @OptIn(HazardousMaterials::class) - val nonce = if (algorithm.requiresNonce()) nonce ?: algorithm.randomNonce() else null + override suspend fun doDecrypt(data: ByteArray, authTag: ByteArray?): ByteArray { + require(mode == PlatformCipher.Mode.DECRYPT) { "Cipher not in DECRYPT mode!" } + return when (algorithm.isAuthenticated()) { + true -> { + if (!algorithm.isIntegrated()) throw ImplementationError("iOS AEAD algorithm mapping") + if (algorithm.nonceTrait !is NonceTrait.Required) TODO("ALGORITHM $algorithm UNSUPPORTED") + when (algorithm) { + is AES<*, *, *> -> AESIOS.gcmDecrypt(data, key, nonce!!, authTag!!, aad) + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.decrypt( + data, + key, + nonce!!, + authTag!!, + aad + ) + else -> TODO("ALGORITHM UNSUPPORTED") + } + } - @Suppress("UNCHECKED_CAST") - return PlatformCipher, KeyType>( - algorithm, key, nonce, aad - ) as PlatformCipher -} + false -> { + require(algorithm is AES<*, *, *>) { "Only AES is supported" } + @Suppress("UNCHECKED_CAST") + AESIOS.cbcEcbCrypt( + algorithm as AES<*, KeyType.Integrated, *>, + encrypt = false, + key, + nonce, + data, + pad = when (algorithm) { + is AES.CBC.Unauthenticated, is AES.ECB -> true + is AES.WRAP.RFC3394 -> false + } + ) + } + } + } -@OptIn(ExperimentalForeignApi::class) -internal actual fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { - @Suppress("UNCHECKED_CAST") (this as PlatformCipher) - - @Suppress("UNCHECKED_CAST") - return when (alg) { - is AES<*, *, *> -> AESIOS.encrypt(alg, data, platformData, nonce, aad) - is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.encrypt(data, platformData, nonce!!, aad) - else -> TODO("ALGORITHM $alg UNSUPPORTED") - } as SealedBox -} - - -@OptIn(ExperimentalForeignApi::class) -internal actual fun SealedBox.doDecryptAEAD( - secretKey: ByteArray, - authenticatedData: ByteArray -): ByteArray { - if (algorithm.nonceTrait !is NonceTrait.Required) TODO("ALGORITHM $algorithm UNSUPPORTED") - this as SealedBox.WithNonce - return when (algorithm) { - is AES<*, *, *> -> AESIOS.gcmDecrypt(encryptedData, secretKey, nonce, authTag, authenticatedData) - is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.decrypt( - encryptedData, - secretKey, - nonce, - authTag, - authenticatedData - ) - - else -> TODO("ALGORITHM UNSUPPORTED") + @OptIn(ExperimentalForeignApi::class) + override suspend fun doEncrypt(data: ByteArray): SealedBox { + require(mode == PlatformCipher.Mode.ENCRYPT) { "Cipher not in ENCRYPT mode!" } + @Suppress("UNCHECKED_CAST") + return when (algorithm) { + is AES<*, *, *> -> AESIOS.encrypt(algorithm, data, key, nonce, aad) + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.encrypt(data, key, nonce!!, aad) + else -> TODO("ALGORITHM $algorithm UNSUPPORTED") + } as SealedBox } } - -@OptIn(ExperimentalForeignApi::class, HazardousMaterials::class) -internal actual fun SealedBox.doDecrypt( - secretKey: ByteArray -): ByteArray { - require(algorithm is AES<*, *, *>) { "Only AES is supported" } - - return AESIOS.cbcEcbCrypt( - algorithm as AES<*, KeyType.Integrated, *>, - encrypt = false, - secretKey, - if (this is SealedBox.WithNonce) nonce else null, - encryptedData, - pad = when (algorithm) { - is AES.CBC.Unauthenticated, is AES.ECB -> true - is AES.WRAP.RFC3394 -> false - else -> throw ImplementationError("Illegal AES encryption state.") - - } - ) - -} --- .../mac/MessageAuthenticationCode.kt | 1 - .../signum/supreme/symmetric/AES.jca.kt | 55 +++--- .../signum/supreme/symmetric/ChaCha.jca.kt | 10 +- .../signum/supreme/symmetric/Encryptor.jca.kt | 185 +++++++----------- .../signum/supreme/symmetric/Encryptor.kt | 155 +++++++++------ .../signum/supreme/symmetric/KeyGen.kt | 4 +- .../signum/supreme/symmetric/decrypt.kt | 28 +-- .../supreme/symmetric/discouraged/encrypt.kt | 4 +- .../signum/supreme/symmetric/encrypt.kt | 10 +- .../signum/supreme/symmetric/Encryptor.ios.kt | 114 +++++------ 10 files changed, 274 insertions(+), 292 deletions(-) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt index 488f30bb4..cc91a84c7 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt @@ -1,6 +1,5 @@ package at.asitplus.signum.indispensable.mac -import at.asitplus.signum.indispensable.DataIntegrityAlgorithm import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.asn1.encoding.Asn1 diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt index e3634a3fe..7761b1164 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt @@ -2,7 +2,6 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.symmetric.AuthCapability -import at.asitplus.signum.indispensable.symmetric.KeyType import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm import javax.crypto.Cipher import javax.crypto.spec.GCMParameterSpec @@ -12,41 +11,31 @@ import javax.crypto.spec.SecretKeySpec @OptIn(HazardousMaterials::class) internal object AESJCA { fun initCipher( + mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm.AES<*, *, *>, key: ByteArray, nonce: ByteArray?, aad: ByteArray? - ) = - Cipher.getInstance(algorithm.jcaName).apply { - val cipher = algorithm.authCapability - if (cipher is AuthCapability.Authenticated.Integrated) - init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpec), - GCMParameterSpec(cipher.tagLength.bits.toInt(), nonce) - ) - else if (algorithm is SymmetricEncryptionAlgorithm.AES.CBC<*, *>) //covers unauthenticated and CBC-HMAC, because CBC will always delegate to here - init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpec), - IvParameterSpec(nonce) - ) - else if ((algorithm is SymmetricEncryptionAlgorithm.AES.ECB) || algorithm is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394) { - init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpec), - ) - } - - else TODO("Algorithm $algorithm is unsupported ") - aad?.let { if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) updateAAD(it) /*CBC-HMAC we do ourselves*/ } - }.let { - @Suppress("UNCHECKED_CAST") - PlatformCipher, KeyType>( - algorithm as SymmetricEncryptionAlgorithm,*, KeyType>, - it, - nonce, - aad + ) = Cipher.getInstance(algorithm.jcaName).apply { + val cipher = algorithm.authCapability + if (cipher is AuthCapability.Authenticated.Integrated) + init( + mode.jcaCipherMode, + SecretKeySpec(key, algorithm.jcaKeySpec), + GCMParameterSpec(cipher.tagLength.bits.toInt(), nonce) + ) + else if (algorithm is SymmetricEncryptionAlgorithm.AES.CBC<*, *>) //covers unauthenticated and CBC-HMAC, because CBC will always delegate to here + init( + mode.jcaCipherMode, + SecretKeySpec(key, algorithm.jcaKeySpec), + IvParameterSpec(nonce) + ) + else if ((algorithm is SymmetricEncryptionAlgorithm.AES.ECB) || algorithm is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394) { + init( + mode.jcaCipherMode, + SecretKeySpec(key, algorithm.jcaKeySpec), ) - } + } else TODO("Algorithm $algorithm is unsupported ") + aad?.let { if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) updateAAD(it) /*CBC-HMAC we do ourselves*/ } + } } diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt index 0a0c2f5e1..20376fb1d 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt @@ -1,20 +1,18 @@ package at.asitplus.signum.supreme.symmetric -import at.asitplus.signum.indispensable.symmetric.AuthCapability -import at.asitplus.signum.indispensable.symmetric.KeyType import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec internal object ChaChaJVM { - fun initCipher(key: ByteArray, nonce: ByteArray, aad: ByteArray?): PlatformCipher = + fun initCipher(mode: PlatformCipher.Mode, key: ByteArray, nonce: ByteArray, aad: ByteArray?): Cipher = Cipher.getInstance(SymmetricEncryptionAlgorithm.ChaCha20Poly1305.jcaName).apply { init( - Cipher.ENCRYPT_MODE, + mode.jcaCipherMode, SecretKeySpec(key, SymmetricEncryptionAlgorithm.ChaCha20Poly1305.jcaKeySpec), IvParameterSpec(nonce) ) - aad?.let { updateAAD(it) } - }.let { PlatformCipher(SymmetricEncryptionAlgorithm.ChaCha20Poly1305, it, nonce, aad) } + aad?.let { updateAAD(it) } + } } \ No newline at end of file diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt index c39372ab3..31f1bcdf8 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt @@ -2,75 +2,89 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.symmetric.* -import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated import javax.crypto.Cipher -import javax.crypto.spec.GCMParameterSpec -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec -internal actual fun , I : NonceTrait, K : KeyType> initCipher( +internal actual suspend fun , I : NonceTrait, K : KeyType> initCipher( + mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm, key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): PlatformCipher = when { - algorithm.requiresNonce() -> { - @OptIn(HazardousMaterials::class) - val nonce = nonce ?: algorithm.randomNonce() +): PlatformCipher = JcaPlatformCipher(mode, algorithm, key, nonce, aad) - @Suppress("UNCHECKED_CAST") - when (algorithm) { - is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaJVM.initCipher(key, nonce, aad) - is SymmetricEncryptionAlgorithm.AES<*, *, *> -> AESJCA.initCipher(algorithm, key, nonce, aad) - } as PlatformCipher - } +internal class JcaPlatformCipher, I : NonceTrait, K : KeyType>( + override val mode: PlatformCipher.Mode, + override val algorithm: SymmetricEncryptionAlgorithm, + override val key: ByteArray, + override val nonce: ByteArray?, + override val aad: ByteArray?, +) : PlatformCipher { - else -> { - @OptIn(HazardousMaterials::class) - if ((algorithm !is SymmetricEncryptionAlgorithm.AES.ECB) && (algorithm !is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) - TODO("$algorithm is UNSUPPORTED") - AESJCA.initCipher(algorithm, key, nonce, aad) as PlatformCipher - } -} + internal val cipher: Cipher = + when { + algorithm.requiresNonce() -> { -internal actual fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { - @Suppress("UNCHECKED_CAST") (this as PlatformCipher) - val jcaCiphertext = platformData.doFinal(data) - - //JCA simply concatenates ciphertext and authtag, so we need to split - val ciphertext = - if (alg.authCapability is AuthCapability.Authenticated<*>) - jcaCiphertext.dropLast(((alg.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) - .toByteArray() - else jcaCiphertext - val authTag = - if (alg.authCapability is AuthCapability.Authenticated<*>) - jcaCiphertext.takeLast(((alg.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) - .toByteArray() else null - - - @Suppress("UNCHECKED_CAST") - return when { - alg.requiresNonce() -> when { - alg.isAuthenticated() -> { - (alg as SymmetricEncryptionAlgorithm, NonceTrait.Required, *>) - alg.sealedBoxFrom(nonce!!, ciphertext, authTag!!) + + @Suppress("UNCHECKED_CAST") + when (algorithm) { + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaJVM.initCipher(mode, key, nonce!!, aad) + is SymmetricEncryptionAlgorithm.AES<*, *, *> -> AESJCA.initCipher(mode, algorithm, key, nonce, aad) + } } - else -> alg.sealedBoxFrom(nonce!!, ciphertext) + else -> { + @OptIn(HazardousMaterials::class) + if ((algorithm !is SymmetricEncryptionAlgorithm.AES.ECB) && (algorithm !is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) + TODO("$algorithm is UNSUPPORTED") + AESJCA.initCipher(mode, algorithm, key, nonce, aad) + } } - else -> when { - alg.isAuthenticated() -> { - (alg as SymmetricEncryptionAlgorithm, NonceTrait.Without, *>) - alg.sealedBoxFrom(ciphertext, authTag!!) + override suspend fun doEncrypt(data: ByteArray): SealedBox { + require(mode == PlatformCipher.Mode.ENCRYPT) { "Cipher not in ENCRYPT mode!" } + val jcaCiphertext = cipher.doFinal(data) + //JCA simply concatenates ciphertext and authtag, so we need to split + val ciphertext = + if (algorithm.authCapability is AuthCapability.Authenticated<*>) + jcaCiphertext.dropLast(((algorithm.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) + .toByteArray() + else jcaCiphertext + val authTag = + if (algorithm.authCapability is AuthCapability.Authenticated<*>) + jcaCiphertext.takeLast(((algorithm.authCapability as AuthCapability.Authenticated<*>).tagLength.bytes.toInt()).toInt()) + .toByteArray() else null + + @Suppress("UNCHECKED_CAST") + return when { + algorithm.requiresNonce() -> when { + algorithm.isAuthenticated() -> { + (algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, *>) + algorithm.sealedBoxFrom(nonce!!, ciphertext, authTag!!) + } + + else -> algorithm.sealedBoxFrom(nonce!!, ciphertext) } - else -> alg.sealedBoxFrom(ciphertext) - } + else -> when { + algorithm.isAuthenticated() -> { + (algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Without, *>) + algorithm.sealedBoxFrom(ciphertext, authTag!!) + } + + else -> algorithm.sealedBoxFrom(ciphertext) + } + + }.getOrThrow() as SealedBox + } - }.getOrThrow() as SealedBox + override suspend fun doDecrypt(data: ByteArray, authTag: ByteArray?): ByteArray { + require(mode == PlatformCipher.Mode.DECRYPT) { "Cipher not in DECRYPT mode!" } + return when (algorithm.isAuthenticated()) { + true -> cipher.doFinal(data + authTag!!) + false -> cipher.doFinal(data) + } + } } val SymmetricEncryptionAlgorithm<*, *, *>.jcaName: String @@ -91,68 +105,9 @@ val SymmetricEncryptionAlgorithm<*, *, *>.jcaKeySpec: String else -> TODO("$this keyspec is unsupported UNSUPPORTED") } -@JvmName("doDecryptAuthenticated") -internal actual fun SealedBox.doDecryptAEAD( - secretKey: ByteArray, - authenticatedData: ByteArray -): ByteArray { - if (!this.hasNonce()) TODO("AEAD algorithm $algorithm is UNSUPPORTED") - - if ((algorithm !is SymmetricEncryptionAlgorithm.ChaCha20Poly1305) && (algorithm !is SymmetricEncryptionAlgorithm.AES.GCM)) - TODO("AEAD algorithm $algorithm is UNSUPPORTED") - - return aeadDecrypt( - algorithm, - secretKey, - nonce, - encryptedData, - authTag, - authenticatedData - ) - -} - -internal actual fun SealedBox.doDecrypt( - secretKey: ByteArray -): ByteArray { - if (algorithm !is SymmetricEncryptionAlgorithm.AES<*, *, *>) - TODO("unauthenticated algorithm $algorithm is UNSUPPORTED") - - @OptIn(HazardousMaterials::class) - if ((algorithm is SymmetricEncryptionAlgorithm.AES.ECB) || (algorithm is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394)) { - return Cipher.getInstance(algorithm.jcaName).also { cipher -> - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey, algorithm.jcaKeySpec), - ) - }.doFinal(encryptedData) +internal val PlatformCipher.Mode.jcaCipherMode + get() = when (this) { + PlatformCipher.Mode.ENCRYPT -> Cipher.ENCRYPT_MODE + PlatformCipher.Mode.DECRYPT -> Cipher.DECRYPT_MODE } - this as SealedBox.WithNonce - return Cipher.getInstance(algorithm.jcaName).also { cipher -> - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey, algorithm.jcaKeySpec), - IvParameterSpec(this@doDecrypt.nonce) - ) - }.doFinal(encryptedData) -} - -internal fun aeadDecrypt( - algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.Integrated>, - secretKey: ByteArray, - nonce: ByteArray, - encryptedData: ByteArray, - authTag: ByteArray, - aad: ByteArray? -): ByteArray = Cipher.getInstance(algorithm.jcaName).also { cipher -> - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey, algorithm.jcaKeySpec), - if (algorithm is SymmetricEncryptionAlgorithm.AES.GCM) - GCMParameterSpec(authTag.size * 8, nonce) - else IvParameterSpec(nonce) - ) - aad?.let { cipher.updateAAD(it) } -}.doFinal(encryptedData + authTag) - diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt index 4ec14bf37..1395a2a7f 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt @@ -1,5 +1,6 @@ package at.asitplus.signum.supreme.symmetric +import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.ImplementationError import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated @@ -10,88 +11,130 @@ internal class Encryptor, I : NonceTrait, out K : KeyT private val algorithm: SymmetricEncryptionAlgorithm, private val key: ByteArray, private val macKey: ByteArray?, - private val iv: ByteArray?, + @OptIn(HazardousMaterials::class) + private val nonce: ByteArray? = if (algorithm.requiresNonce()) algorithm.randomNonce() else null, private val aad: ByteArray?, ) { init { - if (algorithm.nonceTrait is NonceTrait.Required) iv?.let { + + + if (algorithm.nonceTrait is NonceTrait.Required) nonce?.let { require(it.size.toUInt() == (algorithm.nonceTrait as NonceTrait.Required).length.bytes) { "IV must be exactly ${(algorithm.nonceTrait as NonceTrait.Required).length} bits long" } } require(key.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } } - private val platformCipher: PlatformCipher<*, A, out K> = initCipher(algorithm, key, iv, aad) - /** * Encrypts [data] and returns a [at.asitplus.signum.indispensable.symmetric.Ciphertext] matching the algorithm type that was used to create this [Encryptor] object. * E.g., an authenticated encryption algorithm causes this function to return a [at.asitplus.signum.indispensable.symmetric.Ciphertext.Authenticated]. */ - internal fun encrypt(data: ByteArray): SealedBox = ( - //Our own, flexible construction to make any unauthenticated cipher into an authenticated cipher - if (algorithm.hasDedicatedMac()) { - val aMac = algorithm.authCapability - aMac.innerCipher - val innerCipher = - initCipher( - aMac.innerCipher, - key, - iv, - aad - ) - - if (!aMac.innerCipher.requiresNonce()) throw ImplementationError("AES-CBC-HMAC Nonce inconsistency") - if (macKey == null) throw ImplementationError("AES-CBC-HMAC MAC key is null") - - val encrypted = innerCipher.doEncrypt(data) - val macInputCalculation = aMac.dedicatedMacInputCalculation - val hmacInput: ByteArray = - aMac.mac.macInputCalculation( - encrypted.encryptedData, - innerCipher.nonce ?: byteArrayOf(), - aad ?: byteArrayOf() - ) - - val outputTransform = aMac.dedicatedMacAuthTagTransform - val authTag = aMac.outputTransform(aMac.mac.mac(macKey, hmacInput).getOrThrow()) - - @Suppress("UNCHECKED_CAST") - (if (algorithm.requiresNonce()) { - (algorithm).sealedBoxFrom( - (encrypted as SealedBox.WithNonce<*, *>).nonce, - encrypted.encryptedData, - authTag - ) - } else (algorithm).sealedBoxFrom( + internal suspend fun encrypt(data: ByteArray): SealedBox { + //Our own, flexible construction to make any unauthenticated cipher into an authenticated cipher + if (algorithm.hasDedicatedMac()) { + val aMac = algorithm.authCapability + aMac.innerCipher + val innerCipher = + initCipher( + PlatformCipher.Mode.ENCRYPT, + aMac.innerCipher, + key, + nonce, + aad + ) + + if (!aMac.innerCipher.requiresNonce()) throw ImplementationError("AES-CBC-HMAC Nonce inconsistency") + if (macKey == null) throw ImplementationError("AES-CBC-HMAC MAC key is null") + + val encrypted = innerCipher.doEncrypt(data) + val macInputCalculation = aMac.dedicatedMacInputCalculation + val hmacInput: ByteArray = + aMac.mac.macInputCalculation( encrypted.encryptedData, - authTag - )).getOrThrow() as SealedBox + innerCipher.nonce ?: byteArrayOf(), + aad ?: byteArrayOf() + ) + + val outputTransform = aMac.dedicatedMacAuthTagTransform + val authTag = aMac.outputTransform(aMac.mac.mac(macKey, hmacInput).getOrThrow()) - } else platformCipher.doEncrypt(data)) + @Suppress("UNCHECKED_CAST") + return (if (algorithm.requiresNonce()) { + (algorithm).sealedBoxFrom( + (encrypted as SealedBox.WithNonce<*, *>).nonce, + encrypted.encryptedData, + authTag + ) + } else (algorithm).sealedBoxFrom( + encrypted.encryptedData, + authTag + )).getOrThrow() as SealedBox + + } else @Suppress("UNCHECKED_CAST") return initCipher( + PlatformCipher.Mode.ENCRYPT, + algorithm, + key, + nonce, + aad + ).doEncrypt(data) as SealedBox + } } -internal class PlatformCipher, K : KeyType>( - val alg: SymmetricEncryptionAlgorithm, - val platformData: T, - val nonce: ByteArray?, +/** + * Platform cipher abstraction. + */ +internal interface PlatformCipher, I : NonceTrait, K : KeyType> { + /** + * We could do away with the encrypt/decrypt state and add subclassses, etc. but then we'd just have double the glue + * code, because every platform cipher works that way. + */ + val mode: Mode + val algorithm: SymmetricEncryptionAlgorithm + val key: ByteArray + val nonce: ByteArray? val aad: ByteArray? -) + //for later use, we could add val oneshot:Boolean =true here and add update() and doFinal() -internal expect fun SealedBox.doDecryptAEAD( + abstract suspend fun doDecrypt(data: ByteArray, authTag: ByteArray?): ByteArray + + abstract suspend fun doEncrypt(data: ByteArray): SealedBox + + enum class Mode { + ENCRYPT, + DECRYPT, + ; + } +} + + +internal suspend fun SealedBox.doDecryptAEAD( secretKey: ByteArray, authenticatedData: ByteArray -): ByteArray +): ByteArray { + val authTag = if (isAuthenticated()) authTag else null + val nonce = if (hasNonce()) nonce else null + + return initCipher(PlatformCipher.Mode.DECRYPT, algorithm, secretKey, nonce, authenticatedData).doDecrypt( + encryptedData, + authTag + ) +} -internal expect fun SealedBox.doDecrypt( +internal suspend fun SealedBox.doDecrypt( secretKey: ByteArray -): ByteArray - +): ByteArray { + val nonce = if (hasNonce()) nonce else null + return initCipher(PlatformCipher.Mode.DECRYPT, algorithm, secretKey, nonce, null).doDecrypt(encryptedData, null) +} -internal expect fun , I : NonceTrait, K : KeyType> initCipher( +internal expect suspend fun , I : NonceTrait, K : KeyType> initCipher( + mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm, key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): PlatformCipher +): PlatformCipher + -internal expect fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox +internal suspend fun SealedBox<*, *, *>.initDecrypt(key: ByteArray, aad: ByteArray?): PlatformCipher<*, *, *> = + initCipher(PlatformCipher.Mode.DECRYPT, algorithm, key, if (hasNonce()) nonce else null, aad) \ No newline at end of file diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt index 23f7d9ff1..f75db9093 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt @@ -15,7 +15,7 @@ private val secureRandom = SecureRandom() * Generates a fresh random key for this algorithm. */ @Suppress("UNCHECKED_CAST") -fun , I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.randomKey(): SymmetricKey = +suspend fun , I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.randomKey(): SymmetricKey = keyFromInternal( secureRandom.nextBytesOf(keySize.bytes.toInt()), if (authCapability.keyType is KeyType.WithDedicatedMacKey) secureRandom.nextBytesOf(keySize.bytes.toInt()) @@ -28,7 +28,7 @@ fun , I : NonceTrait, K : KeyType> SymmetricEncryption */ @JvmName("randomKeyAndMacKey") @Suppress("UNCHECKED_CAST") -fun SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>.randomKey( +suspend fun SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>.randomKey( macKeyLength: BitLength = preferredMacKeyLength ): SymmetricKey.WithDedicatedMac = keyFromInternal( diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index 023839260..03597908d 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -18,7 +18,7 @@ import kotlin.jvm.JvmName * [key] and [SealedBox].** */ @JvmName("decryptGeneric") -fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult = catching { +suspend fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult = catching { require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } @Suppress("UNCHECKED_CAST") when (algorithm.authCapability) { @@ -49,7 +49,7 @@ fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult * [key] and [SealedBox].** */ @JvmName("decryptAuthenticatedIntegrated") -fun SealedBox, I, KeyType.WithDedicatedMacKey>.decrypt( +suspend fun SealedBox, I, KeyType.WithDedicatedMacKey>.decrypt( key: SymmetricKey.WithDedicatedMac<*>, authenticatedData: ByteArray = byteArrayOf() ) = catching { @@ -68,7 +68,7 @@ fun SealedBox, K : KeyType> SealedBox.decrypt( +suspend fun , K : KeyType> SealedBox.decrypt( key: SymmetricKey, authenticatedData: ByteArray = byteArrayOf() ): KmmResult = catching { @@ -98,7 +98,7 @@ fun , K : KeyType> SealedBox SealedBox.decrypt( +suspend fun SealedBox.decrypt( key: SymmetricKey ): KmmResult = catching { require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } @@ -118,23 +118,23 @@ fun SealedBox.decryptInternal( +private suspend fun SealedBox.decryptInternal( secretKey: ByteArray, authenticatedData: ByteArray ): ByteArray { require(secretKey.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } - return doDecryptAEAD(secretKey, authenticatedData) + return initDecrypt(secretKey, authenticatedData).doDecrypt(encryptedData, authTag) } @JvmName("decryptRaw") -private fun SealedBox.decryptInternal( +private suspend fun SealedBox.decryptInternal( secretKey: ByteArray ): ByteArray { require(secretKey.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } - return doDecrypt(secretKey) + return initDecrypt(secretKey, null).doDecrypt(encryptedData, null) } -private fun SealedBox, *, out KeyType.WithDedicatedMacKey>.decryptInternal( +private suspend fun SealedBox, *, out KeyType.WithDedicatedMacKey>.decryptInternal( secretKey: ByteArray, macKey: ByteArray = secretKey, authenticatedData: ByteArray @@ -159,7 +159,7 @@ private fun SealedBox, *, out KeyType.WithD ) else (innerCipher as SymmetricEncryptionAlgorithm).sealedBoxFrom( encryptedData )).getOrThrow() as SealedBox - return box.doDecrypt(secretKey) + return box.initDecrypt(secretKey, null).doDecrypt(box.encryptedData, null) } @@ -170,7 +170,7 @@ private fun SealedBox, *, out KeyType.WithD * Directly decrypts raw [encryptedData], feeding [nonce] into the decryption process. */ @JvmName("decryptRawUnauthedWithNonce") -fun SymmetricKey.decrypt( +suspend fun SymmetricKey.decrypt( nonce: ByteArray, encryptedData: ByteArray ): KmmResult = algorithm.sealedBoxFrom(nonce, encryptedData).transform { it.decrypt(this) } @@ -180,7 +180,7 @@ fun SymmetricKey.decrypt( +suspend fun SymmetricKey.decrypt( encryptedData: ByteArray ): KmmResult = algorithm.sealedBoxFrom(encryptedData).transform { it.decrypt(this) } @@ -190,7 +190,7 @@ fun SymmetricKey> SymmetricKey.decrypt( +suspend fun > SymmetricKey.decrypt( nonce: ByteArray, encryptedData: ByteArray, authTag: ByteArray, @@ -203,7 +203,7 @@ fun > SymmetricKey> SymmetricKey.decrypt( +suspend fun > SymmetricKey.decrypt( encryptedData: ByteArray, authTag: ByteArray, authenticatedData: ByteArray = byteArrayOf() diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt index 873677bc5..714b45d21 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt @@ -21,7 +21,7 @@ import kotlin.jvm.JvmName */ @HazardousMaterials("NEVER re-use a nonce/IV! Have them auto-generated instead!") @JvmName("encryptAuthenticatedWithNonce") -fun > KeyWithNonceAuthenticating.encrypt( +suspend fun > KeyWithNonceAuthenticating.encrypt( data: ByteArray, authenticatedData: ByteArray? = null ): KmmResult> = catching { @@ -45,7 +45,7 @@ fun > KeyWithNonceAuthentic */ @HazardousMaterials("NEVER re-use a nonce/IV! Have them auto-generated instead!") @JvmName("encryptWithNonce") -fun > KeyWithNonce.encrypt( +suspend fun > KeyWithNonce.encrypt( data: ByteArray ): KmmResult> = catching { Encryptor( diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt index c45d37004..2c6a44537 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt @@ -12,15 +12,14 @@ import kotlin.jvm.JvmName * invalid parameters (e.g., algorithm mismatch, key length, …) */ @JvmName("encryptWithAutoGenIV") -fun , I : NonceTrait> SymmetricKey.encrypt( +suspend fun , I : NonceTrait> SymmetricKey.encrypt( data: ByteArray ): KmmResult> = catching { Encryptor( algorithm, if (this.hasDedicatedMacKey()) encryptionKey else secretKey, if (this.hasDedicatedMacKey()) macKey else null, - null, - null, + aad = null ).encrypt(data) } @@ -38,7 +37,7 @@ fun , I : NonceTrait> SymmetricKey, I : NonceTrait> SymmetricKey.encrypt( +suspend fun , I : NonceTrait> SymmetricKey.encrypt( data: ByteArray, authenticatedData: ByteArray? = null ): KmmResult> = catching { @@ -46,7 +45,6 @@ fun , I : NonceTrait> Symme algorithm, if (this.hasDedicatedMacKey()) encryptionKey else secretKey, if (this.hasDedicatedMacKey()) macKey else null, - null, - authenticatedData, + aad = authenticatedData, ).encrypt(data) } diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt index 1c8d6a8d6..5999f2823 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.ios.kt @@ -3,79 +3,79 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.ImplementationError import at.asitplus.signum.indispensable.symmetric.* -import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES import kotlinx.cinterop.ExperimentalForeignApi -internal actual fun , I : NonceTrait, K : KeyType> initCipher( +internal actual suspend fun , I : NonceTrait, K : KeyType> initCipher( + mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm, key: ByteArray, nonce: ByteArray?, aad: ByteArray? -): PlatformCipher { - - @OptIn(HazardousMaterials::class) - val nonce = if (algorithm.requiresNonce()) nonce ?: algorithm.randomNonce() else null - - @Suppress("UNCHECKED_CAST") - return PlatformCipher, KeyType>( - algorithm, key, nonce, aad - ) as PlatformCipher -} - -@OptIn(ExperimentalForeignApi::class) -internal actual fun , I : NonceTrait, K : KeyType> PlatformCipher<*, A, out K>.doEncrypt(data: ByteArray): SealedBox { - @Suppress("UNCHECKED_CAST") (this as PlatformCipher) +): PlatformCipher { @Suppress("UNCHECKED_CAST") - return when (alg) { - is AES<*, *, *> -> AESIOS.encrypt(alg, data, platformData, nonce, aad) - is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.encrypt(data, platformData, nonce!!, aad) - else -> TODO("ALGORITHM $alg UNSUPPORTED") - } as SealedBox + return IosPlatformCipher(mode, algorithm, key, nonce, aad) } -@OptIn(ExperimentalForeignApi::class) -internal actual fun SealedBox.doDecryptAEAD( - secretKey: ByteArray, - authenticatedData: ByteArray -): ByteArray { - if (algorithm.nonceTrait !is NonceTrait.Required) TODO("ALGORITHM $algorithm UNSUPPORTED") - this as SealedBox.WithNonce - return when (algorithm) { - is AES<*, *, *> -> AESIOS.gcmDecrypt(encryptedData, secretKey, nonce, authTag, authenticatedData) - is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.decrypt( - encryptedData, - secretKey, - nonce, - authTag, - authenticatedData - ) +private class IosPlatformCipher, I : NonceTrait, K : KeyType>( + override val mode: PlatformCipher.Mode, + override val algorithm: SymmetricEncryptionAlgorithm, + override val key: ByteArray, + override val nonce: ByteArray?, + override val aad: ByteArray?, +) : PlatformCipher { - else -> TODO("ALGORITHM UNSUPPORTED") - } -} - -@OptIn(ExperimentalForeignApi::class, HazardousMaterials::class) -internal actual fun SealedBox.doDecrypt( - secretKey: ByteArray -): ByteArray { - require(algorithm is AES<*, *, *>) { "Only AES is supported" } + //the oneshot ccrypt is fully stateless. no init, no update, no final, so we are stateless here too - return AESIOS.cbcEcbCrypt( - algorithm as AES<*, KeyType.Integrated, *>, - encrypt = false, - secretKey, - if (this is SealedBox.WithNonce) nonce else null, - encryptedData, - pad = when (algorithm) { - is AES.CBC.Unauthenticated, is AES.ECB -> true - is AES.WRAP.RFC3394 -> false - else -> throw ImplementationError("Illegal AES encryption state.") + @OptIn(HazardousMaterials::class) + override suspend fun doDecrypt(data: ByteArray, authTag: ByteArray?): ByteArray { + require(mode == PlatformCipher.Mode.DECRYPT) { "Cipher not in DECRYPT mode!" } + return when (algorithm.isAuthenticated()) { + true -> { + if (!algorithm.isIntegrated()) throw ImplementationError("iOS AEAD algorithm mapping") + if (algorithm.nonceTrait !is NonceTrait.Required) TODO("ALGORITHM $algorithm UNSUPPORTED") + when (algorithm) { + is AES<*, *, *> -> AESIOS.gcmDecrypt(data, key, nonce!!, authTag!!, aad) + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.decrypt( + data, + key, + nonce!!, + authTag!!, + aad + ) + else -> TODO("ALGORITHM UNSUPPORTED") + } + } + false -> { + require(algorithm is AES<*, *, *>) { "Only AES is supported" } + @Suppress("UNCHECKED_CAST") + AESIOS.cbcEcbCrypt( + algorithm as AES<*, KeyType.Integrated, *>, + encrypt = false, + key, + nonce, + data, + pad = when (algorithm) { + is AES.CBC.Unauthenticated, is AES.ECB -> true + is AES.WRAP.RFC3394 -> false + } + ) + } } - ) + } + @OptIn(ExperimentalForeignApi::class) + override suspend fun doEncrypt(data: ByteArray): SealedBox { + require(mode == PlatformCipher.Mode.ENCRYPT) { "Cipher not in ENCRYPT mode!" } + @Suppress("UNCHECKED_CAST") + return when (algorithm) { + is AES<*, *, *> -> AESIOS.encrypt(algorithm, data, key, nonce, aad) + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> ChaChaIOS.encrypt(data, key, nonce!!, aad) + else -> TODO("ALGORITHM $algorithm UNSUPPORTED") + } as SealedBox + } } From 9d46d78f6d8ced1f9d45e2c0f6a95622a8dba92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Mon, 24 Feb 2025 08:11:31 +0100 Subject: [PATCH 16/28] more cleanups --- .../signum/supreme/symmetric/Decryptor.kt | 65 +++++++++++ .../signum/supreme/symmetric/Encryptor.kt | 107 ++++++++---------- .../signum/supreme/symmetric/decrypt.kt | 27 +---- .../signum/supreme/symmetric/encrypt.kt | 3 +- 4 files changed, 117 insertions(+), 85 deletions(-) create mode 100644 supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Decryptor.kt diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Decryptor.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Decryptor.kt new file mode 100644 index 000000000..2e8549592 --- /dev/null +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Decryptor.kt @@ -0,0 +1,65 @@ +package at.asitplus.signum.supreme.symmetric + +import at.asitplus.signum.indispensable.symmetric.* +import at.asitplus.signum.supreme.mac.mac + +//needs no generics, because decrypt goes from bytes to bytes +internal class Decryptor( + val platformCipher: PlatformCipher<*, *, *>, + val algorithm: SymmetricEncryptionAlgorithm<*, *, *>, + val authTag: ByteArray?, + val macKey: ByteArray? +) { + companion object { + suspend operator fun invoke( + algorithm: SymmetricEncryptionAlgorithm<*, *, *>, + key: ByteArray, + macKey: ByteArray?, + nonce: ByteArray?, + authTag: ByteArray?, + aad: ByteArray? + ): Decryptor { + val authCapability = algorithm.authCapability + //while ir could be argued that we should not check here. we are only calling this chained with decrypt(), so it is fine to fail fast + val actualAlgorithm = if (authCapability is AuthCapability.Authenticated.WithDedicatedMac<*, *>) { + authCapability.innerCipher + } else algorithm + return Decryptor( + initCipher(PlatformCipher.Mode.DECRYPT, actualAlgorithm, key, nonce, aad), + algorithm, + authTag, + macKey + ) + } + } + + internal suspend fun decrypt(encryptedData: ByteArray): ByteArray { + if (algorithm.hasDedicatedMac()) { + val mac = algorithm.authCapability.mac + val dedicatedMacInputCalculation = algorithm.authCapability.dedicatedMacInputCalculation + val hmacInput = mac.dedicatedMacInputCalculation( + encryptedData, + platformCipher.nonce ?: byteArrayOf(), + platformCipher.aad!! + ) + val transform = algorithm.authCapability.dedicatedMacAuthTagTransform + if (!algorithm.authCapability.transform(mac.mac(macKey!!, hmacInput).getOrThrow()).contentEquals(authTag)) + throw IllegalArgumentException("Auth Tag mismatch!") + } + return platformCipher.doDecrypt(encryptedData, authTag) + } +} + +internal suspend fun SealedBox<*, *, *>.initDecrypt( + key: ByteArray, + macKey: ByteArray?, + aad: ByteArray? +): Decryptor = + Decryptor( + algorithm, + key, + macKey = macKey, + if (hasNonce()) nonce else null, + if (isAuthenticated()) authTag else null, + aad + ) \ No newline at end of file diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt index 1395a2a7f..f2f0e4a7a 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt @@ -3,28 +3,57 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.ImplementationError import at.asitplus.signum.indispensable.symmetric.* -import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated import at.asitplus.signum.supreme.mac.mac - -internal class Encryptor, I : NonceTrait, out K : KeyType> internal constructor( +/** + * Additional abstraction layer atop [PlatformCipher]. Currently, this is used to + * * check parameters (nonce length, key length, …) + * * implement AES-CBC-HMAC using AES-CBC as basic building block + * + * Given we have ECB, we would use it to implement more modes of operation. + */ +internal class Encryptor, I : NonceTrait, K : KeyType> private constructor( + private val platformCipher: PlatformCipher<*, *, *>, private val algorithm: SymmetricEncryptionAlgorithm, - private val key: ByteArray, + /*this needs to go here, because we implement this here, not in PlatformCipher*/ private val macKey: ByteArray?, - @OptIn(HazardousMaterials::class) - private val nonce: ByteArray? = if (algorithm.requiresNonce()) algorithm.randomNonce() else null, - private val aad: ByteArray?, ) { - init { - - - if (algorithm.nonceTrait is NonceTrait.Required) nonce?.let { - require(it.size.toUInt() == (algorithm.nonceTrait as NonceTrait.Required).length.bytes) { "IV must be exactly ${(algorithm.nonceTrait as NonceTrait.Required).length} bits long" } + //suspending init needs faux-ctor + companion object { + suspend operator fun , I : NonceTrait, K : KeyType> invoke( + algorithm: SymmetricEncryptionAlgorithm, + key: ByteArray, + macKey: ByteArray?, + @OptIn(HazardousMaterials::class) + nonce: ByteArray? = if (algorithm.requiresNonce()) algorithm.randomNonce() else null, + aad: ByteArray?, + + ): Encryptor { + if (algorithm.nonceTrait is NonceTrait.Required) nonce?.let { + require(it.size.toUInt() == (algorithm.nonceTrait as NonceTrait.Required).length.bytes) { "IV must be exactly ${(algorithm.nonceTrait as NonceTrait.Required).length} bits long" } + } + require(key.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } + val platformCipher = if (algorithm.hasDedicatedMac()) + initCipher( + PlatformCipher.Mode.ENCRYPT, + algorithm.authCapability.innerCipher, + key, + nonce, + aad + ) + else initCipher( + PlatformCipher.Mode.ENCRYPT, + algorithm, + key, + nonce, + aad + ) + return Encryptor(platformCipher, algorithm, macKey) } - require(key.size.toUInt() == algorithm.keySize.bytes) { "Key must be exactly ${algorithm.keySize} bits long" } } + /** * Encrypts [data] and returns a [at.asitplus.signum.indispensable.symmetric.Ciphertext] matching the algorithm type that was used to create this [Encryptor] object. * E.g., an authenticated encryption algorithm causes this function to return a [at.asitplus.signum.indispensable.symmetric.Ciphertext.Authenticated]. @@ -33,26 +62,17 @@ internal class Encryptor, I : NonceTrait, out K : KeyT //Our own, flexible construction to make any unauthenticated cipher into an authenticated cipher if (algorithm.hasDedicatedMac()) { val aMac = algorithm.authCapability - aMac.innerCipher - val innerCipher = - initCipher( - PlatformCipher.Mode.ENCRYPT, - aMac.innerCipher, - key, - nonce, - aad - ) if (!aMac.innerCipher.requiresNonce()) throw ImplementationError("AES-CBC-HMAC Nonce inconsistency") if (macKey == null) throw ImplementationError("AES-CBC-HMAC MAC key is null") - val encrypted = innerCipher.doEncrypt(data) + val encrypted = platformCipher.doEncrypt(data) val macInputCalculation = aMac.dedicatedMacInputCalculation val hmacInput: ByteArray = aMac.mac.macInputCalculation( encrypted.encryptedData, - innerCipher.nonce ?: byteArrayOf(), - aad ?: byteArrayOf() + platformCipher.nonce ?: byteArrayOf(), + platformCipher.aad ?: byteArrayOf() ) val outputTransform = aMac.dedicatedMacAuthTagTransform @@ -70,13 +90,7 @@ internal class Encryptor, I : NonceTrait, out K : KeyT authTag )).getOrThrow() as SealedBox - } else @Suppress("UNCHECKED_CAST") return initCipher( - PlatformCipher.Mode.ENCRYPT, - algorithm, - key, - nonce, - aad - ).doEncrypt(data) as SealedBox + } else @Suppress("UNCHECKED_CAST") return platformCipher.doEncrypt(data) as SealedBox } } @@ -95,9 +109,9 @@ internal interface PlatformCipher, I : NonceTrait, K : val aad: ByteArray? //for later use, we could add val oneshot:Boolean =true here and add update() and doFinal() - abstract suspend fun doDecrypt(data: ByteArray, authTag: ByteArray?): ByteArray + suspend fun doDecrypt(data: ByteArray, authTag: ByteArray?): ByteArray - abstract suspend fun doEncrypt(data: ByteArray): SealedBox + suspend fun doEncrypt(data: ByteArray): SealedBox enum class Mode { ENCRYPT, @@ -106,27 +120,6 @@ internal interface PlatformCipher, I : NonceTrait, K : } } - -internal suspend fun SealedBox.doDecryptAEAD( - secretKey: ByteArray, - authenticatedData: ByteArray -): ByteArray { - val authTag = if (isAuthenticated()) authTag else null - val nonce = if (hasNonce()) nonce else null - - return initCipher(PlatformCipher.Mode.DECRYPT, algorithm, secretKey, nonce, authenticatedData).doDecrypt( - encryptedData, - authTag - ) -} - -internal suspend fun SealedBox.doDecrypt( - secretKey: ByteArray -): ByteArray { - val nonce = if (hasNonce()) nonce else null - return initCipher(PlatformCipher.Mode.DECRYPT, algorithm, secretKey, nonce, null).doDecrypt(encryptedData, null) -} - internal expect suspend fun , I : NonceTrait, K : KeyType> initCipher( mode: PlatformCipher.Mode, algorithm: SymmetricEncryptionAlgorithm, @@ -134,7 +127,3 @@ internal expect suspend fun , I : NonceTrait, K : KeyT nonce: ByteArray?, aad: ByteArray? ): PlatformCipher - - -internal suspend fun SealedBox<*, *, *>.initDecrypt(key: ByteArray, aad: ByteArray?): PlatformCipher<*, *, *> = - initCipher(PlatformCipher.Mode.DECRYPT, algorithm, key, if (hasNonce()) nonce else null, aad) \ No newline at end of file diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index 03597908d..a98fe8dae 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -7,7 +7,6 @@ import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES -import at.asitplus.signum.supreme.mac.mac import kotlin.jvm.JvmName @@ -123,7 +122,7 @@ private suspend fun SealedBox, *, out KeyType.WithDedicatedMacKey>.decryptInternal( @@ -139,27 +138,7 @@ private suspend fun SealedBox, *, out KeyTy macKey: ByteArray = secretKey, authenticatedData: ByteArray ): ByteArray { - require(this.isAuthenticated()) - val iv: ByteArray? = if (this is SealedBox.WithNonce<*, *>) nonce else null - val authTag = authTag - - val algorithm = algorithm - val innerCipher = algorithm.authCapability.innerCipher - val mac = algorithm.authCapability.mac - val dedicatedMacInputCalculation = algorithm.authCapability.dedicatedMacInputCalculation - val hmacInput = mac.dedicatedMacInputCalculation(encryptedData, iv ?: byteArrayOf(), authenticatedData) - val transform = algorithm.authCapability.dedicatedMacAuthTagTransform - if (!algorithm.authCapability.transform(mac.mac(macKey, hmacInput).getOrThrow()).contentEquals(authTag)) - throw IllegalArgumentException("Auth Tag mismatch!") - - @Suppress("UNCHECKED_CAST") val box: SealedBox = - (if (this is SealedBox.WithNonce<*, *>) (innerCipher as SymmetricEncryptionAlgorithm).sealedBoxFrom( - nonce, - encryptedData - ) else (innerCipher as SymmetricEncryptionAlgorithm).sealedBoxFrom( - encryptedData - )).getOrThrow() as SealedBox - return box.initDecrypt(secretKey, null).doDecrypt(box.encryptedData, null) + return initDecrypt(secretKey, macKey, authenticatedData).decrypt(encryptedData) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt index 2c6a44537..cf74c0288 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt @@ -30,8 +30,7 @@ suspend fun , I : NonceTrait> SymmetricKe * @param authenticatedData Additional data to be authenticated (i.e. fed into the auth tag generation) but not encrypted. * - * It is safe to discard the reference to this data, as the [SealedBox] resulting from this operation will carry the - * corresponding type information. Hence, it is possible to simply access - * [at.asitplus.signum.indispensable.symmetric.authenticatedData] + * corresponding type information. * * @return [KmmResult.success] containing a [SealedBox] if valid parameters were provided or [KmmResult.failure] in case of * invalid parameters (e.g., algorithm mismatch, key length, …) From 2895dab347a1fd5831d94bd8754c32c8afb86efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Mon, 24 Feb 2025 20:18:21 +0100 Subject: [PATCH 17/28] work around KT-75444 --- README.md | 5 +- docs/docs/supreme.md | 18 +- .../symmetric/SealedBoxCreation.kt | 228 +++++++++++++----- .../signum/supreme/symmetric/decrypt.kt | 4 +- .../signum/supreme/symmetric/00ApiTest.kt | 196 +++++++++------ .../supreme/symmetric/00SymmetricTest.kt | 91 ++++--- .../asitplus/signum/supreme/symmetric/Test.kt | 18 +- 7 files changed, 339 insertions(+), 221 deletions(-) diff --git a/README.md b/README.md index 58c4ce42b..ac844aa07 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ val keyBytes = secretKey.secretKey /*for algorithms with a dedicated MAC key, th Decrypting data received from external sources is also straight-forward: ```kotlin -val box = algo.sealedBoxFrom(nonce, ciphertext, authTag).getOrThrow(/*handle error*/) +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: @@ -356,8 +356,7 @@ 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.sealedBoxFrom( - sealedBox.nonce, +val reconstructed = algorithm.sealedBox.withNonce(sealedBox.nonce).from( encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/ authTag = sealedBox.authTag, ).getOrThrow() diff --git a/docs/docs/supreme.md b/docs/docs/supreme.md index 1f4163916..66fb1154f 100644 --- a/docs/docs/supreme.md +++ b/docs/docs/supreme.md @@ -505,7 +505,7 @@ val keyBytes = secretKey.secretKey /*for algorithms with a dedicated MAC key, th Decrypting data received from external sources is also straight-forward: ```kotlin -val box = algo.sealedBoxFrom(nonce, ciphertext, authTag).getOrThrow(/*handle error*/) +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: @@ -538,27 +538,15 @@ val sealedBox = key.encrypt( authenticatedData = aad, ).getOrThrow(/*handle error*/) -//The sealed box object is correctly typed: -// * It is a SealedBox.WithIV -// * The generic type arguments indicate that -// * the ciphertext is authenticated -// * Using a dedicated MAC function atop an unauthenticated cipher -// * we can hence access `authenticatedCiphertext` for: -// * authTag -// * authenticatedData -sealedBox.authenticatedData shouldBe aad - //because everything is structured, decryption is simple val recovered = sealedBox.decrypt(key).getOrThrow(/*handle error*/) recovered shouldBe payload //success! //we can also manually construct the sealed box, if we know the algorithm: -val reconstructed = algorithm.sealedBox( - sealedBox.nonce, +val reconstructed = algorithm.sealedBox.withNonce(sealedBox.Nonce).from( encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/ - authTag = sealedBox.authTag, - authenticatedData = sealedBox.authenticatedData + authTag = sealedBox.authTag ).getOrThrow() val manuallyRecovered = reconstructed.decrypt(key).getOrThrow(/*handle error*/) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt index 5fc879e25..6cb8b62ad 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt @@ -3,100 +3,200 @@ package at.asitplus.signum.indispensable.symmetric import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.misc.bytes -import kotlin.jvm.JvmName + +/** + * Creates a [SealedBox] matching the characteristics of the underlying [SealedBoxBuilder.algorithm]. + * Use this function to load external encrypted data for decryption. + * Returns a KmmResult purely for the sake of consistency, even though this operation will always success + */ +fun , K : KeyType> SealedBoxBuilder.WithNonce.Having.from( + encryptedData: ByteArray, + authTag: ByteArray +): KmmResult> = catching { + @Suppress("UNCHECKED_CAST") + SealedBox.WithNonce.Authenticated( + nonce, + algorithm.authenticatedCipherText(encryptedData, authTag) + ) as SealedBox //TODO why is this an unchecked cast??? +} /** - * Creates a [SealedBox] matching the characteristics of the [SymmetricEncryptionAlgorithm] it was created for. + * Creates a [SealedBox] matching the characteristics of the underlying [SealedBoxBuilder.algorithm]. * Use this function to load external encrypted data for decryption. - * - * Returns a KmmResult purely for the sake of consistency + * Returns a KmmResult purely for the sake of consistency, even though this operation will always success */ -@JvmName("sealedBoxUnauthedWithNonce") -fun SymmetricEncryptionAlgorithm.sealedBoxFrom( - nonce: ByteArray, - encryptedData: ByteArray -) = catching { +fun SealedBoxBuilder.WithNonce.Having.from( + encryptedData: ByteArray, +): KmmResult> = catching { SealedBox.WithNonce.Unauthenticated( nonce, - Ciphertext.Unauthenticated( - this, - encryptedData - ) + Ciphertext.Unauthenticated(algorithm, encryptedData) ) } +// we need both this one and the next to work around https://youtrack.jetbrains.com/issue/KT-75444 /** - * Creates a [SealedBox] matching the characteristics of the [SymmetricEncryptionAlgorithm] it was created for. + * Creates a [SealedBox] matching the characteristics of the underlying [SealedBoxBuilder.algorithm]. * Use this function to load external encrypted data for decryption. - * Returns a KmmResult purely for the sake of consistency + * + * + * @return [at.asitplus.KmmResult.failure] on illegal auth tag length */ -fun SymmetricEncryptionAlgorithm.sealedBoxFrom( - encryptedData: ByteArray -) = catching { - SealedBox.WithoutNonce.Unauthenticated( - Ciphertext.Unauthenticated( - this, - encryptedData - ) +fun SealedBoxBuilder.Without.Authenticated, *>.from( + encryptedData: ByteArray, + authTag: ByteArray +): KmmResult, NonceTrait.Without, *>> = catching { + require(authTag.size.bytes == algorithm.authCapability.tagLength) { "Illegal auth tag length! expected: ${authTag.size * 8}, actual: ${algorithm.authCapability.tagLength.bits}" } + SealedBox.WithoutNonce.Authenticated( + algorithm.authenticatedCipherText(encryptedData, authTag) ) } - /** - * Creates a [SealedBox] matching the characteristics of the [SymmetricEncryptionAlgorithm] it was created for. + * Creates a [SealedBox] matching the characteristics of the underlying [SealedBoxBuilder.algorithm]. * Use this function to load external encrypted data for decryption. * * @return [at.asitplus.KmmResult.failure] on illegal auth tag length */ -@JvmName("sealedBoxAuthenticatedWith") -fun, K: KeyType> SymmetricEncryptionAlgorithm.sealedBoxFrom( - nonce: ByteArray, +fun SealedBoxBuilder.Without.Authenticated, K>.from( encryptedData: ByteArray, - authTag: ByteArray, -) : KmmResult> =catching { - require(authTag.size.bytes == this.authCapability.tagLength) { "Illegal auth tag length! expected: ${authTag.size * 8}, actual: ${this.authCapability.tagLength.bits}" } - when (isIntegrated()) { - false -> SealedBox.WithNonce.Authenticated( - nonce, - authenticatedCipherText(encryptedData, authTag) - ) - - true -> SealedBox.WithNonce.Authenticated( - nonce, - authenticatedCipherText(encryptedData, authTag) - ) - } as SealedBox + authTag: ByteArray +): KmmResult, NonceTrait.Without, K>> = catching { + require(authTag.size.bytes == algorithm.authCapability.tagLength) { "Illegal auth tag length! expected: ${authTag.size * 8}, actual: ${algorithm.authCapability.tagLength.bits}" } + SealedBox.WithoutNonce.Authenticated( + algorithm.authenticatedCipherText(encryptedData, authTag) + ) } + /** - * Creates a [SealedBox] matching the characteristics of the [SymmetricEncryptionAlgorithm] it was created for. + * Creates a [SealedBox] matching the characteristics of the underlying [SealedBoxBuilder.algorithm]. * Use this function to load external encrypted data for decryption. - * - * @return [at.asitplus.KmmResult.failure] on illegal auth tag length + * Returns a KmmResult purely for the sake of consistency */ -@JvmName("sealedBoxAuthenticatedWithout") -fun SymmetricEncryptionAlgorithm, NonceTrait.Without, *>.sealedBoxFrom( +fun SealedBoxBuilder.Without.from( encryptedData: ByteArray, - authTag: ByteArray, -) : KmmResult, NonceTrait.Without,*>> = catching { - require(authTag.size == this.authCapability.tagLength.bytes.toInt()) { "Illegal auth tag length! expected: ${authTag.size * 8}, actual: ${this.authCapability.tagLength.bits}" } - when (hasDedicatedMac()) { - true -> SealedBox.WithoutNonce.Authenticated( - authenticatedCipherText(encryptedData, authTag) - ) - - false -> SealedBox.WithoutNonce.Authenticated( - @Suppress("UNCHECKED_CAST") - (this as SymmetricEncryptionAlgorithm).authenticatedCipherText( - encryptedData, - authTag, - ) - ) - }as SealedBox, NonceTrait.Without,*> +): KmmResult> = catching { + SealedBox.WithoutNonce.Unauthenticated( + Ciphertext.Unauthenticated(algorithm, encryptedData) + ) } -private inline fun , reified I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.authenticatedCipherText( +/** + * [SealedBox] builder from [algorithm] + */ +sealed class SealedBoxBuilder, I : NonceTrait, out K : KeyType>(internal val algorithm: SymmetricEncryptionAlgorithm) { + sealed class WithNonce, K : KeyType>(algorithm: SymmetricEncryptionAlgorithm) : + SealedBoxBuilder(algorithm) { + class Awaiting, K : KeyType> internal constructor(algorithm: SymmetricEncryptionAlgorithm) : + WithNonce(algorithm) { + fun withNonce(nonce: ByteArray): Having = when (algorithm.authCapability) { + is AuthCapability.Authenticated<*> -> Having.Authenticated, KeyType>( + algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType>, + nonce + ) + + else -> Having.Unauthenticated( + algorithm as SymmetricEncryptionAlgorithm, + nonce + ) + } as Having + + } + + sealed class Having, K : KeyType>( + algorithm: SymmetricEncryptionAlgorithm, + internal val nonce: ByteArray + ) : WithNonce(algorithm) { + class Authenticated, K : KeyType> internal constructor( + algorithm: SymmetricEncryptionAlgorithm, + nonce: ByteArray, + ) : Having(algorithm, nonce) + + class Unauthenticated internal constructor( + algorithm: SymmetricEncryptionAlgorithm, + nonce: ByteArray + ) : Having(algorithm, nonce) + } + } + + sealed class Without, K : KeyType>(algorithm: SymmetricEncryptionAlgorithm) : + SealedBoxBuilder(algorithm) { + class Authenticated, K : KeyType> internal constructor( + algorithm: SymmetricEncryptionAlgorithm, + ) : Without(algorithm) + + class Unauthenticated internal constructor( + algorithm: SymmetricEncryptionAlgorithm, + ) : Without(algorithm) + } +} + +/** + * Creates a [SealedBoxBuilder] matching this algorithm's characteristics: + * * Nonce requirement: ✔ + * * Authenticated encryption: ✘ + */ +val SymmetricEncryptionAlgorithm.sealedBox: + SealedBoxBuilder.WithNonce.Awaiting + get() = SealedBoxBuilder.WithNonce.Awaiting(this) + + +/** + * Creates a [SealedBoxBuilder] matching this algorithm's characteristics: + * * Nonce requirement: ✘ + * * Authenticated encryption: ✘ + */ +val SymmetricEncryptionAlgorithm.sealedBox: + SealedBoxBuilder.Without + get() = SealedBoxBuilder.Without.Unauthenticated(this) + + +/** + * Creates a [SealedBoxBuilder] matching this algorithm's characteristics: + * * Nonce requirement: ✔ + * * Authenticated encryption: ✔ + */ +val > SymmetricEncryptionAlgorithm.sealedBox: + SealedBoxBuilder.WithNonce.Awaiting + get() = SealedBoxBuilder.WithNonce.Awaiting(this) + + +// we need both this one and the next two to work around https://youtrack.jetbrains.com/issue/KT-75444 +/** + * Creates a [SealedBoxBuilder] matching this algorithm's characteristics: + * * Nonce requirement: ✘ + * * Authenticated encryption: ✔ + */ +val SymmetricEncryptionAlgorithm, NonceTrait.Without, *>.sealedBox: + SealedBoxBuilder.Without.Authenticated, *> + get() = SealedBoxBuilder.Without.Authenticated, KeyType>(this) + +/** + * Creates a [SealedBoxBuilder] matching this algorithm's characteristics: + * * Nonce requirement: ✘ + * * Authenticated encryption: ✔ + */ +val SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.Integrated>.sealedBox: + SealedBoxBuilder.Without.Authenticated, KeyType.Integrated> + get() = SealedBoxBuilder.Without.Authenticated, KeyType.Integrated>( + this + ) + +/** + * Creates a [SealedBoxBuilder] matching this algorithm's characteristics: + * * Nonce requirement: ✘ + * * Authenticated encryption: ✔ + */ +val SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>.sealedBox: + SealedBoxBuilder.Without.Authenticated, KeyType.WithDedicatedMacKey> + get() = SealedBoxBuilder.Without.Authenticated, KeyType.WithDedicatedMacKey>( + this + ) + + +private inline fun , reified I : NonceTrait, K : KeyType> SymmetricEncryptionAlgorithm.authenticatedCipherText( encryptedData: ByteArray, authTag: ByteArray, ) = Ciphertext.Authenticated, K>( diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index a98fe8dae..77bb40694 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -67,8 +67,8 @@ suspend fun SealedBox, K : KeyType> SealedBox.decrypt( - key: SymmetricKey, +suspend fun ,I: NonceTrait, K : KeyType> SealedBox.decrypt( + key: SymmetricKey, authenticatedData: ByteArray = byteArrayOf() ): KmmResult = catching { require(algorithm == key.algorithm) { "Algorithm mismatch! expected: $algorithm, actual: ${key.algorithm}" } diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt index 7d96da023..724d2a7c5 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00ApiTest.kt @@ -18,7 +18,7 @@ class `00ApiTest` : FreeSpec({ SymmetricEncryptionAlgorithm.AES_128.GCM, SymmetricEncryptionAlgorithm.AES_128.ECB, SymmetricEncryptionAlgorithm.ChaCha20Poly1305 - ) { algorithm-> + ) { algorithm -> //create a key, encrypt and decrypt works! val key = algorithm.randomKey() @@ -47,42 +47,60 @@ class `00ApiTest` : FreeSpec({ when (algorithm.requiresNonce()) { true -> when (algorithm.isAuthenticated()) { true -> { - algorithm - //compile error - //algorithm.sealedBoxFrom(byteArrayOf()) - //compile error - //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf()) - algorithm.sealedBoxFrom( - byteArrayOf(), //nonce - byteArrayOf(), //encrypted - byteArrayOf(), //auth tag - ) + // compile error + // algorithm.sealedBox.from(byteArrayOf(), byteArrayOf()) + + // compile error + // algorithm.sealedBox.from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) + + algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) + + } false -> { - //Compile error - //algorithm.sealedBoxFrom(byteArrayOf()) - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) - //Compile error - //algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(),byteArrayOf()) + // compile error + // algorithm.sealedBox.from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.from(byteArrayOf(), byteArrayOf()) + + algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) + } } false -> when (algorithm.isAuthenticated()) { true -> { + // compile error + // algorithm.sealedBox.from(byteArrayOf()) + + algorithm.sealedBox.from(byteArrayOf(), byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) + //compile error - //algorithm.sealedBoxFrom(byteArrayOf()) - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) - //TODO @Jakob, I need help. this should not compile, but it does. - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) } false -> { - algorithm.sealedBoxFrom(byteArrayOf()) - //compile error - //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf()) - //compile error - //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf(),byteArrayOf()) + algorithm.sealedBox.from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.from(byteArrayOf(),byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) } } } @@ -92,25 +110,29 @@ class `00ApiTest` : FreeSpec({ when (algorithm.isAuthenticated()) { true -> when (algorithm.requiresNonce()) { true -> { - algorithm - //compile error - //algorithm.sealedBoxFrom(byteArrayOf()) - //compile error - //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf()) - //why ambiguous? - algorithm.sealedBoxFrom( - byteArrayOf(), //nonce - byteArrayOf(), //encrypted - byteArrayOf(), //authTag - ) + // compile error + // algorithm.sealedBox.from(byteArrayOf(), byteArrayOf()) + + // compile error + // algorithm.sealedBox.from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) + + algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) } false -> { - //Compile error - //algorithm.sealedBoxFrom(byteArrayOf()) - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) - //TODO @Jakob, I need help. this should not compile, but it does. - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(),byteArrayOf()) + // compile error + // algorithm.sealedBox.from(byteArrayOf()) + + algorithm.sealedBox.from(byteArrayOf(), byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) + + //compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) } @@ -118,19 +140,29 @@ class `00ApiTest` : FreeSpec({ false -> when (algorithm.requiresNonce()) { true -> { - //compile error - //algorithm.sealedBoxFrom(byteArrayOf()) - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf()) - //compile error - //algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) + // compile error + // algorithm.sealedBox.from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.from(byteArrayOf(), byteArrayOf()) + + algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) + } false -> { - algorithm.sealedBoxFrom(byteArrayOf()) - //compile error - //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf()) - //compile error - //algorithm.sealedBoxFrom(byteArrayOf(),byteArrayOf(),byteArrayOf()) + algorithm.sealedBox.from(byteArrayOf()) + // compile error + // algorithm.sealedBox.from(byteArrayOf(),byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) } } @@ -138,43 +170,47 @@ class `00ApiTest` : FreeSpec({ } } - "Authenticated" - { - withData( - SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_512, - SymmetricEncryptionAlgorithm.AES_128.GCM, - SymmetricEncryptionAlgorithm.ChaCha20Poly1305 - ) { + "Authenticated" - { + withData( + SymmetricEncryptionAlgorithm.AES_128.CBC.HMAC.SHA_512, + SymmetricEncryptionAlgorithm.AES_128.GCM, + SymmetricEncryptionAlgorithm.ChaCha20Poly1305 + ) { - val algorithm = it + val algorithm = it - //create a key, encrypt and decrypt works! - val key = algorithm.randomKey() - val box = key.encrypt("Harvest".encodeToByteArray()).getOrThrow() - box.decrypt(key) should succeed + //create a key, encrypt and decrypt works! + val key = algorithm.randomKey() + val box = key.encrypt("Harvest".encodeToByteArray()).getOrThrow() + box.decrypt(key) should succeed - //if you load a key, you are forced to know whether a dedicated MAC key is required - val loadedKey = when (algorithm.hasDedicatedMac()) { - true -> { - algorithm.keyFrom(byteArrayOf(), byteArrayOf()) - //Compile error - //algorithm.keyFrom(byteArrayOf()) - } + //if you load a key, you are forced to know whether a dedicated MAC key is required + val loadedKey = when (algorithm.hasDedicatedMac()) { + true -> { + algorithm.keyFrom(byteArrayOf(), byteArrayOf()) + //Compile error + //algorithm.keyFrom(byteArrayOf()) + } - false -> { - algorithm.keyFrom(byteArrayOf()) - //Compile error - //algorithm.keyFrom(byteArrayOf(),byteArrayOf()) - } + false -> { + algorithm.keyFrom(byteArrayOf()) + //Compile error + //algorithm.keyFrom(byteArrayOf(),byteArrayOf()) } - //compile error - //algorithm.sealedBox(byteArrayOf(), byteArrayOf()) + } + // compile error + // algorithm.sealedBox.from(byteArrayOf()) + + // compile error + // algorithm.sealedBox.from(byteArrayOf(), byteArrayOf()) + + // compile error + // algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf()) - //correct - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) + //correct + algorithm.sealedBox.withNonce(byteArrayOf()).from(byteArrayOf(), byteArrayOf()) - //correct - algorithm.sealedBoxFrom(byteArrayOf(), byteArrayOf(), byteArrayOf()) } } diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index d63b51ea4..2ed268d49 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -21,7 +21,6 @@ import io.kotest.matchers.types.shouldBeInstanceOf import kotlinx.datetime.Clock import kotlin.random.Random import kotlin.random.nextUInt -import at.asitplus.signum.supreme.symmetric.decrypt @OptIn(HazardousMaterials::class) @ExperimentalStdlibApi @@ -48,11 +47,11 @@ class `00SymmetricTest` : FreeSpec({ val preSharedKey = algo.keyFrom(keyBytes).getOrThrow() - val box = algo.sealedBoxFrom(nonce, ciphertext, authTag).getOrThrow(/*handle error*/) + val box = algo.sealedBox.withNonce(nonce).from(ciphertext, authTag).getOrThrow(/*handle error*/) box.decrypt(preSharedKey, /*also pass AAD*/ externalAAD).getOrThrow(/*handle error*/) shouldBe secret //direct decryption - preSharedKey.decrypt(nonce, ciphertext,authTag, externalAAD).getOrThrow(/*handle error*/) shouldBe secret + preSharedKey.decrypt(nonce, ciphertext, authTag, externalAAD).getOrThrow(/*handle error*/) shouldBe secret val payload = "More matter, with less art!".encodeToByteArray() @@ -78,8 +77,7 @@ class `00SymmetricTest` : FreeSpec({ recovered shouldBe payload //success! //we can also manually construct the sealed box, if we know the algorithm: - val reconstructed = algorithm.sealedBoxFrom( - sealedBox.nonce, + val reconstructed = algorithm.sealedBox.withNonce(sealedBox.nonce).from( encryptedData = sealedBox.encryptedData, /*Could also access authenticatedCipherText*/ authTag = sealedBox.authTag, ).getOrThrow() @@ -280,8 +278,7 @@ class `00SymmetricTest` : FreeSpec({ wrongDecrypted.onSuccess { value -> value shouldNotBe plaintext } val wrongCiphertext = - ciphertext.algorithm.sealedBoxFrom( - ciphertext.nonce, + ciphertext.algorithm.sealedBox.withNonce(ciphertext.nonce).from( Random.Default.nextBytes(ciphertext.encryptedData.size) ).getOrThrow() @@ -300,10 +297,10 @@ class `00SymmetricTest` : FreeSpec({ wrongRightDecrypted.onSuccess { value -> value shouldNotBe plaintext } } val wrongIV = - ciphertext.algorithm.sealedBoxFrom( - nonce = ciphertext.nonce.asList().shuffled().toByteArray(), - encryptedData = ciphertext.encryptedData - ).getOrThrow() + ciphertext.algorithm.sealedBox.withNonce(ciphertext.nonce.asList().shuffled().toByteArray()) + .from( + encryptedData = ciphertext.encryptedData + ).getOrThrow() if (plaintext.size > it.blockSize.bytes.toInt()) { //cannot test like that for ciphertexts shorter than IV @@ -369,8 +366,7 @@ class `00SymmetricTest` : FreeSpec({ val wrongDecrypted = ciphertext.decrypt(alg.randomKey()) wrongDecrypted shouldNot succeed - val wrongCiphertext = alg.sealedBoxFrom( - ciphertext.nonce, + val wrongCiphertext = alg.sealedBox.withNonce(ciphertext.nonce).from( Random.Default.nextBytes(ciphertext.encryptedData.size), authTag = ciphertext.authTag, ).getOrThrow() @@ -382,8 +378,7 @@ class `00SymmetricTest` : FreeSpec({ val wrongRightDecrypted = wrongCiphertext.decrypt(key) wrongRightDecrypted shouldNot succeed - val wrongIV = alg.sealedBoxFrom( - nonce = ciphertext.nonce.asList().shuffled().toByteArray(), + val wrongIV = alg.sealedBox.withNonce(ciphertext.nonce.asList().shuffled().toByteArray()).from( ciphertext.encryptedData, authTag = ciphertext.authTag, ).getOrThrow() @@ -394,16 +389,14 @@ class `00SymmetricTest` : FreeSpec({ if (aad != null) { //missing aad - alg.sealedBoxFrom( - nonce = ciphertext.nonce, + alg.sealedBox.withNonce(ciphertext.nonce).from( encryptedData = ciphertext.encryptedData, authTag = ciphertext.authTag, ).getOrThrow().decrypt(key) shouldNot succeed } //shuffled auth tag - alg.sealedBoxFrom( - nonce = ciphertext.nonce, + alg.sealedBox.withNonce(ciphertext.nonce).from( ciphertext.encryptedData, authTag = ciphertext.authTag.asList().shuffled().toByteArray(), ).getOrThrow().decrypt(key, aad ?: byteArrayOf()) shouldNot succeed @@ -417,7 +410,11 @@ class `00SymmetricTest` : FreeSpec({ withData( nameFn = { it.first }, "Default" to DefaultDedicatedMacInputCalculation, - "Oklahoma MAC" to fun MessageAuthenticationCode.(ciphertext: ByteArray, iv: ByteArray?, aad: ByteArray?): ByteArray = + "Oklahoma MAC" to fun MessageAuthenticationCode.( + ciphertext: ByteArray, + iv: ByteArray?, + aad: ByteArray? + ): ByteArray = "Oklahoma".encodeToByteArray() + (iv ?: byteArrayOf()) + (aad ?: byteArrayOf()) + ciphertext) { (_, macInputFun) -> withData( @@ -550,8 +547,7 @@ class `00SymmetricTest` : FreeSpec({ wrongDecrypted shouldNot succeed val wrongCiphertext = - ciphertext.algorithm.sealedBoxFrom( - ciphertext.nonce, + ciphertext.algorithm.sealedBox.withNonce(ciphertext.nonce).from( Random.Default.nextBytes(ciphertext.encryptedData.size), authTag = ciphertext.authTag, ).getOrThrow() @@ -564,22 +560,23 @@ class `00SymmetricTest` : FreeSpec({ wrongRightDecrypted shouldNot succeed val wrongIV = - ciphertext.algorithm.sealedBoxFrom( - nonce = ciphertext.nonce.asList().shuffled().toByteArray(), + ciphertext.algorithm.sealedBox.withNonce( + ciphertext.nonce.asList().shuffled().toByteArray() + ).from( ciphertext.encryptedData, ciphertext.authTag, ).getOrThrow() val wrongIVDecrypted = wrongIV.decrypt(key, aad ?: byteArrayOf()) wrongIVDecrypted shouldNot succeed - ciphertext.algorithm.sealedBoxFrom( - nonce = ciphertext.nonce.asList().shuffled().toByteArray(), + ciphertext.algorithm.sealedBox.withNonce( + ciphertext.nonce.asList().shuffled().toByteArray() + ).from( ciphertext.encryptedData, authTag = ciphertext.authTag, ).getOrThrow().decrypt(key, aad ?: byteArrayOf()) shouldNot succeed - ciphertext.algorithm.sealedBoxFrom( - nonce = ciphertext.nonce, + ciphertext.algorithm.sealedBox.withNonce(ciphertext.nonce).from( ciphertext.encryptedData, authTag = ciphertext.authTag, ).getOrThrow().decrypt( @@ -591,20 +588,17 @@ class `00SymmetricTest` : FreeSpec({ ) shouldNot succeed if (aad != null) { - ciphertext.algorithm.sealedBoxFrom( - ciphertext.nonce, + ciphertext.algorithm.sealedBox.withNonce(ciphertext.nonce).from( ciphertext.encryptedData, ciphertext.authTag, ).getOrThrow().decrypt(key) shouldNot succeed } - ciphertext.algorithm.sealedBoxFrom( - ciphertext.nonce, + ciphertext.algorithm.sealedBox.withNonce(ciphertext.nonce).from( ciphertext.encryptedData, ciphertext.authTag.asList().shuffled().toByteArray(), ).getOrThrow().decrypt(key, aad ?: byteArrayOf()) shouldNot succeed - ciphertext.algorithm.sealedBoxFrom( - ciphertext.nonce, + ciphertext.algorithm.sealedBox.withNonce(ciphertext.nonce).from( ciphertext.encryptedData, ciphertext.authTag.asList().shuffled().toByteArray(), ).getOrThrow().decrypt(it.Custom(ciphertext.authTag.size.bytes) { _, _, _ -> @@ -670,7 +664,7 @@ class `00SymmetricTest` : FreeSpec({ it shouldNotBe data } - alg.sealedBoxFrom(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed + alg.sealedBox.from(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed } else { @@ -695,7 +689,7 @@ class `00SymmetricTest` : FreeSpec({ it shouldNotBe data } - alg.sealedBoxFrom(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed + alg.sealedBox.from(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed } } } @@ -832,49 +826,50 @@ class `00SymmetricTest` : FreeSpec({ val box = when (alg.requiresNonce()) { true -> when (alg.isAuthenticated()) { - true -> alg.sealedBoxFrom( - alg.randomNonce(), + true -> alg.sealedBox.withNonce(alg.randomNonce()).from( plaintext, Random.nextBytes(alg.authTagLength.bytes.toInt()), ) - false -> alg.sealedBoxFrom(alg.randomNonce(), plaintext) + false -> alg.sealedBox.withNonce(alg.randomNonce()).from(plaintext) } false -> when (alg.isAuthenticated()) { - true -> alg.sealedBoxFrom( + true -> alg.sealedBox.from( plaintext, Random.nextBytes(alg.authTagLength.bytes.toInt()), ) - false -> alg.sealedBoxFrom(plaintext) + false -> alg.sealedBox.from(plaintext) } }.getOrThrow() val box2 = when (wrongAlg.requiresNonce()) { true -> when (wrongAlg.isAuthenticated()) { - true -> wrongAlg.sealedBoxFrom( + true -> wrongAlg.sealedBox.withNonce( if (box.hasNonce() && box.nonce.size == wrongAlg.nonceLength.bytes.toInt()) box.nonce else - wrongAlg.randomNonce(), + wrongAlg.randomNonce() + ).from( + plaintext, if (box.isAuthenticated() && box.authTag.size == wrongAlg.authTagLength.bytes.toInt()) box.authTag else Random.nextBytes(wrongAlg.authTagLength.bytes.toInt()), ) - false -> wrongAlg.sealedBoxFrom( - if (box.hasNonce() && box.nonce.size == wrongAlg.nonceLength.bytes.toInt()) box.nonce else - wrongAlg.randomNonce(), plaintext + false -> wrongAlg.sealedBox.withNonce(if (box.hasNonce() && box.nonce.size == wrongAlg.nonceLength.bytes.toInt()) box.nonce else + wrongAlg.randomNonce()).from( + plaintext ) } false -> when (wrongAlg.isAuthenticated()) { - true -> wrongAlg.sealedBoxFrom( + true -> wrongAlg.sealedBox.from( plaintext, if (box.isAuthenticated() && box.authTag.size == wrongAlg.authTagLength.bytes.toInt()) box.authTag else Random.nextBytes(wrongAlg.authTagLength.bytes.toInt()), ) - false -> wrongAlg.sealedBoxFrom(plaintext) + false -> wrongAlg.sealedBox.from(plaintext) } }.getOrThrow() diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index de909f3cc..db68cf2cd 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -95,17 +95,18 @@ class JvmSymmetricTest : FreeSpec({ - own.decrypt(secretKey, aad?:byteArrayOf()).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) + own.decrypt(secretKey, aad ?: byteArrayOf()).getOrThrow() shouldBe jcaCipher.doFinal( + encrypted + ) val wrongKey = own.algorithm.randomKey() own.decrypt(wrongKey) shouldNot succeed - val box = own.algorithm.sealedBoxFrom( - own.algorithm.randomNonce(), + val box = own.algorithm.sealedBox.withNonce(own.algorithm.randomNonce()).from( own.encryptedData, own.authTag, ).getOrThrow() - box.decrypt(secretKey, aad?:byteArrayOf()) shouldNot succeed + box.decrypt(secretKey, aad ?: byteArrayOf()) shouldNot succeed } else { @@ -135,8 +136,7 @@ class JvmSymmetricTest : FreeSpec({ } if (data.size < alg.blockSize.bytes.toInt()) - alg.sealedBoxFrom( - own.algorithm.randomNonce(), + alg.sealedBox.withNonce(own.algorithm.randomNonce()).from( own.encryptedData ).getOrThrow().decrypt(secretKey) shouldNot succeed @@ -202,7 +202,7 @@ class JvmSymmetricTest : FreeSpec({ it shouldNotBe data } - alg.sealedBoxFrom(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed + alg.sealedBox.from(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed } else { @@ -242,7 +242,7 @@ class JvmSymmetricTest : FreeSpec({ it shouldNotBe data } - alg.sealedBoxFrom(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed + alg.sealedBox.from(own.encryptedData).getOrThrow().decrypt(secretKey) should succeed } } } @@ -289,7 +289,7 @@ class JvmSymmetricTest : FreeSpec({ box.nonce.size shouldBe alg.nonceTrait.length.bytes.toInt() box.isAuthenticated() shouldBe true (box.encryptedData + box.authTag) shouldBe fromJCA - box.decrypt(secretKey, aad?:byteArrayOf()).getOrThrow() shouldBe data + box.decrypt(secretKey, aad ?: byteArrayOf()).getOrThrow() shouldBe data } } From 3c4694ae389355207621e8d934830c92d347db24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Mon, 24 Feb 2025 22:16:53 +0100 Subject: [PATCH 18/28] fix compile errors --- .../signum/supreme/symmetric/Encryptor.jca.kt | 8 ++++---- .../asitplus/signum/supreme/symmetric/Encryptor.kt | 5 ++--- .../asitplus/signum/supreme/symmetric/decrypt.kt | 8 ++++---- .../asitplus/signum/supreme/symmetric/AES.ios.kt | 14 +++++--------- .../signum/supreme/symmetric/ChaCha.ios.kt | 8 ++++---- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt index 31f1bcdf8..4d2309e20 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt @@ -60,19 +60,19 @@ internal class JcaPlatformCipher, I : NonceTrait, K : algorithm.requiresNonce() -> when { algorithm.isAuthenticated() -> { (algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, *>) - algorithm.sealedBoxFrom(nonce!!, ciphertext, authTag!!) + algorithm.sealedBox.withNonce(nonce!!).from(ciphertext, authTag!!) } - else -> algorithm.sealedBoxFrom(nonce!!, ciphertext) + else -> algorithm.sealedBox.withNonce(nonce!!).from(ciphertext) } else -> when { algorithm.isAuthenticated() -> { (algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Without, *>) - algorithm.sealedBoxFrom(ciphertext, authTag!!) + algorithm.sealedBox.from(ciphertext, authTag!!) } - else -> algorithm.sealedBoxFrom(ciphertext) + else -> algorithm.sealedBox.from(ciphertext) } }.getOrThrow() as SealedBox diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt index f2f0e4a7a..243597ff5 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.kt @@ -80,12 +80,11 @@ internal class Encryptor, I : NonceTrait, K : KeyType> @Suppress("UNCHECKED_CAST") return (if (algorithm.requiresNonce()) { - (algorithm).sealedBoxFrom( - (encrypted as SealedBox.WithNonce<*, *>).nonce, + algorithm.sealedBox.withNonce( (encrypted as SealedBox.WithNonce<*, *>).nonce).from( encrypted.encryptedData, authTag ) - } else (algorithm).sealedBoxFrom( + } else (algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Without, *>).sealedBox.from( encrypted.encryptedData, authTag )).getOrThrow() as SealedBox diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index 77bb40694..bc0477292 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -152,7 +152,7 @@ private suspend fun SealedBox, *, out KeyTy suspend fun SymmetricKey.decrypt( nonce: ByteArray, encryptedData: ByteArray -): KmmResult = algorithm.sealedBoxFrom(nonce, encryptedData).transform { it.decrypt(this) } +): KmmResult = algorithm.sealedBox.withNonce(nonce).from(encryptedData).transform { it.decrypt(this) } /** @@ -161,7 +161,7 @@ suspend fun SymmetricKey.decrypt( encryptedData: ByteArray -): KmmResult = algorithm.sealedBoxFrom(encryptedData).transform { it.decrypt(this) } +): KmmResult = algorithm.sealedBox.from(encryptedData).transform { it.decrypt(this) } /** @@ -175,7 +175,7 @@ suspend fun > SymmetricKey = - algorithm.sealedBoxFrom(nonce, encryptedData, authTag).transform { it.decrypt(this, authenticatedData) } + algorithm.sealedBox.withNonce(nonce).from(encryptedData, authTag).transform { it.decrypt(this, authenticatedData) } /** * Directly decrypts raw [encryptedData], feeding [authTag], and [authenticatedData] into the decryption process. @@ -187,7 +187,7 @@ suspend fun > SymmetricKey = - algorithm.sealedBoxFrom(encryptedData, authTag).transform { + algorithm.sealedBox.from(encryptedData, authTag).transform { it.decrypt( @Suppress("UNCHECKED_CAST") this as SymmetricKey, NonceTrait.Without, *>, diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt index 465f2eb73..55c6592ee 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.ios.kt @@ -2,11 +2,8 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.ImplementationError -import at.asitplus.signum.indispensable.symmetric.BlockCipher -import at.asitplus.signum.indispensable.symmetric.KeyType -import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm +import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm.AES -import at.asitplus.signum.indispensable.symmetric.sealedBoxFrom import at.asitplus.signum.internals.swiftcall import at.asitplus.signum.internals.toByteArray import at.asitplus.signum.internals.toNSData @@ -43,24 +40,23 @@ internal object AESIOS { ) = when (alg) { is AES.CBC.Unauthenticated -> { val bytes = cbcEcbCrypt(alg, encrypt = true, key, nonce, data, pad = true) - alg.sealedBoxFrom(nonce!!, bytes).getOrThrow() + alg.sealedBox.withNonce(nonce!!).from(bytes).getOrThrow() } is AES.ECB -> { val bytes = cbcEcbCrypt(alg, encrypt = true, key, nonce, data, pad = true) - alg.sealedBoxFrom(bytes).getOrThrow() + alg.sealedBox.from(bytes).getOrThrow() } is AES.WRAP.RFC3394 -> { val bytes = cbcEcbCrypt(alg, encrypt = true, key, nonce, data, pad = false) - alg.sealedBoxFrom(bytes).getOrThrow() + alg.sealedBox.from(bytes).getOrThrow() } is AES.GCM -> { val ciphertext = GCM.encrypt(data.toNSData(), key.toNSData(), nonce?.toNSData(), aad?.toNSData()) if (ciphertext == null) throw IllegalStateException("Error from swift code!") - alg.sealedBoxFrom( - ciphertext.iv().toByteArray(), + alg.sealedBox.withNonce(ciphertext.iv().toByteArray()).from( ciphertext.ciphertext().toByteArray(), ciphertext.authTag().toByteArray() ).getOrThrow() diff --git a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt index 1beb3454a..1e2eef42d 100644 --- a/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt +++ b/supreme/src/iosMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.ios.kt @@ -5,7 +5,8 @@ import at.asitplus.signum.indispensable.symmetric.KeyType import at.asitplus.signum.indispensable.symmetric.NonceTrait import at.asitplus.signum.indispensable.symmetric.SealedBox import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm -import at.asitplus.signum.indispensable.symmetric.sealedBoxFrom +import at.asitplus.signum.indispensable.symmetric.from +import at.asitplus.signum.indispensable.symmetric.sealedBox import at.asitplus.signum.internals.swiftcall import at.asitplus.signum.internals.toByteArray import at.asitplus.signum.internals.toNSData @@ -24,11 +25,10 @@ internal object ChaChaIOS { val ciphertext = ChaCha.encrypt(data.toNSData(), key.toNSData(), nonce.toNSData(), aad?.toNSData()) if (ciphertext == null) throw UnsupportedOperationException("Error from swift code!") @Suppress("UNCHECKED_CAST") - return SymmetricEncryptionAlgorithm.ChaCha20Poly1305.sealedBoxFrom( - ciphertext.iv().toByteArray(), + return SymmetricEncryptionAlgorithm.ChaCha20Poly1305.sealedBox.withNonce( ciphertext.iv().toByteArray()).from( ciphertext.ciphertext().toByteArray(), ciphertext.authTag().toByteArray() - ).getOrThrow() as SealedBox + ).getOrThrow() } From c4fbd977611408d36e64864755822dd02144e3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Tue, 25 Feb 2025 06:06:14 +0100 Subject: [PATCH 19/28] fix JVM compile errors diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt index cc91a84c..488f30bb 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt @@ -1,5 +1,6 @@ package at.asitplus.signum.indispensable.mac +import at.asitplus.signum.indispensable.DataIntegrityAlgorithm import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.asn1.encoding.Asn1 diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt index 6cb8b62a..30926c56 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt @@ -3,6 +3,7 @@ package at.asitplus.signum.indispensable.symmetric import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.misc.bytes +import kotlin.jvm.JvmName /** * Creates a [SealedBox] matching the characteristics of the underlying [SealedBoxBuilder.algorithm]. @@ -43,6 +44,7 @@ fun SealedBoxBuilder.WithNonce.Having, *>.from( encryptedData: ByteArray, authTag: ByteArray @@ -59,6 +61,7 @@ fun SealedBoxBuilder.Without.Authenticated, *>.f * * @return [at.asitplus.KmmResult.failure] on illegal auth tag length */ +@JvmName("fromAuthenticatedWihtKeyType") fun SealedBoxBuilder.Without.Authenticated, K>.from( encryptedData: ByteArray, authTag: ByteArray @@ -75,6 +78,7 @@ fun SealedBoxBuilder.Without.Authenticated.from( encryptedData: ByteArray, ): KmmResult> = catching { diff --git a/supreme/build.gradle.kts b/supreme/build.gradle.kts index dfb07b4c..6356473c 100644 --- a/supreme/build.gradle.kts +++ b/supreme/build.gradle.kts @@ -31,6 +31,8 @@ version = supremeVersion wireAndroidInstrumentedTests() kotlin { + compilerOptions.freeCompilerArgs.add("-Xexpect-actual-classes") + applyDefaultHierarchyTemplate() jvm() androidTarget { @OptIn(ExperimentalKotlinGradlePluginApi::class) @@ -59,7 +61,6 @@ kotlin { sourceSets.androidMain.dependencies { implementation("androidx.biometric:biometric:1.2.0-alpha05") } - } swiftklib { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index bc047729..55bf083c 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -52,6 +52,7 @@ suspend fun SealedBox, authenticatedData: ByteArray = byteArrayOf() ) = catching { + @Suppress("UNCHECKED_CAST") (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( key.encryptionKey, key.macKey, @@ -84,7 +85,7 @@ suspend fun ,I: NonceTrait, K : KeyType> key.encryptionKey, key.macKey, authenticatedData ) } - + //compiler knows its exhaustive, but IDEA complains else -> throw ImplementationError("Authenticated Decryption") } } @@ -188,8 +189,8 @@ suspend fun > SymmetricKey = algorithm.sealedBox.from(encryptedData, authTag).transform { + @Suppress("UNCHECKED_CAST") it.decrypt( - @Suppress("UNCHECKED_CAST") this as SymmetricKey, NonceTrait.Without, *>, authenticatedData ) --- .../signum/indispensable/mac/MessageAuthenticationCode.kt | 1 + .../signum/indispensable/symmetric/SealedBoxCreation.kt | 4 ++++ supreme/build.gradle.kts | 3 ++- .../kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt | 5 +++-- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt index cc91a84c7..488f30bb4 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt @@ -1,5 +1,6 @@ package at.asitplus.signum.indispensable.mac +import at.asitplus.signum.indispensable.DataIntegrityAlgorithm import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.asn1.encoding.Asn1 diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt index 6cb8b62ad..30926c566 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt @@ -3,6 +3,7 @@ package at.asitplus.signum.indispensable.symmetric import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.misc.bytes +import kotlin.jvm.JvmName /** * Creates a [SealedBox] matching the characteristics of the underlying [SealedBoxBuilder.algorithm]. @@ -43,6 +44,7 @@ fun SealedBoxBuilder.WithNonce.Having, *>.from( encryptedData: ByteArray, authTag: ByteArray @@ -59,6 +61,7 @@ fun SealedBoxBuilder.Without.Authenticated, *>.f * * @return [at.asitplus.KmmResult.failure] on illegal auth tag length */ +@JvmName("fromAuthenticatedWihtKeyType") fun SealedBoxBuilder.Without.Authenticated, K>.from( encryptedData: ByteArray, authTag: ByteArray @@ -75,6 +78,7 @@ fun SealedBoxBuilder.Without.Authenticated.from( encryptedData: ByteArray, ): KmmResult> = catching { diff --git a/supreme/build.gradle.kts b/supreme/build.gradle.kts index dfb07b4c5..6356473ce 100644 --- a/supreme/build.gradle.kts +++ b/supreme/build.gradle.kts @@ -31,6 +31,8 @@ version = supremeVersion wireAndroidInstrumentedTests() kotlin { + compilerOptions.freeCompilerArgs.add("-Xexpect-actual-classes") + applyDefaultHierarchyTemplate() jvm() androidTarget { @OptIn(ExperimentalKotlinGradlePluginApi::class) @@ -59,7 +61,6 @@ kotlin { sourceSets.androidMain.dependencies { implementation("androidx.biometric:biometric:1.2.0-alpha05") } - } swiftklib { diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index bc0477292..55bf083c0 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -52,6 +52,7 @@ suspend fun SealedBox, authenticatedData: ByteArray = byteArrayOf() ) = catching { + @Suppress("UNCHECKED_CAST") (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( key.encryptionKey, key.macKey, @@ -84,7 +85,7 @@ suspend fun ,I: NonceTrait, K : KeyType> key.encryptionKey, key.macKey, authenticatedData ) } - + //compiler knows its exhaustive, but IDEA complains else -> throw ImplementationError("Authenticated Decryption") } } @@ -188,8 +189,8 @@ suspend fun > SymmetricKey = algorithm.sealedBox.from(encryptedData, authTag).transform { + @Suppress("UNCHECKED_CAST") it.decrypt( - @Suppress("UNCHECKED_CAST") this as SymmetricKey, NonceTrait.Without, *>, authenticatedData ) From 94359e1137d9ab71e5524c4ee1b92d7f58ef2fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Tue, 25 Feb 2025 07:25:19 +0100 Subject: [PATCH 20/28] fix jvm delcaration clash --- .../indispensable/symmetric/SealedBoxCreation.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt index 30926c566..43d9432bb 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SealedBoxCreation.kt @@ -144,7 +144,9 @@ sealed class SealedBoxBuilder, I : NonceTrait, out K : */ val SymmetricEncryptionAlgorithm.sealedBox: SealedBoxBuilder.WithNonce.Awaiting - get() = SealedBoxBuilder.WithNonce.Awaiting(this) + @JvmName("boxWithNonceUnauthenticated") get() = SealedBoxBuilder.WithNonce.Awaiting( + this + ) /** @@ -154,7 +156,7 @@ val SymmetricEncryptionAlgorithm.sealedBox: SealedBoxBuilder.Without - get() = SealedBoxBuilder.Without.Unauthenticated(this) + @JvmName("boxWithoutNonceUnauthenticated") get() = SealedBoxBuilder.Without.Unauthenticated(this) /** @@ -164,7 +166,7 @@ val SymmetricEncryptionAlgorithm> SymmetricEncryptionAlgorithm.sealedBox: SealedBoxBuilder.WithNonce.Awaiting - get() = SealedBoxBuilder.WithNonce.Awaiting(this) + @JvmName("boxWithNonceAuthenticated") get() = SealedBoxBuilder.WithNonce.Awaiting(this) // we need both this one and the next two to work around https://youtrack.jetbrains.com/issue/KT-75444 @@ -175,7 +177,7 @@ val > SymmetricEncryptionAl */ val SymmetricEncryptionAlgorithm, NonceTrait.Without, *>.sealedBox: SealedBoxBuilder.Without.Authenticated, *> - get() = SealedBoxBuilder.Without.Authenticated, KeyType>(this) + @JvmName("boxWithoutNonceAuthenticatedGeneric") get() = SealedBoxBuilder.Without.Authenticated, KeyType>(this) /** * Creates a [SealedBoxBuilder] matching this algorithm's characteristics: @@ -184,7 +186,7 @@ val SymmetricEncryptionAlgorithm, NonceTrait.Wit */ val SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.Integrated>.sealedBox: SealedBoxBuilder.Without.Authenticated, KeyType.Integrated> - get() = SealedBoxBuilder.Without.Authenticated, KeyType.Integrated>( + @JvmName("boxWithoutNonceAuthenticatedIntegrated") get() = SealedBoxBuilder.Without.Authenticated, KeyType.Integrated>( this ) @@ -195,7 +197,7 @@ val SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>.sealedBox: SealedBoxBuilder.Without.Authenticated, KeyType.WithDedicatedMacKey> - get() = SealedBoxBuilder.Without.Authenticated, KeyType.WithDedicatedMacKey>( + @JvmName("boxWithoutNonceAuthenticatedDedicated") get() = SealedBoxBuilder.Without.Authenticated, KeyType.WithDedicatedMacKey>( this ) From 8e3931e22e30f491f9419b8dd9ab2f56ed4583c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Tue, 25 Feb 2025 07:25:26 +0100 Subject: [PATCH 21/28] fix bogus import --- .../signum/indispensable/mac/MessageAuthenticationCode.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt index 488f30bb4..cc91a84c7 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/mac/MessageAuthenticationCode.kt @@ -1,6 +1,5 @@ package at.asitplus.signum.indispensable.mac -import at.asitplus.signum.indispensable.DataIntegrityAlgorithm import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.asn1.encoding.Asn1 From 1006e80701a1b75583793d694f4bcfdedc9558aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Tue, 25 Feb 2025 13:14:25 +0100 Subject: [PATCH 22/28] move key generarion diff --git a/indispensable/build.gradle.kts b/indispensable/build.gradle.kts index a82e9e05..5ffff7db 100644 --- a/indispensable/build.gradle.kts +++ b/indispensable/build.gradle.kts @@ -57,6 +57,7 @@ kotlin { api(libs.multibase) api(libs.bignum) implementation(project(":internals")) + api(libs.securerandom) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/KeyGen.kt similarity index 100% rename from supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt rename to indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/KeyGen.kt diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index 019eab05..83e5bef5 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -49,7 +49,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key ) : Integrated(algorithm, additionalProperties, secretKey), SymmetricKey.Authenticating { - class RequiringNonce( + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() @@ -57,7 +57,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key algorithm, additionalProperties, secretKey ), SymmetricKey.RequiringNonce - class WithoutNonce( + class WithoutNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() @@ -72,13 +72,13 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key secretKey: ByteArray ) : Integrated(algorithm, additionalProperties, secretKey), SymmetricKey.NonAuthenticating { - class RequiringNonce( + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() ) : NonAuthenticating(algorithm, additionalProperties, secretKey) - class WithoutNonce( + class WithoutNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() @@ -121,7 +121,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key val macKey: ByteArray ) : SymmetricKey, I, KeyType.WithDedicatedMacKey>, SymmetricKey.Authenticating, I, KeyType.WithDedicatedMacKey> { - class RequiringNonce( + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, secretKey: ByteArray, dedicatedMacKey: ByteArray, @@ -131,15 +131,14 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key ), SymmetricKey.RequiringNonce, KeyType.WithDedicatedMacKey> - class WithoutNonce( + class WithoutNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>, secretKey: ByteArray, dedicatedMacKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() ) : WithDedicatedMac( algorithm, additionalProperties, secretKey, dedicatedMacKey - ), - SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> + ), SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/supreme/build.gradle.kts b/supreme/build.gradle.kts index 6356473c..e8c1dd2e 100644 --- a/supreme/build.gradle.kts +++ b/supreme/build.gradle.kts @@ -55,7 +55,6 @@ kotlin { implementation(project(":internals")) implementation(coroutines()) implementation(napier()) - implementation(libs.securerandom) } sourceSets.androidMain.dependencies { --- indispensable/build.gradle.kts | 1 + .../signum/indispensable}/symmetric/KeyGen.kt | 0 .../indispensable/symmetric/SymmetricKey.kt | 15 +++++++-------- supreme/build.gradle.kts | 1 - 4 files changed, 8 insertions(+), 9 deletions(-) rename {supreme/src/commonMain/kotlin/at/asitplus/signum/supreme => indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable}/symmetric/KeyGen.kt (100%) diff --git a/indispensable/build.gradle.kts b/indispensable/build.gradle.kts index a82e9e05b..5ffff7dbf 100644 --- a/indispensable/build.gradle.kts +++ b/indispensable/build.gradle.kts @@ -57,6 +57,7 @@ kotlin { api(libs.multibase) api(libs.bignum) implementation(project(":internals")) + api(libs.securerandom) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/KeyGen.kt similarity index 100% rename from supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/KeyGen.kt rename to indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/KeyGen.kt diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index 019eab05d..83e5bef59 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -49,7 +49,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key ) : Integrated(algorithm, additionalProperties, secretKey), SymmetricKey.Authenticating { - class RequiringNonce( + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() @@ -57,7 +57,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key algorithm, additionalProperties, secretKey ), SymmetricKey.RequiringNonce - class WithoutNonce( + class WithoutNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() @@ -72,13 +72,13 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key secretKey: ByteArray ) : Integrated(algorithm, additionalProperties, secretKey), SymmetricKey.NonAuthenticating { - class RequiringNonce( + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() ) : NonAuthenticating(algorithm, additionalProperties, secretKey) - class WithoutNonce( + class WithoutNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() @@ -121,7 +121,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key val macKey: ByteArray ) : SymmetricKey, I, KeyType.WithDedicatedMacKey>, SymmetricKey.Authenticating, I, KeyType.WithDedicatedMacKey> { - class RequiringNonce( + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, secretKey: ByteArray, dedicatedMacKey: ByteArray, @@ -131,15 +131,14 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key ), SymmetricKey.RequiringNonce, KeyType.WithDedicatedMacKey> - class WithoutNonce( + class WithoutNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>, secretKey: ByteArray, dedicatedMacKey: ByteArray, additionalProperties: MutableMap = mutableMapOf() ) : WithDedicatedMac( algorithm, additionalProperties, secretKey, dedicatedMacKey - ), - SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> + ), SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/supreme/build.gradle.kts b/supreme/build.gradle.kts index 6356473ce..e8c1dd2e9 100644 --- a/supreme/build.gradle.kts +++ b/supreme/build.gradle.kts @@ -55,7 +55,6 @@ kotlin { implementation(project(":internals")) implementation(coroutines()) implementation(napier()) - implementation(libs.securerandom) } sourceSets.androidMain.dependencies { From 8f8f4fb9f26131f31ee6afa8d2072c02035c5e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Tue, 25 Feb 2025 13:32:29 +0100 Subject: [PATCH 23/28] fix ctor visibility --- .../signum/indispensable/symmetric/SymmetricKey.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index 83e5bef59..72bd78f51 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -1,5 +1,6 @@ package at.asitplus.signum.indispensable.symmetric +import at.asitplus.signum.HazardousMaterials import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -121,7 +122,8 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key val macKey: ByteArray ) : SymmetricKey, I, KeyType.WithDedicatedMacKey>, SymmetricKey.Authenticating, I, KeyType.WithDedicatedMacKey> { - class RequiringNonce internal constructor( + + class RequiringNonce @HazardousMaterials("This constructor is public to enable testing. DO NOT USE IT!") constructor( algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, secretKey: ByteArray, dedicatedMacKey: ByteArray, @@ -138,7 +140,8 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key additionalProperties: MutableMap = mutableMapOf() ) : WithDedicatedMac( algorithm, additionalProperties, secretKey, dedicatedMacKey - ), SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> + ), + SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> override fun equals(other: Any?): Boolean { if (this === other) return true From 96983151c9fc06ba1601177da5ac2ec75db78e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Tue, 25 Feb 2025 13:58:48 +0100 Subject: [PATCH 24/28] move JCA extensions --- .../signum/indispensable/JcaExtensions.kt | 21 +++++++++++++++++++ .../signum/supreme/symmetric/Encryptor.jca.kt | 18 ---------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt index 273e91aa5..a4a6c1a00 100644 --- a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt +++ b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt @@ -2,11 +2,13 @@ package at.asitplus.signum.indispensable import at.asitplus.KmmResult import at.asitplus.catching +import at.asitplus.signum.HazardousMaterials import at.asitplus.signum.indispensable.asn1.toAsn1Integer import at.asitplus.signum.indispensable.asn1.toJavaBigInteger import at.asitplus.signum.indispensable.mac.HMAC import at.asitplus.signum.internals.isAndroid import at.asitplus.signum.indispensable.pki.X509Certificate +import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm import com.ionspin.kotlin.bignum.integer.base63.toJavaBigInteger import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex @@ -268,3 +270,22 @@ fun ECPrivateKey.toCryptoPrivateKey(): KmmResult = CryptoPrivateKey.RSA.decodeFromDerSafe(encoded) + + +val SymmetricEncryptionAlgorithm<*, *, *>.jcaName: String + @OptIn(HazardousMaterials::class) + get() = when (this) { + is SymmetricEncryptionAlgorithm.AES.GCM -> "AES/GCM/NoPadding" + is SymmetricEncryptionAlgorithm.AES.CBC<*, *> -> "AES/CBC/PKCS5Padding" + is SymmetricEncryptionAlgorithm.AES.ECB -> "AES/ECB/PKCS5Padding" + is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394 -> "AESWrap" + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> "ChaCha20-Poly1305" + else -> TODO("$this is unsupported") + } + +val SymmetricEncryptionAlgorithm<*, *, *>.jcaKeySpec: String + get() = when (this) { + is SymmetricEncryptionAlgorithm.AES<*, *, *> -> "AES" + is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> "ChaCha20" + else -> TODO("$this keyspec is unsupported UNSUPPORTED") + } \ No newline at end of file diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt index 4d2309e20..f076315ef 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/Encryptor.jca.kt @@ -87,24 +87,6 @@ internal class JcaPlatformCipher, I : NonceTrait, K : } } -val SymmetricEncryptionAlgorithm<*, *, *>.jcaName: String - @OptIn(HazardousMaterials::class) - get() = when (this) { - is SymmetricEncryptionAlgorithm.AES.GCM -> "AES/GCM/NoPadding" - is SymmetricEncryptionAlgorithm.AES.CBC<*, *> -> "AES/CBC/PKCS5Padding" - is SymmetricEncryptionAlgorithm.AES.ECB -> "AES/ECB/PKCS5Padding" - is SymmetricEncryptionAlgorithm.AES.WRAP.RFC3394 -> "AESWrap" - is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> "ChaCha20-Poly1305" - else -> TODO("$this is unsupported") - } - -val SymmetricEncryptionAlgorithm<*, *, *>.jcaKeySpec: String - get() = when (this) { - is SymmetricEncryptionAlgorithm.AES<*, *, *> -> "AES" - is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> "ChaCha20" - else -> TODO("$this keyspec is unsupported UNSUPPORTED") - } - internal val PlatformCipher.Mode.jcaCipherMode get() = when (this) { PlatformCipher.Mode.ENCRYPT -> Cipher.ENCRYPT_MODE From fe3205a5b1c08c4f8351dc8039b1dca2c40fb842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Tue, 25 Feb 2025 14:05:11 +0100 Subject: [PATCH 25/28] more JCA extensions --- .../at/asitplus/signum/indispensable/JcaExtensions.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt index a4a6c1a00..a1f96ab0b 100644 --- a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt +++ b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt @@ -288,4 +288,11 @@ val SymmetricEncryptionAlgorithm<*, *, *>.jcaKeySpec: String is SymmetricEncryptionAlgorithm.AES<*, *, *> -> "AES" is SymmetricEncryptionAlgorithm.ChaCha20Poly1305 -> "ChaCha20" else -> TODO("$this keyspec is unsupported UNSUPPORTED") - } \ No newline at end of file + } + +val HMAC.jcaName: String get() = when(this) { + HMAC.SHA1 -> "HmacSHA1" + HMAC.SHA256 -> "HmacSHA256" + HMAC.SHA384 -> "HmacSHA384" + HMAC.SHA512 -> "HmacSHA512" +} \ No newline at end of file From 6f1a7a36457c4cac4cd31acf51dac310c76763e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Wed, 26 Feb 2025 09:50:15 +0100 Subject: [PATCH 26/28] Make secret keys secret and prepare interfaces for HW-backed symmetric diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4f65a0..8d74702b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ * `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 diff --git a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt index 6bff9f32..64bb9d6a 100644 --- a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt +++ b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt @@ -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 diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SecretExposure.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SecretExposure.kt new file mode 100644 index 00000000..fb8c8a21 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SecretExposure.kt @@ -0,0 +1,5 @@ +package at.asitplus.signum.indispensable + +@RequiresOptIn(message = "Access to secret and private key material requires explicit opt-in. Specify @OptIn(SecretExposure::class). Make sure that you actually want to externalise a secret. Check yourself, before you really, really, wreck yourself!") +/** This guards a secret. Do not expose it lightly! */ +annotation class SecretExposure diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index 72bd78f5..0666e8e2 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -1,6 +1,8 @@ package at.asitplus.signum.indispensable.symmetric +import at.asitplus.KmmResult import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.SecretExposure import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -32,24 +34,47 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key /** * Self-Contained encryption key, i.e. a single byte array is sufficient */ - sealed class Integrated, I : NonceTrait> - protected constructor( - override val algorithm: SymmetricEncryptionAlgorithm, + sealed interface Integrated, I : NonceTrait> : + SymmetricKey { + + + override val algorithm: SymmetricEncryptionAlgorithm - override val additionalProperties: MutableMap = mutableMapOf(), /** * The actual encryption key bytes */ - val secretKey: ByteArray - ) : - SymmetricKey { + @SecretExposure + val secretKey: KmmResult + + sealed class Authenticating( - algorithm: SymmetricEncryptionAlgorithm, - additionalProperties: MutableMap = mutableMapOf(), + override val algorithm: SymmetricEncryptionAlgorithm, + override val additionalProperties: MutableMap = mutableMapOf(), secretKey: ByteArray - ) : Integrated(algorithm, additionalProperties, secretKey), + ) : Integrated, SymmetricKey.Authenticating { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Integrated<*, *>) return false + + if (algorithm != other.algorithm) return false + @OptIn(SecretExposure::class) + if (!secretKey.getOrNull().contentEquals(other.secretKey.getOrNull())) return false + + return true + } + + override fun hashCode(): Int { + var result = algorithm.hashCode() + @OptIn(SecretExposure::class) + result = 31 * result + secretKey.getOrNull().contentHashCode() + return result + } + + @SecretExposure + override val secretKey = KmmResult.success(secretKey) + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, @@ -68,11 +93,34 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key } sealed class NonAuthenticating( - algorithm: SymmetricEncryptionAlgorithm, - additionalProperties: MutableMap = mutableMapOf(), + override val algorithm: SymmetricEncryptionAlgorithm, + override val additionalProperties: MutableMap = mutableMapOf(), secretKey: ByteArray - ) : Integrated(algorithm, additionalProperties, secretKey), + ) : Integrated, SymmetricKey.NonAuthenticating { + + @SecretExposure + override val secretKey = KmmResult.success(secretKey) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Integrated<*, *>) return false + + if (algorithm != other.algorithm) return false + @OptIn(SecretExposure::class) + if (!secretKey.getOrNull().contentEquals(other.secretKey.getOrNull())) return false + + return true + } + + override fun hashCode(): Int { + var result = algorithm.hashCode() + @OptIn(SecretExposure::class) + result = 31 * result + secretKey.getOrNull().contentHashCode() + return result + } + + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, @@ -86,21 +134,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key ) : NonAuthenticating(algorithm, additionalProperties, secretKey) } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Integrated<*, *>) return false - if (algorithm != other.algorithm) return false - if (!secretKey.contentEquals(other.secretKey)) return false - - return true - } - - override fun hashCode(): Int { - var result = algorithm.hashCode() - result = 31 * result + secretKey.contentHashCode() - return result - } } @@ -108,58 +142,116 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key * Encryption key with dedicated MAC key. Used for non-authenticated ciphers that use an external MAC function to * bolt on AEAD capabilities, such as [SymmetricEncryptionAlgorithm.AES.GCM] */ - sealed class WithDedicatedMac - protected constructor( - override val algorithm: SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>, - override val additionalProperties: MutableMap = mutableMapOf(), + sealed interface WithDedicatedMac + : SymmetricKey, I, KeyType.WithDedicatedMacKey>, + SymmetricKey.Authenticating, I, KeyType.WithDedicatedMacKey> { + + + override val algorithm: SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey> + /** * The actual encryption key bytes */ - val encryptionKey: ByteArray, + @SecretExposure + val encryptionKey: KmmResult + /** * The actual dedicated MAC key bytes */ - val macKey: ByteArray - ) : SymmetricKey, I, KeyType.WithDedicatedMacKey>, - SymmetricKey.Authenticating, I, KeyType.WithDedicatedMacKey> { + @SecretExposure + val macKey: KmmResult + class RequiringNonce @HazardousMaterials("This constructor is public to enable testing. DO NOT USE IT!") constructor( - algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, - secretKey: ByteArray, + override val algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, + encryptionKey: ByteArray, dedicatedMacKey: ByteArray, - additionalProperties: MutableMap = mutableMapOf() - ) : WithDedicatedMac( - algorithm, additionalProperties, secretKey, dedicatedMacKey - ), - SymmetricKey.RequiringNonce, KeyType.WithDedicatedMacKey> + override val additionalProperties: MutableMap = mutableMapOf() + ) : WithDedicatedMac, + SymmetricKey.RequiringNonce, KeyType.WithDedicatedMacKey> { + /** + * The actual encryption key bytes + * + * This will fail for hardware-backed keys! + */ + @SecretExposure + override val encryptionKey: KmmResult = KmmResult.success(encryptionKey) - class WithoutNonce internal constructor( - algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>, - secretKey: ByteArray, - dedicatedMacKey: ByteArray, - additionalProperties: MutableMap = mutableMapOf() - ) : WithDedicatedMac( - algorithm, additionalProperties, secretKey, dedicatedMacKey - ), - SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> + /** + * The actual dedicated MAC key bytes + * + * This will fail for hardware-backed keys! + */ + @SecretExposure + override val macKey: KmmResult = KmmResult.success(dedicatedMacKey) - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is WithDedicatedMac<*>) return false + @OptIn(SecretExposure::class) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WithDedicatedMac<*>) return false + + if (algorithm != other.algorithm) return false + if (!encryptionKey.getOrNull().contentEquals(other.encryptionKey.getOrNull())) return false + if (!macKey.getOrNull().contentEquals(other.macKey.getOrNull())) return false + if (additionalProperties != other.additionalProperties) return false + + return true + } + + @OptIn(SecretExposure::class) + override fun hashCode(): Int { + var result = algorithm.hashCode() + result = 31 * result + encryptionKey.getOrNull().contentHashCode() + result = 31 * result + macKey.getOrNull().contentHashCode() + return result + } - if (algorithm != other.algorithm) return false - if (!encryptionKey.contentEquals(other.encryptionKey)) return false - if (!macKey.contentEquals(other.macKey)) return false - if (additionalProperties != other.additionalProperties) return false - return true } - override fun hashCode(): Int { - var result = algorithm.hashCode() - result = 31 * result + encryptionKey.contentHashCode() - result = 31 * result + macKey.contentHashCode() - return result + class WithoutNonce internal constructor( + override val algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>, + encryptionKey: ByteArray, + dedicatedMacKey: ByteArray, + override val additionalProperties: MutableMap = mutableMapOf() + ) : WithDedicatedMac, + SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> { + /** + * The actual encryption key bytes + * + * This will fail for hardware-backed keys! + */ + @SecretExposure + override val encryptionKey: KmmResult = KmmResult.success(encryptionKey) + + /** + * The actual dedicated MAC key bytes + * + * This will fail for hardware-backed keys! + */ + @SecretExposure + override val macKey: KmmResult = KmmResult.success(dedicatedMacKey) + + @OptIn(SecretExposure::class) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WithDedicatedMac<*>) return false + + if (algorithm != other.algorithm) return false + if (!encryptionKey.getOrNull().contentEquals(other.encryptionKey.getOrNull())) return false + if (!macKey.getOrNull().contentEquals(other.macKey.getOrNull())) return false + if (additionalProperties != other.additionalProperties) return false + + return true + } + + @OptIn(SecretExposure::class) + override fun hashCode(): Int { + var result = algorithm.hashCode() + result = 31 * result + encryptionKey.getOrNull().contentHashCode() + result = 31 * result + macKey.getOrNull().contentHashCode() + return result + } } } } @@ -206,5 +298,24 @@ fun , K : KeyType> SymmetricKey.requiresNonce(): /** * The actual encryption key bytes + * + * This will fail for hardware-backed keys! */ +@SecretExposure val , I : NonceTrait> SymmetricKey.secretKey get() = (this as SymmetricKey.Integrated).secretKey + +/** + * The encryption key bytes, if present. + * + * This will fail for hardware-backed keys! + */ +@SecretExposure +val , I : NonceTrait> SymmetricKey.encryptionKey get() = (this as SymmetricKey.WithDedicatedMac).encryptionKey + +/** + * The dedicated MAC key bytes, if present. + * + * This will fail for hardware-backed keys! + */ +@SecretExposure +val , I : NonceTrait> SymmetricKey.macKey get() = (this as SymmetricKey.WithDedicatedMac).macKey diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt index 0e1631c8..213554bc 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt @@ -19,7 +19,7 @@ import at.asitplus.signum.indispensable.asn1.Asn1StructuralException import at.asitplus.signum.indispensable.pki.X509Certificate import at.asitplus.signum.indispensable.pki.leaf import at.asitplus.signum.supreme.AppLifecycleMonitor -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.SignatureResult import at.asitplus.signum.supreme.UnlockFailed import at.asitplus.signum.supreme.UnsupportedCryptoException diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 2707500b..f422622b 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -14,7 +14,7 @@ import at.asitplus.signum.indispensable.parseFromJca import at.asitplus.signum.indispensable.toCryptoPrivateKey import at.asitplus.signum.indispensable.toCryptoPublicKey import at.asitplus.signum.indispensable.toJcaPublicKey -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.signCatching import com.ionspin.kotlin.bignum.integer.base63.toJavaBigInteger import java.security.KeyPair diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt index 9ca25016..fdb4e833 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt @@ -1,10 +1,5 @@ package at.asitplus.signum.supreme - -@RequiresOptIn(message = "Access to secret and private key material requires explicit opt-in. Specify @OptIn(SecretExposure::class). Make sure that you actually want to externalise a secret. Check yourself, before you really, really, wreck yourself!") -/** This guards a secret. Do not expose it lightly! */ -annotation class SecretExposure - sealed class CryptoException(message: String? = null, cause: Throwable? = null) : Throwable(message, cause) open class CryptoOperationFailed(message: String) : CryptoException(message) open class UnsupportedCryptoException(message: String? = null, cause: Throwable? = null) : CryptoException(message, cause) diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeys.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeys.kt index d4985170..2a88b71c 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeys.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeys.kt @@ -8,7 +8,7 @@ import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm import at.asitplus.signum.indispensable.nativeDigest -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.dsl.DSL import at.asitplus.signum.supreme.dsl.DSLConfigureFn import at.asitplus.signum.supreme.os.SignerConfiguration diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt index d12ce4e4..64c71848 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt @@ -5,7 +5,7 @@ import at.asitplus.catching import at.asitplus.signum.indispensable.* import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.SignatureResult import at.asitplus.signum.supreme.agree.UsableECDHPrivateValue import at.asitplus.signum.supreme.dsl.DSL diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index 55bf083c..e0113a55 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -3,6 +3,7 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.ImplementationError +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated @@ -22,18 +23,21 @@ suspend fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey, byteArrayOf() + @OptIn(SecretExposure::class) (key as SymmetricKey.Integrated).secretKey.getOrThrow(), + byteArrayOf() ) is Authenticated.WithDedicatedMac<*, *> -> { key as SymmetricKey.WithDedicatedMac (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( - key.encryptionKey, key.macKey, byteArrayOf() + @OptIn(SecretExposure::class) key.encryptionKey.getOrThrow(), + @OptIn(SecretExposure::class) key.macKey.getOrThrow(), + byteArrayOf() ) } is AuthCapability.Unauthenticated -> (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey + @OptIn(SecretExposure::class) (key as SymmetricKey.Integrated).secretKey.getOrThrow() ) } } @@ -54,8 +58,10 @@ suspend fun SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( - key.encryptionKey, - key.macKey, + @OptIn(SecretExposure::class) + key.encryptionKey.getOrThrow(), + @OptIn(SecretExposure::class) + key.macKey.getOrThrow(), authenticatedData ) } @@ -68,7 +74,7 @@ suspend fun SealedBox,I: NonceTrait, K : KeyType> SealedBox.decrypt( +suspend fun , I : NonceTrait, K : KeyType> SealedBox.decrypt( key: SymmetricKey, authenticatedData: ByteArray = byteArrayOf() ): KmmResult = catching { @@ -76,13 +82,16 @@ suspend fun ,I: NonceTrait, K : KeyType> @Suppress("UNCHECKED_CAST") when (algorithm.authCapability) { is Authenticated.Integrated -> (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey, authenticatedData + @OptIn(SecretExposure::class) (key as SymmetricKey.Integrated).secretKey.getOrThrow(), + authenticatedData ) is Authenticated.WithDedicatedMac<*, *> -> { key as SymmetricKey.WithDedicatedMac (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( - key.encryptionKey, key.macKey, authenticatedData + @OptIn(SecretExposure::class) key.encryptionKey.getOrThrow(), + @OptIn(SecretExposure::class) key.macKey.getOrThrow(), + authenticatedData ) } //compiler knows its exhaustive, but IDEA complains @@ -104,7 +113,7 @@ suspend fun SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey + @OptIn(SecretExposure::class) (key as SymmetricKey.Integrated).secretKey.getOrThrow() ) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt index 714b45d2..ab660fce 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt @@ -3,6 +3,7 @@ package at.asitplus.signum.supreme.symmetric.discouraged import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.SymmetricKey.WithDedicatedMac import at.asitplus.signum.supreme.symmetric.Encryptor @@ -25,13 +26,13 @@ suspend fun > KeyWithNonceA data: ByteArray, authenticatedData: ByteArray? = null ): KmmResult> = catching { - Encryptor( + @OptIn(SecretExposure::class) Encryptor( second.algorithm, - if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).encryptionKey else (second as SymmetricKey.Integrated).secretKey, - if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).macKey else (second as SymmetricKey.Integrated).secretKey, + if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).encryptionKey.getOrThrow() else (second as SymmetricKey.Integrated).secretKey.getOrThrow(), + if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).macKey.getOrThrow() else (second as SymmetricKey.Integrated).secretKey.getOrThrow(), first, authenticatedData, - ).encrypt(data) as SealedBox + ).encrypt(data) as SealedBox } /** @@ -48,10 +49,10 @@ suspend fun > KeyWithNonceA suspend fun > KeyWithNonce.encrypt( data: ByteArray ): KmmResult> = catching { - Encryptor( + @OptIn(SecretExposure::class) Encryptor( first.algorithm, - if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).encryptionKey else (first as SymmetricKey.Integrated).secretKey, - if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).macKey else null, + if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).encryptionKey.getOrThrow() else (first as SymmetricKey.Integrated).secretKey.getOrThrow(), + if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).macKey.getOrThrow() else null, second, null, ).encrypt(data) as SealedBox.WithNonce diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt index cf74c028..1a047962 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt @@ -2,6 +2,7 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.KmmResult import at.asitplus.catching +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.symmetric.* import kotlin.jvm.JvmName @@ -15,10 +16,10 @@ import kotlin.jvm.JvmName suspend fun , I : NonceTrait> SymmetricKey.encrypt( data: ByteArray ): KmmResult> = catching { - Encryptor( + @OptIn(SecretExposure::class) Encryptor( algorithm, - if (this.hasDedicatedMacKey()) encryptionKey else secretKey, - if (this.hasDedicatedMacKey()) macKey else null, + if (this.hasDedicatedMacKey()) encryptionKey.getOrThrow() else secretKey.getOrThrow(), + if (this.hasDedicatedMacKey()) macKey.getOrThrow() else null, aad = null ).encrypt(data) } @@ -40,10 +41,10 @@ suspend fun , I : NonceTrai data: ByteArray, authenticatedData: ByteArray? = null ): KmmResult> = catching { - Encryptor( + @OptIn(SecretExposure::class) Encryptor( algorithm, - if (this.hasDedicatedMacKey()) encryptionKey else secretKey, - if (this.hasDedicatedMacKey()) macKey else null, + if (this.hasDedicatedMacKey()) encryptionKey.getOrThrow() else secretKey.getOrThrow(), + if (this.hasDedicatedMacKey()) macKey.getOrThrow() else null, aad = authenticatedData, ).encrypt(data) } diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt index d291e0fe..6c18f0f4 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt @@ -5,7 +5,7 @@ import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.pki.* import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.os.PlatformSigningKeyConfigurationBase import at.asitplus.signum.supreme.os.SignerConfiguration import at.asitplus.signum.supreme.sign diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/PrivateKeyCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/PrivateKeyCommonTests.kt index 7b5c8e06..44976daa 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/PrivateKeyCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/PrivateKeyCommonTests.kt @@ -2,7 +2,7 @@ package at.asitplus.signum.supreme.sign import at.asitplus.signum.indispensable.CryptoPrivateKey import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.isSuccess import at.asitplus.signum.supreme.signature import io.kotest.core.spec.style.FreeSpec diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 2ed268d4..76cd981a 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -1,4 +1,7 @@ +@file:OptIn(SecretExposure::class) + import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.asn1.encoding.encodeTo4Bytes import at.asitplus.signum.indispensable.mac.HMAC import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode @@ -43,7 +46,7 @@ class `00SymmetricTest` : FreeSpec({ val ciphertext = encrypted.encryptedData val authTag = encrypted.authTag val externalAAD = authenticatedData - val keyBytes = secretKey.secretKey + val keyBytes = secretKey.secretKey.getOrThrow() val preSharedKey = algo.keyFrom(keyBytes).getOrThrow() @@ -91,7 +94,7 @@ class `00SymmetricTest` : FreeSpec({ //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*/), + algorithm.keyFrom(key.encryptionKey.getOrThrow(), key.macKey.getOrThrow()).getOrThrow(/*handle error*/), aad ).getOrThrow(/*handle error*/) shouldBe payload //greatest success! } @@ -199,8 +202,8 @@ class `00SymmetricTest` : FreeSpec({ } shouldNot succeed val key = when (alg.hasDedicatedMac()) { - true -> alg.keyFrom(alg.randomKey().encryptionKey, alg.randomKey().encryptionKey) - false -> alg.keyFrom(alg.randomKey().secretKey) + true -> alg.keyFrom(alg.randomKey().encryptionKey.getOrThrow(), alg.randomKey().encryptionKey.getOrThrow()) + false -> alg.keyFrom(alg.randomKey().secretKey.getOrThrow()) }.getOrThrow() @@ -283,14 +286,14 @@ class `00SymmetricTest` : FreeSpec({ ).getOrThrow() val wrongWrongDecrypted = wrongCiphertext.decrypt(it.randomKey()) - withClue("KEY: ${key.secretKey.toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { + withClue("KEY: ${key.secretKey.getOrThrow().toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { //we're not authenticated, so from time to time, this succeeds //wrongWrongDecrypted shouldNot succeed //instead, we test differently: wrongWrongDecrypted.onSuccess { value -> value shouldNotBe plaintext } } val wrongRightDecrypted = wrongCiphertext.decrypt(key) - withClue("KEY: ${key.secretKey.toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { + withClue("KEY: ${key.secretKey.getOrThrow().toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { //we're not authenticated, so from time to time, this succeeds //wrongRightDecrypted shouldNot succeed //instead, we test differently: @@ -492,7 +495,7 @@ class `00SymmetricTest` : FreeSpec({ Random.Default.nextBytes(21257), ) { plaintext -> - val secretKey = it.randomKey().encryptionKey + val secretKey = it.randomKey().encryptionKey.getOrThrow() withData( nameFn = { "MAC KEY $it" }, @@ -521,8 +524,8 @@ class `00SymmetricTest` : FreeSpec({ val manilaKey = SymmetricKey.WithDedicatedMac.RequiringNonce( manilaAlg, - key.encryptionKey, - key.macKey + key.encryptionKey.getOrThrow(), + key.macKey.getOrThrow() ) if (iv != null) manilaKey.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext, aad) .getOrThrow() shouldNotBe ciphertext @@ -582,8 +585,8 @@ class `00SymmetricTest` : FreeSpec({ ).getOrThrow().decrypt( SymmetricKey.WithDedicatedMac.RequiringNonce( ciphertext.algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, - key.encryptionKey, - dedicatedMacKey = key.macKey.asList().shuffled().toByteArray() + key.encryptionKey.getOrThrow(), + dedicatedMacKey = key.macKey.getOrThrow().asList().shuffled().toByteArray() ), aad ?: byteArrayOf() ) shouldNot succeed @@ -606,8 +609,8 @@ class `00SymmetricTest` : FreeSpec({ }.let { SymmetricKey.WithDedicatedMac.RequiringNonce( it, - key.encryptionKey, - key.macKey + key.encryptionKey.getOrThrow(), + key.macKey.getOrThrow() ) }, aad ?: byteArrayOf()) shouldNot succeed } @@ -737,27 +740,27 @@ class `00SymmetricTest` : FreeSpec({ when (alg.hasDedicatedMac()) { true -> { key shouldBe alg.keyFrom( - (key as SymmetricKey.WithDedicatedMac).encryptionKey, - (key as SymmetricKey.WithDedicatedMac<*>).macKey + (key as SymmetricKey.WithDedicatedMac).encryptionKey.getOrThrow(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.getOrThrow() ).getOrThrow() key shouldNotBe alg.keyFrom( - key.encryptionKey, - (key as SymmetricKey.WithDedicatedMac<*>).macKey.asList().shuffled() + key.encryptionKey.getOrThrow(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.getOrThrow().asList().shuffled() .toByteArray() ).getOrThrow() key shouldNotBe alg.keyFrom( - key.encryptionKey.asList().shuffled().toByteArray(), - (key as SymmetricKey.WithDedicatedMac<*>).macKey + key.encryptionKey.getOrThrow().asList().shuffled().toByteArray(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.getOrThrow() ).getOrThrow() key shouldNotBe alg.keyFrom( - key.encryptionKey.asList().shuffled().toByteArray(), - (key as SymmetricKey.WithDedicatedMac<*>).macKey.asList().shuffled() + key.encryptionKey.getOrThrow().asList().shuffled().toByteArray(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.getOrThrow().asList().shuffled() .toByteArray() ).getOrThrow() } - false -> key shouldBe alg.keyFrom((key as SymmetricKey.Integrated).secretKey).getOrThrow() + false -> key shouldBe alg.keyFrom((key as SymmetricKey.Integrated).secretKey.getOrThrow()).getOrThrow() } } @@ -810,14 +813,14 @@ class `00SymmetricTest` : FreeSpec({ if (alg.hasDedicatedMac() && wrongAlg.hasDedicatedMac()) { alg.randomKey().let { key -> wrongAlg.keyFrom( - key.encryptionKey, - key.macKey /*size will not match, but it will get us a valid key*/ + key.encryptionKey.getOrThrow(), + key.macKey.getOrThrow() /*size will not match, but it will get us a valid key*/ ).getOrThrow() shouldNotBe key } } else if (!wrongAlg.hasDedicatedMac() && !alg.hasDedicatedMac()) { alg.randomKey().let { key -> wrongAlg.keyFrom( - key.secretKey, + key.secretKey.getOrThrow(), ).getOrThrow() shouldNotBe key } } @@ -933,7 +936,7 @@ class `00SymmetricTest` : FreeSpec({ true -> { alg.keyFrom( Random.nextBytes(sz), - alg.randomKey().encryptionKey /*mac key should not trigger, as it is unconstrained*/ + alg.randomKey().encryptionKey.getOrThrow() /*mac key should not trigger, as it is unconstrained*/ ) } diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 84cfb311..dbb2a8bc 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -3,7 +3,7 @@ package at.asitplus.signum.supreme.sign import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.* -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.signCatching import com.ionspin.kotlin.bignum.integer.base63.toJavaBigInteger import java.security.KeyPair diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index db68cf2c..f8d90fb1 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -1,5 +1,8 @@ +@file:OptIn(SecretExposure::class) + import at.asitplus.catching import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.supreme.succeed import at.asitplus.signum.supreme.symmetric.decrypt @@ -71,7 +74,7 @@ class JvmSymmetricTest : FreeSpec({ own.isAuthenticated() shouldBe true jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), GCMParameterSpec( alg.authCapability.tagLength.bits.toInt(), own.nonce/*use our own auto-generated IV*/ @@ -85,7 +88,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), GCMParameterSpec( alg.authCapability.tagLength.bits.toInt(), own.nonce/*use our own auto-generated IV*/ @@ -116,7 +119,7 @@ class JvmSymmetricTest : FreeSpec({ val own = secretKey.encrypt(data).getOrThrow() jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), IvParameterSpec(own.nonce)/*use our own auto-generated IV, if null iv was provided*/ ) val encrypted = jcaCipher.doFinal(data) @@ -125,7 +128,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), IvParameterSpec(own.nonce)/*use our own auto-generated IV, if null iv was provided*/ ) own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) @@ -185,7 +188,7 @@ class JvmSymmetricTest : FreeSpec({ val own = secretKey.encrypt(data).getOrThrow() jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), ) val encrypted = jcaCipher.doFinal(data) @@ -193,7 +196,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), ) own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) @@ -217,7 +220,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), ) val jcaTrail = catching { jcaCipher.doFinal(data) @@ -233,7 +236,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), ) own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) @@ -277,7 +280,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "ChaCha"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "ChaCha"), IvParameterSpec(box.nonce) /*need to do this, otherwise we get a random nonce*/ ) --- CHANGELOG.md | 2 + .../kotlin/at/asitplus/cryptotest/App.kt | 2 +- .../signum/indispensable/SecretExposure.kt | 5 + .../indispensable/symmetric/SymmetricKey.kt | 243 +++++++++++++----- .../supreme/os/AndroidKeyStoreProvider.kt | 2 +- .../signum/supreme/sign/EphemeralKeysImpl.kt | 2 +- .../at/asitplus/signum/supreme/Throwables.kt | 5 - .../signum/supreme/sign/EphemeralKeys.kt | 2 +- .../at/asitplus/signum/supreme/sign/Signer.kt | 2 +- .../signum/supreme/symmetric/decrypt.kt | 27 +- .../supreme/symmetric/discouraged/encrypt.kt | 15 +- .../signum/supreme/symmetric/encrypt.kt | 13 +- .../sign/EphemeralSignerCommonTests.kt | 2 +- .../supreme/sign/PrivateKeyCommonTests.kt | 2 +- .../supreme/symmetric/00SymmetricTest.kt | 55 ++-- .../signum/supreme/sign/EphemeralKeysImpl.kt | 2 +- .../asitplus/signum/supreme/symmetric/Test.kt | 21 +- 17 files changed, 266 insertions(+), 136 deletions(-) create mode 100644 indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SecretExposure.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4f65a05..8d74702b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ * `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 diff --git a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt index 6bff9f32e..64bb9d6a4 100644 --- a/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt +++ b/demoapp/composeApp/src/commonMain/kotlin/at/asitplus/cryptotest/App.kt @@ -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 diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SecretExposure.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SecretExposure.kt new file mode 100644 index 000000000..fb8c8a215 --- /dev/null +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/SecretExposure.kt @@ -0,0 +1,5 @@ +package at.asitplus.signum.indispensable + +@RequiresOptIn(message = "Access to secret and private key material requires explicit opt-in. Specify @OptIn(SecretExposure::class). Make sure that you actually want to externalise a secret. Check yourself, before you really, really, wreck yourself!") +/** This guards a secret. Do not expose it lightly! */ +annotation class SecretExposure diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt index 72bd78f51..0666e8e2e 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/symmetric/SymmetricKey.kt @@ -1,6 +1,8 @@ package at.asitplus.signum.indispensable.symmetric +import at.asitplus.KmmResult import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.SecretExposure import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -32,24 +34,47 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key /** * Self-Contained encryption key, i.e. a single byte array is sufficient */ - sealed class Integrated, I : NonceTrait> - protected constructor( - override val algorithm: SymmetricEncryptionAlgorithm, + sealed interface Integrated, I : NonceTrait> : + SymmetricKey { + + + override val algorithm: SymmetricEncryptionAlgorithm - override val additionalProperties: MutableMap = mutableMapOf(), /** * The actual encryption key bytes */ - val secretKey: ByteArray - ) : - SymmetricKey { + @SecretExposure + val secretKey: KmmResult + + sealed class Authenticating( - algorithm: SymmetricEncryptionAlgorithm, - additionalProperties: MutableMap = mutableMapOf(), + override val algorithm: SymmetricEncryptionAlgorithm, + override val additionalProperties: MutableMap = mutableMapOf(), secretKey: ByteArray - ) : Integrated(algorithm, additionalProperties, secretKey), + ) : Integrated, SymmetricKey.Authenticating { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Integrated<*, *>) return false + + if (algorithm != other.algorithm) return false + @OptIn(SecretExposure::class) + if (!secretKey.getOrNull().contentEquals(other.secretKey.getOrNull())) return false + + return true + } + + override fun hashCode(): Int { + var result = algorithm.hashCode() + @OptIn(SecretExposure::class) + result = 31 * result + secretKey.getOrNull().contentHashCode() + return result + } + + @SecretExposure + override val secretKey = KmmResult.success(secretKey) + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, @@ -68,11 +93,34 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key } sealed class NonAuthenticating( - algorithm: SymmetricEncryptionAlgorithm, - additionalProperties: MutableMap = mutableMapOf(), + override val algorithm: SymmetricEncryptionAlgorithm, + override val additionalProperties: MutableMap = mutableMapOf(), secretKey: ByteArray - ) : Integrated(algorithm, additionalProperties, secretKey), + ) : Integrated, SymmetricKey.NonAuthenticating { + + @SecretExposure + override val secretKey = KmmResult.success(secretKey) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Integrated<*, *>) return false + + if (algorithm != other.algorithm) return false + @OptIn(SecretExposure::class) + if (!secretKey.getOrNull().contentEquals(other.secretKey.getOrNull())) return false + + return true + } + + override fun hashCode(): Int { + var result = algorithm.hashCode() + @OptIn(SecretExposure::class) + result = 31 * result + secretKey.getOrNull().contentHashCode() + return result + } + + class RequiringNonce internal constructor( algorithm: SymmetricEncryptionAlgorithm, secretKey: ByteArray, @@ -86,21 +134,7 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key ) : NonAuthenticating(algorithm, additionalProperties, secretKey) } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Integrated<*, *>) return false - if (algorithm != other.algorithm) return false - if (!secretKey.contentEquals(other.secretKey)) return false - - return true - } - - override fun hashCode(): Int { - var result = algorithm.hashCode() - result = 31 * result + secretKey.contentHashCode() - return result - } } @@ -108,58 +142,116 @@ sealed interface SymmetricKey, I : NonceTrait, K : Key * Encryption key with dedicated MAC key. Used for non-authenticated ciphers that use an external MAC function to * bolt on AEAD capabilities, such as [SymmetricEncryptionAlgorithm.AES.GCM] */ - sealed class WithDedicatedMac - protected constructor( - override val algorithm: SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey>, - override val additionalProperties: MutableMap = mutableMapOf(), + sealed interface WithDedicatedMac + : SymmetricKey, I, KeyType.WithDedicatedMacKey>, + SymmetricKey.Authenticating, I, KeyType.WithDedicatedMacKey> { + + + override val algorithm: SymmetricEncryptionAlgorithm, I, KeyType.WithDedicatedMacKey> + /** * The actual encryption key bytes */ - val encryptionKey: ByteArray, + @SecretExposure + val encryptionKey: KmmResult + /** * The actual dedicated MAC key bytes */ - val macKey: ByteArray - ) : SymmetricKey, I, KeyType.WithDedicatedMacKey>, - SymmetricKey.Authenticating, I, KeyType.WithDedicatedMacKey> { + @SecretExposure + val macKey: KmmResult + class RequiringNonce @HazardousMaterials("This constructor is public to enable testing. DO NOT USE IT!") constructor( - algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, - secretKey: ByteArray, + override val algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, + encryptionKey: ByteArray, dedicatedMacKey: ByteArray, - additionalProperties: MutableMap = mutableMapOf() - ) : WithDedicatedMac( - algorithm, additionalProperties, secretKey, dedicatedMacKey - ), - SymmetricKey.RequiringNonce, KeyType.WithDedicatedMacKey> + override val additionalProperties: MutableMap = mutableMapOf() + ) : WithDedicatedMac, + SymmetricKey.RequiringNonce, KeyType.WithDedicatedMacKey> { + /** + * The actual encryption key bytes + * + * This will fail for hardware-backed keys! + */ + @SecretExposure + override val encryptionKey: KmmResult = KmmResult.success(encryptionKey) + + /** + * The actual dedicated MAC key bytes + * + * This will fail for hardware-backed keys! + */ + @SecretExposure + override val macKey: KmmResult = KmmResult.success(dedicatedMacKey) + + @OptIn(SecretExposure::class) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WithDedicatedMac<*>) return false + + if (algorithm != other.algorithm) return false + if (!encryptionKey.getOrNull().contentEquals(other.encryptionKey.getOrNull())) return false + if (!macKey.getOrNull().contentEquals(other.macKey.getOrNull())) return false + if (additionalProperties != other.additionalProperties) return false + + return true + } + + @OptIn(SecretExposure::class) + override fun hashCode(): Int { + var result = algorithm.hashCode() + result = 31 * result + encryptionKey.getOrNull().contentHashCode() + result = 31 * result + macKey.getOrNull().contentHashCode() + return result + } + + + } class WithoutNonce internal constructor( - algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>, - secretKey: ByteArray, + override val algorithm: SymmetricEncryptionAlgorithm, NonceTrait.Without, KeyType.WithDedicatedMacKey>, + encryptionKey: ByteArray, dedicatedMacKey: ByteArray, - additionalProperties: MutableMap = mutableMapOf() - ) : WithDedicatedMac( - algorithm, additionalProperties, secretKey, dedicatedMacKey - ), - SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is WithDedicatedMac<*>) return false - - if (algorithm != other.algorithm) return false - if (!encryptionKey.contentEquals(other.encryptionKey)) return false - if (!macKey.contentEquals(other.macKey)) return false - if (additionalProperties != other.additionalProperties) return false - - return true - } + override val additionalProperties: MutableMap = mutableMapOf() + ) : WithDedicatedMac, + SymmetricKey.WithoutNonce, KeyType.WithDedicatedMacKey> { + /** + * The actual encryption key bytes + * + * This will fail for hardware-backed keys! + */ + @SecretExposure + override val encryptionKey: KmmResult = KmmResult.success(encryptionKey) + + /** + * The actual dedicated MAC key bytes + * + * This will fail for hardware-backed keys! + */ + @SecretExposure + override val macKey: KmmResult = KmmResult.success(dedicatedMacKey) + + @OptIn(SecretExposure::class) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WithDedicatedMac<*>) return false + + if (algorithm != other.algorithm) return false + if (!encryptionKey.getOrNull().contentEquals(other.encryptionKey.getOrNull())) return false + if (!macKey.getOrNull().contentEquals(other.macKey.getOrNull())) return false + if (additionalProperties != other.additionalProperties) return false + + return true + } - override fun hashCode(): Int { - var result = algorithm.hashCode() - result = 31 * result + encryptionKey.contentHashCode() - result = 31 * result + macKey.contentHashCode() - return result + @OptIn(SecretExposure::class) + override fun hashCode(): Int { + var result = algorithm.hashCode() + result = 31 * result + encryptionKey.getOrNull().contentHashCode() + result = 31 * result + macKey.getOrNull().contentHashCode() + return result + } } } } @@ -206,5 +298,24 @@ fun , K : KeyType> SymmetricKey.requiresNonce(): /** * The actual encryption key bytes + * + * This will fail for hardware-backed keys! */ +@SecretExposure val , I : NonceTrait> SymmetricKey.secretKey get() = (this as SymmetricKey.Integrated).secretKey + +/** + * The encryption key bytes, if present. + * + * This will fail for hardware-backed keys! + */ +@SecretExposure +val , I : NonceTrait> SymmetricKey.encryptionKey get() = (this as SymmetricKey.WithDedicatedMac).encryptionKey + +/** + * The dedicated MAC key bytes, if present. + * + * This will fail for hardware-backed keys! + */ +@SecretExposure +val , I : NonceTrait> SymmetricKey.macKey get() = (this as SymmetricKey.WithDedicatedMac).macKey diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt index 0e1631c87..213554bcf 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/os/AndroidKeyStoreProvider.kt @@ -19,7 +19,7 @@ import at.asitplus.signum.indispensable.asn1.Asn1StructuralException import at.asitplus.signum.indispensable.pki.X509Certificate import at.asitplus.signum.indispensable.pki.leaf import at.asitplus.signum.supreme.AppLifecycleMonitor -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.SignatureResult import at.asitplus.signum.supreme.UnlockFailed import at.asitplus.signum.supreme.UnsupportedCryptoException diff --git a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 2707500be..f422622b3 100644 --- a/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/androidMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -14,7 +14,7 @@ import at.asitplus.signum.indispensable.parseFromJca import at.asitplus.signum.indispensable.toCryptoPrivateKey import at.asitplus.signum.indispensable.toCryptoPublicKey import at.asitplus.signum.indispensable.toJcaPublicKey -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.signCatching import com.ionspin.kotlin.bignum.integer.base63.toJavaBigInteger import java.security.KeyPair diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt index 9ca250167..fdb4e833d 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/Throwables.kt @@ -1,10 +1,5 @@ package at.asitplus.signum.supreme - -@RequiresOptIn(message = "Access to secret and private key material requires explicit opt-in. Specify @OptIn(SecretExposure::class). Make sure that you actually want to externalise a secret. Check yourself, before you really, really, wreck yourself!") -/** This guards a secret. Do not expose it lightly! */ -annotation class SecretExposure - sealed class CryptoException(message: String? = null, cause: Throwable? = null) : Throwable(message, cause) open class CryptoOperationFailed(message: String) : CryptoException(message) open class UnsupportedCryptoException(message: String? = null, cause: Throwable? = null) : CryptoException(message, cause) diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeys.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeys.kt index d49851705..2a88b71cc 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeys.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeys.kt @@ -8,7 +8,7 @@ import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm import at.asitplus.signum.indispensable.nativeDigest -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.dsl.DSL import at.asitplus.signum.supreme.dsl.DSLConfigureFn import at.asitplus.signum.supreme.os.SignerConfiguration diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt index d12ce4e43..64c718487 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/sign/Signer.kt @@ -5,7 +5,7 @@ import at.asitplus.catching import at.asitplus.signum.indispensable.* import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.SignatureResult import at.asitplus.signum.supreme.agree.UsableECDHPrivateValue import at.asitplus.signum.supreme.dsl.DSL diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt index 55bf083c0..e0113a550 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/decrypt.kt @@ -3,6 +3,7 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.ImplementationError +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.AuthCapability.Authenticated @@ -22,18 +23,21 @@ suspend fun SealedBox<*, *, *>.decrypt(key: SymmetricKey<*, *, *>): KmmResult (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey, byteArrayOf() + @OptIn(SecretExposure::class) (key as SymmetricKey.Integrated).secretKey.getOrThrow(), + byteArrayOf() ) is Authenticated.WithDedicatedMac<*, *> -> { key as SymmetricKey.WithDedicatedMac (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( - key.encryptionKey, key.macKey, byteArrayOf() + @OptIn(SecretExposure::class) key.encryptionKey.getOrThrow(), + @OptIn(SecretExposure::class) key.macKey.getOrThrow(), + byteArrayOf() ) } is AuthCapability.Unauthenticated -> (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey + @OptIn(SecretExposure::class) (key as SymmetricKey.Integrated).secretKey.getOrThrow() ) } } @@ -54,8 +58,10 @@ suspend fun SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( - key.encryptionKey, - key.macKey, + @OptIn(SecretExposure::class) + key.encryptionKey.getOrThrow(), + @OptIn(SecretExposure::class) + key.macKey.getOrThrow(), authenticatedData ) } @@ -68,7 +74,7 @@ suspend fun SealedBox,I: NonceTrait, K : KeyType> SealedBox.decrypt( +suspend fun , I : NonceTrait, K : KeyType> SealedBox.decrypt( key: SymmetricKey, authenticatedData: ByteArray = byteArrayOf() ): KmmResult = catching { @@ -76,13 +82,16 @@ suspend fun ,I: NonceTrait, K : KeyType> @Suppress("UNCHECKED_CAST") when (algorithm.authCapability) { is Authenticated.Integrated -> (this as SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey, authenticatedData + @OptIn(SecretExposure::class) (key as SymmetricKey.Integrated).secretKey.getOrThrow(), + authenticatedData ) is Authenticated.WithDedicatedMac<*, *> -> { key as SymmetricKey.WithDedicatedMac (this as SealedBox, *, KeyType.WithDedicatedMacKey>).decryptInternal( - key.encryptionKey, key.macKey, authenticatedData + @OptIn(SecretExposure::class) key.encryptionKey.getOrThrow(), + @OptIn(SecretExposure::class) key.macKey.getOrThrow(), + authenticatedData ) } //compiler knows its exhaustive, but IDEA complains @@ -104,7 +113,7 @@ suspend fun SealedBox).decryptInternal( - (key as SymmetricKey.Integrated).secretKey + @OptIn(SecretExposure::class) (key as SymmetricKey.Integrated).secretKey.getOrThrow() ) } diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt index 714b45d21..ab660fce1 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/discouraged/encrypt.kt @@ -3,6 +3,7 @@ package at.asitplus.signum.supreme.symmetric.discouraged import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.indispensable.symmetric.SymmetricKey.WithDedicatedMac import at.asitplus.signum.supreme.symmetric.Encryptor @@ -25,13 +26,13 @@ suspend fun > KeyWithNonceA data: ByteArray, authenticatedData: ByteArray? = null ): KmmResult> = catching { - Encryptor( + @OptIn(SecretExposure::class) Encryptor( second.algorithm, - if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).encryptionKey else (second as SymmetricKey.Integrated).secretKey, - if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).macKey else (second as SymmetricKey.Integrated).secretKey, + if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).encryptionKey.getOrThrow() else (second as SymmetricKey.Integrated).secretKey.getOrThrow(), + if (second.hasDedicatedMacKey()) (second as WithDedicatedMac).macKey.getOrThrow() else (second as SymmetricKey.Integrated).secretKey.getOrThrow(), first, authenticatedData, - ).encrypt(data) as SealedBox + ).encrypt(data) as SealedBox } /** @@ -48,10 +49,10 @@ suspend fun > KeyWithNonceA suspend fun > KeyWithNonce.encrypt( data: ByteArray ): KmmResult> = catching { - Encryptor( + @OptIn(SecretExposure::class) Encryptor( first.algorithm, - if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).encryptionKey else (first as SymmetricKey.Integrated).secretKey, - if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).macKey else null, + if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).encryptionKey.getOrThrow() else (first as SymmetricKey.Integrated).secretKey.getOrThrow(), + if (first.hasDedicatedMacKey()) (first as WithDedicatedMac).macKey.getOrThrow() else null, second, null, ).encrypt(data) as SealedBox.WithNonce diff --git a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt index cf74c0288..1a0479627 100644 --- a/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt +++ b/supreme/src/commonMain/kotlin/at/asitplus/signum/supreme/symmetric/encrypt.kt @@ -2,6 +2,7 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.KmmResult import at.asitplus.catching +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.symmetric.* import kotlin.jvm.JvmName @@ -15,10 +16,10 @@ import kotlin.jvm.JvmName suspend fun , I : NonceTrait> SymmetricKey.encrypt( data: ByteArray ): KmmResult> = catching { - Encryptor( + @OptIn(SecretExposure::class) Encryptor( algorithm, - if (this.hasDedicatedMacKey()) encryptionKey else secretKey, - if (this.hasDedicatedMacKey()) macKey else null, + if (this.hasDedicatedMacKey()) encryptionKey.getOrThrow() else secretKey.getOrThrow(), + if (this.hasDedicatedMacKey()) macKey.getOrThrow() else null, aad = null ).encrypt(data) } @@ -40,10 +41,10 @@ suspend fun , I : NonceTrai data: ByteArray, authenticatedData: ByteArray? = null ): KmmResult> = catching { - Encryptor( + @OptIn(SecretExposure::class) Encryptor( algorithm, - if (this.hasDedicatedMacKey()) encryptionKey else secretKey, - if (this.hasDedicatedMacKey()) macKey else null, + if (this.hasDedicatedMacKey()) encryptionKey.getOrThrow() else secretKey.getOrThrow(), + if (this.hasDedicatedMacKey()) macKey.getOrThrow() else null, aad = authenticatedData, ).encrypt(data) } diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt index d291e0fe8..6c18f0f42 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt @@ -5,7 +5,7 @@ import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.pki.* import at.asitplus.signum.indispensable.RSAPadding import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.os.PlatformSigningKeyConfigurationBase import at.asitplus.signum.supreme.os.SignerConfiguration import at.asitplus.signum.supreme.sign diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/PrivateKeyCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/PrivateKeyCommonTests.kt index 7b5c8e06c..44976daaf 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/PrivateKeyCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/PrivateKeyCommonTests.kt @@ -2,7 +2,7 @@ package at.asitplus.signum.supreme.sign import at.asitplus.signum.indispensable.CryptoPrivateKey import at.asitplus.signum.indispensable.SignatureAlgorithm -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.isSuccess import at.asitplus.signum.supreme.signature import io.kotest.core.spec.style.FreeSpec diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt index 2ed268d49..76cd981a5 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/symmetric/00SymmetricTest.kt @@ -1,4 +1,7 @@ +@file:OptIn(SecretExposure::class) + import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.asn1.encoding.encodeTo4Bytes import at.asitplus.signum.indispensable.mac.HMAC import at.asitplus.signum.indispensable.mac.MessageAuthenticationCode @@ -43,7 +46,7 @@ class `00SymmetricTest` : FreeSpec({ val ciphertext = encrypted.encryptedData val authTag = encrypted.authTag val externalAAD = authenticatedData - val keyBytes = secretKey.secretKey + val keyBytes = secretKey.secretKey.getOrThrow() val preSharedKey = algo.keyFrom(keyBytes).getOrThrow() @@ -91,7 +94,7 @@ class `00SymmetricTest` : FreeSpec({ //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*/), + algorithm.keyFrom(key.encryptionKey.getOrThrow(), key.macKey.getOrThrow()).getOrThrow(/*handle error*/), aad ).getOrThrow(/*handle error*/) shouldBe payload //greatest success! } @@ -199,8 +202,8 @@ class `00SymmetricTest` : FreeSpec({ } shouldNot succeed val key = when (alg.hasDedicatedMac()) { - true -> alg.keyFrom(alg.randomKey().encryptionKey, alg.randomKey().encryptionKey) - false -> alg.keyFrom(alg.randomKey().secretKey) + true -> alg.keyFrom(alg.randomKey().encryptionKey.getOrThrow(), alg.randomKey().encryptionKey.getOrThrow()) + false -> alg.keyFrom(alg.randomKey().secretKey.getOrThrow()) }.getOrThrow() @@ -283,14 +286,14 @@ class `00SymmetricTest` : FreeSpec({ ).getOrThrow() val wrongWrongDecrypted = wrongCiphertext.decrypt(it.randomKey()) - withClue("KEY: ${key.secretKey.toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { + withClue("KEY: ${key.secretKey.getOrThrow().toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { //we're not authenticated, so from time to time, this succeeds //wrongWrongDecrypted shouldNot succeed //instead, we test differently: wrongWrongDecrypted.onSuccess { value -> value shouldNotBe plaintext } } val wrongRightDecrypted = wrongCiphertext.decrypt(key) - withClue("KEY: ${key.secretKey.toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { + withClue("KEY: ${key.secretKey.getOrThrow().toHexString()}, wrongCiphertext: ${wrongCiphertext.encryptedData.toHexString()}, ciphertext: ${ciphertext.encryptedData.toHexString()}, iv: ${wrongCiphertext.nonce?.toHexString()}") { //we're not authenticated, so from time to time, this succeeds //wrongRightDecrypted shouldNot succeed //instead, we test differently: @@ -492,7 +495,7 @@ class `00SymmetricTest` : FreeSpec({ Random.Default.nextBytes(21257), ) { plaintext -> - val secretKey = it.randomKey().encryptionKey + val secretKey = it.randomKey().encryptionKey.getOrThrow() withData( nameFn = { "MAC KEY $it" }, @@ -521,8 +524,8 @@ class `00SymmetricTest` : FreeSpec({ val manilaKey = SymmetricKey.WithDedicatedMac.RequiringNonce( manilaAlg, - key.encryptionKey, - key.macKey + key.encryptionKey.getOrThrow(), + key.macKey.getOrThrow() ) if (iv != null) manilaKey.andPredefinedNonce(iv).getOrThrow().encrypt(plaintext, aad) .getOrThrow() shouldNotBe ciphertext @@ -582,8 +585,8 @@ class `00SymmetricTest` : FreeSpec({ ).getOrThrow().decrypt( SymmetricKey.WithDedicatedMac.RequiringNonce( ciphertext.algorithm as SymmetricEncryptionAlgorithm, NonceTrait.Required, KeyType.WithDedicatedMacKey>, - key.encryptionKey, - dedicatedMacKey = key.macKey.asList().shuffled().toByteArray() + key.encryptionKey.getOrThrow(), + dedicatedMacKey = key.macKey.getOrThrow().asList().shuffled().toByteArray() ), aad ?: byteArrayOf() ) shouldNot succeed @@ -606,8 +609,8 @@ class `00SymmetricTest` : FreeSpec({ }.let { SymmetricKey.WithDedicatedMac.RequiringNonce( it, - key.encryptionKey, - key.macKey + key.encryptionKey.getOrThrow(), + key.macKey.getOrThrow() ) }, aad ?: byteArrayOf()) shouldNot succeed } @@ -737,27 +740,27 @@ class `00SymmetricTest` : FreeSpec({ when (alg.hasDedicatedMac()) { true -> { key shouldBe alg.keyFrom( - (key as SymmetricKey.WithDedicatedMac).encryptionKey, - (key as SymmetricKey.WithDedicatedMac<*>).macKey + (key as SymmetricKey.WithDedicatedMac).encryptionKey.getOrThrow(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.getOrThrow() ).getOrThrow() key shouldNotBe alg.keyFrom( - key.encryptionKey, - (key as SymmetricKey.WithDedicatedMac<*>).macKey.asList().shuffled() + key.encryptionKey.getOrThrow(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.getOrThrow().asList().shuffled() .toByteArray() ).getOrThrow() key shouldNotBe alg.keyFrom( - key.encryptionKey.asList().shuffled().toByteArray(), - (key as SymmetricKey.WithDedicatedMac<*>).macKey + key.encryptionKey.getOrThrow().asList().shuffled().toByteArray(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.getOrThrow() ).getOrThrow() key shouldNotBe alg.keyFrom( - key.encryptionKey.asList().shuffled().toByteArray(), - (key as SymmetricKey.WithDedicatedMac<*>).macKey.asList().shuffled() + key.encryptionKey.getOrThrow().asList().shuffled().toByteArray(), + (key as SymmetricKey.WithDedicatedMac<*>).macKey.getOrThrow().asList().shuffled() .toByteArray() ).getOrThrow() } - false -> key shouldBe alg.keyFrom((key as SymmetricKey.Integrated).secretKey).getOrThrow() + false -> key shouldBe alg.keyFrom((key as SymmetricKey.Integrated).secretKey.getOrThrow()).getOrThrow() } } @@ -810,14 +813,14 @@ class `00SymmetricTest` : FreeSpec({ if (alg.hasDedicatedMac() && wrongAlg.hasDedicatedMac()) { alg.randomKey().let { key -> wrongAlg.keyFrom( - key.encryptionKey, - key.macKey /*size will not match, but it will get us a valid key*/ + key.encryptionKey.getOrThrow(), + key.macKey.getOrThrow() /*size will not match, but it will get us a valid key*/ ).getOrThrow() shouldNotBe key } } else if (!wrongAlg.hasDedicatedMac() && !alg.hasDedicatedMac()) { alg.randomKey().let { key -> wrongAlg.keyFrom( - key.secretKey, + key.secretKey.getOrThrow(), ).getOrThrow() shouldNotBe key } } @@ -933,7 +936,7 @@ class `00SymmetricTest` : FreeSpec({ true -> { alg.keyFrom( Random.nextBytes(sz), - alg.randomKey().encryptionKey /*mac key should not trigger, as it is unconstrained*/ + alg.randomKey().encryptionKey.getOrThrow() /*mac key should not trigger, as it is unconstrained*/ ) } diff --git a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt index 84cfb3112..dbb2a8bcf 100644 --- a/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt +++ b/supreme/src/jvmMain/kotlin/at/asitplus/signum/supreme/sign/EphemeralKeysImpl.kt @@ -3,7 +3,7 @@ package at.asitplus.signum.supreme.sign import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.* -import at.asitplus.signum.supreme.SecretExposure +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.supreme.signCatching import com.ionspin.kotlin.bignum.integer.base63.toJavaBigInteger import java.security.KeyPair diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index db68cf2cd..f8d90fb17 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -1,5 +1,8 @@ +@file:OptIn(SecretExposure::class) + import at.asitplus.catching import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.SecretExposure import at.asitplus.signum.indispensable.symmetric.* import at.asitplus.signum.supreme.succeed import at.asitplus.signum.supreme.symmetric.decrypt @@ -71,7 +74,7 @@ class JvmSymmetricTest : FreeSpec({ own.isAuthenticated() shouldBe true jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), GCMParameterSpec( alg.authCapability.tagLength.bits.toInt(), own.nonce/*use our own auto-generated IV*/ @@ -85,7 +88,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), GCMParameterSpec( alg.authCapability.tagLength.bits.toInt(), own.nonce/*use our own auto-generated IV*/ @@ -116,7 +119,7 @@ class JvmSymmetricTest : FreeSpec({ val own = secretKey.encrypt(data).getOrThrow() jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), IvParameterSpec(own.nonce)/*use our own auto-generated IV, if null iv was provided*/ ) val encrypted = jcaCipher.doFinal(data) @@ -125,7 +128,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), IvParameterSpec(own.nonce)/*use our own auto-generated IV, if null iv was provided*/ ) own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) @@ -185,7 +188,7 @@ class JvmSymmetricTest : FreeSpec({ val own = secretKey.encrypt(data).getOrThrow() jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), ) val encrypted = jcaCipher.doFinal(data) @@ -193,7 +196,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), ) own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) @@ -217,7 +220,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), ) val jcaTrail = catching { jcaCipher.doFinal(data) @@ -233,7 +236,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.DECRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "AES"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "AES"), ) own.decrypt(secretKey).getOrThrow() shouldBe jcaCipher.doFinal(encrypted) @@ -277,7 +280,7 @@ class JvmSymmetricTest : FreeSpec({ jcaCipher.init( Cipher.ENCRYPT_MODE, - SecretKeySpec(secretKey.secretKey, "ChaCha"), + SecretKeySpec(secretKey.secretKey.getOrThrow(), "ChaCha"), IvParameterSpec(box.nonce) /*need to do this, otherwise we get a random nonce*/ ) From 9c1266aa6ab46d01f378091eb1b37162f4073601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Wed, 26 Feb 2025 14:08:18 +0100 Subject: [PATCH 27/28] adapt to new source layout --- .../at/asitplus/signum/indispensable/JcaExtensions.kt | 8 ++++---- .../at/asitplus/signum/supreme/symmetric/AES.jca.kt | 2 ++ .../at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt index a1f96ab0b..f329f86f5 100644 --- a/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt +++ b/indispensable/src/androidJvmMain/kotlin/at/asitplus/signum/indispensable/JcaExtensions.kt @@ -189,7 +189,7 @@ fun CryptoPublicKey.Companion.fromJcaPublicKey(publicKey: PublicKey): KmmResult< val CryptoSignature.jcaSignatureBytes: ByteArray get() = when (this) { is CryptoSignature.EC -> encodeToDer() - is CryptoSignature.RSA -> rawByteArray + is CryptoSignature.RSAorHMAC -> rawByteArray } /** @@ -202,7 +202,7 @@ fun CryptoSignature.Companion.parseFromJca( if (algorithm is SignatureAlgorithm.ECDSA) CryptoSignature.EC.parseFromJca(input) else - CryptoSignature.RSA.parseFromJca(input) + CryptoSignature.RSAorHMAC.parseFromJca(input) fun CryptoSignature.Companion.parseFromJca( input: ByteArray, @@ -221,8 +221,8 @@ fun CryptoSignature.EC.Companion.parseFromJca(input: ByteArray) = fun CryptoSignature.EC.Companion.parseFromJcaP1363(input: ByteArray) = CryptoSignature.EC.fromRawBytes(input) -fun CryptoSignature.RSA.Companion.parseFromJca(input: ByteArray) = - CryptoSignature.RSA(input) +fun CryptoSignature.RSAorHMAC.Companion.parseFromJca(input: ByteArray) = + CryptoSignature.RSAorHMAC(input) /** * Converts this [X509Certificate] to a [java.security.cert.X509Certificate]. diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt index 7761b1164..fd809b41d 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/AES.jca.kt @@ -1,6 +1,8 @@ package at.asitplus.signum.supreme.symmetric import at.asitplus.signum.HazardousMaterials +import at.asitplus.signum.indispensable.jcaKeySpec +import at.asitplus.signum.indispensable.jcaName import at.asitplus.signum.indispensable.symmetric.AuthCapability import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm import javax.crypto.Cipher diff --git a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt index 20376fb1d..2da7ea5f5 100644 --- a/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt +++ b/supreme/src/androidJvmMain/kotlin/at/asitplus/signum/supreme/symmetric/ChaCha.jca.kt @@ -1,5 +1,7 @@ package at.asitplus.signum.supreme.symmetric +import at.asitplus.signum.indispensable.jcaKeySpec +import at.asitplus.signum.indispensable.jcaName import at.asitplus.signum.indispensable.symmetric.SymmetricEncryptionAlgorithm import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec From cb8d856d39ddacc29b6a4fa1da72fd9b0368f5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Wed, 26 Feb 2025 14:57:21 +0100 Subject: [PATCH 28/28] fix test case --- .../jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt index f8d90fb17..eeebc317f 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/symmetric/Test.kt @@ -141,7 +141,7 @@ class JvmSymmetricTest : FreeSpec({ if (data.size < alg.blockSize.bytes.toInt()) alg.sealedBox.withNonce(own.algorithm.randomNonce()).from( own.encryptedData - ).getOrThrow().decrypt(secretKey) shouldNot succeed + ).getOrThrow().decrypt(secretKey).onSuccess { it shouldNotBe data } } }