Merge pull request #4 from ionspin/aes

AES CBC CTR
This commit is contained in:
Ugljesa Jovanovic 2019-09-25 01:03:07 +02:00 committed by GitHub
commit 55456600d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1611 additions and 11 deletions

View File

@ -1,6 +1,9 @@
## Descriptive changelog
(All dates are DD.MM.YYYY)
#### AES - 0.0.3-SNAPSHOT - 25.9.2019
- Added AES with CBC and CTR modes
#### Updatable SHA hash implementation - 0.0.2 - 21.7.2019
- Added "updatable" version for SHA
- Moved sha and blake to hash package

View File

@ -7,6 +7,9 @@ Kotlin Multiplatform Crypto is a library for various cryptographic applications.
This is an extremely early release, currently only consisting of Blake2b and SHA256 and 512.
API is very opinionated, ment to be used on both encrypting and decrypting side. The idea is that API leaves less room for
errors when using it.
## Notes & Roadmap
**The API will move fast and break often until v1.0**
@ -17,22 +20,37 @@ After that tenative plan is to add 25519 curve based signing and key exchange ne
## Should I use this in production?
No, it's untested and unproven.
No.
## Should I use this in code that is critical in any way, shape or form?
No.
## Why?
This is an experimental implementation, mostly for expanding personal understanding of cryptography.
It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure.
## Integration
## Supported
## Hashing functions
* Blake2b
* SHA512
* SHA256
## Symmetric cipher (Currently only available only in 0.0.3-SNAPSHOT)
* AES
* Modes: CBC, CTR
More to come.
## Integration
#### Gradle
```kotlin
implementation("com.ionspin.kotlin:crypto:0.0.1")
implementation("com.ionspin.kotlin:crypto:0.0.2")
```
#### Snapshot builds
@ -42,7 +60,7 @@ repositories {
url = uri("https://oss.sonatype.org/content/repositories/snapshots")
}
}
implementation("com.ionspin.kotlin:crypto:0.0.1-SNAPSHOT")
implementation("com.ionspin.kotlin:crypto:0.0.3-SNAPSHOT")
```
@ -117,6 +135,58 @@ val sha512 = Sha512()
sha512.update("abc")
val result = sha512.digest()
```
### Symmetric encryption
#### AES
Aes is available with CBC and CTR mode through `AesCbc` and `AesCtr` classes/objects.
Similarly to hashes you can either use stateless or updateable version.
Initialization vector, or counter states are chosen by the SDK automaticaly, and returned alongside encrypted data
##### Stateless AesCbc and AesCtr
AesCtr
```kotlin
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCtr.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initialCounter
)
plainText == decrypted.toHexString()
```
AesCbc
```kotlin
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val encryptedDataAndInitializationVector = AesCbc.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCbc.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initilizationVector
)
plainText == decrypted.toHexString()
```

View File

@ -16,16 +16,16 @@
object Versions {
val klock = "1.1.1"
val kotlinCoroutines = "1.3.0-M2"
val kotlinCoroutines = "1.3.0"
val timber = "5.0.0-SNAPSHOT"
val oshi = "3.12.0"
val kotlin = "1.3.40"
val kotlin = "1.3.50"
val ktor = "1.1.1"
val kotlinSerialization = "0.11.1"
val nodePlugin = "1.3.0"
val dokkaPlugin = "0.9.18"
val kotlinBigNumVersion = "0.1.0-SNAPSHOT"
val kotlinBigNumVersion = "0.1.1-SNAPSHOT"
}

View File

@ -16,6 +16,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -149,9 +149,6 @@ kotlin {
}
val nativeMain by creating {
dependsOn(commonMain)
dependencies {
implementation(Deps.Native.coroutines)
}
}
val nativeTest by creating {

View File

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

View File

@ -64,3 +64,27 @@ infix fun UInt.rotateRight(places: Int): UInt {
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)
}
}
}

View File

@ -0,0 +1,377 @@
package com.ionspin.kotlin.crypto.symmetric
/**
* Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 07/Sep/2019
*/
@ExperimentalUnsignedTypes
internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UByte>) {
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: Array<UByte>): Array<UByte> {
return Aes(aesKey, input).encrypt()
}
fun decrypt(aesKey: AesKey, input: Array<UByte>): Array<UByte> {
return Aes(aesKey, input).decrypt()
}
}
val state: Array<Array<UByte>> = (0 until 4).map { outerCounter ->
Array<UByte>(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<Array<UByte>> = 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] = arrayOf(state[0][0], state[0][1], state[0][2], state[0][3])
state[1] = arrayOf(state[1][1], state[1][2], state[1][3], state[1][0])
state[2] = arrayOf(state[2][2], state[2][3], state[2][0], state[2][1])
state[3] = arrayOf(state[3][3], state[3][0], state[3][1], state[3][2])
}
fun inversShiftRows() {
state[0] = arrayOf(state[0][0], state[0][1], state[0][2], state[0][3])
state[1] = arrayOf(state[1][3], state[1][0], state[1][1], state[1][2])
state[2] = arrayOf(state[2][2], state[2][3], state[2][0], state[2][1])
state[3] = arrayOf(state[3][1], state[3][2], state[3][3], state[3][0])
}
fun mixColumns() {
val stateMixed: Array<Array<UByte>> = (0 until 4).map {
Array<UByte>(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<Array<UByte>> = (0 until 4).map {
Array<UByte>(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<Array<UByte>> {
val expandedKey = (0 until 4 * (numberOfRounds + 1)).map {
Array<UByte>(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]
}.toTypedArray()
clearArray(temp)
}
return expandedKey
}
fun encrypt(): Array<UByte> {
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 ->
Array<UByte>(4) { 0U }
}.toTypedArray()
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.flatten().toTypedArray()
}
fun decrypt(): Array<UByte> {
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 ->
Array<UByte>(4) { 0U }
}.toTypedArray()
for (i in 0 until 4) {
for (j in 0 until 4) {
transposedMatrix[i][j] = state[j][i]
}
}
printState(transposedMatrix)
state.forEach { clearArray(it) }
completed = true
return transposedMatrix.flatten().toTypedArray()
}
private fun clearArray(array : Array<UByte>) {
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 : Array<Array<UByte>>) {
if (!debug) {
return
}
println()
specific.forEach {
println(it.joinToString(separator = " ") { it.toString(16) })
}
}
}
sealed class AesKey(val key: String, val keyLength: Int) {
val keyArray: Array<UByte> = key.chunked(2).map { it.toUByte(16) }.toTypedArray()
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")
}
}
}

View File

@ -0,0 +1,241 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.symmetric
import com.ionspin.kotlin.crypto.SRNG
import com.ionspin.kotlin.crypto.chunked
import com.ionspin.kotlin.crypto.xor
/**
* Advanced encryption standard with cipher block chaining and PKCS #5
*
* For bulk encryption/decryption use [AesCbc.encrypt] and [AesCbc.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 AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializationVector: Array<UByte>? = 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) : AesCbc {
return AesCbc(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) : AesCbc {
return AesCbc(aesKey, Mode.DECRYPT)
}
/**
* Bulk encryption, returns encrypted data and a random initialization vector
*/
fun encrypt(aesKey: AesKey, data: Array<UByte>): EncryptedDataAndInitializationVector {
val aesCbc = AesCbc(aesKey, Mode.ENCRYPT)
aesCbc.addData(data)
return aesCbc.encrypt()
}
/**
* Bulk decryption, returns decrypted data
*/
fun decrypt(aesKey: AesKey, data: Array<UByte>, initialCounter: Array<UByte>? = null): Array<UByte> {
val aesCbc = AesCbc(aesKey, Mode.DECRYPT, initialCounter)
aesCbc.addData(data)
return aesCbc.decrypt()
}
private fun padToBlock(unpadded: Array<UByte>): Array<UByte> {
val paddingSize = 16 - unpadded.size
if (unpadded.size == BLOCK_BYTES) {
return unpadded
}
if (unpadded.size == BLOCK_BYTES) {
return Array(BLOCK_BYTES) {
BLOCK_BYTES.toUByte()
}
}
if (unpadded.size > BLOCK_BYTES) {
throw IllegalStateException("Block larger than 128 bytes")
}
return Array(BLOCK_BYTES) {
when (it) {
in unpadded.indices -> unpadded[it]
else -> paddingSize.toUByte()
}
}
}
}
var currentOutput: Array<UByte> = arrayOf()
var previousEncrypted: Array<UByte> = arrayOf()
val initVector = if (initializationVector.isNullOrEmpty()) {
SRNG.getRandomBytes(16)
} else {
initializationVector
}
val output = MutableList<Array<UByte>>(0) { arrayOf() }
var buffer: Array<UByte> = UByteArray(16) { 0U }.toTypedArray()
var bufferCounter = 0
fun addData(data: Array<UByte>) {
//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, bufferCounter)
} else {
chunk.copyInto(
destination = buffer,
destinationOffset = bufferCounter,
startIndex = 0,
endIndex = BLOCK_BYTES - bufferCounter
)
output += consumeBlock(buffer)
buffer = Array<UByte>(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)
output += consumeBlock(chunks[0])
output += consumeBlock(chunks[1])
} else {
output += consumeBlock(lastBlockPadded)
}
}
return EncryptedDataAndInitializationVector(
output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes },
initVector
)
}
/**
* Decrypt data
* @return Decrypted data
*/
fun decrypt(): Array<UByte> {
val removePaddingCount = output.last().last()
val removedPadding = if (removePaddingCount > 0U && removePaddingCount < 16U) {
output.last().dropLast(removePaddingCount.toInt() and 0x7F)
} else {
output.last().toList()
}
val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray()
//JS compiler freaks out here if we don't supply exact type
val reversed : List<Array<UByte>> = preparedOutput.reversed() as List<Array<UByte>>
val folded : Array<UByte> = reversed.foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc ->
acc + arrayOfUBytes }
return folded
}
private fun appendToBuffer(array: Array<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size
}
private fun consumeBlock(data: Array<UByte>): Array<UByte> {
return when (mode) {
Mode.ENCRYPT -> {
currentOutput = if (currentOutput.isEmpty()) {
println("IV: $initVector")
Aes.encrypt(aesKey, data xor initVector)
} else {
Aes.encrypt(aesKey, data xor currentOutput)
}
currentOutput
}
Mode.DECRYPT -> {
if (currentOutput.isEmpty()) {
currentOutput = Aes.decrypt(aesKey, data) xor initVector
} else {
currentOutput = Aes.decrypt(aesKey, data) xor previousEncrypted
}
previousEncrypted = data
currentOutput
}
}
}
}
@ExperimentalUnsignedTypes
data class EncryptedDataAndInitializationVector(val encryptedData : Array<UByte>, val initilizationVector : Array<UByte>) {
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
}
}

View File

@ -0,0 +1,209 @@
/*
* 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.chunked
import com.ionspin.kotlin.crypto.symmetric.AesCtr.Companion.encrypt
import com.ionspin.kotlin.crypto.xor
/**
*
* Advanced encryption standard with counter mode
*
* For bulk encryption/decryption use [AesCtr.encrypt] and [AesCtr.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 AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCounter: Array<UByte>? = 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) : AesCtr {
return AesCtr(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) : AesCtr {
return AesCtr(aesKey, Mode.DECRYPT)
}
/**
* Bulk encryption, returns encrypted data and a random initial counter
*/
fun encrypt(aesKey: AesKey, data: Array<UByte>): EncryptedDataAndInitialCounter {
val aesCtr = AesCtr(aesKey, Mode.ENCRYPT)
aesCtr.addData(data)
return aesCtr.encrypt()
}
/**
* Bulk decryption, returns decrypted data
*/
fun decrypt(aesKey: AesKey, data: Array<UByte>, initialCounter: Array<UByte>? = null): Array<UByte> {
val aesCtr = AesCtr(aesKey, Mode.DECRYPT, initialCounter)
aesCtr.addData(data)
return aesCtr.decrypt()
}
}
var currentOutput: Array<UByte> = arrayOf()
var previousEncrypted: Array<UByte> = arrayOf()
val counterStart = if (initialCounter.isNullOrEmpty()) {
SRNG.getRandomBytes(16)
} else {
initialCounter
}
var blockCounter = modularCreator.fromBigInteger(BigInteger.fromUByteArray(counterStart, Endianness.BIG))
val output = MutableList<Array<UByte>>(0) { arrayOf() }
var buffer: Array<UByte> = UByteArray(16) { 0U }.toTypedArray()
var bufferCounter = 0
fun addData(data: Array<UByte>) {
//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, bufferCounter)
} else {
chunk.copyInto(
destination = buffer,
destinationOffset = bufferCounter,
startIndex = 0,
endIndex = BLOCK_BYTES - bufferCounter
)
output += consumeBlock(buffer, blockCounter)
blockCounter += 1
buffer = Array<UByte>(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(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes },
counterStart
)
}
/**
* Decrypt data
* @return Decrypted data
*/
fun decrypt(): Array<UByte> {
if (bufferCounter > 0) {
output += consumeBlock(buffer, blockCounter)
}
//JS compiler freaks out here if we don't supply exact type
val reversed: List<Array<UByte>> = output.reversed() as List<Array<UByte>>
val folded: Array<UByte> = reversed.foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc ->
acc + arrayOfUBytes
}
return folded
}
private fun appendToBuffer(array: Array<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size
}
private fun consumeBlock(data: Array<UByte>, blockCount: ModularBigInteger): Array<UByte> {
val blockCountAsByteArray = blockCount.toUByteArray(Endianness.BIG).expandCounterTo16Bytes()
return when (mode) {
Mode.ENCRYPT -> {
Aes.encrypt(aesKey, blockCountAsByteArray) xor data
}
Mode.DECRYPT -> {
Aes.encrypt(aesKey, blockCountAsByteArray) xor data
}
}
}
private fun Array<UByte>.expandCounterTo16Bytes() : Array<UByte> {
return if (this.size < 16) {
println("Expanding")
val diff = 16 - this.size
val pad = Array<UByte>(diff) { 0U }
pad + this
} else {
this
}
}
}
@ExperimentalUnsignedTypes
data class EncryptedDataAndInitialCounter(val encryptedData : Array<UByte>, val initialCounter : Array<UByte>) {
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
}
}

View File

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

View File

@ -0,0 +1,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 kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
class SRNGTest {
@Test
fun testSrng() {
val randomBytes1 = SRNG.getRandomBytes(10)
val randomBytes2 = SRNG.getRandomBytes(10)
assertTrue { !randomBytes1.contentEquals(randomBytes2) }
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.hexStringToUByteArray
import com.ionspin.kotlin.crypto.toHexString
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
@ExperimentalUnsignedTypes
class AesCbcTest {
@Test
fun testCbcEncryption() {
assertTrue {
val key = "4278b840fb44aaa757c1bf04acbe1a3e"
val iv = "57f02a5c5339daeb0a2908a06ac6393f"
val plaintext = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val expectedCipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7"
val aesCbc =
AesCbc(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initializationVector = iv.hexStringToUByteArray())
aesCbc.addData(plaintext.hexStringToUByteArray())
val encrypted = aesCbc.encrypt()
println("Encrypted: ${encrypted.encryptedData.toHexString()}")
expectedCipherText == encrypted.encryptedData.toHexString() &&
iv == encrypted.initilizationVector.toHexString()
}
}
@Test
fun testEncryptionApi() {
assertTrue {
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val encryptedDataAndInitializationVector = AesCbc.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCbc.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initilizationVector
)
plainText == decrypted.toHexString()
}
}
@Test
fun testCbcDecryption() {
assertTrue {
val key = "4278b840fb44aaa757c1bf04acbe1a3e"
val iv = "57f02a5c5339daeb0a2908a06ac6393f"
val cipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7"
val expectedPlainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val aesCbc =
AesCbc(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initializationVector = iv.hexStringToUByteArray())
aesCbc.addData(cipherText.hexStringToUByteArray())
val decrypted = aesCbc.decrypt()
println("Decrypted: ${decrypted.toHexString()}")
expectedPlainText == decrypted.toHexString()
}
}
@Test
fun testDecryptionApi() {
assertTrue {
val key = "4278b840fb44aaa757c1bf04acbe1a3e"
val iv = "57f02a5c5339daeb0a2908a06ac6393f"
val cipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7"
val expectedPlainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val decrypted = AesCbc.decrypt(AesKey.Aes128Key(key), cipherText.hexStringToUByteArray(), iv.hexStringToUByteArray())
println("Decrypted: ${decrypted.toHexString()}")
expectedPlainText == decrypted.toHexString()
}
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.hexStringToUByteArray
import com.ionspin.kotlin.crypto.toHexString
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
@ExperimentalUnsignedTypes
class AesCtrTest {
@Test
fun testCtrEncryption() {
assertTrue {
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
val plaintext =
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val expectedCipherText =
"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
val aesCtr = AesCtr(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initialCounter = ic.hexStringToUByteArray())
aesCtr.addData(
plaintext.hexStringToUByteArray()
)
val encrypted = aesCtr.encrypt()
println("Encrypted: ${encrypted.encryptedData.toHexString()}")
expectedCipherText == encrypted.encryptedData.toHexString() &&
ic == encrypted.initialCounter.toHexString()
}
}
@Test
fun testEncryptionApi() {
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCtr.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initialCounter
)
assertTrue {
plainText == decrypted.toHexString()
}
}
@Test
fun testCtrDecryption() {
assertTrue {
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
val cipherText =
"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
val expectedPlainText =
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val aesCtr = AesCtr(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initialCounter = ic.hexStringToUByteArray())
aesCtr.addData(cipherText.hexStringToUByteArray())
val decrypted = aesCtr.decrypt()
println("Decrypted: ${decrypted.toHexString()}")
expectedPlainText == decrypted.toHexString()
}
}
@Test
fun testCtrDecryptionApi() {
assertTrue {
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
val cipherText =
"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
val expectedPlainText =
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val decrypted = AesCtr.decrypt(AesKey.Aes128Key(key), cipherText.hexStringToUByteArray(), ic.hexStringToUByteArray())
println("Decrypted: ${decrypted.toHexString()}")
expectedPlainText == decrypted.toHexString()
}
}
}

View File

@ -0,0 +1,269 @@
package com.ionspin.kotlin.crypto.symmetric
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 10/Sep/2019
*/
@ExperimentalUnsignedTypes
class AesTest {
val irrelevantKey = "01234567890123345678901234567890"
val irrelevantInput = UByteArray(16) { 0U }.toTypedArray()
@Test
fun testSubBytes() {
val fakeState = arrayOf(
ubyteArrayOf(0x53U, 0U, 0U, 0U).toTypedArray(),
ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(),
ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(),
ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray()
)
val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput)
fakeState.copyInto(aes.state)
aes.subBytes()
assertTrue {
aes.state[0][0] == 0xEDU.toUByte()
}
}
@Test
fun testShiftRows() {
val fakeState = arrayOf(
ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(),
ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(),
ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(),
ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray()
)
val expectedState = arrayOf(
ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(),
ubyteArrayOf(1U, 2U, 3U, 0U).toTypedArray(),
ubyteArrayOf(2U, 3U, 0U, 1U).toTypedArray(),
ubyteArrayOf(3U, 0U, 1U, 2U).toTypedArray()
)
val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput)
fakeState.copyInto(aes.state)
aes.shiftRows()
assertTrue {
aes.state.contentDeepEquals(expectedState)
}
}
@Test
fun testGaloisMultiply() {
//Samples from FIPS-197
assertTrue {
val a = 0x57U
val b = 0x83U
val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput)
val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte())
c == 0xC1U.toUByte()
}
assertTrue {
val a = 0x57U
val b = 0x13U
val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput)
val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte())
c == 0xFEU.toUByte()
}
}
@Test
fun testMixColumns() {
//Test vectors from wikipedia
val fakeState = arrayOf(
ubyteArrayOf(0xdbU, 0xf2U, 0x01U, 0xc6U).toTypedArray(),
ubyteArrayOf(0x13U, 0x0aU, 0x01U, 0xc6U).toTypedArray(),
ubyteArrayOf(0x53U, 0x22U, 0x01U, 0xc6U).toTypedArray(),
ubyteArrayOf(0x45U, 0x5cU, 0x01U, 0xc6U).toTypedArray()
)
val expectedState = arrayOf(
ubyteArrayOf(0x8eU, 0x9fU, 0x01U, 0xc6U).toTypedArray(),
ubyteArrayOf(0x4dU, 0xdcU, 0x01U, 0xc6U).toTypedArray(),
ubyteArrayOf(0xa1U, 0x58U, 0x01U, 0xc6U).toTypedArray(),
ubyteArrayOf(0xbcU, 0x9dU, 0x01U, 0xc6U).toTypedArray()
)
val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput)
fakeState.copyInto(aes.state)
aes.mixColumns()
assertTrue {
aes.state.contentDeepEquals(expectedState)
}
}
@Test
fun testKeyExpansion() {
assertTrue {
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val expectedExpandedKey = uintArrayOf(
// @formatter:off
0x2b7e1516U, 0x28aed2a6U, 0xabf71588U, 0x09cf4f3cU, 0xa0fafe17U, 0x88542cb1U,
0x23a33939U, 0x2a6c7605U, 0xf2c295f2U, 0x7a96b943U, 0x5935807aU, 0x7359f67fU,
0x3d80477dU, 0x4716fe3eU, 0x1e237e44U, 0x6d7a883bU, 0xef44a541U, 0xa8525b7fU,
0xb671253bU, 0xdb0bad00U, 0xd4d1c6f8U, 0x7c839d87U, 0xcaf2b8bcU, 0x11f915bcU,
0x6d88a37aU, 0x110b3efdU, 0xdbf98641U, 0xca0093fdU, 0x4e54f70eU, 0x5f5fc9f3U,
0x84a64fb2U, 0x4ea6dc4fU, 0xead27321U, 0xb58dbad2U, 0x312bf560U, 0x7f8d292fU,
0xac7766f3U, 0x19fadc21U, 0x28d12941U, 0x575c006eU, 0xd014f9a8U, 0xc9ee2589U,
0xe13f0cc8U, 0xb6630ca6U
// @formatter:on
).toTypedArray()
val aes = Aes(AesKey.Aes128Key(key), irrelevantInput)
val result = aes.expandedKey.map {
it.foldIndexed(0U) { index, acc, uByte ->
acc + (uByte.toUInt() shl (24 - index * 8))
}
}.toTypedArray()
expectedExpandedKey.contentEquals(result)
}
assertTrue {
val key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"
val expectedExpandedKey = uintArrayOf(
// @formatter:off
0x8e73b0f7U, 0xda0e6452U, 0xc810f32bU, 0x809079e5U, 0x62f8ead2U, 0x522c6b7bU,
0xfe0c91f7U, 0x2402f5a5U, 0xec12068eU, 0x6c827f6bU, 0x0e7a95b9U, 0x5c56fec2U, 0x4db7b4bdU, 0x69b54118U,
0x85a74796U, 0xe92538fdU, 0xe75fad44U, 0xbb095386U, 0x485af057U, 0x21efb14fU, 0xa448f6d9U, 0x4d6dce24U,
0xaa326360U, 0x113b30e6U, 0xa25e7ed5U, 0x83b1cf9aU, 0x27f93943U, 0x6a94f767U, 0xc0a69407U, 0xd19da4e1U,
0xec1786ebU, 0x6fa64971U, 0x485f7032U, 0x22cb8755U, 0xe26d1352U, 0x33f0b7b3U, 0x40beeb28U, 0x2f18a259U,
0x6747d26bU, 0x458c553eU, 0xa7e1466cU, 0x9411f1dfU, 0x821f750aU, 0xad07d753U, 0xca400538U, 0x8fcc5006U,
0x282d166aU, 0xbc3ce7b5U, 0xe98ba06fU, 0x448c773cU, 0x8ecc7204U, 0x01002202U
// @formatter:on
).toTypedArray()
val aes = Aes(AesKey.Aes192Key(key), irrelevantInput)
val result = aes.expandedKey.map {
it.foldIndexed(0U) { index, acc, uByte ->
acc + (uByte.toUInt() shl (24 - index * 8))
}
}.toTypedArray()
expectedExpandedKey.contentEquals(result)
}
assertTrue {
val key = "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"
val expectedExpandedKey = uintArrayOf(
// @formatter:off
0x603deb10U, 0x15ca71beU, 0x2b73aef0U, 0x857d7781U, 0x1f352c07U, 0x3b6108d7U, 0x2d9810a3U, 0x0914dff4U,
0x9ba35411U, 0x8e6925afU, 0xa51a8b5fU, 0x2067fcdeU, 0xa8b09c1aU, 0x93d194cdU, 0xbe49846eU, 0xb75d5b9aU,
0xd59aecb8U, 0x5bf3c917U, 0xfee94248U, 0xde8ebe96U, 0xb5a9328aU, 0x2678a647U, 0x98312229U, 0x2f6c79b3U,
0x812c81adU, 0xdadf48baU, 0x24360af2U, 0xfab8b464U, 0x98c5bfc9U, 0xbebd198eU, 0x268c3ba7U, 0x09e04214U,
0x68007bacU, 0xb2df3316U, 0x96e939e4U, 0x6c518d80U, 0xc814e204U, 0x76a9fb8aU, 0x5025c02dU, 0x59c58239U,
0xde136967U, 0x6ccc5a71U, 0xfa256395U, 0x9674ee15U, 0x5886ca5dU, 0x2e2f31d7U, 0x7e0af1faU, 0x27cf73c3U,
0x749c47abU, 0x18501ddaU, 0xe2757e4fU, 0x7401905aU, 0xcafaaae3U, 0xe4d59b34U, 0x9adf6aceU, 0xbd10190dU,
0xfe4890d1U, 0xe6188d0bU, 0x046df344U, 0x706c631eU
// @formatter:on
).toTypedArray()
val aes = Aes(AesKey.Aes256Key(key), irrelevantInput)
val result = aes.expandedKey.map {
it.foldIndexed(0U) { index, acc, uByte ->
acc + (uByte.toUInt() shl (24 - index * 8))
}
}.toTypedArray()
expectedExpandedKey.contentEquals(result)
}
}
@Test
fun testEncryption() {
val input = "3243f6a8885a308d313198a2e0370734"
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val expectedResult = "3925841d02dc09fbdc118597196a0b32"
val aes = Aes(AesKey.Aes128Key(key), input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray())
val result = aes.encrypt()
assertTrue {
result.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray())
}
}
@Test
fun testEncryptionAndDecryption() {
assertTrue {
val input = "3243f6a8885a308d313198a2e0370734"
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val expectedResult = "3925841d02dc09fbdc118597196a0b32"
val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()
val aes = Aes(AesKey.Aes128Key(key), original)
val encrypted = aes.encrypt()
assertTrue {
encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray())
}
val decrypted = Aes.decrypt(AesKey.Aes128Key(key), encrypted)
decrypted.contentEquals(original)
}
assertTrue {
val input = "00112233445566778899aabbccddeeff"
val key = "000102030405060708090a0b0c0d0e0f"
val expectedResult = "69c4e0d86a7b0430d8cdb78070b4c55a"
val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()
val aes = Aes(AesKey.Aes128Key(key), original)
val encrypted = aes.encrypt()
assertTrue {
encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray())
}
val aesDec = Aes(AesKey.Aes128Key(key), encrypted)
val decrypted = aesDec.decrypt()
assertTrue {
aesDec.expandedKey.contentDeepEquals(aes.expandedKey)
}
decrypted.contentDeepEquals(original)
}
assertTrue {
val input = "00112233445566778899aabbccddeeff"
val key = "000102030405060708090a0b0c0d0e0f"
val expectedResult = "69c4e0d86a7b0430d8cdb78070b4c55a"
val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()
val encrypted = Aes.encrypt(AesKey.Aes128Key(key), original)
assertTrue {
encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray())
}
val decrypted = Aes.decrypt(AesKey.Aes128Key(key), encrypted)
decrypted.contentDeepEquals(original)
}
assertTrue {
val input = "00112233445566778899aabbccddeeff"
val key = "000102030405060708090a0b0c0d0e0f1011121314151617"
val expectedResult = "dda97ca4864cdfe06eaf70a0ec0d7191"
val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()
val encrypted = Aes.encrypt(AesKey.Aes192Key(key), original)
assertTrue {
encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray())
}
val decrypted = Aes.decrypt(AesKey.Aes192Key(key), encrypted)
decrypted.contentDeepEquals(original)
}
assertTrue {
val input = "00112233445566778899aabbccddeeff"
val key = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
val expectedResult = "8ea2b7ca516745bfeafc49904b496089"
val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()
val encrypted = Aes.encrypt(AesKey.Aes256Key(key), original)
assertTrue {
encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray())
}
val decrypted = Aes.decrypt(AesKey.Aes256Key(key), encrypted)
decrypted.contentDeepEquals(original)
}
}
}

View File

@ -0,0 +1,36 @@
/*
* 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
*/
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.
}
}

View File

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

View File

@ -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): Array<UByte> {
memScoped {
val array = allocArray<UByteVar>(amount)
val urandomFile = fopen("/dev/urandom", "rb")
if (urandomFile != null) {
fread(array, 1, amount.convert(), urandomFile)
}
return Array(amount) {
array[it]
}
}
}
}