Dropping AES gcm from public api as it's not portable in libsodium and going the xchacha20poly1305 as only AEAD

This commit is contained in:
Ugljesa Jovanovic 2020-06-14 17:05:28 +02:00 committed by Ugljesa Jovanovic
parent 5c10d3abf4
commit e24f0a29f2
No known key found for this signature in database
GPG Key ID: 178E6DFCECCB0E0F
12 changed files with 279 additions and 38 deletions

View File

@ -71,7 +71,7 @@ It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be
### Authenticated symmetric encryption (AEAD)
* TODO
* XChaCha20-Poly1305
### Delegated flavor dependancy table
@ -82,6 +82,7 @@ The following table describes which library is used for particular cryptographic
| Blake2b | LazySodium | libsodium.js | libsodium |
| SHA256 | LazySodium | libsodium.js | libsodium |
| SHA512 | LazySodium | libsodium.js | libsodium |
| XChaCha20-Poly1305 | LazySodium | libsodium.js | libsodium |
@ -190,7 +191,34 @@ sha512.update("abc".encodeToUByteArray())
val result = sha512.digest()
```
### Symmetric encryption
### Key derivation
#### Argon2
NOTE: This implementation is tested against KAT generated by reference Argon2 implementation, which does not follow
specification completely. See this issue https://github.com/P-H-C/phc-winner-argon2/issues/183
```kotlin
val argon2Instance = Argon2(
password = "Password",
salt = "RandomSalt",
parallelism = 8,
tagLength = 64U,
requestedMemorySize = 256U, //4GB
numberOfIterations = 4U,
key = "",
associatedData = "",
argonType = ArgonType.Argon2id
)
val tag = argon2Instance.derive()
val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "")
val expectedTagString = "c255e3e94305817d5e09a7c771e574e3a81cc78fef5da4a9644b6df0" +
"0ba1c9b424e3dd0ce7e600b1269b14c84430708186a8a60403e1bfbda935991592b9ff37"
println("Tag: ${tagString}")
assertEquals(tagString, expectedTagString)
```
### Symmetric encryption (OUTDATED, won't be exposed in next release, no counterpart in delegated flavor - 0.10.1)
#### AES
@ -236,33 +264,6 @@ plainText == decrypted.toHexString()
```
### Key derivation
#### Argon2
NOTE: This implementation is tested against KAT generated by reference Argon2 implementation, which does not follow
specification completely. See this issue https://github.com/P-H-C/phc-winner-argon2/issues/183
```kotlin
val argon2Instance = Argon2(
password = "Password",
salt = "RandomSalt",
parallelism = 8,
tagLength = 64U,
requestedMemorySize = 256U, //4GB
numberOfIterations = 4U,
key = "",
associatedData = "",
argonType = ArgonType.Argon2id
)
val tag = argon2Instance.derive()
val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "")
val expectedTagString = "c255e3e94305817d5e09a7c771e574e3a81cc78fef5da4a9644b6df0" +
"0ba1c9b424e3dd0ce7e600b1269b14c84430708186a8a60403e1bfbda935991592b9ff37"
println("Tag: ${tagString}")
assertEquals(tagString, expectedTagString)
```

View File

@ -6,10 +6,10 @@ package com.ionspin.kotlin.crypto.authenticated
* on 14-Jun-2020
*/
interface Aes256GcmStateless {
/**
* Nonce autogenerated, key autogenerated
*/
fun encrypt(message: UByteArray, additionalData: UByteArray, rawData : UByteArray) : Aes256GcmEncryptionResult
fun encrypt(message: UByteArray, additionalData: UByteArray, rawData : UByteArray, key: Aes256GcmKey) : Aes256GcmEncryptionResult
fun decrypt(encryptedData: UByteArray, nonce: UByteArray, key : Aes256GcmKey) : UByteArray
}
data class Aes256GcmEncryptionResult(val cyphertext : UByteArray, val additionalData: UByteArray, val nonce: UByteArray, val tag: UByteArray) {
@ -35,3 +35,8 @@ data class Aes256GcmEncryptionResult(val cyphertext : UByteArray, val additional
return result
}
}
interface Aes256GcmKey {
val key : UByteArray
}

View File

@ -0,0 +1,8 @@
package com.ionspin.kotlin.crypto.authenticated
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jun-2020
*/
interface XChaCha20Poly1305

View File

@ -0,0 +1,47 @@
package com.ionspin.kotlin.crypto.symmetric
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jun-2020
*/
interface XChaCha20 {
interface Nonce {
val content: UByteArray
}
interface Key {
val content : UByteArray
}
fun encrypt(key: Key, inputMessage: UByteArray) : XChaCha20EncryptionResult
fun decrypt(key: Key, nonce: Nonce) : UByteArray
}
data class XChaCha20EncryptionResult(val nonce: UByteArray, val encryptionData: UByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as XChaCha20EncryptionResult
if (nonce != other.nonce) return false
if (encryptionData != other.encryptionData) return false
return true
}
override fun hashCode(): Int {
var result = nonce.hashCode()
result = 31 * result + encryptionData.hashCode()
return result
}
}
interface XChaCha20KeyProvider {
fun generateNewKey() : XChaCha20.Key
fun createFromUByteArray(uByteArray: UByteArray) : XChaCha20.Key
}

View File

@ -1,18 +1,24 @@
package com.ionspin.kotlin.crypto.authenticated
import com.ionspin.kotlin.crypto.SRNG
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jun-2020
*/
class Aes256GcmStatelessPure : Aes256GcmStateless {
internal class Aes256GcmStatelessPure : Aes256GcmStateless {
/**
* Nonce autogenerated
*/
override fun encrypt(message: UByteArray, additionalData: UByteArray, rawData : UByteArray, key:) : Aes256GcmEncryptionResult {
override fun encrypt(message: UByteArray, additionalData: UByteArray, rawData : UByteArray, key: Aes256GcmKey) : Aes256GcmEncryptionResult {
TODO()
}
}
override fun decrypt(encryptedData: UByteArray, nonce: UByteArray, key: Aes256GcmKey): UByteArray {
TODO("not implemented yet")
}
}

View File

@ -0,0 +1,7 @@
package com.ionspin.kotlin.crypto.authenticated
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jun-2020
*/

View File

@ -0,0 +1,31 @@
package com.ionspin.kotlin.crypto.symmetric
import com.ionspin.kotlin.crypto.util.*
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jun-2020
*/
class Salsa20 {
companion object {
fun coreHash(input: UByteArray) : UByteArray {
val y0 = input.fromBigEndianArrayToUintWithPosition(0)
val y1 = input.fromBigEndianArrayToUintWithPosition(4)
val y2 = input.fromBigEndianArrayToUintWithPosition(8)
val y3 = input.fromBigEndianArrayToUintWithPosition(12);
val z1 = y1 xor ((y0 + y3) rotateLeft 7)
val z2 = y2 xor ((z1 + y0) rotateLeft 9)
val z3 = y3 xor ((z2 + z1) rotateLeft 13)
val z0 = y0 xor ((z3 + z2) rotateLeft 18)
val result = UByteArray(16)
result.insertUIntAtPositionAsBigEndian(0, z0)
result.insertUIntAtPositionAsBigEndian(4, z1)
result.insertUIntAtPositionAsBigEndian(8, z2)
result.insertUIntAtPositionAsBigEndian(12, z3)
return result
}
}
}

View File

@ -0,0 +1,20 @@
package com.ionspin.kotlin.crypto.symmetric
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jun-2020
*/
class XChaCha20Pure {
companion object {
val chachaState = UByteArray(64)
fun quarterRound() {
}
}
}

View File

@ -18,6 +18,8 @@
package com.ionspin.kotlin.crypto.util
import com.ionspin.kotlin.crypto.keyderivation.argon2.ArgonBlockPointer
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
@ -72,6 +74,14 @@ infix fun ULong.rotateRight(places: Int): ULong {
return (this shr places) xor (this shl (64 - places))
}
infix fun UInt.rotateLeft(places: Int): UInt {
return (this shl places) xor (this shr (32 - places))
}
infix fun ULong.rotateLeft(places: Int): ULong {
return (this shl places) xor (this shr (64 - places))
}
infix fun Array<UByte>.xor(other : Array<UByte>) : Array<UByte> {
if (this.size != other.size) {
@ -231,9 +241,33 @@ fun UByteArray.fromLittleEndianArrayToUInt() : UInt {
return uint
}
fun UByteArray.fromLittleEndianArrayToUintWithPosition(position: Int) : UInt{
var uint = 0U
for (i in 0 until 4) {
uint = uint or (this[position + i].toUInt() shl (i * 8))
}
return uint
}
fun UByteArray.fromBigEndianArrayToUintWithPosition(position: Int) : UInt{
var uint = 0U
for (i in 0 until 4) {
uint = uint shl 8 or (this[position + i].toUInt())
}
return uint
}
fun UByteArray.insertUIntAtPositionAsLittleEndian(position: Int, value: UInt) {
for (i in position until position + 4) {
this[i] = ((value shr (i * 8)) and 0xFFU).toUByte()
}
}
fun UByteArray.insertUIntAtPositionAsBigEndian(position: Int, value: UInt) {
for (i in position until position + 4) {
this[i] = ((value shr (24 - i * 8)) and 0xFFU).toUByte()
}
}
fun Array<UByte>.fromBigEndianArrayToUInt() : UInt {
if (this.size > 4) {

View File

@ -43,7 +43,7 @@ class AesCbcTest {
println("Encrypted: ${encrypted.encryptedData.toHexString()}")
expectedCipherText == encrypted.encryptedData.toHexString() &&
iv == encrypted.initilizationVector.toHexString()
iv == encrypted.initializationVector.toHexString()
}
@ -62,7 +62,7 @@ class AesCbcTest {
val decrypted = AesCbcPure.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initilizationVector
encryptedDataAndInitializationVector.initializationVector
)
plainText == decrypted.toHexString()
}

View File

@ -0,0 +1,82 @@
package com.ionspin.kotlin.crypto.symmetric
import com.ionspin.kotlin.crypto.util.hexStringToUByteArray
import com.ionspin.kotlin.crypto.util.rotateLeft
import com.ionspin.kotlin.crypto.util.toHexString
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 14-Jun-2020
*/
class Salsa20Test {
@Test
fun testRotateLeft() {
val a = 0xc0a8787eU
val b = a rotateLeft 5
val expected = 0x150f0fd8U
assertEquals(b, expected)
}
@Test
fun testCoreHash() {
assertTrue {
val input = "00000000000000000000000000000000".hexStringToUByteArray()
val expected = "00000000000000000000000000000000".hexStringToUByteArray()
val result = Salsa20.coreHash(input)
println("Result ${result.toHexString()}")
expected.contentEquals(result)
}
assertTrue {
val input = "00000001000000000000000000000000".hexStringToUByteArray()
val expected = "08008145000000800001020020500000".hexStringToUByteArray()
val result = Salsa20.coreHash(input)
println("Result ${result.toHexString()}")
expected.contentEquals(result)
}
assertTrue {
val input = "00000000000000010000000000000000".hexStringToUByteArray()
val expected = "88000100000000010000020000402000".hexStringToUByteArray()
val result = Salsa20.coreHash(input)
println("Result ${result.toHexString()}")
expected.contentEquals(result)
}
assertTrue {
val input = "00000000000000000000000100000000".hexStringToUByteArray()
val expected = "80040000000000000000000100002000".hexStringToUByteArray()
val result = Salsa20.coreHash(input)
println("Result ${result.toHexString()}")
expected.contentEquals(result)
}
assertTrue {
val input = "00000000000000000000000000000001".hexStringToUByteArray()
val expected = "00048044000000800001000020100001".hexStringToUByteArray()
val result = Salsa20.coreHash(input)
println("Result ${result.toHexString()}")
expected.contentEquals(result)
}
assertTrue {
val input = "d3917c5b55f1c40752a58a7a8f887a3b".hexStringToUByteArray()
val expected = "3e2f308cd90a8f366ab2a9232883524c".hexStringToUByteArray()
val result = Salsa20.coreHash(input)
println("Result ${result.toHexString()}")
expected.contentEquals(result)
}
}
}