Implemented chacha20

This commit is contained in:
Ugljesa Jovanovic 2020-06-16 19:03:59 +02:00 committed by Ugljesa Jovanovic
parent c7445376ca
commit ae1aa53f0e
No known key found for this signature in database
GPG Key ID: 178E6DFCECCB0E0F
5 changed files with 197 additions and 39 deletions

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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<ULong>.hexColumsPrint(chunk: Int = 3) {
val printout = this.map { it.toString(16) }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }

View File

@ -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)
}
}
}