diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/ChaCha20Pure.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/ChaCha20Pure.kt index 345c953..8cbfdfd 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/ChaCha20Pure.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/ChaCha20Pure.kt @@ -1,6 +1,6 @@ package com.ionspin.kotlin.crypto.symmetric -import com.ionspin.kotlin.crypto.util.rotateLeft +import com.ionspin.kotlin.crypto.util.* /** * Created by Ugljesa Jovanovic @@ -10,29 +10,90 @@ import com.ionspin.kotlin.crypto.util.rotateLeft class ChaCha20Pure { companion object { fun quarterRound(input: UIntArray, aPosition: Int, bPosition: Int, cPosition: Int, dPosition: Int) { - input[aPosition] += input[bPosition]; input[dPosition] = input[dPosition] xor input[aPosition]; input[dPosition] = input[dPosition] rotateLeft 16 - input[cPosition] += input[dPosition]; input[bPosition] = input[bPosition] xor input[cPosition]; input[bPosition] = input[bPosition] rotateLeft 12 - input[aPosition] += input[bPosition]; input[dPosition] = input[dPosition] xor input[aPosition]; input[dPosition] = input[dPosition] rotateLeft 8 - input[cPosition] += input[dPosition]; input[bPosition] = input[bPosition] xor input[cPosition]; input[bPosition] = input[bPosition] rotateLeft 7 - } + input[aPosition] += input[bPosition] + input[dPosition] = input[dPosition] xor input[aPosition] + input[dPosition] = input[dPosition] rotateLeft 16 - fun rowRound(input: UIntArray) { - Salsa20Pure.quarterRound(input, 0, 1, 2, 3) - Salsa20Pure.quarterRound(input, 5, 6, 7, 4) - Salsa20Pure.quarterRound(input, 10, 11, 8, 9) - Salsa20Pure.quarterRound(input, 15, 12, 13, 14) - } + input[cPosition] += input[dPosition] + input[bPosition] = input[bPosition] xor input[cPosition] + input[bPosition] = input[bPosition] rotateLeft 12 - fun columnRound(input: UIntArray) { - Salsa20Pure.quarterRound(input, 0, 4, 8, 12) - Salsa20Pure.quarterRound(input, 5, 9, 13, 1) - Salsa20Pure.quarterRound(input, 10, 14, 2, 6) - Salsa20Pure.quarterRound(input, 15, 3, 7, 11) + input[aPosition] += input[bPosition] + input[dPosition] = input[dPosition] xor input[aPosition] + input[dPosition] = input[dPosition] rotateLeft 8 + + input[cPosition] += input[dPosition] + input[bPosition] = input[bPosition] xor input[cPosition] + input[bPosition] = input[bPosition] rotateLeft 7 } fun doubleRound(input: UIntArray) { - columnRound(input) - rowRound(input) + quarterRound(input, 0, 4, 8, 12) + quarterRound(input, 1, 5, 9, 13) + quarterRound(input, 2, 6, 10, 14) + quarterRound(input, 3, 7, 11, 15) + + quarterRound(input, 0, 5, 10, 15) + quarterRound(input, 1, 6, 11, 12) + quarterRound(input, 2, 7, 8, 13) + quarterRound(input, 3, 4, 9, 14) + } + + fun hash(initialState: UIntArray): UByteArray { + val state = initialState.copyOf() + for (i in 0 until 10) { + doubleRound(state) + } + val result = UByteArray(64) + for (i in 0 until 16) { + littleEndianInverted(initialState[i] + state[i], result, i * 4) + } + return result + } + + val sigma0_32 = 1634760805U //ubyteArrayOf(101U, 120U, 112U, 97U) + val sigma1_32 = 857760878U //ubyteArrayOf(110U, 100U, 32U, 51U) + val sigma2_32 = 2036477234U //ubyteArrayOf(50U, 45U, 98U, 121U) + val sigma3_32 = 1797285236U //ubyteArrayOf(116U, 101U, 32U, 107U) + + fun encrypt(key: UByteArray, nonce: UByteArray, message: UByteArray, initialCounter: UInt = 0U): UByteArray { + val ciphertext = UByteArray(message.size) + val state = UIntArray(16) { + + + when (it) { + 0 -> sigma0_32 + 1 -> sigma1_32 + 2 -> sigma2_32 + 3 -> sigma3_32 + 4 -> key.fromLittleEndianArrayToUIntWithPosition(0) + 5 -> key.fromLittleEndianArrayToUIntWithPosition(4) + 6 -> key.fromLittleEndianArrayToUIntWithPosition(8) + 7 -> key.fromLittleEndianArrayToUIntWithPosition(12) + 8 -> key.fromLittleEndianArrayToUIntWithPosition(16) + 9 -> key.fromLittleEndianArrayToUIntWithPosition(20) + 10 -> key.fromLittleEndianArrayToUIntWithPosition(24) + 11 -> key.fromLittleEndianArrayToUIntWithPosition(28) + 12 -> initialCounter + 13 -> nonce.fromLittleEndianArrayToUIntWithPosition(0) + 14 -> nonce.fromLittleEndianArrayToUIntWithPosition(4) + 15 -> nonce.fromLittleEndianArrayToUIntWithPosition(8) + else -> 0U + } + } + val blocks = message.size / 64 + val remainder = message.size % 64 + for (i in 0 until blocks) { + hash(state).xorWithPositionsAndInsertIntoArray(0, 64, message, i * 64, ciphertext, i * 64) + state[12] += 1U + } + + hash(state).xorWithPositionsAndInsertIntoArray( + 0, remainder, + message, blocks * 64, + ciphertext, blocks * 64 + ) + return ciphertext } } } \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20Pure.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20Pure.kt index e3648b4..3260e03 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20Pure.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20Pure.kt @@ -49,6 +49,11 @@ class Salsa20Pure { return result } + internal val sigma0_32_uint = 1634760805U //ubyteArrayOf(101U, 120U, 112U, 97U) + internal val sigma1_32_uint = 857760878U //ubyteArrayOf(110U, 100U, 32U, 51U) + internal val sigma2_32_uint = 2036477234U //ubyteArrayOf(50U, 45U, 98U, 121U) + internal val sigma3_32_uint = 1797285236U //ubyteArrayOf(116U, 101U, 32U, 107U) + val sigma0_32 = ubyteArrayOf(101U, 120U, 112U, 97U) val sigma1_32 = ubyteArrayOf(110U, 100U, 32U, 51U) val sigma2_32 = ubyteArrayOf(50U, 45U, 98U, 121U) @@ -71,22 +76,22 @@ class Salsa20Pure { val ciphertext = UByteArray(message.size) val state = UIntArray(16) { when (it) { - 0 -> sigma0_32.fromLittleEndianArrayToUInt() + 0 -> sigma0_32_uint 1 -> key.fromLittleEndianArrayToUIntWithPosition(0) 2 -> key.fromLittleEndianArrayToUIntWithPosition(4) 3 -> key.fromLittleEndianArrayToUIntWithPosition(8) 4 -> key.fromLittleEndianArrayToUIntWithPosition(12) - 5 -> sigma1_32.fromLittleEndianArrayToUInt() + 5 -> sigma1_32_uint 6 -> nonce.fromLittleEndianArrayToUIntWithPosition(0) 7 -> nonce.fromLittleEndianArrayToUIntWithPosition(4) 8 -> 0U 9 -> 0U - 10 -> sigma2_32.fromLittleEndianArrayToUInt() + 10 -> sigma2_32_uint 11 -> key.fromLittleEndianArrayToUIntWithPosition(16) 12 -> key.fromLittleEndianArrayToUIntWithPosition(20) 13 -> key.fromLittleEndianArrayToUIntWithPosition(24) 14 -> key.fromLittleEndianArrayToUIntWithPosition(28) - 15 -> sigma3_32.fromLittleEndianArrayToUInt() + 15 -> sigma3_32_uint else -> 0U } } @@ -102,12 +107,8 @@ class Salsa20Pure { hash(state).xorWithPositionsAndInsertIntoArray( 0, remainder, - message, (blocks - 1) * 64, - ciphertext, (blocks - 1) * 64) - state[8] += 1U - if (state[8] == 0U) { - state[9] += 1U - } + message, blocks * 64, + ciphertext, blocks * 64) return ciphertext } diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XSalsa20Pure.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XSalsa20Pure.kt index 9144e28..e80b536 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XSalsa20Pure.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XSalsa20Pure.kt @@ -61,22 +61,22 @@ class XSalsa20Pure { val hSalsaKey = hSalsa(key, nonce) val state = UIntArray(16) { when (it) { - 0 -> Salsa20Pure.sigma0_32.fromLittleEndianArrayToUInt() + 0 -> Salsa20Pure.sigma0_32_uint 1 -> hSalsaKey[0] 2 -> hSalsaKey[1] 3 -> hSalsaKey[2] 4 -> hSalsaKey[3] - 5 -> Salsa20Pure.sigma1_32.fromLittleEndianArrayToUInt() + 5 -> Salsa20Pure.sigma1_32_uint 6 -> nonce.fromLittleEndianArrayToUIntWithPosition(16) //Last 63 bit of 192 bit nonce 7 -> nonce.fromLittleEndianArrayToUIntWithPosition(20) 8 -> 0U 9 -> 0U - 10 -> Salsa20Pure.sigma2_32.fromLittleEndianArrayToUInt() + 10 -> Salsa20Pure.sigma2_32_uint 11 -> hSalsaKey[4] 12 -> hSalsaKey[5] 13 -> hSalsaKey[6] 14 -> hSalsaKey[7] - 15 -> Salsa20Pure.sigma3_32.fromLittleEndianArrayToUInt() + 15 -> Salsa20Pure.sigma3_32_uint else -> 0U } } @@ -92,12 +92,9 @@ class XSalsa20Pure { Salsa20Pure.hash(state).xorWithPositionsAndInsertIntoArray( 0, remainder, - message, (blocks - 1).coerceAtLeast(0) * 64, - ciphertext, (blocks - 1).coerceAtLeast(0) * 64) - state[8] += 1U - if (state[8] == 0U) { - state[9] += 1U - } + message, blocks * 64, + ciphertext, blocks * 64) + return ciphertext } diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt index 8878a47..a0bea12 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt @@ -41,6 +41,11 @@ fun UByteArray.hexColumsPrint(chunk : Int = 16) { printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) } } +fun UIntArray.hexColumsPrint(chunk : Int = 4) { + val printout = this.map { it.toString(16).padStart(8, '0') }.chunked(chunk) + printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) } +} + fun Array.hexColumsPrint(chunk: Int = 3) { val printout = this.map { it.toString(16) }.chunked(chunk) printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/ChaCha20Test.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/ChaCha20Test.kt new file mode 100644 index 0000000..feedcfa --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/ChaCha20Test.kt @@ -0,0 +1,94 @@ +package com.ionspin.kotlin.crypto.symmetric + +import com.ionspin.kotlin.crypto.hash.encodeToUByteArray +import com.ionspin.kotlin.crypto.util.hexColumsPrint +import com.ionspin.kotlin.crypto.util.toHexString +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 16-Jun-2020 + */ +class ChaCha20Test { + + + @Test + fun testQuarterRound() { + val a = 0x11111111U + val b = 0x01020304U + val c = 0x9b8d6f43U + val d = 0x01234567U + val input = uintArrayOf(a, b, c, d) + val aExpected = 0xea2a92f4U + val bExpected = 0xcb1cf8ceU + val cExpected = 0x4581472eU + val dExpected = 0x5881c4bbU + val expected = uintArrayOf(aExpected, bExpected, cExpected, dExpected) + ChaCha20Pure.quarterRound(input, 0, 1, 2, 3) + assertTrue { + input.contentEquals(expected) + } + } + + @Test + fun testBlockFunction() { + //From RFC 7539 + val state = uintArrayOf( + 0x61707865U, 0x3320646eU, 0x79622d32U, 0x6b206574U, + 0x03020100U, 0x07060504U, 0x0b0a0908U, 0x0f0e0d0cU, + 0x13121110U, 0x17161514U, 0x1b1a1918U, 0x1f1e1d1cU, + 0x00000001U, 0x09000000U, 0x4a000000U, 0x00000000U, + ) + val expected = ubyteArrayOf( + 0x10U, 0xf1U, 0xe7U, 0xe4U, 0xd1U, 0x3bU, 0x59U, 0x15U, 0x50U, 0x0fU, 0xddU, 0x1fU, 0xa3U, 0x20U, 0x71U, 0xc4U, + 0xc7U, 0xd1U, 0xf4U, 0xc7U, 0x33U, 0xc0U, 0x68U, 0x03U, 0x04U, 0x22U, 0xaaU, 0x9aU, 0xc3U, 0xd4U, 0x6cU, 0x4eU, + 0xd2U, 0x82U, 0x64U, 0x46U, 0x07U, 0x9fU, 0xaaU, 0x09U, 0x14U, 0xc2U, 0xd7U, 0x05U, 0xd9U, 0x8bU, 0x02U, 0xa2U, + 0xb5U, 0x12U, 0x9cU, 0xd1U, 0xdeU, 0x16U, 0x4eU, 0xb9U, 0xcbU, 0xd0U, 0x83U, 0xe8U, 0xa2U, 0x50U, 0x3cU, 0x4eU + ) + val result = ChaCha20Pure.hash(state) + assertTrue { + expected.contentEquals(result) + } + } + + @Test + fun testEncryption() { + + //From RFC 7539 + val key = ubyteArrayOf( + 0x00U, 0x01U, 0x02U, 0x03U, 0x04U, 0x05U, + 0x06U, 0x07U, 0x08U, 0x09U, 0x0aU, 0x0bU, + 0x0cU, 0x0dU, 0x0eU, 0x0fU, 0x10U, 0x11U, + 0x12U, 0x13U, 0x14U, 0x15U, 0x16U, 0x17U, + 0x18U, 0x19U, 0x1aU, 0x1bU, 0x1cU, 0x1dU, + 0x1eU, 0x1fU + ) + val nonce = ubyteArrayOf( + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x4aU, 0x00U, 0x00U, 0x00U, 0x00U + ) + val message = + "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.".encodeToUByteArray() + val expected = ubyteArrayOf( + 0x6eU, 0x2eU, 0x35U, 0x9aU, 0x25U, 0x68U, 0xf9U, 0x80U, 0x41U, 0xbaU, 0x07U, 0x28U, 0xddU, 0x0dU, 0x69U, 0x81U, + 0xe9U, 0x7eU, 0x7aU, 0xecU, 0x1dU, 0x43U, 0x60U, 0xc2U, 0x0aU, 0x27U, 0xafU, 0xccU, 0xfdU, 0x9fU, 0xaeU, 0x0bU, + 0xf9U, 0x1bU, 0x65U, 0xc5U, 0x52U, 0x47U, 0x33U, 0xabU, 0x8fU, 0x59U, 0x3dU, 0xabU, 0xcdU, 0x62U, 0xb3U, 0x57U, + 0x16U, 0x39U, 0xd6U, 0x24U, 0xe6U, 0x51U, 0x52U, 0xabU, 0x8fU, 0x53U, 0x0cU, 0x35U, 0x9fU, 0x08U, 0x61U, 0xd8U, + 0x07U, 0xcaU, 0x0dU, 0xbfU, 0x50U, 0x0dU, 0x6aU, 0x61U, 0x56U, 0xa3U, 0x8eU, 0x08U, 0x8aU, 0x22U, 0xb6U, 0x5eU, + 0x52U, 0xbcU, 0x51U, 0x4dU, 0x16U, 0xccU, 0xf8U, 0x06U, 0x81U, 0x8cU, 0xe9U, 0x1aU, 0xb7U, 0x79U, 0x37U, 0x36U, + 0x5aU, 0xf9U, 0x0bU, 0xbfU, 0x74U, 0xa3U, 0x5bU, 0xe6U, 0xb4U, 0x0bU, 0x8eU, 0xedU, 0xf2U, 0x78U, 0x5eU, 0x42U, + 0x87U, 0x4dU, + ) + val result = ChaCha20Pure.encrypt(key, nonce, message, 1U) + println(result.toHexString()) + println(expected.toHexString()) + assertTrue { + expected.contentEquals(result) + } + + + } + + +} \ No newline at end of file