Merge pull request #13 from ionspin/extract-interfaces

Extract interfaces
This commit is contained in:
Ugljesa Jovanovic 2020-06-13 16:12:04 +02:00 committed by GitHub
commit c5cb66d087
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
158 changed files with 7144 additions and 1168 deletions

16
.gitignore vendored
View File

@ -13,3 +13,19 @@ build/
/package.json /package.json
/multiplatform-crypto/src/jsMain/npm/node_modules /multiplatform-crypto/src/jsMain/npm/node_modules
/multiplatform-crypto/src/jsMain/npm/package-lock.json /multiplatform-crypto/src/jsMain/npm/package-lock.json
/multiplatform-crypto-delegated/node_modules
/multiplatform-crypto-delegated/package.json
/multiplatform-crypto-delegated/package-lock.json
/sodiumWrapper/include/
/sodiumWrapper/lib/
/sodiumWrapper/ios-include/
/sodiumWrapper/ios-lib/
/sodiumWrapper/static-arm64/
/sodiumWrapper/static-arm32/
/sodiumWrapper/static-ios/
/sodiumWrapper/static-linux-x86-64/
/sodiumWrapper/static-macos-x86-64/
/sodiumWrapper/static-mingw-x86-64/
/sodiumWrapper/static-tvos/
/sodiumWrapper/static-watchos/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "sodiumWrapper/libsodium"]
path = sodiumWrapper/libsodium
url = https://github.com/ionspin/libsodium.git

View File

@ -1,33 +1,74 @@
matrix: matrix:
include: include:
- os: linux - os: linux
name: linux
language: java language: java
jdk: openjdk12 jdk: openjdk12
# before_script:
# - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
# - source install-jdk.sh --url 'https://api.adoptopenjdk.net/v2/binary/releases/openjdk12?openjdk_impl=hotspot&os=linux&arch=x64&release=latest&heap_size=normal&type=jdk'
# - java --version
env: env:
KBUILD=linux KBUILD=linux
JAVA_OPTS=-Xmx2g JAVA_OPTS=-Xmx2g
#skip ./gradlew assemble that is normally invoked in installation step
install:
- sudo apt-get update
- sudo apt-get -y install automake
script: script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./linuxBuild.sh; fi' - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./linuxBuild.sh; fi'
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./linuxBuildAndPublish.sh; fi' - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./linuxBuildAndPublish.sh; fi'
# OSX macos/ios
- os: osx - os: osx
name: osx-mac-ios
osx_image: xcode11.4 osx_image: xcode11.4
language: java language: java
jdk: openjdk12 jdk: openjdk12
# before_script: install: true
# - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
# - source install-jdk.sh --url 'https://api.adoptopenjdk.net/v2/binary/releases/openjdk12?openjdk_impl=hotspot&os=linux&arch=x64&release=latest&heap_size=normal&type=jdk'
# - java --version
env: env:
KBUILD=linux KBUILD=linux
JAVA_OPTS=-Xmx2g JAVA_OPTS=-Xmx2g
script: script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./macBuild.sh; fi' - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./macBuild-mac-ios.sh; fi'
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./macBuildAndPublish.sh; fi' - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./macBuildAndPublish-mac-ios.sh; fi'
# OSX watchos
- os: osx
name: osx-watchos
osx_image: xcode11.4
language: java
jdk: openjdk12
install: true
env:
KBUILD=linux
JAVA_OPTS=-Xmx2g
script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./macBuild-watchos.sh; fi'
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./macBuildAndPublish-watchos.sh; fi'
# OSX tvos
- os: osx
name: osx-tvos
osx_image: xcode11.4
language: java
jdk: openjdk12
install: true
env:
KBUILD=linux
JAVA_OPTS=-Xmx2g
script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./macBuild-tvos.sh; fi'
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./macBuildAndPublish-tvos.sh; fi'
# OSX pure
- os: osx
name: osx-pure
osx_image: xcode11.4
language: java
jdk: openjdk12
install: true
env:
KBUILD=linux
JAVA_OPTS=-Xmx2g
script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./macBuild-pure.sh; fi'
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./macBuildAndPublish-pure.sh; fi'
- os: windows - os: windows
name: windwos-pure
language: shell language: shell
jdk: openjdk12 jdk: openjdk12
env: env:
@ -37,15 +78,68 @@ matrix:
before_install: before_install:
- curl "${GRAVIS}.install-jdk-travis.sh" --output ~/.install-jdk-travis.sh - curl "${GRAVIS}.install-jdk-travis.sh" --output ~/.install-jdk-travis.sh
- source ~/.install-jdk-travis.sh - source ~/.install-jdk-travis.sh
install: true
script: script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then ./gradlew build ; fi' - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then $shell ./windowsBuild-pure.sh; fi'
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then ./gradlew build publishMingwx64PublicationToSnapshotRepository; fi' - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then ./windowsBuildAndPublish-pure.sh; fi'
- os: windows
name: windows-delegated
language: shell
jdk: openjdk12
env:
- GRAVIS="https://raw.githubusercontent.com/DanySK/Gravis-CI/master/"
- JAVA_OPTS=-Xmx2g
- JDK="adopt-openj9@1.11"
before_install:
- curl "${GRAVIS}.install-jdk-travis.sh" --output ~/.install-jdk-travis.sh
- source ~/.install-jdk-travis.sh
- |-
case $TRAVIS_OS_NAME in
windows)
[[ ! -f C:/tools/msys64/msys2_shell.cmd ]] && rm -rf C:/tools/msys64
choco uninstall -y mingw
choco upgrade --no-progress -y msys2 bazel
export msys2='cmd //C RefreshEnv.cmd '
export msys2+='& set MSYS=winsymlinks:nativestrict '
export msys2+='& C:\\tools\\msys64\\msys2_shell.cmd -defterm -no-start'
export shell="$msys2 -mingw64 -full-path -here -c \$\* --"
export msys2+=" -msys2 -c \$\* --"
$msys2 pacman --sync --noconfirm --needed \
autoconf \
automake \
mingw-w64-x86_64-libtool \
mingw-w64-x86_64-toolchain \
perl \
unzip
taskkill //IM gpg-agent.exe //F
export CPPFLAGS=-D__USE_MINGW_ANSI_STDIO=1
export PATH=/C/tools/msys64/mingw64/bin:$PATH
export GNU_MAKE=mingw32-make
export MAKE=mingw32-make
export AR=gcc-ar
export RANLIB=gcc-ranlib
export COVERITY_SCAN_BRANCH_PATTERN=disable_coverity_scan
;;
esac
- export GIT=git
- g++ --version
- $GNU_MAKE --version
- $GIT --version
install: true
before_cache:
- $msys2 pacman --sync --clean --noconfirm
script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then $shell ./windowsBuild-delegated.sh; fi'
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then ./windowsBuildAndPublish-delegated.sh; fi'
cache: cache:
directories: directories:
- $HOME/.m2/ - $HOME/.m2/
- $HOME/.gradle/caches/ - $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/ - $HOME/.gradle/wrapper/
- $HOME/.konan/cache - $HOME/.konan/cache
- $HOME/.konan/dependencies
- $HOME/AppData/Local/Temp/chocolatey
- /C/tools/msys64
branches: branches:
only: only:
- master - master

43
EXTERNAL_LICENSES Normal file
View File

@ -0,0 +1,43 @@
Libraries used by multipatform-crypto-delegated follow:
Libsodium, licensed under ISC License
/*
* ISC License
*
* Copyright (c) 2013-2020
* Frank Denis <j at pureftpd dot org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
Libsodium.js (temporarily included libsodium-wrappers-sumo-0.7.6.tgz packed npm package with additional sha256 and sha512 multipart wrappers)
Copyright (c) 2015-2020
Ahmad Ben Mrad <batikhsouri at gmail dot org>
Frank Denis <j at pureftpd dot org>
Ryan Lester <ryan at cyph dot com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

115
README.md
View File

@ -5,58 +5,97 @@
Kotlin Multiplatform Crypto is a library for various cryptographic applications. Kotlin Multiplatform Crypto is a library for various cryptographic applications.
This is an extremely early release, currently only consisting of Blake2b and SHA256 and 512. The library comes in two flavors `multiplatform-crypto` and `multiplatform-crypto-delegated`
* `multiplatform-crypto` contains pure kotlin implementations, is not reviewed, should be considered unsafe and only
for prototyping or experimentation purposes.
* `multiplatform-crypto-delegated` relies on platform specific implementations, like libsodium, but care should still be taken that the kotlin code is not reviewed or proven safe.
APIs of both variants are identical.
## Supported platforms by variant
|Platform|Pure variant| Delegated variant|
|--------|------------|------------------|
|Linux X86 64| :heavy_check_mark: | :heavy_check_mark: |
|Linux Arm 64| :heavy_check_mark: | :heavy_check_mark: |
|Linux Arm 32| :heavy_check_mark: | :x: |
|macOS X86 64| :heavy_check_mark: | :heavy_check_mark: |
|iOS x86 64 | :heavy_check_mark: | :heavy_check_mark: |
|iOS Arm 64 | :heavy_check_mark: | :heavy_check_mark: |
|iOS Arm 32 | :heavy_check_mark: | :heavy_check_mark: |
|watchOS X86 32 | :heavy_check_mark: | :heavy_check_mark: |
|watchOS Arm 64(_32) | :heavy_check_mark: | :heavy_check_mark: |
|watchos Arm 32 | :heavy_check_mark: | :heavy_check_mark: |
|tvOS X86 64 | :heavy_check_mark: | :heavy_check_mark: |
|tvOS Arm 64 | :heavy_check_mark: | :heavy_check_mark: |
|minGW X86 64| :heavy_check_mark: | :heavy_check_mark: |
|minGW X86 32| :x: | :x: |
API is very opinionated, ment to be used on both encrypting and decrypting side. The idea is that API leaves less room for
errors when using it.
## Notes & Roadmap ## Notes & Roadmap
**The API will move fast and break often until v1.0** **The API will move fast and break often until v1.0**
Make SHA hashes "updatable" like Blake2b Next steps:
- Expand API (AEAD, ECC ...)
After that tenative plan is to add 25519 curve based signing and key exchange next.
## Should I use this in production? ## Should I use this in production?
No. No, until it is reviewed.
## Should I use this in code that is critical in any way, shape or form? ## Should I use this in code that is *critical* in any way, shape or form?
No. No, but even if after being warned you decide to, then use `multiplatform-crypto-delegated` as it relies on reputable libraries.
## Why? ## Why?
This is an experimental implementation, mostly for expanding personal understanding of cryptography. This is an experimental implementation, mostly for expanding personal understanding of cryptography.
It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure. It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure.
## Integration ## Currently supported
### Hashing functions
## Hashing functions
* Blake2b * Blake2b
* SHA512 * SHA512
* SHA256 * SHA256
## Symmetric cipher ### Symmetric cipher
* AES * AES
* Modes: CBC, CTR * Modes: CBC, CTR
## Key Derivation ### Key Derivation
* Argon2 * Argon2
## AEAD ### AEAD
TODO() TODO()
### Delegated flavor dependancy table
The following table describes which library is used for particular cryptographic primitive
| Primitive | JVM | JS | Native |
| ----------|-----|----|--------|
| Blake2b | LazySodium | libsodium.js | libsodium |
| SHA256 | LazySodium | libsodium.js | libsodium |
| SHA512 | LazySodium | libsodium.js | libsodium |
| AES-CBC | LazySodium | libsodium.js | libsodium |
| AES-CTR | LazySodium | libsodium.js | libsodium |
## Integration ## Integration
#### Gradle #### Gradle
Kotlin
```kotlin ```kotlin
implementation("com.ionspin.kotlin:multiplatform-crypto:0.0.2") implementation("com.ionspin.kotlin:multiplatform-crypto:0.0.5")
or
implementation("com.ionspin.kotlin:multiplatform-crypto-delegated:0.0.5")
``` ```
#### Snapshot builds #### Snapshot builds
@ -66,12 +105,19 @@ repositories {
url = uri("https://oss.sonatype.org/content/repositories/snapshots") url = uri("https://oss.sonatype.org/content/repositories/snapshots")
} }
} }
implementation("com.ionspin.kotlin:multiplatform-crypto:0.0.3-SNAPSHOT") implementation("com.ionspin.kotlin:multiplatform-crypto:0.0.6-SNAPSHOT")
``` ```
## Usage ## Usage
### Helper functions
All API take `UByteArray` as message/key/nonce/etc parameter. For convenience when working with strings we provide
`String.enocdeToUbyteArray()` extensions function, and `UByteArray.toHexString` extension function.
More convenience functions will be added.
### Hashes ### Hashes
Hashes are provided in two versions, "stateless", usually the companion object of the hash, Hashes are provided in two versions, "stateless", usually the companion object of the hash,
@ -87,23 +133,23 @@ You need to deliver the complete data that is to be hashed in one go
```kotlin ```kotlin
val input = "abc" val input = "abc"
val result = Blake2b.digest(input) val result = Crypto.Blake2b.stateless(input.encodeToUByteArray())
``` ```
Result is returned as a `Array<Byte>` Result is returned as a `UByteArray`
##### Updatable instance version ##### Updatable instance version
You can create an instance and feed the data by using `update(input : Array<Byte>)` call. Once all data is supplied, You can create an instance and feed the data by using `update(input : UByteArray)` call. Once all data is supplied,
you should call `digest()` or `digestString()` convenience method that converts the `Array<Byte>` into hexadecimal string. you should call `digest()`.
If you want to use Blake2b with a key, you should supply it when creating the `Blake2b` instance. If you want to use Blake2b with a key, you should supply it when creating the `Blake2b` instance.
```kotlin ```kotlin
val test = "abc" val test = "abc"
val key = "key" val key = "key"
val blake2b = Blake2b(key) val blake2b = Crypto.Blake2b.updateable(key.encodeToUByteArray())
blake2b.update(test) blake2b.update(test.encodeToUByteArray())
val result = blake2b.digest() val result = blake2b.digest().toHexString()
``` ```
After digest is called, the instance is reset and can be reused (Keep in mind key stays the same for the particular instance). After digest is called, the instance is reset and can be reused (Keep in mind key stays the same for the particular instance).
@ -111,36 +157,37 @@ After digest is called, the instance is reset and can be reused (Keep in mind ke
##### Stateless version ##### Stateless version
You need to deliver the complete data that is to be hashed in one go. You can either provide the `Array<Byte>` as input You need to deliver the complete data that is to be hashed in one go. You can either provide the `UByteArray` as input
or `String`. Result is always returned as `Array<Byte>` (At least in verision 0.0.1) or `String`. Result is always returned as `UByteArray` (At least in verision 0.0.1)
```kotlin ```kotlin
val input = "abc" val input = "abc"
val result = Sha256.digest(input) val result = Crypto.Sha256.stateless(input.encodeToUByteArray())
``` ```
```kotlin ```kotlin
val input ="abc" val input ="abc"
val result = Sha512.digest(message = input.encodeToByteArray().map { it.toUByte() }.toTypedArray()) val result = Crypto.Sha512.stateless(input.encodeToUByteArray())
``` ```
Result is returned as a `Array<Byte>` Result is returned as a `UByteArray`
##### Updateable version ##### Updateable version
Or you can use the updatable instance version Or you can use the updatable instance version
```kotlin ```kotlin
val sha256 = Sha256() val sha256 = Crypto.Sha256.updateable()
sha256.update("abc") sha256.update("abc".encodeToUByteArray())
val result = sha256.digest() val result = sha256.digest()
``` ```
```kotlin ```kotlin
val sha512 = Sha512() val sha512 = Crypto.Sha512.updateable()
sha512.update("abc") sha512.update("abc".encodeToUByteArray())
val result = sha512.digest() val result = sha512.digest()
``` ```
### Symmetric encryption ### Symmetric encryption
#### AES #### AES

View File

@ -40,6 +40,7 @@ allprojects {
google() google()
maven ("https://kotlin.bintray.com/kotlinx") maven ("https://kotlin.bintray.com/kotlinx")
maven ("https://dl.bintray.com/kotlin/kotlin-eap") maven ("https://dl.bintray.com/kotlin/kotlin-eap")
maven ("https://kotlin.bintray.com/kotlin-dev")
jcenter() jcenter()
maven { maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots") url = uri("https://oss.sonatype.org/content/repositories/snapshots")

View File

@ -21,5 +21,14 @@ plugins {
} }
repositories { repositories {
mavenCentral()
maven ("https://dl.bintray.com/kotlin/kotlin-eap")
jcenter() jcenter()
} }
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4-M2")
}
System.setProperty("PROJECT_PATH", project.projectDir.parentFile.toString())
println("Path ${project.projectDir.parentFile}")

View File

@ -15,38 +15,57 @@
*/ */
object Versions { object Versions {
val kotlinCoroutines = "1.3.5-native-mt-1.4-M1" val kotlinCoroutines = "1.3.5-native-mt-arm-1.4-M2-SNAPSHOT" //NOTE: my linux arm32 and arm64 build
val kotlin = "1.4-M1" val kotlin = "1.4-M2"
val kotlinSerialization = "0.20.0-1.4-M1" val kotlinSerialization = "0.20.0-1.4-M2"
val atomicfu = "0.14.2-1.4-M1" val atomicfu = "0.14.3-M2-2-SNAPSHOT" //NOTE: my linux arm32 and arm64 build
val nodePlugin = "1.3.0" val nodePlugin = "1.3.0"
val dokkaPlugin = "0.9.18" val dokkaPlugin = "0.9.18"
val taskTreePlugin = "1.5"
val kotlinBigNumVersion = "0.1.6-SNAPSHOT" val kotlinBigNumVersion = "0.1.6-1.4-M2-SNAPSHOT"
val lazySodium = "4.2.6"
val jna = "5.5.0"
} }
object ReleaseInfo {
val group = "com.ionspin.kotlin"
val version = "0.1.0-SNAPSHOT"
}
object Deps { object Deps {
object Common { object Common {
val stdLib = "stdlib-common" val stdLib = "stdlib-common"
val test = "test-common" val test = "test-common"
val testAnnotation = "test-annotations-common" val testAnnotation = "test-annotations-common"
val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Versions.kotlinCoroutines}" // val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Versions.kotlinCoroutines}"
val coroutines = "com.ionspin.kotlin.coroutines:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:${Versions.kotlinSerialization}" val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:${Versions.kotlinSerialization}"
val atomicfu = "org.jetbrains.kotlinx:atomicfu:${Versions.atomicfu}" val atomicfu = "com.ionspin.kotlin.atomicfu:atomicfu:${Versions.atomicfu}"
val kotlinBigNum = "com.ionspin.kotlin:bignum:${Versions.kotlinBigNumVersion}" val kotlinBigNum = "com.ionspin.kotlin:bignum:${Versions.kotlinBigNumVersion}"
val apiProject = ":multiplatform-crypto-api"
} }
object Js { object Js {
val stdLib = "stdlib-js" val stdLib = "stdlib-js"
val test = "test-js" val test = "test-js"
val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Versions.kotlinCoroutines}" // val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Versions.kotlinCoroutines}"
val coroutines = "com.ionspin.kotlin.coroutines:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:${Versions.kotlinSerialization}" val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:${Versions.kotlinSerialization}"
object Npm {
val libsodium = Pair("libsodium-wrappers-sumo", "0.7.6")
// val libsodiumWrappers = Pair("libsodium-wrappers-sumo", "0.7.6")
val libsodiumWrappers = Pair("libsodium-wrappers-sumo", "file:${getProjectPath()}/multiplatform-crypto-delegated/libsodium-wrappers-sumo-0.7.6.tgz")
}
} }
object Jvm { object Jvm {
@ -54,20 +73,29 @@ object Deps {
val test = "test" val test = "test"
val testJUnit = "test-junit" val testJUnit = "test-junit"
val reflection = "reflect" val reflection = "reflect"
val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}" // val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
val coroutinesCore = "com.ionspin.kotlin.coroutines:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
val coroutinesjdk8 = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${Versions.kotlinCoroutines}" val coroutinesjdk8 = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${Versions.kotlinCoroutines}"
val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime:${Versions.kotlinSerialization}" val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime:${Versions.kotlinSerialization}"
val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutines}" // val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutines}"
val coroutinesTest = "com.ionspin.kotlin.coroutines:kotlinx-coroutines-test:${Versions.kotlinCoroutines}"
object Delegated {
val lazysodium = "com.goterl.lazycode:lazysodium-java:${Versions.lazySodium}"
val jna = "net.java.dev.jna:jna:${Versions.jna}"
}
} }
object iOs { object iOs {
val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:${Versions.kotlinSerialization}" val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:${Versions.kotlinSerialization}"
val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:${Versions.kotlinCoroutines}" // val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:${Versions.kotlinCoroutines}"
val coroutines = "com.ionspin.kotlin.coroutines:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
} }
object Native { object Native {
val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:${Versions.kotlinSerialization}" val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:${Versions.kotlinSerialization}"
val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:${Versions.kotlinCoroutines}" // val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:${Versions.kotlinCoroutines}"
val coroutines = "com.ionspin.kotlin.coroutines:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
} }
@ -81,5 +109,6 @@ object PluginsDeps {
val mavenPublish = "maven-publish" val mavenPublish = "maven-publish"
val signing = "signing" val signing = "signing"
val dokka = "org.jetbrains.dokka" val dokka = "org.jetbrains.dokka"
val taskTree = "com.dorongold.task-tree"
} }

View File

@ -0,0 +1,112 @@
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.nativeplatform.platform.internal.Architectures
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import java.io.File
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 30-May-2020
*/
fun isInIdea() = System.getProperty("idea.active") == "true"
fun isInTravis() = System.getenv("TRAVIS") == "true"
fun getProjectPath() : String {
val path = System.getProperty("PROJECT_PATH")
return path
}
fun getHostOsName(): String {
val target = System.getProperty("os.name")
if (target == "Linux") return "linux"
if (target.startsWith("Windows")) return "windows"
if (target.startsWith("Mac")) return "macos"
return "unknown"
}
fun getHostArchitecture(): String {
val architecture = System.getProperty("os.arch")
DefaultNativePlatform.getCurrentArchitecture()
println("Arch: $architecture")
val resolvedArch = Architectures.forInput(architecture).name
println("Resolved arch: $resolvedArch")
return resolvedArch
}
fun KotlinMultiplatformExtension.isRunningInIdea(block: KotlinMultiplatformExtension.() -> Unit) {
if (isInIdea()) {
block(this)
}
}
fun KotlinMultiplatformExtension.isNotRunningInIdea(block: KotlinMultiplatformExtension.() -> Unit) {
if (!isInIdea()) {
block(this)
}
}
fun KotlinMultiplatformExtension.isRunningInTravis(block: KotlinMultiplatformExtension.() -> Unit) {
if (isInTravis()) {
block(this)
}
}
fun KotlinMultiplatformExtension.runningOnLinuxx86_64(block: KotlinMultiplatformExtension.() -> Unit) {
if (getHostOsName() == "linux" && getHostArchitecture() == "x86-64") {
block(this)
}
}
fun KotlinMultiplatformExtension.runningOnLinuxArm64(block: KotlinMultiplatformExtension.() -> Unit) {
if (getHostOsName() == "linux" && getHostArchitecture() == "aarch64") {
block(this)
}
}
fun KotlinMultiplatformExtension.runningOnLinuxArm32(block: KotlinMultiplatformExtension.() -> Unit) {
if (getHostOsName() == "linux" && getHostArchitecture() == "arm-v7") {
block(this)
}
}
fun KotlinMultiplatformExtension.runningOnMacos(block: KotlinMultiplatformExtension.() -> Unit) {
if (getHostOsName() == "macos") {
block(this)
}
}
fun KotlinMultiplatformExtension.runningOnWindows(block: KotlinMultiplatformExtension.() -> Unit) {
if (getHostOsName() == "windows") {
block(this)
}
}
fun independentDependencyBlock(nativeDeps: KotlinDependencyHandler.() -> Unit): KotlinDependencyHandler.() -> Unit {
return nativeDeps
}
/**
* On mac when two targets that have the same parent source set have cinterops defined, gradle creates a "common"
* target task for that source set metadata, even though it's a native source set, to work around that, we create
* an intermediary source set with the same set of dependancies
*
*/
fun NamedDomainObjectContainer<KotlinSourceSet>.createWorkaroundNativeMainSourceSet(
name: String,
nativeDeps: KotlinDependencyHandler.() -> Unit
): KotlinSourceSet {
return create("${name}Workaround") {
if (!isInIdea()) {
kotlin.srcDir("src/nativeMain")
dependencies {
nativeDeps.invoke(this)
}
}
}
}

View File

@ -17,7 +17,9 @@ org.gradle.parallel=true
kotlin.code.style=official kotlin.code.style=official
kotlin.js.compiler=both kotlin.js.compiler=ir
kotlin.mpp.enableGranularSourceSetsMetadata=true #kotlin.js.experimental.generateKotlinExternals=true
#kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.disableCompilerDaemon=true
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=4096m org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=4096m

23
linuxBuild.sh Normal file → Executable file
View File

@ -1 +1,22 @@
./gradlew build set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
export CLANG_BIN=$HOME/.konan/dependencies/clang-llvm-8.0.0-linux-x86-64/bin
cd sodiumWrapper
./makeLinuxX86-64.sh
#Workaround for travis using wrong ld
if [ "$TRAVIS" = "true" ]
then
sudo mv /usr/bin/ld /usr/bin/ld.bck
sudo ln -s $CLANG_BIN/ld.lld /usr/bin/ld
fi
./makeLinuxArm64.sh
#now we can do the delegated build
cd ..
./gradlew multiplatform-crypto-delegated:build
#and finally pure build
./gradlew multiplatform-crypto:build
set +e

25
linuxBuildAndPublish.sh Normal file → Executable file
View File

@ -1 +1,24 @@
./gradlew build publishJvmPublicationToSnapshotRepository publishJsPublicationToSnapshotRepository publishKotlinMultiplatformPublicationToSnapshotRepository publishLinuxPublicationToSnapshotRepository publishMetadataPublicationToSnapshotRepository set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
export CLANG_BIN=$HOME/.konan/dependencies/clang-llvm-8.0.0-linux-x86-64/bin
cd sodiumWrapper
./makeLinuxX86-64.sh
#Workaround for travis using wrong ld
if [ "$TRAVIS" = "true" ]
then
sudo mv /usr/bin/ld /usr/bin/ld.bck
sudo ln -s $CLANG_BIN/ld.lld /usr/bin/ld
fi
./makeLinuxArm64.sh
#now we can do the delegated build
cd ..
./gradlew multiplatform-crypto-delegated:build
#and finally pure build
./gradlew multiplatform-crypto:build
./gradlew publishJvmPublicationToSnapshotRepository publishJsPublicationToSnapshotRepository \
publishKotlinMultiplatformPublicationToSnapshotRepository publishLinuxX64PublicationToSnapshotRepository \
publishLinuxArm64PublicationToSnapshotRepository publishMetadataPublicationToSnapshotRepository
set +e

18
macBuild-mac-ios.sh Executable file
View File

@ -0,0 +1,18 @@
set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
cd sodiumWrapper
./makeMacosX86-64.sh
./makeIos.sh
#now we can do the delegated build of ios and macos libraries
cd ..
./gradlew multiplatform-crypto-delegated:iosArm32MainKlibrary multiplatform-crypto-delegated:iosArm32TestKlibrary \
multiplatform-crypto-delegated:iosArm64MainKlibrary multiplatform-crypto-delegated:iosArm64TestKlibrary \
multiplatform-crypto-delegated:iosX64MainKlibrary multiplatform-crypto-delegated:iosX64TestKlibrary \
multiplatform-crypto-delegated:macosX64MainKlibrary multiplatform-crypto-delegated:macosX64TestKlibrary
./gradlew multiplatform-crypto-delegated:iosX64Test
./gradlew multiplatform-crypto-delegated:macosX64Test
set +e

5
macBuild-pure.sh Executable file
View File

@ -0,0 +1,5 @@
set -e
#!/bin/sh
./gradlew multiplatform-crypto-api:build
./gradlew multiplatform-crypto:build
set +e

13
macBuild-tvos.sh Executable file
View File

@ -0,0 +1,13 @@
set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
cd sodiumWrapper
./makeTvos.sh
#now we can do the delegated build of ios and macos libraries
cd ..
./gradlew multiplatform-crypto-delegated:tvosArm64MainKlibrary multiplatform-crypto-delegated:tvosArm64TestKlibrary \
multiplatform-crypto-delegated:tvosX64MainKlibrary multiplatform-crypto-delegated:tvosX64TestKlibrary
./gradlew multiplatform-crypto-delegated:tvosX64Test
set +e

14
macBuild-watchos.sh Executable file
View File

@ -0,0 +1,14 @@
set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
cd sodiumWrapper
./makeWatchos.sh
#now we can do the delegated build of ios and macos libraries
cd ..
./gradlew multiplatform-crypto-delegated:watchosArm32MainKlibrary multiplatform-crypto-delegated:watchosArm32TestKlibrary \
multiplatform-crypto-delegated:watchosArm64MainKlibrary multiplatform-crypto-delegated:watchosArm64TestKlibrary \
multiplatform-crypto-delegated:watchosX86MainKlibrary multiplatform-crypto-delegated:watchosX86TestKlibrary
./gradlew multiplatform-crypto-delegated:watchosX86Test
set +e

17
macBuild.sh Normal file → Executable file
View File

@ -1 +1,16 @@
./gradlew build set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
cd sodiumWrapper
./makeMacosX86-64.sh
./makeIos.sh
./makeTvos.sh
./makeWatchos.sh
#now we can do the delegated build
cd ..
./gradlew multiplatform-crypto-delegated:build
#and finally pure build
./gradlew multiplatform-crypto:build
set +e

15
macBuildAndPublish-mac-ios.sh Executable file
View File

@ -0,0 +1,15 @@
set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
cd sodiumWrapper
./makeMacosX86-64.sh
./makeIos.sh
#now we can do the delegated build of ios and macos libraries
cd ..
./gradlew multiplatform-crypto-delegated:publishIosArm32PublicationToSnapshotRepository \
multiplatform-crypto-delegated:publishIosArm64PublicationToSnapshotRepository \
multiplatform-crypto-delegated:publishIosX64PublicationToSnapshotRepository \
multiplatform-crypto-delegated:publishMacosX64PublicationToSnapshotRepository
set +e

14
macBuildAndPublish-pure.sh Executable file
View File

@ -0,0 +1,14 @@
set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
./gradlew multiplatform-crypto:publishIosArm32PublicationToSnapshotRepository \
multiplatform-crypto:publishIosArm64PublicationToSnapshotRepository \
multiplatform-crypto:publishIosX64PublicationToSnapshotRepository \
multiplatform-crypto:publishMacosX64PublicationToSnapshotRepository \
multiplatform-crypto:publishTvosArm64PublicationToSnapshotRepository \
multiplatform-crypto:publishTvosX64PublicationToSnapshotRepository \
multiplatform-crypto:publishWatchosArm32PublicationToSnapshotRepository \
multiplatform-crypto:publishWatchosArm64PublicationToSnapshotRepository \
multiplatform-crypto:publishWatchosX86PublicationToSnapshotRepository
set +e

12
macBuildAndPublish-tvos.sh Executable file
View File

@ -0,0 +1,12 @@
set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
cd sodiumWrapper
./makeTvos.sh
#now we can do the delegated build of ios and macos libraries
cd ..
./gradlew multiplatform-crypto-delegated:publishTvosArm64PublicationToSnapshotRepository \
multiplatform-crypto-delegated:publishTvosX64PublicationToSnapshotRepository
set +e

13
macBuildAndPublish-watchos.sh Executable file
View File

@ -0,0 +1,13 @@
set -e
#!/bin/sh
#this will hopefully download all konan dependancies that we use in the build scripts
./gradlew multiplatform-crypto-api:build
#now let's build linux deps
cd sodiumWrapper
./makeWatchos.sh
#now we can do the delegated build of ios and macos libraries
cd ..
./gradlew multiplatform-crypto-delegated:publishWatchosArm32PublicationToSnapshotRepository \
multiplatform-crypto-delegated:publishWatchosArm64PublicationToSnapshotRepository \
multiplatform-crypto-delegated:publishWatchosX86PublicationToSnapshotRepository
set +e

View File

@ -1 +0,0 @@
./gradlew build publishIos64ArmPublicationToSnapshotRepository publishIosPublicationToSnapshotRepository publishMacosX64PublicationToSnapshotRepository publishIos32ArmPublicationToSnapshotRepository

View File

@ -1 +0,0 @@
./gradlew publishAllPublicationsToMavenRepository -x publishMetadataPublicationToMavenRepository -x publishKotlinMultiplatformPublicationToMavenRepository

View File

@ -0,0 +1,344 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
@file:Suppress("UnstableApiUsage")
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest
plugins {
kotlin(PluginsDeps.multiplatform)
id (PluginsDeps.mavenPublish)
id (PluginsDeps.signing)
id (PluginsDeps.dokka) version Versions.dokkaPlugin
}
repositories {
mavenCentral()
jcenter()
}
group = ReleaseInfo.group
version = ReleaseInfo.version
val ideaActive = System.getProperty("idea.active") == "true"
kotlin {
val hostOsName = getHostOsName()
runningOnLinuxx86_64 {
jvm()
js {
browser {
testTask {
enabled = false //Until I sort out testing on travis
useKarma {
useChrome()
}
}
}
nodejs {
testTask {
useMocha() {
timeout = "10s"
}
}
}
}
linuxX64("linux") {
binaries {
staticLib {
optimized = true
}
}
}
//Not supported in OFFICIAL coroutines at the moment
linuxArm64() {
binaries {
staticLib {
}
}
}
//Not supported in OFFICAL coroutines at the moment
linuxArm32Hfp() {
binaries {
staticLib {
}
}
}
}
runningOnLinuxArm64 {
}
runningOnLinuxArm32 {
}
runningOnMacos {
iosX64() {
binaries {
framework {
optimized = true
}
}
}
iosArm64() {
binaries {
framework {
optimized = true
}
}
}
iosArm32() {
binaries {
framework {
optimized = true
}
}
}
macosX64() {
binaries {
framework {
optimized = true
}
}
}
tvosX64() {
binaries {
framework {
optimized = true
}
}
}
tvosArm64() {
binaries {
framework {
optimized = true
}
}
}
watchosArm64() {
binaries {
framework {
optimized = true
}
}
}
watchosArm32() {
binaries {
framework {
optimized = true
}
}
}
watchosX86() {
binaries {
framework {
optimized = true
}
}
}
}
runningOnWindows {
mingwX64() {
binaries {
staticLib {
optimized = true
}
}
}
mingwX86() {
binaries {
staticLib {
}
}
}
}
println(targets.names)
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin(Deps.Common.stdLib))
implementation(kotlin(Deps.Common.test))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin(Deps.Common.test))
implementation(kotlin(Deps.Common.testAnnotation))
}
}
runningOnLinuxx86_64 {
val jvmMain by getting {
dependencies {
implementation(kotlin(Deps.Jvm.stdLib))
implementation(kotlin(Deps.Jvm.test))
implementation(kotlin(Deps.Jvm.testJUnit))
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin(Deps.Jvm.test))
implementation(kotlin(Deps.Jvm.testJUnit))
implementation(kotlin(Deps.Jvm.reflection))
}
}
val jsMain by getting {
dependencies {
implementation(kotlin(Deps.Js.stdLib))
}
}
val jsTest by getting {
dependencies {
implementation(kotlin(Deps.Js.test))
}
}
}
runningOnMacos {
val tvosX64Main by getting {
dependsOn(commonMain)
}
val tvosArm64Main by getting {
dependsOn(commonMain)
}
val watchosX86Main by getting {
dependsOn(commonMain)
}
val watchosArm64Main by getting {
dependsOn(commonMain)
}
val watchosArm32Main by getting {
dependsOn(commonMain)
}
}
all {
languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalStdlibApi")
}
}
}
tasks {
create<Jar>("javadocJar") {
dependsOn(dokka)
archiveClassifier.set("javadoc")
from(dokka.get().outputDirectory)
}
dokka {
println ("Dokka !")
impliedPlatforms = mutableListOf("Common")
kotlinTasks {
listOf()
}
sourceRoot {
println ("Common !")
path = "/home/ionspin/Projects/Future/kotlin-multiplatform-crypto/crypto/src/commonMain" //TODO remove static path!
platforms = listOf("Common")
}
}
if (getHostOsName() == "linux" && getHostArchitecture() == "x86-64") {
val jvmTest by getting(Test::class) {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
}
}
val linuxTest by getting(KotlinNativeTest::class) {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
// showStandardStreams = true
}
}
val jsNodeTest by getting(KotlinJsTest::class) {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
showStandardStreams = true
}
}
// val legacyjsNodeTest by getting(KotlinJsTest::class) {
//
// testLogging {
// events("PASSED", "FAILED", "SKIPPED")
// showStandardStreams = true
// }
// }
// val jsIrBrowserTest by getting(KotlinJsTest::class) {
// testLogging {
// events("PASSED", "FAILED", "SKIPPED")
// showStandardStreams = true
// }
// }
}
if (getHostOsName() == "windows") {
val mingwX64Test by getting(KotlinNativeTest::class) {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
showStandardStreams = true
}
}
}
}

View File

@ -0,0 +1,12 @@
package com.ionspin.kotlin.crypto
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 27-May-2020
*/
interface CryptoProvider {
suspend fun initialize()
}

View File

@ -0,0 +1,12 @@
package _multiplatform_crypto_api
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
//Workaround for https://youtrack.jetbrains.com/issue/KT-36878
val byteArray = byteArrayOf(0)
val byte = 0.toByte()
val longArray = longArrayOf(0)
val long = 0L

View File

@ -26,25 +26,20 @@ interface Hash {
val MAX_HASH_BYTES : Int val MAX_HASH_BYTES : Int
} }
@ExperimentalUnsignedTypes
interface UpdatableHash : Hash { interface UpdatableHash : Hash {
fun update(data : Array<UByte>) fun update(data : UByteArray)
fun update(data : String) fun digest() : UByteArray
fun digest() : Array<UByte>
fun digestString() : String
} }
@ExperimentalUnsignedTypes
interface StatelessHash : Hash { interface StatelessHash : Hash {
fun digest(inputString: String, key: String? = null, hashLength: Int = MAX_HASH_BYTES): Array<UByte>
fun digest( }
inputMessage: Array<UByte> = emptyArray(),
key: Array<UByte> = emptyArray(), fun String.encodeToUByteArray() : UByteArray{
hashLength: Int = MAX_HASH_BYTES return encodeToByteArray().toUByteArray()
): Array<UByte>
} }

View File

@ -0,0 +1,31 @@
package com.ionspin.kotlin.crypto.hash.blake2b
import com.ionspin.kotlin.crypto.hash.StatelessHash
import com.ionspin.kotlin.crypto.hash.UpdatableHash
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
object Blake2bProperties {
const val MAX_HASH_BYTES = 64
}
interface Blake2b : UpdatableHash {
override val MAX_HASH_BYTES: Int
get() = Blake2bProperties.MAX_HASH_BYTES
}
interface Blake2bStateless : StatelessHash {
override val MAX_HASH_BYTES: Int
get() = Blake2bProperties.MAX_HASH_BYTES
fun digest(
inputMessage: UByteArray = ubyteArrayOf(),
key: UByteArray = ubyteArrayOf(),
hashLength: Int = MAX_HASH_BYTES
): UByteArray
}

View File

@ -0,0 +1,26 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.hash.StatelessHash
import com.ionspin.kotlin.crypto.hash.UpdatableHash
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
object Sha256Properties {
const val MAX_HASH_BYTES = 32
}
interface Sha256 : UpdatableHash {
override val MAX_HASH_BYTES: Int
get() = Sha256Properties.MAX_HASH_BYTES
}
interface StatelessSha256 : StatelessHash {
override val MAX_HASH_BYTES: Int
get() = Sha256Properties.MAX_HASH_BYTES
fun digest(
inputMessage: UByteArray = ubyteArrayOf()
): UByteArray
}

View File

@ -0,0 +1,25 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.hash.StatelessHash
import com.ionspin.kotlin.crypto.hash.UpdatableHash
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
object Sha512Properties {
const val MAX_HASH_BYTES = 64
}
interface Sha512 : UpdatableHash {
override val MAX_HASH_BYTES: Int
get() = Sha256Properties.MAX_HASH_BYTES
}
interface StatelessSha512 : StatelessHash {
override val MAX_HASH_BYTES: Int
get() = Sha512Properties.MAX_HASH_BYTES
fun digest(
inputMessage: UByteArray = ubyteArrayOf()
): UByteArray
}

View File

@ -22,5 +22,5 @@ package com.ionspin.kotlin.crypto.keyderivation
* on 16-May-2020 * on 16-May-2020
*/ */
interface KeyDerivationFunction { interface KeyDerivationFunction {
fun derive() : Array<UByte> fun derive() : UByteArray
} }

View File

@ -0,0 +1,667 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
@file:Suppress("UnstableApiUsage")
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest
plugins {
kotlin(PluginsDeps.multiplatform)
id(PluginsDeps.mavenPublish)
id(PluginsDeps.signing)
id(PluginsDeps.node) version Versions.nodePlugin
id(PluginsDeps.dokka) version Versions.dokkaPlugin
id(PluginsDeps.taskTree) version Versions.taskTreePlugin
}
val sonatypeStaging = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
val sonatypeSnapshots = "https://oss.sonatype.org/content/repositories/snapshots/"
val sonatypePassword: String? by project
val sonatypeUsername: String? by project
val sonatypePasswordEnv: String? = System.getenv()["SONATYPE_PASSWORD"]
val sonatypeUsernameEnv: String? = System.getenv()["SONATYPE_USERNAME"]
repositories {
mavenCentral()
jcenter()
}
group = ReleaseInfo.group
version = ReleaseInfo.version
val ideaActive = isInIdea()
println("Idea active: $ideaActive")
kotlin {
val hostOsName = getHostOsName()
runningOnLinuxx86_64 {
println("Configuring Linux X86-64 targets")
jvm()
js {
browser {
testTask {
isRunningInTravis {
enabled = false //Until I sort out testing on travis
}
useKarma {
useChrome()
}
}
}
nodejs {
testTask {
useMocha() {
timeout = "10s"
}
}
}
}
linuxX64() {
compilations.getByName("main") {
val libsodiumCinterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-linux-x86-64/include/")
}
kotlinOptions.freeCompilerArgs = listOf(
"-include-binary", "${project.rootDir}/sodiumWrapper/static-linux-x86-64/lib/libsodium.a"
)
}
binaries {
staticLib {
}
}
}
linuxArm64() {
binaries {
staticLib {
}
}
}
// Linux 32 is using target-sysroot-2-raspberrypi which is missing getrandom and explicit_bzero in stdlib
// so konanc can't build klib because getrandom missing will cause sodium_misuse()
// ld.lld: error: undefined symbol: explicit_bzero
// >>> referenced by utils.c
// >>> libsodium_la-utils.o:(sodium_memzero) in archive /tmp/included11051337748775083797/libsodium.a
//
// ld.lld: error: undefined symbol: getrandom
// >>> referenced by randombytes_sysrandom.c
// >>> libsodium_la-randombytes_sysrandom.o:(_randombytes_linux_getrandom) in archive /tmp/included11051337748775083797/libsodium.a
// linuxArm32Hfp() {
// binaries {
// staticLib {
// }
// }
// compilations.getByName("main") {
// val libsodiumCinterop by cinterops.creating {
// defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
// compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-arm32/include/")
// }
// kotlinOptions.freeCompilerArgs = listOf(
// "-include-binary", "${project.rootDir}/sodiumWrapper/static-arm32/lib/libsodium.a"
// )
// }
// }
}
//Not supported in OFFICIAL coroutines at the moment (we're running a custom build)
runningOnLinuxArm64 {
println("Configuring Linux Arm 64 targets")
}
runningOnLinuxArm32 {
println("Configuring Linux Arm 32 targets")
}
runningOnMacos {
println("Configuring macos targets")
iosX64() {
binaries {
framework {
optimized = true
}
}
}
iosArm64() {
binaries {
framework {
optimized = true
}
}
}
iosArm32() {
binaries {
framework {
optimized = true
}
}
}
macosX64() {
binaries {
framework {
optimized = true
}
}
compilations.getByName("main") {
val libsodiumCinterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-macos-x86-64/include")
}
kotlinOptions.freeCompilerArgs = listOf(
"-include-binary", "${project.rootDir}/sodiumWrapper/static-macos-x86-64/lib/libsodium.a"
)
}
}
tvosX64() {
binaries {
framework {
optimized = true
}
}
}
tvosArm64() {
binaries {
framework {
optimized = true
}
}
}
watchosArm64() {
binaries {
framework {
optimized = true
}
}
}
watchosArm32() {
binaries {
framework {
optimized = true
}
}
}
watchosX86() {
binaries {
framework {
optimized = true
}
}
}
}
runningOnWindows {
println("Configuring Mingw targets")
mingwX64() {
binaries {
staticLib {
optimized = true
}
}
compilations.getByName("main") {
val libsodiumCinterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-mingw-x86-64/include")
}
kotlinOptions.freeCompilerArgs = listOf(
"-include-binary", "${project.rootDir}/sodiumWrapper/static-mingw-x86-64/lib/libsodium.a"
)
}
}
}
println(targets.names)
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin(Deps.Common.stdLib))
implementation(kotlin(Deps.Common.test))
implementation(Deps.Common.coroutines)
implementation(Deps.Common.kotlinBigNum)
api(project(Deps.Common.apiProject))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin(Deps.Common.test))
implementation(kotlin(Deps.Common.testAnnotation))
}
}
val nativeDependencies = independentDependencyBlock {
implementation(Deps.Native.coroutines)
}
val nativeMain by creating {
dependsOn(commonMain)
isRunningInIdea {
kotlin.setSrcDirs(emptySet<String>())
}
dependencies {
nativeDependencies(this)
}
}
val nativeTest by creating {
dependsOn(commonTest)
isRunningInIdea {
kotlin.setSrcDirs(emptySet<String>())
}
dependencies {
implementation(Deps.Native.coroutines)
}
}
//Set up shared source sets
//linux, linuxArm32Hfp, linuxArm64
val linux64Bit = setOf(
"linuxX64"
)
val linuxArm64Bit = setOf(
"linuxArm64"
)
val linux32Bit = setOf(
"" // "linuxArm32Hfp"
)
//iosArm32, iosArm64, iosX64, macosX64, metadata, tvosArm64, tvosX64, watchosArm32, watchosArm64, watchosX86
val macos64Bit = setOf(
"macosX64"
)
val ios64Bit = setOf(
"iosArm64", "iosX64"
)
val ios32Bit = setOf(
"iosArm32"
)
val mingw64Bit = setOf(
"mingwX64"
)
val tvos64Bit = setOf(
"tvosArm64", "tvosX64"
)
val watchos32Bit = setOf(
"watchosX86", "watchosArm32", "watchosArm64"
)
targets.withType<KotlinNativeTarget> {
println("Target $name")
compilations.getByName("main") {
if (linux64Bit.contains(this@withType.name)) {
defaultSourceSet.dependsOn(nativeMain)
}
if (linuxArm64Bit.contains(this@withType.name)) {
defaultSourceSet.dependsOn(
createWorkaroundNativeMainSourceSet(
this@withType.name,
nativeDependencies
)
)
compilations.getByName("main") {
val libsodiumCinterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-arm64/include/")
}
kotlinOptions.freeCompilerArgs = listOf(
"-include-binary", "${project.rootDir}/sodiumWrapper/static-arm64/lib/libsodium.a"
)
}
}
if (linux32Bit.contains(this@withType.name)) {
defaultSourceSet.dependsOn(createWorkaroundNativeMainSourceSet(this@withType.name, nativeDependencies))
}
if (macos64Bit.contains(this@withType.name)) {
defaultSourceSet.dependsOn(createWorkaroundNativeMainSourceSet(this@withType.name, nativeDependencies))
}
//All ioses share the same static library
if (ios64Bit.contains(this@withType.name)) {
defaultSourceSet.dependsOn(createWorkaroundNativeMainSourceSet(this@withType.name, nativeDependencies))
println("Setting ios cinterop for $this")
val libsodiumCinterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-ios/include")
}
kotlinOptions.freeCompilerArgs = listOf(
"-include-binary", "${project.rootDir}/sodiumWrapper/static-ios/lib/libsodium.a"
)
}
if (ios32Bit.contains(this@withType.name)) {
defaultSourceSet.dependsOn(createWorkaroundNativeMainSourceSet(this@withType.name, nativeDependencies))
println("Setting ios cinterop for $this")
val libsodiumCinterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-ios/include")
}
kotlinOptions.freeCompilerArgs = listOf(
"-include-binary", "${project.rootDir}/sodiumWrapper/static-ios/lib/libsodium.a"
)
}
if (tvos64Bit.contains(this@withType.name)) {
defaultSourceSet.dependsOn(createWorkaroundNativeMainSourceSet(this@withType.name, nativeDependencies))
println("Setting ios cinterop for $this")
val libsodiumCinterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-tvos/include")
}
kotlinOptions.freeCompilerArgs = listOf(
"-include-binary", "${project.rootDir}/sodiumWrapper/static-tvos/lib/libsodium.a"
)
}
if (watchos32Bit.contains(this@withType.name)) {
defaultSourceSet.dependsOn(createWorkaroundNativeMainSourceSet(this@withType.name, nativeDependencies))
println("Setting ios cinterop for $this")
val libsodiumCinterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/libsodium.def"))
compilerOpts.add("-I${project.rootDir}/sodiumWrapper/static-watchos/include")
}
kotlinOptions.freeCompilerArgs = listOf(
"-include-binary", "${project.rootDir}/sodiumWrapper/static-watchos/lib/libsodium.a"
)
}
}
compilations.getByName("test") {
println("Setting native test dep for $this@withType.name")
defaultSourceSet.dependsOn(nativeTest)
}
}
runningOnLinuxx86_64 {
println("Configuring Linux 64 Bit source sets")
val jvmMain by getting {
dependencies {
implementation(kotlin(Deps.Jvm.stdLib))
implementation(kotlin(Deps.Jvm.test))
implementation(kotlin(Deps.Jvm.testJUnit))
implementation(Deps.Jvm.coroutinesCore)
//lazysodium
implementation(Deps.Jvm.Delegated.lazysodium)
implementation(Deps.Jvm.Delegated.jna)
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin(Deps.Jvm.test))
implementation(kotlin(Deps.Jvm.testJUnit))
implementation(Deps.Jvm.coroutinesTest)
implementation(kotlin(Deps.Jvm.reflection))
}
}
val jsMain by getting {
dependencies {
implementation(kotlin(Deps.Js.stdLib))
implementation(Deps.Js.coroutines)
implementation(npm(Deps.Js.Npm.libsodiumWrappers.first, Deps.Js.Npm.libsodiumWrappers.second))
}
}
val jsTest by getting {
dependencies {
implementation(Deps.Js.coroutines)
implementation(kotlin(Deps.Js.test))
implementation(npm(Deps.Js.Npm.libsodiumWrappers.first, Deps.Js.Npm.libsodiumWrappers.second))
}
}
val linuxX64Main by getting {
isRunningInIdea {
kotlin.srcDir("src/nativeMain/kotlin")
}
}
val linuxX64Test by getting {
dependsOn(nativeTest)
isRunningInIdea {
kotlin.srcDir("src/nativeTest/kotlin")
}
}
}
runningOnMacos {
println("Configuring Macos source sets")
val macosX64Main by getting {
dependsOn(nativeMain)
if (ideaActive) {
kotlin.srcDir("src/nativeMain/kotlin")
}
}
val macosX64Test by getting {
dependsOn(nativeTest)
if (ideaActive) {
kotlin.srcDir("src/nativeTest/kotlin")
}
}
val tvosX64Main by getting {
dependsOn(commonMain)
}
val tvosArm64Main by getting {
dependsOn(commonMain)
}
val watchosX86Main by getting {
dependsOn(commonMain)
}
val watchosArm64Main by getting {
dependsOn(commonMain)
}
val watchosArm32Main by getting {
dependsOn(commonMain)
}
}
if (hostOsName == "windows") {
val mingwX64Main by getting {
dependsOn(nativeMain)
if (ideaActive) {
kotlin.srcDir("src/nativeMain/kotlin")
}
}
val mingwX64Test by getting {
dependsOn(nativeTest)
if (ideaActive) {
kotlin.srcDir("src/nativeTest/kotlin")
}
}
}
all {
languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalStdlibApi")
}
}
}
tasks {
create<Jar>("javadocJar") {
dependsOn(dokka)
archiveClassifier.set("javadoc")
from(dokka.get().outputDirectory)
}
dokka {
}
if (getHostOsName() == "linux" && getHostArchitecture() == "x86-64") {
val jvmTest by getting(Test::class) {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
}
}
val linuxX64Test by getting(KotlinNativeTest::class) {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
showStandardStreams = true
}
}
val jsNodeTest by getting(KotlinJsTest::class) {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
// showStandardStreams = true
}
}
// val legacyjsNodeTest by getting(KotlinJsTest::class) {
//
// testLogging {
// events("PASSED", "FAILED", "SKIPPED")
// showStandardStreams = true
// }
// }
// val jsIrBrowserTest by getting(KotlinJsTest::class) {
// testLogging {
// events("PASSED", "FAILED", "SKIPPED")
// showStandardStreams = true
// }
// }
}
if (getHostOsName() == "windows") {
val mingwX64Test by getting(KotlinNativeTest::class) {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
showStandardStreams = true
}
}
}
}
signing {
isRequired = false
sign(publishing.publications)
}
publishing {
publications.withType(MavenPublication::class) {
artifact(tasks["javadocJar"])
pom {
name.set("Kotlin Multiplatform Crypto")
description.set("Kotlin Multiplatform Crypto library")
url.set("https://github.com/ionspin/kotlin-multiplatform-crypto")
licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
developers {
developer {
id.set("ionspin")
name.set("Ugljesa Jovanovic")
email.set("opensource@ionspin.com")
}
}
scm {
url.set("https://github.com/ionspin/kotlin-multiplatform-crypto")
connection.set("scm:git:git://git@github.com:ionspin/kotlin-multiplatform-crypto.git")
developerConnection.set("scm:git:ssh://git@github.com:ionspin/kotlin-multiplatform-crypto.git")
}
}
}
repositories {
maven {
url = uri(sonatypeStaging)
credentials {
username = sonatypeUsername ?: sonatypeUsernameEnv ?: ""
password = sonatypePassword ?: sonatypePasswordEnv ?: ""
}
}
maven {
name = "snapshot"
url = uri(sonatypeSnapshots)
credentials {
username = sonatypeUsername ?: sonatypeUsernameEnv ?: ""
password = sonatypePassword ?: sonatypePasswordEnv ?: ""
}
}
}
}
//configurations.forEach {
//
// if (it.name == "linuxCompileKlibraries") {
// println("Configuration name: ${it.name}")
// it.attributes {
// this.keySet().forEach { key ->
// val attribute = getAttribute(key)
// println(" |-- Attribute $key ${attribute}")
// attribute(org.jetbrains.kotlin.gradle.plugin.ProjectLocalConfigurations.ATTRIBUTE, "publicZ")
// }
// }
// }
//}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 20-Jul-2019
*/
object Config {
const val DEBUG = false
}

View File

@ -0,0 +1,83 @@
package com.ionspin.kotlin.crypto
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bProperties
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bDelegated
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bDelegatedStateless
import com.ionspin.kotlin.crypto.hash.sha.*
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
object Crypto : CryptoProvider {
override suspend fun initialize() {
Initializer.initialize()
}
fun initializeWithCallback(done: () -> Unit) {
Initializer.initializeWithCallback(done)
}
object Blake2b {
fun updateable(key: UByteArray? = null, hashLength: Int = Blake2bProperties.MAX_HASH_BYTES): com.ionspin.kotlin.crypto.hash.blake2b.Blake2b {
checkInitialization()
return Blake2bDelegated(key, hashLength)
}
fun stateless(message: UByteArray, key: UByteArray = ubyteArrayOf(), hashLength: Int = Blake2bProperties.MAX_HASH_BYTES): UByteArray {
checkInitialization()
return Blake2bDelegatedStateless.digest(message, key, hashLength)
}
}
object Sha256 {
fun updateable(): com.ionspin.kotlin.crypto.hash.sha.Sha256 {
checkInitialization()
return Sha256Delegated()
}
fun stateless(message: UByteArray) : UByteArray{
checkInitialization()
return Sha256StatelessDelegated.digest(inputMessage = message)
}
}
object Sha512 {
fun updateable(): com.ionspin.kotlin.crypto.hash.sha.Sha512 {
checkInitialization()
return Sha512Delegated()
}
fun stateless(message: UByteArray) : UByteArray {
checkInitialization()
return Sha512StatelessDelegated.digest(inputMessage = message)
}
}
private fun checkInitialization() {
if (!Initializer.isInitialized()) {
throw RuntimeException("Platform library not initialized, check if you called Initializer.initialize()")
}
}
}
object SimpleCrypto {
fun hash(message: String): UByteArray {
return ubyteArrayOf(0U)
}
}
expect object Initializer {
fun isInitialized() : Boolean
suspend fun initialize()
fun initializeWithCallback(done: () -> (Unit))
}

View File

@ -0,0 +1,12 @@
package _multiplatform_crypto_delegated
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
//Workaround for https://youtrack.jetbrains.com/issue/KT-36878
val byteArray = byteArrayOf(0)
val byte = 0.toByte()
val longArray = longArrayOf(0)
val long = 0L

View File

@ -0,0 +1,26 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
expect object SRNG {
fun getRandomBytes(amount : Int) : UByteArray
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.hash.blake2b
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jul-2019
*/
expect class Blake2bDelegated(key: UByteArray? = null, hashLength: Int = Blake2bProperties.MAX_HASH_BYTES) : Blake2b
expect object Blake2bDelegatedStateless : Blake2bStateless

View File

@ -0,0 +1,29 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.hash.sha
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
expect class Sha256Delegated() : Sha256
expect object Sha256StatelessDelegated : StatelessSha256

View File

@ -0,0 +1,29 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.hash.sha
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
expect class Sha512Delegated() : Sha512
expect object Sha512StatelessDelegated : StatelessSha512

View File

@ -0,0 +1,10 @@
package com.ionspin.kotlin.crypto.keyderivation.argon2
import com.ionspin.kotlin.crypto.keyderivation.KeyDerivationFunction
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
interface Argon2 : KeyDerivationFunction

View File

@ -0,0 +1,34 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.parallelization
import kotlin.time.ExperimentalTime
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-May-2020
*/
@ExperimentalTime
object Coroutines14 {
fun argonParallel() : Array<UByte> {
// val argon = Argon2()
// argon
println("Placeholder")
return emptyArray()
}
}

View File

@ -17,13 +17,12 @@
package com.ionspin.kotlin.crypto.symmetric package com.ionspin.kotlin.crypto.symmetric
import com.ionspin.kotlin.crypto.SRNG import com.ionspin.kotlin.crypto.SRNG
import com.ionspin.kotlin.crypto.util.chunked
import com.ionspin.kotlin.crypto.util.xor import com.ionspin.kotlin.crypto.util.xor
/** /**
* Advanced encryption standard with cipher block chaining and PKCS #5 * Advanced encryption standard with cipher block chaining and PKCS #5
* *
* For bulk encryption/decryption use [AesCbc.encrypt] and [AesCbc.decrypt] * For bulk encryption/decryption use [AesCbcPure.encrypt] and [AesCbcPure.decrypt]
* *
* To get an instance of AesCbc and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor] * To get an instance of AesCbc and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor]
* *
@ -31,8 +30,8 @@ import com.ionspin.kotlin.crypto.util.xor
* ugljesa.jovanovic@ionspin.com * ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019 * on 21-Sep-2019
*/ */
@ExperimentalUnsignedTypes
class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializationVector: Array<UByte>? = null) { class AesCbcPure internal constructor(val aesKey: AesKey, val mode: Mode, initializationVector: UByteArray? = null) {
companion object { companion object {
const val BLOCK_BYTES = 16 const val BLOCK_BYTES = 16
@ -40,22 +39,22 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
* Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all * Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all
* data call [encrypt] * data call [encrypt]
*/ */
fun createEncryptor(aesKey: AesKey) : AesCbc { fun createEncryptor(aesKey: AesKey) : AesCbcPure {
return AesCbc(aesKey, Mode.ENCRYPT) return AesCbcPure(aesKey, Mode.ENCRYPT)
} }
/** /**
* Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all * Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all
* data call [decrypt] * data call [decrypt]
*/ */
fun createDecryptor(aesKey : AesKey) : AesCbc { fun createDecryptor(aesKey : AesKey) : AesCbcPure {
return AesCbc(aesKey, Mode.DECRYPT) return AesCbcPure(aesKey, Mode.DECRYPT)
} }
/** /**
* Bulk encryption, returns encrypted data and a random initialization vector * Bulk encryption, returns encrypted data and a random initialization vector
*/ */
fun encrypt(aesKey: AesKey, data: Array<UByte>): EncryptedDataAndInitializationVector { fun encrypt(aesKey: AesKey, data: UByteArray): EncryptedDataAndInitializationVector {
val aesCbc = AesCbc(aesKey, Mode.ENCRYPT) val aesCbc = AesCbcPure(aesKey, Mode.ENCRYPT)
aesCbc.addData(data) aesCbc.addData(data)
return aesCbc.encrypt() return aesCbc.encrypt()
} }
@ -63,20 +62,20 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
/** /**
* Bulk decryption, returns decrypted data * Bulk decryption, returns decrypted data
*/ */
fun decrypt(aesKey: AesKey, data: Array<UByte>, initialCounter: Array<UByte>? = null): Array<UByte> { fun decrypt(aesKey: AesKey, data: UByteArray, initialCounter: UByteArray? = null): UByteArray {
val aesCbc = AesCbc(aesKey, Mode.DECRYPT, initialCounter) val aesCbc = AesCbcPure(aesKey, Mode.DECRYPT, initialCounter)
aesCbc.addData(data) aesCbc.addData(data)
return aesCbc.decrypt() return aesCbc.decrypt()
} }
private fun padToBlock(unpadded: Array<UByte>): Array<UByte> { private fun padToBlock(unpadded: UByteArray): UByteArray {
val paddingSize = 16 - unpadded.size val paddingSize = 16 - unpadded.size
if (unpadded.size == BLOCK_BYTES) { if (unpadded.size == BLOCK_BYTES) {
return unpadded return unpadded
} }
if (unpadded.size == BLOCK_BYTES) { if (unpadded.size == BLOCK_BYTES) {
return Array(BLOCK_BYTES) { return UByteArray(BLOCK_BYTES) {
BLOCK_BYTES.toUByte() BLOCK_BYTES.toUByte()
} }
} }
@ -85,7 +84,7 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
throw IllegalStateException("Block larger than 128 bytes") throw IllegalStateException("Block larger than 128 bytes")
} }
return Array(BLOCK_BYTES) { return UByteArray(BLOCK_BYTES) {
when (it) { when (it) {
in unpadded.indices -> unpadded[it] in unpadded.indices -> unpadded[it]
else -> paddingSize.toUByte() else -> paddingSize.toUByte()
@ -95,20 +94,20 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
} }
} }
var currentOutput: Array<UByte> = arrayOf() var currentOutput: UByteArray = ubyteArrayOf()
var previousEncrypted: Array<UByte> = arrayOf() var previousEncrypted: UByteArray = ubyteArrayOf()
val initVector = if (initializationVector.isNullOrEmpty()) { val initVector = if (initializationVector.isNullOrEmpty()) {
SRNG.getRandomBytes(16) SRNG.getRandomBytes(16)
} else { } else {
initializationVector initializationVector
} }
val output = MutableList<Array<UByte>>(0) { arrayOf() } val output = MutableList<UByteArray>(0) { ubyteArrayOf() }
var buffer: Array<UByte> = UByteArray(16) { 0U }.toTypedArray() var buffer: UByteArray = UByteArray(16) { 0U }
var bufferCounter = 0 var bufferCounter = 0
fun addData(data: Array<UByte>) { fun addData(data: UByteArray) {
//Padding //Padding
when { when {
bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter) bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter)
@ -116,16 +115,16 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
val chunked = data.chunked(BLOCK_BYTES) val chunked = data.chunked(BLOCK_BYTES)
chunked.forEach { chunk -> chunked.forEach { chunk ->
if (bufferCounter + chunk.size < BLOCK_BYTES) { if (bufferCounter + chunk.size < BLOCK_BYTES) {
appendToBuffer(chunk, bufferCounter) appendToBuffer(chunk.toUByteArray(), bufferCounter)
} else { } else {
chunk.copyInto( chunk.toUByteArray().copyInto(
destination = buffer, destination = buffer,
destinationOffset = bufferCounter, destinationOffset = bufferCounter,
startIndex = 0, startIndex = 0,
endIndex = BLOCK_BYTES - bufferCounter endIndex = BLOCK_BYTES - bufferCounter
) )
output += consumeBlock(buffer) output += consumeBlock(buffer)
buffer = Array<UByte>(BLOCK_BYTES) { buffer = UByteArray(BLOCK_BYTES) {
when (it) { when (it) {
in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> { in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> {
chunk[it + (BLOCK_BYTES - bufferCounter)] chunk[it + (BLOCK_BYTES - bufferCounter)]
@ -153,7 +152,7 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
if (bufferCounter > 0) { if (bufferCounter > 0) {
val lastBlockPadded = padToBlock(buffer) val lastBlockPadded = padToBlock(buffer)
if (lastBlockPadded.size > BLOCK_BYTES) { if (lastBlockPadded.size > BLOCK_BYTES) {
val chunks = lastBlockPadded.chunked(BLOCK_BYTES) val chunks = lastBlockPadded.chunked(BLOCK_BYTES).map { it.toUByteArray() }
output += consumeBlock(chunks[0]) output += consumeBlock(chunks[0])
output += consumeBlock(chunks[1]) output += consumeBlock(chunks[1])
} else { } else {
@ -161,7 +160,7 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
} }
} }
return EncryptedDataAndInitializationVector( return EncryptedDataAndInitializationVector(
output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }, output.reversed().foldRight(UByteArray(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes },
initVector initVector
) )
} }
@ -170,7 +169,7 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
* Decrypt data * Decrypt data
* @return Decrypted data * @return Decrypted data
*/ */
fun decrypt(): Array<UByte> { fun decrypt(): UByteArray {
val removePaddingCount = output.last().last() val removePaddingCount = output.last().last()
@ -178,37 +177,38 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
output.last().dropLast(removePaddingCount.toInt() and 0x7F) output.last().dropLast(removePaddingCount.toInt() and 0x7F)
} else { } else {
output.last().toList() output.last().toList()
} }.toUByteArray()
val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray() val preparedOutput = (output.dropLast(1) + listOf(removedPadding))
//JS compiler freaks out here if we don't supply exact type //JS compiler freaks out here if we don't supply exact type
val reversed : List<Array<UByte>> = preparedOutput.reversed() as List<Array<UByte>> val reversed : List<UByteArray> = preparedOutput.reversed() as List<UByteArray>
val folded : Array<UByte> = reversed.foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> val folded : UByteArray = reversed.foldRight(UByteArray(0) { 0U }) { uByteArray, acc ->
acc + arrayOfUBytes } acc + uByteArray
}
return folded return folded
} }
private fun appendToBuffer(array: Array<UByte>, start: Int) { private fun appendToBuffer(array: UByteArray, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size bufferCounter += array.size
} }
private fun consumeBlock(data: Array<UByte>): Array<UByte> { private fun consumeBlock(data: UByteArray): UByteArray {
return when (mode) { return when (mode) {
Mode.ENCRYPT -> { Mode.ENCRYPT -> {
currentOutput = if (currentOutput.isEmpty()) { currentOutput = if (currentOutput.isEmpty()) {
println("IV: $initVector") println("IV: $initVector")
Aes.encrypt(aesKey, data xor initVector) AesPure.encrypt(aesKey, data xor initVector)
} else { } else {
Aes.encrypt(aesKey, data xor currentOutput) AesPure.encrypt(aesKey, data xor currentOutput)
} }
currentOutput currentOutput
} }
Mode.DECRYPT -> { Mode.DECRYPT -> {
if (currentOutput.isEmpty()) { if (currentOutput.isEmpty()) {
currentOutput = Aes.decrypt(aesKey, data) xor initVector currentOutput = AesPure.decrypt(aesKey, data) xor initVector
} else { } else {
currentOutput = Aes.decrypt(aesKey, data) xor previousEncrypted currentOutput = AesPure.decrypt(aesKey, data) xor previousEncrypted
} }
previousEncrypted = data previousEncrypted = data
currentOutput currentOutput
@ -219,8 +219,8 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
} }
@ExperimentalUnsignedTypes
data class EncryptedDataAndInitializationVector(val encryptedData : Array<UByte>, val initilizationVector : Array<UByte>) { data class EncryptedDataAndInitializationVector(val encryptedData : UByteArray, val initilizationVector : UByteArray) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other == null || this::class != other::class) return false if (other == null || this::class != other::class) return false

View File

@ -20,15 +20,14 @@ import com.ionspin.kotlin.bignum.Endianness
import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.integer.BigInteger
import com.ionspin.kotlin.bignum.modular.ModularBigInteger import com.ionspin.kotlin.bignum.modular.ModularBigInteger
import com.ionspin.kotlin.crypto.SRNG import com.ionspin.kotlin.crypto.SRNG
import com.ionspin.kotlin.crypto.util.chunked import com.ionspin.kotlin.crypto.symmetric.AesCtrPure.Companion.encrypt
import com.ionspin.kotlin.crypto.symmetric.AesCtr.Companion.encrypt
import com.ionspin.kotlin.crypto.util.xor import com.ionspin.kotlin.crypto.util.xor
/** /**
* *
* Advanced encryption standard with counter mode * Advanced encryption standard with counter mode
* *
* For bulk encryption/decryption use [AesCtr.encrypt] and [AesCtr.decrypt] * For bulk encryption/decryption use [AesCtrPure.encrypt] and [AesCtrPure.decrypt]
* *
* To get an instance of AesCtr and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor] * To get an instance of AesCtr and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor]
* *
@ -36,8 +35,8 @@ import com.ionspin.kotlin.crypto.util.xor
* ugljesa.jovanovic@ionspin.com * ugljesa.jovanovic@ionspin.com
* on 22-Sep-2019 * on 22-Sep-2019
*/ */
@ExperimentalUnsignedTypes
class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCounter: Array<UByte>? = null) { class AesCtrPure internal constructor(val aesKey: AesKey, val mode: Mode, initialCounter: UByteArray? = null) {
companion object { companion object {
const val BLOCK_BYTES = 16 const val BLOCK_BYTES = 16
@ -47,50 +46,50 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
* Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all * Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all
* data call [encrypt] * data call [encrypt]
*/ */
fun createEncryptor(aesKey: AesKey) : AesCtr { fun createEncryptor(aesKey: AesKey) : AesCtrPure {
return AesCtr(aesKey, Mode.ENCRYPT) return AesCtrPure(aesKey, Mode.ENCRYPT)
} }
/** /**
* Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all * Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all
* data call [decrypt] * data call [decrypt]
*/ */
fun createDecryptor(aesKey : AesKey) : AesCtr { fun createDecryptor(aesKey : AesKey) : AesCtrPure {
return AesCtr(aesKey, Mode.DECRYPT) return AesCtrPure(aesKey, Mode.DECRYPT)
} }
/** /**
* Bulk encryption, returns encrypted data and a random initial counter * Bulk encryption, returns encrypted data and a random initial counter
*/ */
fun encrypt(aesKey: AesKey, data: Array<UByte>): EncryptedDataAndInitialCounter { fun encrypt(aesKey: AesKey, data: UByteArray): EncryptedDataAndInitialCounter {
val aesCtr = AesCtr(aesKey, Mode.ENCRYPT) val aesCtr = AesCtrPure(aesKey, Mode.ENCRYPT)
aesCtr.addData(data) aesCtr.addData(data)
return aesCtr.encrypt() return aesCtr.encrypt()
} }
/** /**
* Bulk decryption, returns decrypted data * Bulk decryption, returns decrypted data
*/ */
fun decrypt(aesKey: AesKey, data: Array<UByte>, initialCounter: Array<UByte>? = null): Array<UByte> { fun decrypt(aesKey: AesKey, data: UByteArray, initialCounter: UByteArray? = null): UByteArray {
val aesCtr = AesCtr(aesKey, Mode.DECRYPT, initialCounter) val aesCtr = AesCtrPure(aesKey, Mode.DECRYPT, initialCounter)
aesCtr.addData(data) aesCtr.addData(data)
return aesCtr.decrypt() return aesCtr.decrypt()
} }
} }
var currentOutput: Array<UByte> = arrayOf() var currentOutput: UByteArray = ubyteArrayOf()
var previousEncrypted: Array<UByte> = arrayOf() var previousEncrypted: UByteArray = ubyteArrayOf()
val counterStart = if (initialCounter.isNullOrEmpty()) { val counterStart = if (initialCounter.isNullOrEmpty()) {
SRNG.getRandomBytes(16) SRNG.getRandomBytes(16)
} else { } else {
initialCounter initialCounter
} }
var blockCounter = modularCreator.fromBigInteger(BigInteger.fromUByteArray(counterStart, Endianness.BIG)) var blockCounter = modularCreator.fromBigInteger(BigInteger.fromUByteArray(counterStart.toTypedArray(), Endianness.BIG))
val output = MutableList<Array<UByte>>(0) { arrayOf() } val output = MutableList<UByteArray>(0) { ubyteArrayOf() }
var buffer: Array<UByte> = UByteArray(16) { 0U }.toTypedArray() var buffer: UByteArray = UByteArray(16) { 0U }
var bufferCounter = 0 var bufferCounter = 0
fun addData(data: Array<UByte>) { fun addData(data: UByteArray) {
//Padding //Padding
when { when {
bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter) bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter)
@ -98,9 +97,9 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
val chunked = data.chunked(BLOCK_BYTES) val chunked = data.chunked(BLOCK_BYTES)
chunked.forEach { chunk -> chunked.forEach { chunk ->
if (bufferCounter + chunk.size < BLOCK_BYTES) { if (bufferCounter + chunk.size < BLOCK_BYTES) {
appendToBuffer(chunk, bufferCounter) appendToBuffer(chunk.toUByteArray(), bufferCounter)
} else { } else {
chunk.copyInto( chunk.toUByteArray().copyInto(
destination = buffer, destination = buffer,
destinationOffset = bufferCounter, destinationOffset = bufferCounter,
startIndex = 0, startIndex = 0,
@ -108,7 +107,7 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
) )
output += consumeBlock(buffer, blockCounter) output += consumeBlock(buffer, blockCounter)
blockCounter += 1 blockCounter += 1
buffer = Array<UByte>(BLOCK_BYTES) { buffer = UByteArray(BLOCK_BYTES) {
when (it) { when (it) {
in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> { in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> {
chunk[it + (BLOCK_BYTES - bufferCounter)] chunk[it + (BLOCK_BYTES - bufferCounter)]
@ -136,7 +135,7 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
output += consumeBlock(buffer, blockCounter) output += consumeBlock(buffer, blockCounter)
} }
return EncryptedDataAndInitialCounter( return EncryptedDataAndInitialCounter(
output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }, output.reversed().foldRight(UByteArray(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes },
counterStart counterStart
) )
} }
@ -144,41 +143,41 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
* Decrypt data * Decrypt data
* @return Decrypted data * @return Decrypted data
*/ */
fun decrypt(): Array<UByte> { fun decrypt(): UByteArray {
if (bufferCounter > 0) { if (bufferCounter > 0) {
output += consumeBlock(buffer, blockCounter) output += consumeBlock(buffer, blockCounter)
} }
//JS compiler freaks out here if we don't supply exact type //JS compiler freaks out here if we don't supply exact type
val reversed: List<Array<UByte>> = output.reversed() as List<Array<UByte>> val reversed: List<UByteArray> = output.reversed() as List<UByteArray>
val folded: Array<UByte> = reversed.foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> val folded: UByteArray = reversed.foldRight(UByteArray(0) { 0U }) { arrayOfUBytes, acc ->
acc + arrayOfUBytes acc + arrayOfUBytes
} }
return folded return folded
} }
private fun appendToBuffer(array: Array<UByte>, start: Int) { private fun appendToBuffer(array: UByteArray, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size bufferCounter += array.size
} }
private fun consumeBlock(data: Array<UByte>, blockCount: ModularBigInteger): Array<UByte> { private fun consumeBlock(data: UByteArray, blockCount: ModularBigInteger): UByteArray {
val blockCountAsByteArray = blockCount.toUByteArray(Endianness.BIG).expandCounterTo16Bytes() val blockCountAsByteArray = blockCount.toUByteArray(Endianness.BIG).toUByteArray().expandCounterTo16Bytes()
return when (mode) { return when (mode) {
Mode.ENCRYPT -> { Mode.ENCRYPT -> {
Aes.encrypt(aesKey, blockCountAsByteArray) xor data AesPure.encrypt(aesKey, blockCountAsByteArray) xor data
} }
Mode.DECRYPT -> { Mode.DECRYPT -> {
Aes.encrypt(aesKey, blockCountAsByteArray) xor data AesPure.encrypt(aesKey, blockCountAsByteArray) xor data
} }
} }
} }
private fun Array<UByte>.expandCounterTo16Bytes() : Array<UByte> { private fun UByteArray.expandCounterTo16Bytes() : UByteArray {
return if (this.size < 16) { return if (this.size < 16) {
println("Expanding") println("Expanding")
val diff = 16 - this.size val diff = 16 - this.size
val pad = Array<UByte>(diff) { 0U } val pad = UByteArray(diff) { 0U }
pad + this pad + this
} else { } else {
this this
@ -187,8 +186,8 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
} }
@ExperimentalUnsignedTypes
data class EncryptedDataAndInitialCounter(val encryptedData : Array<UByte>, val initialCounter : Array<UByte>) { data class EncryptedDataAndInitialCounter(val encryptedData : UByteArray, val initialCounter : UByteArray) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other == null || this::class != other::class) return false if (other == null || this::class != other::class) return false

View File

@ -1,10 +1,12 @@
package com.ionspin.kotlin.crypto.symmetric package com.ionspin.kotlin.crypto.symmetric
import com.ionspin.kotlin.crypto.util.flattenToUByteArray
/** /**
* Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 07/Sep/2019 * Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 07/Sep/2019
*/ */
@ExperimentalUnsignedTypes
internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UByte>) { internal class AesPure internal constructor(val aesKey: AesKey, val input: UByteArray) {
companion object { companion object {
private val debug = false private val debug = false
@ -54,18 +56,18 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
val rcon: UByteArray = ubyteArrayOf(0x8DU, 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1BU, 0x36U) val rcon: UByteArray = ubyteArrayOf(0x8DU, 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1BU, 0x36U)
fun encrypt(aesKey: AesKey, input: Array<UByte>): Array<UByte> { fun encrypt(aesKey: AesKey, input: UByteArray): UByteArray {
return Aes(aesKey, input).encrypt() return AesPure(aesKey, input).encrypt()
} }
fun decrypt(aesKey: AesKey, input: Array<UByte>): Array<UByte> { fun decrypt(aesKey: AesKey, input: UByteArray): UByteArray {
return Aes(aesKey, input).decrypt() return AesPure(aesKey, input).decrypt()
} }
} }
val state: Array<Array<UByte>> = (0 until 4).map { outerCounter -> val state: Array<UByteArray> = (0 until 4).map { outerCounter ->
Array<UByte>(4) { innerCounter -> input[innerCounter * 4 + outerCounter] } UByteArray(4) { innerCounter -> input[innerCounter * 4 + outerCounter] }
}.toTypedArray() }.toTypedArray()
val numberOfRounds = when (aesKey) { val numberOfRounds = when (aesKey) {
@ -74,7 +76,7 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
is AesKey.Aes256Key -> 14 is AesKey.Aes256Key -> 14
} }
val expandedKey: Array<Array<UByte>> = expandKey() val expandedKey: Array<UByteArray> = expandKey()
var round = 0 var round = 0
@ -110,22 +112,22 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
} }
fun shiftRows() { fun shiftRows() {
state[0] = arrayOf(state[0][0], state[0][1], state[0][2], state[0][3]) state[0] = ubyteArrayOf(state[0][0], state[0][1], state[0][2], state[0][3])
state[1] = arrayOf(state[1][1], state[1][2], state[1][3], state[1][0]) state[1] = ubyteArrayOf(state[1][1], state[1][2], state[1][3], state[1][0])
state[2] = arrayOf(state[2][2], state[2][3], state[2][0], state[2][1]) state[2] = ubyteArrayOf(state[2][2], state[2][3], state[2][0], state[2][1])
state[3] = arrayOf(state[3][3], state[3][0], state[3][1], state[3][2]) state[3] = ubyteArrayOf(state[3][3], state[3][0], state[3][1], state[3][2])
} }
fun inversShiftRows() { fun inversShiftRows() {
state[0] = arrayOf(state[0][0], state[0][1], state[0][2], state[0][3]) state[0] = ubyteArrayOf(state[0][0], state[0][1], state[0][2], state[0][3])
state[1] = arrayOf(state[1][3], state[1][0], state[1][1], state[1][2]) state[1] = ubyteArrayOf(state[1][3], state[1][0], state[1][1], state[1][2])
state[2] = arrayOf(state[2][2], state[2][3], state[2][0], state[2][1]) state[2] = ubyteArrayOf(state[2][2], state[2][3], state[2][0], state[2][1])
state[3] = arrayOf(state[3][1], state[3][2], state[3][3], state[3][0]) state[3] = ubyteArrayOf(state[3][1], state[3][2], state[3][3], state[3][0])
} }
fun mixColumns() { fun mixColumns() {
val stateMixed: Array<Array<UByte>> = (0 until 4).map { val stateMixed: Array<UByteArray> = (0 until 4).map {
Array<UByte>(4) { 0U } UByteArray(4) { 0U }
}.toTypedArray() }.toTypedArray()
for (c in 0..3) { for (c in 0..3) {
@ -138,8 +140,8 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
} }
fun inverseMixColumns() { fun inverseMixColumns() {
val stateMixed: Array<Array<UByte>> = (0 until 4).map { val stateMixed: Array<UByteArray> = (0 until 4).map {
Array<UByte>(4) { 0U } UByteArray(4) { 0U }
}.toTypedArray() }.toTypedArray()
for (c in 0..3) { for (c in 0..3) {
stateMixed[0][c] = stateMixed[0][c] =
@ -203,9 +205,9 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
return galoisFieldMultiply(this.toUByte(), second) return galoisFieldMultiply(this.toUByte(), second)
} }
fun expandKey(): Array<Array<UByte>> { fun expandKey(): Array<UByteArray> {
val expandedKey = (0 until 4 * (numberOfRounds + 1)).map { val expandedKey = (0 until 4 * (numberOfRounds + 1)).map {
Array<UByte>(4) { 0U } UByteArray(4) { 0U }
}.toTypedArray() }.toTypedArray()
// First round // First round
for (i in 0 until aesKey.numberOf32BitWords) { for (i in 0 until aesKey.numberOf32BitWords) {
@ -241,13 +243,13 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
} }
expandedKey[i] = expandedKey[i - aesKey.numberOf32BitWords].mapIndexed { index, it -> expandedKey[i] = expandedKey[i - aesKey.numberOf32BitWords].mapIndexed { index, it ->
it xor temp[index] it xor temp[index]
}.toTypedArray() }.toUByteArray()
clearArray(temp) clearArray(temp)
} }
return expandedKey return expandedKey
} }
fun encrypt(): Array<UByte> { fun encrypt(): UByteArray {
if (completed) { if (completed) {
throw RuntimeException("Encrypt can only be called once per Aes instance, since the state is cleared at the " + throw RuntimeException("Encrypt can only be called once per Aes instance, since the state is cleared at the " +
"end of the operation") "end of the operation")
@ -273,8 +275,8 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
addRoundKey() addRoundKey()
printState() printState()
val transposedMatrix = (0 until 4).map { outerCounter -> val transposedMatrix = (0 until 4).map { outerCounter ->
Array<UByte>(4) { 0U } UByteArray(4) { 0U }
}.toTypedArray() }
for (i in 0 until 4) { for (i in 0 until 4) {
for (j in 0 until 4) { for (j in 0 until 4) {
transposedMatrix[i][j] = state[j][i] transposedMatrix[i][j] = state[j][i]
@ -282,10 +284,10 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
} }
state.forEach { clearArray(it) } state.forEach { clearArray(it) }
completed = true completed = true
return transposedMatrix.flatten().toTypedArray() return transposedMatrix.flattenToUByteArray()
} }
fun decrypt(): Array<UByte> { fun decrypt(): UByteArray {
if (completed) { if (completed) {
throw RuntimeException("Decrypt can only be called once per Aes instance, since the state is cleared at the " + throw RuntimeException("Decrypt can only be called once per Aes instance, since the state is cleared at the " +
"end of the operation") "end of the operation")
@ -313,20 +315,19 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
printState() printState()
val transposedMatrix = (0 until 4).map { outerCounter -> val transposedMatrix = (0 until 4).map { outerCounter ->
Array<UByte>(4) { 0U } UByteArray(4) { 0U }
}.toTypedArray() }
for (i in 0 until 4) { for (i in 0 until 4) {
for (j in 0 until 4) { for (j in 0 until 4) {
transposedMatrix[i][j] = state[j][i] transposedMatrix[i][j] = state[j][i]
} }
} }
printState(transposedMatrix)
state.forEach { clearArray(it) } state.forEach { clearArray(it) }
completed = true completed = true
return transposedMatrix.flatten().toTypedArray() return transposedMatrix.flattenToUByteArray()
} }
private fun clearArray(array : Array<UByte>) { private fun clearArray(array : UByteArray) {
array.indices.forEach { array[it] = 0U } array.indices.forEach { array[it] = 0U }
} }
@ -342,7 +343,7 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
} }
} }
private fun printState(specific : Array<Array<UByte>>) { private fun printState(specific : List<UByteArray>) {
if (!debug) { if (!debug) {
return return
} }
@ -356,7 +357,7 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
} }
sealed class AesKey(val key: String, val keyLength: Int) { sealed class AesKey(val key: String, val keyLength: Int) {
val keyArray: Array<UByte> = key.chunked(2).map { it.toUByte(16) }.toTypedArray() val keyArray: UByteArray = key.chunked(2).map { it.toUByte(16) }.toUByteArray()
val numberOf32BitWords = keyLength / 32 val numberOf32BitWords = keyLength / 32
class Aes128Key(key: String) : AesKey(key, 128) class Aes128Key(key: String) : AesKey(key, 128)

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.symmetric
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 18-Sep-2019
*/
enum class Mode {
ENCRYPT, DECRYPT
}

View File

@ -0,0 +1,262 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("EXPERIMENTAL_API_USAGE")
package com.ionspin.kotlin.crypto.util
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 15-Jul-2019
*/
fun Array<Byte>.hexColumsPrint() {
val printout = this.map { it.toString(16) }.chunked(16)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun Array<UByte>.hexColumsPrint(chunk : Int = 16) {
val printout = this.map { it.toString(16).padStart(2, '0') }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun UByteArray.hexColumsPrint(chunk : Int = 16) {
val printout = this.map { it.toString(16).padStart(2, '0') }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun Array<ULong>.hexColumsPrint(chunk: Int = 3) {
val printout = this.map { it.toString(16) }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
inline fun <reified T> Array<T>.chunked(sliceSize: Int): Array<Array<T>> {
val last = this.size % sliceSize
val hasLast = last != 0
val numberOfSlices = this.size / sliceSize
val result : MutableList<List<T>> = MutableList<List<T>>(0) { emptyList() }
for (i in 0 until numberOfSlices) {
result.add(this.slice(i * sliceSize until (i + 1) * sliceSize))
}
if (hasLast) {
result.add(this.slice(numberOfSlices * sliceSize until this.size))
}
return result.map { it.toTypedArray() }.toTypedArray()
}
infix fun UInt.rotateRight(places: Int): UInt {
return (this shr places) xor (this shl (32 - places))
}
infix fun ULong.rotateRight(places: Int): ULong {
return (this shr places) xor (this shl (64 - places))
}
infix fun Array<UByte>.xor(other : Array<UByte>) : Array<UByte> {
if (this.size != other.size) {
throw RuntimeException("Operands of different sizes are not supported yet")
}
return Array(this.size) { this[it] xor other[it] }
}
infix fun UByteArray.xor(other : UByteArray) : UByteArray {
if (this.size != other.size) {
throw RuntimeException("Operands of different sizes are not supported yet")
}
return UByteArray(this.size) { this[it] xor other[it] }
}
fun String.hexStringToTypedUByteArray() : Array<UByte> {
return this.chunked(2).map { it.toUByte(16) }.toTypedArray()
}
fun String.hexStringToUByteArray() : UByteArray {
return this.chunked(2).map { it.toUByte(16) }.toUByteArray()
}
fun Array<UByte>.toHexString() : String {
return this.joinToString(separator = "") {
if (it <= 0x0FU) {
"0${it.toString(16)}"
} else {
it.toString(16)
}
}
}
fun UByteArray.toHexString() : String {
return this.joinToString(separator = "") {
if (it <= 0x0FU) {
"0${it.toString(16)}"
} else {
it.toString(16)
}
}
}
// UInt / Array utils
fun UInt.toBigEndianUByteArray() : Array<UByte> {
return Array<UByte> (4) {
((this shr (24 - (it * 8))) and 0xFFU).toUByte()
}
}
fun UInt.toLittleEndianTypedUByteArray() : Array<UByte> {
return Array<UByte> (4) {
((this shr (it * 8)) and 0xFFU).toUByte()
}
}
fun UInt.toLittleEndianUByteArray() : UByteArray {
return UByteArray (4) {
((this shr (it * 8)) and 0xFFU).toUByte()
}
}
// UInt / Array utils
fun ULong.toBigEndianUByteArray() : Array<UByte> {
return Array<UByte> (8) {
((this shr (56 - (it * 8))) and 0xFFU).toUByte()
}
}
fun ULong.toLittleEndianTypedUByteArray() : Array<UByte> {
return Array<UByte> (8) {
((this shr (it * 8)) and 0xFFU).toUByte()
}
}
fun ULong.toLittleEndianUByteArray() :UByteArray {
return UByteArray (8) {
((this shr (it * 8)) and 0xFFU).toUByte()
}
}
fun Array<UByte>.fromLittleEndianArrayToULong() : ULong {
if (this.size > 8) {
throw RuntimeException("ore than 8 bytes in input, potential overflow")
}
var ulong = this.foldIndexed(0UL) { index, acc, uByte -> acc or (uByte.toULong() shl (index * 8))}
return ulong
}
fun UByteArray.fromLittleEndianArrayToULong() : ULong {
if (this.size > 8) {
throw RuntimeException("ore than 8 bytes in input, potential overflow")
}
var ulong = this.foldIndexed(0UL) { index, acc, uByte -> acc or (uByte.toULong() shl (index * 8))}
return ulong
}
fun UByteArray.arrayChunked(sliceSize: Int): List<UByteArray> {
val last = this.size % sliceSize
val hasLast = last != 0
val numberOfSlices = this.size / sliceSize
val result : MutableList<UByteArray> = MutableList<UByteArray>(0) { ubyteArrayOf() }
for (i in 0 until numberOfSlices) {
result.add(this.sliceArray(i * sliceSize until (i + 1) * sliceSize))
}
if (hasLast) {
result.add(this.sliceArray(numberOfSlices * sliceSize until this.size))
}
return result
}
fun Array<UByte>.fromBigEndianArrayToULong() : ULong {
if (this.size > 8) {
throw RuntimeException("ore than 8 bytes in input, potential overflow")
}
var ulong = this.foldIndexed(0UL) {
index, acc, uByte ->
val res = acc or (uByte.toULong() shl (56 - (index * 8)))
res
}
return ulong
}
fun Array<UByte>.fromLittleEndianArrayToUInt() : UInt {
if (this.size > 4) {
throw RuntimeException("ore than 8 bytes in input, potential overflow")
}
var uint = this.foldIndexed(0U) { index, acc, uByte -> acc or (uByte.toUInt() shl (index * 8))}
return uint
}
fun UByteArray.fromLittleEndianArrayToUInt() : UInt {
if (this.size > 4) {
throw RuntimeException("ore than 8 bytes in input, potential overflow")
}
var uint = this.foldIndexed(0U) { index, acc, uByte -> acc or (uByte.toUInt() shl (index * 8))}
return uint
}
fun Array<UByte>.fromBigEndianArrayToUInt() : UInt {
if (this.size > 4) {
throw RuntimeException("ore than 8 bytes in input, potential overflow")
}
var uint = this.foldIndexed(0U) { index, acc, uByte -> acc or (uByte.toUInt() shl (24 - (index * 8))) }
return uint
}
operator fun UInt.plus(other : UByteArray) : UByteArray {
return this.toLittleEndianUByteArray() + other
}
//AES Flatten
fun Collection<UByteArray>.flattenToUByteArray(): UByteArray {
val result = UByteArray(sumBy { it.size })
var position = 0
for (element in this) {
element.forEach { uByte ->
result[position] = uByte
position++
}
}
return result
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto
import com.ionspin.kotlin.crypto.util.testBlocking
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
class SRNGTest {
@Test
fun testSrng() = testBlocking {
Crypto.initialize()
//Just a sanity test, need to add better srng tests.
val randomBytes1 = SRNG.getRandomBytes(10)
val randomBytes2 = SRNG.getRandomBytes(10)
assertTrue { !randomBytes1.contentEquals(randomBytes2) }
}
}

View File

@ -0,0 +1,45 @@
package com.ionspin.kotlin.crypto.hash.blake2b
import com.ionspin.kotlin.crypto.Crypto
import com.ionspin.kotlin.crypto.Initializer
import com.ionspin.kotlin.crypto.hash.encodeToUByteArray
import com.ionspin.kotlin.crypto.util.testBlocking
import com.ionspin.kotlin.crypto.util.toHexString
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 09-Jun-2020
*/
class Blake2bTest {
@Test
fun statelessSimpleTest() = testBlocking {
Initializer.initialize()
val expected = "a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a89" +
"6c0411f38312e1d66e0bf16386c86a89bea572"
val result = Crypto.Blake2b.stateless("test".encodeToUByteArray()).toHexString()
// println("Result: $result")
assertTrue { result == expected }
}
//This is a bad test since it's not larger than one block
//but for now I'm testing that the platform library is being correctly called
@Test
fun updateableSimpleTest() = testBlocking {
Initializer.initialize()
val expected = "a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a89" +
"6c0411f38312e1d66e0bf16386c86a89bea572"
val blake2b = Crypto.Blake2b.updateable()
blake2b.update("t".encodeToUByteArray())
blake2b.update(("est".encodeToUByteArray()))
val result = blake2b.digest().toHexString()
// println("Result: $result")
assertTrue { result == expected }
}
}

View File

@ -0,0 +1,43 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.Crypto
import com.ionspin.kotlin.crypto.Initializer
import com.ionspin.kotlin.crypto.hash.encodeToUByteArray
import com.ionspin.kotlin.crypto.util.testBlocking
import com.ionspin.kotlin.crypto.util.toHexString
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 07-Jun-2020
*/
class Sha256Test {
@BeforeTest
fun beforeTest() = testBlocking {
Initializer.initialize()
}
@Test
fun statelessSimpleTest() {
val expected = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
val result = Crypto.Sha256.stateless("test".encodeToUByteArray()).toHexString()
// println("Result: $result")
assertTrue { result == expected }
}
//This is a bad test since it's not larger than one block
//but for now I'm testing that the platform library is being correctly called
@Test
fun updateableSimpleTest() {
val expected = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
val sha256 = Crypto.Sha256.updateable()
sha256.update("t".encodeToUByteArray())
sha256.update(("est".encodeToUByteArray()))
val result = sha256.digest().toHexString()
// println("Result: $result")
assertTrue { result == expected }
}
}

View File

@ -0,0 +1,45 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.Crypto
import com.ionspin.kotlin.crypto.Initializer
import com.ionspin.kotlin.crypto.hash.encodeToUByteArray
import com.ionspin.kotlin.crypto.util.testBlocking
import com.ionspin.kotlin.crypto.util.toHexString
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 07-Jun-2020
*/
class Sha512Test {
@BeforeTest
fun beforeTest() = testBlocking {
Initializer.initialize()
}
@Test
fun statelessSimpleTest() {
val expected = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67" +
"b143732c304cc5fa9ad8e6f57f50028a8ff"
val result = Crypto.Sha512.stateless("test".encodeToUByteArray()).toHexString()
// println("Result: $result")
assertTrue { result == expected }
}
//This is a bad test since it's not larger than one block
//but for now I'm testing that the platform library is being correctly called
@Test
fun updateableSimpleTest() {
val expected = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67" +
"b143732c304cc5fa9ad8e6f57f50028a8ff"
val sha512 = Crypto.Sha512.updateable()
sha512.update("t".encodeToUByteArray())
sha512.update(("est".encodeToUByteArray()))
val result = sha512.digest().toHexString()
// println("Result: $result")
assertTrue { result == expected }
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.util
import kotlinx.coroutines.CoroutineScope
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 20-Jul-2019
*/
expect fun testBlocking(block : suspend () -> Unit)

View File

@ -0,0 +1,43 @@
package com.ionspin.kotlin.crypto
import ext.libsodium.com.ionspin.kotlin.crypto.JsSodiumInterface
import ext.libsodium.com.ionspin.kotlin.crypto.JsSodiumLoader
/* 1.4-M1 has some weirdness with static/objects, or I'm misusing something, not sure */
lateinit var sodiumPointer : JsSodiumInterface
var sodiumLoaded: Boolean = false
fun getSodium() : JsSodiumInterface = sodiumPointer
//fun getSodiumAdvanced() : JsSodiumAdvancedInterface = js("sodiumPointer.libsodium")
fun setSodiumPointer(jsSodiumInterface: JsSodiumInterface) {
js("sodiumPointer = jsSodiumInterface")
}
fun getSodiumLoaded() : Boolean = sodiumLoaded
fun setSodiumLoaded(loaded: Boolean) {
js("sodiumLoaded = loaded")
}
actual object Initializer {
private var isPlatformInitialized = false
actual suspend fun initialize() {
JsSodiumLoader.load()
isPlatformInitialized = true
}
actual fun initializeWithCallback(done: () -> Unit) {
JsSodiumLoader.loadWithCallback {
isPlatformInitialized = true
done()
}
}
actual fun isInitialized(): Boolean {
return isPlatformInitialized
}
}

View File

@ -0,0 +1,43 @@
package ext.libsodium.com.ionspin.kotlin.crypto
import org.khronos.webgl.Uint8Array
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 27-May-2020
*/
interface JsSodiumInterface {
fun randombytes_buf(numberOfBytes: Int): Uint8Array
fun crypto_generichash(hashLength: Int, inputMessage: Uint8Array, key: Uint8Array,): Uint8Array
fun crypto_hash_sha256(message: Uint8Array): Uint8Array
fun crypto_hash_sha512(message: Uint8Array): Uint8Array
//Updateable
fun crypto_generichash_init(key : Uint8Array, hashLength: Int) : dynamic
fun crypto_generichash_update(state: dynamic, inputMessage: Uint8Array)
fun crypto_generichash_final(state: dynamic, hashLength: Int) : Uint8Array
fun crypto_hash_sha256_init() : dynamic
fun crypto_hash_sha256_update(state: dynamic, message: Uint8Array)
fun crypto_hash_sha256_final(state: dynamic): Uint8Array
fun crypto_hash_sha512_init() : dynamic
fun crypto_hash_sha512_update(state: dynamic, message: Uint8Array)
fun crypto_hash_sha512_final(state: dynamic): Uint8Array
}

View File

@ -0,0 +1,56 @@
package ext.libsodium.com.ionspin.kotlin.crypto
import com.ionspin.kotlin.crypto.getSodiumLoaded
import com.ionspin.kotlin.crypto.setSodiumPointer
import com.ionspin.kotlin.crypto.sodiumLoaded
import ext.libsodium.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.suspendCoroutine
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 27-May-2020
*/
object JsSodiumLoader {
class _EmitJsSodiumFunction {
init {
println(::crypto_generichash)
println(::crypto_hash_sha256)
println(::crypto_hash_sha512)
println(::crypto_hash_sha256_init)
}
}
fun storeSodium(promisedSodium: dynamic, continuation: Continuation<Unit>) {
setSodiumPointer(promisedSodium)
sodiumLoaded = true
continuation.resumeWith(Result.success(Unit))
}
suspend fun load() = suspendCoroutine<Unit> { continuation ->
console.log(getSodiumLoaded())
if (!getSodiumLoaded()) {
val libsodiumModule = js("\$module\$libsodium_wrappers_sumo")
_libsodiumPromise.then<dynamic> { storeSodium(libsodiumModule, continuation) }
} else {
continuation.resumeWith(Result.success(Unit))
}
}
fun loadWithCallback(doneCallback: () -> (Unit)) {
console.log(getSodiumLoaded())
if (!getSodiumLoaded()) {
val libsodiumModule = js("\$module\$libsodium_wrappers_sumo")
_libsodiumPromise.then<dynamic> {
setSodiumPointer(libsodiumModule)
sodiumLoaded = true
doneCallback.invoke()
}
} else {
doneCallback.invoke()
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto
import org.khronos.webgl.get
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
actual object SRNG {
var counter = 0
actual fun getRandomBytes(amount: Int): UByteArray {
val randomBytes = getSodium().randombytes_buf(amount)
val randomBytesUByteArray = UByteArray(amount) {
0U
}
for (i in 0 until amount) {
js("""
randomBytesUByteArray[i] = randomBytes[i]
""")
}
return randomBytesUByteArray
}
}

View File

@ -0,0 +1,62 @@
package com.ionspin.kotlin.crypto.hash.blake2b
import com.ionspin.kotlin.crypto.getSodium
import com.ionspin.kotlin.crypto.hash.sha.Sha256StatelessDelegated
import com.ionspin.kotlin.crypto.util.toHexString
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jul-2019
*/
actual class Blake2bDelegated actual constructor(key: UByteArray?, val hashLength: Int) : Blake2b {
override val MAX_HASH_BYTES: Int = 64
val state : dynamic
init {
state = getSodium().crypto_generichash_init(
Uint8Array(key?.toByteArray()?.toTypedArray() ?: arrayOf()),
hashLength
)
}
override fun update(data: UByteArray) {
getSodium().crypto_generichash_update(state, Uint8Array(data.toByteArray().toTypedArray()))
}
override fun digest(): UByteArray {
val hashed = getSodium().crypto_generichash_final(state, hashLength)
val hash = UByteArray(hashLength)
for (i in 0 until hashLength) {
hash[i] = hashed[i].toUByte()
}
return hash
}
}
actual object Blake2bDelegatedStateless : Blake2bStateless {
override val MAX_HASH_BYTES: Int = 64
override fun digest(inputMessage: UByteArray, key: UByteArray, hashLength: Int): UByteArray {
val hashed = getSodium().crypto_generichash(hashLength,
Uint8Array(inputMessage.toByteArray().toTypedArray()),
Uint8Array(key.toByteArray().toTypedArray())
)
val hash = UByteArray(hashLength)
for (i in 0 until hashLength) {
hash[i] = hashed[i].toUByte()
}
return hash
}
}

View File

@ -0,0 +1,49 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.getSodium
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
actual class Sha256Delegated : Sha256 {
val state : dynamic
init {
state = getSodium().crypto_hash_sha256_init()
}
override fun update(data: UByteArray) {
getSodium().crypto_hash_sha256_update(state, Uint8Array(data.toByteArray().toTypedArray()))
}
override fun digest(): UByteArray {
val hashed = getSodium().crypto_hash_sha256_final(state)
val hash = UByteArray(Sha256StatelessDelegated.MAX_HASH_BYTES)
console.log(hashed)
for (i in 0 until Sha256StatelessDelegated.MAX_HASH_BYTES) {
hash[i] = hashed[i].toUByte()
}
return hash
}
}
actual object Sha256StatelessDelegated : StatelessSha256 {
override fun digest(inputMessage: UByteArray): UByteArray {
val hashed = getSodium().crypto_hash_sha256(Uint8Array(inputMessage.toByteArray().toTypedArray()))
val hash = UByteArray(MAX_HASH_BYTES)
for (i in 0 until MAX_HASH_BYTES) {
hash[i] = hashed[i].toUByte()
}
return hash
}
}

View File

@ -0,0 +1,48 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.getSodium
import com.ionspin.kotlin.crypto.getSodium
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
actual class Sha512Delegated : Sha512 {
val state : dynamic
init {
state = getSodium().crypto_hash_sha512_init()
}
override fun update(data: UByteArray) {
getSodium().crypto_hash_sha512_update(state, Uint8Array(data.toByteArray().toTypedArray()))
}
override fun digest(): UByteArray {
val hashed = getSodium().crypto_hash_sha512_final(state)
val hash = UByteArray(Sha512StatelessDelegated.MAX_HASH_BYTES)
for (i in 0 until Sha512StatelessDelegated.MAX_HASH_BYTES) {
hash[i] = hashed[i].toUByte()
}
return hash
}
}
actual object Sha512StatelessDelegated : StatelessSha512 {
override fun digest(inputMessage: UByteArray): UByteArray {
val hashed = getSodium().crypto_hash_sha512(Uint8Array(inputMessage.toByteArray().toTypedArray()))
val hash = UByteArray(Sha512StatelessDelegated.MAX_HASH_BYTES)
for (i in 0 until Sha512StatelessDelegated.MAX_HASH_BYTES) {
hash[i] = hashed[i].toUByte()
}
return hash
}
}

View File

@ -0,0 +1,28 @@
@file:JsModule("libsodium-wrappers-sumo")
@file:JsNonModule
package ext.libsodium
import org.khronos.webgl.Uint8Array
import kotlin.js.Promise
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 25-May-2020
*/
@JsName("ready")
external val _libsodiumPromise : Promise<dynamic>
external fun crypto_generichash(hashLength: Int, inputMessage: Uint8Array) : Uint8Array
external fun crypto_hash_sha256(message: Uint8Array) : Uint8Array
external fun crypto_hash_sha512(message: Uint8Array) : Uint8Array
external fun crypto_hash_sha256_init(): dynamic

View File

@ -0,0 +1,52 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto
import com.ionspin.kotlin.crypto.util.testBlocking
import ext.libsodium.com.ionspin.kotlin.crypto.JsSodiumLoader
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 05-Jan-2020
*/
class SRNGJsTest {
@Test
fun testJsSrng() = testBlocking {
JsSodiumLoader.load()
val bytes1 = SRNG.getRandomBytes(10)
val bytes2 = SRNG.getRandomBytes(10)
// println("BYTES1\n")
// bytes1.forEach {
// print(it.toString(16).padStart(2, '0'))
// }
// println("BYTES2\n")
// bytes2.forEach {
// print(it.toString(16).padStart(2, '0'))
// }
assertTrue {
!bytes1.contentEquals(bytes2) &&
bytes1.size == 10 &&
bytes2.size == 10
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.util
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.promise
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 20-Jul-2019
*/
actual fun testBlocking(block: suspend ()-> Unit) : dynamic = GlobalScope.promise { block() }

View File

@ -0,0 +1,25 @@
package com.ionspin.kotlin.crypto
import com.goterl.lazycode.lazysodium.SodiumJava
actual object Initializer {
private var isPlatformInitialized = false
lateinit var sodium : SodiumJava
actual suspend fun initialize() {
sodium = SodiumJava()
isPlatformInitialized = true
}
actual fun initializeWithCallback(done: () -> Unit) {
sodium = SodiumJava()
isPlatformInitialized = true
done()
}
actual fun isInitialized(): Boolean {
return isPlatformInitialized
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto
import java.security.SecureRandom
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
actual object SRNG {
val secureRandom = SecureRandom()
actual fun getRandomBytes(amount: Int): UByteArray {
val byteArray = ByteArray(amount)
secureRandom.nextBytes(byteArray)
return byteArray.toUByteArray()
}
}

View File

@ -0,0 +1,40 @@
package com.ionspin.kotlin.crypto.hash.blake2b
import com.ionspin.kotlin.crypto.Initializer.sodium
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jul-2019
*/
actual class Blake2bDelegated actual constructor(key: UByteArray?, val hashLength: Int) : Blake2b {
val state = ByteArray(sodium.crypto_generichash_statebytes())
init {
sodium.crypto_generichash_init(state,key?.toByteArray() ?: byteArrayOf(), key?.size ?: 0, hashLength)
}
override fun update(data: UByteArray) {
sodium.crypto_generichash_update(state, data.toByteArray(), data.size.toLong())
}
override fun digest(): UByteArray {
val hashed = ByteArray(hashLength)
sodium.crypto_generichash_final(state, hashed, hashLength)
return hashed.toUByteArray()
}
}
actual object Blake2bDelegatedStateless : Blake2bStateless {
override fun digest(inputMessage: UByteArray, key: UByteArray, hashLength: Int): UByteArray {
val hashed = ByteArray(hashLength)
sodium.crypto_generichash(hashed, hashed.size, inputMessage.toByteArray(), inputMessage.size.toLong(), key.toByteArray(), key.size)
return hashed.toUByteArray()
}
}

View File

@ -0,0 +1,41 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.goterl.lazycode.lazysodium.interfaces.Hash
import com.ionspin.kotlin.crypto.Initializer.sodium
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
actual class Sha256Delegated actual constructor() : Sha256 {
val state = Hash.State256()
init {
sodium.crypto_hash_sha256_init(state)
}
override fun update(data: UByteArray) {
sodium.crypto_hash_sha256_update(state, data.toByteArray(), data.size.toLong())
}
override fun digest(): UByteArray {
val hashed = ByteArray(Sha256Properties.MAX_HASH_BYTES)
sodium.crypto_hash_sha256_final(state, hashed)
return hashed.toUByteArray()
}
}
actual object Sha256StatelessDelegated : StatelessSha256 {
override fun digest(inputMessage: UByteArray): UByteArray {
val hashed = ByteArray(Sha256Properties.MAX_HASH_BYTES)
sodium.crypto_hash_sha256(hashed, inputMessage.toByteArray(), inputMessage.size.toLong())
return hashed.toUByteArray()
}
}

View File

@ -0,0 +1,40 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.goterl.lazycode.lazysodium.interfaces.Hash
import com.ionspin.kotlin.crypto.Initializer
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
actual class Sha512Delegated : Sha512 {
val state = Hash.State512()
init {
Initializer.sodium.crypto_hash_sha512_init(state)
}
override fun update(data: UByteArray) {
Initializer.sodium.crypto_hash_sha512_update(state, data.toByteArray(), data.size.toLong())
}
override fun digest(): UByteArray {
val hashed = ByteArray(Sha512Properties.MAX_HASH_BYTES)
Initializer.sodium.crypto_hash_sha512_final(state, hashed)
return hashed.toUByteArray()
}
}
actual object Sha512StatelessDelegated : StatelessSha512 {
override fun digest(inputMessage: UByteArray): UByteArray {
val hashed = ByteArray(Sha512Properties.MAX_HASH_BYTES)
Initializer.sodium.crypto_hash_sha512(hashed, inputMessage.toByteArray(), inputMessage.size.toLong())
return hashed.toUByteArray()
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 20-Jul-2019
*/
actual fun testBlocking(block: suspend () -> Unit) = runBlocking { block() }

View File

@ -0,0 +1,9 @@
package com.ionspin.kotlin.crypto.hash.sha
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 07-Jun-2020
*/
import platform.posix.*
//import cin

View File

@ -0,0 +1,44 @@
//We'll handle SRNG through libsodium
///*
// * Copyright 2019 Ugljesa Jovanovic
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
//package com.ionspin.kotlin.crypto
//
//import kotlinx.cinterop.*
//import platform.windows.*
//
///**
// * Created by Ugljesa Jovanovic
// * ugljesa.jovanovic@ionspin.com
// * on 21-Sep-2019
// */
//actual object SRNG {
// private val advapi by lazy { LoadLibraryA("ADVAPI32.DLL")}
//
// private val advapiRandom by lazy {
// GetProcAddress(advapi, "SystemFunction036")?.reinterpret<CFunction<Function2<CPointer<ByteVar>, ULong, Int>>>() ?: error("Failed getting advapi random")
// }
//
// @Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
// actual fun getRandomBytes(amount: Int): UByteArray {
// memScoped {
// val randArray = allocArray<ByteVar>(amount)
// val pointer = randArray.getPointer(this)
// val status = advapiRandom(pointer.reinterpret(), amount.convert())
// return UByteArray(amount) { pointer[it].toUByte() }
// }
// }
//}

View File

@ -0,0 +1,6 @@
headers = sodium.h
headerFilter = sodium.h sodium/**
#staticLibraries = libsodium.a
#libraryPaths = sodiumWrapper/lib
#compilerOpts = -I./sodiumWrapper/include
linkerOpts =

View File

@ -0,0 +1,32 @@
package com.ionspin.kotlin.crypto
import kotlinx.atomicfu.AtomicBoolean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import libsodium.sodium_init
import kotlin.native.concurrent.AtomicInt
actual object Initializer {
private var isPlatformInitialized : AtomicInt = AtomicInt(0)
actual suspend fun initialize() {
if (isPlatformInitialized.compareAndSet(0, 1)) {
sodium_init()
}
}
actual fun initializeWithCallback(done: () -> Unit) {
if (isPlatformInitialized.compareAndSet(0, 1)) {
sodium_init()
}
done()
}
actual fun isInitialized(): Boolean {
return isPlatformInitialized.value != 0
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto
import kotlinx.cinterop.*
import libsodium.randombytes_buf
import platform.posix.*
//import libsod
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
actual object SRNG {
@Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
actual fun getRandomBytes(amount: Int): UByteArray {
memScoped {
val array = allocArray<UByteVar>(amount)
randombytes_buf(array, amount.convert())
return UByteArray(amount) {
array[it]
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.ionspin.kotlin.crypto.hash.blake2b
import com.ionspin.kotlin.crypto.util.toHexString
import kotlinx.cinterop.*
import libsodium.*
import platform.posix.free
import platform.posix.malloc
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jul-2019
*/
actual class Blake2bDelegated actual constructor(key: UByteArray?, hashLength: Int) : Blake2b {
override val MAX_HASH_BYTES: Int = 64
val requestedHashLength : Int
val state : crypto_generichash_state
init {
requestedHashLength = hashLength
val allocated = malloc(crypto_generichash_state.size.convert())!!
state = allocated.reinterpret<crypto_generichash_state>().pointed
crypto_generichash_init(state.ptr, key?.run { this.toUByteArray().toCValues() }, key?.size?.convert() ?: 0UL.convert(), hashLength.convert())
}
override fun update(data: UByteArray) {
crypto_generichash_update(state.ptr, data.toCValues(), data.size.convert())
}
override fun digest(): UByteArray {
val hashResult = UByteArray(requestedHashLength)
val hashResultPinned = hashResult.pin()
crypto_generichash_final(state.ptr, hashResultPinned.addressOf(0), requestedHashLength.convert())
free(state.ptr)
return hashResult
}
}
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
actual object Blake2bDelegatedStateless : Blake2bStateless {
override fun digest(inputMessage: UByteArray, key: UByteArray, hashLength: Int): UByteArray {
val hashResult = UByteArray(MAX_HASH_BYTES)
val hashResultPinned = hashResult.pin()
crypto_generichash(
hashResultPinned.addressOf(0),
hashLength.convert(),
inputMessage.toCValues(),
inputMessage.size.convert(),
key.toCValues(),
key.size.convert()
)
hashResultPinned.unpin()
return hashResult
}
}

View File

@ -0,0 +1,54 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bDelegatedStateless
import kotlinx.cinterop.*
import libsodium.*
import platform.posix.free
import platform.posix.malloc
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
actual class Sha256Delegated : Sha256 {
val state : crypto_hash_sha256_state
init {
val allocated = malloc(crypto_hash_sha256_state.size.convert())!!
state = allocated.reinterpret<crypto_hash_sha256_state>().pointed
crypto_hash_sha256_init(state.ptr)
}
override fun update(data: UByteArray) {
crypto_hash_sha256_update(state.ptr, data.toCValues(), data.size.convert())
}
override fun digest(): UByteArray {
val hashResult = UByteArray(Sha256Properties.MAX_HASH_BYTES)
val hashResultPinned = hashResult.pin()
crypto_hash_sha256_final(state.ptr, hashResultPinned.addressOf(0))
free(state.ptr)
return hashResult
}
}
actual object Sha256StatelessDelegated : StatelessSha256 {
override fun digest(inputMessage: UByteArray): UByteArray {
val hashResult = UByteArray(MAX_HASH_BYTES)
val hashResultPinned = hashResult.pin()
crypto_hash_sha256(hashResultPinned.addressOf(0), inputMessage.toCValues(), inputMessage.size.convert())
hashResultPinned.unpin()
return hashResult
}
}

View File

@ -0,0 +1,49 @@
package com.ionspin.kotlin.crypto.hash.sha
import kotlinx.cinterop.*
import libsodium.*
import platform.posix.free
import platform.posix.malloc
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
actual class Sha512Delegated : Sha512 {
val state : crypto_hash_sha512_state
init {
val allocated = malloc(crypto_hash_sha512_state.size.convert())!!
state = allocated.reinterpret<crypto_hash_sha512_state>().pointed
crypto_hash_sha512_init(state.ptr)
}
override fun update(data: UByteArray) {
crypto_hash_sha512_update(state.ptr, data.toCValues(), data.size.convert())
}
override fun digest(): UByteArray {
val hashResult = UByteArray(Sha512Properties.MAX_HASH_BYTES)
val hashResultPinned = hashResult.pin()
crypto_hash_sha512_final(state.ptr, hashResultPinned.addressOf(0))
free(state.ptr)
return hashResult
}
}
actual object Sha512StatelessDelegated : StatelessSha512 {
override fun digest(inputMessage: UByteArray): UByteArray {
val hashResult = UByteArray(Sha512StatelessDelegated.MAX_HASH_BYTES)
val hashResultPinned = hashResult.pin()
crypto_hash_sha512(hashResultPinned.addressOf(0), inputMessage.toCValues(), inputMessage.size.convert())
hashResultPinned.unpin()
return hashResult
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 20-Jul-2019
*/
actual fun testBlocking(block: suspend () -> Unit) = runBlocking { block() }

View File

@ -16,12 +16,8 @@
*/ */
@file:Suppress("UnstableApiUsage") @file:Suppress("UnstableApiUsage")
import com.moowork.gradle.node.task.NodeTask
import org.gradle.api.tasks.testing.logging.TestLogging
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
plugins { plugins {
kotlin(PluginsDeps.multiplatform) kotlin(PluginsDeps.multiplatform)
@ -46,53 +42,29 @@ repositories {
jcenter() jcenter()
} }
group = "com.ionspin.kotlin" group = ReleaseInfo.group
version = "0.0.4-SNAPSHOT" version = ReleaseInfo.version
val ideaActive = System.getProperty("idea.active") == "true" val ideaActive = System.getProperty("idea.active") == "true"
fun getHostOsName(): String {
val target = System.getProperty("os.name")
if (target == "Linux") return "linux"
if (target.startsWith("Windows")) return "windows"
if (target.startsWith("Mac")) return "macos"
return "unknown"
}
kotlin { kotlin {
val hostOsName = getHostOsName() val hostOsName = getHostOsName()
if (ideaActive) { runningOnLinuxx86_64 {
when(hostOsName) {
"linux" -> linuxX64("native")
"macos" -> macosX64("native")
"windows" -> mingwX64("native")
}
}
if (hostOsName == "linux") {
jvm() jvm()
js { js {
compilations {
this.forEach {
it.compileKotlinTask.kotlinOptions.sourceMap = true
it.compileKotlinTask.kotlinOptions.moduleKind = "commonjs"
it.compileKotlinTask.kotlinOptions.metaInfo = true
if (it.name == "main") { browser {
it.compileKotlinTask.kotlinOptions.main = "call"
} testTask {
println("Compilation name ${it.name} set") // isRunningInTravis {
println("Destination dir ${it.compileKotlinTask.destinationDir}") enabled = false //Until I sort out testing on travis, and figure out how to increase karma timeout
// }
useKarma {
useChrome()
}
} }
} }
//Until I figure out how to run headless chrome on travis
// browser {
//
// testTask {
// useKarma {
// useChrome()
// }
// }
// }
nodejs { nodejs {
testTask { testTask {
useMocha() { useMocha() {
@ -105,39 +77,41 @@ kotlin {
linuxX64("linux") { linuxX64("linux") {
binaries { binaries {
staticLib { staticLib {
optimized = true
}
}
}
linuxArm64() {
binaries {
staticLib {
} }
} }
} }
//Not supported in coroutines at the moment
// linuxArm32Hfp() { linuxArm32Hfp() {
// binaries { binaries {
// staticLib { staticLib {
// } }
// } }
// } }
//Not supported in coroutines at the moment
// linuxArm64() {
// binaries {
// staticLib {
// }
// }
// }
} }
if (hostOsName == "macos") { runningOnMacos {
iosX64("ios") { iosX64("ios") {
binaries { binaries {
framework { framework {
optimized = true
} }
} }
} }
iosArm64("ios64Arm") { iosArm64("ios64Arm") {
binaries { binaries {
framework { framework {
optimized = true
} }
} }
} }
@ -145,24 +119,24 @@ kotlin {
iosArm32("ios32Arm") { iosArm32("ios32Arm") {
binaries { binaries {
framework { framework {
optimized = true
} }
} }
} }
macosX64() { macosX64() {
binaries { binaries {
framework { framework {
optimized = true
} }
} }
} }
} }
if (hostOsName == "windows") { runningOnWindows {
mingwX64() { mingwX64() {
binaries { binaries {
staticLib { staticLib {
optimized = true
} }
} }
} }
@ -186,6 +160,7 @@ kotlin {
implementation(kotlin(Deps.Common.test)) implementation(kotlin(Deps.Common.test))
implementation(Deps.Common.coroutines) implementation(Deps.Common.coroutines)
implementation(Deps.Common.kotlinBigNum) implementation(Deps.Common.kotlinBigNum)
implementation(project(Deps.Common.apiProject))
} }
} }
val commonTest by getting { val commonTest by getting {
@ -195,42 +170,49 @@ kotlin {
} }
} }
val nativeMain = if (ideaActive) {
val nativeMain by getting {
dependsOn(commonMain)
dependencies {
implementation(Deps.Native.coroutines)
}
}
nativeMain
} else {
val nativeMain by creating { val nativeMain by creating {
dependsOn(commonMain) dependsOn(commonMain)
dependencies { dependencies {
implementation(Deps.Native.coroutines) implementation(Deps.Native.coroutines)
} }
} isRunningInIdea {
nativeMain kotlin.setSrcDirs(emptySet<String>())
}
val nativeTest = if (ideaActive) {
val nativeTest by getting {
dependsOn(commonTest)
dependencies {
implementation(Deps.Native.coroutines)
} }
} }
nativeTest
} else {
val nativeTest by creating { val nativeTest by creating {
dependsOn(commonTest) dependsOn(commonTest)
dependencies { dependencies {
implementation(Deps.Native.coroutines) implementation(Deps.Native.coroutines)
} }
} }
nativeTest
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
compilations.getByName("main") {
println("Setting native sourceset dependancy for $name")
if ((this@withType.name.contains("ios") ||
this@withType.name.contains("mingw")).not()
) {
println("Setting native sourceset dependancy for $this@withType.name")
defaultSourceSet.dependsOn(nativeMain)
}
}
compilations.getByName("test") {
println("Setting native sourceset dependancy for $name")
if ((this@withType.name.contains("ios") ||
this@withType.name.contains("mingw")).not()
) {
println("Setting native sourceset dependancy for $this@withType.name")
defaultSourceSet.dependsOn(nativeTest)
}
}
} }
if (hostOsName == "linux") {
runningOnLinuxx86_64 {
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
implementation(kotlin(Deps.Jvm.stdLib)) implementation(kotlin(Deps.Jvm.stdLib))
@ -250,41 +232,50 @@ kotlin {
val jsMain by getting { val jsMain by getting {
dependencies { dependencies {
implementation(kotlin(Deps.Js.stdLib)) implementation(kotlin(Deps.Js.stdLib))
implementation(kotlin(Deps.Js.test))
implementation(Deps.Js.coroutines) implementation(Deps.Js.coroutines)
} }
} }
val jsTest by getting { val jsTest by getting {
dependencies { dependencies {
implementation(kotlin("test-js")) implementation(Deps.Js.coroutines)
implementation(kotlin(Deps.Js.test))
} }
} }
val linuxMain by getting { val linuxMain by getting {
dependsOn(nativeMain) dependsOn(nativeMain)
//Force idea to consider native sourceset
if (ideaActive) {
kotlin.srcDir("src/nativeMain/kotlin")
}
} }
val linuxTest by getting { val linuxTest by getting {
dependsOn(nativeTest) dependsOn(nativeTest)
// Force idea to consider native sourceset
if (ideaActive) {
kotlin.srcDir("src/nativeTest/kotlin")
}
} }
//Not supported in coroutines at the moment
// val linuxArm32HfpMain by getting {
// dependsOn(nativeMain)
// }
//
// val linuxArm32HfpTest by getting {
// dependsOn(nativeTest)
// }
// val linuxArm64Main by getting { val linuxArm64Main by getting {
// dependsOn(nativeMain) dependsOn(nativeMain)
// } }
//
// val linuxArm64Test by getting { val linuxArm64Test by getting {
// dependsOn(nativeTest) dependsOn(nativeTest)
// } }
val linuxArm32HfpMain by getting {
dependsOn(nativeMain)
}
val linuxArm32HfpTest by getting {
dependsOn(nativeTest)
}
} }
if (hostOsName == "macos") {
runningOnMacos{
val iosMain by getting { val iosMain by getting {
dependsOn(nativeMain) dependsOn(nativeMain)
@ -308,10 +299,16 @@ kotlin {
} }
val macosX64Main by getting { val macosX64Main by getting {
dependsOn(nativeMain) dependsOn(commonMain)
if (ideaActive) {
kotlin.srcDir("src/nativeMain/kotlin")
}
} }
val macosX64Test by getting { val macosX64Test by getting {
dependsOn(nativeTest) dependsOn(commonTest)
if (ideaActive) {
kotlin.srcDir("src/nativeTest/kotlin")
}
} }
} }
@ -327,7 +324,7 @@ kotlin {
// dependsOn(commonTest) // dependsOn(commonTest)
// } // }
// //
if (hostOsName == "windows") { runningOnWindows {
val mingwX64Main by getting { val mingwX64Main by getting {
dependsOn(commonMain) dependsOn(commonMain)
dependencies { dependencies {
@ -343,6 +340,8 @@ kotlin {
all { all {
languageSettings.enableLanguageFeature("InlineClasses") languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalStdlibApi")
} }
} }
@ -376,15 +375,12 @@ tasks {
} }
sourceRoot { sourceRoot {
println("Common !") println("Common !")
path = "/home/ionspin/Projects/Future/kotlin-multiplatform-crypto/crypto/src/commonMain" //TODO remove static path! path =
"/home/ionspin/Projects/Future/kotlin-multiplatform-crypto/crypto/src/commonMain" //TODO remove static path!
platforms = listOf("Common") platforms = listOf("Common")
} }
} }
if (getHostOsName() == "linux") { if (getHostOsName() == "linux" && getHostArchitecture() == "x86-64") {
val npmInstall by getting
val compileKotlinJs by getting(AbstractCompile::class)
val compileTestKotlinJs by getting(Kotlin2JsCompile::class)
val jvmTest by getting(Test::class) { val jvmTest by getting(Test::class) {
testLogging { testLogging {
@ -400,21 +396,20 @@ tasks {
} }
} }
val jsIrNodeTest by getting(KotlinJsTest::class) { val jsNodeTest by getting(KotlinJsTest::class) {
testLogging { testLogging {
events("PASSED", "FAILED", "SKIPPED") events("PASSED", "FAILED", "SKIPPED")
showStandardStreams = true showStandardStreams = true
} }
} }
val legacyjsNodeTest by getting(KotlinJsTest::class) { // val legacyjsNodeTest by getting(KotlinJsTest::class) {
//
testLogging { // testLogging {
events("PASSED", "FAILED", "SKIPPED") // events("PASSED", "FAILED", "SKIPPED")
showStandardStreams = true // showStandardStreams = true
} // }
} // }
// val jsIrBrowserTest by getting(KotlinJsTest::class) { // val jsIrBrowserTest by getting(KotlinJsTest::class) {
// testLogging { // testLogging {

View File

@ -1,21 +0,0 @@
#
# Copyright 2019 Ugljesa Jovanovic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -0,0 +1,67 @@
package com.ionspin.kotlin.crypto
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bProperties
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bPure
import com.ionspin.kotlin.crypto.hash.sha.Sha256Pure
import com.ionspin.kotlin.crypto.hash.sha.Sha512Pure
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
typealias Blake2bPureStateless = Blake2bPure.Companion
typealias Sha256PureStateless = Sha256Pure.Companion
typealias Sha512PureStateless = Sha512Pure.Companion
object Crypto : CryptoProvider {
override suspend fun initialize() {
//Nothing to do atm.
}
fun initializeWithCallback(done: () -> Unit) {
done()
}
object Blake2b {
fun updateable(key: UByteArray? = null, hashLength: Int = Blake2bProperties.MAX_HASH_BYTES): com.ionspin.kotlin.crypto.hash.blake2b.Blake2b {
checkInitialization()
return Blake2bPure(key, hashLength)
}
fun stateless(message: UByteArray, key: UByteArray = ubyteArrayOf(), hashLength: Int = Blake2bProperties.MAX_HASH_BYTES): UByteArray {
checkInitialization()
return Blake2bPureStateless.digest(message, key, hashLength)
}
}
object Sha256 {
fun updateable(): com.ionspin.kotlin.crypto.hash.sha.Sha256 {
checkInitialization()
return Sha256Pure()
}
fun stateless(message: UByteArray) : UByteArray{
checkInitialization()
return Sha256PureStateless.digest(inputMessage = message)
}
}
object Sha512 {
fun updateable(): com.ionspin.kotlin.crypto.hash.sha.Sha512 {
checkInitialization()
return Sha512Pure()
}
fun stateless(message: UByteArray) : UByteArray {
checkInitialization()
return Sha512PureStateless.digest(inputMessage = message)
}
}
private fun checkInitialization() {
// Nothing to do atm
}
}

View File

@ -0,0 +1,12 @@
package _multiplatform_crypto
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 24-May-2020
*/
//Workaround for https://youtrack.jetbrains.com/issue/KT-36878
val byteArray = byteArrayOf(0)
val byte = 0.toByte()
val longArray = longArrayOf(0)
val long = 0L

View File

@ -22,5 +22,5 @@ package com.ionspin.kotlin.crypto
* on 21-Sep-2019 * on 21-Sep-2019
*/ */
expect object SRNG { expect object SRNG {
fun getRandomBytes(amount : Int) : Array<UByte> fun getRandomBytes(amount : Int) : UByteArray
} }

View File

@ -19,9 +19,6 @@ package com.ionspin.kotlin.crypto.hash.blake2b
import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.integer.BigInteger
import com.ionspin.kotlin.bignum.integer.toBigInteger import com.ionspin.kotlin.bignum.integer.toBigInteger
import com.ionspin.kotlin.crypto.* import com.ionspin.kotlin.crypto.*
import com.ionspin.kotlin.crypto.hash.StatelessHash
import com.ionspin.kotlin.crypto.hash.UpdatableHash
import com.ionspin.kotlin.crypto.util.chunked
import com.ionspin.kotlin.crypto.util.rotateRight import com.ionspin.kotlin.crypto.util.rotateRight
/** /**
@ -30,11 +27,15 @@ import com.ionspin.kotlin.crypto.util.rotateRight
* on 14-Jul-2019 * on 14-Jul-2019
*/ */
@ExperimentalUnsignedTypes
class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : UpdatableHash {
companion object : StatelessHash { class Blake2bPure(val key: UByteArray? = null, val hashLength: Int = 64) : Blake2b {
companion object : Blake2bStateless {
//Hack start
//If this line is not included konanc 1.4-M1 fails to link because it cant find ByteArray which is
//a backing class for UByteArray
val byteArray: ByteArray = byteArrayOf(0, 1)
//Hack end
const val BITS_IN_WORD = 64 const val BITS_IN_WORD = 64
const val ROUNDS_IN_COMPRESS = 12 const val ROUNDS_IN_COMPRESS = 12
const val BLOCK_BYTES = 128 const val BLOCK_BYTES = 128
@ -103,7 +104,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
fun compress( fun compress(
h: Array<ULong>, h: Array<ULong>,
input: Array<UByte>, input: UByteArray,
offsetCounter: BigInteger, offsetCounter: BigInteger,
finalBlock: Boolean finalBlock: Boolean
): Array<ULong> { ): Array<ULong> {
@ -130,7 +131,6 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
v[13] = v[13] xor (offsetCounter shr BITS_IN_WORD).ulongValue() v[13] = v[13] xor (offsetCounter shr BITS_IN_WORD).ulongValue()
if (finalBlock) { if (finalBlock) {
// v[14] = v[14] xor 0xFFFFFFFFFFFFFFFFUL
v[14] = v[14].inv() v[14] = v[14].inv()
} }
@ -144,25 +144,25 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
return h return h
} }
@ExperimentalStdlibApi
override fun digest(inputString: String, key: String?, hashLength: Int): Array<UByte> { fun digest(inputString: String, key: String?, hashLength: Int): UByteArray {
val array = inputString.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray() val array = inputString.encodeToByteArray().toUByteArray()
val keyBytes = key?.run { val keyBytes = key?.run {
encodeToByteArray().map { it.toUByte() }.toTypedArray() encodeToByteArray().toUByteArray()
} ?: emptyArray() } ?: ubyteArrayOf()
return digest(inputMessage = array, key = keyBytes, hashLength = hashLength) return digest(inputMessage = array, key = keyBytes, hashLength = hashLength)
} }
override fun digest( override fun digest(
inputMessage: Array<UByte>, inputMessage: UByteArray,
key: Array<UByte>, key: UByteArray,
hashLength: Int hashLength: Int
): Array<UByte> { ): UByteArray {
if (hashLength > MAX_HASH_BYTES) { if (hashLength > MAX_HASH_BYTES) {
throw RuntimeException("Invalid hash length. Requested length more than maximum length. Requested length $hashLength") throw RuntimeException("Invalid hash length. Requested length more than maximum length. Requested length $hashLength")
} }
val chunkedMessage = inputMessage.chunked(BLOCK_BYTES) val chunkedMessage = inputMessage.chunked(BLOCK_BYTES).map { it.toUByteArray() }.toTypedArray()
val h = iv.copyOf() val h = iv.copyOf()
@ -172,7 +172,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
val message = if (key.isEmpty()) { val message = if (key.isEmpty()) {
if (chunkedMessage.isEmpty()) { if (chunkedMessage.isEmpty()) {
Array(1) { Array(1) {
Array<UByte>(128) { UByteArray(128) {
0U 0U
} }
} }
@ -199,7 +199,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
val lastBlockPadded = if (message.isNotEmpty()) { val lastBlockPadded = if (message.isNotEmpty()) {
padToBlock(message[message.size - 1]) padToBlock(message[message.size - 1])
} else { } else {
Array<UByte>(16) { 0U } UByteArray(16) { 0U }
} }
compress(h, lastBlockPadded, lastSize.toBigInteger(), true).copyInto(h) compress(h, lastBlockPadded, lastSize.toBigInteger(), true).copyInto(h)
@ -208,7 +208,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
return formatResult(h).copyOfRange(0, hashLength) return formatResult(h).copyOfRange(0, hashLength)
} }
private fun formatResult(h: Array<ULong>): Array<UByte> { private fun formatResult(h: Array<ULong>): UByteArray {
return h.map { return h.map {
arrayOf( arrayOf(
(it and 0xFFUL).toUByte(), (it and 0xFFUL).toUByte(),
@ -222,10 +222,10 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
) )
}.flatMap { }.flatMap {
it.toList() it.toList()
}.toTypedArray() }.toUByteArray()
} }
private fun padToBlock(unpadded: Array<UByte>): Array<UByte> { private fun padToBlock(unpadded: UByteArray): UByteArray {
if (unpadded.size == BLOCK_BYTES) { if (unpadded.size == BLOCK_BYTES) {
return unpadded return unpadded
} }
@ -234,7 +234,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
throw IllegalStateException("Block larger than 128 bytes") throw IllegalStateException("Block larger than 128 bytes")
} }
return Array(BLOCK_BYTES) { return UByteArray(BLOCK_BYTES) {
when (it) { when (it) {
in 0 until unpadded.size -> unpadded[it] in 0 until unpadded.size -> unpadded[it]
else -> 0U else -> 0U
@ -243,21 +243,21 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
} }
} }
@ExperimentalStdlibApi
constructor( constructor(
key: String?, key: String?,
requestedHashLenght: Int = 64 requestedHashLenght: Int = 64
) : this( ) : this(
(key?.encodeToByteArray()?.map { it.toUByte() }?.toTypedArray() ?: emptyArray<UByte>()), (key?.encodeToByteArray()?.toUByteArray() ?: ubyteArrayOf()),
requestedHashLenght requestedHashLenght
) )
override val MAX_HASH_BYTES: Int = Blake2b.MAX_HASH_BYTES override val MAX_HASH_BYTES: Int = Blake2bPure.MAX_HASH_BYTES
var h = iv.copyOf() var h = iv.copyOf()
var counter = BigInteger.ZERO var counter = BigInteger.ZERO
var bufferCounter = 0 var bufferCounter = 0
var buffer = Array<UByte>(BLOCK_BYTES) { 0U } var buffer = UByteArray(BLOCK_BYTES) { 0U }
init { init {
@ -268,7 +268,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
} }
} }
override fun update(data: Array<UByte>) { override fun update(data: UByteArray) {
if (data.isEmpty()) { if (data.isEmpty()) {
throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating") throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
} }
@ -276,7 +276,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
when { when {
bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter) bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter)
bufferCounter + data.size >= BLOCK_BYTES -> { bufferCounter + data.size >= BLOCK_BYTES -> {
val chunked = data.chunked(BLOCK_BYTES) val chunked = data.chunked(BLOCK_BYTES).map { it.toUByteArray() }
chunked.forEach { chunk -> chunked.forEach { chunk ->
if (bufferCounter + chunk.size < BLOCK_BYTES) { if (bufferCounter + chunk.size < BLOCK_BYTES) {
appendToBuffer(chunk, bufferCounter) appendToBuffer(chunk, bufferCounter)
@ -289,7 +289,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
) )
counter += BLOCK_BYTES counter += BLOCK_BYTES
consumeBlock(buffer) consumeBlock(buffer)
buffer = Array<UByte>(BLOCK_BYTES) { buffer = UByteArray(BLOCK_BYTES) {
when (it) { when (it) {
in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> { in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> {
chunk[it + (BLOCK_BYTES - bufferCounter)] chunk[it + (BLOCK_BYTES - bufferCounter)]
@ -308,21 +308,21 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
} }
} }
@ExperimentalStdlibApi
override fun update(data: String) { fun update(data: String) {
update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray()) update(data.encodeToByteArray().toUByteArray())
} }
private fun appendToBuffer(array: Array<UByte>, start: Int) { private fun appendToBuffer(array: UByteArray, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size bufferCounter += array.size
} }
private fun consumeBlock(block: Array<UByte>) { private fun consumeBlock(block: UByteArray) {
h = compress(h, block, counter, false) h = compress(h, block, counter, false)
} }
override fun digest(): Array<UByte> { override fun digest(): UByteArray {
val lastBlockPadded = padToBlock(buffer) val lastBlockPadded = padToBlock(buffer)
counter += bufferCounter counter += bufferCounter
compress(h, lastBlockPadded, counter, true) compress(h, lastBlockPadded, counter, true)
@ -333,15 +333,12 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
} }
override fun digestString(): String {
return digest().map { it.toString(16) }.joinToString(separator = "")
}
private fun reset() { private fun reset() {
h = iv.copyOf() h = iv.copyOf()
counter = BigInteger.ZERO counter = BigInteger.ZERO
bufferCounter = 0 bufferCounter = 0
buffer = Array<UByte>(BLOCK_BYTES) { 0U } buffer = UByteArray(BLOCK_BYTES) { 0U }
} }

View File

@ -16,9 +16,6 @@
package com.ionspin.kotlin.crypto.hash.sha package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.util.chunked
import com.ionspin.kotlin.crypto.hash.StatelessHash
import com.ionspin.kotlin.crypto.hash.UpdatableHash
import com.ionspin.kotlin.crypto.util.rotateRight import com.ionspin.kotlin.crypto.util.rotateRight
@ -29,13 +26,13 @@ import com.ionspin.kotlin.crypto.util.rotateRight
*/ */
@ExperimentalUnsignedTypes
class Sha256 : UpdatableHash { class Sha256Pure : Sha256 {
override val MAX_HASH_BYTES: Int = 32 override val MAX_HASH_BYTES: Int = 32
companion object : StatelessHash { companion object : StatelessSha256 {
const val BLOCK_SIZE = 512 const val BLOCK_SIZE = 512
const val BLOCK_SIZE_IN_BYTES = 64 const val BLOCK_SIZE_IN_BYTES = 64
const val UINT_MASK = 0xFFFFFFFFU const val UINT_MASK = 0xFFFFFFFFU
@ -66,15 +63,8 @@ class Sha256 : UpdatableHash {
0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U
) )
@ExperimentalStdlibApi
override fun digest(inputString: String, key: String?, hashLength: Int): Array<UByte> {
return digest(
inputString.encodeToByteArray().map { it.toUByte() }.toTypedArray(),
key?.run { encodeToByteArray().map { it.toUByte() }.toTypedArray() } ?: emptyArray<UByte>(),
hashLength)
}
override fun digest(inputMessage: Array<UByte>, key: Array<UByte>, hashLength: Int): Array<UByte> { override fun digest(inputMessage: UByteArray): UByteArray {
var h = iv.copyOf() var h = iv.copyOf()
@ -88,7 +78,7 @@ class Sha256 : UpdatableHash {
.chunked(BLOCK_SIZE_IN_BYTES) .chunked(BLOCK_SIZE_IN_BYTES)
chunks.forEach { chunk -> chunks.forEach { chunk ->
val w = expandChunk(chunk) val w = expandChunk(chunk.toUByteArray())
mix(h, w).copyInto(h) mix(h, w).copyInto(h)
} }
@ -128,7 +118,7 @@ class Sha256 : UpdatableHash {
return (((x and y) xor (x and z) xor (y and z))) return (((x and y) xor (x and z) xor (y and z)))
} }
private fun expandChunk(chunk: Array<UByte>): Array<UInt> { private fun expandChunk(chunk: UByteArray): Array<UInt> {
val w = Array<UInt>(BLOCK_SIZE_IN_BYTES) { val w = Array<UInt>(BLOCK_SIZE_IN_BYTES) {
when (it) { when (it) {
in 0 until 16 -> { in 0 until 16 -> {
@ -188,7 +178,7 @@ class Sha256 : UpdatableHash {
} }
fun createExpansionArray(originalSizeInBytes: Int): Array<UByte> { fun createExpansionArray(originalSizeInBytes: Int): UByteArray {
val originalMessageSizeInBits = originalSizeInBytes * 8 val originalMessageSizeInBits = originalSizeInBytes * 8
@ -198,7 +188,7 @@ class Sha256 : UpdatableHash {
0 -> 0 0 -> 0
else -> (BLOCK_SIZE - expandedRemainderOf512) / 8 else -> (BLOCK_SIZE - expandedRemainderOf512) / 8
} }
val expansionArray = Array<UByte>(zeroAddAmount + 1) { val expansionArray = UByteArray(zeroAddAmount + 1) {
when (it) { when (it) {
0 -> 0b10000000U 0 -> 0b10000000U
else -> 0U else -> 0U
@ -207,9 +197,9 @@ class Sha256 : UpdatableHash {
return expansionArray return expansionArray
} }
private fun ULong.toPaddedByteArray(): Array<UByte> { private fun ULong.toPaddedByteArray(): UByteArray {
val byteMask = BYTE_MASK_FROM_ULONG val byteMask = BYTE_MASK_FROM_ULONG
return Array(8) { return UByteArray(8) {
when (it) { when (it) {
7 -> (this and byteMask).toUByte() 7 -> (this and byteMask).toUByte()
6 -> ((this shr 8) and byteMask).toUByte() 6 -> ((this shr 8) and byteMask).toUByte()
@ -224,9 +214,9 @@ class Sha256 : UpdatableHash {
} }
} }
private fun UInt.toPaddedByteArray(): Array<UByte> { private fun UInt.toPaddedByteArray(): UByteArray {
val byteMask = BYTE_MASK_FROM_UINT val byteMask = BYTE_MASK_FROM_UINT
return Array(4) { return UByteArray(4) {
when (it) { when (it) {
3 -> (this and byteMask).toUByte() 3 -> (this and byteMask).toUByte()
2 -> ((this shr 8) and byteMask).toUByte() 2 -> ((this shr 8) and byteMask).toUByte()
@ -242,14 +232,14 @@ class Sha256 : UpdatableHash {
var h = iv.copyOf() var h = iv.copyOf()
var counter = 0 var counter = 0
var bufferCounter = 0 var bufferCounter = 0
var buffer = Array<UByte>(BLOCK_SIZE_IN_BYTES) { 0U } var buffer = UByteArray(BLOCK_SIZE_IN_BYTES) { 0U }
@ExperimentalStdlibApi
override fun update(data: String) { fun update(data: String) {
return update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray()) return update(data.encodeToByteArray().toUByteArray())
} }
override fun update(data: Array<UByte>) { override fun update(data: UByteArray) {
if (data.isEmpty()) { if (data.isEmpty()) {
throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating") throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
} }
@ -260,9 +250,9 @@ class Sha256 : UpdatableHash {
val chunked = data.chunked(BLOCK_SIZE_IN_BYTES) val chunked = data.chunked(BLOCK_SIZE_IN_BYTES)
chunked.forEach { chunk -> chunked.forEach { chunk ->
if (bufferCounter + chunk.size < BLOCK_SIZE_IN_BYTES) { if (bufferCounter + chunk.size < BLOCK_SIZE_IN_BYTES) {
appendToBuffer(chunk, bufferCounter) appendToBuffer(chunk.toUByteArray(), bufferCounter)
} else { } else {
chunk.copyInto( chunk.toUByteArray().copyInto(
destination = buffer, destination = buffer,
destinationOffset = bufferCounter, destinationOffset = bufferCounter,
startIndex = 0, startIndex = 0,
@ -270,7 +260,7 @@ class Sha256 : UpdatableHash {
) )
counter += BLOCK_SIZE_IN_BYTES counter += BLOCK_SIZE_IN_BYTES
consumeBlock(buffer) consumeBlock(buffer)
buffer = Array<UByte>(BLOCK_SIZE_IN_BYTES) { buffer = UByteArray(BLOCK_SIZE_IN_BYTES) {
when (it) { when (it) {
in (0 until (chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter))) -> { in (0 until (chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter))) -> {
chunk[it + (BLOCK_SIZE_IN_BYTES - bufferCounter)] chunk[it + (BLOCK_SIZE_IN_BYTES - bufferCounter)]
@ -289,18 +279,18 @@ class Sha256 : UpdatableHash {
} }
} }
private fun consumeBlock(block: Array<UByte>) { private fun consumeBlock(block: UByteArray) {
val w = expandChunk(block) val w = expandChunk(block)
mix(h, w).copyInto(h) mix(h, w).copyInto(h)
} }
override fun digest(): Array<UByte> { override fun digest(): UByteArray {
val length = counter + bufferCounter val length = counter + bufferCounter
val expansionArray = createExpansionArray(length) val expansionArray = createExpansionArray(length)
val finalBlock = val finalBlock =
buffer.copyOfRange(0, bufferCounter) + expansionArray + (length * 8).toULong().toPaddedByteArray() buffer.copyOfRange(0, bufferCounter) + expansionArray + (length * 8).toULong().toPaddedByteArray()
finalBlock.chunked(BLOCK_SIZE_IN_BYTES).forEach { finalBlock.chunked(BLOCK_SIZE_IN_BYTES).forEach {
consumeBlock(it) consumeBlock(it.toUByteArray())
} }
@ -315,11 +305,7 @@ class Sha256 : UpdatableHash {
return digest return digest
} }
override fun digestString(): String { private fun appendToBuffer(array: UByteArray, start: Int) {
return digest().map { it.toString(16) }.joinToString(separator = "")
}
private fun appendToBuffer(array: Array<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size bufferCounter += array.size
} }

View File

@ -16,9 +16,6 @@
package com.ionspin.kotlin.crypto.hash.sha package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.util.chunked
import com.ionspin.kotlin.crypto.hash.StatelessHash
import com.ionspin.kotlin.crypto.hash.UpdatableHash
import com.ionspin.kotlin.crypto.util.rotateRight import com.ionspin.kotlin.crypto.util.rotateRight
/** /**
@ -27,12 +24,12 @@ import com.ionspin.kotlin.crypto.util.rotateRight
* on 18-Jul-2019 * on 18-Jul-2019
*/ */
@ExperimentalUnsignedTypes
class Sha512 : UpdatableHash { class Sha512Pure : Sha512 {
override val MAX_HASH_BYTES: Int = 32 override val MAX_HASH_BYTES: Int = 32
companion object : StatelessHash { companion object : StatelessSha512 {
const val BLOCK_SIZE = 1024 const val BLOCK_SIZE = 1024
const val BLOCK_SIZE_IN_BYTES = 128 const val BLOCK_SIZE_IN_BYTES = 128
const val CHUNK_SIZE = 80 const val CHUNK_SIZE = 80
@ -134,16 +131,8 @@ class Sha512 : UpdatableHash {
0x5be0cd19137e2179UL 0x5be0cd19137e2179UL
) )
@ExperimentalStdlibApi
override fun digest(inputString: String, key: String?, hashLength: Int): Array<UByte> {
return digest(
inputString.encodeToByteArray().map { it.toUByte() }.toTypedArray(),
key?.run { encodeToByteArray().map { it.toUByte() }.toTypedArray() } ?: emptyArray<UByte>(),
hashLength = hashLength
)
}
override fun digest(inputMessage: Array<UByte>, key: Array<UByte>, hashLength: Int): Array<UByte> { override fun digest(inputMessage: UByteArray): UByteArray {
var h = iv.copyOf() var h = iv.copyOf()
@ -155,7 +144,7 @@ class Sha512 : UpdatableHash {
) )
chunks.forEach { chunk -> chunks.forEach { chunk ->
val w = expandChunk(chunk) val w = expandChunk(chunk.toUByteArray())
mix(h, w) mix(h, w)
} }
@ -196,7 +185,7 @@ class Sha512 : UpdatableHash {
return ((x and y) xor (x and z) xor (y and z)) return ((x and y) xor (x and z) xor (y and z))
} }
private fun expandChunk(chunk: Array<UByte>): Array<ULong> { private fun expandChunk(chunk: UByteArray): Array<ULong> {
val w = Array<ULong>(CHUNK_SIZE) { val w = Array<ULong>(CHUNK_SIZE) {
when (it) { when (it) {
in 0 until 16 -> { in 0 until 16 -> {
@ -259,7 +248,7 @@ class Sha512 : UpdatableHash {
return h return h
} }
fun createExpansionArray(originalSizeInBytes: Int): Array<UByte> { fun createExpansionArray(originalSizeInBytes: Int): UByteArray {
val originalMessageSizeInBits = originalSizeInBytes * 8 val originalMessageSizeInBits = originalSizeInBytes * 8
val expandedRemainderOf1024 = (originalMessageSizeInBits + 129) % BLOCK_SIZE val expandedRemainderOf1024 = (originalMessageSizeInBits + 129) % BLOCK_SIZE
@ -267,7 +256,7 @@ class Sha512 : UpdatableHash {
0 -> 0 0 -> 0
else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8 else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8
} }
val expansionArray = Array<UByte>(zeroAddAmount + 1) { val expansionArray = UByteArray(zeroAddAmount + 1) {
when (it) { when (it) {
0 -> 0b10000000U 0 -> 0b10000000U
else -> 0U else -> 0U
@ -277,10 +266,10 @@ class Sha512 : UpdatableHash {
} }
private fun ULong.toPaddedByteArray(): Array<UByte> { private fun ULong.toPaddedByteArray(): UByteArray {
val byteMask = 0xFFUL val byteMask = 0xFFUL
//Ignore messages longer than 64 bits for now //Ignore messages longer than 64 bits for now
return Array(8) { return UByteArray(8) {
when (it) { when (it) {
7 -> (this and byteMask).toUByte() 7 -> (this and byteMask).toUByte()
6 -> ((this shr 8) and byteMask).toUByte() 6 -> ((this shr 8) and byteMask).toUByte()
@ -295,10 +284,10 @@ class Sha512 : UpdatableHash {
} }
} }
private fun ULong.toPadded128BitByteArray(): Array<UByte> { private fun ULong.toPadded128BitByteArray(): UByteArray {
val byteMask = 0xFFUL val byteMask = 0xFFUL
//Ignore messages longer than 64 bits for now //Ignore messages longer than 64 bits for now
return Array(16) { return UByteArray(16) {
when (it) { when (it) {
15 -> (this and byteMask).toUByte() 15 -> (this and byteMask).toUByte()
14 -> ((this shr 8) and byteMask).toUByte() 14 -> ((this shr 8) and byteMask).toUByte()
@ -317,14 +306,14 @@ class Sha512 : UpdatableHash {
var h = iv.copyOf() var h = iv.copyOf()
var counter = 0 var counter = 0
var bufferCounter = 0 var bufferCounter = 0
var buffer = Array<UByte>(BLOCK_SIZE_IN_BYTES) { 0U } var buffer = UByteArray(BLOCK_SIZE_IN_BYTES) { 0U }
@ExperimentalStdlibApi
override fun update(data: String) { fun update(data: String) {
return update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray()) return update(data.encodeToByteArray().toUByteArray())
} }
override fun update(data: Array<UByte>) { override fun update(data: UByteArray) {
if (data.isEmpty()) { if (data.isEmpty()) {
throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating") throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
} }
@ -335,9 +324,9 @@ class Sha512 : UpdatableHash {
val chunked = data.chunked(BLOCK_SIZE_IN_BYTES) val chunked = data.chunked(BLOCK_SIZE_IN_BYTES)
chunked.forEach { chunk -> chunked.forEach { chunk ->
if (bufferCounter + chunk.size < BLOCK_SIZE_IN_BYTES) { if (bufferCounter + chunk.size < BLOCK_SIZE_IN_BYTES) {
appendToBuffer(chunk, bufferCounter) appendToBuffer(chunk.toUByteArray(), bufferCounter)
} else { } else {
chunk.copyInto( chunk.toUByteArray().copyInto(
destination = buffer, destination = buffer,
destinationOffset = bufferCounter, destinationOffset = bufferCounter,
startIndex = 0, startIndex = 0,
@ -345,7 +334,7 @@ class Sha512 : UpdatableHash {
) )
counter += BLOCK_SIZE_IN_BYTES counter += BLOCK_SIZE_IN_BYTES
consumeBlock(buffer) consumeBlock(buffer)
buffer = Array<UByte>(BLOCK_SIZE_IN_BYTES) { buffer = UByteArray(BLOCK_SIZE_IN_BYTES) {
when (it) { when (it) {
in (0 until (chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter))) -> { in (0 until (chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter))) -> {
chunk[it + (BLOCK_SIZE_IN_BYTES - bufferCounter)] chunk[it + (BLOCK_SIZE_IN_BYTES - bufferCounter)]
@ -364,18 +353,18 @@ class Sha512 : UpdatableHash {
} }
} }
private fun consumeBlock(block: Array<UByte>) { private fun consumeBlock(block: UByteArray) {
val w = expandChunk(block) val w = expandChunk(block)
mix(h, w).copyInto(h) mix(h, w).copyInto(h)
} }
override fun digest(): Array<UByte> { override fun digest(): UByteArray {
val length = counter + bufferCounter val length = counter + bufferCounter
val expansionArray = createExpansionArray(length) val expansionArray = createExpansionArray(length)
val finalBlock = val finalBlock =
buffer.copyOfRange(0, bufferCounter) + expansionArray + (length * 8).toULong().toPadded128BitByteArray() buffer.copyOfRange(0, bufferCounter) + expansionArray + (length * 8).toULong().toPadded128BitByteArray()
finalBlock.chunked(BLOCK_SIZE_IN_BYTES).forEach { finalBlock.chunked(BLOCK_SIZE_IN_BYTES).forEach {
consumeBlock(it) consumeBlock(it.toUByteArray())
} }
@ -390,11 +379,7 @@ class Sha512 : UpdatableHash {
return digest return digest
} }
override fun digestString(): String { private fun appendToBuffer(array: UByteArray, start: Int) {
return digest().map { it.toString(16) }.joinToString(separator = "")
}
private fun appendToBuffer(array: Array<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size bufferCounter += array.size
} }

View File

@ -1,377 +1,10 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package com.ionspin.kotlin.crypto.keyderivation.argon2 package com.ionspin.kotlin.crypto.keyderivation.argon2
import com.ionspin.kotlin.bignum.integer.toBigInteger
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b
import com.ionspin.kotlin.crypto.keyderivation.KeyDerivationFunction import com.ionspin.kotlin.crypto.keyderivation.KeyDerivationFunction
import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.argonBlake2bArbitraryLenghtHash
import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.compressionFunctionG
import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.validateArgonParameters
import com.ionspin.kotlin.crypto.util.fromLittleEndianArrayToUInt
import com.ionspin.kotlin.crypto.util.hexColumsPrint
import com.ionspin.kotlin.crypto.util.toLittleEndianUByteArray
/** /**
* Created by Ugljesa Jovanovic * Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com * ugljesa.jovanovic@ionspin.com
* on 16-May-2020 * on 24-May-2020
*/ */
interface Argon2 : KeyDerivationFunction
enum class ArgonType(val typeId: Int) {
Argon2d(0), Argon2i(1), Argon2id(2)
}
data class SegmentPosition(
val iteration: Int,
val lane: Int,
val slice: Int
)
@ExperimentalStdlibApi
class Argon2(
private val password: Array<UByte>,
private val salt: Array<UByte> = emptyArray(),
private val parallelism: Int = 1,
private val tagLength: UInt = 64U,
requestedMemorySize: UInt = 0U,
private val numberOfIterations: UInt = 1U,
private val key: Array<UByte> = emptyArray(),
private val associatedData: Array<UByte> = emptyArray(),
private val argonType: ArgonType = ArgonType.Argon2id
) : KeyDerivationFunction {
constructor(
password: String,
salt: String = "",
parallelism: Int = 1,
tagLength: UInt = 64U,
requestedMemorySize: UInt = 0U,
numberOfIterations: UInt = 10U,
key: String = "",
associatedData: String = "",
argonType: ArgonType = ArgonType.Argon2id
) : this(
password.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray(),
salt.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray(),
parallelism,
tagLength,
requestedMemorySize,
numberOfIterations,
key.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray(),
associatedData.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray(),
argonType
)
init {
validateArgonParameters(
password,
salt,
parallelism,
tagLength,
requestedMemorySize,
numberOfIterations,
key,
associatedData,
argonType
)
}
//We support only the latest version
private val versionNumber: UInt = 0x13U
//Use either requested memory size, or default, or throw exception if the requested amount is less than 8*parallelism
private val memorySize = if (requestedMemorySize == 0U) {
((8 * parallelism) * 2).toUInt()
} else {
requestedMemorySize
}
private val blockCount = (memorySize / (4U * parallelism.toUInt())) * (4U * parallelism.toUInt())
private val columnCount = (blockCount / parallelism.toUInt()).toInt()
private val segmentLength = columnCount / 4
private val useIndependentAddressing = argonType == ArgonType.Argon2id || argonType == ArgonType.Argon2i
// State
private val matrix = Array(parallelism) {
Array(columnCount) {
Array<UByte>(1024) { 0U }
}
}
private fun clearMatrix() {
matrix.forEachIndexed { laneIndex, lane ->
lane.forEachIndexed { columnIndex, block ->
block.forEachIndexed { byteIndex, byte ->
matrix[laneIndex][columnIndex][byteIndex] = 0U
}
}
}
}
private fun populateAddressBlock(
iteration: Int,
slice: Int,
lane: Int,
addressBlock: Array<UByte>,
addressCounter: ULong
): Array<UByte> {
//Calculate first pass
val firstPass = compressionFunctionG(
Array<UByte>(1024) { 0U },
iteration.toULong().toLittleEndianUByteArray() +
lane.toULong().toLittleEndianUByteArray() +
slice.toULong().toLittleEndianUByteArray() +
blockCount.toULong().toLittleEndianUByteArray() +
numberOfIterations.toULong().toLittleEndianUByteArray() +
argonType.typeId.toULong().toLittleEndianUByteArray() +
addressCounter.toLittleEndianUByteArray() +
Array<UByte>(968) { 0U },
addressBlock,
false
)
val secondPass = compressionFunctionG(
Array<UByte>(1024) { 0U },
firstPass,
firstPass,
false
)
return secondPass
}
private fun computeReferenceBlockIndexes(
iteration: Int,
slice: Int,
lane: Int,
column: Int,
addressBlock: Array<UByte>?
): Pair<Int, Int> {
val segmentIndex = (column % segmentLength)
val independentIndex = segmentIndex % 128 // 128 is the number of addresses in address block
val (j1, j2) = when (argonType) {
ArgonType.Argon2d -> {
val previousBlock = if (column == 0) {
matrix[lane][columnCount - 1] //Get last block in the SAME lane
} else {
matrix[lane][column - 1]
}
val first32Bit = previousBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt()
val second32Bit = previousBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt()
Pair(first32Bit, second32Bit)
}
ArgonType.Argon2i -> {
val selectedAddressBlock = addressBlock!!.sliceArray((independentIndex * 8) until (independentIndex * 8) + 8)
val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt()
val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt()
Pair(first32Bit, second32Bit)
}
ArgonType.Argon2id -> {
if (iteration == 0 && (slice == 0 || slice == 1)) {
val selectedAddressBlock =
addressBlock!!.sliceArray((independentIndex * 8) until (independentIndex * 8) + 8)
val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt()
val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt()
Pair(first32Bit, second32Bit)
} else {
val previousBlock = if (column == 0) {
matrix[lane][columnCount - 1] //Get last block in the SAME lane
} else {
matrix[lane][column - 1]
}
val first32Bit = previousBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt()
val second32Bit = previousBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt()
Pair(first32Bit, second32Bit)
}
}
}
//If this is first iteration and first slice, block is taken from the current lane
val l = if (iteration == 0 && slice == 0) {
lane
} else {
(j2.toBigInteger() % parallelism).intValue()
}
val referenceAreaSize = if (iteration == 0) {
if (slice == 0) {
//All indices except the previous
segmentIndex - 1
} else {
if (lane == l) {
//Same lane
column - 1
} else {
slice * (columnCount / 4) + if (segmentIndex == 0) { // Check if column is first block of the SEGMENT
-1
} else {
0
}
}
}
} else {
if (lane == l) {
columnCount - (columnCount / 4) + (segmentIndex - 1)
} else {
columnCount - (columnCount / 4) + if (segmentIndex == 0) {
-1
} else {
0
}
}
}
val x = (j1.toULong() * j1) shr 32
val y = (referenceAreaSize.toULong() * x) shr 32
val z = referenceAreaSize.toULong() - 1U - y
val startPosition = if (iteration == 0) {
0
} else {
if (slice == 3) {
0
} else {
(slice + 1) * segmentLength
}
}
val absolutePosition = (startPosition + z.toInt()) % columnCount
return Pair(l, absolutePosition)
}
override fun derive(): Array<UByte> {
val h0 = Blake2b.digest(
parallelism.toUInt()
.toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() +
numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + argonType.typeId.toUInt()
.toLittleEndianUByteArray() +
password.size.toUInt().toLittleEndianUByteArray() + password +
salt.size.toUInt().toLittleEndianUByteArray() + salt +
key.size.toUInt().toLittleEndianUByteArray() + key +
associatedData.size.toUInt().toLittleEndianUByteArray() + associatedData
)
//Compute B[i][0]
for (i in 0 until parallelism.toInt()) {
matrix[i][0] =
argonBlake2bArbitraryLenghtHash(
h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(),
1024U
)
}
//Compute B[i][1]
for (i in 0 until parallelism.toInt()) {
matrix[i][1] =
argonBlake2bArbitraryLenghtHash(
h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(),
1024U
)
}
executeArgonWithSingleThread()
val result = matrix.foldIndexed(emptyArray<UByte>()) { lane, acc, laneArray ->
if (acc.size == 0) {
acc + laneArray[columnCount - 1] // add last element in first lane to the accumulator
} else {
// For each element in our accumulator, xor it with an appropriate element from the last column in current lane (from 1 to `parallelism`)
acc.mapIndexed { index, it -> it xor laneArray[columnCount - 1][index] }
.toTypedArray()
}
}
//Hash the xored last blocks
val hash = argonBlake2bArbitraryLenghtHash(result, tagLength)
clearMatrix()
return hash
}
private fun executeArgonWithSingleThread() {
for (iteration in 0 until numberOfIterations.toInt()) {
for (slice in 0 until 4) {
for (lane in 0 until parallelism) {
val segmentPosition = SegmentPosition(iteration, lane, slice)
processSegment(segmentPosition)
}
}
}
}
private fun processSegment(segmentPosition: SegmentPosition) {
val iteration = segmentPosition.iteration
val slice = segmentPosition.slice
val lane = segmentPosition.lane
var addressBlock: Array<UByte>? = null
var addressCounter = 1UL //Starts from 1 in each segment as defined by the spec
//Generate initial segment address block
if (useIndependentAddressing) {
addressBlock = Array<UByte>(1024) {
0U
}
addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock, addressCounter)
addressCounter++
}
val startColumn = if (iteration == 0 && slice == 0) {
2
} else {
slice * segmentLength
}
for (column in startColumn until (slice + 1) * segmentLength) {
val segmentIndex = column - (slice * segmentLength)
//Each address block contains 128 addresses, and we use one per iteration,
//so once we do 128 iterations we need to calculate a new address block
if (useIndependentAddressing && segmentIndex != 0 && segmentIndex % 128 == 0) {
addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock!!, addressCounter)
addressCounter++
addressBlock.hexColumsPrint(16)
}
val previousColumn = if (column == 0) {
columnCount - 1
} else {
column - 1
}
val (l, z) = computeReferenceBlockIndexes(
iteration,
slice,
lane,
column,
addressBlock
)
matrix[lane][column] =
compressionFunctionG(
matrix[lane][previousColumn],
matrix[l][z],
matrix[lane][column],
true
)
}
}
}

View File

@ -23,8 +23,8 @@ package com.ionspin.kotlin.crypto.keyderivation.argon2
*/ */
class Argon2TagTooShort(tagLength: UInt) : RuntimeException("Too short tag (output) requested. Requested: $tagLength") class Argon2TagTooShort(tagLength: UInt) : RuntimeException("Too short tag (output) requested. Requested: $tagLength")
class Argon2TagTooLong(tagLength: UInt) : RuntimeException("Too long tag (output) requested. Requested: $tagLength") class Argon2TagTooLong(tagLength: UInt) : RuntimeException("Too long tag (output) requested. Requested: $tagLength")
class Argon2TimeTooShort(iterations: UInt) : RuntimeException("Too short time parameter (Too few iterations). Requested iterations: $iterations") class Argon2TimeTooShort(iterations: Int) : RuntimeException("Too short time parameter (Too few iterations). Requested iterations: $iterations")
class Argon2TimeTooLong(iterations: UInt) : RuntimeException("Too long time parameter (Too many iterations). Requested iterations: $iterations") class Argon2TimeTooLong(iterations: Int) : RuntimeException("Too long time parameter (Too many iterations). Requested iterations: $iterations")
class Argon2MemoryTooLitlle(requestedMemorySize: UInt) : RuntimeException("Requested memory size must be larger than 8 * parallelism. Requested size: $requestedMemorySize") class Argon2MemoryTooLitlle(requestedMemorySize: UInt) : RuntimeException("Requested memory size must be larger than 8 * parallelism. Requested size: $requestedMemorySize")
class Argon2MemoryTooMuch(requestedMemorySize: UInt) : RuntimeException("Requested memory size too large. Requested size: $requestedMemorySize") class Argon2MemoryTooMuch(requestedMemorySize: UInt) : RuntimeException("Requested memory size too large. Requested size: $requestedMemorySize")
class Argon2LanesTooFew(parallelism: Int) : RuntimeException("Too few, or invalid number of threads requested $parallelism") class Argon2LanesTooFew(parallelism: Int) : RuntimeException("Too few, or invalid number of threads requested $parallelism")

View File

@ -0,0 +1,405 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package com.ionspin.kotlin.crypto.keyderivation.argon2
import com.ionspin.kotlin.bignum.integer.toBigInteger
import com.ionspin.kotlin.crypto.Blake2bPureStateless
import com.ionspin.kotlin.crypto.SRNG
import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.argonBlake2bArbitraryLenghtHash
import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.compressionFunctionG
import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.validateArgonParameters
import com.ionspin.kotlin.crypto.util.*
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 16-May-2020
*/
enum class ArgonType(val typeId: Int) {
Argon2d(0), Argon2i(1), Argon2id(2)
}
data class SegmentPosition(
val iteration: Int,
val lane: Int,
val slice: Int
)
data class ArgonResult(
val hashBytes: UByteArray,
val salt: UByteArray
) {
val hashString by lazy { hashBytes.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "") }
val saltString by lazy { salt.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "") }
}
class Argon2Pure(
private val password: UByteArray,
private val salt: UByteArray = ubyteArrayOf(),
private val parallelism: Int = 1,
private val tagLength: UInt = 64U,
requestedMemorySize: UInt = 0U,
private val numberOfIterations: Int = 1,
private val key: UByteArray = ubyteArrayOf(),
private val associatedData: UByteArray = ubyteArrayOf(),
private val argonType: ArgonType = ArgonType.Argon2id
) : Argon2 {
companion object {
fun derive(
password: String,
salt: String? = null,
key: String,
associatedData: String,
parallelism: Int = 16,
tagLength: Int = 64,
memory: Int = 4096,
numberOfIterations: Int = 10,
): ArgonResult {
val salt = SRNG.getRandomBytes(64)
val argon = Argon2Pure(
password.encodeToByteArray().toUByteArray(),
salt,
parallelism,
tagLength.toUInt(),
memory.toUInt(),
numberOfIterations,
key.encodeToByteArray().toUByteArray(),
associatedData.encodeToByteArray().toUByteArray(),
ArgonType.Argon2id
)
val resultArray = argon.derive()
return ArgonResult(resultArray, salt)
}
}
constructor(
password: String,
salt: String = "",
parallelism: Int = 1,
tagLength: UInt = 64U,
requestedMemorySize: UInt = 0U,
numberOfIterations: Int = 10,
key: String = "",
associatedData: String = "",
argonType: ArgonType = ArgonType.Argon2id
) : this(
password.encodeToByteArray().toUByteArray(),
salt.encodeToByteArray().toUByteArray(),
parallelism,
tagLength,
requestedMemorySize,
numberOfIterations,
key.encodeToByteArray().toUByteArray(),
associatedData.encodeToByteArray().toUByteArray(),
argonType
)
//We support only the latest version
private val versionNumber: UInt = 0x13U
//Use either requested memory size, or default, or throw exception if the requested amount is less than 8*parallelism
private val memorySize = if (requestedMemorySize == 0U) {
((8 * parallelism) * 2).toUInt()
} else {
requestedMemorySize
}
private val blockCount = (memorySize / (4U * parallelism.toUInt())) * (4U * parallelism.toUInt())
private val columnCount = (blockCount / parallelism.toUInt()).toInt()
private val segmentLength = columnCount / 4
private val useIndependentAddressing = argonType == ArgonType.Argon2id || argonType == ArgonType.Argon2i
// State
private val matrix: ArgonMatrix
init {
matrix = ArgonMatrix(columnCount, parallelism)
validateArgonParameters(
password,
salt,
parallelism,
tagLength,
requestedMemorySize,
numberOfIterations,
key,
associatedData,
argonType
)
}
private fun populateAddressBlock(
iteration: Int,
slice: Int,
lane: Int,
addressBlock: ArgonBlockPointer,
addressCounter: ULong
): ArgonBlockPointer {
//Calculate first pass
val zeroesBlock = ArgonBlock()
val firstPass = compressionFunctionG(
zeroesBlock.getBlockPointer(),
ArgonBlock(iteration.toULong().toLittleEndianUByteArray() +
lane.toULong().toLittleEndianUByteArray() +
slice.toULong().toLittleEndianUByteArray() +
blockCount.toULong().toLittleEndianUByteArray() +
numberOfIterations.toULong().toLittleEndianUByteArray() +
argonType.typeId.toULong().toLittleEndianUByteArray() +
addressCounter.toLittleEndianUByteArray() +
UByteArray(968) { 0U }
).getBlockPointer(),
addressBlock,
false
)
val secondPass = compressionFunctionG(
zeroesBlock.getBlockPointer(),
firstPass,
firstPass,
false
)
return secondPass
}
private fun computeReferenceBlockIndexes(
iteration: Int,
slice: Int,
lane: Int,
column: Int,
addressBlockPointer: ArgonBlockPointer?
): Pair<Int, Int> {
val segmentIndex = (column % segmentLength)
val independentIndex = segmentIndex % 128 // 128 is the number of addresses in address block
val (j1, j2) = when (argonType) {
ArgonType.Argon2d -> {
val previousBlockStart = if (column == 0) {
matrix.getBlockPointer(lane, columnCount - 1) //Get last block in the SAME lane
} else {
matrix.getBlockPointer(lane, column - 1)
}
val first32Bit = matrix.sliceArray(previousBlockStart.asInt() until previousBlockStart.asInt() + 4).fromLittleEndianArrayToUInt()
val second32Bit = matrix.sliceArray(previousBlockStart.asInt() + 4 until previousBlockStart.asInt() + 8).fromLittleEndianArrayToUInt()
Pair(first32Bit, second32Bit)
}
ArgonType.Argon2i -> {
val first32Bit = addressBlockPointer!!.getUIntFromPosition(independentIndex * 8)
val second32Bit = addressBlockPointer!!.getUIntFromPosition(independentIndex * 8 + 4)
Pair(first32Bit, second32Bit)
}
ArgonType.Argon2id -> {
if (iteration == 0 && (slice == 0 || slice == 1)) {
val first32Bit = addressBlockPointer!!.getUIntFromPosition(independentIndex * 8)
val second32Bit = addressBlockPointer!!.getUIntFromPosition(independentIndex * 8 + 4)
Pair(first32Bit, second32Bit)
} else {
val previousBlockStart = if (column == 0) {
matrix.getBlockPointer(lane, columnCount - 1) //Get last block in the SAME lane
} else {
matrix.getBlockPointer(lane, column - 1)
}
val first32Bit = matrix.sliceArray(previousBlockStart.asInt() until previousBlockStart.asInt() + 4).fromLittleEndianArrayToUInt()
val second32Bit = matrix.sliceArray(previousBlockStart.asInt() + 4 until previousBlockStart.asInt() + 8).fromLittleEndianArrayToUInt()
Pair(first32Bit, second32Bit)
}
}
}
//If this is first iteration and first slice, block is taken from the current lane
val l = if (iteration == 0 && slice == 0) {
lane
} else {
(j2.toBigInteger() % parallelism).intValue()
}
val referenceAreaSize = if (iteration == 0) {
if (slice == 0) {
//All indices except the previous
segmentIndex - 1
} else {
if (lane == l) {
//Same lane
column - 1
} else {
slice * (columnCount / 4) + if (segmentIndex == 0) { // Check if column is first block of the SEGMENT
-1
} else {
0
}
}
}
} else {
if (lane == l) {
columnCount - (columnCount / 4) + (segmentIndex - 1)
} else {
columnCount - (columnCount / 4) + if (segmentIndex == 0) {
-1
} else {
0
}
}
}
val x = (j1.toULong() * j1) shr 32
val y = (referenceAreaSize.toULong() * x) shr 32
val z = referenceAreaSize.toULong() - 1U - y
val startPosition = if (iteration == 0) {
0
} else {
if (slice == 3) {
0
} else {
(slice + 1) * segmentLength
}
}
val absolutePosition = (startPosition + z.toInt()) % columnCount
return Pair(l, absolutePosition)
}
override fun derive(): UByteArray {
val blakeInput = parallelism.toUInt().toLittleEndianUByteArray() +
tagLength.toLittleEndianUByteArray() +
memorySize.toLittleEndianUByteArray() +
numberOfIterations.toUInt().toLittleEndianUByteArray() +
versionNumber.toLittleEndianUByteArray() +
argonType.typeId.toUInt().toLittleEndianUByteArray() +
password.size.toUInt().toLittleEndianUByteArray() + password +
salt.size.toUInt().toLittleEndianUByteArray() + salt +
key.size.toUInt().toLittleEndianUByteArray() + key +
associatedData.size.toUInt().toLittleEndianUByteArray() + associatedData
val h0 = Blake2bPureStateless.digest(
blakeInput
)
//Compute B[i][0]
for (i in 0 until parallelism) {
matrix.setBlockAt(i, 0,
argonBlake2bArbitraryLenghtHash(
(h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray()).toUByteArray(),
1024U
)
)
}
//Compute B[i][1]
for (i in 0 until parallelism) {
matrix.setBlockAt(i, 1,
argonBlake2bArbitraryLenghtHash(
(h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray()).toUByteArray(),
1024U
)
)
}
//Run all iterations over all lanes and all segments
executeArgonWithSingleThread()
val acc = ArgonBlock(matrix.getBlockAt(0, columnCount - 1))
val accPointer = acc.getBlockPointer()
for (i in 1 until parallelism) {
accPointer.xorInplaceWith(matrix.getBlockPointer(i, columnCount - 1))
}
//Hash the xored last blocks
val hash = argonBlake2bArbitraryLenghtHash(acc.storage, tagLength)
matrix.clearMatrix()
return hash
}
private fun executeArgonWithSingleThread() {
for (iteration in 0 until numberOfIterations) {
for (slice in 0 until 4) {
for (lane in 0 until parallelism) {
val segmentPosition = SegmentPosition(iteration, lane, slice)
processSegment(segmentPosition)
}
}
}
}
private fun processSegment(segmentPosition: SegmentPosition) {
val iteration = segmentPosition.iteration
val slice = segmentPosition.slice
val lane = segmentPosition.lane
var addressBlock: ArgonBlockPointer? = null
var addressCounter = 1UL //Starts from 1 in each segment as defined by the spec
//Generate initial segment address block
if (useIndependentAddressing) {
addressBlock = ArgonBlock().getBlockPointer()
addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock, addressCounter)
addressCounter++
}
val startColumn = if (iteration == 0 && slice == 0) {
2
} else {
slice * segmentLength
}
for (column in startColumn until (slice + 1) * segmentLength) {
val segmentIndex = column - (slice * segmentLength)
//Each address block contains 128 addresses, and we use one per iteration,
//so once we do 128 iterations we need to calculate a new address block
if (useIndependentAddressing && segmentIndex != 0 && segmentIndex % 128 == 0) {
addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock!!, addressCounter)
addressCounter++
}
val previousColumn = if (column == 0) {
columnCount - 1
} else {
column - 1
}
val (l, z) = computeReferenceBlockIndexes(
iteration,
slice,
lane,
column,
addressBlock
)
matrix.setBlockAt(lane, column,
compressionFunctionG(
matrix.getBlockPointer(lane, previousColumn),
matrix.getBlockPointer(l,z),
matrix.getBlockPointer(lane,column),
true
).getAsUByteArray()
)
}
}
}

View File

@ -18,8 +18,10 @@
package com.ionspin.kotlin.crypto.keyderivation.argon2 package com.ionspin.kotlin.crypto.keyderivation.argon2
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bPure
import com.ionspin.kotlin.crypto.util.* import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.BLOCK_SIZE
import com.ionspin.kotlin.crypto.util.plus
import com.ionspin.kotlin.crypto.util.rotateRight
/** /**
* Created by Ugljesa Jovanovic * Created by Ugljesa Jovanovic
@ -27,28 +29,28 @@ import com.ionspin.kotlin.crypto.util.*
* on 16-May-2020 * on 16-May-2020
*/ */
object Argon2Utils { object Argon2Utils {
const val BLOCK_SIZE = 1024
const val R1 = 32 const val R1 = 32
const val R2 = 24 const val R2 = 24
const val R3 = 16 const val R3 = 16
const val R4 = 63 const val R4 = 63
//based on Blake2b mixRound //Based on Blake2b mix
private fun mixRound(input: Array<UByte>): Array<ULong> { internal fun inplaceMixRound(v : ULongArray) : ULongArray{
var v = input.chunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray() mix(v, 0, 4, 8, 12)
v = mix(v, 0, 4, 8, 12) mix(v, 1, 5, 9, 13)
v = mix(v, 1, 5, 9, 13) mix(v, 2, 6, 10, 14)
v = mix(v, 2, 6, 10, 14) mix(v, 3, 7, 11, 15)
v = mix(v, 3, 7, 11, 15) mix(v, 0, 5, 10, 15)
v = mix(v, 0, 5, 10, 15) mix(v, 1, 6, 11, 12)
v = mix(v, 1, 6, 11, 12) mix(v, 2, 7, 8, 13)
v = mix(v, 2, 7, 8, 13) mix(v, 3, 4, 9, 14)
v = mix(v, 3, 4, 9, 14) return v //Just for chaining, array is mixed in place
return v
} }
//Based on Blake2b mix //Based on Blake2b mix
private fun mix(v: Array<ULong>, a: Int, b: Int, c: Int, d: Int): Array<ULong> { private fun mix(v: ULongArray, a: Int, b: Int, c: Int, d: Int) {
v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL))
v[d] = (v[d] xor v[a]) rotateRight R1 v[d] = (v[d] xor v[a]) rotateRight R1
v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL))
@ -57,11 +59,10 @@ object Argon2Utils {
v[d] = (v[d] xor v[a]) rotateRight R3 v[d] = (v[d] xor v[a]) rotateRight R3
v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL))
v[b] = (v[b] xor v[c]) rotateRight R4 v[b] = (v[b] xor v[c]) rotateRight R4
return v
} }
private fun extractColumnFromGBlock(gBlock: Array<UByte>, columnPosition: Int): Array<UByte> { internal fun extractColumnFromGBlock(gBlock: UByteArray, columnPosition: Int): UByteArray {
val result = Array<UByte>(128) { 0U } val result = UByteArray(128) { 0U }
for (i in 0..7) { for (i in 0..7) {
gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16) gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16)
.copyInto(result, i * 16) .copyInto(result, i * 16)
@ -69,69 +70,50 @@ object Argon2Utils {
return result return result
} }
private fun copyIntoGBlockColumn(gBlock: Array<UByte>, columnPosition: Int, columnData: Array<UByte>) {
for (i in 0..7) {
val column = columnData.copyOfRange(i * 16, i * 16 + 16)
column.copyInto(gBlock, i * 128 + columnPosition * 16)
}
}
internal fun compressionFunctionG( internal fun compressionFunctionG(
previousBlock: Array<UByte>, previousBlock: ArgonBlockPointer,
referenceBlock: Array<UByte>, referenceBlock: ArgonBlockPointer,
currentBlock: Array<UByte>, currentBlock: ArgonBlockPointer,
xorWithCurrentBlock: Boolean xorWithCurrentBlock: Boolean
): Array<UByte> { ): ArgonBlockPointer {
val r = referenceBlock xor previousBlock val r = (referenceBlock xorBlocksAndGetPointerToNewBlock previousBlock).getBlockPointer()
val q = Array<UByte>(1024) { 0U } //Since we are doing inplace xors, we don't need the Q that exists in specification
val z = Array<UByte>(1024) { 0U } val z = ArgonBlock().getBlockPointer()
// Do the argon/blake2b mixing on rows // Do the argon/blake2b mixing on rows
for (i in 0..7) { for (i in 0..7) {
val startOfRow = (i * 8 * 16) z.setRowFromMixedULongs(i, inplaceMixRound(r.getRowOfULongsForMixing(i)))
val endOfRow = startOfRow + (8 * 16)
val rowToMix = r.copyOfRange(startOfRow, endOfRow)
mixRound(rowToMix)
.map { it.toLittleEndianUByteArray() }
.flatMap { it.asIterable() }
.toTypedArray()
.copyInto(q, startOfRow)
} }
// Do the argon/blake2b mixing on columns // Do the argon/blake2b mixing on columns
for (i in 0..7) { for (i in 0..7) {
copyIntoGBlockColumn( z.setColumnFromMixedULongs(i, inplaceMixRound(z.getColumnOfULongsForMixing(i)))
z,
i,
mixRound(extractColumnFromGBlock(q, i))
.map { it.toLittleEndianUByteArray() }
.flatMap { it.asIterable() }
.toTypedArray()
)
} }
val final = if (xorWithCurrentBlock) { val final = if (xorWithCurrentBlock) {
(z xor r) xor currentBlock (z xorInplaceWith r) xorInplaceWith currentBlock
} else { } else {
z xor r z xorInplaceWith r
} }
return final return final
} }
internal fun argonBlake2bArbitraryLenghtHash(input: Array<UByte>, length: UInt): Array<UByte> { internal fun argonBlake2bArbitraryLenghtHash(input: UByteArray, length: UInt): UByteArray {
if (length <= 64U) { if (length <= 64U) {
return Blake2b.digest(inputMessage = length + input, hashLength = length.toInt()) return Blake2bPure.digest(inputMessage = length + input, hashLength = length.toInt())
} }
//We can cast to int because UInt even if MAX_VALUE divided by 32 is guaranteed not to overflow //We can cast to int because UInt even if MAX_VALUE divided by 32 is guaranteed not to overflow
val numberOf64ByteBlocks = (1U + ((length - 1U) / 32U) - 2U).toInt() // equivalent to ceil(length/32) - 2 val numberOf64ByteBlocks = (1U + ((length - 1U) / 32U) - 2U).toInt() // equivalent to ceil(length/32) - 2
val v = Array<Array<UByte>>(numberOf64ByteBlocks) { emptyArray() } val v = Array<UByteArray>(numberOf64ByteBlocks) { ubyteArrayOf() }
v[0] = Blake2b.digest(length + input) v[0] = Blake2bPure.digest(length + input)
for (i in 1 until numberOf64ByteBlocks) { for (i in 1 until numberOf64ByteBlocks) {
v[i] = Blake2b.digest(v[i - 1]) v[i] = Blake2bPure.digest(v[i - 1])
} }
val remainingPartOfInput = length.toInt() - numberOf64ByteBlocks * 32 val remainingPartOfInput = length.toInt() - numberOf64ByteBlocks * 32
val vLast = Blake2b.digest(v[numberOf64ByteBlocks - 1], hashLength = remainingPartOfInput) val vLast = Blake2bPure.digest(v[numberOf64ByteBlocks - 1], hashLength = remainingPartOfInput)
val concat = val concat =
(v.map { it.copyOfRange(0, 32) }) (v.map { it.copyOfRange(0, 32) })
.plus(listOf(vLast)) .plus(listOf(vLast))
.foldRight(emptyArray<UByte>()) { arrayOfUBytes, acc -> arrayOfUBytes + acc } .foldRight(ubyteArrayOf()) { arrayOfUBytes, acc -> arrayOfUBytes + acc }
return concat return concat
} }
@ -143,14 +125,14 @@ object Argon2Utils {
* tagLength, requested memory size and number of iterations, so no need to check for upper bound, just lower. * tagLength, requested memory size and number of iterations, so no need to check for upper bound, just lower.
*/ */
internal fun validateArgonParameters( internal fun validateArgonParameters(
password: Array<UByte>, password: UByteArray,
salt: Array<UByte>, salt: UByteArray,
parallelism: Int , parallelism: Int ,
tagLength: UInt, tagLength: UInt,
requestedMemorySize: UInt , requestedMemorySize: UInt ,
numberOfIterations: UInt , numberOfIterations: Int ,
key: Array<UByte>, key: UByteArray,
associatedData: Array<UByte>, associatedData: UByteArray,
argonType: ArgonType argonType: ArgonType
) { ) {
@ -170,9 +152,16 @@ object Argon2Utils {
throw Argon2MemoryTooLitlle(requestedMemorySize) throw Argon2MemoryTooLitlle(requestedMemorySize)
} }
//Number of iterations //Number of iterations
if (numberOfIterations <= 0U) { if (numberOfIterations <= 0) {
throw Argon2TimeTooShort(numberOfIterations) throw Argon2TimeTooShort(numberOfIterations)
} }
} }
} }
// ------------ Arithmetic and other utils
fun UByteArray.xorWithBlock(other : ArgonMatrix, rowPosition: Int, columnPosition: Int) : UByteArray {
return UByteArray(BLOCK_SIZE) { this[it] xor other[rowPosition, columnPosition, it] }
}

View File

@ -0,0 +1,285 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package com.ionspin.kotlin.crypto.keyderivation.argon2
import com.ionspin.kotlin.crypto.util.xor
/**
* Represents a pointer to a Argon2 Block, this abstracts what the backing structure is, a Argon 2 Matrix or
* or a UByteArray block
*/
interface ArgonBlockPointer {
companion object {
private val _emitLongArray : LongArray = longArrayOf(1L,2L)
}
val blockStartPosition: Int
fun pointerArithmetic(block : (Int, Int) -> Int) : ArgonBlockPointer
infix fun xorBlocksAndGetPointerToNewBlock(other: ArgonBlockPointer) : ArgonBlock
operator fun get(blockPosition: Int) : UByte
operator fun set(blockPosition: Int, value: UByte)
infix fun xorInplaceWith(other: ArgonBlockPointer) : ArgonBlockPointer {
for (it in 0 until 1024) {
this[it] = this[it] xor other[it]
}
return this //For chaining
}
fun asInt() : Int
fun getAsUByteArray() : UByteArray
}
fun ArgonBlockPointer._emitLongArray() : LongArray = longArrayOf(0,1)
fun ArgonBlockPointer.getRowOfULongsForMixing(rowIndex: Int) : ULongArray {
// Each row has 16 unsigned longs (16 ulongs * 8 bytes = 128 bytes) -- Argon2 considers this as 2 word unsigned
// numbers, so strictly speaking argon representation is 8 * 8 matrix of 2 word unsigned numbers (registers
val ulongArray = ULongArray(16)
for (columnIndex in 0 until 16) {
var ulong = 0UL
//Now we create the ulong
for (bytePosition in 0 until 8) {
ulong = ulong or (this[rowIndex * 128 + columnIndex * 8 + bytePosition].toULong() shl (bytePosition * 8))
}
ulongArray[columnIndex] = ulong
}
return ulongArray
}
fun ArgonBlockPointer.setRowFromMixedULongs(rowIndex: Int, ulongs: ULongArray) {
// Each row has 16 unsigned longs (16 ulongs * 8 bytes = 128 bytes) -- Argon2 considers this as 2 word unsigned
// numbers, so strictly speaking argon representation is 8 * 8 matrix of 2 word unsigned numbers (registers
for (columnIndex in 0 until 16) {
val ulongToConvert = ulongs[columnIndex]
for (bytePosition in 0 until 8) {
this[rowIndex * 128 + columnIndex * 8 + bytePosition] = ((ulongToConvert shr (bytePosition * 8)) and 0xFFU).toUByte()
}
}
}
fun ArgonBlockPointer.getColumnOfULongsForMixing(columnIndex: Int) : ULongArray {
//In Argon2 representation there are 8 double word registers (numbers, but we work with 16 single word ulongs
val ulongArray = ULongArray(16)
//There are 8 rows that consist of 2 words (registers, in our case ulongs) each
for (rowIndex in 0 until 8) {
var ulong = 0UL
//Now we create the ulong
for (bytePosition in 0 until 8) {
ulong = ulong or (this[rowIndex * 128 + columnIndex * 16 + bytePosition].toULong() shl (bytePosition * 8))
}
ulongArray[rowIndex * 2] = ulong
ulong = 0UL
// But unlike in columns where we can directly iterate and get all TWO WORD registers, here we also need to grab
// the next word
for (bytePosition in 8 until 16) {
ulong = ulong or (this[rowIndex * 128 + columnIndex * 16 + bytePosition].toULong() shl (bytePosition * 8))
}
ulongArray[rowIndex * 2 + 1] = ulong
}
return ulongArray
}
fun ArgonBlockPointer.setColumnFromMixedULongs(columnIndex: Int, ulongs: ULongArray) {
//In Argon2 representation there are 8 double word registers (numbers, but we work with 16 single word ulongs
//There are 8 rows that consist of 2 words (registers, in our case ulongs) each
var ulongToConvert = 0UL
for (rowIndex in 0 until 8) {
ulongToConvert = ulongs[rowIndex * 2]
//Now we create the ulong
for (bytePosition in 0 until 8) {
this[rowIndex * 128 + columnIndex * 16 + bytePosition] = ((ulongToConvert shr (bytePosition * 8)) and 0xFFU).toUByte()
}
// But unlike in columns where we can directly iterate and get all TWO WORD registers, here we also need to set
// the next word
ulongToConvert = ulongs[rowIndex * 2 + 1]
for (bytePosition in 8 until 16) {
this[rowIndex * 128 + columnIndex * 16 + bytePosition] = ((ulongToConvert shr (bytePosition * 8)) and 0xFFU).toUByte()
}
}
}
fun ArgonBlockPointer.getUIntFromPosition(positionInBlock: Int) : UInt {
var uint = 0U
for (i in 0 until 4) {
uint = uint or (this[positionInBlock + i].toUInt() shl (i * 8))
}
return uint
}
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-May-2020
*/
class ArgonMatrix(val columnCount: Int, val rowCount: Int) {
internal val storage: UByteArray = UByteArray(columnCount * rowCount * 1024)
operator fun get(rowPosition: Int, columnPosition: Int, inBlockPosition: Int) : UByte {
if (rowPosition > rowCount - 1) {
throw RuntimeException("Invalid row (lane) requested: $rowPosition, rowCount: $rowCount")
}
if (columnPosition > columnCount - 1) {
throw RuntimeException("Invalid column requested: $columnPosition, columnCount: $columnCount")
}
return storage[getBlockStartPositionPointer(rowPosition, columnPosition) + inBlockPosition]
}
val size = storage.size
operator fun get(absolutePosition: Int) : UByte {
return storage[absolutePosition]
}
operator fun set(rowPosition: Int, columnPosition: Int, inBlockPosition: Int, value: UByte) {
storage[getBlockStartPositionPointer(rowPosition, columnPosition) + inBlockPosition] = value
}
operator fun set(absolutePosition: Int, value: UByte) {
storage[absolutePosition] = value
}
fun getBlockPointer(rowPosition: Int, columnPosition: Int) : ArgonBlockPointer {
return ArgonBlockPointerWithMatrix(getBlockStartPositionPointer(rowPosition, columnPosition), this)
}
fun sliceArray(indices: IntRange): UByteArray {
return storage.sliceArray(indices)
}
fun getBlockAt(rowPosition: Int, columnPosition: Int) : UByteArray {
println("Expensive get")
return storage.copyOfRange(
getBlockStartPositionPointer(rowPosition, columnPosition),
getBlockStartPositionPointer(rowPosition, columnPosition) + 1024
)
}
fun setBlockAt(rowPosition: Int, columnPosition: Int, blockValue: UByteArray) {
blockValue.copyInto(
storage,
getBlockStartPositionPointer(rowPosition, columnPosition)
)
}
private inline fun getBlockStartPositionPointer(rowPosition: Int, columnPosition: Int) : Int {
return rowPosition * columnCount * 1024 + columnPosition * 1024
}
internal fun clearMatrix() {
for( index in storage.indices) { storage[index] = 0U }
}
private class ArgonBlockPointerWithMatrix constructor(override val blockStartPosition: Int, val matrix: ArgonMatrix) : ArgonBlockPointer {
override fun pointerArithmetic(block: (Int, Int) -> Int): ArgonBlockPointer {
return ArgonBlockPointerWithMatrix(block(blockStartPosition, matrix.size), matrix)
}
override fun asInt(): Int {
return blockStartPosition
}
override operator fun get(blockPosition: Int) : UByte {
return matrix[blockStartPosition + blockPosition]
}
override fun set(blockPosition: Int, value: UByte) {
matrix[blockStartPosition + blockPosition] = value
}
override infix fun xorBlocksAndGetPointerToNewBlock(other: ArgonBlockPointer) : ArgonBlock {
return ArgonBlock(UByteArray(1024){
matrix[blockStartPosition + it] xor other[it]
})
}
override fun getAsUByteArray(): UByteArray {
return matrix.storage.slice(blockStartPosition until blockStartPosition + 1024).toUByteArray()
}
}
}
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
inline class ArgonBlock internal constructor(internal val storage: UByteArray) {
constructor() : this(UByteArray(1024))
operator fun get(index: Int) : UByte {
return storage.get(index)
}
operator fun set(index: Int, value: UByte) {
storage.set(index, value)
}
val size: Int get() = storage.size
internal fun getAsUByteArray() : UByteArray = storage
fun getBlockPointer() : ArgonBlockPointer{
return ArgonBlockPointerWithBlock( this)
}
infix fun xorInplaceWith(other: ArgonBlock) : ArgonBlock {
storage.indices.forEach {
this[it] = this[it] xor other[it]
}
return this //For chaining
}
private class ArgonBlockPointerWithBlock constructor(val storageBlock: ArgonBlock) : ArgonBlockPointer {
override val blockStartPosition: Int = 0
override fun pointerArithmetic(block: (Int, Int) -> Int): ArgonBlockPointer {
throw RuntimeException("Haven't really tought out pointer arithmetic with blocks")
}
override fun asInt(): Int {
return blockStartPosition
}
override operator fun get(blockPosition: Int) : UByte {
return storageBlock[blockPosition]
}
override fun set(blockPosition: Int, value: UByte) {
storageBlock[blockPosition] = value
}
override infix fun xorBlocksAndGetPointerToNewBlock(other: ArgonBlockPointer) : ArgonBlock {
return ArgonBlock(UByteArray(1024){
storageBlock[it] xor other[it]
})
}
override fun getAsUByteArray(): UByteArray {
return storageBlock.storage
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.parallelization
import kotlin.time.ExperimentalTime
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-May-2020
*/
@ExperimentalTime
object Coroutines14 {
fun argonParallel() : Array<UByte> {
// val argon = Argon2()
// argon
println("Placeholder")
return emptyArray()
}
}

View File

@ -0,0 +1,241 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.symmetric
import com.ionspin.kotlin.crypto.SRNG
import com.ionspin.kotlin.crypto.util.xor
/**
* Advanced encryption standard with cipher block chaining and PKCS #5
*
* For bulk encryption/decryption use [AesCbcPure.encrypt] and [AesCbcPure.decrypt]
*
* To get an instance of AesCbc and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor]
*
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
class AesCbcPure internal constructor(val aesKey: AesKey, val mode: Mode, initializationVector: UByteArray? = null) {
companion object {
const val BLOCK_BYTES = 16
/**
* Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all
* data call [encrypt]
*/
fun createEncryptor(aesKey: AesKey) : AesCbcPure {
return AesCbcPure(aesKey, Mode.ENCRYPT)
}
/**
* Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all
* data call [decrypt]
*/
fun createDecryptor(aesKey : AesKey) : AesCbcPure {
return AesCbcPure(aesKey, Mode.DECRYPT)
}
/**
* Bulk encryption, returns encrypted data and a random initialization vector
*/
fun encrypt(aesKey: AesKey, data: UByteArray): EncryptedDataAndInitializationVector {
val aesCbc = AesCbcPure(aesKey, Mode.ENCRYPT)
aesCbc.addData(data)
return aesCbc.encrypt()
}
/**
* Bulk decryption, returns decrypted data
*/
fun decrypt(aesKey: AesKey, data: UByteArray, initialCounter: UByteArray? = null): UByteArray {
val aesCbc = AesCbcPure(aesKey, Mode.DECRYPT, initialCounter)
aesCbc.addData(data)
return aesCbc.decrypt()
}
private fun padToBlock(unpadded: UByteArray): UByteArray {
val paddingSize = 16 - unpadded.size
if (unpadded.size == BLOCK_BYTES) {
return unpadded
}
if (unpadded.size == BLOCK_BYTES) {
return UByteArray(BLOCK_BYTES) {
BLOCK_BYTES.toUByte()
}
}
if (unpadded.size > BLOCK_BYTES) {
throw IllegalStateException("Block larger than 128 bytes")
}
return UByteArray(BLOCK_BYTES) {
when (it) {
in unpadded.indices -> unpadded[it]
else -> paddingSize.toUByte()
}
}
}
}
var currentOutput: UByteArray = ubyteArrayOf()
var previousEncrypted: UByteArray = ubyteArrayOf()
val initVector = if (initializationVector.isNullOrEmpty()) {
SRNG.getRandomBytes(16)
} else {
initializationVector
}
val output = MutableList<UByteArray>(0) { ubyteArrayOf() }
var buffer: UByteArray = UByteArray(16) { 0U }
var bufferCounter = 0
fun addData(data: UByteArray) {
//Padding
when {
bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter)
bufferCounter + data.size >= BLOCK_BYTES -> {
val chunked = data.chunked(BLOCK_BYTES)
chunked.forEach { chunk ->
if (bufferCounter + chunk.size < BLOCK_BYTES) {
appendToBuffer(chunk.toUByteArray(), bufferCounter)
} else {
chunk.toUByteArray().copyInto(
destination = buffer,
destinationOffset = bufferCounter,
startIndex = 0,
endIndex = BLOCK_BYTES - bufferCounter
)
output += consumeBlock(buffer)
buffer = UByteArray(BLOCK_BYTES) {
when (it) {
in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> {
chunk[it + (BLOCK_BYTES - bufferCounter)]
}
else -> {
0U
}
}
}
bufferCounter = chunk.size - (BLOCK_BYTES - bufferCounter)
}
}
}
}
}
/**
* Encrypt fed data and return it alongside the randomly chosen initialization vector
* @return Encrypted data and initialization vector
*/
fun encrypt(): EncryptedDataAndInitializationVector {
if (bufferCounter > 0) {
val lastBlockPadded = padToBlock(buffer)
if (lastBlockPadded.size > BLOCK_BYTES) {
val chunks = lastBlockPadded.chunked(BLOCK_BYTES).map { it.toUByteArray() }
output += consumeBlock(chunks[0])
output += consumeBlock(chunks[1])
} else {
output += consumeBlock(lastBlockPadded)
}
}
return EncryptedDataAndInitializationVector(
output.reversed().foldRight(UByteArray(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes },
initVector
)
}
/**
* Decrypt data
* @return Decrypted data
*/
fun decrypt(): UByteArray {
val removePaddingCount = output.last().last()
val removedPadding = if (removePaddingCount > 0U && removePaddingCount < 16U) {
output.last().dropLast(removePaddingCount.toInt() and 0x7F)
} else {
output.last().toList()
}.toUByteArray()
val preparedOutput = (output.dropLast(1) + listOf(removedPadding))
//JS compiler freaks out here if we don't supply exact type
val reversed : List<UByteArray> = preparedOutput.reversed() as List<UByteArray>
val folded : UByteArray = reversed.foldRight(UByteArray(0) { 0U }) { uByteArray, acc ->
acc + uByteArray
}
return folded
}
private fun appendToBuffer(array: UByteArray, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size
}
private fun consumeBlock(data: UByteArray): UByteArray {
return when (mode) {
Mode.ENCRYPT -> {
currentOutput = if (currentOutput.isEmpty()) {
println("IV: $initVector")
AesPure.encrypt(aesKey, data xor initVector)
} else {
AesPure.encrypt(aesKey, data xor currentOutput)
}
currentOutput
}
Mode.DECRYPT -> {
if (currentOutput.isEmpty()) {
currentOutput = AesPure.decrypt(aesKey, data) xor initVector
} else {
currentOutput = AesPure.decrypt(aesKey, data) xor previousEncrypted
}
previousEncrypted = data
currentOutput
}
}
}
}
data class EncryptedDataAndInitializationVector(val encryptedData : UByteArray, val initilizationVector : UByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as EncryptedDataAndInitializationVector
if (!encryptedData.contentEquals(other.encryptedData)) return false
if (!initilizationVector.contentEquals(other.initilizationVector)) return false
return true
}
override fun hashCode(): Int {
var result = encryptedData.contentHashCode()
result = 31 * result + initilizationVector.contentHashCode()
return result
}
}

Some files were not shown because too many files have changed in this diff Show More