├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .kotlin-js-store └── yarn.lock ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASING.md ├── benchmarks ├── .gitignore ├── README.md ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── org │ └── kotlincrypto │ └── core │ └── benchmarks │ ├── DigestOps.kt │ └── XofOps.kt ├── build-logic ├── .gitignore ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── -KmpConfigurationExtension.kt │ ├── configuration.gradle.kts │ ├── dokka.gradle.kts │ └── publication.gradle.kts ├── build.gradle.kts ├── gh-pages └── publish.sh ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── core │ ├── .gitignore │ ├── README.md │ ├── api │ │ ├── core.api │ │ └── core.klib.api │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ ├── Algorithm.kt │ │ │ ├── Annotation.kt │ │ │ ├── Copyable.kt │ │ │ ├── Resettable.kt │ │ │ └── Updatable.kt │ │ └── jvmMain │ │ ├── java9 │ │ └── module-info.java │ │ └── kotlin │ │ └── org │ │ └── kotlincrypto │ │ └── core │ │ └── -AndroidSdkInt.kt ├── digest │ ├── .gitignore │ ├── README.md │ ├── api │ │ ├── digest.api │ │ └── digest.klib.api │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ └── digest │ │ │ ├── Digest.kt │ │ │ └── internal │ │ │ ├── -Buffer.kt │ │ │ ├── -CommonPlatform.kt │ │ │ └── DigestState.kt │ │ ├── commonTest │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ └── digest │ │ │ ├── AbstractTestUpdateExceptions.kt │ │ │ ├── DigestUnitTest.kt │ │ │ └── TestDigest.kt │ │ ├── jvmMain │ │ ├── java9 │ │ │ └── module-info.java │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ └── digest │ │ │ └── Digest.kt │ │ ├── jvmTest │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ └── digest │ │ │ ├── JvmDigestExceptionTest.kt │ │ │ └── JvmDigestUnitTest.kt │ │ └── nonJvmMain │ │ └── kotlin │ │ └── org │ │ └── kotlincrypto │ │ └── core │ │ └── digest │ │ └── Digest.kt ├── mac │ ├── .gitignore │ ├── README.md │ ├── api │ │ ├── mac.api │ │ └── mac.klib.api │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ └── mac │ │ │ ├── Mac.kt │ │ │ └── internal │ │ │ └── -CommonPlatform.kt │ │ ├── commonTest │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ └── mac │ │ │ ├── MacUnitTest.kt │ │ │ └── TestMac.kt │ │ ├── jvmMain │ │ ├── java9 │ │ │ └── module-info.java │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ └── mac │ │ │ ├── Mac.kt │ │ │ └── internal │ │ │ └── AndroidApi21to23MacSpiProvider.kt │ │ ├── jvmTest │ │ └── kotlin │ │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ └── mac │ │ │ └── JvmMacUnitTest.kt │ │ └── nonJvmMain │ │ └── kotlin │ │ └── org │ │ └── kotlincrypto │ │ └── core │ │ └── mac │ │ └── Mac.kt └── xof │ ├── .gitignore │ ├── README.md │ ├── api │ ├── xof.api │ └── xof.klib.api │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ ├── commonMain │ └── kotlin │ │ └── org │ │ └── kotlincrypto │ │ └── core │ │ └── xof │ │ ├── Xof.kt │ │ ├── XofAlgorithm.kt │ │ └── XofFactory.kt │ ├── commonTest │ └── kotlin │ │ └── org │ │ └── kotlincrypto │ │ └── core │ │ └── xof │ │ └── XofUtilsUnitTest.kt │ └── jvmMain │ └── java9 │ └── module-info.java ├── settings.gradle.kts ├── test-android ├── .gitignore ├── api │ ├── test-android.api │ └── test-android.klib.api ├── build.gradle.kts └── src │ ├── androidInstrumentedTest │ ├── digest │ ├── kotlin │ │ └── org │ │ │ └── kotlincrypto │ │ │ └── core │ │ │ ├── AndroidSdkIntTest.kt │ │ │ └── mac │ │ │ └── AndroidMacTest.kt │ └── mac │ ├── androidMain │ └── kotlin │ │ └── org │ │ └── kotlincrypto │ │ └── core │ │ └── -Stub.kt │ └── androidUnitTest │ ├── digest │ ├── kotlin │ └── org │ │ └── kotlincrypto │ │ └── core │ │ └── AndroidSdkIntUnitTest.kt │ └── mac └── tools └── check-publication ├── .gitignore ├── build.gradle.kts └── src └── commonMain └── kotlin └── tools └── check └── publication └── Stub.kt /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [ pull_request ] 4 | 5 | env: 6 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError -XX:MetaspaceSize=1g" 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ macos-latest, ubuntu-latest, windows-latest ] 14 | java-version: [ 11, 19 ] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Validate Gradle Wrapper 21 | uses: gradle/actions/wrapper-validation@v3 22 | 23 | - name: Setup JDK 24 | uses: actions/setup-java@v4 25 | with: 26 | distribution: 'zulu' 27 | java-version: ${{ matrix.java-version }} 28 | 29 | - name: Check API Compatibility 30 | if: matrix.os == 'macos-latest' && matrix.java-version == 19 31 | run: > 32 | ./gradlew apiCheck --stacktrace 33 | 34 | - name: Run macOS Tests [ Java 11 ] 35 | if: matrix.os == 'macos-latest' && matrix.java-version == 11 36 | run: > 37 | ./gradlew check --stacktrace 38 | -PKMP_TARGETS="IOS_ARM64,IOS_X64,IOS_SIMULATOR_ARM64,JS,JVM,WASM_JS,WASM_WASI" 39 | 40 | - name: Run macOS Tests [ Java 19 ] 41 | if: matrix.os == 'macos-latest' && matrix.java-version == 19 42 | run: > 43 | ./gradlew check --stacktrace 44 | -PKMP_TARGETS="JVM,MACOS_ARM64,MACOS_X64,TVOS_ARM64,TVOS_X64,TVOS_SIMULATOR_ARM64,WATCHOS_ARM32,WATCHOS_ARM64,WATCHOS_DEVICE_ARM64,WATCHOS_X64,WATCHOS_SIMULATOR_ARM64" 45 | 46 | - name: Run Linux Tests [ Java 11 ] 47 | if: matrix.os == 'ubuntu-latest' && matrix.java-version == 11 48 | run: > 49 | ./gradlew check --stacktrace 50 | -PKMP_TARGETS="JS,JVM,WASM_JS,WASM_WASI" 51 | 52 | - name: Run Linux Tests [ Java 19 ] 53 | if: matrix.os == 'ubuntu-latest' && matrix.java-version == 19 54 | run: > 55 | ./gradlew check --stacktrace 56 | -PKMP_TARGETS="ANDROID,ANDROID_ARM32,ANDROID_ARM64,ANDROID_X64,ANDROID_X86,JVM,LINUX_ARM64,LINUX_X64" 57 | 58 | - name: Run Windows Tests [ Java 11 ] 59 | if: matrix.os == 'windows-latest' && matrix.java-version == 11 60 | run: > 61 | ./gradlew check --stacktrace 62 | -PKMP_TARGETS="JS,JVM,MINGW_X64,WASM_JS,WASM_WASI" 63 | 64 | - name: Upload Test Reports 65 | uses: actions/upload-artifact@v4 66 | if: ${{ always() }} 67 | with: 68 | name: test-report-${{ matrix.os }}-java${{ matrix.java-version }} 69 | path: '**/build/reports/tests/**' 70 | retention-days: 1 71 | 72 | benchmark: 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | os: [ macos-latest, ubuntu-latest, windows-latest ] 77 | runs-on: ${{ matrix.os }} 78 | steps: 79 | - name: Checkout Repository 80 | uses: actions/checkout@v4 81 | 82 | - name: Validate Gradle Wrapper 83 | uses: gradle/actions/wrapper-validation@v3 84 | 85 | - name: Setup JDK 86 | uses: actions/setup-java@v4 87 | with: 88 | distribution: 'zulu' 89 | java-version: 11 90 | 91 | - name: Run Benchmark 92 | run: > 93 | ./gradlew benchmark 94 | -PKMP_TARGETS="JVM,JS,LINUX_ARM64,LINUX_X64,MACOS_ARM64,MACOS_X64,MINGW_X64,WASM_JS,WASM_WASI" 95 | 96 | - name: Upload Benchmark Reports 97 | uses: actions/upload-artifact@v4 98 | with: 99 | name: benchmark-report-${{ matrix.os }} 100 | path: '**/build/reports/benchmarks/**' 101 | 102 | android-check: 103 | strategy: 104 | fail-fast: false 105 | matrix: 106 | api-level: [ 15, 21, 23, 24, 29 ] 107 | runs-on: ubuntu-latest 108 | steps: 109 | - name: Checkout Repository 110 | uses: actions/checkout@v4 111 | 112 | - name: Enable KVM 113 | run: | 114 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 115 | sudo udevadm control --reload-rules 116 | sudo udevadm trigger --name-match=kvm 117 | 118 | - name: Validate Gradle Wrapper 119 | uses: gradle/actions/wrapper-validation@v3 120 | 121 | - name: Setup JDK 122 | uses: actions/setup-java@v4 123 | with: 124 | distribution: 'zulu' 125 | java-version: 17 126 | 127 | - name: Run Android Instrumented Tests 128 | uses: reactivecircus/android-emulator-runner@v2 129 | with: 130 | emulator-boot-timeout: 300 # 5 minutes 131 | api-level: ${{ matrix.api-level }} 132 | script: ./gradlew :test-android:connectedCheck -PKMP_TARGETS="ANDROID,JVM" 133 | 134 | - name: Upload Test Reports 135 | uses: actions/upload-artifact@v4 136 | if: ${{ always() }} 137 | with: 138 | name: test-report-android-${{ matrix.api-level }} 139 | path: '**/build/reports/androidTests/**' 140 | retention-days: 1 141 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | *.iml 4 | .idea/ 5 | local.properties 6 | .kotlin/ 7 | 8 | gh-pages/* 9 | !gh-pages/publish.sh 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Version 0.7.0 (2025-02-25) 4 | - Updates `kotlin` to `2.1.10` [[#122]][122] 5 | - Updates `kotlincrypto.error` to `0.3.0` [[#122]][122] 6 | - `Mac` and `Digest` constructors now throw `InvalidParameterException` instead 7 | of `IllegalArgumentException` [[#124]][124] 8 | - `Mac.Engine` constructor, `Mac.reset(newKey)`, and `Xof.Companion.reset(newKey)` now 9 | throw `InvalidKeyException` instead of `IllegalArgumentException` [[#124]][124] 10 | 11 | ## Version 0.6.1 (2025-02-09) 12 | - Adds `Digest.digestInto` API for populating an existing `ByteArray` with output [[#108]][108] 13 | - Adds `Mac.doFinalInto` API for populating an existing `ByteArray` with output [[#109]][109] 14 | - Adds `Mac.Engine.resetOnDoFinal` constructor argument for providing implementations the ability 15 | to disable automatic `Engine.reset` call when `doFinal` or `doFinalInto` gets executed [[#111]][111] 16 | - Mitigates double resets when implementation is backed by a `Digest`. 17 | - Adds `dokka` documentation at `https://core.kotlincrypto.org` [[#116]][116] 18 | - Adds API dependency `org.kotlincrypto:error` to module `core` [[#118]][118] 19 | 20 | ## Version 0.6.0 (2025-01-15) 21 | - `@Throws` annotation removed from `Updatable.update` (it is documented). 22 | - Finalizes `Digest` internal API and removes `InternalKotlinCryptoApi` opt-in requirement from constructors. 23 | - Drops usage of `DigestState` in favor of secondary constructor which takes 24 | `Digest` as an argument (for `copy` function implementations). 25 | - `Copyable.copy` override is now passed through to implementations so that the proper 26 | return type can be defined (instead of requiring API consumers to cast from `Digest`). 27 | - `protected` functions renamed: 28 | - `compress` >> `compressProtected` 29 | - `digest` >> `digestProtected` 30 | - `updateDigest` >> `updateProtected` 31 | - `resetDigest` >> `resetProtected` 32 | - `digestProtected` (previously `digest`) now only provides the buffered input and position as 33 | arguments; `bitLength` is no longer tracked by `Digest`. 34 | - `Digest.digest` now zero's out stale buffered input from `bufPos` to `buf.size` before passing it to 35 | `digestProtected` (previously `digest`) as an argument. 36 | - Finalizes `Mac` internal API and removes `InternalKotlinCryptoApi` opt-in requirement from constructors. 37 | - Provides secondary constructor which takes `Mac` as an argument (for `copy` function implementations). 38 | - `Copyable.copy` override is now passed through to `Mac` implementations so that the proper 39 | return type can be defined (instead of requiring API consumers to cast from `Mac`). 40 | - Adds ability to reinitialize `Mac` with a new `key` parameter via `reset(newKey)` (or clear it after use). 41 | - Adds `Mac.clearKey` helper function for zeroing out key material before dereferencing the `Mac` (if desired). 42 | - Removes `Mac.Engine.State` in favor of secondary constructor which takes `Mac.Engine` as an argument 43 | (for `copy` function implementation). 44 | - Adds abstract function `Mac.Engine.reset(newKey)` for reinitialization functionality. 45 | - Finalizes `Xof` and `XofFactory` internal API and removes `InternalKotlinCryptoApi` opt-in 46 | requirement from constructors. 47 | - `Xof.Reader.readProtected` no longer passes `bytesRead` as an argument. 48 | - Adds `ReKeyableXofAlgorithm` interface for `Xof` implementations who's backing delegate is a `Mac`. 49 | - Adds `Xof.Companion.reset(newKey)` extension function which allows API consumers the ability to 50 | reinitialize the `Xof` with a new `key` parameter when that `Xof` is backed by an instance of 51 | `ReKeyableXofAlgorithm` (i.e. a `Mac` implementation). 52 | - `Xof.use` and `Xof.Reader.use` functions are now inlined. 53 | - Removes usage of `KotlinCrypto.endians` library (deprecated) from `Xof.Utils`. 54 | 55 | ## Version 0.5.5 (2024-12-20) 56 | - Fixes optimization issues with `Digest.update` internals [[#70]][70] 57 | 58 | ## Version 0.5.4 (2024-12-19) 59 | - Adds benchmarking to repository [[#64]][64] 60 | - Refactors `Digest` internals (performance improvements) [[#66]][66] 61 | - Changes module `:common` name to `:core` to be more in line with its 62 | package name of `org.kotlincrypto.core` 63 | - Publication coordinates have changed from `org.kotlincrypto.core:common` 64 | to `org.kotlincrypto.core:core` 65 | 66 | ## Version 0.5.3 (2024-08-31) 67 | - Updates `kotlin` to `1.9.24` [[#61]][61] 68 | - Updates `endians` to `0.3.1` [[#61]][61] 69 | - Fixes multiplatform metadata manifest `unique_name` parameter for 70 | all source sets to be truly unique. [[#61]][61] 71 | - Updates jvm `.kotlin_module` with truly unique file name. [[#61]][61] 72 | 73 | ## Version 0.5.1 (2024-03-18) 74 | - Use `transitive` keyword for `JPMS` `module-info.java` files [[#58]][58] 75 | 76 | ## Version 0.5.0 (2024-03-18) 77 | - Updates `kotlin` to `1.9.23` [[#55]][55] 78 | - Updates `endians` to `0.3.0` [[#55]][55] 79 | - Add experimental support for `wasmJs` & `wasmWasi` [[#55]][55] 80 | - Add support for Java9 `JPMS` via Multi-Release jar [[#56]][56] 81 | 82 | ## Version 0.4.0 (2023-11-30) 83 | - Adds check for Android Runtime to `KC_ANDROID_SDK_INT` [[#51]][51] 84 | - Android Unit Tests: `KC_ANDROID_SDK_INT` will now be `null` 85 | - Android Runtime: `KC_ANDROID_SDK_INT` will **NOT** be `null` 86 | - Updates `kotlin` to `1.9.21` [[#52]][52] 87 | - Updates `endians` to `0.2.0` [[#52]][52] 88 | - Drops support for the following deprecated targets: 89 | - `iosArm32` 90 | - `watchosX86` 91 | - `linuxArm32Hfp` 92 | - `linuxMips32` 93 | - `linuxMipsel32` 94 | - `mingwX86` 95 | - `wasm32` 96 | 97 | ## Version 0.3.0 (2023-06-28) 98 | - Fixes JPMS split packages [[#48]][48] 99 | - **API BREAKING CHANGES** 100 | - Package names were changed for `digest`, `mac`, and `xof` dependencies 101 | - Example: 102 | - `org.kotlincrypto.core.Digest` was moved to `org.kotlincrypto.core.digest.Digest` 103 | - `org.kotlincrypto.core.Mac` was moved to `org.kotlincrypto.core.mac.Mac` 104 | - `org.kotlincrypto.core.Xof` was moved to `org.kotlincrypto.core.xof.Xof` 105 | - See the [ANNOUNCEMENT][discussion-3] for more information on `0.3.0` release 106 | 107 | ## Version 0.2.7 (2023-06-09) 108 | - Fixes Android API 23 and below not calling `javax.crypto.MacSpi.engineReset` 109 | whenever `javax.crypto.Mac.doFinal` is invoked [[#46]][46] 110 | 111 | ## Version 0.2.6 (2023-06-08) 112 | - Fixes Android API 21-23 requiring a `Provider` in order to set 113 | `javax.crypto.Mac.spiImpl` when `javax.crypto.Mac.init` is 114 | invoked [[#44]][44] 115 | - Throw `InvalidKeyException` if `javax.crypto.Mac.init` is invoked [[#43]][43] 116 | - All `org.kotlincrypto.core.Mac` APIs are structured such that 117 | implementations always require a key as a constructor argument 118 | and are initialized immediately. As such, if a java caller 119 | attempts to re-initialize the `javax.crypto.Mac` with a different key 120 | they may assume future output produced is using the new key. This 121 | is **not** the case as `Kotlin Crypto` does not use a provider based 122 | architecture. A new, uninitialized `Mac` *cannot* be created. 123 | - Note that `Mac.init` is **not** available from `commonMain`. It is 124 | a remnant of bad API design requiring ability to lazily initialize 125 | things which `Kotlin Crypto` will **never** support as it leads 126 | to monolithic structures, instead of building on good abstractions. 127 | If `Mac.init` is required to be called, a wholly new instance of the 128 | `org.kotlincrypto.core.Mac` implementation needs to be instantiated 129 | with the new key. 130 | 131 | ## Version 0.2.5 (2023-06-07) 132 | - Fixes Android API 23 and below not accepting `null` for `Mac.init` key 133 | parameter [[#38]][38] 134 | - Updates `kotlin` to `1.8.21` [[#40]][40] 135 | 136 | ## Version 0.2.4 (2023-04-16) 137 | - Adds `Digest.updateDigest` protected open functions for implementors 138 | to override when needing access to input before it is buffered [[#34]][34] 139 | - Adds input argument check for nonJvm `Mac.update` when `offset` and `len` 140 | parameters are specified [[#35]][35] 141 | - Updates `Digest.digestLength` constructor argument check to now accept 0 142 | as a valid length [[#36]][36] 143 | - Previously, passing 0 would throw an `IllegalArgumentException`. 144 | 145 | ## Version 0.2.3 (2023-04-08) 146 | - Fix `nonJvm` `Mac.doFinal` not calling `engine.reset()` [[#27]][27] 147 | - Only implementation of `Mac` is `Hmac` via `MACs` repo, which is 148 | unaffected by this issue. 149 | - Adds `XofAlgorithm` interface [[#29]][29] 150 | 151 | ## Version 0.2.2 (2023-04-07) 152 | - Adds abstraction for `XOF`s (`Extendable-Output Functions`) [[#25]][25] 153 | 154 | ## Version 0.2.0 (2023-03-28) 155 | - **BREAKING CHANGE:** 156 | - `Digest.compress` function was changed to also include an offset. 157 | This drastically improves performance by mitigating excessive/unnecessary 158 | array copying. [[#21]][21] 159 | 160 | ## Version 0.1.1 (2023-03-06) 161 | - Fix `Digest.update` miscalculation when `offset` parameter is provided [[#16]][16] 162 | - Fix `jvm` `Digest.algorithm()` StackOverflow error [[#12]][12] 163 | 164 | ## Version 0.1.0 (2023-03-04) 165 | - Initial Release 166 | 167 | [discussion-3]: https://github.com/orgs/KotlinCrypto/discussions/3 168 | [12]: https://github.com/KotlinCrypto/core/pull/12 169 | [16]: https://github.com/KotlinCrypto/core/pull/16 170 | [21]: https://github.com/KotlinCrypto/core/pull/21 171 | [25]: https://github.com/KotlinCrypto/core/pull/25 172 | [27]: https://github.com/KotlinCrypto/core/pull/27 173 | [29]: https://github.com/KotlinCrypto/core/pull/29 174 | [34]: https://github.com/KotlinCrypto/core/pull/34 175 | [35]: https://github.com/KotlinCrypto/core/pull/35 176 | [36]: https://github.com/KotlinCrypto/core/pull/36 177 | [38]: https://github.com/KotlinCrypto/core/pull/38 178 | [40]: https://github.com/KotlinCrypto/core/pull/40 179 | [43]: https://github.com/KotlinCrypto/core/pull/43 180 | [44]: https://github.com/KotlinCrypto/core/pull/44 181 | [46]: https://github.com/KotlinCrypto/core/pull/46 182 | [48]: https://github.com/KotlinCrypto/core/pull/48 183 | [51]: https://github.com/KotlinCrypto/core/pull/51 184 | [52]: https://github.com/KotlinCrypto/core/pull/52 185 | [55]: https://github.com/KotlinCrypto/core/pull/55 186 | [56]: https://github.com/KotlinCrypto/core/pull/56 187 | [58]: https://github.com/KotlinCrypto/core/pull/58 188 | [61]: https://github.com/KotlinCrypto/core/pull/61 189 | [64]: https://github.com/KotlinCrypto/core/pull/64 190 | [66]: https://github.com/KotlinCrypto/core/pull/66 191 | [70]: https://github.com/KotlinCrypto/core/pull/70 192 | [108]: https://github.com/KotlinCrypto/core/pull/108 193 | [109]: https://github.com/KotlinCrypto/core/pull/109 194 | [111]: https://github.com/KotlinCrypto/core/pull/111 195 | [116]: https://github.com/KotlinCrypto/core/pull/116 196 | [118]: https://github.com/KotlinCrypto/core/pull/118 197 | [122]: https://github.com/KotlinCrypto/core/pull/122 198 | [124]: https://github.com/KotlinCrypto/core/pull/124 199 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # core 2 | [![badge-license]][url-license] 3 | [![badge-latest-release]][url-latest-release] 4 | 5 | [![badge-kotlin]][url-kotlin] 6 | [![badge-error]][url-error] 7 | 8 | ![badge-platform-android] 9 | ![badge-platform-jvm] 10 | ![badge-platform-js] 11 | ![badge-platform-js-node] 12 | ![badge-platform-wasm] 13 | ![badge-platform-linux] 14 | ![badge-platform-macos] 15 | ![badge-platform-ios] 16 | ![badge-platform-tvos] 17 | ![badge-platform-watchos] 18 | ![badge-platform-windows] 19 | ![badge-support-android-native] 20 | ![badge-support-apple-silicon] 21 | ![badge-support-js-ir] 22 | ![badge-support-linux-arm] 23 | 24 | Low level core cryptographic components for Kotlin Multiplatform 25 | 26 | NOTE: For Jvm, `Digest` extends `java.security.MessageDigest` and `Mac` extends `javax.crypto.Mac` 27 | for interoperability. 28 | 29 | ### Modules 30 | 31 | - [core](library/core/README.md) 32 | - [digest](library/digest/README.md) 33 | - [mac](library/mac/README.md) 34 | - [xof](library/xof/README.md) 35 | 36 | ### API Docs 37 | 38 | - [https://core.kotlincrypto.org][url-docs] 39 | 40 | ### Library Authors 41 | 42 | Modules in `core` are intentionally **single purpose** and **small** such that you 43 | are able to include them in your APIs without having to import some massive crypto 44 | library. Consumers of your APIs can then import the higher level implementations 45 | to use with your library (giving them the **choice** of what algorithms they wish 46 | to use, importing only what they need). 47 | 48 | This also means that as new higher level functions get implemented, you do not need 49 | to do anything. 50 | 51 | ```kotlin 52 | /** 53 | * This feature of Foo requires a dependency if you wish to use it. 54 | * See: https://github.com/KotlinCrypto/hash 55 | * */ 56 | class FooFeatureA(digest: Digest) { 57 | // ... 58 | } 59 | ``` 60 | 61 | ### Get Started 62 | 63 | The best way to keep `KotlinCrypto` dependencies up to date is by using the 64 | [version-catalog][url-version-catalog]. Alternatively, see below. 65 | 66 | 67 | 68 | ```kotlin 69 | // build.gradle.kts 70 | dependencies { 71 | val core = "0.7.0" 72 | implementation("org.kotlincrypto.core:digest:$core") 73 | implementation("org.kotlincrypto.core:mac:$core") 74 | implementation("org.kotlincrypto.core:xof:$core") 75 | } 76 | ``` 77 | 78 | 79 | [badge-latest-release]: https://img.shields.io/badge/latest--release-0.7.0-blue.svg?style=flat 80 | [badge-license]: https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat 81 | 82 | 83 | [badge-kotlin]: https://img.shields.io/badge/kotlin-2.1.10-blue.svg?logo=kotlin 84 | [badge-error]: https://img.shields.io/badge/kotlincrypto.error-0.3.0-blue.svg 85 | 86 | 87 | [badge-platform-android]: http://img.shields.io/badge/-android-6EDB8D.svg?style=flat 88 | [badge-platform-jvm]: http://img.shields.io/badge/-jvm-DB413D.svg?style=flat 89 | [badge-platform-js]: http://img.shields.io/badge/-js-F8DB5D.svg?style=flat 90 | [badge-platform-js-node]: https://img.shields.io/badge/-nodejs-68a063.svg?style=flat 91 | [badge-platform-linux]: http://img.shields.io/badge/-linux-2D3F6C.svg?style=flat 92 | [badge-platform-macos]: http://img.shields.io/badge/-macos-111111.svg?style=flat 93 | [badge-platform-ios]: http://img.shields.io/badge/-ios-CDCDCD.svg?style=flat 94 | [badge-platform-tvos]: http://img.shields.io/badge/-tvos-808080.svg?style=flat 95 | [badge-platform-watchos]: http://img.shields.io/badge/-watchos-C0C0C0.svg?style=flat 96 | [badge-platform-wasm]: https://img.shields.io/badge/-wasm-624FE8.svg?style=flat 97 | [badge-platform-windows]: http://img.shields.io/badge/-windows-4D76CD.svg?style=flat 98 | [badge-support-android-native]: http://img.shields.io/badge/support-[AndroidNative]-6EDB8D.svg?style=flat 99 | [badge-support-apple-silicon]: http://img.shields.io/badge/support-[AppleSilicon]-43BBFF.svg?style=flat 100 | [badge-support-js-ir]: https://img.shields.io/badge/support-[js--IR]-AAC4E0.svg?style=flat 101 | [badge-support-linux-arm]: http://img.shields.io/badge/support-[LinuxArm]-2D3F6C.svg?style=flat 102 | [badge-support-linux-mips]: http://img.shields.io/badge/support-[LinuxMIPS]-2D3F6C.svg?style=flat 103 | 104 | [url-latest-release]: https://github.com/KotlinCrypto/core/releases/latest 105 | [url-license]: https://www.apache.org/licenses/LICENSE-2.0.txt 106 | [url-kotlin]: https://kotlinlang.org 107 | [url-error]: https://github.com/KotlinCrypto/error 108 | [url-version-catalog]: https://github.com/KotlinCrypto/version-catalog 109 | [url-docs]: https://core.kotlincrypto.org 110 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | The release process is documented [HERE](https://github.com/KotlinCrypto/documentation/blob/master/RELEASING.md) 4 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # benchmarks 2 | 3 | Benchmarks for tracking performance of `core` implementation. 4 | 5 | **NOTE:** Benchmarking is run on every Pull Request. Results can be viewed for each 6 | workflow run on the [GitHub Actions][url-actions] tab of the repository. 7 | 8 | - Run All platforms: 9 | ```shell 10 | ./gradlew benchmark 11 | ``` 12 | 13 | - Run Jvm: 14 | ```shell 15 | ./gradlew jvmBenchmark 16 | ``` 17 | 18 | - Run Js: 19 | ```shell 20 | ./gradlew jsBenchmark 21 | ``` 22 | 23 | - Run WasmJs: 24 | ```shell 25 | ./gradlew wasmJsBenchmark 26 | ``` 27 | 28 | - Run Native: 29 | ```shell 30 | ./gradlew nativeHostBenchmark 31 | ``` 32 | 33 | [url-actions]: https://github.com/KotlinCrypto/core/actions/ 34 | -------------------------------------------------------------------------------- /benchmarks/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | import io.matthewnelson.kmp.configuration.extension.container.target.KmpTarget 17 | import kotlinx.benchmark.gradle.BenchmarksExtension 18 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 19 | import org.jetbrains.kotlin.gradle.plugin.KotlinTarget 20 | import org.jetbrains.kotlin.konan.target.HostManager 21 | import org.jetbrains.kotlin.konan.target.KonanTarget 22 | 23 | plugins { 24 | id("configuration") 25 | } 26 | 27 | kmpConfiguration { 28 | val benchmarks by lazy { extensions.getByType() } 29 | 30 | configure { 31 | fun KmpTarget.register() { 32 | target { benchmarks.targets.register(name) } 33 | } 34 | 35 | jvm { register() } 36 | 37 | js { target { browser(); nodejs() }; register() } 38 | @OptIn(ExperimentalWasmDsl::class) 39 | wasmJs { target { browser(); nodejs() }; register() } 40 | 41 | val nativeHost = "nativeHost" 42 | when (HostManager.host) { 43 | is KonanTarget.LINUX_X64 -> linuxX64(nativeHost) { register() } 44 | is KonanTarget.LINUX_ARM64 -> linuxArm64(nativeHost) { register() } 45 | is KonanTarget.MACOS_X64 -> macosX64(nativeHost) { register() } 46 | is KonanTarget.MACOS_ARM64 -> macosArm64(nativeHost) { register() } 47 | is KonanTarget.MINGW_X64 -> mingwX64(nativeHost) { register() } 48 | else -> {} 49 | } 50 | 51 | common { 52 | pluginIds(libs.plugins.benchmark.get().pluginId) 53 | 54 | sourceSetMain { 55 | dependencies { 56 | implementation(libs.benchmark.runtime) 57 | implementation(project(":library:digest")) 58 | implementation(project(":library:xof")) 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestOps.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("unused") 17 | 18 | package org.kotlincrypto.core.benchmarks 19 | 20 | import kotlinx.benchmark.* 21 | import org.kotlincrypto.core.digest.Digest 22 | import kotlin.random.Random 23 | 24 | @State(Scope.Benchmark) 25 | @BenchmarkMode(Mode.AverageTime) 26 | @OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS) 27 | @Warmup(iterations = 5, time = 2) 28 | @Measurement(iterations = 5, time = 3) 29 | open class DigestBenchmark { 30 | 31 | private class TestDigest: Digest { 32 | constructor(): super("Benchmark", 32, 32) 33 | private constructor(other: TestDigest): super(other) 34 | override fun resetProtected() {} 35 | override fun copy(): TestDigest = TestDigest(this) 36 | override fun compressProtected(input: ByteArray, offset: Int) {} 37 | override fun digestProtected(buf: ByteArray, bufPos: Int): ByteArray = ByteArray(0) 38 | } 39 | 40 | private val digest = TestDigest() 41 | private val bytes = Random.Default.nextBytes(100) 42 | 43 | @Setup 44 | fun setup() { digest.update(bytes, 0, digest.blockSize()) } 45 | 46 | @Benchmark 47 | fun update() { digest.update(bytes) } 48 | } 49 | -------------------------------------------------------------------------------- /benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("unused") 17 | 18 | package org.kotlincrypto.core.benchmarks 19 | 20 | import kotlinx.benchmark.* 21 | import org.kotlincrypto.core.InternalKotlinCryptoApi 22 | import org.kotlincrypto.core.xof.Xof 23 | 24 | @State(Scope.Benchmark) 25 | @BenchmarkMode(Mode.AverageTime) 26 | @OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS) 27 | @Warmup(iterations = 5, time = 2) 28 | @Measurement(iterations = 5, time = 3) 29 | @OptIn(InternalKotlinCryptoApi::class) 30 | open class XofUtilsBenchmark { 31 | 32 | @Benchmark 33 | fun leftEncodeLong() { 34 | Xof.Utils.leftEncode(-99993873488683833L) 35 | } 36 | 37 | @Benchmark 38 | fun leftEncodeInt() { 39 | Xof.Utils.leftEncode( 11978435) 40 | } 41 | 42 | @Benchmark 43 | fun leftEncodeLoHi() { 44 | Xof.Utils.leftEncode(184581845, 11978435) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /build-logic/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | plugins { 17 | `kotlin-dsl` 18 | } 19 | 20 | dependencies { 21 | implementation(libs.gradle.dokka) 22 | implementation(libs.gradle.kmp.configuration) 23 | implementation(libs.gradle.kotlin) 24 | implementation(libs.gradle.publish.maven) 25 | } 26 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("UnstableApiUsage") 17 | 18 | rootProject.name = "build-logic" 19 | 20 | dependencyResolutionManagement { 21 | repositories { 22 | mavenCentral() 23 | gradlePluginPortal() 24 | } 25 | 26 | versionCatalogs { 27 | create("libs") { 28 | from(files("../gradle/libs.versions.toml")) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/-KmpConfigurationExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | import io.matthewnelson.kmp.configuration.extension.KmpConfigurationExtension 17 | import io.matthewnelson.kmp.configuration.extension.container.target.KmpConfigurationContainerDsl 18 | import org.gradle.api.Action 19 | import org.gradle.api.JavaVersion 20 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 21 | 22 | fun KmpConfigurationExtension.configureShared( 23 | java9ModuleName: String? = null, 24 | publish: Boolean = false, 25 | action: Action 26 | ) { 27 | if (publish) { 28 | require(!java9ModuleName.isNullOrBlank()) { "publications must specify a module-info name" } 29 | } 30 | 31 | configure { 32 | options { 33 | useUniqueModuleNames = true 34 | } 35 | 36 | jvm { 37 | kotlinJvmTarget = JavaVersion.VERSION_1_8 38 | compileSourceCompatibility = JavaVersion.VERSION_1_8 39 | compileTargetCompatibility = JavaVersion.VERSION_1_8 40 | 41 | java9ModuleInfoName = java9ModuleName 42 | } 43 | 44 | js() 45 | @OptIn(ExperimentalWasmDsl::class) 46 | wasmJs { 47 | target { 48 | browser() 49 | nodejs() 50 | } 51 | } 52 | @OptIn(ExperimentalWasmDsl::class) 53 | wasmWasi { 54 | target { 55 | nodejs() 56 | } 57 | } 58 | 59 | androidNativeAll() 60 | 61 | iosAll() 62 | macosAll() 63 | tvosAll() 64 | watchosAll() 65 | 66 | linuxAll() 67 | mingwAll() 68 | 69 | common { 70 | if (publish) pluginIds("publication", "dokka") 71 | 72 | sourceSetTest { 73 | dependencies { 74 | implementation(kotlin("test")) 75 | } 76 | } 77 | } 78 | 79 | if (publish) kotlin { explicitApi() } 80 | 81 | action.execute(this) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/configuration.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | import org.gradle.api.tasks.testing.logging.TestExceptionFormat 17 | import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED 18 | import org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED 19 | import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED 20 | import org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR 21 | import org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_OUT 22 | import org.gradle.api.tasks.testing.logging.TestLogEvent.STARTED 23 | 24 | plugins { 25 | id("io.matthewnelson.kmp.configuration") 26 | } 27 | 28 | tasks.withType { 29 | testLogging { 30 | exceptionFormat = TestExceptionFormat.FULL 31 | events(STARTED, PASSED, SKIPPED, FAILED, STANDARD_ERROR, STANDARD_OUT) 32 | showStandardStreams = true 33 | showStackTraces = true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/dokka.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | import org.jetbrains.dokka.gradle.DokkaExtension 17 | import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier 18 | import java.net.URI 19 | import java.time.LocalDate 20 | 21 | plugins { 22 | id("org.jetbrains.dokka") 23 | } 24 | 25 | rootProject.dependencies { dokka(project(project.path)) } 26 | 27 | extensions.configure { 28 | dokkaPublications.configureEach { 29 | suppressObviousFunctions.set(true) 30 | } 31 | 32 | dokkaSourceSets.configureEach { 33 | includes.from("README.md") 34 | enableKotlinStdLibDocumentationLink.set(false) 35 | 36 | externalDocumentationLinks { 37 | register(project.path + ":error") { 38 | url.set(URI("https://error.kotlincrypto.org/")) 39 | } 40 | } 41 | 42 | sourceLink { 43 | localDirectory.set(rootDir) 44 | remoteUrl.set(URI("https://github.com/KotlinCrypto/core/tree/master")) 45 | remoteLineSuffix.set("#L") 46 | } 47 | 48 | documentedVisibilities( 49 | VisibilityModifier.Public, 50 | VisibilityModifier.Protected, 51 | ) 52 | } 53 | 54 | pluginsConfiguration.html { 55 | footerMessage.set("© 2023-${LocalDate.now().year} Copyright KotlinCrypto") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/publication.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | import org.gradle.plugins.signing.SigningExtension 17 | 18 | plugins { 19 | id("com.vanniktech.maven.publish") 20 | } 21 | 22 | if (!version.toString().endsWith("-SNAPSHOT")) { 23 | extensions.configure("signing") { 24 | useGpgCmd() 25 | } 26 | } 27 | 28 | tasks.withType().configureEach { 29 | isPreserveFileTimestamps = false 30 | isReproducibleFileOrder = true 31 | } 32 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport 17 | import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin 18 | import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension 19 | 20 | plugins { 21 | alias(libs.plugins.android.library) apply(false) 22 | alias(libs.plugins.benchmark) apply(false) 23 | alias(libs.plugins.binary.compat) 24 | alias(libs.plugins.dokka) 25 | alias(libs.plugins.kotlin.multiplatform) apply(false) 26 | } 27 | 28 | allprojects { 29 | // https://github.com/Kotlin/dokka/issues/4030#issuecomment-2669254887 30 | if (this.project == this.rootProject) { 31 | group = "root" 32 | } else { 33 | findProperty("GROUP")?.let { group = it } 34 | } 35 | 36 | findProperty("VERSION_NAME")?.let { version = it } 37 | findProperty("POM_DESCRIPTION")?.let { description = it.toString() } 38 | 39 | repositories { 40 | mavenCentral() 41 | 42 | if (version.toString().endsWith("-SNAPSHOT")) { 43 | // Only allow snapshot dependencies for non-release versions. 44 | // This would cause a build failure if attempting to make a release 45 | // while depending on a -SNAPSHOT version (such as core). 46 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") 47 | } 48 | } 49 | } 50 | 51 | @Suppress("PropertyName") 52 | val CHECK_PUBLICATION = findProperty("CHECK_PUBLICATION") != null 53 | 54 | plugins.withType { 55 | the().lockFileDirectory = rootDir.resolve(".kotlin-js-store") 56 | if (CHECK_PUBLICATION) { 57 | the().yarnLockMismatchReport = YarnLockMismatchReport.NONE 58 | } 59 | } 60 | 61 | apiValidation { 62 | @OptIn(kotlinx.validation.ExperimentalBCVApi::class) 63 | klib.enabled = findProperty("KMP_TARGETS") == null 64 | 65 | if (CHECK_PUBLICATION) { 66 | ignoredProjects.add("check-publication") 67 | } else { 68 | ignoredProjects.add("benchmarks") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gh-pages/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) 2025 KotlinCrypto 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | set -e 16 | 17 | readonly DIR_SCRIPT="$( cd "$( dirname "$0" )" >/dev/null && pwd )" 18 | readonly REPO_NAME="core" 19 | 20 | trap 'rm -rf "$DIR_SCRIPT/$REPO_NAME"' EXIT 21 | 22 | cd "$DIR_SCRIPT" 23 | git clone -b gh-pages --single-branch https://github.com/KotlinCrypto/$REPO_NAME.git 24 | rm -rf "$DIR_SCRIPT/$REPO_NAME/"* 25 | echo "$REPO_NAME.kotlincrypto.org" > "$DIR_SCRIPT/$REPO_NAME/CNAME" 26 | 27 | cd .. 28 | ./gradlew clean -DKMP_TARGETS_ALL 29 | ./gradlew dokkaGenerate --no-build-cache -DKMP_TARGETS_ALL 30 | cp -aR build/dokka/html/* gh-pages/$REPO_NAME 31 | 32 | cd "$DIR_SCRIPT/$REPO_NAME" 33 | sed -i "s|module:|module:library/|g" "package-list" 34 | 35 | git add --all 36 | git commit -S --message "Update dokka docs" 37 | git push 38 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 2 | org.gradle.parallel=true 3 | org.gradle.caching=true 4 | 5 | android.useAndroidX=true 6 | android.enableJetifier=true 7 | 8 | kotlin.code.style=official 9 | kotlin.mpp.applyDefaultHierarchyTemplate=false 10 | kotlin.mpp.stability.nowarn=true 11 | kotlin.native.ignoreDisabledTargets=true 12 | 13 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 14 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true 15 | 16 | SONATYPE_HOST=S01 17 | RELEASE_SIGNING_ENABLED=true 18 | 19 | GROUP=org.kotlincrypto.core 20 | 21 | POM_INCEPTION_YEAR=2023 22 | 23 | POM_URL=https://github.com/KotlinCrypto/core/ 24 | POM_SCM_URL=https://github.com/KotlinCrypto/core/ 25 | POM_SCM_CONNECTION=scm:git:git://github.com/KotlinCrypto/core.git 26 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/KotlinCrypto/core.git 27 | 28 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 29 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 30 | POM_LICENCE_DIST=repo 31 | 32 | POM_DEVELOPER_ID=KotlinCrypto 33 | POM_DEVELOPER_NAME=Kotlin Crypto 34 | POM_DEVELOPER_URL=https://github.com/KotlinCrypto/ 35 | 36 | VERSION_NAME=0.7.1-SNAPSHOT 37 | # 0.1.0-alpha01 = 00 01 00 11 38 | # 0.1.0-beta01 = 00 01 00 21 39 | # 0.1.0-rc01 = 00 01 00 31 40 | # 0.1.0 = 00 01 00 99 41 | # 1.1.0 = 01 01 00 99 42 | VERSION_CODE=00070111 43 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | #noinspection GradleDependency 3 | androidx-test-runner = "1.5.2" # Do not upgrade. Tests run for API 15+, where next version requires minSdk 19+ 4 | 5 | gradle-benchmark = "0.4.13" 6 | gradle-android = "8.7.3" 7 | gradle-binary-compat = "0.17.0" 8 | gradle-dokka = "2.0.0" 9 | gradle-kmp-configuration = "0.4.0" 10 | gradle-kotlin = "2.1.10" 11 | gradle-publish-maven = "0.30.0" 12 | 13 | kotlincrypto-error = "0.3.0" 14 | 15 | [libraries] 16 | gradle-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "gradle-dokka" } 17 | gradle-kmp-configuration = { module = "io.matthewnelson:gradle-kmp-configuration-plugin", version.ref = "gradle-kmp-configuration" } 18 | gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "gradle-kotlin" } 19 | gradle-publish-maven = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradle-publish-maven" } 20 | 21 | kotlincrypto-error = { module = "org.kotlincrypto:error", version.ref = "kotlincrypto-error" } 22 | 23 | # tests & tools 24 | androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } 25 | benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "gradle-benchmark" } 26 | 27 | [plugins] 28 | android-library = { id = "com.android.library", version.ref = "gradle-android" } 29 | benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "gradle-benchmark" } 30 | binary-compat = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "gradle-binary-compat" } 31 | dokka = { id = "org.jetbrains.dokka", version.ref = "gradle-dokka" } 32 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "gradle-kotlin" } 33 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinCrypto/core/c40795a456fa382f1f1d3844b94bd8f6dc8d4a6f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStorePath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | 6 | # https://gradle.org/release-checksums/ 7 | distributionSha256Sum=296742a352f0b20ec14b143fb684965ad66086c7810b7b255dee216670716175 8 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip 9 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /library/core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/core/README.md: -------------------------------------------------------------------------------- 1 | # Module core 2 | 3 | Very small module, only containing public interfaces and annotations. Is exported by all other modules. 4 | -------------------------------------------------------------------------------- /library/core/api/core.api: -------------------------------------------------------------------------------- 1 | public abstract interface class org/kotlincrypto/core/Algorithm { 2 | public abstract fun algorithm ()Ljava/lang/String; 3 | } 4 | 5 | public abstract interface class org/kotlincrypto/core/Copyable { 6 | public abstract fun copy ()Ljava/lang/Object; 7 | } 8 | 9 | public abstract interface annotation class org/kotlincrypto/core/ExperimentalKotlinCryptoApi : java/lang/annotation/Annotation { 10 | } 11 | 12 | public abstract interface annotation class org/kotlincrypto/core/InternalKotlinCryptoApi : java/lang/annotation/Annotation { 13 | } 14 | 15 | public abstract interface class org/kotlincrypto/core/Resettable { 16 | public abstract fun reset ()V 17 | } 18 | 19 | public abstract interface class org/kotlincrypto/core/Updatable { 20 | public abstract fun update (B)V 21 | public abstract fun update ([B)V 22 | public abstract fun update ([BII)V 23 | } 24 | 25 | public final class org/kotlincrypto/core/_AndroidSdkIntKt { 26 | public static final fun getKC_ANDROID_SDK_INT ()Ljava/lang/Integer; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /library/core/api/core.klib.api: -------------------------------------------------------------------------------- 1 | // Klib ABI Dump 2 | // Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] 3 | // Rendering settings: 4 | // - Signature version: 2 5 | // - Show manifest properties: true 6 | // - Show declarations: true 7 | 8 | // Library unique name: 9 | open annotation class org.kotlincrypto.core/ExperimentalKotlinCryptoApi : kotlin/Annotation { // org.kotlincrypto.core/ExperimentalKotlinCryptoApi|null[0] 10 | constructor () // org.kotlincrypto.core/ExperimentalKotlinCryptoApi.|(){}[0] 11 | } 12 | 13 | open annotation class org.kotlincrypto.core/InternalKotlinCryptoApi : kotlin/Annotation { // org.kotlincrypto.core/InternalKotlinCryptoApi|null[0] 14 | constructor () // org.kotlincrypto.core/InternalKotlinCryptoApi.|(){}[0] 15 | } 16 | 17 | abstract interface <#A: out kotlin/Any> org.kotlincrypto.core/Copyable { // org.kotlincrypto.core/Copyable|null[0] 18 | abstract fun copy(): #A // org.kotlincrypto.core/Copyable.copy|copy(){}[0] 19 | } 20 | 21 | abstract interface org.kotlincrypto.core/Algorithm { // org.kotlincrypto.core/Algorithm|null[0] 22 | abstract fun algorithm(): kotlin/String // org.kotlincrypto.core/Algorithm.algorithm|algorithm(){}[0] 23 | } 24 | 25 | abstract interface org.kotlincrypto.core/Resettable { // org.kotlincrypto.core/Resettable|null[0] 26 | abstract fun reset() // org.kotlincrypto.core/Resettable.reset|reset(){}[0] 27 | } 28 | 29 | abstract interface org.kotlincrypto.core/Updatable { // org.kotlincrypto.core/Updatable|null[0] 30 | abstract fun update(kotlin/Byte) // org.kotlincrypto.core/Updatable.update|update(kotlin.Byte){}[0] 31 | abstract fun update(kotlin/ByteArray) // org.kotlincrypto.core/Updatable.update|update(kotlin.ByteArray){}[0] 32 | abstract fun update(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core/Updatable.update|update(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] 33 | } 34 | -------------------------------------------------------------------------------- /library/core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | plugins { 17 | id("configuration") 18 | } 19 | 20 | kmpConfiguration { 21 | configureShared(java9ModuleName = "org.kotlincrypto.core", publish = true) { 22 | common { 23 | sourceSetMain { 24 | dependencies { 25 | api(libs.kotlincrypto.error) 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /library/core/gradle.properties: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 KotlinCrypto 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | POM_ARTIFACT_ID=core 15 | POM_NAME=KotlinCrypto Core Common 16 | POM_DESCRIPTION=Common core cryptography components 17 | -------------------------------------------------------------------------------- /library/core/src/commonMain/kotlin/org/kotlincrypto/core/Algorithm.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core 17 | 18 | public interface Algorithm { 19 | 20 | /** 21 | * Returns a string that identifies the algorithm, independent of 22 | * implementation details. The name should be a standard Java Security 23 | * name (such as "SHA-256"). 24 | * */ 25 | public fun algorithm(): String 26 | } 27 | -------------------------------------------------------------------------------- /library/core/src/commonMain/kotlin/org/kotlincrypto/core/Annotation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("SpellCheckingInspection") 17 | 18 | package org.kotlincrypto.core 19 | 20 | /** 21 | * Denotes an api as experimental, such that it may change 22 | * inadvertently without notice. 23 | * 24 | * Any usage of a declaration annotated with [ExperimentalKotlinCryptoApi] 25 | * must be accepted by annotating that usage with the [OptIn] 26 | * annotation, e.g. @OptIn(ExperimentalKotlinCryptoApi::class), or by using 27 | * the following compiler argument: 28 | * 29 | * -Xopt-in=org.kotlincrypto.core.ExperimentalKotlinCryptoApi 30 | * */ 31 | @RequiresOptIn 32 | @MustBeDocumented 33 | @Target( 34 | AnnotationTarget.CLASS, 35 | AnnotationTarget.ANNOTATION_CLASS, 36 | AnnotationTarget.PROPERTY, 37 | AnnotationTarget.FIELD, 38 | AnnotationTarget.LOCAL_VARIABLE, 39 | AnnotationTarget.VALUE_PARAMETER, 40 | AnnotationTarget.CONSTRUCTOR, 41 | AnnotationTarget.FUNCTION, 42 | AnnotationTarget.PROPERTY_GETTER, 43 | AnnotationTarget.PROPERTY_SETTER, 44 | AnnotationTarget.TYPEALIAS 45 | ) 46 | @Retention(AnnotationRetention.BINARY) 47 | public annotation class ExperimentalKotlinCryptoApi 48 | 49 | /** 50 | * Denotes an api as internal, and subject to change at any time. 51 | * 52 | * Any usage of a declaration annotated with [InternalKotlinCryptoApi] 53 | * must be accepted by annotating that usage with the [OptIn] 54 | * annotation, e.g. @OptIn(InternalKotlinCryptoApi::class), or by using 55 | * the following compiler argument: 56 | * 57 | * -Xopt-in=org.kotlincrypto.core.InternalKotlinCryptoApi 58 | * */ 59 | @RequiresOptIn 60 | @MustBeDocumented 61 | @Target( 62 | AnnotationTarget.CLASS, 63 | AnnotationTarget.ANNOTATION_CLASS, 64 | AnnotationTarget.PROPERTY, 65 | AnnotationTarget.FIELD, 66 | AnnotationTarget.LOCAL_VARIABLE, 67 | AnnotationTarget.VALUE_PARAMETER, 68 | AnnotationTarget.CONSTRUCTOR, 69 | AnnotationTarget.FUNCTION, 70 | AnnotationTarget.PROPERTY_GETTER, 71 | AnnotationTarget.PROPERTY_SETTER, 72 | AnnotationTarget.TYPEALIAS 73 | ) 74 | @Retention(AnnotationRetention.BINARY) 75 | public annotation class InternalKotlinCryptoApi 76 | -------------------------------------------------------------------------------- /library/core/src/commonMain/kotlin/org/kotlincrypto/core/Copyable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core 17 | 18 | public interface Copyable { 19 | 20 | /** 21 | * Copies the instance and its state to a new instance that 22 | * is wholly independent of the old instance. 23 | * */ 24 | public fun copy(): T 25 | } 26 | -------------------------------------------------------------------------------- /library/core/src/commonMain/kotlin/org/kotlincrypto/core/Resettable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core 17 | 18 | public interface Resettable { 19 | 20 | /** 21 | * Resets the instance for further use. 22 | * */ 23 | public fun reset() 24 | } 25 | -------------------------------------------------------------------------------- /library/core/src/commonMain/kotlin/org/kotlincrypto/core/Updatable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core 17 | 18 | public interface Updatable { 19 | 20 | /** 21 | * Updates the instance with the specified byte. 22 | * 23 | * @param [input] The byte to update the instance with. 24 | * */ 25 | public fun update(input: Byte) 26 | 27 | /** 28 | * Updates the instance with the specified array of bytes. 29 | * 30 | * @param [input] The array of bytes to update the instance with. 31 | * */ 32 | public fun update(input: ByteArray) 33 | 34 | /** 35 | * Updates the instance with the specified array of bytes, 36 | * starting at [offset] 37 | * 38 | * @param [input] The array of bytes to update the instance with. 39 | * @param [offset] The index to start from in the array of bytes. 40 | * @param [len] The number of bytes to use, starting at [offset]. 41 | * @throws [IllegalArgumentException] if [input] size is inappropriate 42 | * for given [len] parameter. 43 | * @throws [IndexOutOfBoundsException] if [offset] and [len] are 44 | * inappropriate. 45 | * */ 46 | public fun update(input: ByteArray, offset: Int, len: Int) 47 | } 48 | -------------------------------------------------------------------------------- /library/core/src/jvmMain/java9/module-info.java: -------------------------------------------------------------------------------- 1 | module org.kotlincrypto.core { 2 | requires transitive kotlin.stdlib; 3 | requires transitive org.kotlincrypto.error; 4 | 5 | exports org.kotlincrypto.core; 6 | } 7 | -------------------------------------------------------------------------------- /library/core/src/jvmMain/kotlin/org/kotlincrypto/core/-AndroidSdkInt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core 17 | 18 | @InternalKotlinCryptoApi 19 | public val KC_ANDROID_SDK_INT: Int? by lazy { 20 | if ( 21 | System.getProperty("java.runtime.name") 22 | ?.contains("android", ignoreCase = true) != true 23 | ) { 24 | // Not Android runtime 25 | return@lazy null 26 | } 27 | 28 | try { 29 | val clazz = Class.forName("android.os.Build\$VERSION") 30 | 31 | try { 32 | clazz?.getField("SDK_INT")?.getInt(null) 33 | } catch (_: Throwable) { 34 | clazz?.getField("SDK")?.get(null)?.toString()?.toIntOrNull() 35 | } 36 | } catch (_: Throwable) { 37 | null 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /library/digest/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/digest/README.md: -------------------------------------------------------------------------------- 1 | # Module digest 2 | 3 | `Digest` abstraction for hashing. 4 | 5 | Implementations can be found at [KotlinCrypto/hash][url-hash] 6 | 7 | ```kotlin 8 | // Using SHA256 from KotlinCrypto/hash repo as an example 9 | import org.kotlincrypto.hash.sha2.SHA256 10 | 11 | fun main() { 12 | val digest = SHA256() 13 | val bytes = Random.Default.nextBytes(615) 14 | 15 | // Digest implements interface Algorithm 16 | println(digest.algorithm()) 17 | // SHA-256 18 | 19 | // Digest implements interface Updatable 20 | digest.update(5.toByte()) 21 | digest.update(bytes) 22 | digest.update(bytes, 10, 88) 23 | 24 | // Digest implements interface Resettable 25 | digest.reset() 26 | 27 | digest.update(bytes) 28 | 29 | // Digest implements interface Copyable 30 | val copy = digest.copy() 31 | 32 | val hash = digest.digest() 33 | val hash2 = copy.digest(bytes) 34 | val hash3 = ByteArray(digest.digestLength()) 35 | digest.update(bytes) 36 | digest.digestInto(hash3, destOffset = 0) 37 | } 38 | ``` 39 | 40 | [url-hash]: https://github.com/KotlinCrypto/hash 41 | -------------------------------------------------------------------------------- /library/digest/api/digest.api: -------------------------------------------------------------------------------- 1 | public abstract class org/kotlincrypto/core/digest/Digest : java/security/MessageDigest, java/lang/Cloneable, org/kotlincrypto/core/Algorithm, org/kotlincrypto/core/Copyable, org/kotlincrypto/core/Resettable, org/kotlincrypto/core/Updatable { 2 | protected fun (Ljava/lang/String;II)V 3 | protected fun (Lorg/kotlincrypto/core/digest/Digest;)V 4 | public final fun algorithm ()Ljava/lang/String; 5 | public final fun blockSize ()I 6 | public final fun clone ()Ljava/lang/Object; 7 | protected abstract fun compressProtected ([BI)V 8 | public final fun digest ()[B 9 | public final fun digest ([B)[B 10 | public final fun digest ([BII)I 11 | public final fun digestInto ([BI)I 12 | protected fun digestIntoProtected ([BI[BI)V 13 | public final fun digestLength ()I 14 | protected abstract fun digestProtected ([BI)[B 15 | protected final fun engineDigest ()[B 16 | protected final fun engineDigest ([BII)I 17 | protected final fun engineGetDigestLength ()I 18 | protected final fun engineReset ()V 19 | protected final fun engineUpdate (B)V 20 | protected final fun engineUpdate (Ljava/nio/ByteBuffer;)V 21 | protected final fun engineUpdate ([BII)V 22 | public final fun equals (Ljava/lang/Object;)Z 23 | public final fun hashCode ()I 24 | public final fun reset ()V 25 | protected abstract fun resetProtected ()V 26 | public final fun toString ()Ljava/lang/String; 27 | public final fun update (B)V 28 | public final fun update ([B)V 29 | public final fun update ([BII)V 30 | protected fun updateProtected (B)V 31 | protected fun updateProtected ([BII)V 32 | } 33 | 34 | public abstract class org/kotlincrypto/core/digest/internal/DigestState { 35 | } 36 | 37 | -------------------------------------------------------------------------------- /library/digest/api/digest.klib.api: -------------------------------------------------------------------------------- 1 | // Klib ABI Dump 2 | // Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] 3 | // Rendering settings: 4 | // - Signature version: 2 5 | // - Show manifest properties: true 6 | // - Show declarations: true 7 | 8 | // Library unique name: 9 | abstract class org.kotlincrypto.core.digest/Digest : org.kotlincrypto.core/Algorithm, org.kotlincrypto.core/Copyable, org.kotlincrypto.core/Resettable, org.kotlincrypto.core/Updatable { // org.kotlincrypto.core.digest/Digest|null[0] 10 | constructor (kotlin/String, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.digest/Digest.|(kotlin.String;kotlin.Int;kotlin.Int){}[0] 11 | constructor (org.kotlincrypto.core.digest/Digest) // org.kotlincrypto.core.digest/Digest.|(org.kotlincrypto.core.digest.Digest){}[0] 12 | 13 | abstract fun compressProtected(kotlin/ByteArray, kotlin/Int) // org.kotlincrypto.core.digest/Digest.compressProtected|compressProtected(kotlin.ByteArray;kotlin.Int){}[0] 14 | abstract fun digestProtected(kotlin/ByteArray, kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.digest/Digest.digestProtected|digestProtected(kotlin.ByteArray;kotlin.Int){}[0] 15 | abstract fun resetProtected() // org.kotlincrypto.core.digest/Digest.resetProtected|resetProtected(){}[0] 16 | final fun algorithm(): kotlin/String // org.kotlincrypto.core.digest/Digest.algorithm|algorithm(){}[0] 17 | final fun blockSize(): kotlin/Int // org.kotlincrypto.core.digest/Digest.blockSize|blockSize(){}[0] 18 | final fun digest(): kotlin/ByteArray // org.kotlincrypto.core.digest/Digest.digest|digest(){}[0] 19 | final fun digest(kotlin/ByteArray): kotlin/ByteArray // org.kotlincrypto.core.digest/Digest.digest|digest(kotlin.ByteArray){}[0] 20 | final fun digestInto(kotlin/ByteArray, kotlin/Int): kotlin/Int // org.kotlincrypto.core.digest/Digest.digestInto|digestInto(kotlin.ByteArray;kotlin.Int){}[0] 21 | final fun digestLength(): kotlin/Int // org.kotlincrypto.core.digest/Digest.digestLength|digestLength(){}[0] 22 | final fun equals(kotlin/Any?): kotlin/Boolean // org.kotlincrypto.core.digest/Digest.equals|equals(kotlin.Any?){}[0] 23 | final fun hashCode(): kotlin/Int // org.kotlincrypto.core.digest/Digest.hashCode|hashCode(){}[0] 24 | final fun reset() // org.kotlincrypto.core.digest/Digest.reset|reset(){}[0] 25 | final fun toString(): kotlin/String // org.kotlincrypto.core.digest/Digest.toString|toString(){}[0] 26 | final fun update(kotlin/Byte) // org.kotlincrypto.core.digest/Digest.update|update(kotlin.Byte){}[0] 27 | final fun update(kotlin/ByteArray) // org.kotlincrypto.core.digest/Digest.update|update(kotlin.ByteArray){}[0] 28 | final fun update(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.digest/Digest.update|update(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] 29 | open fun digestIntoProtected(kotlin/ByteArray, kotlin/Int, kotlin/ByteArray, kotlin/Int) // org.kotlincrypto.core.digest/Digest.digestIntoProtected|digestIntoProtected(kotlin.ByteArray;kotlin.Int;kotlin.ByteArray;kotlin.Int){}[0] 30 | open fun updateProtected(kotlin/Byte) // org.kotlincrypto.core.digest/Digest.updateProtected|updateProtected(kotlin.Byte){}[0] 31 | open fun updateProtected(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.digest/Digest.updateProtected|updateProtected(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] 32 | } 33 | 34 | sealed class org.kotlincrypto.core.digest.internal/DigestState // org.kotlincrypto.core.digest.internal/DigestState|null[0] 35 | -------------------------------------------------------------------------------- /library/digest/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | plugins { 17 | id("configuration") 18 | } 19 | 20 | kmpConfiguration { 21 | configureShared(java9ModuleName = "org.kotlincrypto.core.digest", publish = true) { 22 | common { 23 | sourceSetMain { 24 | dependencies { 25 | api(project(":library:core")) 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /library/digest/gradle.properties: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 KotlinCrypto 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | POM_ARTIFACT_ID=digest 15 | POM_NAME=KotlinCrypto Digest Abstraction 16 | POM_DESCRIPTION=Core component for creating Digests 17 | -------------------------------------------------------------------------------- /library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/Digest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package org.kotlincrypto.core.digest 19 | 20 | import org.kotlincrypto.core.* 21 | import org.kotlincrypto.error.InvalidParameterException 22 | import org.kotlincrypto.error.ShortBufferException 23 | 24 | /** 25 | * Core abstraction for Message Digest implementations. 26 | * 27 | * A Digest provides secure one-way hash functions that take in 28 | * arbitrary sized data and output a fixed-length hash value. 29 | * 30 | * Implementations of [Digest] should follow the Java naming 31 | * guidelines for [algorithm] which can be found at: 32 | * 33 | * https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#messagedigest-algorithms 34 | * */ 35 | public expect abstract class Digest: Algorithm, Copyable, Resettable, Updatable { 36 | 37 | /** 38 | * Creates a new [Digest] for the specified parameters. 39 | * 40 | * @param [algorithm] See [Algorithm.algorithm] 41 | * @param [blockSize] See [Digest.blockSize] 42 | * @param [digestLength] See [Digest.digestLength] 43 | * @throws [InvalidParameterException] when: 44 | * - [algorithm] is blank 45 | * - [blockSize] is less than or equal to 0 46 | * - [blockSize] is not a factor of 8 47 | * - [digestLength] is negative 48 | * */ 49 | @Throws(InvalidParameterException::class) 50 | protected constructor(algorithm: String, blockSize: Int, digestLength: Int) 51 | 52 | /** 53 | * Creates a new [Digest] from [other], copying its state. 54 | * 55 | * Implementors of [Digest] should have a private secondary constructor 56 | * that is utilized by its [copy] implementation. 57 | * 58 | * e.g. 59 | * 60 | * public class SHA256: Digest { 61 | * 62 | * // ... 63 | * 64 | * private constructor(other: SHA256): super(other) { 65 | * // Copy implementation details... 66 | * } 67 | * 68 | * // Notice the updated return type 69 | * public override fun copy(): SHA256 = SHA256(this) 70 | * 71 | * // ... 72 | * } 73 | * */ 74 | protected constructor(other: Digest) 75 | 76 | /** 77 | * The number of byte blocks (in factors of 8) that the implementation 78 | * requires before one round of input processing is to occur. This value 79 | * is also representative of the digest's buffer size, and will always 80 | * be greater than 0. 81 | * */ 82 | public fun blockSize(): Int 83 | 84 | /** 85 | * The number of bytes the implementation returns when [digest] is called. 86 | * */ 87 | public fun digestLength(): Int 88 | 89 | // See Algorithm interface documentation 90 | public final override fun algorithm(): String 91 | 92 | // See Updatable interface documentation 93 | public final override fun update(input: Byte) 94 | // See Updatable interface documentation 95 | public final override fun update(input: ByteArray) 96 | // See Updatable interface documentation 97 | public final override fun update(input: ByteArray, offset: Int, len: Int) 98 | 99 | /** 100 | * Completes the computation, performing final operations and returning 101 | * the resultant array of bytes. The [Digest] is [reset] afterward. 102 | * */ 103 | public fun digest(): ByteArray 104 | 105 | /** 106 | * Updates the instance with provided [input] then completes the computation, 107 | * performing final operations and returning the resultant array of bytes. The 108 | * [Digest] is [reset] afterward. 109 | * */ 110 | public fun digest(input: ByteArray): ByteArray 111 | 112 | /** 113 | * Completes the computation, performing final operations and placing the 114 | * resultant bytes into the provided [dest] array starting at index [destOffset]. 115 | * The [Digest] is [reset] afterward. 116 | * 117 | * @return The number of bytes put into [dest] (i.e. the [digestLength]) 118 | * @throws [IndexOutOfBoundsException] if [destOffset] is inappropriate 119 | * @throws [ShortBufferException] if [digestLength] number of bytes are unable 120 | * to fit into [dest] for provided [destOffset] 121 | * */ 122 | public fun digestInto(dest: ByteArray, destOffset: Int): Int 123 | 124 | // See Resettable interface documentation 125 | public final override fun reset() 126 | 127 | /** 128 | * Called whenever a full [blockSize] worth of bytes are available for processing, 129 | * starting at index [offset] for the provided [input]. Implementations **must not** 130 | * alter [input]. 131 | * */ 132 | protected abstract fun compressProtected(input: ByteArray, offset: Int) 133 | 134 | /** 135 | * Called to complete the computation, providing any input that may be buffered 136 | * and awaiting processing. 137 | * 138 | * **NOTE:** The buffer from [bufPos] to the end will always be zeroed out to clear 139 | * any potentially stale input left over from a previous state. 140 | * 141 | * @param [buf] Unprocessed input 142 | * @param [bufPos] The index at which the **next** input would be placed into [buf] 143 | * */ 144 | protected abstract fun digestProtected(buf: ByteArray, bufPos: Int): ByteArray 145 | 146 | /** 147 | * Called to complete the computation, providing any input that may be buffered 148 | * and awaiting processing. 149 | * 150 | * Implementations should override this addition to the API for performance reasons. 151 | * If overridden, `super.digestIntoProtected` should **not** be called. 152 | * 153 | * **NOTE:** The buffer from [bufPos] to the end will always be zeroed out to clear 154 | * any potentially stale input left over from a previous state. 155 | * 156 | * **NOTE:** The public [digestInto] function always checks [dest] for capacity of 157 | * [digestLength], starting at [destOffset], before calling this function. 158 | * 159 | * @param [dest] The array to place resultant bytes 160 | * @param [destOffset] The index to begin placing bytes into [dest] 161 | * @param [buf] Unprocessed input 162 | * @param [bufPos] The index at which the **next** input would be placed into [buf] 163 | * */ 164 | protected open fun digestIntoProtected(dest: ByteArray, destOffset: Int, buf: ByteArray, bufPos: Int) 165 | 166 | /** 167 | * Optional override for implementations to intercept cleansed input before 168 | * being processed by the [Digest] abstraction. 169 | * */ 170 | protected open fun updateProtected(input: Byte) 171 | 172 | /** 173 | * Optional override for implementations to intercept cleansed input before 174 | * being processed by the [Digest] abstraction. Parameters passed to this 175 | * function are always valid and have been checked for appropriateness. 176 | * */ 177 | protected open fun updateProtected(input: ByteArray, offset: Int, len: Int) 178 | 179 | protected abstract fun resetProtected() 180 | 181 | /** @suppress */ 182 | public final override fun equals(other: Any?): Boolean 183 | /** @suppress */ 184 | public final override fun hashCode(): Int 185 | /** @suppress */ 186 | public final override fun toString(): String 187 | } 188 | -------------------------------------------------------------------------------- /library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-Buffer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("KotlinRedundantDiagnosticSuppress", "NOTHING_TO_INLINE") 17 | 18 | package org.kotlincrypto.core.digest.internal 19 | 20 | import org.kotlincrypto.core.digest.Digest 21 | import org.kotlincrypto.error.InvalidParameterException 22 | import org.kotlincrypto.error.ShortBufferException 23 | import org.kotlincrypto.error.requireParam 24 | import kotlin.contracts.ExperimentalContracts 25 | import kotlin.contracts.InvocationKind 26 | import kotlin.contracts.contract 27 | import kotlin.jvm.JvmInline 28 | 29 | @JvmInline 30 | internal value class Buffer internal constructor(internal val value: ByteArray) 31 | 32 | @Throws(InvalidParameterException::class) 33 | @Suppress("UnusedReceiverParameter") 34 | internal inline fun Digest.initializeBuffer( 35 | algorithm: String, 36 | blockSize: Int, 37 | digestLength: Int, 38 | ): Buffer { 39 | requireParam(algorithm.isNotBlank()) { "algorithm cannot be blank" } 40 | requireParam(blockSize > 0) { "blockSize must be greater than 0" } 41 | requireParam(blockSize % 8 == 0) { "blockSize must be a factor of 8" } 42 | requireParam(digestLength >= 0) { "digestLength cannot be negative" } 43 | return Buffer(ByteArray(blockSize)) 44 | } 45 | 46 | internal inline fun Buffer.copy(): Buffer = Buffer(value.copyOf()) 47 | 48 | @OptIn(ExperimentalContracts::class) 49 | internal inline fun Buffer.commonUpdate( 50 | input: Byte, 51 | bufPosPlusPlus: Int, 52 | bufPosSet: (zero: Int) -> Unit, 53 | compressProtected: (ByteArray, Int) -> Unit, 54 | ) { 55 | contract { 56 | callsInPlace(bufPosSet, InvocationKind.AT_MOST_ONCE) 57 | callsInPlace(compressProtected, InvocationKind.AT_MOST_ONCE) 58 | } 59 | 60 | val buf = value 61 | buf[bufPosPlusPlus] = input 62 | 63 | // buf.size == blockSize 64 | if ((bufPosPlusPlus + 1) != buf.size) return 65 | compressProtected(buf, 0) 66 | bufPosSet(0) 67 | } 68 | 69 | @OptIn(ExperimentalContracts::class) 70 | internal inline fun Buffer.commonUpdate( 71 | input: ByteArray, 72 | offset: Int, 73 | len: Int, 74 | bufPos: Int, 75 | bufPosSet: (value: Int) -> Unit, 76 | compressProtected: (ByteArray, Int) -> Unit, 77 | ) { 78 | contract { 79 | callsInPlace(bufPosSet, InvocationKind.EXACTLY_ONCE) 80 | callsInPlace(compressProtected, InvocationKind.UNKNOWN) 81 | } 82 | 83 | val buf = value 84 | val blockSize = buf.size 85 | val limitInput = offset + len 86 | var posInput = offset 87 | var posBuf = bufPos 88 | 89 | if (posBuf > 0) { 90 | // Need to use buffered data (if possible) 91 | 92 | if (posBuf + len < blockSize) { 93 | // Not enough for a compression. Add it to the buffer. 94 | input.copyInto(buf, posBuf, posInput, limitInput) 95 | bufPosSet(posBuf + len) 96 | return 97 | } 98 | 99 | // Add enough input to do a compression 100 | val needed = blockSize - posBuf 101 | input.copyInto(buf, posBuf, posInput, posInput + needed) 102 | compressProtected(buf, 0) 103 | posBuf = 0 104 | posInput += needed 105 | } 106 | 107 | // Chunk blocks (if possible) 108 | while (posInput < limitInput) { 109 | val posNext = posInput + blockSize 110 | 111 | if (posNext > limitInput) { 112 | // Not enough for a compression. Add it to the buffer. 113 | input.copyInto(buf, 0, posInput, limitInput) 114 | posBuf = limitInput - posInput 115 | break 116 | } 117 | 118 | compressProtected(input, posInput) 119 | posInput = posNext 120 | } 121 | 122 | // Update globals 123 | bufPosSet(posBuf) 124 | } 125 | 126 | @OptIn(ExperimentalContracts::class) 127 | internal inline fun Buffer.commonDigest( 128 | input: ByteArray, 129 | updateProtected: (ByteArray, Int, Int) -> Unit, 130 | bufPosGet: () -> Int, 131 | digestProtected: (buf: ByteArray, bufPos: Int) -> ByteArray, 132 | resetProtected: () -> Unit, 133 | bufPosSet: (zero: Int) -> Unit, 134 | ): ByteArray { 135 | contract { 136 | callsInPlace(updateProtected, InvocationKind.EXACTLY_ONCE) 137 | callsInPlace(bufPosGet, InvocationKind.EXACTLY_ONCE) 138 | callsInPlace(digestProtected, InvocationKind.EXACTLY_ONCE) 139 | callsInPlace(resetProtected, InvocationKind.EXACTLY_ONCE) 140 | callsInPlace(bufPosSet, InvocationKind.EXACTLY_ONCE) 141 | } 142 | 143 | updateProtected(input, 0, input.size) 144 | return commonDigest(bufPosGet(), digestProtected, resetProtected, bufPosSet) 145 | } 146 | 147 | @OptIn(ExperimentalContracts::class) 148 | internal inline fun Buffer.commonDigest( 149 | bufPos: Int, 150 | digestProtected: (buf: ByteArray, bufPos: Int) -> ByteArray, 151 | resetProtected: () -> Unit, 152 | bufPosSet: (zero: Int) -> Unit, 153 | ): ByteArray { 154 | contract { 155 | callsInPlace(digestProtected, InvocationKind.EXACTLY_ONCE) 156 | callsInPlace(resetProtected, InvocationKind.EXACTLY_ONCE) 157 | callsInPlace(bufPosSet, InvocationKind.EXACTLY_ONCE) 158 | } 159 | 160 | // Zero out any stale input that may be left in the buffer 161 | value.fill(0, bufPos) 162 | val digest = digestProtected(value, bufPos) 163 | commonReset(resetProtected, bufPosSet) 164 | return digest 165 | } 166 | 167 | @OptIn(ExperimentalContracts::class) 168 | @Throws(ShortBufferException::class) 169 | internal inline fun Buffer.commonDigestInto( 170 | bufPos: Int, 171 | dest: ByteArray, 172 | destOffset: Int, 173 | digestLength: Int, 174 | digestIntoProtected: (dest: ByteArray, destOffset: Int, buf: ByteArray, bufPos: Int) -> Unit, 175 | resetProtected: () -> Unit, 176 | bufPosSet: (zero: Int) -> Unit, 177 | ): Int { 178 | contract { 179 | callsInPlace(digestIntoProtected, InvocationKind.AT_MOST_ONCE) 180 | callsInPlace(resetProtected, InvocationKind.AT_MOST_ONCE) 181 | callsInPlace(bufPosSet, InvocationKind.AT_MOST_ONCE) 182 | } 183 | 184 | dest.commonCheckArgs(destOffset, digestLength, onShortInput = ::ShortBufferException) 185 | 186 | // Zero out any stale input that may be left in the buffer 187 | value.fill(0, bufPos) 188 | digestIntoProtected(dest, destOffset, value, bufPos) 189 | commonReset(resetProtected, bufPosSet) 190 | return digestLength 191 | } 192 | 193 | @OptIn(ExperimentalContracts::class) 194 | internal inline fun Buffer.commonReset( 195 | resetProtected: () -> Unit, 196 | bufPosSet: (zero: Int) -> Unit, 197 | ) { 198 | contract { 199 | callsInPlace(resetProtected, InvocationKind.EXACTLY_ONCE) 200 | callsInPlace(bufPosSet, InvocationKind.EXACTLY_ONCE) 201 | } 202 | 203 | value.fill(0) 204 | bufPosSet(0) 205 | resetProtected() 206 | } 207 | -------------------------------------------------------------------------------- /library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-CommonPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("KotlinRedundantDiagnosticSuppress", "NOTHING_TO_INLINE") 17 | 18 | package org.kotlincrypto.core.digest.internal 19 | 20 | import org.kotlincrypto.core.digest.Digest 21 | import kotlin.contracts.ExperimentalContracts 22 | import kotlin.contracts.InvocationKind 23 | import kotlin.contracts.contract 24 | 25 | internal inline fun Digest.commonToString(): String { 26 | return "Digest[${algorithm()}]@${hashCode()}" 27 | } 28 | 29 | @Throws(Exception::class) 30 | @OptIn(ExperimentalContracts::class) 31 | internal inline fun ByteArray.commonCheckArgs( 32 | offset: Int, 33 | len: Int, 34 | onShortInput: (message: String) -> Exception = ::IllegalArgumentException, 35 | onOutOfBounds: (message: String) -> Exception = ::IndexOutOfBoundsException, 36 | ) { 37 | contract { 38 | callsInPlace(onShortInput, InvocationKind.AT_MOST_ONCE) 39 | callsInPlace(onOutOfBounds, InvocationKind.AT_MOST_ONCE) 40 | } 41 | 42 | if (size - offset < len) throw onShortInput("Too Short. size[$size] - offset[$offset] < len[$len]") 43 | if (offset < 0) throw onOutOfBounds("offset[$offset] < 0") 44 | if (len < 0) throw onOutOfBounds("len[$len] < 0") 45 | if (offset > size - len) throw onOutOfBounds("offset[$offset] > size[$size] - len[$len]") 46 | } 47 | -------------------------------------------------------------------------------- /library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/DigestState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.digest.internal 17 | 18 | /** @suppress */ 19 | @Deprecated("Removed from usage in Digest.copy implementation in 0.6.0") 20 | public sealed class DigestState 21 | -------------------------------------------------------------------------------- /library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/AbstractTestUpdateExceptions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.digest 17 | 18 | import org.kotlincrypto.core.Updatable 19 | import kotlin.test.Test 20 | import kotlin.test.fail 21 | 22 | /** 23 | * Ensures that all exception cases are handled the 24 | * same for [Digest] as Java's MessageDigest would handle 25 | * them. 26 | * */ 27 | abstract class AbstractTestUpdateExceptions: Updatable { 28 | 29 | @Test 30 | fun givenDigest_whenEmptyBytes_thenDoesNotThrow() { 31 | update(ByteArray(0)) 32 | } 33 | 34 | @Test 35 | fun givenDigest_whenLengthNegative_thenThrows() { 36 | try { 37 | update(ByteArray(0), 0, -1) 38 | fail() 39 | } catch (_: IndexOutOfBoundsException) { 40 | // pass 41 | } 42 | } 43 | 44 | @Test 45 | fun givenDigest_whenOffsetNegative_thenThrows() { 46 | try { 47 | update(ByteArray(10), -1, 10) 48 | fail() 49 | } catch (_: IndexOutOfBoundsException) { 50 | // pass 51 | } 52 | } 53 | 54 | @Test 55 | fun givenDigest_whenInputTooShort_thenThrows() { 56 | try { 57 | update(ByteArray(5), -1, 10) 58 | fail() 59 | } catch (_: IllegalArgumentException) { 60 | // pass 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/DigestUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.digest 17 | 18 | import org.kotlincrypto.error.InvalidParameterException 19 | import org.kotlincrypto.error.ShortBufferException 20 | import kotlin.random.Random 21 | import kotlin.test.* 22 | 23 | class DigestUnitTest: AbstractTestUpdateExceptions() { 24 | 25 | private val digest = TestDigest() 26 | 27 | override fun update(input: ByteArray) { 28 | digest.update(input) 29 | } 30 | 31 | override fun update(input: Byte) { 32 | digest.update(input) 33 | } 34 | 35 | override fun update(input: ByteArray, offset: Int, len: Int) { 36 | digest.update(input, offset, len) 37 | } 38 | 39 | @Test 40 | fun givenDigest_whenLengthNegative_thenThrowsException() { 41 | // accepts 0 length 42 | TestDigest(digestLength = 0) 43 | assertFailsWith { TestDigest(digestLength = -1) } 44 | } 45 | 46 | @Test 47 | fun givenDigest_whenDigested_thenResetIsInvoked() { 48 | val expected = ByteArray(10) { 1 } 49 | var resetCount = 0 50 | 51 | val digest = TestDigest( 52 | digest = { _, _ -> expected }, 53 | reset = { resetCount++ } 54 | ) 55 | 56 | assertEquals(expected, digest.digest()) 57 | assertEquals(1, resetCount) 58 | assertEquals(expected, digest.digest()) 59 | assertEquals(2, resetCount) 60 | } 61 | 62 | @Test 63 | fun givenDigest_whenUpdated_thenChunksProperly() { 64 | val digest = TestDigest( 65 | // Return byte array sized to the offset 66 | digest = { _, offset -> ByteArray(offset) } 67 | ) 68 | 69 | digest.update(ByteArray(digest.blockSize() - 1)) 70 | assertEquals(0, digest.compressions) 71 | 72 | digest.update(ByteArray(digest.blockSize() + 1)) 73 | assertEquals(2, digest.compressions) 74 | 75 | digest.update(ByteArray(digest.blockSize() - 1)) 76 | assertEquals(2, digest.compressions) 77 | 78 | digest.update(4) 79 | assertEquals(3, digest.compressions) 80 | 81 | digest.update(ByteArray(digest.blockSize())) 82 | assertEquals(4, digest.compressions) 83 | 84 | // Check the internal bufferOffset was 0 after all that 85 | assertEquals(0, digest.digest().size) 86 | assertEquals(0, digest.compressions) 87 | } 88 | 89 | @Test 90 | fun givenDigest_whenCopied_thenIsNewInstance() { 91 | val digest = TestDigest( 92 | digest = { b, _ -> 93 | assertEquals(1, b[0]) 94 | assertEquals(0, b[1]) 95 | b 96 | } 97 | ).apply { 98 | update(1) 99 | } 100 | 101 | val copy = digest.copy() 102 | 103 | assertEquals(digest.blockSize(), copy.blockSize()) 104 | assertEquals(digest.digestLength(), copy.digestLength()) 105 | assertEquals(digest.algorithm(), copy.algorithm()) 106 | assertNotEquals(copy, digest) 107 | 108 | val digestDigest = digest.digest() 109 | val copyDigest = copy.digest() 110 | 111 | assertNotEquals(copyDigest, digestDigest) 112 | assertEquals(digestDigest.size, copyDigest.size) 113 | } 114 | 115 | @Test 116 | fun givenBuffer_whenDigestProtected_thenStaleInputIsZeroedOut() { 117 | var bufCopy: ByteArray? = null 118 | var bufCopyPos: Int = -1 119 | val digest = TestDigest(digest = { buf, bufPos -> 120 | bufCopy = buf.copyOf() 121 | bufCopyPos = bufPos 122 | buf 123 | }) 124 | digest.update(Random.Default.nextBytes(digest.blockSize() - 1)) 125 | assertEquals(0, digest.compressions) 126 | digest.update(5) 127 | assertEquals(1, digest.compressions) 128 | 129 | val expected: Byte = -42 130 | digest.update(ByteArray(digest.blockSize() - 10) { expected }) 131 | assertEquals(1, digest.compressions) 132 | digest.digest() 133 | 134 | assertNotNull(bufCopy) 135 | assertNotEquals(-1, bufCopyPos) 136 | assertNotEquals(0, bufCopyPos) 137 | assertNotEquals(digest.blockSize(), bufCopyPos) 138 | 139 | for (i in 0 until bufCopyPos) { 140 | assertEquals(expected, bufCopy!![i]) 141 | } 142 | 143 | for (i in bufCopyPos until digest.blockSize()) { 144 | assertEquals(0, bufCopy!![i]) 145 | } 146 | } 147 | 148 | @Test 149 | fun givenDigest_whenDigestInto_thenDefaultImplementationCopiesResultIntoDest() { 150 | val expected = ByteArray(10) { 1 } 151 | val digest = TestDigest( 152 | digestLength = expected.size, 153 | digest = { _, _ -> expected.copyOf() } 154 | ) 155 | val actual = ByteArray(expected.size + 2) { 4 } 156 | digest.digestInto(actual, 1) 157 | 158 | assertEquals(4, actual[0]) 159 | assertEquals(4, actual[actual.size - 1]) 160 | for (i in expected.indices) { 161 | assertEquals(expected[i], actual[i + 1]) 162 | } 163 | } 164 | 165 | @Test 166 | fun givenDigest_whenDigestInto_thenThrowsExceptionsAsExpected() { 167 | val dSize = digest.digestLength() 168 | assertFailsWith { digest.digestInto(ByteArray(dSize), 1) } 169 | assertFailsWith { digest.digestInto(ByteArray(dSize), -1) } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/TestDigest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.digest 17 | 18 | class TestDigest: Digest { 19 | 20 | private val compress: (input: ByteArray, offset: Int) -> Unit 21 | private val finalize: (buf: ByteArray, bufPos: Int) -> ByteArray 22 | private val reset: () -> Unit 23 | 24 | constructor(algorithm: String): this(algorithm, 64) 25 | 26 | var compressions: Int = 0 27 | private set 28 | 29 | constructor( 30 | algorithm: String = "TEST", 31 | blockSize: Int = 64, 32 | digestLength: Int = 32, 33 | compress: (input: ByteArray, offset: Int) -> Unit = { _, _ -> }, 34 | digest: (buf: ByteArray, bufPos: Int) -> ByteArray = { _, _ -> ByteArray(blockSize) }, 35 | reset: () -> Unit = {}, 36 | ): super(algorithm, blockSize, digestLength) { 37 | this.compress = compress 38 | this.finalize = digest 39 | this.reset = reset 40 | } 41 | 42 | private constructor(other: TestDigest): super(other) { 43 | this.compress = other.compress 44 | this.finalize = other.finalize 45 | this.reset = other.reset 46 | this.compressions = other.compressions 47 | } 48 | 49 | override fun compressProtected(input: ByteArray, offset: Int) { 50 | compress.invoke(input, offset) 51 | compressions++ 52 | } 53 | 54 | override fun digestProtected(buf: ByteArray, bufPos: Int): ByteArray { 55 | return finalize.invoke(buf, bufPos) 56 | } 57 | 58 | override fun resetProtected() { 59 | reset.invoke() 60 | compressions = 0 61 | } 62 | 63 | override fun copy(): TestDigest = TestDigest(this) 64 | } 65 | -------------------------------------------------------------------------------- /library/digest/src/jvmMain/java9/module-info.java: -------------------------------------------------------------------------------- 1 | module org.kotlincrypto.core.digest { 2 | requires kotlin.stdlib; 3 | requires transitive org.kotlincrypto.core; 4 | 5 | exports org.kotlincrypto.core.digest; 6 | } 7 | -------------------------------------------------------------------------------- /library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/JvmDigestExceptionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.digest 17 | 18 | import java.security.MessageDigest 19 | 20 | /** 21 | * Verifies that [Digest] abstraction produces the same exceptions that 22 | * the Jvm's [MessageDigest] does. 23 | * */ 24 | class JvmDigestExceptionTest: AbstractTestUpdateExceptions() { 25 | 26 | private val digest = MessageDigest.getInstance("MD5") 27 | 28 | override fun update(input: Byte) { 29 | digest.update(input) 30 | } 31 | 32 | override fun update(input: ByteArray) { 33 | digest.update(input) 34 | } 35 | 36 | override fun update(input: ByteArray, offset: Int, len: Int) { 37 | digest.update(input, offset, len) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/JvmDigestUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.digest 17 | 18 | import java.lang.AssertionError 19 | import java.security.DigestException 20 | import java.security.MessageDigest 21 | import kotlin.random.Random 22 | import kotlin.test.Test 23 | import kotlin.test.assertContentEquals 24 | import kotlin.test.assertEquals 25 | import kotlin.test.assertFailsWith 26 | 27 | /** 28 | * Compares [Digest] functionality to [MessageDigest] 29 | * */ 30 | @Suppress("DEPRECATION") 31 | class JvmDigestUnitTest { 32 | 33 | class MessageDigestWrap: Digest { 34 | 35 | private val delegate: MessageDigest 36 | 37 | constructor(digest: MessageDigest, blockSize: Int): super(digest.algorithm, blockSize, digest.digestLength) { 38 | delegate = digest.clone() as MessageDigest 39 | } 40 | private constructor(other: MessageDigestWrap): super(other) { 41 | delegate = other.delegate.clone() as MessageDigest 42 | } 43 | 44 | override fun copy(): MessageDigestWrap = MessageDigestWrap(this) 45 | 46 | override fun compressProtected(input: ByteArray, offset: Int) { 47 | delegate.update(input, offset, blockSize()) 48 | } 49 | 50 | override fun digestProtected(buf: ByteArray, bufPos: Int): ByteArray { 51 | delegate.update(buf, 0, bufPos) 52 | return delegate.digest() 53 | } 54 | 55 | override fun resetProtected() { 56 | delegate.reset() 57 | } 58 | } 59 | 60 | private val jvm = MessageDigest.getInstance("SHA-256") 61 | private val wrap = MessageDigestWrap(jvm, 64) 62 | private val bytes = Random.Default.nextBytes(1_000) 63 | 64 | @Test(AssertionError::class) 65 | fun givenWrappedMessageDigest_whenOneUpdated_thenReturnsDifferent() { 66 | // To ensure that the wrapped digest test class 67 | // actually cloned the digest 68 | wrap.update(bytes) 69 | assertContentEquals(jvm.digest(), wrap.digest()) 70 | } 71 | 72 | @Test 73 | fun givenWrappedMessageDigest_whenUpdated_thenReturnsTheSame() { 74 | wrap.update(bytes) 75 | jvm.update(bytes) 76 | 77 | assertContentEquals(jvm.digest(), wrap.digest()) 78 | } 79 | 80 | @Test 81 | fun givenWrappedMessageDigest_whenUpdatedByte_thenReturnsTheSame() { 82 | wrap.update(bytes[0]) 83 | jvm.update(bytes[0]) 84 | 85 | assertContentEquals(jvm.digest(), wrap.digest()) 86 | } 87 | 88 | @Test 89 | fun givenWrappedMessageDigest_whenUpdatedOffset_thenReturnsTheSame() { 90 | wrap.update(bytes, 10, 100) 91 | jvm.update(bytes, 10, 100) 92 | 93 | assertContentEquals(jvm.digest(), wrap.digest()) 94 | 95 | wrap.update(bytes, 500, 1) 96 | jvm.update(bytes, 500, 1) 97 | 98 | assertContentEquals(jvm.digest(), wrap.digest()) 99 | } 100 | 101 | @Test 102 | fun givenWrappedMessageDigest_whenDigestToBuf_thenWorksAsExpected() { 103 | wrap.update(bytes, 10, 100) 104 | jvm.update(bytes, 10, 100) 105 | val expected = wrap.digest() 106 | wrap.update(bytes, 10, 100) 107 | 108 | assertFailsWith { wrap.digest(ByteArray(2), 0, wrap.digestLength()) } 109 | assertFailsWith { jvm.digest(ByteArray(2), 0, wrap.digestLength()) } 110 | 111 | assertFailsWith { wrap.digest(ByteArray(wrap.digestLength()), 1, wrap.digestLength()) } 112 | assertFailsWith { jvm.digest(ByteArray(wrap.digestLength()), 1, wrap.digestLength()) } 113 | 114 | assertFailsWith { wrap.digest(ByteArray(wrap.digestLength()), -1, wrap.digestLength()) } 115 | assertFailsWith { jvm.digest(ByteArray(wrap.digestLength()), -1, wrap.digestLength()) } 116 | 117 | assertFailsWith { wrap.digest(ByteArray(wrap.digestLength()), 0, 0) } 118 | assertFailsWith { jvm.digest(ByteArray(wrap.digestLength()), 0, 0) } 119 | 120 | assertFailsWith { wrap.digest(ByteArray(wrap.digestLength()), wrap.digestLength() + 1, wrap.digestLength()) } 121 | assertFailsWith { jvm.digest(ByteArray(wrap.digestLength()), wrap.digestLength() + 1, wrap.digestLength()) } 122 | 123 | assertFailsWith { wrap.digest(null, 0, wrap.digestLength()) } 124 | assertFailsWith { jvm.digest(null, 0, wrap.digestLength()) } 125 | 126 | assertContentEquals(expected, wrap.digest()) 127 | assertContentEquals(expected, jvm.digest()) 128 | 129 | wrap.update(bytes, 10, 100) 130 | jvm.update(bytes, 10, 100) 131 | 132 | assertFailsWith { wrap.digest(ByteArray(wrap.digestLength()), 0, wrap.digestLength() - 1) } 133 | assertFailsWith { jvm.digest(ByteArray(wrap.digestLength()), 0, wrap.digestLength() - 1) } 134 | 135 | assertContentEquals(expected, wrap.digest()) 136 | assertContentEquals(expected, jvm.digest()) 137 | 138 | wrap.update(bytes, 10, 100) 139 | jvm.update(bytes, 10, 100) 140 | 141 | val resultWrap = ByteArray(wrap.digestLength() + 4) 142 | val resultJvm = resultWrap.copyOf() 143 | 144 | // Expressing longer length than what digest outputs should be ignored 145 | assertEquals(wrap.digestLength(), wrap.digest(resultWrap, 2, resultWrap.size - 2)) 146 | assertEquals(wrap.digestLength(), jvm.digest(resultJvm, 2, resultJvm.size - 2)) 147 | 148 | assertContentEquals(resultWrap, resultJvm) 149 | for (i in expected.indices) { 150 | assertEquals(expected[i], resultWrap[i + 2]) 151 | assertEquals(expected[i], resultJvm[i + 2]) 152 | } 153 | for (i in 0 until 2) { 154 | assertEquals(0, resultWrap[i]) 155 | assertEquals(0, resultJvm[i]) 156 | } 157 | for (i in (resultWrap.size - 2) until resultWrap.size) { 158 | assertEquals(0, resultWrap[i]) 159 | assertEquals(0, resultJvm[i]) 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /library/digest/src/nonJvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "KotlinRedundantDiagnosticSuppress") 17 | 18 | package org.kotlincrypto.core.digest 19 | 20 | import org.kotlincrypto.core.* 21 | import org.kotlincrypto.core.digest.internal.* 22 | import org.kotlincrypto.error.InvalidParameterException 23 | import org.kotlincrypto.error.ShortBufferException 24 | 25 | /** 26 | * Core abstraction for Message Digest implementations. 27 | * 28 | * A Digest provides secure one-way hash functions that take in 29 | * arbitrary sized data and output a fixed-length hash value. 30 | * 31 | * Implementations of [Digest] should follow the Java naming 32 | * guidelines for [algorithm] which can be found at: 33 | * 34 | * https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#messagedigest-algorithms 35 | * */ 36 | public actual abstract class Digest: Algorithm, Copyable, Resettable, Updatable { 37 | 38 | private val algorithm: String 39 | private val digestLength: Int 40 | private val buf: Buffer 41 | private var bufPos: Int 42 | 43 | /** 44 | * Creates a new [Digest] for the specified parameters. 45 | * 46 | * @param [algorithm] See [Algorithm.algorithm] 47 | * @param [blockSize] See [Digest.blockSize] 48 | * @param [digestLength] See [Digest.digestLength] 49 | * @throws [InvalidParameterException] when: 50 | * - [algorithm] is blank 51 | * - [blockSize] is less than or equal to 0 52 | * - [blockSize] is not a factor of 8 53 | * - [digestLength] is negative 54 | * */ 55 | @Throws(InvalidParameterException::class) 56 | protected actual constructor(algorithm: String, blockSize: Int, digestLength: Int) { 57 | this.buf = initializeBuffer(algorithm, blockSize, digestLength) 58 | this.algorithm = algorithm 59 | this.digestLength = digestLength 60 | this.bufPos = 0 61 | } 62 | 63 | /** 64 | * Creates a new [Digest] from [other], copying its state. 65 | * 66 | * Implementors of [Digest] should have a private secondary constructor 67 | * that is utilized by its [copy] implementation. 68 | * 69 | * e.g. 70 | * 71 | * public class SHA256: Digest { 72 | * 73 | * // ... 74 | * 75 | * private constructor(other: SHA256): super(other) { 76 | * // Copy implementation details... 77 | * } 78 | * 79 | * // Notice the updated return type 80 | * public override fun copy(): SHA256 = SHA256(this) 81 | * 82 | * // ... 83 | * } 84 | * */ 85 | protected actual constructor(other: Digest) { 86 | this.algorithm = other.algorithm 87 | this.digestLength = other.digestLength 88 | this.buf = other.buf.copy() 89 | this.bufPos = other.bufPos 90 | } 91 | 92 | /** 93 | * The number of byte blocks (in factors of 8) that the implementation 94 | * requires before one round of input processing is to occur. This value 95 | * is also representative of the digest's buffer size, and will always 96 | * be greater than 0. 97 | * */ 98 | public actual fun blockSize(): Int = buf.value.size 99 | 100 | /** 101 | * The number of bytes the implementation returns when [digest] is called. 102 | * */ 103 | public actual fun digestLength(): Int = digestLength 104 | 105 | // See Algorithm interface documentation 106 | public actual final override fun algorithm(): String = algorithm 107 | 108 | // See Updatable interface documentation 109 | public actual final override fun update(input: Byte) { 110 | updateProtected(input) 111 | } 112 | // See Updatable interface documentation 113 | public actual final override fun update(input: ByteArray) { 114 | updateProtected(input, 0, input.size) 115 | } 116 | // See Updatable interface documentation 117 | public actual final override fun update(input: ByteArray, offset: Int, len: Int) { 118 | input.commonCheckArgs(offset, len) 119 | updateProtected(input, offset, len) 120 | } 121 | 122 | /** 123 | * Completes the computation, performing final operations and returning 124 | * the resultant array of bytes. The [Digest] is [reset] afterward. 125 | * */ 126 | public actual fun digest(): ByteArray = buf.commonDigest( 127 | bufPos = bufPos, 128 | digestProtected = ::digestProtected, 129 | resetProtected = ::resetProtected, 130 | bufPosSet = { bufPos = it }, 131 | ) 132 | 133 | /** 134 | * Updates the instance with provided [input] then completes the computation, 135 | * performing final operations and returning the resultant array of bytes. The 136 | * [Digest] is [reset] afterward. 137 | * */ 138 | public actual fun digest(input: ByteArray): ByteArray = buf.commonDigest( 139 | input = input, 140 | updateProtected = ::updateProtected, 141 | bufPosGet = ::bufPos, 142 | digestProtected = ::digestProtected, 143 | resetProtected = ::resetProtected, 144 | bufPosSet = { bufPos = it }, 145 | ) 146 | 147 | /** 148 | * Completes the computation, performing final operations and placing the 149 | * resultant bytes into the provided [dest] array starting at index [destOffset]. 150 | * The [Digest] is [reset] afterward. 151 | * 152 | * @return The number of bytes put into [dest] (i.e. the [digestLength]) 153 | * @throws [IndexOutOfBoundsException] if [destOffset] is inappropriate 154 | * @throws [ShortBufferException] if [digestLength] number of bytes are unable 155 | * to fit into [dest] for provided [destOffset] 156 | * */ 157 | public actual fun digestInto(dest: ByteArray, destOffset: Int): Int = buf.commonDigestInto( 158 | bufPos = bufPos, 159 | dest = dest, 160 | destOffset = destOffset, 161 | digestLength = digestLength, 162 | digestIntoProtected = ::digestIntoProtected, 163 | resetProtected = ::resetProtected, 164 | bufPosSet = { bufPos = it }, 165 | ) 166 | 167 | // See Resettable interface documentation 168 | public actual final override fun reset() { 169 | buf.commonReset( 170 | resetProtected = ::resetProtected, 171 | bufPosSet = { bufPos = it }, 172 | ) 173 | } 174 | 175 | /** 176 | * Called whenever a full [blockSize] worth of bytes are available for processing, 177 | * starting at index [offset] for the provided [input]. Implementations **must not** 178 | * alter [input]. 179 | * */ 180 | protected actual abstract fun compressProtected(input: ByteArray, offset: Int) 181 | 182 | /** 183 | * Called to complete the computation, providing any input that may be buffered 184 | * and awaiting processing. 185 | * 186 | * **NOTE:** The buffer from [bufPos] to the end will always be zeroed out to clear 187 | * any potentially stale input left over from a previous state. 188 | * 189 | * @param [buf] Unprocessed input 190 | * @param [bufPos] The index at which the **next** input would be placed into [buf] 191 | * */ 192 | protected actual abstract fun digestProtected(buf: ByteArray, bufPos: Int): ByteArray 193 | 194 | /** 195 | * Called to complete the computation, providing any input that may be buffered 196 | * and awaiting processing. 197 | * 198 | * Implementations should override this addition to the API for performance reasons. 199 | * If overridden, `super.digestIntoProtected` should **not** be called. 200 | * 201 | * **NOTE:** The buffer from [bufPos] to the end will always be zeroed out to clear 202 | * any potentially stale input left over from a previous state. 203 | * 204 | * **NOTE:** The public [digestInto] function always checks [dest] for capacity of 205 | * [digestLength], starting at [destOffset], before calling this function. 206 | * 207 | * @param [dest] The array to place resultant bytes 208 | * @param [destOffset] The index to begin placing bytes into [dest] 209 | * @param [buf] Unprocessed input 210 | * @param [bufPos] The index at which the **next** input would be placed into [buf] 211 | * */ 212 | protected actual open fun digestIntoProtected(dest: ByteArray, destOffset: Int, buf: ByteArray, bufPos: Int) { 213 | // Default implementation. Extenders of Digest should override. 214 | val result = digestProtected(buf, bufPos) 215 | result.copyInto(dest, destOffset) 216 | result.fill(0) 217 | } 218 | 219 | /** 220 | * Optional override for implementations to intercept cleansed input before 221 | * being processed by the [Digest] abstraction. 222 | * */ 223 | protected actual open fun updateProtected(input: Byte) { 224 | buf.commonUpdate( 225 | input = input, 226 | bufPosPlusPlus = bufPos++, 227 | bufPosSet = { bufPos = it }, 228 | compressProtected = ::compressProtected, 229 | ) 230 | } 231 | 232 | /** 233 | * Optional override for implementations to intercept cleansed input before 234 | * being processed by the [Digest] abstraction. Parameters passed to this 235 | * function are always valid and have been checked for appropriateness. 236 | * */ 237 | protected actual open fun updateProtected(input: ByteArray, offset: Int, len: Int) { 238 | buf.commonUpdate( 239 | input = input, 240 | offset = offset, 241 | len = len, 242 | bufPos = bufPos, 243 | bufPosSet = { bufPos = it }, 244 | compressProtected = ::compressProtected, 245 | ) 246 | } 247 | 248 | protected actual abstract fun resetProtected() 249 | 250 | /** @suppress */ 251 | public actual final override fun equals(other: Any?): Boolean = other is Digest && other.buf == buf 252 | /** @suppress */ 253 | public actual final override fun hashCode(): Int = buf.hashCode() 254 | /** @suppress */ 255 | public actual final override fun toString(): String = commonToString() 256 | } 257 | -------------------------------------------------------------------------------- /library/mac/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/mac/README.md: -------------------------------------------------------------------------------- 1 | # Module mac 2 | 3 | `Mac` abstraction for creating & verifying Message Authentication Codes. 4 | 5 | Implementations can be found at [KotlinCrypto/MACs][url-macs] 6 | 7 | ```kotlin 8 | // Using CryptoRand from KotlinCrypto/random repo as an example 9 | import org.kotlincrypto.random.CryptoRand 10 | // Using HmacSHA3_256 from KotlinCrypto/MACs repo as an example 11 | import org.kotlincrypto.macs.hmac.sha3.HmacSHA3_256 12 | 13 | fun main() { 14 | val key = CryptoRand.Default.nextBytes(ByteArray(100)) 15 | val mac = HmacSHA3_256(key) 16 | val bytes = Random.Default.nextBytes(615) 17 | 18 | // Mac implements interface Algorithm 19 | println(mac.algorithm()) 20 | // HmacSHA3-256 21 | 22 | // Mac implements interface Updatable 23 | mac.update(5.toByte()) 24 | mac.update(bytes) 25 | mac.update(bytes, 10, 88) 26 | 27 | // Mac implements interface Resettable 28 | mac.reset() 29 | 30 | mac.update(bytes) 31 | 32 | // Mac implements interface Copyable 33 | val copy = mac.copy() 34 | 35 | val hash = mac.doFinal() 36 | val hash2 = copy.doFinal(bytes) 37 | val hash3 = ByteArray(mac.macLength()) 38 | mac.update(bytes) 39 | mac.doFinalInto(hash3, destOffset = 0) 40 | 41 | // Reinitialize Mac instance with a new key 42 | // to use for something else. 43 | val newKey = CryptoRand.Default.nextBytes(ByteArray(100)) 44 | mac.reset(newKey = newKey) 45 | 46 | // Zero out key material before dereferencing 47 | copy.clearKey() 48 | } 49 | ``` 50 | 51 | [url-macs]: https://github.com/KotlinCrypto/MACs 52 | -------------------------------------------------------------------------------- /library/mac/api/mac.api: -------------------------------------------------------------------------------- 1 | public abstract class org/kotlincrypto/core/mac/Mac : javax/crypto/Mac, org/kotlincrypto/core/Algorithm, org/kotlincrypto/core/Copyable, org/kotlincrypto/core/Resettable, org/kotlincrypto/core/Updatable { 2 | protected fun (Ljava/lang/String;Lorg/kotlincrypto/core/mac/Mac$Engine;)V 3 | protected fun (Lorg/kotlincrypto/core/mac/Mac;)V 4 | public final fun algorithm ()Ljava/lang/String; 5 | public final fun clearKey ()V 6 | public final fun doFinalInto ([BI)I 7 | public final fun equals (Ljava/lang/Object;)Z 8 | public final fun hashCode ()I 9 | public final fun macLength ()I 10 | public final fun reset ([B)V 11 | public final fun toString ()Ljava/lang/String; 12 | } 13 | 14 | protected abstract class org/kotlincrypto/core/mac/Mac$Engine : javax/crypto/MacSpi, java/lang/Cloneable, org/kotlincrypto/core/Copyable, org/kotlincrypto/core/Resettable, org/kotlincrypto/core/Updatable { 15 | public final field resetOnDoFinal Z 16 | protected fun (Lorg/kotlincrypto/core/mac/Mac$Engine;)V 17 | public fun ([B)V 18 | public fun ([BZ)V 19 | public final fun clone ()Ljava/lang/Object; 20 | public abstract fun doFinal ()[B 21 | public fun doFinalInto ([BI)V 22 | protected final fun engineDoFinal ()[B 23 | protected final fun engineGetMacLength ()I 24 | protected final fun engineInit (Ljava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V 25 | protected final fun engineReset ()V 26 | protected final fun engineUpdate (B)V 27 | protected final fun engineUpdate (Ljava/nio/ByteBuffer;)V 28 | protected final fun engineUpdate ([BII)V 29 | public final fun equals (Ljava/lang/Object;)Z 30 | public final fun hashCode ()I 31 | public abstract fun macLength ()I 32 | public abstract fun reset ([B)V 33 | public fun update ([B)V 34 | } 35 | 36 | -------------------------------------------------------------------------------- /library/mac/api/mac.klib.api: -------------------------------------------------------------------------------- 1 | // Klib ABI Dump 2 | // Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] 3 | // Rendering settings: 4 | // - Signature version: 2 5 | // - Show manifest properties: true 6 | // - Show declarations: true 7 | 8 | // Library unique name: 9 | abstract class org.kotlincrypto.core.mac/Mac : org.kotlincrypto.core/Algorithm, org.kotlincrypto.core/Copyable, org.kotlincrypto.core/Resettable, org.kotlincrypto.core/Updatable { // org.kotlincrypto.core.mac/Mac|null[0] 10 | constructor (kotlin/String, org.kotlincrypto.core.mac/Mac.Engine) // org.kotlincrypto.core.mac/Mac.|(kotlin.String;org.kotlincrypto.core.mac.Mac.Engine){}[0] 11 | constructor (org.kotlincrypto.core.mac/Mac) // org.kotlincrypto.core.mac/Mac.|(org.kotlincrypto.core.mac.Mac){}[0] 12 | 13 | final fun algorithm(): kotlin/String // org.kotlincrypto.core.mac/Mac.algorithm|algorithm(){}[0] 14 | final fun clearKey() // org.kotlincrypto.core.mac/Mac.clearKey|clearKey(){}[0] 15 | final fun doFinal(): kotlin/ByteArray // org.kotlincrypto.core.mac/Mac.doFinal|doFinal(){}[0] 16 | final fun doFinal(kotlin/ByteArray): kotlin/ByteArray // org.kotlincrypto.core.mac/Mac.doFinal|doFinal(kotlin.ByteArray){}[0] 17 | final fun doFinalInto(kotlin/ByteArray, kotlin/Int): kotlin/Int // org.kotlincrypto.core.mac/Mac.doFinalInto|doFinalInto(kotlin.ByteArray;kotlin.Int){}[0] 18 | final fun equals(kotlin/Any?): kotlin/Boolean // org.kotlincrypto.core.mac/Mac.equals|equals(kotlin.Any?){}[0] 19 | final fun hashCode(): kotlin/Int // org.kotlincrypto.core.mac/Mac.hashCode|hashCode(){}[0] 20 | final fun macLength(): kotlin/Int // org.kotlincrypto.core.mac/Mac.macLength|macLength(){}[0] 21 | final fun reset() // org.kotlincrypto.core.mac/Mac.reset|reset(){}[0] 22 | final fun reset(kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.reset|reset(kotlin.ByteArray){}[0] 23 | final fun toString(): kotlin/String // org.kotlincrypto.core.mac/Mac.toString|toString(){}[0] 24 | final fun update(kotlin/Byte) // org.kotlincrypto.core.mac/Mac.update|update(kotlin.Byte){}[0] 25 | final fun update(kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.update|update(kotlin.ByteArray){}[0] 26 | final fun update(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.mac/Mac.update|update(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] 27 | 28 | abstract class Engine : org.kotlincrypto.core/Copyable, org.kotlincrypto.core/Resettable, org.kotlincrypto.core/Updatable { // org.kotlincrypto.core.mac/Mac.Engine|null[0] 29 | constructor (kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.Engine.|(kotlin.ByteArray){}[0] 30 | constructor (kotlin/ByteArray, kotlin/Boolean) // org.kotlincrypto.core.mac/Mac.Engine.|(kotlin.ByteArray;kotlin.Boolean){}[0] 31 | constructor (org.kotlincrypto.core.mac/Mac.Engine) // org.kotlincrypto.core.mac/Mac.Engine.|(org.kotlincrypto.core.mac.Mac.Engine){}[0] 32 | 33 | final val resetOnDoFinal // org.kotlincrypto.core.mac/Mac.Engine.resetOnDoFinal|{}resetOnDoFinal[0] 34 | final fun (): kotlin/Boolean // org.kotlincrypto.core.mac/Mac.Engine.resetOnDoFinal.|(){}[0] 35 | 36 | abstract fun doFinal(): kotlin/ByteArray // org.kotlincrypto.core.mac/Mac.Engine.doFinal|doFinal(){}[0] 37 | abstract fun macLength(): kotlin/Int // org.kotlincrypto.core.mac/Mac.Engine.macLength|macLength(){}[0] 38 | abstract fun reset(kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.Engine.reset|reset(kotlin.ByteArray){}[0] 39 | final fun equals(kotlin/Any?): kotlin/Boolean // org.kotlincrypto.core.mac/Mac.Engine.equals|equals(kotlin.Any?){}[0] 40 | final fun hashCode(): kotlin/Int // org.kotlincrypto.core.mac/Mac.Engine.hashCode|hashCode(){}[0] 41 | open fun doFinalInto(kotlin/ByteArray, kotlin/Int) // org.kotlincrypto.core.mac/Mac.Engine.doFinalInto|doFinalInto(kotlin.ByteArray;kotlin.Int){}[0] 42 | open fun update(kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.Engine.update|update(kotlin.ByteArray){}[0] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/mac/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | plugins { 17 | id("configuration") 18 | } 19 | 20 | kmpConfiguration { 21 | configureShared(java9ModuleName = "org.kotlincrypto.core.mac", publish = true) { 22 | common { 23 | sourceSetMain { 24 | dependencies { 25 | api(project(":library:core")) 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /library/mac/gradle.properties: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 KotlinCrypto 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | POM_ARTIFACT_ID=mac 15 | POM_NAME=KotlinCrypto Message Authentication Code Abstraction 16 | POM_DESCRIPTION=Core component for creating Macs 17 | -------------------------------------------------------------------------------- /library/mac/src/commonMain/kotlin/org/kotlincrypto/core/mac/Mac.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package org.kotlincrypto.core.mac 19 | 20 | import org.kotlincrypto.core.* 21 | import org.kotlincrypto.error.InvalidKeyException 22 | import org.kotlincrypto.error.InvalidParameterException 23 | import org.kotlincrypto.error.ShortBufferException 24 | 25 | /** 26 | * Core abstraction for Message Authentication Code implementations. 27 | * 28 | * A MAC provides a way to check the integrity of information transmitted 29 | * over or stored in an unreliable medium, based on a secret (key). Typically, 30 | * message authentication codes are used between two parties that share a 31 | * secret (key) in order to validate information transmitted between these 32 | * parties. 33 | * 34 | * Implementations of [Mac] should follow the Java naming guidelines for 35 | * [algorithm] which can be found at: 36 | * 37 | * https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#mac-algorithms 38 | * 39 | * @see [Engine] 40 | * */ 41 | public expect abstract class Mac: Algorithm, Copyable, Resettable, Updatable { 42 | 43 | /** 44 | * Creates a new [Mac] for the specified parameters. 45 | * 46 | * @param [algorithm] See [Algorithm.algorithm] 47 | * @param [engine] See [Engine] 48 | * @throws [InvalidParameterException] when: 49 | * - [algorithm] is blank 50 | * */ 51 | @Throws(InvalidParameterException::class) 52 | protected constructor(algorithm: String, engine: Engine) 53 | 54 | /** 55 | * Creates a new [Mac] from [other], copying its [Engine] and state. 56 | * 57 | * Implementors of [Mac] should have a private secondary constructor 58 | * that is utilized by its [copy] implementation. 59 | * 60 | * e.g. 61 | * 62 | * public class HmacSHA256: Mac { 63 | * 64 | * // ... 65 | * 66 | * private constructor(other: HmacSHA256): super(other) { 67 | * // Copy implementation details... 68 | * } 69 | * 70 | * // Notice the updated return type 71 | * public override fun copy(): HmacSHA256 = HmacSHA256(this) 72 | * 73 | * // ... 74 | * } 75 | * */ 76 | protected constructor(other: Mac) 77 | 78 | /** 79 | * The number of bytes the implementation returns when [doFinal] is called. 80 | * */ 81 | public fun macLength(): Int 82 | 83 | // See Algorithm interface documentation 84 | public final override fun algorithm(): String 85 | 86 | // See Updatable interface documentation 87 | public final override fun update(input: Byte) 88 | // See Updatable interface documentation 89 | public final override fun update(input: ByteArray) 90 | // See Updatable interface documentation 91 | public final override fun update(input: ByteArray, offset: Int, len: Int) 92 | 93 | /** 94 | * Completes the computation, performing final operations and returning 95 | * the resultant array of bytes. The [Mac] is [reset] afterward. 96 | * */ 97 | public fun doFinal(): ByteArray 98 | 99 | /** 100 | * Updates the instance with provided [input] then completes the computation, 101 | * performing final operations and returning the resultant array of bytes. The 102 | * [Mac] is [reset] afterward. 103 | * */ 104 | public fun doFinal(input: ByteArray): ByteArray 105 | 106 | /** 107 | * Completes the computation, performing final operations and placing the 108 | * resultant bytes into the provided [dest] array starting at index [destOffset]. 109 | * The [Mac] is [reset] afterward. 110 | * 111 | * @return The number of bytes put into [dest] (i.e. the [macLength]) 112 | * @throws [IndexOutOfBoundsException] if [destOffset] is inappropriate 113 | * @throws [ShortBufferException] if [macLength] number of bytes are unable 114 | * to fit into [dest] for provided [destOffset] 115 | * */ 116 | public fun doFinalInto(dest: ByteArray, destOffset: Int): Int 117 | 118 | // See Resettable interface documentation 119 | public final override fun reset() 120 | 121 | /** 122 | * Resets the [Mac] and will reinitialize it with the provided key. 123 | * 124 | * This is useful if wanting to zero out the key before de-referencing. 125 | * 126 | * @see [clearKey] 127 | * @throws [InvalidKeyException] if [newKey] is empty, or of a length 128 | * inappropriate for the [Mac] implementation. 129 | * */ 130 | public fun reset(newKey: ByteArray) 131 | 132 | /** 133 | * Helper function that will call [reset] with a blank key in order 134 | * to zero it out. 135 | * */ 136 | public fun clearKey() 137 | 138 | /** 139 | * Core abstraction for powering a [Mac] implementation. 140 | * 141 | * Implementors of [Engine] **must** initialize the instance with the 142 | * provided key parameter. 143 | * */ 144 | protected abstract class Engine: Copyable, Resettable, Updatable { 145 | 146 | /** 147 | * Most [Mac.Engine] are backed by a `Digest`, whereby calling [reset] after 148 | * [doFinal] will cause a double reset (because `Digest.digest` does this inherently). 149 | * By setting this value to `false`, [Engine.reset] will **not** be called whenever 150 | * [doFinal] gets invoked. 151 | * 152 | * **NOTE:** Implementations taking ownership of the automatic reset functionality 153 | * by setting this to `false` must ensure that whatever re-initialization steps were 154 | * taken in their [Engine.reset] function body are executed before their [doFinal] 155 | * and [doFinalInto] implementations return. 156 | * */ 157 | public val resetOnDoFinal: Boolean 158 | 159 | /** 160 | * Initializes a new [Engine] with the provided [key] with the default [resetOnDoFinal] 161 | * value of `true` (i.e. [Engine.reset] will be called automatically after [Engine.doFinal] 162 | * or [Engine.doFinalInto] have been invoked). 163 | * 164 | * @param [key] The key that this [Engine] instance will use to apply its function to 165 | * @throws [InvalidKeyException] if [key] is empty 166 | * */ 167 | @Throws(InvalidKeyException::class) 168 | public constructor(key: ByteArray) 169 | 170 | /** 171 | * Initializes a new [Engine] with the provided [key] and [resetOnDoFinal] configuration. 172 | * 173 | * @param [key] the key that this [Engine] instance will use to apply its function to 174 | * @param [resetOnDoFinal] See [Engine.resetOnDoFinal] documentation 175 | * @throws [InvalidKeyException] if [key] is empty 176 | * */ 177 | @Throws(InvalidKeyException::class) 178 | public constructor(key: ByteArray, resetOnDoFinal: Boolean) 179 | 180 | /** 181 | * Creates a new [Engine] from [other], copying its state. 182 | * */ 183 | protected constructor(other: Engine) 184 | 185 | /** 186 | * The number of bytes the implementation returns when [doFinal] is called. 187 | * */ 188 | public abstract fun macLength(): Int 189 | 190 | // See Updatable interface documentation 191 | public override fun update(input: ByteArray) 192 | 193 | /** 194 | * Completes the computation, performing final operations and returning 195 | * the resultant array of bytes. The [Engine] is [reset] afterward. 196 | * */ 197 | public abstract fun doFinal(): ByteArray 198 | 199 | /** 200 | * Called to complete the computation, performing final operations and placing 201 | * the resultant bytes into the provided [dest] array starting at index [destOffset]. 202 | * The [Engine] is [reset] afterward. 203 | * 204 | * Implementations should override this addition to the API for performance reasons. 205 | * If overridden, `super.doFinalInto` should **not** be called. 206 | * 207 | * **NOTE:** The public [Mac.doFinalInto] function always checks [dest] for capacity 208 | * of [macLength], starting at [destOffset], before calling this function. 209 | * 210 | * @param [dest] The array to place resultant bytes 211 | * @param [destOffset] The index to begin placing bytes into [dest] 212 | * */ 213 | public open fun doFinalInto(dest: ByteArray, destOffset: Int) 214 | 215 | /** 216 | * Resets the [Engine] and will reinitialize it with the provided key. 217 | * 218 | * **NOTE:** [newKey] is checked to be non-empty by the [Mac] abstraction 219 | * before passing it here. Implementations should ensure any old key material 220 | * is zeroed out. 221 | * 222 | * @throws [InvalidKeyException] if [newKey] is a length inappropriate 223 | * for the [Engine] implementation. 224 | * */ 225 | @Throws(InvalidKeyException::class) 226 | public abstract fun reset(newKey: ByteArray) 227 | 228 | /** @suppress */ 229 | final override fun equals(other: Any?): Boolean 230 | /** @suppress */ 231 | final override fun hashCode(): Int 232 | } 233 | 234 | /** @suppress */ 235 | public final override fun equals(other: Any?): Boolean 236 | /** @suppress */ 237 | public final override fun hashCode(): Int 238 | /** @suppress */ 239 | public final override fun toString(): String 240 | } 241 | -------------------------------------------------------------------------------- /library/mac/src/commonMain/kotlin/org/kotlincrypto/core/mac/internal/-CommonPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("KotlinRedundantDiagnosticSuppress", "NOTHING_TO_INLINE") 17 | 18 | package org.kotlincrypto.core.mac.internal 19 | 20 | import org.kotlincrypto.core.mac.Mac 21 | import org.kotlincrypto.error.InvalidParameterException 22 | import org.kotlincrypto.error.ShortBufferException 23 | import org.kotlincrypto.error.requireParam 24 | import kotlin.contracts.ExperimentalContracts 25 | import kotlin.contracts.InvocationKind 26 | import kotlin.contracts.contract 27 | 28 | private val SINGLE_0BYTE_KEY = ByteArray(1) { 0 } 29 | 30 | @Suppress("UnusedReceiverParameter") 31 | @Throws(InvalidParameterException::class) 32 | internal inline fun Mac.commonInit(algorithm: String) { 33 | requireParam(algorithm.isNotBlank()) { "algorithm cannot be blank" } 34 | } 35 | 36 | internal inline fun Mac.commonToString(): String { 37 | return "Mac[${algorithm()}]@${hashCode()}" 38 | } 39 | 40 | @Throws(Exception::class) 41 | @OptIn(ExperimentalContracts::class) 42 | internal inline fun ByteArray.commonCheckArgs( 43 | offset: Int, 44 | len: Int, 45 | onShortInput: (message: String) -> Exception = ::IllegalArgumentException, 46 | onOutOfBounds: (message: String) -> Exception = ::IndexOutOfBoundsException, 47 | ) { 48 | contract { 49 | callsInPlace(onShortInput, InvocationKind.AT_MOST_ONCE) 50 | callsInPlace(onOutOfBounds, InvocationKind.AT_MOST_ONCE) 51 | } 52 | 53 | if (size - offset < len) throw onShortInput("Too Short. size[$size] - offset[$offset] < len[$len]") 54 | if (offset < 0) throw onOutOfBounds("offset[$offset] < 0") 55 | if (len < 0) throw onOutOfBounds("len[$len] < 0") 56 | if (offset > size - len) throw onOutOfBounds("offset[$offset] > size[$size] - len[$len]") 57 | } 58 | 59 | @OptIn(ExperimentalContracts::class) 60 | internal inline fun Mac.commonClearKey(engineReset: (ByteArray) -> Unit) { 61 | contract { 62 | callsInPlace(engineReset, InvocationKind.AT_LEAST_ONCE) 63 | } 64 | 65 | try { 66 | engineReset(SINGLE_0BYTE_KEY) 67 | } catch (e1: Throwable) { 68 | try { 69 | engineReset(ByteArray(macLength())) 70 | } catch (e2: Throwable) { 71 | e2.addSuppressed(e1) 72 | throw e2 73 | } 74 | } 75 | } 76 | 77 | @OptIn(ExperimentalContracts::class) 78 | @Throws(IndexOutOfBoundsException::class, ShortBufferException::class) 79 | internal inline fun Mac.commonDoFinalInto( 80 | dest: ByteArray, 81 | destOffset: Int, 82 | engineResetOnDoFinal: Boolean, 83 | engineDoFinalInto: (dest: ByteArray, destOffset: Int) -> Unit, 84 | engineReset: () -> Unit, 85 | ): Int { 86 | contract { 87 | callsInPlace(engineDoFinalInto, InvocationKind.AT_MOST_ONCE) 88 | callsInPlace(engineReset, InvocationKind.AT_MOST_ONCE) 89 | } 90 | 91 | val len = macLength() 92 | dest.commonCheckArgs(destOffset, len, onShortInput = ::ShortBufferException) 93 | engineDoFinalInto(dest, destOffset) 94 | if (engineResetOnDoFinal) engineReset() 95 | return len 96 | } 97 | -------------------------------------------------------------------------------- /library/mac/src/commonTest/kotlin/org/kotlincrypto/core/mac/MacUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.mac 17 | 18 | import org.kotlincrypto.error.InvalidKeyException 19 | import org.kotlincrypto.error.InvalidParameterException 20 | import org.kotlincrypto.error.ShortBufferException 21 | import kotlin.test.* 22 | 23 | class MacUnitTest { 24 | 25 | @Test 26 | fun givenMac_whenEmptyKey_thenThrowsException() { 27 | assertFailsWith { 28 | TestMac(ByteArray(0), "not empty") 29 | } 30 | } 31 | 32 | @Test 33 | fun givenMac_whenBlankAlgorithm_thenThrowsException() { 34 | assertFailsWith { 35 | TestMac(ByteArray(5), " ") 36 | } 37 | } 38 | 39 | @Test 40 | fun givenMac_whenResetWithEmptyKey_thenThrowsException() { 41 | val mac = TestMac(ByteArray(5), "my algorithm") 42 | assertFailsWith { 43 | mac.reset(ByteArray(0)) 44 | } 45 | } 46 | 47 | @Test 48 | fun givenMac_whenInstantiated_thenInitializes() { 49 | val mac = TestMac(ByteArray(5), "not blank") 50 | assertFailsWith { 51 | // test mac throws ConcurrentModificationException 52 | // on update for single byte input for verifying 53 | // instantiation is in working order. 54 | mac.update(5) 55 | } 56 | } 57 | 58 | @Test 59 | fun givenMac_whenCopied_thenIsNewInstance() { 60 | val mac = TestMac(ByteArray(5), "not blank") 61 | val copy = mac.copy() 62 | assertNotEquals(mac, copy) 63 | } 64 | 65 | @Test 66 | fun givenMac_whenDoFinal_thenEngineResetIsCalled() { 67 | var resetCount = 0 68 | var doFinalCount = 0 69 | val finalExpected = ByteArray(20) { it.toByte() } 70 | 71 | val mac = TestMac( 72 | ByteArray(5), 73 | "not blank", 74 | macLen = finalExpected.size, 75 | reset = { resetCount++ }, 76 | doFinal = { doFinalCount++; finalExpected }, 77 | ) 78 | mac.update(ByteArray(20)) 79 | 80 | // doFinal 81 | assertEquals(finalExpected, mac.doFinal()) 82 | assertEquals(1, doFinalCount) 83 | assertEquals(1, resetCount) 84 | 85 | // update & doFinal 86 | assertEquals(finalExpected, mac.doFinal(ByteArray(20))) 87 | assertEquals(2, doFinalCount) 88 | assertEquals(2, resetCount) 89 | 90 | // doFinalInto 91 | assertEquals(finalExpected.size, mac.doFinalInto(ByteArray(25), 0)) 92 | assertEquals(3, doFinalCount) 93 | assertEquals(3, resetCount) 94 | } 95 | 96 | @Test 97 | fun givenMacEngine_whenResetOnDoFinalFalse_thenEngineResetIsNOTCalled() { 98 | var resetCount = 0 99 | var doFinalCount = 0 100 | val finalExpected = ByteArray(15) { (it + 25).toByte() } 101 | 102 | val mac = TestMac( 103 | ByteArray(5), 104 | algorithm = "test resetOnDoFinal false", 105 | macLen = finalExpected.size, 106 | resetOnDoFinal = false, 107 | reset = { resetCount++ }, 108 | doFinal = { doFinalCount++; finalExpected }, 109 | ) 110 | 111 | mac.reset() 112 | assertEquals(1, resetCount) 113 | 114 | // doFinal 115 | mac.doFinal() 116 | assertEquals(1, doFinalCount) 117 | assertEquals(1, resetCount) 118 | 119 | // update & doFinal 120 | mac.doFinal(ByteArray(25)) 121 | assertEquals(2, doFinalCount) 122 | assertEquals(1, resetCount) 123 | 124 | // doFinalInto 125 | mac.doFinalInto(ByteArray(finalExpected.size), 0) 126 | assertEquals(3, doFinalCount) 127 | assertEquals(1, resetCount) 128 | 129 | mac.reset() 130 | assertEquals(2, resetCount) 131 | } 132 | 133 | @Test 134 | fun givenMac_whenClearKey_thenSingle0ByteKeyPassedToEngine() { 135 | var zeroKey: ByteArray? = null 136 | 137 | TestMac( 138 | ByteArray(5), 139 | "test rekey", 140 | rekey = { zeroKey = it } 141 | ).clearKey() 142 | 143 | assertNotNull(zeroKey) 144 | assertContentEquals(ByteArray(1) { 0 }, zeroKey) 145 | } 146 | 147 | @Test 148 | fun givenMac_whenDoFinalInto_thenDefaultImplementationCopiesResultIntoDest() { 149 | val expected = ByteArray(10) { 1 } 150 | val mac = TestMac( 151 | key = ByteArray(5), 152 | algorithm = "test doFinalInto", 153 | macLen = expected.size, 154 | doFinal = { expected.copyOf() } 155 | ) 156 | val actual = ByteArray(expected.size + 2) { 4 } 157 | mac.doFinalInto(actual, 1) 158 | 159 | assertEquals(4, actual[0]) 160 | assertEquals(4, actual[actual.size - 1]) 161 | for (i in expected.indices) { 162 | assertEquals(expected[i], actual[i + 1]) 163 | } 164 | } 165 | 166 | @Test 167 | fun givenMac_whenLength0_thenDoFinalIntoDoesNotFail() { 168 | val mac = TestMac( 169 | key = ByteArray(5), 170 | algorithm = "test doFinalInto 0", 171 | macLen = 0 172 | ) 173 | 174 | mac.doFinalInto(ByteArray(0), 0) 175 | mac.doFinalInto(ByteArray(2), 1) 176 | mac.doFinalInto(ByteArray(2), 2) 177 | } 178 | 179 | @Test 180 | fun givenMac_whenDoFinalInto_thenThrowsExceptionsAsExpected() { 181 | val mac = TestMac( 182 | key = ByteArray(5), 183 | algorithm = "test doFinalInto Exceptions", 184 | macLen = 5, 185 | ) 186 | val mSize = mac.macLength() 187 | assertFailsWith { mac.doFinalInto(ByteArray(mSize), 1) } 188 | assertFailsWith { mac.doFinalInto(ByteArray(mSize), -1) } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /library/mac/src/commonTest/kotlin/org/kotlincrypto/core/mac/TestMac.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.mac 17 | 18 | class TestMac : Mac { 19 | 20 | constructor( 21 | key: ByteArray, 22 | algorithm: String, 23 | macLen: Int = 0, 24 | resetOnDoFinal: Boolean = true, 25 | reset: () -> Unit = {}, 26 | rekey: (new: ByteArray) -> Unit = {}, 27 | doFinal: () -> ByteArray = { ByteArray(macLen) }, 28 | ): super(algorithm, TestEngine(key, reset, rekey, doFinal, macLen, resetOnDoFinal)) 29 | 30 | private constructor(algorithm: String, engine: TestEngine): super(algorithm, engine) 31 | private constructor(other: TestMac): super(other) 32 | 33 | override fun copy(): Mac = TestMac(this) 34 | 35 | private class TestEngine: Engine { 36 | 37 | private val reset: () -> Unit 38 | private val rekey: (new: ByteArray) -> Unit 39 | private val doFinal: () -> ByteArray 40 | private val macLen: Int 41 | 42 | constructor( 43 | key: ByteArray, 44 | reset: () -> Unit, 45 | rekey: (new: ByteArray) -> Unit, 46 | doFinal: () -> ByteArray, 47 | macLen: Int, 48 | resetOnDoFinal: Boolean, 49 | ): super(key, resetOnDoFinal) { 50 | this.reset = reset 51 | this.rekey = rekey 52 | this.doFinal = doFinal 53 | this.macLen = macLen 54 | } 55 | 56 | private constructor(other: TestEngine): super(other) { 57 | this.reset = other.reset 58 | this.rekey = other.rekey 59 | this.doFinal = other.doFinal 60 | this.macLen = other.macLen 61 | } 62 | 63 | // To ensure that Java implementation initializes javax.crypto.Mac 64 | // on instantiation so that it does not throw IllegalStateException 65 | // whenever updating. 66 | override fun update(input: Byte) { throw ConcurrentModificationException() } 67 | 68 | override fun reset() { reset.invoke() } 69 | override fun reset(newKey: ByteArray) { rekey.invoke(newKey) } 70 | override fun update(input: ByteArray) {} 71 | override fun update(input: ByteArray, offset: Int, len: Int) {} 72 | override fun macLength(): Int = macLen 73 | override fun doFinal(): ByteArray = doFinal.invoke() 74 | 75 | override fun copy(): Engine = TestEngine(this) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/mac/src/jvmMain/java9/module-info.java: -------------------------------------------------------------------------------- 1 | module org.kotlincrypto.core.mac { 2 | requires kotlin.stdlib; 3 | requires transitive org.kotlincrypto.core; 4 | 5 | exports org.kotlincrypto.core.mac; 6 | } 7 | -------------------------------------------------------------------------------- /library/mac/src/jvmMain/kotlin/org/kotlincrypto/core/mac/internal/AndroidApi21to23MacSpiProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.mac.internal 17 | 18 | import org.kotlincrypto.core.KC_ANDROID_SDK_INT 19 | import org.kotlincrypto.core.InternalKotlinCryptoApi 20 | import java.security.NoSuchAlgorithmException 21 | import java.security.Provider 22 | import javax.crypto.MacSpi 23 | 24 | /** 25 | * Android API 21-23 requires that a Provider be set, otherwise 26 | * when [javax.crypto.Mac.init] is called it will not use the 27 | * provided [org.kotlincrypto.core.mac.Mac.Engine] (i.e., [spi]). 28 | * 29 | * This simply wraps the [org.kotlincrypto.core.mac.Mac.Engine] 30 | * such that initial [javax.crypto.Mac.init] call sets it 31 | * as the spiImpl, and does not look to system providers 32 | * for an instance that supports the [algorithm]. 33 | * 34 | * See: https://github.com/KotlinCrypto/core/issues/37 35 | * See: https://github.com/KotlinCrypto/core/issues/41 36 | * See: https://github.com/KotlinCrypto/core/issues/42 37 | * */ 38 | @Suppress("DEPRECATION") 39 | internal class AndroidApi21to23MacSpiProvider private constructor( 40 | @Volatile 41 | private var spi: MacSpi?, 42 | private val algorithm: String, 43 | ): Provider("KC", 0.0, "") { 44 | 45 | override fun getService(type: String?, algorithm: String?): Service = synchronized(this) { 46 | if (type == "Mac" && algorithm == this.algorithm && spi != null) { 47 | SpiProviderService() 48 | } else { 49 | throw NoSuchAlgorithmException("type[$type] and algorithm[$algorithm] not supported") 50 | } 51 | } 52 | 53 | private inner class SpiProviderService: Service( 54 | /* provider */ this, 55 | /* type */ "Mac", 56 | /* algorithm */ algorithm, 57 | /* className */ "", 58 | /* aliases */ null, 59 | /* attributes */ null 60 | ) { 61 | override fun newInstance(constructorParameter: Any?): Any = synchronized(this@AndroidApi21to23MacSpiProvider) { 62 | val engine = spi ?: throw NoSuchAlgorithmException("algorithm[$algorithm] not supported") 63 | 64 | // javax.crypto.Mac.init was called with a blanked key via org.kotlincrypto.core.mac.Mac's 65 | // init block in order to set javax.crypto.Mac.initialized to true. Return 66 | // the MacSpi (i.e. org.kotlincrypto.core.mac.Mac.Engine), and null the reference as 67 | // we cannot provide a new instance if called again and do not want to return the 68 | // same, already initialized org.kotlincrypto.core.mac.Mac.Engine 69 | spi = null 70 | 71 | return engine 72 | } 73 | } 74 | 75 | internal companion object { 76 | 77 | @JvmSynthetic 78 | internal fun createOrNull(engine: MacSpi, algorithm: String): AndroidApi21to23MacSpiProvider? { 79 | @OptIn(InternalKotlinCryptoApi::class) 80 | return KC_ANDROID_SDK_INT?.let { sdkInt -> 81 | if (sdkInt in 21..23) { 82 | AndroidApi21to23MacSpiProvider(engine, algorithm) 83 | } else { 84 | null 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /library/mac/src/jvmTest/kotlin/org/kotlincrypto/core/mac/JvmMacUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.mac 17 | 18 | import junit.framework.TestCase.assertEquals 19 | import javax.crypto.spec.SecretKeySpec 20 | import kotlin.test.* 21 | 22 | class JvmMacUnitTest { 23 | 24 | private val key = ByteArray(20) { it.toByte() } 25 | 26 | @Test 27 | fun givenJvm_whenNotAndroid_providerIsNotSet() { 28 | val mac = TestMac(key, "My Algorithm", doFinal = { key }) 29 | assertEquals(key, mac.doFinal()) 30 | assertNull(mac.provider) 31 | } 32 | 33 | @Test 34 | fun givenJvm_whenJavaxCryptoMacInitInvoked_thenResetsWithNewKey() { 35 | val oldKey = key 36 | val newKey = oldKey.copyOf(oldKey.size - 4) 37 | var rekey: ByteArray? = null 38 | val mac = TestMac(key, "My Algorithm", rekey = { rekey = it }) 39 | 40 | mac.init(SecretKeySpec(newKey, mac.algorithm())) 41 | assertNotNull(rekey) 42 | assertContentEquals(newKey, rekey) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/xof/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /library/xof/README.md: -------------------------------------------------------------------------------- 1 | # Module xof 2 | 3 | `Xof` abstraction for [Extendable-Output Functions][url-pub-xof] 4 | 5 | `XOF`s are very similar to `Digest` and `Mac` except that instead of calling `digest()` 6 | or `doFinal()`, which returns a fixed size `ByteArray`, their output size can be variable 7 | in length. 8 | 9 | As such, [KotlinCrypto][url-kotlincrypto] takes the approach of making them distinctly 10 | different from those types, while implementing the same interfaces (`Algorithm`, `Copyable`, 11 | `Resettable`, `Updatable`). 12 | 13 | Output for an `Xof` is done by reading, instead. 14 | 15 | ```kotlin 16 | // Using SHAKE128 from KotlinCrypto/hash repo as an example 17 | import org.kotlincrypto.hash.sha3.SHAKE128 18 | 19 | fun main() { 20 | val xof: Xof = SHAKE128.xOf() 21 | val bytes = Random.Default.nextBytes(615) 22 | 23 | // Xof implements interface Algorithm 24 | println(xof.algorithm()) 25 | // SHAKE128 26 | 27 | // Xof implements interface Updatable 28 | xof.update(5.toByte()) 29 | xof.update(bytes) 30 | xof.update(bytes, 10, 88) 31 | 32 | // Xof implements interface Resettable 33 | xof.reset() 34 | 35 | xof.update(bytes) 36 | 37 | // Xof implements interface Copyable 38 | xof.copy() 39 | 40 | val out1 = ByteArray(100) 41 | val out2 = ByteArray(12345) 42 | 43 | // Use produces a Reader which auto-closes when your action finishes. 44 | // Reader is using a snapshot of the Xof state (thus the 45 | // optional argument to resetXof with a default of true). 46 | xof.use(resetXof = false) { read(out1, 0, out1.size); read(out2) } 47 | 48 | val out3 = ByteArray(out1.size) 49 | val out4 = ByteArray(out2.size) 50 | 51 | // Can also create a Reader that won't auto-close 52 | val reader = xof.reader(resetXof = false) 53 | reader.read(out3) 54 | reader.read(out4) 55 | reader.close() 56 | 57 | try { 58 | // The Reader has been closed and will throw 59 | // exception when trying to read from again. 60 | reader.use { read(out4) } 61 | } catch (e: IllegalStateException) { 62 | e.printStackTrace() 63 | } 64 | 65 | // Contents are the same because Reader uses 66 | // a snapshot of Xof, which was not updated 67 | // between production of Readers. 68 | assertContentEquals(out1 + out2, out3 + out4) 69 | 70 | // Still able to update Xof, independent of the production 71 | // and usage of Readers. 72 | xof.update(10.toByte()) 73 | xof.use { read(out3); read(out4) } 74 | 75 | try { 76 | assertContentEquals(out1 + out2, out3 + out4) 77 | throw IllegalStateException() 78 | } catch (_: AssertionError) { 79 | // pass 80 | } 81 | } 82 | ``` 83 | 84 | ```kotlin 85 | // Using CryptoRand from KotlinCrypto/random repo as an example 86 | import org.kotlincrypto.random.CryptoRand 87 | // Using KMAC128 from KotlinCrypto/MACs repo as an example 88 | import org.kotlincrypto.macs.kmac.KMAC128 89 | 90 | fun main() { 91 | val key = CryptoRand.Default.nextBytes(ByteArray(100)) 92 | val kmacXof: Xof = KMAC128.xOf(key) 93 | 94 | // If Xof is for a Mac that implements ReKeyableXofAlgorithm, 95 | // reinitialize the instance via the `Xof.Companion.reset` 96 | // extension function for reuse. 97 | val newKey = CryptoRand.Default.nextBytes(ByteArray(100)) 98 | kmacXof.reset(newKey = newKey) 99 | 100 | // Or zero out key material before dereferencing 101 | kmacXof.reset(newKey = ByteArray(1)) 102 | } 103 | ``` 104 | 105 | [url-kotlincrypto]: https://github.com/KotlinCrypto 106 | [url-pub-xof]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf 107 | -------------------------------------------------------------------------------- /library/xof/api/xof.api: -------------------------------------------------------------------------------- 1 | public abstract interface class org/kotlincrypto/core/xof/ReKeyableXofAlgorithm : org/kotlincrypto/core/xof/XofAlgorithm { 2 | public abstract fun reset ([B)V 3 | } 4 | 5 | public abstract class org/kotlincrypto/core/xof/Xof : org/kotlincrypto/core/Algorithm, org/kotlincrypto/core/Copyable, org/kotlincrypto/core/Resettable, org/kotlincrypto/core/Updatable { 6 | public static final field Companion Lorg/kotlincrypto/core/xof/Xof$Companion; 7 | protected final field delegate Lorg/kotlincrypto/core/xof/XofAlgorithm; 8 | public synthetic fun (Lorg/kotlincrypto/core/xof/XofAlgorithm;Lkotlin/jvm/internal/DefaultConstructorMarker;)V 9 | protected abstract fun newReader ()Lorg/kotlincrypto/core/xof/Xof$Reader; 10 | public final fun reader ()Lorg/kotlincrypto/core/xof/Xof$Reader; 11 | public final fun reader (Z)Lorg/kotlincrypto/core/xof/Xof$Reader; 12 | public static synthetic fun reader$default (Lorg/kotlincrypto/core/xof/Xof;ZILjava/lang/Object;)Lorg/kotlincrypto/core/xof/Xof$Reader; 13 | public static final fun reset (Lorg/kotlincrypto/core/xof/Xof;[B)V 14 | public final fun toString ()Ljava/lang/String; 15 | public final fun use (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; 16 | public final fun use (ZLkotlin/jvm/functions/Function1;)Ljava/lang/Object; 17 | public static synthetic fun use$default (Lorg/kotlincrypto/core/xof/Xof;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; 18 | } 19 | 20 | public final class org/kotlincrypto/core/xof/Xof$Companion { 21 | public final fun reset (Lorg/kotlincrypto/core/xof/Xof;[B)V 22 | } 23 | 24 | public abstract class org/kotlincrypto/core/xof/Xof$Reader { 25 | public fun (Lorg/kotlincrypto/core/xof/Xof;)V 26 | public final fun bytesRead ()J 27 | public final fun close ()V 28 | protected abstract fun closeProtected ()V 29 | public final fun isClosed ()Z 30 | public final fun read ([B)I 31 | public final fun read ([BII)I 32 | protected abstract fun readProtected ([BII)I 33 | public final fun toString ()Ljava/lang/String; 34 | public final fun use (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; 35 | } 36 | 37 | public final class org/kotlincrypto/core/xof/Xof$Utils { 38 | public static final field INSTANCE Lorg/kotlincrypto/core/xof/Xof$Utils; 39 | public static final fun leftEncode (I)[B 40 | public static final fun leftEncode (II)[B 41 | public static final fun leftEncode (J)[B 42 | public static final fun rightEncode (I)[B 43 | public static final fun rightEncode (II)[B 44 | public static final fun rightEncode (J)[B 45 | } 46 | 47 | public abstract interface class org/kotlincrypto/core/xof/XofAlgorithm : org/kotlincrypto/core/Algorithm { 48 | } 49 | 50 | public abstract class org/kotlincrypto/core/xof/XofFactory { 51 | public fun ()V 52 | } 53 | 54 | protected abstract class org/kotlincrypto/core/xof/XofFactory$XofDelegate : org/kotlincrypto/core/xof/Xof, org/kotlincrypto/core/Algorithm, org/kotlincrypto/core/Resettable, org/kotlincrypto/core/Updatable { 55 | protected fun (Lorg/kotlincrypto/core/xof/XofFactory;Lorg/kotlincrypto/core/xof/XofAlgorithm;)V 56 | public fun algorithm ()Ljava/lang/String; 57 | public final fun equals (Ljava/lang/Object;)Z 58 | public final fun hashCode ()I 59 | protected final fun newReader ()Lorg/kotlincrypto/core/xof/Xof$Reader; 60 | protected abstract fun newReader (Lorg/kotlincrypto/core/xof/XofAlgorithm;)Lorg/kotlincrypto/core/xof/Xof$Reader; 61 | public fun reset ()V 62 | public fun update (B)V 63 | public fun update ([B)V 64 | public fun update ([BII)V 65 | } 66 | 67 | -------------------------------------------------------------------------------- /library/xof/api/xof.klib.api: -------------------------------------------------------------------------------- 1 | // Klib ABI Dump 2 | // Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] 3 | // Rendering settings: 4 | // - Signature version: 2 5 | // - Show manifest properties: true 6 | // - Show declarations: true 7 | 8 | // Library unique name: 9 | abstract interface org.kotlincrypto.core.xof/ReKeyableXofAlgorithm : org.kotlincrypto.core.xof/XofAlgorithm { // org.kotlincrypto.core.xof/ReKeyableXofAlgorithm|null[0] 10 | abstract fun reset(kotlin/ByteArray) // org.kotlincrypto.core.xof/ReKeyableXofAlgorithm.reset|reset(kotlin.ByteArray){}[0] 11 | } 12 | 13 | abstract interface org.kotlincrypto.core.xof/XofAlgorithm : org.kotlincrypto.core/Algorithm // org.kotlincrypto.core.xof/XofAlgorithm|null[0] 14 | 15 | abstract class <#A: org.kotlincrypto.core.xof/XofAlgorithm> org.kotlincrypto.core.xof/XofFactory { // org.kotlincrypto.core.xof/XofFactory|null[0] 16 | constructor () // org.kotlincrypto.core.xof/XofFactory.|(){}[0] 17 | 18 | abstract inner class XofDelegate : org.kotlincrypto.core.xof/Xof<#A>, org.kotlincrypto.core/Algorithm, org.kotlincrypto.core/Resettable, org.kotlincrypto.core/Updatable { // org.kotlincrypto.core.xof/XofFactory.XofDelegate|null[0] 19 | constructor (#A) // org.kotlincrypto.core.xof/XofFactory.XofDelegate.|(2:0){}[0] 20 | 21 | abstract fun newReader(#A): org.kotlincrypto.core.xof/Xof.Reader<#A> // org.kotlincrypto.core.xof/XofFactory.XofDelegate.newReader|newReader(2:0){}[0] 22 | final fun equals(kotlin/Any?): kotlin/Boolean // org.kotlincrypto.core.xof/XofFactory.XofDelegate.equals|equals(kotlin.Any?){}[0] 23 | final fun hashCode(): kotlin/Int // org.kotlincrypto.core.xof/XofFactory.XofDelegate.hashCode|hashCode(){}[0] 24 | final fun newReader(): org.kotlincrypto.core.xof/Xof.Reader<#A> // org.kotlincrypto.core.xof/XofFactory.XofDelegate.newReader|newReader(){}[0] 25 | open fun algorithm(): kotlin/String // org.kotlincrypto.core.xof/XofFactory.XofDelegate.algorithm|algorithm(){}[0] 26 | open fun reset() // org.kotlincrypto.core.xof/XofFactory.XofDelegate.reset|reset(){}[0] 27 | open fun update(kotlin/Byte) // org.kotlincrypto.core.xof/XofFactory.XofDelegate.update|update(kotlin.Byte){}[0] 28 | open fun update(kotlin/ByteArray) // org.kotlincrypto.core.xof/XofFactory.XofDelegate.update|update(kotlin.ByteArray){}[0] 29 | open fun update(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.xof/XofFactory.XofDelegate.update|update(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] 30 | } 31 | } 32 | 33 | sealed class <#A: org.kotlincrypto.core.xof/XofAlgorithm> org.kotlincrypto.core.xof/Xof : org.kotlincrypto.core/Algorithm, org.kotlincrypto.core/Copyable>, org.kotlincrypto.core/Resettable, org.kotlincrypto.core/Updatable { // org.kotlincrypto.core.xof/Xof|null[0] 34 | final val delegate // org.kotlincrypto.core.xof/Xof.delegate|{}delegate[0] 35 | final fun (): #A // org.kotlincrypto.core.xof/Xof.delegate.|(){}[0] 36 | 37 | abstract fun newReader(): org.kotlincrypto.core.xof/Xof.Reader<#A> // org.kotlincrypto.core.xof/Xof.newReader|newReader(){}[0] 38 | final fun reader(kotlin/Boolean = ...): org.kotlincrypto.core.xof/Xof.Reader<#A> // org.kotlincrypto.core.xof/Xof.reader|reader(kotlin.Boolean){}[0] 39 | final fun toString(): kotlin/String // org.kotlincrypto.core.xof/Xof.toString|toString(){}[0] 40 | final inline fun <#A1: kotlin/Any?> use(kotlin/Boolean = ..., kotlin/Function1, #A1>): #A1 // org.kotlincrypto.core.xof/Xof.use|use(kotlin.Boolean;kotlin.Function1,0:0>){0§}[0] 41 | 42 | abstract inner class Reader { // org.kotlincrypto.core.xof/Xof.Reader|null[0] 43 | constructor () // org.kotlincrypto.core.xof/Xof.Reader.|(){}[0] 44 | 45 | final val bytesRead // org.kotlincrypto.core.xof/Xof.Reader.bytesRead|{}bytesRead[0] 46 | final fun (): kotlin/Long // org.kotlincrypto.core.xof/Xof.Reader.bytesRead.|(){}[0] 47 | 48 | final var isClosed // org.kotlincrypto.core.xof/Xof.Reader.isClosed|{}isClosed[0] 49 | final fun (): kotlin/Boolean // org.kotlincrypto.core.xof/Xof.Reader.isClosed.|(){}[0] 50 | 51 | abstract fun closeProtected() // org.kotlincrypto.core.xof/Xof.Reader.closeProtected|closeProtected(){}[0] 52 | abstract fun readProtected(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // org.kotlincrypto.core.xof/Xof.Reader.readProtected|readProtected(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] 53 | final fun close() // org.kotlincrypto.core.xof/Xof.Reader.close|close(){}[0] 54 | final fun read(kotlin/ByteArray): kotlin/Int // org.kotlincrypto.core.xof/Xof.Reader.read|read(kotlin.ByteArray){}[0] 55 | final fun read(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // org.kotlincrypto.core.xof/Xof.Reader.read|read(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] 56 | final fun toString(): kotlin/String // org.kotlincrypto.core.xof/Xof.Reader.toString|toString(){}[0] 57 | final inline fun <#A2: kotlin/Any?> use(kotlin/Function1, #A2>): #A2 // org.kotlincrypto.core.xof/Xof.Reader.use|use(kotlin.Function1,0:0>){0§}[0] 58 | } 59 | 60 | final object Companion { // org.kotlincrypto.core.xof/Xof.Companion|null[0] 61 | final fun <#A2: org.kotlincrypto.core.xof/ReKeyableXofAlgorithm> (org.kotlincrypto.core.xof/Xof<#A2>).reset(kotlin/ByteArray) // org.kotlincrypto.core.xof/Xof.Companion.reset|reset@org.kotlincrypto.core.xof.Xof<0:0>(kotlin.ByteArray){0§}[0] 62 | } 63 | 64 | final object Utils { // org.kotlincrypto.core.xof/Xof.Utils|null[0] 65 | final fun leftEncode(kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.leftEncode|leftEncode(kotlin.Int){}[0] 66 | final fun leftEncode(kotlin/Int, kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.leftEncode|leftEncode(kotlin.Int;kotlin.Int){}[0] 67 | final fun leftEncode(kotlin/Long): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.leftEncode|leftEncode(kotlin.Long){}[0] 68 | final fun rightEncode(kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.rightEncode|rightEncode(kotlin.Int){}[0] 69 | final fun rightEncode(kotlin/Int, kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.rightEncode|rightEncode(kotlin.Int;kotlin.Int){}[0] 70 | final fun rightEncode(kotlin/Long): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.rightEncode|rightEncode(kotlin.Long){}[0] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /library/xof/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | plugins { 17 | id("configuration") 18 | } 19 | 20 | kmpConfiguration { 21 | configureShared(java9ModuleName = "org.kotlincrypto.core.xof", publish = true) { 22 | common { 23 | sourceSetMain { 24 | dependencies { 25 | api(project(":library:core")) 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /library/xof/gradle.properties: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 KotlinCrypto 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | POM_ARTIFACT_ID=xof 15 | POM_NAME=KotlinCrypto Extendable-Output Functions 16 | POM_DESCRIPTION=Extendable-Output Functions 17 | -------------------------------------------------------------------------------- /library/xof/src/commonMain/kotlin/org/kotlincrypto/core/xof/XofAlgorithm.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.xof 17 | 18 | import org.kotlincrypto.core.Algorithm 19 | import org.kotlincrypto.error.InvalidKeyException 20 | 21 | /** 22 | * Denotes a class as a user of a specified cryptographic [algorithm] 23 | * which supports Extendable-Output Functions (XOFs). 24 | * */ 25 | public interface XofAlgorithm: Algorithm 26 | 27 | /** 28 | * Extended functionality, specifically for a [Xof] who's backing instance 29 | * is a [org.kotlincrypto.core.mac.Mac]. 30 | * 31 | * 32 | * @see [Xof.Companion.reset] 33 | * */ 34 | public interface ReKeyableXofAlgorithm: XofAlgorithm { 35 | 36 | /** 37 | * Resets and re-initializes the instance with the provided [newKey] 38 | * 39 | * This is useful if wanting to clear the key before de-referencing. 40 | * 41 | * @throws [InvalidKeyException] if [newKey] is unacceptable. 42 | * */ 43 | public fun reset(newKey: ByteArray) 44 | } 45 | -------------------------------------------------------------------------------- /library/xof/src/commonMain/kotlin/org/kotlincrypto/core/xof/XofFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.xof 17 | 18 | import org.kotlincrypto.core.* 19 | 20 | /** 21 | * Factory class for implementors of Digest or Mac to wrap them 22 | * in [XofDelegate] such that there is a distinct separation of 23 | * functionality between those class types and [Xof]s, while 24 | * still being able to share similarities/capabilities. 25 | * 26 | * e.g. 27 | * 28 | * class SHAKE128: SHAKEDigest { 29 | * 30 | * // ... 31 | * 32 | * companion object: XofFactory() { 33 | * @JvmStatic 34 | * fun xOf(): Xof = object : XofDelegate(SHAKE128()) { 35 | * // ... 36 | * } 37 | * } 38 | * } 39 | * 40 | * @see [XofDelegate] 41 | * */ 42 | public abstract class XofFactory public constructor() { 43 | 44 | /** 45 | * Wrapper for a Digest, Mac, etc. to act as a delegate to 46 | * the [Xof]. 47 | * 48 | * @throws [ClassCastException] if [delegate] is: 49 | * - Not an instance of [Resettable] 50 | * - Not an instance of [Updatable] 51 | * @throws [IllegalArgumentException] if [delegate] is: 52 | * - Not an instance of [Copyable] 53 | * - An instance of [Xof] 54 | * */ 55 | protected abstract inner class XofDelegate 56 | @Throws(ClassCastException::class, IllegalArgumentException::class) 57 | protected constructor(delegate: A) : Xof(delegate), 58 | Algorithm by delegate, 59 | Resettable by delegate as Resettable, 60 | Updatable by delegate as Updatable 61 | { 62 | 63 | init { 64 | require(delegate is Copyable<*>) { "delegate must be Copyable" } 65 | require(delegate !is Xof<*>) { "delegate cannot be an instance of Xof" } 66 | } 67 | 68 | protected final override fun newReader(): Reader { 69 | @Suppress("UNCHECKED_CAST") 70 | return newReader((delegate as Copyable<*>).copy() as A) 71 | } 72 | 73 | /** 74 | * Returns a new [Xof.Reader] for the snapshot (i.e. copy) of [delegate] 75 | * in its current state. 76 | * */ 77 | protected abstract fun newReader(delegateCopy: A): Reader 78 | 79 | /** @suppress */ 80 | public final override fun equals(other: Any?): Boolean { 81 | return other is XofFactory<*>.XofDelegate && other.delegate == delegate 82 | } 83 | /** @suppress */ 84 | public final override fun hashCode(): Int = delegate.hashCode() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core.xof 17 | 18 | import org.kotlincrypto.core.InternalKotlinCryptoApi 19 | import kotlin.test.Test 20 | import kotlin.test.assertContentEquals 21 | 22 | @OptIn(InternalKotlinCryptoApi::class) 23 | class XofUtilsUnitTest { 24 | 25 | private sealed class Data private constructor() { 26 | class L(val value: Long): Data() { 27 | override fun leftEncode(): ByteArray = Xof.Utils.leftEncode(value) 28 | override fun rightEncode(): ByteArray = Xof.Utils.rightEncode(value) 29 | } 30 | class I(val value: Int): Data() { 31 | override fun leftEncode(): ByteArray = Xof.Utils.leftEncode(value) 32 | override fun rightEncode(): ByteArray = Xof.Utils.rightEncode(value) 33 | } 34 | class LoHi(val lo: Int, val hi: Int): Data() { 35 | override fun leftEncode(): ByteArray = Xof.Utils.leftEncode(lo = lo, hi = hi) 36 | override fun rightEncode(): ByteArray = Xof.Utils.rightEncode(lo = lo, hi = hi) 37 | } 38 | 39 | abstract fun leftEncode(): ByteArray 40 | abstract fun rightEncode(): ByteArray 41 | } 42 | 43 | @Test 44 | fun givenValue_whenEncoded_thenIsAsExpected() { 45 | listOf( 46 | Data.L(777711L) to byteArrayOf(3, 11, -35, -17), 47 | Data.L(-777711L) to byteArrayOf(8, -1, -1, -1, -1, -1, -12, 34, 17), 48 | Data.LoHi(-777711, -1) to byteArrayOf(8, -1, -1, -1, -1, -1, -12, 34, 17), 49 | Data.L(555L) to byteArrayOf(2, 2, 43), 50 | Data.I(555) to byteArrayOf(2, 2, 43), 51 | Data.L(Long.MIN_VALUE) to byteArrayOf(8, -128, 0, 0, 0, 0, 0, 0, 0), 52 | Data.L(Long.MAX_VALUE) to byteArrayOf(8, 127, -1, -1, -1, -1, -1, -1, -1), 53 | ).forEach { (data, expected) -> 54 | assertContentEquals(expected, data.leftEncode()) 55 | 56 | // Shift expected for right encoding 57 | var i = 0 58 | while (i < expected.lastIndex) { 59 | val old = expected[i] 60 | val new = expected[i + 1] 61 | expected[i] = new 62 | expected[i + 1] = old 63 | i++ 64 | } 65 | 66 | assertContentEquals(expected, data.rightEncode()) 67 | } 68 | } 69 | 70 | @Test 71 | fun givenLeftEncoding_whenValueZero_thenResultIsAsExpected() { 72 | val expected = ByteArray(2).apply { this[0] = 1 } 73 | assertContentEquals(expected, Xof.Utils.leftEncode(0L)) 74 | } 75 | 76 | @Test 77 | fun givenRightEncoding_whenValueZero_thenResultIsAsExpected() { 78 | val expected = ByteArray(2).apply { this[1] = 1 } 79 | assertContentEquals(expected, Xof.Utils.rightEncode(0L)) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /library/xof/src/jvmMain/java9/module-info.java: -------------------------------------------------------------------------------- 1 | module org.kotlincrypto.core.xof { 2 | requires kotlin.stdlib; 3 | requires transitive org.kotlincrypto.core; 4 | 5 | exports org.kotlincrypto.core.xof; 6 | } 7 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | rootProject.name = "core" 17 | 18 | pluginManagement { 19 | repositories { 20 | mavenCentral() 21 | gradlePluginPortal() 22 | google() 23 | } 24 | } 25 | 26 | includeBuild("build-logic") 27 | 28 | @Suppress("PrivatePropertyName") 29 | private val CHECK_PUBLICATION: String? by settings 30 | 31 | if (CHECK_PUBLICATION != null) { 32 | include(":tools:check-publication") 33 | } else { 34 | listOf( 35 | "core", 36 | "digest", 37 | "mac", 38 | "xof", 39 | ).forEach { name -> 40 | include(":library:$name") 41 | } 42 | 43 | include(":benchmarks") 44 | include(":test-android") 45 | } 46 | -------------------------------------------------------------------------------- /test-android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /test-android/api/test-android.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinCrypto/core/c40795a456fa382f1f1d3844b94bd8f6dc8d4a6f/test-android/api/test-android.api -------------------------------------------------------------------------------- /test-android/api/test-android.klib.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinCrypto/core/c40795a456fa382f1f1d3844b94bd8f6dc8d4a6f/test-android/api/test-android.klib.api -------------------------------------------------------------------------------- /test-android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | plugins { 17 | id("configuration") 18 | } 19 | 20 | repositories { google() } 21 | 22 | kmpConfiguration { 23 | configure { 24 | androidLibrary { 25 | kotlinJvmTarget = JavaVersion.VERSION_11 26 | compileSourceCompatibility = JavaVersion.VERSION_11 27 | compileTargetCompatibility = JavaVersion.VERSION_11 28 | 29 | android { 30 | namespace = "org.kotlincrypto.core" 31 | compileSdk = 34 32 | 33 | defaultConfig { 34 | minSdk = 14 35 | 36 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 37 | } 38 | } 39 | 40 | sourceSetTest { 41 | kotlin.srcDir("src/androidUnitTest/digest") 42 | kotlin.srcDir("src/androidUnitTest/mac") 43 | } 44 | 45 | sourceSetTestInstrumented { 46 | kotlin.srcDir("src/androidInstrumentedTest/digest") 47 | kotlin.srcDir("src/androidInstrumentedTest/mac") 48 | 49 | dependencies { 50 | implementation(libs.androidx.test.runner) 51 | } 52 | } 53 | } 54 | 55 | common { 56 | sourceSetTest { 57 | dependencies { 58 | implementation(kotlin("test")) 59 | implementation(project(":library:digest")) 60 | implementation(project(":library:mac")) 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test-android/src/androidInstrumentedTest/digest: -------------------------------------------------------------------------------- 1 | ../../../library/digest/src/commonTest/kotlin -------------------------------------------------------------------------------- /test-android/src/androidInstrumentedTest/kotlin/org/kotlincrypto/core/AndroidSdkIntTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertNotNull 20 | 21 | @OptIn(InternalKotlinCryptoApi::class) 22 | class AndroidSdkIntTest { 23 | 24 | @Test 25 | fun givenAndroidSdkInt_whenAndroidRuntime_thenIsNotNull() { 26 | assertNotNull(KC_ANDROID_SDK_INT) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test-android/src/androidInstrumentedTest/kotlin/org/kotlincrypto/core/mac/AndroidMacTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("UnnecessaryOptInAnnotation") 17 | 18 | package org.kotlincrypto.core.mac 19 | 20 | import android.os.Build 21 | import java.security.NoSuchAlgorithmException 22 | import java.security.NoSuchProviderException 23 | import java.security.Provider 24 | import kotlin.test.* 25 | 26 | class AndroidMacTest { 27 | 28 | private val key = ByteArray(50) { it.toByte() } 29 | 30 | @Test 31 | fun givenAndroid_whenApi23OrBelow_thenUsesProvider() { 32 | val provider = TestMac(key, TEST_ALGORITHM).provider 33 | if (Build.VERSION.SDK_INT in 21..23) { 34 | assertNotNull(provider) 35 | } else { 36 | assertNull(provider) 37 | } 38 | } 39 | 40 | @Test 41 | fun givenAndroid_whenProvider_getServiceThrowsException() { 42 | val (mac, provider) = testMacAndProviderOrNull() ?: return 43 | 44 | try { 45 | provider.getService("Mac", mac.algorithm) 46 | fail() 47 | } catch (_: NoSuchAlgorithmException) { 48 | // pass 49 | } 50 | } 51 | 52 | @Test 53 | fun givenAndroid_whenMacGetInstanceForAlgorithm_thenIsNotCachedInMacSERVICE() { 54 | val (mac, _) = testMacAndProviderOrNull() ?: return 55 | 56 | try { 57 | javax.crypto.Mac.getInstance(mac.algorithm) 58 | fail() 59 | } catch (_: NoSuchAlgorithmException) { 60 | // pass 61 | } 62 | } 63 | 64 | @Test 65 | fun givenAndroid_whenMacGetInstanceForAlgorithmAndProviderName_thenIsNotCachedInMacSERVICE() { 66 | val (mac, provider) = testMacAndProviderOrNull() ?: return 67 | 68 | try { 69 | javax.crypto.Mac.getInstance(mac.algorithm, provider.name) 70 | fail() 71 | } catch (_: NoSuchProviderException) { 72 | // pass 73 | } 74 | } 75 | 76 | @Test 77 | fun givenAndroid_whenMacGetInstanceForAlgorithmAndProvider_thenIsNotCachedInMacSERVICE() { 78 | val (mac, provider) = testMacAndProviderOrNull() ?: return 79 | 80 | try { 81 | // Even if the provider is used in getInstance, the spi should 82 | // be de-referenced which would return null and then throw 83 | // an exception here. We do NOT want any provider apis used 84 | // to obtain an instance of the spi. 85 | javax.crypto.Mac.getInstance(mac.algorithm, provider) 86 | fail() 87 | } catch (_: NoSuchAlgorithmException) { 88 | // pass 89 | } 90 | } 91 | 92 | private fun testMacAndProviderOrNull(): Pair? { 93 | val mac = TestMac(key, TEST_ALGORITHM) 94 | val provider = mac.provider ?: return null 95 | return Pair(mac, provider) 96 | } 97 | 98 | private companion object { 99 | private const val TEST_ALGORITHM = "AndroidMacTest" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test-android/src/androidInstrumentedTest/mac: -------------------------------------------------------------------------------- 1 | ../../../library/mac/src/commonTest/kotlin -------------------------------------------------------------------------------- /test-android/src/androidMain/kotlin/org/kotlincrypto/core/-Stub.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core 17 | 18 | internal fun stub() { /* no-op */ } 19 | -------------------------------------------------------------------------------- /test-android/src/androidUnitTest/digest: -------------------------------------------------------------------------------- 1 | ../../../library/digest/src/commonTest/kotlin -------------------------------------------------------------------------------- /test-android/src/androidUnitTest/kotlin/org/kotlincrypto/core/AndroidSdkIntUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package org.kotlincrypto.core 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertNull 20 | 21 | @OptIn(InternalKotlinCryptoApi::class) 22 | class AndroidSdkIntUnitTest { 23 | 24 | @Test 25 | fun givenAndroidSdkInt_whenNOTAndroidRuntime_thenIsNull() { 26 | assertNull(KC_ANDROID_SDK_INT) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test-android/src/androidUnitTest/mac: -------------------------------------------------------------------------------- 1 | ../../../library/mac/src/commonTest/kotlin -------------------------------------------------------------------------------- /tools/check-publication/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /tools/check-publication/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | plugins { 17 | id("configuration") 18 | } 19 | 20 | repositories { 21 | val host = "https://s01.oss.sonatype.org" 22 | 23 | if (version.toString().endsWith("-SNAPSHOT")) { 24 | maven("$host/content/repositories/snapshots/") 25 | } else { 26 | maven("$host/content/groups/staging") { 27 | val p = rootProject.properties 28 | 29 | credentials { 30 | username = p["mavenCentralUsername"]?.toString() 31 | password = p["mavenCentralPassword"]?.toString() 32 | } 33 | } 34 | } 35 | } 36 | 37 | kmpConfiguration { 38 | configureShared { 39 | common { 40 | sourceSetMain { 41 | dependencies { 42 | implementation("$group:core:$version") 43 | implementation("$group:digest:$version") 44 | implementation("$group:mac:$version") 45 | implementation("$group:xof:$version") 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tools/check-publication/src/commonMain/kotlin/tools/check/publication/Stub.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package tools.check.publication 17 | 18 | internal fun stub() { /* no-op */ } 19 | --------------------------------------------------------------------------------