From 2db5523893d96062804efcc22396ab1a328da21a Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 4 Jul 2020 19:05:15 +0200 Subject: [PATCH] Cleanup 1 --- .../kotlin/com/ionspin/kotlin/crypto/Api.kt | 7 +- .../com/ionspin/kotlin/crypto/Crypto.kt | 24 +-- .../DelegatedXChaCha20Poly1305.kt | 6 +- .../authenticated/XChaCha20Poly1305Test.kt | 6 +- .../XChaCha20Poly1305Delegated.kt | 2 +- .../XChaCha20Poly1305Delegated.kt | 2 +- .../XChaCha20Poly1305Delegated.kt | 2 +- .../authenticated/XChaCha20Poly1305Pure.kt | 140 +----------------- .../authenticated/XChaCha20Poly1305Test.kt | 2 +- 9 files changed, 19 insertions(+), 172 deletions(-) diff --git a/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Api.kt b/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Api.kt index 7a2c2a6..0b7efa8 100644 --- a/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Api.kt +++ b/multiplatform-crypto-api/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Api.kt @@ -72,15 +72,10 @@ interface AuthenticatedEncryption { data class EncryptedDataPart(val data : UByteArray) data class DecryptedDataPart(val data : UByteArray) -data class MultipartEncryptedDataDescriptor(val data: UByteArray, val nonce: UByteArray) +data class MultipartEncryptionHeader(val nonce: UByteArray) class InvalidTagException : RuntimeException("Tag mismatch! Encrypted data is corrupted or tampered with.") -interface MultipartAuthenticatedVerification { - fun verifyPartialData(data: EncryptedDataPart) - fun finalizeVerificationAndPrepareDecryptor() : MultipartAuthenticatedDecryption -} - interface MultipartAuthenticatedDecryption { fun decryptPartialData(data: EncryptedDataPart) : DecryptedDataPart } diff --git a/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Crypto.kt b/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Crypto.kt index 7d162f7..e9664b9 100644 --- a/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Crypto.kt +++ b/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Crypto.kt @@ -171,32 +171,14 @@ object Crypto { class MultipartAuthenticatedEncryptor internal constructor(val key : SymmetricKey, additionalData: UByteArray) : MultipartAuthenticatedEncryption { val primitive = XChaCha20Poly1305Delegated(key.value, additionalData) + + override fun encryptPartialData(data: UByteArray): EncryptedDataPart { - return EncryptedDataPart(primitive.encryptPartialData(data)) - } - - override fun finish(): MultipartEncryptedDataDescriptor { - val finished = primitive.finishEncryption() - return MultipartEncryptedDataDescriptor(finished.first, finished.second) + return EncryptedDataPart(primitive.encrypt(data)) } } -class MultiplatformAuthenticatedVerificator internal constructor(key: SymmetricKey, multipartEncryptedDataDescriptor: MultipartEncryptedDataDescriptor, additionalData: UByteArray) : MultipartAuthenticatedVerification { - val primitive = XChaCha20Poly1305Delegated(key.value, additionalData) - val tag = multipartEncryptedDataDescriptor.data.sliceArray( - multipartEncryptedDataDescriptor.data.size - 16 until multipartEncryptedDataDescriptor.data.size - ) - override fun verifyPartialData(data: EncryptedDataPart) { - primitive.verifyPartialData(data.data) - } - - override fun finalizeVerificationAndPrepareDecryptor(): MultipartAuthenticatedDecryption { - primitive.checkTag(tag) - return MultipartAuthenticatedDecryptor(primitive) - } - -} class MultipartAuthenticatedDecryptor internal constructor(val encryptor: XChaCha20Poly1305Delegated) : MultipartAuthenticatedDecryption { override fun decryptPartialData(data: EncryptedDataPart): DecryptedDataPart { diff --git a/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/DelegatedXChaCha20Poly1305.kt b/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/DelegatedXChaCha20Poly1305.kt index 1c6405e..f749d72 100644 --- a/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/DelegatedXChaCha20Poly1305.kt +++ b/multiplatform-crypto-delegated/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/DelegatedXChaCha20Poly1305.kt @@ -13,11 +13,9 @@ expect class XChaCha20Poly1305Delegated constructor(key: UByteArray, additionalD fun decrypt(key: UByteArray, nonce: UByteArray, ciphertext: UByteArray, additionalData: UByteArray) : UByteArray } - fun encryptPartialData(data: UByteArray) : UByteArray - fun verifyPartialData(data: UByteArray) - fun checkTag(expectedTag: UByteArray) + fun encrypt(data: UByteArray) : UByteArray fun decrypt(data: UByteArray) : UByteArray - fun finishEncryption() : Pair + } diff --git a/multiplatform-crypto-delegated/src/commonTest/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Test.kt b/multiplatform-crypto-delegated/src/commonTest/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Test.kt index 4516110..746a1a5 100644 --- a/multiplatform-crypto-delegated/src/commonTest/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Test.kt +++ b/multiplatform-crypto-delegated/src/commonTest/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Test.kt @@ -4,8 +4,6 @@ import com.ionspin.kotlin.crypto.CryptoInitializerDelegated import com.ionspin.kotlin.crypto.hash.encodeToUByteArray import com.ionspin.kotlin.crypto.util.hexColumsPrint import com.ionspin.kotlin.crypto.util.testBlocking -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertTrue @@ -145,7 +143,7 @@ class XChaCha20Poly1305Test { 0xcfU, 0x49U ) val xChaChaPoly = XChaCha20Poly1305Delegated(key, additionalData) - val firstChunk = xChaChaPoly.encryptPartialData(message) + val firstChunk = xChaChaPoly.encrypt(message) val finalChunk = xChaChaPoly.finishEncryption().first val result = firstChunk + finalChunk @@ -176,7 +174,7 @@ class XChaCha20Poly1305Test { 0x84U, 0x6fU, 0xfcU, 0x75U, 0x31U, 0xbfU, 0x0cU, 0x2dU ) val xChaChaPoly = XChaCha20Poly1305Delegated(key, additionalData) - val firstChunk = xChaChaPoly.encryptPartialData(message) + val firstChunk = xChaChaPoly.encrypt(message) val finalChunk = xChaChaPoly.finishEncryption().first val result = firstChunk + finalChunk result.contentEquals(expected) diff --git a/multiplatform-crypto-delegated/src/jsMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt b/multiplatform-crypto-delegated/src/jsMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt index 5ed2c5d..f4b6b96 100644 --- a/multiplatform-crypto-delegated/src/jsMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt +++ b/multiplatform-crypto-delegated/src/jsMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt @@ -55,7 +55,7 @@ actual class XChaCha20Poly1305Delegated actual constructor(key: UByteArray, addi } - actual fun encryptPartialData(data: UByteArray): UByteArray { + actual fun encrypt(data: UByteArray): UByteArray { TODO("not implemented yet") } diff --git a/multiplatform-crypto-delegated/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt b/multiplatform-crypto-delegated/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt index 5af036f..065f9d2 100644 --- a/multiplatform-crypto-delegated/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt +++ b/multiplatform-crypto-delegated/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt @@ -59,7 +59,7 @@ actual class XChaCha20Poly1305Delegated actual constructor(key: UByteArray, addi } - actual fun encryptPartialData(data: UByteArray): UByteArray { + actual fun encrypt(data: UByteArray): UByteArray { TODO("not implemented yet") } diff --git a/multiplatform-crypto-delegated/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt b/multiplatform-crypto-delegated/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt index ba80283..03db8bd 100644 --- a/multiplatform-crypto-delegated/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt +++ b/multiplatform-crypto-delegated/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Delegated.kt @@ -97,7 +97,7 @@ actual class XChaCha20Poly1305Delegated actual constructor(val key: UByteArray,v } - actual fun encryptPartialData(data: UByteArray): UByteArray { + actual fun encrypt(data: UByteArray): UByteArray { val ciphertextWithTag = UByteArray(data.size + crypto_secretstream_xchacha20poly1305_ABYTES.toInt()) val ciphertextWithTagPinned = ciphertextWithTag.pin() crypto_secretstream_xchacha20poly1305_push( diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Pure.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Pure.kt index c944120..2695834 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Pure.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Pure.kt @@ -69,49 +69,12 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray) { } - private val updateableEncryptionPrimitive = XChaCha20Pure(key, nonce, initialCounter = 1U) - private val updateableMacPrimitive : Poly1305 private val polyBuffer = UByteArray(16) private var polyBufferByteCounter = 0 private var processedBytes = 0 - init { - val subKey = XChaCha20Pure.hChacha(key, nonce) - val authKey = - ChaCha20Pure.xorWithKeystream( - subKey.toLittleEndianUByteArray(), - ubyteArrayOf(0U, 0U, 0U, 0U) + nonce.sliceArray(16 until 24), - UByteArray(64) { 0U }, - 0U // If this is moved as a default parameter in encrypt, and not here (in 1.4-M2) - // js compiler dies with: e: java.lang.NullPointerException - // at org.jetbrains.kotlin.ir.backend.js.lower.ConstTransformer$visitConst$1$3.invoke(ConstLowering.kt:28) - // at org.jetbrains.kotlin.ir.backend.js.lower.ConstTransformer.lowerConst(ConstLowering.kt:38) - ) - updateableMacPrimitive = Poly1305(authKey) -// val additionalDataPad = UByteArray(16 - additionalData.size % 16) { 0U } -// processPolyBytes(additionalData + additionalDataPad) - - } - - // Sketch libsodium stream cipher with chachapoly, because my two pass approach to multipart encryption is - // inneficient, to put it mildly. - - // libsodium-like state - // key, 32 Bytes - // nonce, 12 Bytes - // pad, 8 bytes - - //libsodium like header - //random header bytes 24 and put that into out - //then hchacha20 of key and random bytes (out) to generate state key - //the reset counter to 1 - //then copy to state->NONCE, HCHACHAINPUTBYTES (16B) from out, length of INONCE_BYTES which is 8, which uses up all random from previous step - //Pad state with 8B of zeroes - - //header is a 24byte nonce - internal val calcKey : UByteArray = UByteArray(32) internal val calcNonce : UByteArray = UByteArray(12) @@ -131,117 +94,39 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray) { println("Calcnonce---------") } - fun encryptPartial(data: UByteArray, additionalData: UByteArray = ubyteArrayOf(), tag : UByte = 0U) : UByteArray { + fun streamEncrypt(data: UByteArray, additionalData: UByteArray = ubyteArrayOf(), tag : UByte = 0U) : UByteArray { val result = UByteArray(1 + data.size + 16) //Tag marker, ciphertext, mac //get encryption state val block = UByteArray(64) { 0U } - println("--block") - block.hexColumsPrint() - println("--block") - println("--key") - calcKey.hexColumsPrint() - println("--key") - println("--nonce") - calcNonce.hexColumsPrint() - println("--nonce") ChaCha20Pure.xorWithKeystream(calcKey, calcNonce, block, 0U).copyInto(block) // This is equivalent to the first 64 bytes of keystream - /* - 5C 9A C3 7B CA 8F 2B 12 9A D8 41 3B 0C E9 55 EF - 25 27 9A 4B 5B 7F 87 75 0C 47 E9 C9 DE 82 44 BA - 6C 51 48 F4 9C 0A 24 6B F2 7C 51 5E 62 1A 16 E1 - 28 23 C6 B5 12 2E AD 58 AD 51 AA 34 78 33 08 C9 - */ - println("--block") - block.hexColumsPrint() - println("--block") val poly1305 = Poly1305(block) block.overwriteWithZeroes() if (additionalData.isNotEmpty()) { val additionalDataPadded = additionalData + UByteArray(16 - additionalData.size % 16) { 0U } processPolyBytes(poly1305, additionalDataPadded) } - block[0] = tag ChaCha20Pure.xorWithKeystream(calcKey, calcNonce, block, 1U).copyInto(block) // This just xors block[0] with keystream processPolyBytes(poly1305, block) // but updates the mac with the full block! -// println("--poly 1") - // 13 10 8E D1 3C B9 77 C1 9B 95 66 C8 1B 8A 5D D3 -// poly1305.finalizeMac().hexColumsPrint() -// println("--poly 1") // In libsodium c code, it now sets the first byte to be a tag, we'll just save it for now val encryptedTag = block[0] //And then encrypt the rest of the message val ciphertext = ChaCha20Pure.xorWithKeystream(calcKey, calcNonce, data, 2U) // With appropriate counter - println("ciphertext-----") - /* - paddedCipherText-- - D3 2D 59 B8 C4 66 2E 47 29 C6 F9 93 4B 09 27 24 - DD F3 05 48 94 67 10 00 21 85 22 96 3C CE 8E B7 - 53 9D 46 F5 3C 5E 48 9B 8C 13 B7 28 6B B3 6C 3A - 04 B7 25 B9 50 45 08 0B 89 A2 0F 70 CC 60 1B C3 - 17 35 9F AE 82 51 43 1B 9D 53 9E E2 AF 20 1F FD - 03 59 11 51 9E AC 83 CD 78 D1 D0 E5 D7 0E 41 DE - FB 5C 7F 1C 00 00 00 00 00 00 00 00 00 00 00 00 - paddedCipherText-- - */ - (ciphertext + UByteArray(((16 - 64 + data.size) % 16)) { 0U }).hexColumsPrint() - println("pad: ${16 - ((data.size) % 16)}") - println("pad: ${((16U + data.size.toUInt() - block.size.toUInt()) % 16U).toInt()}") - println("ciphertext-----") + // Next we update the poly1305 with ciphertext and padding, BUT the padding in libsodium is not correctly calculated, so it doesn't + // pad correctly. https://github.com/jedisct1/libsodium/issues/976 + // We want to use libsodium in delegated flavour, so we will use the same incorrect padding here. + // From security standpoint there are no obvious drawbacks, as padding was initially added to decrease implementation complexity. processPolyBytes(poly1305, ciphertext + UByteArray(((16U + data.size.toUInt() - block.size.toUInt()) % 16U).toInt()) { 0U } ) //TODO this is inefficient as it creates a new array and copies data -// println("--poly cipher") - // 93 D9 13 DC AB 1D 07 D7 51 03 17 85 8A 5C F0 84 -// poly1305.finalizeMac().hexColumsPrint() -// println("--poly cipher") + // Last 16byte block containing actual additional data nad ciphertext sizes val finalMac = additionalData.size.toULong().toLittleEndianUByteArray() + (ciphertext.size + 64).toULong().toLittleEndianUByteArray() processPolyBytes(poly1305, finalMac) val mac = poly1305.finalizeMac(polyBuffer.sliceArray(0 until polyBufferByteCounter)) - //19 F3 39 CC DE 82 35 08 C1 82 DB 3D F1 EF 89 45 - println("poly final --") - mac.hexColumsPrint() - println("poly final --") return ubyteArrayOf(encryptedTag) + ciphertext + mac } - // Sketch end + fun streamDecrypt(data: UByteArray, additionalData: UByteArray = ubyteArrayOf(), tag: UBy) - fun encryptPartialData(data: UByteArray) : UByteArray { - processedBytes += data.size - val encrypted = updateableEncryptionPrimitive.xorWithKeystream(data) -// processPolyBytes(encrypted) - val cipherTextPad = UByteArray(16 - processedBytes % 16) { 0U } - val macData = cipherTextPad + -// additionalData.size.toULong().toLittleEndianUByteArray() + - processedBytes.toULong().toLittleEndianUByteArray() -// processPolyBytes(macData) - val tag = updateableMacPrimitive.finalizeMac() - - return encrypted + tag - } - - fun verifyPartialData(data: UByteArray) { -// processPolyBytes(data) - } - - fun checkTag(expectedTag: UByteArray) { - val cipherTextPad = UByteArray(16 - processedBytes % 16) { 0U } - val macData = cipherTextPad + -// additionalData.size.toULong().toLittleEndianUByteArray() + - processedBytes.toULong().toLittleEndianUByteArray() -// processPolyBytes(macData) - val tag = updateableMacPrimitive.finalizeMac() - if (!tag.contentEquals(expectedTag)) { - throw InvalidTagException() - } - } - - fun decrypt(data: UByteArray) : UByteArray { - processedBytes += data.size - val decrypted = updateableEncryptionPrimitive.xorWithKeystream(data) -// processPolyBytes(decrypted) - return decrypted - } private fun processPolyBytes(updateableMacPrimitive: Poly1305, data: UByteArray) { if (polyBufferByteCounter == 0) { @@ -284,16 +169,5 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray) { } } - fun finishEncryption() : Pair { - - val cipherTextPad = UByteArray(16 - processedBytes % 16) { 0U } - val macData = cipherTextPad + -// additionalData.size.toULong().toLittleEndianUByteArray() + - processedBytes.toULong().toLittleEndianUByteArray() -// processPolyBytes(macData) - val tag = updateableMacPrimitive.finalizeMac() - return Pair(tag, nonce) - } - } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Test.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Test.kt index 7ba0ef7..2e32d7a 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Test.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/authenticated/XChaCha20Poly1305Test.kt @@ -207,6 +207,6 @@ class XChaCha20Poly1305Test { xcha.calcNonce.contentEquals(state.sliceArray(32 until 44)) } val data = UByteArray(100) { 0U } - xcha.encryptPartial(data).hexColumsPrint() + xcha.streamEncrypt(data).hexColumsPrint() } }