Implemented chacha20
This commit is contained in:
parent
c7445376ca
commit
ae1aa53f0e
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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() }) }
|
||||
|
@ -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