commit
2078775119
@ -14,7 +14,7 @@ matrix:
|
||||
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./linuxBuild.sh; fi'
|
||||
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./linuxBuildAndPublish.sh; fi'
|
||||
- os: osx
|
||||
osx_image: xcode11.2
|
||||
osx_image: xcode11.4
|
||||
language: java
|
||||
jdk: openjdk12
|
||||
# before_script:
|
||||
|
39
README.md
39
README.md
@ -40,11 +40,17 @@ It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be
|
||||
* SHA512
|
||||
* SHA256
|
||||
|
||||
## Symmetric cipher (Currently only available only in 0.0.3-SNAPSHOT)
|
||||
## Symmetric cipher
|
||||
* AES
|
||||
* Modes: CBC, CTR
|
||||
|
||||
## Key Derivation
|
||||
|
||||
More to come.
|
||||
* Argon2
|
||||
|
||||
## AEAD
|
||||
|
||||
TODO()
|
||||
|
||||
## Integration
|
||||
|
||||
@ -181,6 +187,35 @@ plainText == decrypted.toHexString()
|
||||
|
||||
```
|
||||
|
||||
### Key derivation
|
||||
|
||||
#### Argon2
|
||||
|
||||
NOTE: This implementation is tested against KAT generated by reference Argon2 implementation, which does not follow
|
||||
specification completely. See this issue https://github.com/P-H-C/phc-winner-argon2/issues/183
|
||||
|
||||
```kotlin
|
||||
val argon2Instance = Argon2(
|
||||
password = "Password",
|
||||
salt = "RandomSalt",
|
||||
parallelism = 8,
|
||||
tagLength = 64U,
|
||||
requestedMemorySize = 256U, //4GB
|
||||
numberOfIterations = 4U,
|
||||
key = "",
|
||||
associatedData = "",
|
||||
argonType = ArgonType.Argon2id
|
||||
)
|
||||
val tag = argon2Instance.derive()
|
||||
val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "")
|
||||
val expectedTagString = "c255e3e94305817d5e09a7c771e574e3a81cc78fef5da4a9644b6df0" +
|
||||
"0ba1c9b424e3dd0ce7e600b1269b14c84430708186a8a60403e1bfbda935991592b9ff37"
|
||||
println("Tag: ${tagString}")
|
||||
assertEquals(tagString, expectedTagString)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
object Versions {
|
||||
val kotlinCoroutines = "1.3.3"
|
||||
val kotlin = "1.3.61"
|
||||
val kotlinSerialization = "0.11.1"
|
||||
val kotlinCoroutines = "1.3.6"
|
||||
val kotlin = "1.3.72"
|
||||
val kotlinSerialization = "0.20.0"
|
||||
val nodePlugin = "1.3.0"
|
||||
val dokkaPlugin = "0.9.18"
|
||||
|
||||
val kotlinBigNumVersion = "0.1.5-SNAPSHOT"
|
||||
val kotlinBigNumVersion = "0.1.6-SNAPSHOT"
|
||||
|
||||
|
||||
}
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -16,6 +16,6 @@
|
||||
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
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.js.testing.KotlinJsTest
|
||||
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
|
||||
|
||||
plugins {
|
||||
@ -47,7 +49,25 @@ repositories {
|
||||
group = "com.ionspin.kotlin"
|
||||
version = "0.0.3-SNAPSHOT"
|
||||
|
||||
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 {
|
||||
val hostOsName = getHostOsName()
|
||||
if (ideaActive) {
|
||||
when(hostOsName) {
|
||||
"linux" -> linuxX64("native")
|
||||
"macos" -> macosX64("native")
|
||||
"windows" -> mingwX64("native")
|
||||
}
|
||||
}
|
||||
jvm()
|
||||
js {
|
||||
compilations {
|
||||
@ -63,13 +83,23 @@ kotlin {
|
||||
println("Destination dir ${it.compileKotlinTask.destinationDir}")
|
||||
}
|
||||
}
|
||||
nodejs() {
|
||||
//Until I figure out how to run headless chrome on travis
|
||||
// browser {
|
||||
//
|
||||
// testTask {
|
||||
// useKarma {
|
||||
// useChrome()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
nodejs {
|
||||
testTask {
|
||||
useMocha() {
|
||||
timeout = "10s"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
linuxX64("linux") {
|
||||
binaries {
|
||||
@ -115,14 +145,14 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mingwX86() {
|
||||
binaries {
|
||||
staticLib {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// No coroutines support for mingwX86
|
||||
// mingwX86() {
|
||||
// binaries {
|
||||
// staticLib {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
linuxArm32Hfp() {
|
||||
binaries {
|
||||
@ -153,7 +183,6 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(kotlin(Deps.Common.test))
|
||||
implementation(kotlin(Deps.Common.testAnnotation))
|
||||
implementation(Deps.Common.coroutines)
|
||||
}
|
||||
}
|
||||
val jvmMain by getting {
|
||||
@ -184,15 +213,35 @@ kotlin {
|
||||
implementation(kotlin("test-js"))
|
||||
}
|
||||
}
|
||||
val nativeMain by creating {
|
||||
dependsOn(commonMain)
|
||||
}
|
||||
val nativeTest by creating {
|
||||
dependsOn(commonTest)
|
||||
dependencies {
|
||||
implementation(Deps.Native.coroutines)
|
||||
val nativeMain = if (ideaActive) {
|
||||
val nativeMain by getting {
|
||||
dependsOn(commonMain)
|
||||
}
|
||||
nativeMain
|
||||
} else {
|
||||
val nativeMain by creating {
|
||||
dependsOn(commonMain)
|
||||
}
|
||||
nativeMain
|
||||
}
|
||||
val nativeTest = if (ideaActive) {
|
||||
val nativeTest by getting {
|
||||
dependsOn(commonTest)
|
||||
dependencies {
|
||||
implementation(Deps.Native.coroutines)
|
||||
}
|
||||
}
|
||||
nativeTest
|
||||
} else {
|
||||
val nativeTest by creating {
|
||||
dependsOn(commonTest)
|
||||
dependencies {
|
||||
implementation(Deps.Native.coroutines)
|
||||
}
|
||||
}
|
||||
nativeTest
|
||||
}
|
||||
|
||||
|
||||
val iosMain by getting {
|
||||
dependsOn(nativeMain)
|
||||
@ -227,21 +276,27 @@ kotlin {
|
||||
val linuxTest by getting {
|
||||
dependsOn(nativeTest)
|
||||
}
|
||||
// Coroutines don't support mingwx86 yet
|
||||
// val mingwX86Main by getting {
|
||||
// dependsOn(commonMain)
|
||||
// dependencies {
|
||||
// implementation(Deps.Native.coroutines)
|
||||
// }
|
||||
// }
|
||||
|
||||
val mingwX86Main by getting {
|
||||
dependsOn(nativeMain)
|
||||
}
|
||||
|
||||
val mingwX86Test by getting {
|
||||
dependsOn(nativeTest)
|
||||
}
|
||||
|
||||
// val mingwX86Test by getting {
|
||||
// dependsOn(commonTest)
|
||||
// }
|
||||
//
|
||||
val mingwX64Main by getting {
|
||||
dependsOn(nativeMain)
|
||||
dependsOn(commonMain)
|
||||
dependencies {
|
||||
implementation(Deps.Native.coroutines)
|
||||
}
|
||||
}
|
||||
|
||||
val mingwX64Test by getting {
|
||||
dependsOn(nativeTest)
|
||||
dependsOn(commonTest)
|
||||
}
|
||||
|
||||
val linuxArm32HfpMain by getting {
|
||||
@ -259,6 +314,9 @@ kotlin {
|
||||
val linuxArm64Test by getting {
|
||||
dependsOn(nativeTest)
|
||||
}
|
||||
all {
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -304,13 +362,42 @@ tasks {
|
||||
}
|
||||
}
|
||||
|
||||
val linuxTest by getting(KotlinNativeTest::class) {
|
||||
|
||||
testLogging {
|
||||
events("PASSED", "FAILED", "SKIPPED")
|
||||
// showStandardStreams = true
|
||||
}
|
||||
}
|
||||
|
||||
val mingwX64Test 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 jsBrowserTest by getting(KotlinJsTest::class) {
|
||||
//
|
||||
// testLogging {
|
||||
// events("PASSED", "FAILED", "SKIPPED")
|
||||
// showStandardStreams = true
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
signing {
|
||||
isRequired = false
|
||||
sign(publishing.publications)
|
||||
|
@ -1,90 +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.
|
||||
*/
|
||||
|
||||
package com.ionspin.kotlin.crypto
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
val printout = this.map { it.toString(16) }.chunked(16)
|
||||
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
|
||||
}
|
||||
|
||||
fun Array<ULong>.hexColumsPrint() {
|
||||
val printout = this.map { it.toString(16) }.chunked(3)
|
||||
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()
|
||||
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
infix fun UInt.rotateRight(places: Int): UInt {
|
||||
return (this shr places) xor (this shl (32 - places))
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
infix fun ULong.rotateRight(places: Int): ULong {
|
||||
return (this shr places) xor (this shl (64 - places))
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
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 this.copyOf().mapIndexed { index, it -> it xor other[index] }.toTypedArray()
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun String.hexStringToUByteArray() : Array<UByte> {
|
||||
return this.chunked(2).map { it.toUByte(16) }.toTypedArray()
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun Array<UByte>.toHexString() : String {
|
||||
return this.joinToString(separator = "") {
|
||||
if (it <= 0x0FU) {
|
||||
"0${it.toString(16)}"
|
||||
} else {
|
||||
it.toString(16)
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ import com.ionspin.kotlin.bignum.integer.toBigInteger
|
||||
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
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
@ -148,7 +150,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
|
||||
val keyBytes = key?.run {
|
||||
encodeToByteArray().map { it.toUByte() }.toTypedArray()
|
||||
} ?: emptyArray()
|
||||
return digest(inputMessage = array, key = keyBytes)
|
||||
return digest(inputMessage = array, key = keyBytes, hashLength = hashLength)
|
||||
|
||||
}
|
||||
|
||||
@ -157,6 +159,9 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
|
||||
key: Array<UByte>,
|
||||
hashLength: Int
|
||||
): Array<UByte> {
|
||||
if (hashLength > MAX_HASH_BYTES) {
|
||||
throw RuntimeException("Invalid hash length. Requested length more than maximum length. Requested length $hashLength")
|
||||
}
|
||||
val chunkedMessage = inputMessage.chunked(BLOCK_BYTES)
|
||||
|
||||
val h = iv.copyOf()
|
||||
@ -200,7 +205,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatab
|
||||
compress(h, lastBlockPadded, lastSize.toBigInteger(), true).copyInto(h)
|
||||
|
||||
|
||||
return formatResult(h)
|
||||
return formatResult(h).copyOfRange(0, hashLength)
|
||||
}
|
||||
|
||||
private fun formatResult(h: Array<ULong>): Array<UByte> {
|
||||
|
@ -16,10 +16,10 @@
|
||||
|
||||
package com.ionspin.kotlin.crypto.hash.sha
|
||||
|
||||
import com.ionspin.kotlin.crypto.chunked
|
||||
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.rotateRight
|
||||
import com.ionspin.kotlin.crypto.util.rotateRight
|
||||
|
||||
|
||||
/**
|
||||
|
@ -16,10 +16,10 @@
|
||||
|
||||
package com.ionspin.kotlin.crypto.hash.sha
|
||||
|
||||
import com.ionspin.kotlin.crypto.chunked
|
||||
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.rotateRight
|
||||
import com.ionspin.kotlin.crypto.util.rotateRight
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
|
@ -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.keyderivation
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 16-May-2020
|
||||
*/
|
||||
interface KeyDerivationFunction {
|
||||
fun derive() : Array<UByte>
|
||||
}
|
@ -0,0 +1,377 @@
|
||||
/*
|
||||
* 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.hash.blake2b.Blake2b
|
||||
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
|
||||
* 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
|
||||
)
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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.keyderivation.argon2
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 16-May-2020
|
||||
*/
|
||||
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 Argon2TimeTooShort(iterations: UInt) : 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 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 Argon2LanesTooFew(parallelism: Int) : RuntimeException("Too few, or invalid number of threads requested $parallelism")
|
||||
class Argon2LanesTooMany(parallelism: Int) : RuntimeException("Too many threads requested (parallelism). Requested: $parallelism")
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.hash.blake2b.Blake2b
|
||||
import com.ionspin.kotlin.crypto.util.*
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 16-May-2020
|
||||
*/
|
||||
object Argon2Utils {
|
||||
|
||||
const val R1 = 32
|
||||
const val R2 = 24
|
||||
const val R3 = 16
|
||||
const val R4 = 63
|
||||
|
||||
//based on Blake2b mixRound
|
||||
private fun mixRound(input: Array<UByte>): Array<ULong> {
|
||||
var v = input.chunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray()
|
||||
v = mix(v, 0, 4, 8, 12)
|
||||
v = mix(v, 1, 5, 9, 13)
|
||||
v = mix(v, 2, 6, 10, 14)
|
||||
v = mix(v, 3, 7, 11, 15)
|
||||
v = mix(v, 0, 5, 10, 15)
|
||||
v = mix(v, 1, 6, 11, 12)
|
||||
v = mix(v, 2, 7, 8, 13)
|
||||
v = mix(v, 3, 4, 9, 14)
|
||||
return v
|
||||
}
|
||||
|
||||
//Based on Blake2b mix
|
||||
private fun mix(v: Array<ULong>, a: Int, b: Int, c: Int, d: Int): Array<ULong> {
|
||||
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[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL))
|
||||
v[b] = (v[b] xor v[c]) rotateRight R2
|
||||
v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL))
|
||||
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[b] = (v[b] xor v[c]) rotateRight R4
|
||||
return v
|
||||
}
|
||||
|
||||
private fun extractColumnFromGBlock(gBlock: Array<UByte>, columnPosition: Int): Array<UByte> {
|
||||
val result = Array<UByte>(128) { 0U }
|
||||
for (i in 0..7) {
|
||||
gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16)
|
||||
.copyInto(result, i * 16)
|
||||
}
|
||||
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(
|
||||
previousBlock: Array<UByte>,
|
||||
referenceBlock: Array<UByte>,
|
||||
currentBlock: Array<UByte>,
|
||||
xorWithCurrentBlock: Boolean
|
||||
): Array<UByte> {
|
||||
val r = referenceBlock xor previousBlock
|
||||
val q = Array<UByte>(1024) { 0U }
|
||||
val z = Array<UByte>(1024) { 0U }
|
||||
// Do the argon/blake2b mixing on rows
|
||||
for (i in 0..7) {
|
||||
val startOfRow = (i * 8 * 16)
|
||||
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
|
||||
for (i in 0..7) {
|
||||
copyIntoGBlockColumn(
|
||||
z,
|
||||
i,
|
||||
mixRound(extractColumnFromGBlock(q, i))
|
||||
.map { it.toLittleEndianUByteArray() }
|
||||
.flatMap { it.asIterable() }
|
||||
.toTypedArray()
|
||||
)
|
||||
}
|
||||
val final = if (xorWithCurrentBlock) {
|
||||
(z xor r) xor currentBlock
|
||||
} else {
|
||||
z xor r
|
||||
}
|
||||
return final
|
||||
}
|
||||
|
||||
internal fun argonBlake2bArbitraryLenghtHash(input: Array<UByte>, length: UInt): Array<UByte> {
|
||||
if (length <= 64U) {
|
||||
return Blake2b.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
|
||||
val numberOf64ByteBlocks = (1U + ((length - 1U) / 32U) - 2U).toInt() // equivalent to ceil(length/32) - 2
|
||||
val v = Array<Array<UByte>>(numberOf64ByteBlocks) { emptyArray() }
|
||||
v[0] = Blake2b.digest(length + input)
|
||||
for (i in 1 until numberOf64ByteBlocks) {
|
||||
v[i] = Blake2b.digest(v[i - 1])
|
||||
}
|
||||
val remainingPartOfInput = length.toInt() - numberOf64ByteBlocks * 32
|
||||
val vLast = Blake2b.digest(v[numberOf64ByteBlocks - 1], hashLength = remainingPartOfInput)
|
||||
val concat =
|
||||
(v.map { it.copyOfRange(0, 32) })
|
||||
.plus(listOf(vLast))
|
||||
.foldRight(emptyArray<UByte>()) { arrayOfUBytes, acc -> arrayOfUBytes + acc }
|
||||
|
||||
return concat
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the argon 2 parameters.
|
||||
* Since Kotlin arrays that we are currently using cannot have more than 2^31 bytes, we don't need to check
|
||||
* sizes for password, salt, key and associated data. Also since UInt is 32bit we cant set more than 2^32-1 of
|
||||
* tagLength, requested memory size and number of iterations, so no need to check for upper bound, just lower.
|
||||
*/
|
||||
internal fun validateArgonParameters(
|
||||
password: Array<UByte>,
|
||||
salt: Array<UByte>,
|
||||
parallelism: Int ,
|
||||
tagLength: UInt,
|
||||
requestedMemorySize: UInt ,
|
||||
numberOfIterations: UInt ,
|
||||
key: Array<UByte>,
|
||||
associatedData: Array<UByte>,
|
||||
argonType: ArgonType
|
||||
) {
|
||||
|
||||
//Parallelism
|
||||
if (parallelism > 0xFFFFFF) {
|
||||
throw Argon2LanesTooMany(parallelism)
|
||||
}
|
||||
if (parallelism <= 0) {
|
||||
throw Argon2LanesTooFew(parallelism)
|
||||
}
|
||||
//Tag length
|
||||
if (tagLength <= 0U) {
|
||||
throw Argon2TagTooShort(tagLength)
|
||||
}
|
||||
//Requested memory
|
||||
if (requestedMemorySize < 8U || requestedMemorySize < (8 * parallelism).toUInt()) {
|
||||
throw Argon2MemoryTooLitlle(requestedMemorySize)
|
||||
}
|
||||
//Number of iterations
|
||||
if (numberOfIterations <= 0U) {
|
||||
throw Argon2TimeTooShort(numberOfIterations)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -17,8 +17,8 @@
|
||||
package com.ionspin.kotlin.crypto.symmetric
|
||||
|
||||
import com.ionspin.kotlin.crypto.SRNG
|
||||
import com.ionspin.kotlin.crypto.chunked
|
||||
import com.ionspin.kotlin.crypto.xor
|
||||
import com.ionspin.kotlin.crypto.util.chunked
|
||||
import com.ionspin.kotlin.crypto.util.xor
|
||||
|
||||
/**
|
||||
* Advanced encryption standard with cipher block chaining and PKCS #5
|
||||
|
@ -20,9 +20,9 @@ import com.ionspin.kotlin.bignum.Endianness
|
||||
import com.ionspin.kotlin.bignum.integer.BigInteger
|
||||
import com.ionspin.kotlin.bignum.modular.ModularBigInteger
|
||||
import com.ionspin.kotlin.crypto.SRNG
|
||||
import com.ionspin.kotlin.crypto.chunked
|
||||
import com.ionspin.kotlin.crypto.util.chunked
|
||||
import com.ionspin.kotlin.crypto.symmetric.AesCtr.Companion.encrypt
|
||||
import com.ionspin.kotlin.crypto.xor
|
||||
import com.ionspin.kotlin.crypto.util.xor
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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 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()
|
||||
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
infix fun UInt.rotateRight(places: Int): UInt {
|
||||
return (this shr places) xor (this shl (32 - places))
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
infix fun ULong.rotateRight(places: Int): ULong {
|
||||
return (this shr places) xor (this shl (64 - places))
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
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] }
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun String.hexStringToUByteArray() : Array<UByte> {
|
||||
return this.chunked(2).map { it.toUByte(16) }.toTypedArray()
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun Array<UByte>.toHexString() : String {
|
||||
return this.joinToString(separator = "") {
|
||||
if (it <= 0x0FU) {
|
||||
"0${it.toString(16)}"
|
||||
} else {
|
||||
it.toString(16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UInt / Array utils
|
||||
@ExperimentalUnsignedTypes
|
||||
fun UInt.toBigEndianUByteArray() : Array<UByte> {
|
||||
return Array<UByte> (4) {
|
||||
((this shr (24 - (it * 8))) and 0xFFU).toUByte()
|
||||
}
|
||||
}
|
||||
@ExperimentalUnsignedTypes
|
||||
fun UInt.toLittleEndianUByteArray() : Array<UByte> {
|
||||
return Array<UByte> (4) {
|
||||
((this shr (it * 8)) and 0xFFU).toUByte()
|
||||
}
|
||||
}
|
||||
|
||||
// UInt / Array utils
|
||||
@ExperimentalUnsignedTypes
|
||||
fun ULong.toBigEndianUByteArray() : Array<UByte> {
|
||||
return Array<UByte> (8) {
|
||||
((this shr (56 - (it * 8))) and 0xFFU).toUByte()
|
||||
}
|
||||
}
|
||||
@ExperimentalUnsignedTypes
|
||||
fun ULong.toLittleEndianUByteArray() : Array<UByte> {
|
||||
return Array<UByte> (8) {
|
||||
((this shr (it * 8)) and 0xFFU).toUByte()
|
||||
}
|
||||
}
|
||||
@ExperimentalUnsignedTypes
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
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
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
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
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
operator fun UInt.plus(other : Array<UByte>) : Array<UByte> {
|
||||
return this.toLittleEndianUByteArray() + other
|
||||
}
|
@ -19,8 +19,10 @@ package com.ionspin.kotlin.crypto
|
||||
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b
|
||||
import com.ionspin.kotlin.crypto.hash.sha.Sha256
|
||||
import com.ionspin.kotlin.crypto.hash.sha.Sha512
|
||||
import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2
|
||||
import com.ionspin.kotlin.crypto.keyderivation.argon2.ArgonType
|
||||
import kotlin.test.Test
|
||||
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
@ -73,7 +75,7 @@ class ReadmeTest {
|
||||
@ExperimentalStdlibApi
|
||||
@Test
|
||||
fun sha256Example() {
|
||||
val input ="abc"
|
||||
val input = "abc"
|
||||
val result = Sha256.digest(inputString = input)
|
||||
val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
|
||||
assertTrue {
|
||||
@ -86,9 +88,9 @@ class ReadmeTest {
|
||||
@ExperimentalStdlibApi
|
||||
@Test
|
||||
fun sha512Example() {
|
||||
val input ="abc"
|
||||
val input = "abc"
|
||||
val result = Sha512.digest(inputMessage = input.encodeToByteArray().map { it.toUByte() }.toTypedArray())
|
||||
println(result.map {it.toString(16)})
|
||||
println(result.map { it.toString(16) })
|
||||
val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" +
|
||||
"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
|
||||
assertTrue {
|
||||
@ -124,4 +126,26 @@ class ReadmeTest {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun argon2StringExample() {
|
||||
val argon2Instance = Argon2(
|
||||
password = "Password",
|
||||
salt = "RandomSalt",
|
||||
parallelism = 4,
|
||||
tagLength = 64U,
|
||||
requestedMemorySize = 32U, //Travis build on mac fails with higher values
|
||||
numberOfIterations = 4U,
|
||||
key = "",
|
||||
associatedData = "",
|
||||
argonType = ArgonType.Argon2id
|
||||
)
|
||||
val tag = argon2Instance.derive()
|
||||
val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "")
|
||||
val expectedTagString = "ca134003c9f9f76ca8869359c1d9065603ec54ac30f5158f06af647cacaef2c1c3e" +
|
||||
"c71e81960278c0596febc64125acbbe5959146db1c128199a1b7cb38982a9"
|
||||
println("Tag: ${tagString}")
|
||||
assertEquals(tagString, expectedTagString)
|
||||
|
||||
}
|
||||
}
|
@ -27,9 +27,11 @@ import kotlin.test.assertTrue
|
||||
class SRNGTest {
|
||||
@Test
|
||||
fun testSrng() {
|
||||
//Just a sanity test, need to add better srng tests.
|
||||
val randomBytes1 = SRNG.getRandomBytes(10)
|
||||
val randomBytes2 = SRNG.getRandomBytes(10)
|
||||
// assertTrue { !randomBytes1.contentEquals(randomBytes2) }
|
||||
//TODO implement SRNG for minGW
|
||||
randomBytes1.forEach { println("RB1: $it")}
|
||||
randomBytes2.forEach { println("RB2: $it")}
|
||||
assertTrue { !randomBytes1.contentEquals(randomBytes2) }
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
package com.ionspin.kotlin.crypto.hash.blake2b
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
@ -280,4 +281,13 @@ class Blake2BTest {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInvalidHashLength() {
|
||||
val test = "1234567890"
|
||||
assertFailsWith(RuntimeException::class) {
|
||||
val result = Blake2b.digest(inputString = test, hashLength = 65)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package com.ionspin.kotlin.crypto.hash.keyderivation
|
||||
|
||||
import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2
|
||||
import com.ionspin.kotlin.crypto.keyderivation.argon2.ArgonType
|
||||
import com.ionspin.kotlin.crypto.util.hexColumsPrint
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 10-May-2020
|
||||
*/
|
||||
@ExperimentalStdlibApi
|
||||
class Argon2Test {
|
||||
|
||||
@Test
|
||||
fun argon2dKATTest() {
|
||||
val expected : Array<UByte> = arrayOf(
|
||||
0x51U, 0x2BU, 0x39U, 0x1BU, 0x6FU, 0x11U, 0x62U, 0x97U,
|
||||
0x53U, 0x71U, 0xD3U, 0x09U, 0x19U, 0x73U, 0x42U, 0x94U,
|
||||
0xF8U, 0x68U, 0xE3U, 0xBEU, 0x39U, 0x84U, 0xF3U, 0xC1U,
|
||||
0xA1U, 0x3AU, 0x4DU, 0xB9U, 0xFAU, 0xBEU, 0x4AU, 0xCBU
|
||||
)
|
||||
|
||||
|
||||
val memory = 32U //KiB
|
||||
val iterations = 3U
|
||||
val parallelism = 4U
|
||||
val tagLength = 32U
|
||||
val password: Array<UByte> = arrayOf(
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U
|
||||
)
|
||||
val salt: Array<UByte> = arrayOf(0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U)
|
||||
val secret: Array<UByte> = arrayOf(0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U)
|
||||
val associatedData: Array<UByte> = arrayOf(0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U)
|
||||
|
||||
val digest = Argon2(
|
||||
password,
|
||||
salt,
|
||||
parallelism.toInt(),
|
||||
tagLength,
|
||||
memory,
|
||||
iterations,
|
||||
secret,
|
||||
associatedData,
|
||||
ArgonType.Argon2d
|
||||
)
|
||||
val result = digest.derive()
|
||||
result.hexColumsPrint(8)
|
||||
assertTrue { expected.contentEquals(result) }
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun argon2iKATTest() {
|
||||
val expected : Array<UByte> = arrayOf(
|
||||
0xc8U, 0x14U, 0xd9U, 0xd1U, 0xdcU, 0x7fU, 0x37U, 0xaaU,
|
||||
0x13U, 0xf0U, 0xd7U, 0x7fU, 0x24U, 0x94U, 0xbdU, 0xa1U,
|
||||
0xc8U, 0xdeU, 0x6bU, 0x01U, 0x6dU, 0xd3U, 0x88U, 0xd2U,
|
||||
0x99U, 0x52U, 0xa4U, 0xc4U, 0x67U, 0x2bU, 0x6cU, 0xe8U
|
||||
)
|
||||
|
||||
|
||||
val memory = 32U //KiB
|
||||
val iterations = 3U
|
||||
val parallelism = 4U
|
||||
val tagLength = 32U
|
||||
val password: Array<UByte> = arrayOf(
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U
|
||||
)
|
||||
val salt: Array<UByte> = arrayOf(0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U)
|
||||
val secret: Array<UByte> = arrayOf(0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U)
|
||||
val associatedData: Array<UByte> = arrayOf(0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U)
|
||||
|
||||
val digest = Argon2(
|
||||
password,
|
||||
salt,
|
||||
parallelism.toInt(),
|
||||
tagLength,
|
||||
memory,
|
||||
iterations,
|
||||
secret,
|
||||
associatedData,
|
||||
ArgonType.Argon2i
|
||||
)
|
||||
val result = digest.derive()
|
||||
result.hexColumsPrint(8)
|
||||
assertTrue { expected.contentEquals(result) }
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun argon2idKATTest() {
|
||||
val expected : Array<UByte> = arrayOf(
|
||||
0x0dU, 0x64U, 0x0dU, 0xf5U, 0x8dU, 0x78U, 0x76U, 0x6cU,
|
||||
0x08U, 0xc0U, 0x37U, 0xa3U, 0x4aU, 0x8bU, 0x53U, 0xc9U,
|
||||
0xd0U, 0x1eU, 0xf0U, 0x45U, 0x2dU, 0x75U, 0xb6U, 0x5eU,
|
||||
0xb5U, 0x25U, 0x20U, 0xe9U, 0x6bU, 0x01U, 0xe6U, 0x59U
|
||||
)
|
||||
|
||||
|
||||
val memory = 32U //KiB
|
||||
val iterations = 3U
|
||||
val parallelism = 4U
|
||||
val tagLength = 32U
|
||||
val password: Array<UByte> = arrayOf(
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U
|
||||
)
|
||||
val salt: Array<UByte> = arrayOf(0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U)
|
||||
val secret: Array<UByte> = arrayOf(0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U)
|
||||
val associatedData: Array<UByte> = arrayOf(0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U)
|
||||
|
||||
val digest = Argon2(
|
||||
password,
|
||||
salt,
|
||||
parallelism.toInt(),
|
||||
tagLength,
|
||||
memory,
|
||||
iterations,
|
||||
secret,
|
||||
associatedData,
|
||||
ArgonType.Argon2id
|
||||
)
|
||||
val result = digest.derive()
|
||||
result.hexColumsPrint(8)
|
||||
assertTrue { expected.contentEquals(result) }
|
||||
|
||||
}
|
||||
}
|
@ -16,8 +16,8 @@
|
||||
|
||||
package com.ionspin.kotlin.crypto.symmetric
|
||||
|
||||
import com.ionspin.kotlin.crypto.hexStringToUByteArray
|
||||
import com.ionspin.kotlin.crypto.toHexString
|
||||
import com.ionspin.kotlin.crypto.util.hexStringToUByteArray
|
||||
import com.ionspin.kotlin.crypto.util.toHexString
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
package com.ionspin.kotlin.crypto.symmetric
|
||||
|
||||
import com.ionspin.kotlin.crypto.hexStringToUByteArray
|
||||
import com.ionspin.kotlin.crypto.toHexString
|
||||
import com.ionspin.kotlin.crypto.util.hexStringToUByteArray
|
||||
import com.ionspin.kotlin.crypto.util.toHexString
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 17-Jul-2019
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
class UtilTest {
|
||||
|
||||
@Test
|
||||
fun testSlicer() {
|
||||
val array = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
|
||||
val chunked = array.chunked(2)
|
||||
assertTrue {
|
||||
chunked.size == 9 && chunked[8][0] == 17
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUIntToBigEndianArray() {
|
||||
assertTrue {
|
||||
val original = 1U
|
||||
val converted = original.toBigEndianUByteArray()
|
||||
converted[0] = 1U
|
||||
true
|
||||
}
|
||||
assertTrue {
|
||||
val original = 0xAABBCCDDU
|
||||
val converted = original.toBigEndianUByteArray()
|
||||
converted[0] == 0xAAU.toUByte() &&
|
||||
converted[1] == 0xBBU.toUByte() &&
|
||||
converted[2] == 0xCCU.toUByte() &&
|
||||
converted[3] == 0xDDU.toUByte()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUIntToLittleEndianArray() {
|
||||
assertTrue {
|
||||
val original = 1U
|
||||
val converted = original.toLittleEndianUByteArray()
|
||||
converted[3] = 1U
|
||||
true
|
||||
}
|
||||
assertTrue {
|
||||
val original = 0xAABBCCDDU
|
||||
val converted = original.toLittleEndianUByteArray()
|
||||
converted[0] == 0xDDU.toUByte() &&
|
||||
converted[1] == 0xCCU.toUByte() &&
|
||||
converted[2] == 0xBBU.toUByte() &&
|
||||
converted[3] == 0xAAU.toUByte()
|
||||
|
||||
}
|
||||
assertTrue {
|
||||
val original = 123456U
|
||||
val converted = original.toLittleEndianUByteArray()
|
||||
converted[0] == 0x40U.toUByte() &&
|
||||
converted[1] == 0xE2U.toUByte() &&
|
||||
converted[2] == 0x01U.toUByte() &&
|
||||
converted[3] == 0x00U.toUByte()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromBigEndianByteArrayToLong() {
|
||||
|
||||
assertTrue {
|
||||
val ubyteArray = ubyteArrayOf(0xA1U, 0xA2U, 0xB1U, 0xB2U, 0xC1U, 0xC2U, 0xD1U, 0xD2U).toTypedArray()
|
||||
val expected = 0xA1A2B1B2C1C2D1D2U
|
||||
val reconstructed = ubyteArray.fromBigEndianArrayToULong();
|
||||
reconstructed == expected
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromLittleEndianByteArrayToLong() {
|
||||
|
||||
assertTrue {
|
||||
val ubyteArray = ubyteArrayOf(0xA1U, 0xA2U, 0xB1U, 0xB2U, 0xC1U, 0xC2U, 0xD1U, 0xD2U).toTypedArray()
|
||||
val expected = 0xD2D1C2C1B2B1A2A1UL
|
||||
val reconstructed = ubyteArray.fromLittleEndianArrayToULong();
|
||||
reconstructed == expected
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -25,12 +25,27 @@ actual object SRNG {
|
||||
var counter = 0
|
||||
@ExperimentalUnsignedTypes
|
||||
actual fun getRandomBytes(amount: Int): Array<UByte> {
|
||||
// val runningOnNode = js("(typeof window === 'undefined')").unsafeCast<Boolean>()
|
||||
// if (runningOnNode) {
|
||||
// js("var crypto = require('crypto')").asDynamic().randomBytes(amount)
|
||||
// } else {
|
||||
// throw RuntimeException("Secure random not supported yet for non-nodejs environment")
|
||||
// }
|
||||
return Array<UByte>(amount) { (counter++).toUByte() } // TODO Wow. Such random. Very entropy.
|
||||
val runningOnNode = js(
|
||||
"if (typeof window === 'undefined') {\n" +
|
||||
" true;\n" +
|
||||
" } else {\n" +
|
||||
" false;\n" +
|
||||
" }"
|
||||
)
|
||||
val randomBytes = if (runningOnNode) {
|
||||
js("require('crypto')").randomBytes(amount).toJSON().data
|
||||
} else {
|
||||
js(
|
||||
"""
|
||||
var randomArray = new Uint8Array(amount);
|
||||
var crypto = (self.crypto || self.msCrypto);
|
||||
crypto.getRandomValues(randomArray);
|
||||
"""
|
||||
)
|
||||
var randomArrayResult = js("Array.prototype.slice.call(randomArray);")
|
||||
randomArrayResult
|
||||
}
|
||||
|
||||
return randomBytes as Array<UByte>
|
||||
}
|
||||
}
|
@ -16,23 +16,26 @@
|
||||
|
||||
package com.ionspin.kotlin.crypto
|
||||
|
||||
import com.ionspin.kotlin.crypto.chunked
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 17-Jul-2019
|
||||
* on 05-Jan-2020
|
||||
*/
|
||||
class UtilTest {
|
||||
@ExperimentalUnsignedTypes
|
||||
class SRNGJsTest {
|
||||
|
||||
@Test
|
||||
fun testSlicer() {
|
||||
val array = arrayOf(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17)
|
||||
val chunked = array.chunked(2)
|
||||
fun testJsSrng() {
|
||||
val bytes1 = SRNG.getRandomBytes(10)
|
||||
val bytes2 = SRNG.getRandomBytes(10)
|
||||
assertTrue {
|
||||
chunked.size == 9 && chunked[8][0] == 17
|
||||
!bytes1.contentEquals(bytes2) &&
|
||||
bytes1.size == 10 &&
|
||||
bytes2.size == 10
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 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): Array<UByte> {
|
||||
memScoped {
|
||||
val randArray = allocArray<ByteVar>(amount)
|
||||
val pointer = randArray.getPointer(this)
|
||||
val status = advapiRandom(pointer.reinterpret(), amount.convert())
|
||||
return Array<UByte>(amount) { pointer[it].toUByte() }
|
||||
}
|
||||
}
|
||||
}
|
@ -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 (scope: CoroutineScope) -> Unit) = runBlocking { block(this) }
|
Loading…
x
Reference in New Issue
Block a user