diff --git a/README.md b/README.md index 0aeaf5b..673ee68 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,16 @@ 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. ## Supported @@ -26,6 +35,9 @@ No, it's untested and unproven. * SHA512 * SHA256 +## Symmetric cipher +* AES + More to come. ## Integration diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index b8d272f..aa18506 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -4,9 +4,9 @@ package com.ionspin.kotlin.crypto.symmetric * Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 07/Sep/2019 */ @ExperimentalUnsignedTypes -class Aes(val aesKey: AesKey, val input: Array) { +internal class Aes internal constructor(val aesKey: AesKey, val input: Array) { companion object { - val sBox: UByteArray = + private val sBox: UByteArray = ubyteArrayOf( // @formatter:off 0x63U, 0x7cU, 0x77U, 0x7bU, 0xf2U, 0x6bU, 0x6fU, 0xc5U, 0x30U, 0x01U, 0x67U, 0x2bU, 0xfeU, 0xd7U, 0xabU, 0x76U, @@ -28,7 +28,7 @@ class Aes(val aesKey: AesKey, val input: Array) { // @formatter:on ) - val inverseSBox: UByteArray = + private val inverseSBox: UByteArray = ubyteArrayOf( // @formatter:off 0x52U, 0x09U, 0x6aU, 0xd5U, 0x30U, 0x36U, 0xa5U, 0x38U, 0xbfU, 0x40U, 0xa3U, 0x9eU, 0x81U, 0xf3U, 0xd7U, 0xfbU, @@ -52,6 +52,14 @@ class Aes(val aesKey: AesKey, val input: Array) { val rcon: UByteArray = ubyteArrayOf(0x8DU, 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1BU, 0x36U) + fun encrypt(aesKey: AesKey, input: Array): Array { + return Aes(aesKey, input).encrypt() + } + + fun decrypt(aesKey: AesKey, input: Array): Array { + return Aes(aesKey, input).decrypt() + } + } init { @@ -60,11 +68,11 @@ class Aes(val aesKey: AesKey, val input: Array) { } } - val state: Array> = (0 until 4).map{ outerCounter -> + val state: Array> = (0 until 4).map { outerCounter -> Array(4) { innerCounter -> input[innerCounter * 4 + outerCounter] } }.toTypedArray() - val numberOfRounds = when(aesKey) { + val numberOfRounds = when (aesKey) { is AesKey.Aes128Key -> 10 is AesKey.Aes192Key -> 12 is AesKey.Aes256Key -> 14 @@ -73,7 +81,6 @@ class Aes(val aesKey: AesKey, val input: Array) { val expandedKey: Array> = expandKey() - var round = 0 @@ -91,6 +98,20 @@ class Aes(val aesKey: AesKey, val input: Array) { 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]) @@ -98,22 +119,40 @@ class Aes(val aesKey: AesKey, val input: Array) { 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> = (0 until 4).map { Array(4) { 0U } }.toTypedArray() for (c in 0..3) { - stateMixed[0][c] = (2U gfm state[0][c]) xor galoisFieldMultiply( - 3U, - state[1][c] - ) xor state[2][c] xor state[3][c] + 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> = (0 until 4).map { + Array(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] = - state[0][c] xor (2U gfm state[1][c]) xor (3U gfm state[2][c]) xor state[3][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] = - state[0][c] xor state[1][c] xor (2U gfm state[2][c]) xor (3U gfm state[3][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] = - 3U gfm state[0][c] xor state[1][c] xor state[2][c] xor (2U gfm state[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) } @@ -153,6 +192,16 @@ class Aes(val aesKey: AesKey, val input: Array) { 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) } @@ -200,30 +249,85 @@ class Aes(val aesKey: AesKey, val input: Array) { return expandedKey } - fun encrypt() : Array { + fun encrypt(): Array { + printState() addRoundKey() - + printState() for (i in 0 until numberOfRounds - 1) { subBytes() + printState() shiftRows() + printState() mixColumns() + printState() addRoundKey() + printState() } subBytes() + printState() shiftRows() + printState() addRoundKey() - return state.flatten().toTypedArray() + printState() + val transposedMatrix = (0 until 4).map { outerCounter -> + Array(4) { 0U } + }.toTypedArray() + for (i in 0 until 4) { + for (j in 0 until 4) { + transposedMatrix[i][j] = state[j][i] + } + } +// printState(transposedMatrix) + return transposedMatrix.flatten().toTypedArray() } - fun decrypt() : Array { - return ubyteArrayOf().toTypedArray() + fun decrypt(): Array { + 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(4) { 0U } + }.toTypedArray() + for (i in 0 until 4) { + for (j in 0 until 4) { + transposedMatrix[i][j] = state[j][i] + } + } + printState(transposedMatrix) + return transposedMatrix.flatten().toTypedArray() } fun printState() { println() state.forEach { - println(it.joinToString(separator = " ") { it.toString(16) }) + println(it.joinToString(separator = " ") { it.toString(16) }) + } + } + + fun printState(specific : Array>) { + println() + specific.forEach { + println(it.joinToString(separator = " ") { it.toString(16) }) } } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt index bab9e23..8f4e77e 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -23,9 +23,6 @@ class AesTest { val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) fakeState.copyInto(aes.state) aes.subBytes() - aes.state.forEach { - println(it.joinToString { it.toString(16) }) - } assertTrue { aes.state[0][0] == 0xEDU.toUByte() } @@ -48,9 +45,6 @@ class AesTest { val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) fakeState.copyInto(aes.state) aes.shiftRows() - aes.state.forEach { - println(it.joinToString { it.toString(16) }) - } assertTrue { aes.state.contentDeepEquals(expectedState) } @@ -98,9 +92,6 @@ class AesTest { val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) fakeState.copyInto(aes.state) aes.mixColumns() - aes.state.forEach { - println(it.joinToString { it.toString(16) }) - } assertTrue { aes.state.contentDeepEquals(expectedState) } @@ -190,7 +181,7 @@ class AesTest { fun testEncryption() { val input = "3243f6a8885a308d313198a2e0370734" val key = "2b7e151628aed2a6abf7158809cf4f3c" - val expectedResult = "3902dc1925dc116a8409850b1dfb9732" + val expectedResult = "3925841d02dc09fbdc118597196a0b32" val aes = Aes(AesKey.Aes128Key(key), input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) val result = aes.encrypt() @@ -198,4 +189,44 @@ class AesTest { result.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) } } + + @Test + fun testDecryption() { + 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) + assertTrue { + decrypted.contentEquals(original) + } + } + + @Test + fun testDecryption2() { + 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) + } + assertTrue { + decrypted.contentDeepEquals(original) + } + } + + } \ No newline at end of file