diff --git a/README.md b/README.md index 901857d..a3f5b73 100644 --- a/README.md +++ b/README.md @@ -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) -``` - diff --git a/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/Aes256Gcm.kt b/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/Aes256Gcm.kt index 6b06777..66c665e 100644 --- a/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/Aes256Gcm.kt +++ b/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/Aes256Gcm.kt @@ -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 +} diff --git a/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305.kt b/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305.kt new file mode 100644 index 0000000..68fa7fd --- /dev/null +++ b/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305.kt @@ -0,0 +1,8 @@ +package com.ionspin.kotlin.crypto.authenticated + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 14-Jun-2020 + */ +interface XChaCha20Poly1305 \ No newline at end of file diff --git a/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XChaCha20.kt b/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XChaCha20.kt new file mode 100644 index 0000000..a14a769 --- /dev/null +++ b/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XChaCha20.kt @@ -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 +} \ No newline at end of file diff --git a/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/DelegatedAes256Gcm.kt b/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/DelegatedXChaCha20Poly1305.kt similarity index 100% rename from multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/DelegatedAes256Gcm.kt rename to multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/DelegatedXChaCha20Poly1305.kt diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/Aes256GcmPure.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/Aes256GcmPure.kt index da554fe..afd913e 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/Aes256GcmPure.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/Aes256GcmPure.kt @@ -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() } -} \ No newline at end of file + override fun decrypt(encryptedData: UByteArray, nonce: UByteArray, key: Aes256GcmKey): UByteArray { + TODO("not implemented yet") + } + +} diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaChaPoly1305.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaChaPoly1305.kt new file mode 100644 index 0000000..055b82f --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaChaPoly1305.kt @@ -0,0 +1,7 @@ +package com.ionspin.kotlin.crypto.authenticated + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 14-Jun-2020 + */ diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20.kt new file mode 100644 index 0000000..770dd3a --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20.kt @@ -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 + } + } + +} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XChaCha20Pure.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XChaCha20Pure.kt new file mode 100644 index 0000000..10fc1a8 --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/XChaCha20Pure.kt @@ -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() { + + } + } + +} \ No newline at end of file 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 28e9232..a654ea5 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 @@ -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.xor(other : Array) : Array { 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.fromBigEndianArrayToUInt() : UInt { if (this.size > 4) { diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt index d2b25b3..8a6af88 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt @@ -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() } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20Test.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20Test.kt new file mode 100644 index 0000000..5e758d5 --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/Salsa20Test.kt @@ -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) + } + } +} \ No newline at end of file