Implemented chacha20
This commit is contained in:
parent
c7445376ca
commit
ae1aa53f0e
@ -1,6 +1,6 @@
|
|||||||
package com.ionspin.kotlin.crypto.symmetric
|
package com.ionspin.kotlin.crypto.symmetric
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.rotateLeft
|
import com.ionspin.kotlin.crypto.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Ugljesa Jovanovic
|
* Created by Ugljesa Jovanovic
|
||||||
@ -10,29 +10,90 @@ import com.ionspin.kotlin.crypto.util.rotateLeft
|
|||||||
class ChaCha20Pure {
|
class ChaCha20Pure {
|
||||||
companion object {
|
companion object {
|
||||||
fun quarterRound(input: UIntArray, aPosition: Int, bPosition: Int, cPosition: Int, dPosition: Int) {
|
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[aPosition] += input[bPosition]
|
||||||
input[cPosition] += input[dPosition]; input[bPosition] = input[bPosition] xor input[cPosition]; input[bPosition] = input[bPosition] rotateLeft 12
|
input[dPosition] = input[dPosition] xor input[aPosition]
|
||||||
input[aPosition] += input[bPosition]; input[dPosition] = input[dPosition] xor input[aPosition]; input[dPosition] = input[dPosition] rotateLeft 8
|
input[dPosition] = input[dPosition] rotateLeft 16
|
||||||
input[cPosition] += input[dPosition]; input[bPosition] = input[bPosition] xor input[cPosition]; input[bPosition] = input[bPosition] rotateLeft 7
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rowRound(input: UIntArray) {
|
input[cPosition] += input[dPosition]
|
||||||
Salsa20Pure.quarterRound(input, 0, 1, 2, 3)
|
input[bPosition] = input[bPosition] xor input[cPosition]
|
||||||
Salsa20Pure.quarterRound(input, 5, 6, 7, 4)
|
input[bPosition] = input[bPosition] rotateLeft 12
|
||||||
Salsa20Pure.quarterRound(input, 10, 11, 8, 9)
|
|
||||||
Salsa20Pure.quarterRound(input, 15, 12, 13, 14)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun columnRound(input: UIntArray) {
|
input[aPosition] += input[bPosition]
|
||||||
Salsa20Pure.quarterRound(input, 0, 4, 8, 12)
|
input[dPosition] = input[dPosition] xor input[aPosition]
|
||||||
Salsa20Pure.quarterRound(input, 5, 9, 13, 1)
|
input[dPosition] = input[dPosition] rotateLeft 8
|
||||||
Salsa20Pure.quarterRound(input, 10, 14, 2, 6)
|
|
||||||
Salsa20Pure.quarterRound(input, 15, 3, 7, 11)
|
input[cPosition] += input[dPosition]
|
||||||
|
input[bPosition] = input[bPosition] xor input[cPosition]
|
||||||
|
input[bPosition] = input[bPosition] rotateLeft 7
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doubleRound(input: UIntArray) {
|
fun doubleRound(input: UIntArray) {
|
||||||
columnRound(input)
|
quarterRound(input, 0, 4, 8, 12)
|
||||||
rowRound(input)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -49,6 +49,11 @@ class Salsa20Pure {
|
|||||||
return result
|
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 sigma0_32 = ubyteArrayOf(101U, 120U, 112U, 97U)
|
||||||
val sigma1_32 = ubyteArrayOf(110U, 100U, 32U, 51U)
|
val sigma1_32 = ubyteArrayOf(110U, 100U, 32U, 51U)
|
||||||
val sigma2_32 = ubyteArrayOf(50U, 45U, 98U, 121U)
|
val sigma2_32 = ubyteArrayOf(50U, 45U, 98U, 121U)
|
||||||
@ -71,22 +76,22 @@ class Salsa20Pure {
|
|||||||
val ciphertext = UByteArray(message.size)
|
val ciphertext = UByteArray(message.size)
|
||||||
val state = UIntArray(16) {
|
val state = UIntArray(16) {
|
||||||
when (it) {
|
when (it) {
|
||||||
0 -> sigma0_32.fromLittleEndianArrayToUInt()
|
0 -> sigma0_32_uint
|
||||||
1 -> key.fromLittleEndianArrayToUIntWithPosition(0)
|
1 -> key.fromLittleEndianArrayToUIntWithPosition(0)
|
||||||
2 -> key.fromLittleEndianArrayToUIntWithPosition(4)
|
2 -> key.fromLittleEndianArrayToUIntWithPosition(4)
|
||||||
3 -> key.fromLittleEndianArrayToUIntWithPosition(8)
|
3 -> key.fromLittleEndianArrayToUIntWithPosition(8)
|
||||||
4 -> key.fromLittleEndianArrayToUIntWithPosition(12)
|
4 -> key.fromLittleEndianArrayToUIntWithPosition(12)
|
||||||
5 -> sigma1_32.fromLittleEndianArrayToUInt()
|
5 -> sigma1_32_uint
|
||||||
6 -> nonce.fromLittleEndianArrayToUIntWithPosition(0)
|
6 -> nonce.fromLittleEndianArrayToUIntWithPosition(0)
|
||||||
7 -> nonce.fromLittleEndianArrayToUIntWithPosition(4)
|
7 -> nonce.fromLittleEndianArrayToUIntWithPosition(4)
|
||||||
8 -> 0U
|
8 -> 0U
|
||||||
9 -> 0U
|
9 -> 0U
|
||||||
10 -> sigma2_32.fromLittleEndianArrayToUInt()
|
10 -> sigma2_32_uint
|
||||||
11 -> key.fromLittleEndianArrayToUIntWithPosition(16)
|
11 -> key.fromLittleEndianArrayToUIntWithPosition(16)
|
||||||
12 -> key.fromLittleEndianArrayToUIntWithPosition(20)
|
12 -> key.fromLittleEndianArrayToUIntWithPosition(20)
|
||||||
13 -> key.fromLittleEndianArrayToUIntWithPosition(24)
|
13 -> key.fromLittleEndianArrayToUIntWithPosition(24)
|
||||||
14 -> key.fromLittleEndianArrayToUIntWithPosition(28)
|
14 -> key.fromLittleEndianArrayToUIntWithPosition(28)
|
||||||
15 -> sigma3_32.fromLittleEndianArrayToUInt()
|
15 -> sigma3_32_uint
|
||||||
else -> 0U
|
else -> 0U
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,12 +107,8 @@ class Salsa20Pure {
|
|||||||
|
|
||||||
hash(state).xorWithPositionsAndInsertIntoArray(
|
hash(state).xorWithPositionsAndInsertIntoArray(
|
||||||
0, remainder,
|
0, remainder,
|
||||||
message, (blocks - 1) * 64,
|
message, blocks * 64,
|
||||||
ciphertext, (blocks - 1) * 64)
|
ciphertext, blocks * 64)
|
||||||
state[8] += 1U
|
|
||||||
if (state[8] == 0U) {
|
|
||||||
state[9] += 1U
|
|
||||||
}
|
|
||||||
|
|
||||||
return ciphertext
|
return ciphertext
|
||||||
}
|
}
|
||||||
|
@ -61,22 +61,22 @@ class XSalsa20Pure {
|
|||||||
val hSalsaKey = hSalsa(key, nonce)
|
val hSalsaKey = hSalsa(key, nonce)
|
||||||
val state = UIntArray(16) {
|
val state = UIntArray(16) {
|
||||||
when (it) {
|
when (it) {
|
||||||
0 -> Salsa20Pure.sigma0_32.fromLittleEndianArrayToUInt()
|
0 -> Salsa20Pure.sigma0_32_uint
|
||||||
1 -> hSalsaKey[0]
|
1 -> hSalsaKey[0]
|
||||||
2 -> hSalsaKey[1]
|
2 -> hSalsaKey[1]
|
||||||
3 -> hSalsaKey[2]
|
3 -> hSalsaKey[2]
|
||||||
4 -> hSalsaKey[3]
|
4 -> hSalsaKey[3]
|
||||||
5 -> Salsa20Pure.sigma1_32.fromLittleEndianArrayToUInt()
|
5 -> Salsa20Pure.sigma1_32_uint
|
||||||
6 -> nonce.fromLittleEndianArrayToUIntWithPosition(16) //Last 63 bit of 192 bit nonce
|
6 -> nonce.fromLittleEndianArrayToUIntWithPosition(16) //Last 63 bit of 192 bit nonce
|
||||||
7 -> nonce.fromLittleEndianArrayToUIntWithPosition(20)
|
7 -> nonce.fromLittleEndianArrayToUIntWithPosition(20)
|
||||||
8 -> 0U
|
8 -> 0U
|
||||||
9 -> 0U
|
9 -> 0U
|
||||||
10 -> Salsa20Pure.sigma2_32.fromLittleEndianArrayToUInt()
|
10 -> Salsa20Pure.sigma2_32_uint
|
||||||
11 -> hSalsaKey[4]
|
11 -> hSalsaKey[4]
|
||||||
12 -> hSalsaKey[5]
|
12 -> hSalsaKey[5]
|
||||||
13 -> hSalsaKey[6]
|
13 -> hSalsaKey[6]
|
||||||
14 -> hSalsaKey[7]
|
14 -> hSalsaKey[7]
|
||||||
15 -> Salsa20Pure.sigma3_32.fromLittleEndianArrayToUInt()
|
15 -> Salsa20Pure.sigma3_32_uint
|
||||||
else -> 0U
|
else -> 0U
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,12 +92,9 @@ class XSalsa20Pure {
|
|||||||
|
|
||||||
Salsa20Pure.hash(state).xorWithPositionsAndInsertIntoArray(
|
Salsa20Pure.hash(state).xorWithPositionsAndInsertIntoArray(
|
||||||
0, remainder,
|
0, remainder,
|
||||||
message, (blocks - 1).coerceAtLeast(0) * 64,
|
message, blocks * 64,
|
||||||
ciphertext, (blocks - 1).coerceAtLeast(0) * 64)
|
ciphertext, blocks * 64)
|
||||||
state[8] += 1U
|
|
||||||
if (state[8] == 0U) {
|
|
||||||
state[9] += 1U
|
|
||||||
}
|
|
||||||
return ciphertext
|
return ciphertext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,11 @@ fun UByteArray.hexColumsPrint(chunk : Int = 16) {
|
|||||||
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
|
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) {
|
fun Array<ULong>.hexColumsPrint(chunk: Int = 3) {
|
||||||
val printout = this.map { it.toString(16) }.chunked(chunk)
|
val printout = this.map { it.toString(16) }.chunked(chunk)
|
||||||
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
|
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user