Adding delegated implementation
This commit is contained in:
1 parent
3cfbd0ea63
commit
7238b09112
326 files changed
+109874
-20
No files matched your search
+26
@@ -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
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.ionspin.kotlin.crypto
|
||||
|
||||
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 Sha256Stateless = Sha256Pure.Companion
|
||||
typealias Sha512Stateless = Sha512Pure.Companion
|
||||
+12
@@ -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
|
||||
+26
@@ -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
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.ionspin.kotlin.bignum.integer.BigInteger
|
||||
import com.ionspin.kotlin.bignum.integer.toBigInteger
|
||||
import com.ionspin.kotlin.crypto.*
|
||||
import com.ionspin.kotlin.crypto.util.rotateRight
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 14-Jul-2019
|
||||
*/
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
expect class Blake2bDelegated(key: UByteArray? = null, hashLength: Int = 64) : Blake2b
|
||||
|
||||
expect class Blake2bStateless : Blake2bStatelessInterface
|
||||
|
||||
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.ionspin.kotlin.crypto.hash.StatelessHash
|
||||
import com.ionspin.kotlin.crypto.hash.UpdatableHash
|
||||
import com.ionspin.kotlin.crypto.util.rotateRight
|
||||
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 17-Jul-2019
|
||||
*/
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
class Sha256Pure : Sha256 {
|
||||
|
||||
override val MAX_HASH_BYTES: Int = 32
|
||||
|
||||
|
||||
companion object : StatelessSha256 {
|
||||
const val BLOCK_SIZE = 512
|
||||
const val BLOCK_SIZE_IN_BYTES = 64
|
||||
const val UINT_MASK = 0xFFFFFFFFU
|
||||
const val BYTE_MASK_FROM_ULONG = 0xFFUL
|
||||
const val BYTE_MASK_FROM_UINT = 0xFFU
|
||||
|
||||
override val MAX_HASH_BYTES: Int = 32
|
||||
|
||||
val iv = arrayOf(
|
||||
0x6a09e667U,
|
||||
0xbb67ae85U,
|
||||
0x3c6ef372U,
|
||||
0xa54ff53aU,
|
||||
0x510e527fU,
|
||||
0x9b05688cU,
|
||||
0x1f83d9abU,
|
||||
0x5be0cd19U
|
||||
)
|
||||
|
||||
val k = arrayOf(
|
||||
0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU, 0x59f111f1U, 0x923f82a4U, 0xab1c5ed5U,
|
||||
0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU, 0x9bdc06a7U, 0xc19bf174U,
|
||||
0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, 0x2de92c6fU, 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU,
|
||||
0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U,
|
||||
0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU, 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U,
|
||||
0xa2bfe8a1U, 0xa81a664bU, 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U,
|
||||
0x19a4c116U, 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U,
|
||||
0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U
|
||||
)
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
override fun digest(inputString: String, key: String?, hashLength: Int): UByteArray {
|
||||
return digest(
|
||||
inputString.encodeToByteArray().toUByteArray(),
|
||||
key?.run { encodeToByteArray().toUByteArray()} ?: ubyteArrayOf(),
|
||||
hashLength)
|
||||
}
|
||||
|
||||
override fun digest(inputMessage: UByteArray, key: UByteArray, hashLength: Int): UByteArray {
|
||||
|
||||
var h = iv.copyOf()
|
||||
|
||||
val expansionArray = createExpansionArray(inputMessage.size)
|
||||
|
||||
val chunks = (
|
||||
inputMessage +
|
||||
expansionArray +
|
||||
(inputMessage.size * 8).toULong().toPaddedByteArray()
|
||||
)
|
||||
.chunked(BLOCK_SIZE_IN_BYTES)
|
||||
|
||||
chunks.forEach { chunk ->
|
||||
val w = expandChunk(chunk.toUByteArray())
|
||||
mix(h, w).copyInto(h)
|
||||
|
||||
}
|
||||
|
||||
val digest = h[0].toPaddedByteArray() +
|
||||
h[1].toPaddedByteArray() +
|
||||
h[2].toPaddedByteArray() +
|
||||
h[3].toPaddedByteArray() +
|
||||
h[4].toPaddedByteArray() +
|
||||
h[5].toPaddedByteArray() +
|
||||
h[6].toPaddedByteArray() +
|
||||
h[7].toPaddedByteArray()
|
||||
return digest
|
||||
}
|
||||
|
||||
private fun scheduleSigma0(value: UInt): UInt {
|
||||
return value.rotateRight(7) xor value.rotateRight(18) xor (value shr 3)
|
||||
}
|
||||
|
||||
private fun scheduleSigma1(value: UInt): UInt {
|
||||
return value.rotateRight(17) xor value.rotateRight(19) xor (value shr 10)
|
||||
}
|
||||
|
||||
private fun compressionSigma0(a: UInt): UInt {
|
||||
return (a rotateRight 2) xor (a rotateRight 13) xor (a rotateRight 22)
|
||||
}
|
||||
|
||||
private fun compressionSigma1(e: UInt): UInt {
|
||||
return (e rotateRight 6) xor (e rotateRight 11) xor (e rotateRight 25)
|
||||
}
|
||||
|
||||
private fun ch(x: UInt, y: UInt, z: UInt): UInt {
|
||||
return ((x and y) xor ((x xor UINT_MASK) and z))
|
||||
}
|
||||
|
||||
private fun maj(x: UInt, y: UInt, z: UInt): UInt {
|
||||
return (((x and y) xor (x and z) xor (y and z)))
|
||||
}
|
||||
|
||||
private fun expandChunk(chunk: UByteArray): Array<UInt> {
|
||||
val w = Array<UInt>(BLOCK_SIZE_IN_BYTES) {
|
||||
when (it) {
|
||||
in 0 until 16 -> {
|
||||
var collected = (chunk[(it * 4)].toUInt() shl 24) +
|
||||
(chunk[(it * 4) + 1].toUInt() shl 16) +
|
||||
(chunk[(it * 4) + 2].toUInt() shl 8) +
|
||||
(chunk[(it * 4) + 3].toUInt())
|
||||
collected
|
||||
}
|
||||
else -> 0U
|
||||
}
|
||||
}
|
||||
for (i in 16 until BLOCK_SIZE_IN_BYTES) {
|
||||
val s0 = scheduleSigma0(w[i - 15])
|
||||
val s1 = scheduleSigma1(w[i - 2])
|
||||
w[i] = w[i - 16] + s0 + w[i - 7] + s1
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
private fun mix(h: Array<UInt>, w: Array<UInt>): Array<UInt> {
|
||||
var paramA = h[0]
|
||||
var paramB = h[1]
|
||||
var paramC = h[2]
|
||||
var paramD = h[3]
|
||||
var paramE = h[4]
|
||||
var paramF = h[5]
|
||||
var paramG = h[6]
|
||||
var paramH = h[7]
|
||||
|
||||
for (i in 0 until BLOCK_SIZE_IN_BYTES) {
|
||||
val s1 = compressionSigma1(paramE)
|
||||
val ch = ch(paramE, paramF, paramG)
|
||||
val temp1 = paramH + s1 + ch + k[i] + w[i]
|
||||
val s0 = compressionSigma0(paramA)
|
||||
val maj = maj(paramA, paramB, paramC)
|
||||
val temp2 = s0 + maj
|
||||
paramH = paramG
|
||||
paramG = paramF
|
||||
paramF = paramE
|
||||
paramE = paramD + temp1
|
||||
paramD = paramC
|
||||
paramC = paramB
|
||||
paramB = paramA
|
||||
paramA = temp1 + temp2
|
||||
}
|
||||
|
||||
h[0] += paramA
|
||||
h[1] += paramB
|
||||
h[2] += paramC
|
||||
h[3] += paramD
|
||||
h[4] += paramE
|
||||
h[5] += paramF
|
||||
h[6] += paramG
|
||||
h[7] += paramH
|
||||
return h
|
||||
}
|
||||
|
||||
|
||||
fun createExpansionArray(originalSizeInBytes: Int): UByteArray {
|
||||
val originalMessageSizeInBits = originalSizeInBytes * 8
|
||||
|
||||
|
||||
//K such that L + 1 + K + 64 is a multiple of 512
|
||||
val expandedRemainderOf512 = (originalMessageSizeInBits + BLOCK_SIZE_IN_BYTES + 1) % BLOCK_SIZE
|
||||
val zeroAddAmount = when (expandedRemainderOf512) {
|
||||
0 -> 0
|
||||
else -> (BLOCK_SIZE - expandedRemainderOf512) / 8
|
||||
}
|
||||
val expansionArray = UByteArray(zeroAddAmount + 1) {
|
||||
when (it) {
|
||||
0 -> 0b10000000U
|
||||
else -> 0U
|
||||
}
|
||||
}
|
||||
return expansionArray
|
||||
}
|
||||
|
||||
private fun ULong.toPaddedByteArray(): UByteArray {
|
||||
val byteMask = BYTE_MASK_FROM_ULONG
|
||||
return UByteArray(8) {
|
||||
when (it) {
|
||||
7 -> (this and byteMask).toUByte()
|
||||
6 -> ((this shr 8) and byteMask).toUByte()
|
||||
5 -> ((this shr 16) and byteMask).toUByte()
|
||||
4 -> ((this shr 24) and byteMask).toUByte()
|
||||
3 -> ((this shr 32) and byteMask).toUByte()
|
||||
2 -> ((this shr 40) and byteMask).toUByte()
|
||||
1 -> ((this shr 48) and byteMask).toUByte()
|
||||
0 -> ((this shr 54) and byteMask).toUByte()
|
||||
else -> throw RuntimeException("Invalid conversion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun UInt.toPaddedByteArray(): UByteArray {
|
||||
val byteMask = BYTE_MASK_FROM_UINT
|
||||
return UByteArray(4) {
|
||||
when (it) {
|
||||
3 -> (this and byteMask).toUByte()
|
||||
2 -> ((this shr 8) and byteMask).toUByte()
|
||||
1 -> ((this shr 16) and byteMask).toUByte()
|
||||
0 -> ((this shr 24) and byteMask).toUByte()
|
||||
else -> throw RuntimeException("Invalid conversion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var h = iv.copyOf()
|
||||
var counter = 0
|
||||
var bufferCounter = 0
|
||||
var buffer = UByteArray(BLOCK_SIZE_IN_BYTES) { 0U }
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
override fun update(data: String) {
|
||||
return update(data.encodeToByteArray().toUByteArray())
|
||||
}
|
||||
|
||||
override fun update(data: UByteArray) {
|
||||
if (data.isEmpty()) {
|
||||
throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
|
||||
}
|
||||
|
||||
when {
|
||||
bufferCounter + data.size < BLOCK_SIZE_IN_BYTES -> appendToBuffer(data, bufferCounter)
|
||||
bufferCounter + data.size >= BLOCK_SIZE_IN_BYTES -> {
|
||||
val chunked = data.chunked(BLOCK_SIZE_IN_BYTES)
|
||||
chunked.forEach { chunk ->
|
||||
if (bufferCounter + chunk.size < BLOCK_SIZE_IN_BYTES) {
|
||||
appendToBuffer(chunk.toUByteArray(), bufferCounter)
|
||||
} else {
|
||||
chunk.toUByteArray().copyInto(
|
||||
destination = buffer,
|
||||
destinationOffset = bufferCounter,
|
||||
startIndex = 0,
|
||||
endIndex = BLOCK_SIZE_IN_BYTES - bufferCounter
|
||||
)
|
||||
counter += BLOCK_SIZE_IN_BYTES
|
||||
consumeBlock(buffer)
|
||||
buffer = UByteArray(BLOCK_SIZE_IN_BYTES) {
|
||||
when (it) {
|
||||
in (0 until (chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter))) -> {
|
||||
chunk[it + (BLOCK_SIZE_IN_BYTES - bufferCounter)]
|
||||
}
|
||||
else -> {
|
||||
0U
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
bufferCounter = chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumeBlock(block: UByteArray) {
|
||||
val w = expandChunk(block)
|
||||
mix(h, w).copyInto(h)
|
||||
}
|
||||
|
||||
override fun digest(): UByteArray {
|
||||
val length = counter + bufferCounter
|
||||
val expansionArray = createExpansionArray(length)
|
||||
val finalBlock =
|
||||
buffer.copyOfRange(0, bufferCounter) + expansionArray + (length * 8).toULong().toPaddedByteArray()
|
||||
finalBlock.chunked(BLOCK_SIZE_IN_BYTES).forEach {
|
||||
consumeBlock(it.toUByteArray())
|
||||
}
|
||||
|
||||
|
||||
val digest = h[0].toPaddedByteArray() +
|
||||
h[1].toPaddedByteArray() +
|
||||
h[2].toPaddedByteArray() +
|
||||
h[3].toPaddedByteArray() +
|
||||
h[4].toPaddedByteArray() +
|
||||
h[5].toPaddedByteArray() +
|
||||
h[6].toPaddedByteArray() +
|
||||
h[7].toPaddedByteArray()
|
||||
return digest
|
||||
}
|
||||
|
||||
override fun digestString(): String {
|
||||
return digest().map { it.toString(16) }.joinToString(separator = "")
|
||||
}
|
||||
|
||||
private fun appendToBuffer(array: UByteArray, start: Int) {
|
||||
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
|
||||
bufferCounter += array.size
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+402
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.ionspin.kotlin.crypto.hash.StatelessHash
|
||||
import com.ionspin.kotlin.crypto.hash.UpdatableHash
|
||||
import com.ionspin.kotlin.crypto.util.rotateRight
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 18-Jul-2019
|
||||
*/
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
class Sha512Pure : Sha512 {
|
||||
|
||||
override val MAX_HASH_BYTES: Int = 32
|
||||
|
||||
companion object : StatelessSha512 {
|
||||
const val BLOCK_SIZE = 1024
|
||||
const val BLOCK_SIZE_IN_BYTES = 128
|
||||
const val CHUNK_SIZE = 80
|
||||
const val ULONG_MASK = 0xFFFFFFFFFFFFFFFFUL
|
||||
|
||||
override val MAX_HASH_BYTES: Int = 32
|
||||
|
||||
val k = arrayOf(
|
||||
0x428a2f98d728ae22UL,
|
||||
0x7137449123ef65cdUL,
|
||||
0xb5c0fbcfec4d3b2fUL,
|
||||
0xe9b5dba58189dbbcUL,
|
||||
0x3956c25bf348b538UL,
|
||||
0x59f111f1b605d019UL,
|
||||
0x923f82a4af194f9bUL,
|
||||
0xab1c5ed5da6d8118UL,
|
||||
0xd807aa98a3030242UL,
|
||||
0x12835b0145706fbeUL,
|
||||
0x243185be4ee4b28cUL,
|
||||
0x550c7dc3d5ffb4e2UL,
|
||||
0x72be5d74f27b896fUL,
|
||||
0x80deb1fe3b1696b1UL,
|
||||
0x9bdc06a725c71235UL,
|
||||
0xc19bf174cf692694UL,
|
||||
0xe49b69c19ef14ad2UL,
|
||||
0xefbe4786384f25e3UL,
|
||||
0x0fc19dc68b8cd5b5UL,
|
||||
0x240ca1cc77ac9c65UL,
|
||||
0x2de92c6f592b0275UL,
|
||||
0x4a7484aa6ea6e483UL,
|
||||
0x5cb0a9dcbd41fbd4UL,
|
||||
0x76f988da831153b5UL,
|
||||
0x983e5152ee66dfabUL,
|
||||
0xa831c66d2db43210UL,
|
||||
0xb00327c898fb213fUL,
|
||||
0xbf597fc7beef0ee4UL,
|
||||
0xc6e00bf33da88fc2UL,
|
||||
0xd5a79147930aa725UL,
|
||||
0x06ca6351e003826fUL,
|
||||
0x142929670a0e6e70UL,
|
||||
0x27b70a8546d22ffcUL,
|
||||
0x2e1b21385c26c926UL,
|
||||
0x4d2c6dfc5ac42aedUL,
|
||||
0x53380d139d95b3dfUL,
|
||||
0x650a73548baf63deUL,
|
||||
0x766a0abb3c77b2a8UL,
|
||||
0x81c2c92e47edaee6UL,
|
||||
0x92722c851482353bUL,
|
||||
0xa2bfe8a14cf10364UL,
|
||||
0xa81a664bbc423001UL,
|
||||
0xc24b8b70d0f89791UL,
|
||||
0xc76c51a30654be30UL,
|
||||
0xd192e819d6ef5218UL,
|
||||
0xd69906245565a910UL,
|
||||
0xf40e35855771202aUL,
|
||||
0x106aa07032bbd1b8UL,
|
||||
0x19a4c116b8d2d0c8UL,
|
||||
0x1e376c085141ab53UL,
|
||||
0x2748774cdf8eeb99UL,
|
||||
0x34b0bcb5e19b48a8UL,
|
||||
0x391c0cb3c5c95a63UL,
|
||||
0x4ed8aa4ae3418acbUL,
|
||||
0x5b9cca4f7763e373UL,
|
||||
0x682e6ff3d6b2b8a3UL,
|
||||
0x748f82ee5defb2fcUL,
|
||||
0x78a5636f43172f60UL,
|
||||
0x84c87814a1f0ab72UL,
|
||||
0x8cc702081a6439ecUL,
|
||||
0x90befffa23631e28UL,
|
||||
0xa4506cebde82bde9UL,
|
||||
0xbef9a3f7b2c67915UL,
|
||||
0xc67178f2e372532bUL,
|
||||
0xca273eceea26619cUL,
|
||||
0xd186b8c721c0c207UL,
|
||||
0xeada7dd6cde0eb1eUL,
|
||||
0xf57d4f7fee6ed178UL,
|
||||
0x06f067aa72176fbaUL,
|
||||
0x0a637dc5a2c898a6UL,
|
||||
0x113f9804bef90daeUL,
|
||||
0x1b710b35131c471bUL,
|
||||
0x28db77f523047d84UL,
|
||||
0x32caab7b40c72493UL,
|
||||
0x3c9ebe0a15c9bebcUL,
|
||||
0x431d67c49c100d4cUL,
|
||||
0x4cc5d4becb3e42b6UL,
|
||||
0x597f299cfc657e2aUL,
|
||||
0x5fcb6fab3ad6faecUL,
|
||||
0x6c44198c4a475817UL
|
||||
)
|
||||
|
||||
val iv = arrayOf(
|
||||
0x6a09e667f3bcc908UL,
|
||||
0xbb67ae8584caa73bUL,
|
||||
0x3c6ef372fe94f82bUL,
|
||||
0xa54ff53a5f1d36f1UL,
|
||||
0x510e527fade682d1UL,
|
||||
0x9b05688c2b3e6c1fUL,
|
||||
0x1f83d9abfb41bd6bUL,
|
||||
0x5be0cd19137e2179UL
|
||||
)
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
override fun digest(inputString: String, key: String?, hashLength: Int): UByteArray {
|
||||
return digest(
|
||||
inputString.encodeToByteArray().toUByteArray(),
|
||||
key?.run { encodeToByteArray().toUByteArray() } ?: ubyteArrayOf(),
|
||||
hashLength = hashLength
|
||||
)
|
||||
}
|
||||
|
||||
override fun digest(inputMessage: UByteArray, key: UByteArray, hashLength: Int): UByteArray {
|
||||
|
||||
var h = iv.copyOf()
|
||||
|
||||
val expansionArray = createExpansionArray(inputMessage.size)
|
||||
|
||||
val chunks =
|
||||
(inputMessage + expansionArray + (inputMessage.size * 8).toULong().toPadded128BitByteArray()).chunked(
|
||||
BLOCK_SIZE_IN_BYTES
|
||||
)
|
||||
|
||||
chunks.forEach { chunk ->
|
||||
val w = expandChunk(chunk.toUByteArray())
|
||||
mix(h, w)
|
||||
|
||||
}
|
||||
|
||||
val digest =
|
||||
h[0].toPaddedByteArray() +
|
||||
h[1].toPaddedByteArray() +
|
||||
h[2].toPaddedByteArray() +
|
||||
h[3].toPaddedByteArray() +
|
||||
h[4].toPaddedByteArray() +
|
||||
h[5].toPaddedByteArray() +
|
||||
h[6].toPaddedByteArray() +
|
||||
h[7].toPaddedByteArray()
|
||||
return digest
|
||||
}
|
||||
|
||||
private fun scheduleSigma0(value: ULong): ULong {
|
||||
return value.rotateRight(1) xor value.rotateRight(8) xor (value shr 7)
|
||||
}
|
||||
|
||||
private fun scheduleSigma1(value: ULong): ULong {
|
||||
return value.rotateRight(19) xor value.rotateRight(61) xor (value shr 6)
|
||||
}
|
||||
|
||||
private fun compressionSigma0(e: ULong): ULong {
|
||||
return (e rotateRight 28) xor (e rotateRight 34) xor (e rotateRight 39)
|
||||
}
|
||||
|
||||
private fun compressionSigma1(a: ULong): ULong {
|
||||
return (a rotateRight 14) xor (a rotateRight 18) xor (a rotateRight 41)
|
||||
}
|
||||
|
||||
private fun ch(x: ULong, y: ULong, z: ULong): ULong {
|
||||
return ((x and y) xor ((x xor ULONG_MASK) and z))
|
||||
}
|
||||
|
||||
private fun maj(x: ULong, y: ULong, z: ULong): ULong {
|
||||
return ((x and y) xor (x and z) xor (y and z))
|
||||
}
|
||||
|
||||
private fun expandChunk(chunk: UByteArray): Array<ULong> {
|
||||
val w = Array<ULong>(CHUNK_SIZE) {
|
||||
when (it) {
|
||||
in 0 until 16 -> {
|
||||
var collected = (chunk[(it * 8)].toULong() shl 56) +
|
||||
(chunk[(it * 8) + 1].toULong() shl 48) +
|
||||
(chunk[(it * 8) + 2].toULong() shl 40) +
|
||||
(chunk[(it * 8) + 3].toULong() shl 32) +
|
||||
(chunk[(it * 8) + 4].toULong() shl 24) +
|
||||
(chunk[(it * 8) + 5].toULong() shl 16) +
|
||||
(chunk[(it * 8) + 6].toULong() shl 8) +
|
||||
(chunk[(it * 8) + 7].toULong())
|
||||
collected
|
||||
}
|
||||
else -> 0UL
|
||||
}
|
||||
}
|
||||
for (i in 16 until CHUNK_SIZE) {
|
||||
val s0 = scheduleSigma0(w[i - 15])
|
||||
val s1 = scheduleSigma1(w[i - 2])
|
||||
w[i] = w[i - 16] + s0 + w[i - 7] + s1
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
private fun mix(h: Array<ULong>, w: Array<ULong>): Array<ULong> {
|
||||
var paramA = h[0]
|
||||
var paramB = h[1]
|
||||
var paramC = h[2]
|
||||
var paramD = h[3]
|
||||
var paramE = h[4]
|
||||
var paramF = h[5]
|
||||
var paramG = h[6]
|
||||
var paramH = h[7]
|
||||
|
||||
for (i in 0 until CHUNK_SIZE) {
|
||||
val s1 = compressionSigma1(paramE)
|
||||
val ch = ch(paramE, paramF, paramG)
|
||||
val temp1 = paramH + s1 + ch + k[i] + w[i]
|
||||
val s0 = compressionSigma0(paramA)
|
||||
val maj = maj(paramA, paramB, paramC)
|
||||
val temp2 = s0 + maj
|
||||
paramH = paramG
|
||||
paramG = paramF
|
||||
paramF = paramE
|
||||
paramE = paramD + temp1
|
||||
paramD = paramC
|
||||
paramC = paramB
|
||||
paramB = paramA
|
||||
paramA = temp1 + temp2
|
||||
}
|
||||
|
||||
h[0] += paramA
|
||||
h[1] += paramB
|
||||
h[2] += paramC
|
||||
h[3] += paramD
|
||||
h[4] += paramE
|
||||
h[5] += paramF
|
||||
h[6] += paramG
|
||||
h[7] += paramH
|
||||
return h
|
||||
}
|
||||
|
||||
fun createExpansionArray(originalSizeInBytes: Int): UByteArray {
|
||||
val originalMessageSizeInBits = originalSizeInBytes * 8
|
||||
|
||||
val expandedRemainderOf1024 = (originalMessageSizeInBits + 129) % BLOCK_SIZE
|
||||
val zeroAddAmount = when (expandedRemainderOf1024) {
|
||||
0 -> 0
|
||||
else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8
|
||||
}
|
||||
val expansionArray = UByteArray(zeroAddAmount + 1) {
|
||||
when (it) {
|
||||
0 -> 0b10000000U
|
||||
else -> 0U
|
||||
}
|
||||
}
|
||||
return expansionArray
|
||||
}
|
||||
|
||||
|
||||
private fun ULong.toPaddedByteArray(): UByteArray {
|
||||
val byteMask = 0xFFUL
|
||||
//Ignore messages longer than 64 bits for now
|
||||
return UByteArray(8) {
|
||||
when (it) {
|
||||
7 -> (this and byteMask).toUByte()
|
||||
6 -> ((this shr 8) and byteMask).toUByte()
|
||||
5 -> ((this shr 16) and byteMask).toUByte()
|
||||
4 -> ((this shr 24) and byteMask).toUByte()
|
||||
3 -> ((this shr 32) and byteMask).toUByte()
|
||||
2 -> ((this shr 40) and byteMask).toUByte()
|
||||
1 -> ((this shr 48) and byteMask).toUByte()
|
||||
0 -> ((this shr 56) and byteMask).toUByte()
|
||||
else -> 0U
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ULong.toPadded128BitByteArray(): UByteArray {
|
||||
val byteMask = 0xFFUL
|
||||
//Ignore messages longer than 64 bits for now
|
||||
return UByteArray(16) {
|
||||
when (it) {
|
||||
15 -> (this and byteMask).toUByte()
|
||||
14 -> ((this shr 8) and byteMask).toUByte()
|
||||
13 -> ((this shr 16) and byteMask).toUByte()
|
||||
12 -> ((this shr 24) and byteMask).toUByte()
|
||||
11 -> ((this shr 32) and byteMask).toUByte()
|
||||
10 -> ((this shr 40) and byteMask).toUByte()
|
||||
9 -> ((this shr 48) and byteMask).toUByte()
|
||||
8 -> ((this shr 54) and byteMask).toUByte()
|
||||
else -> 0U
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var h = iv.copyOf()
|
||||
var counter = 0
|
||||
var bufferCounter = 0
|
||||
var buffer = UByteArray(BLOCK_SIZE_IN_BYTES) { 0U }
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
override fun update(data: String) {
|
||||
return update(data.encodeToByteArray().toUByteArray())
|
||||
}
|
||||
|
||||
override fun update(data: UByteArray) {
|
||||
if (data.isEmpty()) {
|
||||
throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
|
||||
}
|
||||
|
||||
when {
|
||||
bufferCounter + data.size < BLOCK_SIZE_IN_BYTES -> appendToBuffer(data, bufferCounter)
|
||||
bufferCounter + data.size >= BLOCK_SIZE_IN_BYTES -> {
|
||||
val chunked = data.chunked(BLOCK_SIZE_IN_BYTES)
|
||||
chunked.forEach { chunk ->
|
||||
if (bufferCounter + chunk.size < BLOCK_SIZE_IN_BYTES) {
|
||||
appendToBuffer(chunk.toUByteArray(), bufferCounter)
|
||||
} else {
|
||||
chunk.toUByteArray().copyInto(
|
||||
destination = buffer,
|
||||
destinationOffset = bufferCounter,
|
||||
startIndex = 0,
|
||||
endIndex = BLOCK_SIZE_IN_BYTES - bufferCounter
|
||||
)
|
||||
counter += BLOCK_SIZE_IN_BYTES
|
||||
consumeBlock(buffer)
|
||||
buffer = UByteArray(BLOCK_SIZE_IN_BYTES) {
|
||||
when (it) {
|
||||
in (0 until (chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter))) -> {
|
||||
chunk[it + (BLOCK_SIZE_IN_BYTES - bufferCounter)]
|
||||
}
|
||||
else -> {
|
||||
0U
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
bufferCounter = chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumeBlock(block: UByteArray) {
|
||||
val w = expandChunk(block)
|
||||
mix(h, w).copyInto(h)
|
||||
}
|
||||
|
||||
override fun digest(): UByteArray {
|
||||
val length = counter + bufferCounter
|
||||
val expansionArray = createExpansionArray(length)
|
||||
val finalBlock =
|
||||
buffer.copyOfRange(0, bufferCounter) + expansionArray + (length * 8).toULong().toPadded128BitByteArray()
|
||||
finalBlock.chunked(BLOCK_SIZE_IN_BYTES).forEach {
|
||||
consumeBlock(it.toUByteArray())
|
||||
}
|
||||
|
||||
|
||||
val digest = h[0].toPaddedByteArray() +
|
||||
h[1].toPaddedByteArray() +
|
||||
h[2].toPaddedByteArray() +
|
||||
h[3].toPaddedByteArray() +
|
||||
h[4].toPaddedByteArray() +
|
||||
h[5].toPaddedByteArray() +
|
||||
h[6].toPaddedByteArray() +
|
||||
h[7].toPaddedByteArray()
|
||||
return digest
|
||||
}
|
||||
|
||||
override fun digestString(): String {
|
||||
return digest().map { it.toString(16) }.joinToString(separator = "")
|
||||
}
|
||||
|
||||
private fun appendToBuffer(array: UByteArray, start: Int) {
|
||||
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
|
||||
bufferCounter += array.size
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+10
@@ -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
|
||||
+34
@@ -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()
|
||||
}
|
||||
}
|
||||
+241
@@ -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
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
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
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.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.symmetric.AesCtrPure.Companion.encrypt
|
||||
import com.ionspin.kotlin.crypto.util.xor
|
||||
|
||||
/**
|
||||
*
|
||||
* Advanced encryption standard with counter mode
|
||||
*
|
||||
* 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]
|
||||
*
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 22-Sep-2019
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
class AesCtrPure internal constructor(val aesKey: AesKey, val mode: Mode, initialCounter: UByteArray? = null) {
|
||||
|
||||
companion object {
|
||||
const val BLOCK_BYTES = 16
|
||||
|
||||
val modularCreator = ModularBigInteger.creatorForModulo(BigInteger.ONE.shl(128) - 1)
|
||||
/**
|
||||
* Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all
|
||||
* data call [encrypt]
|
||||
*/
|
||||
fun createEncryptor(aesKey: AesKey) : AesCtrPure {
|
||||
return AesCtrPure(aesKey, Mode.ENCRYPT)
|
||||
}
|
||||
/**
|
||||
* Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all
|
||||
* data call [decrypt]
|
||||
*/
|
||||
fun createDecryptor(aesKey : AesKey) : AesCtrPure {
|
||||
return AesCtrPure(aesKey, Mode.DECRYPT)
|
||||
}
|
||||
/**
|
||||
* Bulk encryption, returns encrypted data and a random initial counter
|
||||
*/
|
||||
fun encrypt(aesKey: AesKey, data: UByteArray): EncryptedDataAndInitialCounter {
|
||||
val aesCtr = AesCtrPure(aesKey, Mode.ENCRYPT)
|
||||
aesCtr.addData(data)
|
||||
return aesCtr.encrypt()
|
||||
}
|
||||
/**
|
||||
* Bulk decryption, returns decrypted data
|
||||
*/
|
||||
fun decrypt(aesKey: AesKey, data: UByteArray, initialCounter: UByteArray? = null): UByteArray {
|
||||
val aesCtr = AesCtrPure(aesKey, Mode.DECRYPT, initialCounter)
|
||||
aesCtr.addData(data)
|
||||
return aesCtr.decrypt()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var currentOutput: UByteArray = ubyteArrayOf()
|
||||
var previousEncrypted: UByteArray = ubyteArrayOf()
|
||||
val counterStart = if (initialCounter.isNullOrEmpty()) {
|
||||
SRNG.getRandomBytes(16)
|
||||
} else {
|
||||
initialCounter
|
||||
}
|
||||
var blockCounter = modularCreator.fromBigInteger(BigInteger.fromUByteArray(counterStart.toTypedArray(), Endianness.BIG))
|
||||
|
||||
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, blockCounter)
|
||||
blockCounter += 1
|
||||
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 initial counter state
|
||||
* @return Encrypted data and initial counter state
|
||||
*/
|
||||
fun encrypt(): EncryptedDataAndInitialCounter {
|
||||
if (bufferCounter > 0) {
|
||||
output += consumeBlock(buffer, blockCounter)
|
||||
}
|
||||
return EncryptedDataAndInitialCounter(
|
||||
output.reversed().foldRight(UByteArray(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes },
|
||||
counterStart
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Decrypt data
|
||||
* @return Decrypted data
|
||||
*/
|
||||
fun decrypt(): UByteArray {
|
||||
if (bufferCounter > 0) {
|
||||
output += consumeBlock(buffer, blockCounter)
|
||||
}
|
||||
//JS compiler freaks out here if we don't supply exact type
|
||||
val reversed: List<UByteArray> = output.reversed() as List<UByteArray>
|
||||
val folded: UByteArray = reversed.foldRight(UByteArray(0) { 0U }) { arrayOfUBytes, acc ->
|
||||
acc + arrayOfUBytes
|
||||
}
|
||||
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, blockCount: ModularBigInteger): UByteArray {
|
||||
val blockCountAsByteArray = blockCount.toUByteArray(Endianness.BIG).toUByteArray().expandCounterTo16Bytes()
|
||||
return when (mode) {
|
||||
Mode.ENCRYPT -> {
|
||||
AesPure.encrypt(aesKey, blockCountAsByteArray) xor data
|
||||
}
|
||||
Mode.DECRYPT -> {
|
||||
AesPure.encrypt(aesKey, blockCountAsByteArray) xor data
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun UByteArray.expandCounterTo16Bytes() : UByteArray {
|
||||
return if (this.size < 16) {
|
||||
println("Expanding")
|
||||
val diff = 16 - this.size
|
||||
val pad = UByteArray(diff) { 0U }
|
||||
pad + this
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
data class EncryptedDataAndInitialCounter(val encryptedData : UByteArray, val initialCounter : 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 (!initialCounter.contentEquals(other.initilizationVector)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = encryptedData.contentHashCode()
|
||||
result = 31 * result + initialCounter.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
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
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
internal class AesPure internal constructor(val aesKey: AesKey, val input: UByteArray) {
|
||||
companion object {
|
||||
private val debug = false
|
||||
|
||||
private val sBox: UByteArray =
|
||||
ubyteArrayOf(
|
||||
// @formatter:off
|
||||
0x63U, 0x7cU, 0x77U, 0x7bU, 0xf2U, 0x6bU, 0x6fU, 0xc5U, 0x30U, 0x01U, 0x67U, 0x2bU, 0xfeU, 0xd7U, 0xabU, 0x76U,
|
||||
0xcaU, 0x82U, 0xc9U, 0x7dU, 0xfaU, 0x59U, 0x47U, 0xf0U, 0xadU, 0xd4U, 0xa2U, 0xafU, 0x9cU, 0xa4U, 0x72U, 0xc0U,
|
||||
0xb7U, 0xfdU, 0x93U, 0x26U, 0x36U, 0x3fU, 0xf7U, 0xccU, 0x34U, 0xa5U, 0xe5U, 0xf1U, 0x71U, 0xd8U, 0x31U, 0x15U,
|
||||
0x04U, 0xc7U, 0x23U, 0xc3U, 0x18U, 0x96U, 0x05U, 0x9aU, 0x07U, 0x12U, 0x80U, 0xe2U, 0xebU, 0x27U, 0xb2U, 0x75U,
|
||||
0x09U, 0x83U, 0x2cU, 0x1aU, 0x1bU, 0x6eU, 0x5aU, 0xa0U, 0x52U, 0x3bU, 0xd6U, 0xb3U, 0x29U, 0xe3U, 0x2fU, 0x84U,
|
||||
0x53U, 0xd1U, 0x00U, 0xedU, 0x20U, 0xfcU, 0xb1U, 0x5bU, 0x6aU, 0xcbU, 0xbeU, 0x39U, 0x4aU, 0x4cU, 0x58U, 0xcfU,
|
||||
0xd0U, 0xefU, 0xaaU, 0xfbU, 0x43U, 0x4dU, 0x33U, 0x85U, 0x45U, 0xf9U, 0x02U, 0x7fU, 0x50U, 0x3cU, 0x9fU, 0xa8U,
|
||||
0x51U, 0xa3U, 0x40U, 0x8fU, 0x92U, 0x9dU, 0x38U, 0xf5U, 0xbcU, 0xb6U, 0xdaU, 0x21U, 0x10U, 0xffU, 0xf3U, 0xd2U,
|
||||
0xcdU, 0x0cU, 0x13U, 0xecU, 0x5fU, 0x97U, 0x44U, 0x17U, 0xc4U, 0xa7U, 0x7eU, 0x3dU, 0x64U, 0x5dU, 0x19U, 0x73U,
|
||||
0x60U, 0x81U, 0x4fU, 0xdcU, 0x22U, 0x2aU, 0x90U, 0x88U, 0x46U, 0xeeU, 0xb8U, 0x14U, 0xdeU, 0x5eU, 0x0bU, 0xdbU,
|
||||
0xe0U, 0x32U, 0x3aU, 0x0aU, 0x49U, 0x06U, 0x24U, 0x5cU, 0xc2U, 0xd3U, 0xacU, 0x62U, 0x91U, 0x95U, 0xe4U, 0x79U,
|
||||
0xe7U, 0xc8U, 0x37U, 0x6dU, 0x8dU, 0xd5U, 0x4eU, 0xa9U, 0x6cU, 0x56U, 0xf4U, 0xeaU, 0x65U, 0x7aU, 0xaeU, 0x08U,
|
||||
0xbaU, 0x78U, 0x25U, 0x2eU, 0x1cU, 0xa6U, 0xb4U, 0xc6U, 0xe8U, 0xddU, 0x74U, 0x1fU, 0x4bU, 0xbdU, 0x8bU, 0x8aU,
|
||||
0x70U, 0x3eU, 0xb5U, 0x66U, 0x48U, 0x03U, 0xf6U, 0x0eU, 0x61U, 0x35U, 0x57U, 0xb9U, 0x86U, 0xc1U, 0x1dU, 0x9eU,
|
||||
0xe1U, 0xf8U, 0x98U, 0x11U, 0x69U, 0xd9U, 0x8eU, 0x94U, 0x9bU, 0x1eU, 0x87U, 0xe9U, 0xceU, 0x55U, 0x28U, 0xdfU,
|
||||
0x8cU, 0xa1U, 0x89U, 0x0dU, 0xbfU, 0xe6U, 0x42U, 0x68U, 0x41U, 0x99U, 0x2dU, 0x0fU, 0xb0U, 0x54U, 0xbbU, 0x16U
|
||||
// @formatter:on
|
||||
)
|
||||
|
||||
private val inverseSBox: UByteArray =
|
||||
ubyteArrayOf(
|
||||
// @formatter:off
|
||||
0x52U, 0x09U, 0x6aU, 0xd5U, 0x30U, 0x36U, 0xa5U, 0x38U, 0xbfU, 0x40U, 0xa3U, 0x9eU, 0x81U, 0xf3U, 0xd7U, 0xfbU,
|
||||
0x7cU, 0xe3U, 0x39U, 0x82U, 0x9bU, 0x2fU, 0xffU, 0x87U, 0x34U, 0x8eU, 0x43U, 0x44U, 0xc4U, 0xdeU, 0xe9U, 0xcbU,
|
||||
0x54U, 0x7bU, 0x94U, 0x32U, 0xa6U, 0xc2U, 0x23U, 0x3dU, 0xeeU, 0x4cU, 0x95U, 0x0bU, 0x42U, 0xfaU, 0xc3U, 0x4eU,
|
||||
0x08U, 0x2eU, 0xa1U, 0x66U, 0x28U, 0xd9U, 0x24U, 0xb2U, 0x76U, 0x5bU, 0xa2U, 0x49U, 0x6dU, 0x8bU, 0xd1U, 0x25U,
|
||||
0x72U, 0xf8U, 0xf6U, 0x64U, 0x86U, 0x68U, 0x98U, 0x16U, 0xd4U, 0xa4U, 0x5cU, 0xccU, 0x5dU, 0x65U, 0xb6U, 0x92U,
|
||||
0x6cU, 0x70U, 0x48U, 0x50U, 0xfdU, 0xedU, 0xb9U, 0xdaU, 0x5eU, 0x15U, 0x46U, 0x57U, 0xa7U, 0x8dU, 0x9dU, 0x84U,
|
||||
0x90U, 0xd8U, 0xabU, 0x00U, 0x8cU, 0xbcU, 0xd3U, 0x0aU, 0xf7U, 0xe4U, 0x58U, 0x05U, 0xb8U, 0xb3U, 0x45U, 0x06U,
|
||||
0xd0U, 0x2cU, 0x1eU, 0x8fU, 0xcaU, 0x3fU, 0x0fU, 0x02U, 0xc1U, 0xafU, 0xbdU, 0x03U, 0x01U, 0x13U, 0x8aU, 0x6bU,
|
||||
0x3aU, 0x91U, 0x11U, 0x41U, 0x4fU, 0x67U, 0xdcU, 0xeaU, 0x97U, 0xf2U, 0xcfU, 0xceU, 0xf0U, 0xb4U, 0xe6U, 0x73U,
|
||||
0x96U, 0xacU, 0x74U, 0x22U, 0xe7U, 0xadU, 0x35U, 0x85U, 0xe2U, 0xf9U, 0x37U, 0xe8U, 0x1cU, 0x75U, 0xdfU, 0x6eU,
|
||||
0x47U, 0xf1U, 0x1aU, 0x71U, 0x1dU, 0x29U, 0xc5U, 0x89U, 0x6fU, 0xb7U, 0x62U, 0x0eU, 0xaaU, 0x18U, 0xbeU, 0x1bU,
|
||||
0xfcU, 0x56U, 0x3eU, 0x4bU, 0xc6U, 0xd2U, 0x79U, 0x20U, 0x9aU, 0xdbU, 0xc0U, 0xfeU, 0x78U, 0xcdU, 0x5aU, 0xf4U,
|
||||
0x1fU, 0xddU, 0xa8U, 0x33U, 0x88U, 0x07U, 0xc7U, 0x31U, 0xb1U, 0x12U, 0x10U, 0x59U, 0x27U, 0x80U, 0xecU, 0x5fU,
|
||||
0x60U, 0x51U, 0x7fU, 0xa9U, 0x19U, 0xb5U, 0x4aU, 0x0dU, 0x2dU, 0xe5U, 0x7aU, 0x9fU, 0x93U, 0xc9U, 0x9cU, 0xefU,
|
||||
0xa0U, 0xe0U, 0x3bU, 0x4dU, 0xaeU, 0x2aU, 0xf5U, 0xb0U, 0xc8U, 0xebU, 0xbbU, 0x3cU, 0x83U, 0x53U, 0x99U, 0x61U,
|
||||
0x17U, 0x2bU, 0x04U, 0x7eU, 0xbaU, 0x77U, 0xd6U, 0x26U, 0xe1U, 0x69U, 0x14U, 0x63U, 0x55U, 0x21U, 0x0cU, 0x7dU
|
||||
// @formatter:on
|
||||
)
|
||||
|
||||
val rcon: UByteArray = ubyteArrayOf(0x8DU, 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1BU, 0x36U)
|
||||
|
||||
fun encrypt(aesKey: AesKey, input: UByteArray): UByteArray {
|
||||
return AesPure(aesKey, input).encrypt()
|
||||
}
|
||||
|
||||
fun decrypt(aesKey: AesKey, input: UByteArray): UByteArray {
|
||||
return AesPure(aesKey, input).decrypt()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val state: Array<UByteArray> = (0 until 4).map { outerCounter ->
|
||||
UByteArray(4) { innerCounter -> input[innerCounter * 4 + outerCounter] }
|
||||
}.toTypedArray()
|
||||
|
||||
val numberOfRounds = when (aesKey) {
|
||||
is AesKey.Aes128Key -> 10
|
||||
is AesKey.Aes192Key -> 12
|
||||
is AesKey.Aes256Key -> 14
|
||||
}
|
||||
|
||||
val expandedKey: Array<UByteArray> = expandKey()
|
||||
|
||||
|
||||
var round = 0
|
||||
var completed : Boolean = false
|
||||
private set
|
||||
|
||||
fun subBytes() {
|
||||
state.forEachIndexed { indexRow, row ->
|
||||
row.forEachIndexed { indexColumn, element ->
|
||||
state[indexRow][indexColumn] = getSBoxValue(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getSBoxValue(element: UByte): UByte {
|
||||
val firstDigit = (element / 16U).toInt()
|
||||
val secondDigit = (element % 16U).toInt()
|
||||
return sBox[firstDigit * 16 + secondDigit]
|
||||
}
|
||||
|
||||
fun inverseSubBytes() {
|
||||
state.forEachIndexed { indexRow, row ->
|
||||
row.forEachIndexed { indexColumn, element ->
|
||||
state[indexRow][indexColumn] = getInverseSBoxValue(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getInverseSBoxValue(element: UByte): UByte {
|
||||
val firstDigit = (element / 16U).toInt()
|
||||
val secondDigit = (element % 16U).toInt()
|
||||
return inverseSBox[firstDigit * 16 + secondDigit]
|
||||
}
|
||||
|
||||
fun shiftRows() {
|
||||
state[0] = ubyteArrayOf(state[0][0], state[0][1], state[0][2], state[0][3])
|
||||
state[1] = ubyteArrayOf(state[1][1], state[1][2], state[1][3], state[1][0])
|
||||
state[2] = ubyteArrayOf(state[2][2], state[2][3], state[2][0], state[2][1])
|
||||
state[3] = ubyteArrayOf(state[3][3], state[3][0], state[3][1], state[3][2])
|
||||
}
|
||||
|
||||
fun inversShiftRows() {
|
||||
state[0] = ubyteArrayOf(state[0][0], state[0][1], state[0][2], state[0][3])
|
||||
state[1] = ubyteArrayOf(state[1][3], state[1][0], state[1][1], state[1][2])
|
||||
state[2] = ubyteArrayOf(state[2][2], state[2][3], state[2][0], state[2][1])
|
||||
state[3] = ubyteArrayOf(state[3][1], state[3][2], state[3][3], state[3][0])
|
||||
}
|
||||
|
||||
fun mixColumns() {
|
||||
val stateMixed: Array<UByteArray> = (0 until 4).map {
|
||||
UByteArray(4) { 0U }
|
||||
}.toTypedArray()
|
||||
for (c in 0..3) {
|
||||
|
||||
stateMixed[0][c] = (2U gfm state[0][c]) xor (3U gfm state[1][c]) xor state[2][c] xor state[3][c]
|
||||
stateMixed[1][c] = state[0][c] xor (2U gfm state[1][c]) xor (3U gfm state[2][c]) xor state[3][c]
|
||||
stateMixed[2][c] = state[0][c] xor state[1][c] xor (2U gfm state[2][c]) xor (3U gfm state[3][c])
|
||||
stateMixed[3][c] = 3U gfm state[0][c] xor state[1][c] xor state[2][c] xor (2U gfm state[3][c])
|
||||
}
|
||||
stateMixed.copyInto(state)
|
||||
}
|
||||
|
||||
fun inverseMixColumns() {
|
||||
val stateMixed: Array<UByteArray> = (0 until 4).map {
|
||||
UByteArray(4) { 0U }
|
||||
}.toTypedArray()
|
||||
for (c in 0..3) {
|
||||
stateMixed[0][c] =
|
||||
(0x0eU gfm state[0][c]) xor (0x0bU gfm state[1][c]) xor (0x0dU gfm state[2][c]) xor (0x09U gfm state[3][c])
|
||||
stateMixed[1][c] =
|
||||
(0x09U gfm state[0][c]) xor (0x0eU gfm state[1][c]) xor (0x0bU gfm state[2][c]) xor (0x0dU gfm state[3][c])
|
||||
stateMixed[2][c] =
|
||||
(0x0dU gfm state[0][c]) xor (0x09U gfm state[1][c]) xor (0x0eU gfm state[2][c]) xor (0x0bU gfm state[3][c])
|
||||
stateMixed[3][c] =
|
||||
(0x0bU gfm state[0][c]) xor (0x0dU gfm state[1][c]) xor (0x09U gfm state[2][c]) xor (0x0eU gfm state[3][c])
|
||||
}
|
||||
stateMixed.copyInto(state)
|
||||
}
|
||||
|
||||
fun galoisFieldAdd(first: UByte, second: UByte): UByte {
|
||||
return first xor second
|
||||
}
|
||||
|
||||
fun galoisFieldMultiply(first: UByte, second: UByte): UByte {
|
||||
var result: UInt = 0U
|
||||
var firstInt = first.toUInt()
|
||||
var secondInt = second.toUInt()
|
||||
var carry: UInt = 0U
|
||||
for (i in 0..7) {
|
||||
if (secondInt and 0x01U == 1U) {
|
||||
result = result xor firstInt
|
||||
}
|
||||
carry = firstInt and 0x80U
|
||||
firstInt = firstInt shl 1
|
||||
if (carry == 0x80U) {
|
||||
firstInt = firstInt xor 0x001BU
|
||||
}
|
||||
secondInt = secondInt shr 1
|
||||
firstInt = firstInt and 0xFFU
|
||||
}
|
||||
return result.toUByte()
|
||||
}
|
||||
|
||||
fun addRoundKey() {
|
||||
|
||||
for (i in 0 until 4) {
|
||||
state[0][i] = state[0][i] xor expandedKey[round * 4 + i][0]
|
||||
state[1][i] = state[1][i] xor expandedKey[round * 4 + i][1]
|
||||
state[2][i] = state[2][i] xor expandedKey[round * 4 + i][2]
|
||||
state[3][i] = state[3][i] xor expandedKey[round * 4 + i][3]
|
||||
}
|
||||
round++
|
||||
}
|
||||
|
||||
fun inverseAddRoundKey() {
|
||||
for (i in 0 until 4) {
|
||||
state[0][i] = state[0][i] xor expandedKey[round * 4 + i][0]
|
||||
state[1][i] = state[1][i] xor expandedKey[round * 4 + i][1]
|
||||
state[2][i] = state[2][i] xor expandedKey[round * 4 + i][2]
|
||||
state[3][i] = state[3][i] xor expandedKey[round * 4 + i][3]
|
||||
}
|
||||
round--
|
||||
}
|
||||
|
||||
infix fun UInt.gfm(second: UByte): UByte {
|
||||
return galoisFieldMultiply(this.toUByte(), second)
|
||||
}
|
||||
|
||||
fun expandKey(): Array<UByteArray> {
|
||||
val expandedKey = (0 until 4 * (numberOfRounds + 1)).map {
|
||||
UByteArray(4) { 0U }
|
||||
}.toTypedArray()
|
||||
// First round
|
||||
for (i in 0 until aesKey.numberOf32BitWords) {
|
||||
expandedKey[i][0] = aesKey.keyArray[i * 4 + 0]
|
||||
expandedKey[i][1] = aesKey.keyArray[i * 4 + 1]
|
||||
expandedKey[i][2] = aesKey.keyArray[i * 4 + 2]
|
||||
expandedKey[i][3] = aesKey.keyArray[i * 4 + 3]
|
||||
}
|
||||
|
||||
for (i in aesKey.numberOf32BitWords until 4 * (numberOfRounds + 1)) {
|
||||
val temp = expandedKey[i - 1].copyOf()
|
||||
if (i % aesKey.numberOf32BitWords == 0) {
|
||||
//RotWord
|
||||
val tempTemp = temp[0]
|
||||
temp[0] = temp[1]
|
||||
temp[1] = temp[2]
|
||||
temp[2] = temp[3]
|
||||
temp[3] = tempTemp
|
||||
|
||||
//SubWord
|
||||
temp[0] = getSBoxValue(temp[0])
|
||||
temp[1] = getSBoxValue(temp[1])
|
||||
temp[2] = getSBoxValue(temp[2])
|
||||
temp[3] = getSBoxValue(temp[3])
|
||||
|
||||
temp[0] = temp[0] xor rcon[i / aesKey.numberOf32BitWords]
|
||||
|
||||
} else if (aesKey is AesKey.Aes256Key && i % aesKey.numberOf32BitWords == 4) {
|
||||
temp[0] = getSBoxValue(temp[0])
|
||||
temp[1] = getSBoxValue(temp[1])
|
||||
temp[2] = getSBoxValue(temp[2])
|
||||
temp[3] = getSBoxValue(temp[3])
|
||||
}
|
||||
expandedKey[i] = expandedKey[i - aesKey.numberOf32BitWords].mapIndexed { index, it ->
|
||||
it xor temp[index]
|
||||
}.toUByteArray()
|
||||
clearArray(temp)
|
||||
}
|
||||
return expandedKey
|
||||
}
|
||||
|
||||
fun encrypt(): UByteArray {
|
||||
if (completed) {
|
||||
throw RuntimeException("Encrypt can only be called once per Aes instance, since the state is cleared at the " +
|
||||
"end of the operation")
|
||||
}
|
||||
printState()
|
||||
addRoundKey()
|
||||
printState()
|
||||
for (i in 0 until numberOfRounds - 1) {
|
||||
subBytes()
|
||||
printState()
|
||||
shiftRows()
|
||||
printState()
|
||||
mixColumns()
|
||||
printState()
|
||||
addRoundKey()
|
||||
printState()
|
||||
}
|
||||
|
||||
subBytes()
|
||||
printState()
|
||||
shiftRows()
|
||||
printState()
|
||||
addRoundKey()
|
||||
printState()
|
||||
val transposedMatrix = (0 until 4).map { outerCounter ->
|
||||
UByteArray(4) { 0U }
|
||||
}
|
||||
for (i in 0 until 4) {
|
||||
for (j in 0 until 4) {
|
||||
transposedMatrix[i][j] = state[j][i]
|
||||
}
|
||||
}
|
||||
state.forEach { clearArray(it) }
|
||||
completed = true
|
||||
return transposedMatrix.flattenToUByteArray()
|
||||
}
|
||||
|
||||
fun decrypt(): UByteArray {
|
||||
if (completed) {
|
||||
throw RuntimeException("Decrypt can only be called once per Aes instance, since the state is cleared at the " +
|
||||
"end of the operation")
|
||||
}
|
||||
round = numberOfRounds
|
||||
printState()
|
||||
inverseAddRoundKey()
|
||||
printState()
|
||||
for (i in 0 until numberOfRounds - 1) {
|
||||
inversShiftRows()
|
||||
printState()
|
||||
inverseSubBytes()
|
||||
printState()
|
||||
inverseAddRoundKey()
|
||||
printState()
|
||||
inverseMixColumns()
|
||||
printState()
|
||||
}
|
||||
|
||||
inversShiftRows()
|
||||
printState()
|
||||
inverseSubBytes()
|
||||
printState()
|
||||
inverseAddRoundKey()
|
||||
printState()
|
||||
|
||||
val transposedMatrix = (0 until 4).map { outerCounter ->
|
||||
UByteArray(4) { 0U }
|
||||
}
|
||||
for (i in 0 until 4) {
|
||||
for (j in 0 until 4) {
|
||||
transposedMatrix[i][j] = state[j][i]
|
||||
}
|
||||
}
|
||||
state.forEach { clearArray(it) }
|
||||
completed = true
|
||||
return transposedMatrix.flattenToUByteArray()
|
||||
}
|
||||
|
||||
private fun clearArray(array : UByteArray) {
|
||||
array.indices.forEach { array[it] = 0U }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun printState() {
|
||||
if (!debug) {
|
||||
return
|
||||
}
|
||||
println()
|
||||
state.forEach {
|
||||
println(it.joinToString(separator = " ") { it.toString(16) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun printState(specific : List<UByteArray>) {
|
||||
if (!debug) {
|
||||
return
|
||||
}
|
||||
println()
|
||||
specific.forEach {
|
||||
println(it.joinToString(separator = " ") { it.toString(16) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
sealed class AesKey(val key: String, val keyLength: Int) {
|
||||
val keyArray: UByteArray = key.chunked(2).map { it.toUByte(16) }.toUByteArray()
|
||||
val numberOf32BitWords = keyLength / 32
|
||||
|
||||
class Aes128Key(key: String) : AesKey(key, 128)
|
||||
class Aes192Key(key: String) : AesKey(key, 192)
|
||||
class Aes256Key(key: String) : AesKey(key, 256)
|
||||
|
||||
init {
|
||||
checkKeyLength(key, keyLength)
|
||||
}
|
||||
|
||||
fun checkKeyLength(key: String, expectedLength: Int) {
|
||||
if ((key.length / 2) != expectedLength / 8) {
|
||||
throw RuntimeException("Invalid key length")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
@@ -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
|
||||
}
|
||||
+262
@@ -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()
|
||||
|
||||
}
|
||||
|
||||
@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
|
||||
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] }
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun String.hexStringToTypedUByteArray() : Array<UByte> {
|
||||
return this.chunked(2).map { it.toUByte(16) }.toTypedArray()
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun String.hexStringToUByteArray() : UByteArray {
|
||||
return this.chunked(2).map { it.toUByte(16) }.toUByteArray()
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun Array<UByte>.toHexString() : String {
|
||||
return this.joinToString(separator = "") {
|
||||
if (it <= 0x0FU) {
|
||||
"0${it.toString(16)}"
|
||||
} else {
|
||||
it.toString(16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun UByteArray.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.toLittleEndianTypedUByteArray() : Array<UByte> {
|
||||
return Array<UByte> (4) {
|
||||
((this shr (it * 8)) and 0xFFU).toUByte()
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun UInt.toLittleEndianUByteArray() : UByteArray {
|
||||
return UByteArray (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.toLittleEndianTypedUByteArray() : Array<UByte> {
|
||||
return Array<UByte> (8) {
|
||||
((this shr (it * 8)) and 0xFFU).toUByte()
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun ULong.toLittleEndianUByteArray() :UByteArray {
|
||||
return UByteArray (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 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
|
||||
}
|
||||
|
||||
|
||||
@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 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@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 : 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
|
||||
}
|
||||
+26
@@ -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)
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 kotlin.browser.window
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 21-Sep-2019
|
||||
*/
|
||||
actual object SRNG {
|
||||
var counter = 0
|
||||
@ExperimentalUnsignedTypes
|
||||
actual fun getRandomBytes(amount: Int): UByteArray {
|
||||
val runningOnNode = jsTypeOf(window) == "undefined"
|
||||
val randomBytes = if (runningOnNode) {
|
||||
println("Running on node")
|
||||
js("require('crypto')").randomBytes(amount).toJSON().data
|
||||
} else {
|
||||
println("Running in browser")
|
||||
js(
|
||||
"""
|
||||
var randomArray = new Uint8Array(amount);
|
||||
var crypto = (self.crypto || self.msCrypto);
|
||||
crypto.getRandomValues(randomArray);
|
||||
"""
|
||||
)
|
||||
var randomArrayResult = js("Array.prototype.slice.call(randomArray);")
|
||||
randomArrayResult
|
||||
}
|
||||
println("Random bytes: $randomBytes")
|
||||
print("Byte at ${randomBytes[0]}")
|
||||
val randomBytesUByteArray = UByteArray(amount) {
|
||||
0U
|
||||
}
|
||||
for (i in 0 until amount) {
|
||||
js("""
|
||||
randomBytesUByteArray[i] = randomBytes[i]
|
||||
""")
|
||||
}
|
||||
return randomBytesUByteArray
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.ionspin.kotlin.crypto.hash.blake2b
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 14-Jul-2019
|
||||
*/
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
actual class Blake2bDelegated actual constructor(key: UByteArray?, hashLength: Int) : Blake2b {
|
||||
override val MAX_HASH_BYTES: Int = 128
|
||||
|
||||
|
||||
override fun update(data: UByteArray) {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun update(data: String) {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digest(): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digestString(): String {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
actual class Blake2bStateless : Blake2bStatelessInterface {
|
||||
override fun digest(inputString: String, key: String?, hashLength: Int): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digest(inputMessage: UByteArray, key: UByteArray, hashLength: Int): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override val MAX_HASH_BYTES: Int
|
||||
get() = TODO("not implemented yet")
|
||||
|
||||
}
|
||||
Whitespace-only changes.
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 05-Jan-2020
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
class SRNGJsTest {
|
||||
|
||||
@Test
|
||||
fun testJsSrng() {
|
||||
val bytes1 = SRNG.getRandomBytes(10)
|
||||
val bytes2 = SRNG.getRandomBytes(10)
|
||||
println("BYTES1")
|
||||
bytes1.forEach {
|
||||
print(it.toString(16).padStart(2, '0'))
|
||||
}
|
||||
println("BYTES2")
|
||||
bytes2.forEach {
|
||||
print(it.toString(16).padStart(2, '0'))
|
||||
}
|
||||
assertTrue {
|
||||
!bytes1.contentEquals(bytes2) &&
|
||||
bytes1.size == 10 &&
|
||||
bytes2.size == 10
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+28
@@ -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() }
|
||||
Whitespace-only changes.
@@ -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
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
actual object SRNG {
|
||||
val secureRandom = SecureRandom()
|
||||
actual fun getRandomBytes(amount: Int): UByteArray {
|
||||
val byteArray = ByteArray(amount)
|
||||
secureRandom.nextBytes(byteArray)
|
||||
return byteArray.toUByteArray()
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.ionspin.kotlin.crypto.hash.blake2b
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 14-Jul-2019
|
||||
*/
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
actual class Blake2bDelegated actual constructor(key: UByteArray?, hashLength: Int) : Blake2b {
|
||||
override val MAX_HASH_BYTES: Int
|
||||
get() = TODO("not implemented yet")
|
||||
|
||||
override fun update(data: UByteArray) {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun update(data: String) {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digest(): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digestString(): String {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
}
|
||||
@ExperimentalUnsignedTypes
|
||||
actual class Blake2bStateless : Blake2bStatelessInterface {
|
||||
override fun digest(inputString: String, key: String?, hashLength: Int): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digest(inputMessage: UByteArray, key: UByteArray, hashLength: Int): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override val MAX_HASH_BYTES: Int
|
||||
get() = TODO("not implemented yet")
|
||||
|
||||
}
|
||||
+27
@@ -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() }
|
||||
Whitespace-only changes.
+43
@@ -0,0 +1,43 @@
|
||||
package com.ionspin.kotlin.crypto.hash.blake2b
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 14-Jul-2019
|
||||
*/
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
actual class Blake2bDelegated actual constructor(key: UByteArray?, hashLength: Int) : Blake2b {
|
||||
override val MAX_HASH_BYTES: Int
|
||||
get() = TODO("not implemented yet")
|
||||
|
||||
override fun update(data: UByteArray) {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun update(data: String) {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digest(): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digestString(): String {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
}
|
||||
|
||||
actual class Blake2bStateless : Blake2bStatelessInterface {
|
||||
override fun digest(inputString: String, key: String?, hashLength: Int): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override fun digest(inputMessage: UByteArray, key: UByteArray, hashLength: Int): UByteArray {
|
||||
TODO("not implemented yet")
|
||||
}
|
||||
|
||||
override val MAX_HASH_BYTES: Int
|
||||
get() = TODO("not implemented yet")
|
||||
|
||||
}
|
||||
Whitespace-only changes.
+43
@@ -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): 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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -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) }
|
||||
@@ -0,0 +1,6 @@
|
||||
headers = sodium.h
|
||||
headerFilter = sodium.h sodium/**
|
||||
staticLibraries = libsodium.a
|
||||
libraryPaths = lib
|
||||
compilerOpts = -Iinclude
|
||||
linkerOpts = {{LDFLAGS}}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.posix.*
|
||||
|
||||
/**
|
||||
* 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)
|
||||
val urandomFile = fopen("/dev/urandom", "rb")
|
||||
if (urandomFile != null) {
|
||||
fread(array, 1, amount.convert(), urandomFile)
|
||||
}
|
||||
return UByteArray(amount) {
|
||||
array[it]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -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() }
|
||||
Reference in new issue
Block a user