From f2e2dbbe2179a69767d5583d8b21a4f8621222ee Mon Sep 17 00:00:00 2001 From: benkuly Date: Wed, 13 Dec 2023 13:26:47 +0100 Subject: [PATCH] add aes gcm --- .../net/folivo/trixnity/crypto/core/aesCtr.kt | 6 +- .../net/folivo/trixnity/crypto/core/aesGcm.kt | 107 ++++++++++++ .../crypto/core/AesDecryptionException.kt | 3 + .../net/folivo/trixnity/crypto/core/aesCtr.kt | 2 - .../net/folivo/trixnity/crypto/core/aesGcm.kt | 12 ++ .../folivo/trixnity/crypto/core/AesGcmTest.kt | 71 ++++++++ .../net/folivo/trixnity/crypto/core/aesCtr.kt | 3 +- .../net/folivo/trixnity/crypto/core/aesGcm.kt | 82 ++++++++++ .../src/jsMain/kotlin/nodeCrypto.kt | 2 + .../net/folivo/trixnity/crypto/core/aesGcm.kt | 36 +++++ .../net/folivo/trixnity/crypto/core/aesCtr.kt | 4 +- .../net/folivo/trixnity/crypto/core/aesGcm.kt | 153 ++++++++++++++++++ 12 files changed, 473 insertions(+), 8 deletions(-) create mode 100644 trixnity-crypto-core/src/appleMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt create mode 100644 trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/AesDecryptionException.kt create mode 100644 trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt create mode 100644 trixnity-crypto-core/src/commonTest/kotlin/net/folivo/trixnity/crypto/core/AesGcmTest.kt create mode 100644 trixnity-crypto-core/src/jsMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt create mode 100644 trixnity-crypto-core/src/jvmMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt create mode 100644 trixnity-crypto-core/src/opensslMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt diff --git a/trixnity-crypto-core/src/appleMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt b/trixnity-crypto-core/src/appleMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt index 13ada130d..1547ff62d 100644 --- a/trixnity-crypto-core/src/appleMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt +++ b/trixnity-crypto-core/src/appleMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt @@ -16,15 +16,15 @@ actual fun ByteArrayFlow.decryptAes256Ctr( initialisationVector: ByteArray ): ByteArrayFlow = try { - check(key.isNotEmpty()) { "key must not be empty" } - check(initialisationVector.isNotEmpty()) { "initialisationVector must not be empty" } + require(key.isNotEmpty()) { "key must not be empty" } + require(initialisationVector.isNotEmpty()) { "initialisationVector must not be empty" } withCCCryptor(kCCDecrypt, key, initialisationVector) } catch (error: Throwable) { throw AesDecryptionException(error) } @OptIn(ExperimentalUnsignedTypes::class) -fun ByteArrayFlow.withCCCryptor( +private fun ByteArrayFlow.withCCCryptor( operation: CCOperation, key: ByteArray, initialisationVector: ByteArray diff --git a/trixnity-crypto-core/src/appleMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt b/trixnity-crypto-core/src/appleMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt new file mode 100644 index 000000000..98dcc2210 --- /dev/null +++ b/trixnity-crypto-core/src/appleMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt @@ -0,0 +1,107 @@ +package net.folivo.trixnity.crypto.core + +import kotlinx.cinterop.* +import platform.CoreCrypto.* +import platform.posix.* + +actual suspend fun ByteArray.encryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray { + require(initialisationVector.size == 12) { "initialization vector must have size 12" } + return withCCCryptor(kCCEncrypt, key, initialisationVector) +} + +actual suspend fun ByteArray.decryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray { + require(initialisationVector.size == 12) { "initialization vector must have size 12" } + return try { + require(key.isNotEmpty()) { "key must not be empty" } + require(initialisationVector.isNotEmpty()) { "initialisationVector must not be empty" } + withCCCryptor(kCCDecrypt, key, initialisationVector) + } catch (error: Throwable) { + throw AesDecryptionException(error) + } +} + +@OptIn(ExperimentalUnsignedTypes::class) +private fun ByteArray.withCCCryptor( + operation: CCOperation, + key: ByteArray, + initialisationVector: ByteArray +): ByteArray = + memScoped { + val context = alloc() + try { + key.asUByteArray().usePinned { pinnedKey -> + initialisationVector.asUByteArray().usePinned { pinnedInitialisationVector -> + checkError( + CCCryptorCreateWithMode( + op = operation, + mode = kCCModeGCM, + alg = kCCAlgorithmAES, + options = 0.convert(), + key = pinnedKey.addressOf(0), + keyLength = key.size.convert(), + iv = pinnedInitialisationVector.addressOf(0), + tweak = null, + tweakLength = 0.convert(), + numRounds = 0, + padding = 0u, + cryptorRef = context.ptr, + ) + ) + } + } + val input = this@withCCCryptor + return input.asUByteArray().usePinned { pinnedInput -> + memScoped { + val predictedOutputLength: Int = + CCCryptorGetOutputLength( + cryptorRef = context.value, + inputLength = input.size.convert(), + final = false + ).convert() + val output = ByteArray(predictedOutputLength) + val outputLength = alloc() + output.asUByteArray().usePinned { pinnedOutput -> + checkError( + CCCryptorUpdate( + cryptorRef = context.value, + dataIn = pinnedInput.addressOf(0), + dataInLength = input.size.convert(), + dataOut = pinnedOutput.addressOf(0), + dataOutAvailable = output.size.convert(), + dataOutMoved = outputLength.ptr, + ) + ) + } + output.wrapSizeTo(outputLength.value.convert()) + } + } + memScoped { + val predictedOutputLength: Int = + CCCryptorGetOutputLength( + cryptorRef = context.value, + inputLength = 0.convert(), + final = true + ).convert() + val output = ByteArray(predictedOutputLength.coerceAtLeast(1)) + val outputLength = alloc() + output.asUByteArray().usePinned { pinnedOutput -> + checkError( + CCCryptorFinal( + cryptorRef = context.value, + dataOut = pinnedOutput.addressOf(0), + dataOutAvailable = output.size.convert(), + dataOutMoved = outputLength.ptr, + ) + ) + } + output.wrapSizeTo(outputLength.value.convert()) + } + } finally { + CCCryptorRelease(context.value) + } + } \ No newline at end of file diff --git a/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/AesDecryptionException.kt b/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/AesDecryptionException.kt new file mode 100644 index 000000000..b9cb8e5ec --- /dev/null +++ b/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/AesDecryptionException.kt @@ -0,0 +1,3 @@ +package net.folivo.trixnity.crypto.core + +class AesDecryptionException(reason: Throwable) : Exception(reason) \ No newline at end of file diff --git a/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt b/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt index bb239ad80..a09287551 100644 --- a/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt +++ b/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt @@ -3,8 +3,6 @@ package net.folivo.trixnity.crypto.core import net.folivo.trixnity.utils.ByteArrayFlow -class AesDecryptionException(reason: Throwable) : Exception(reason) - expect fun ByteArrayFlow.encryptAes256Ctr( key: ByteArray, initialisationVector: ByteArray diff --git a/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt b/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt new file mode 100644 index 000000000..e0057ffdb --- /dev/null +++ b/trixnity-crypto-core/src/commonMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt @@ -0,0 +1,12 @@ +package net.folivo.trixnity.crypto.core + + +expect suspend fun ByteArray.encryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray + +expect suspend fun ByteArray.decryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray \ No newline at end of file diff --git a/trixnity-crypto-core/src/commonTest/kotlin/net/folivo/trixnity/crypto/core/AesGcmTest.kt b/trixnity-crypto-core/src/commonTest/kotlin/net/folivo/trixnity/crypto/core/AesGcmTest.kt new file mode 100644 index 000000000..0b9d0e99a --- /dev/null +++ b/trixnity-crypto-core/src/commonTest/kotlin/net/folivo/trixnity/crypto/core/AesGcmTest.kt @@ -0,0 +1,71 @@ +package net.folivo.trixnity.crypto.core + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import korlibs.crypto.encoding.hex +import korlibs.crypto.encoding.unhex +import kotlinx.coroutines.test.runTest +import kotlin.random.Random +import kotlin.test.Test + +class AesGcmTest { + private val key = ByteArray(32) { (it + 1).toByte() } + private val initialisationVector = ByteArray(12) { (it + 1).toByte() } + + @Test + fun shouldEncrypt() = runTest { + "hello".encodeToByteArray() + .encryptAes256Gcm(key, initialisationVector).hex shouldBe "c64bc0f15fdd3d884fa4ba8cff4339f2b5e3cf64c2" + } + + @Test + fun shouldEncryptEmptyContent() = runTest { + ByteArray(0).encryptAes256Gcm(key, initialisationVector).hex shouldBe "7737397e14746df371992992a1791250" + } + + @Test + fun shouldDecrypt() = runTest { + "c64bc0f15fdd3d884fa4ba8cff4339f2b5e3cf64c2".unhex.decryptAes256Gcm(key, initialisationVector) + .decodeToString() shouldBe "hello" + } + + @Test + fun shouldEncryptAndDecrypt() = runTest { + "hello".encodeToByteArray() + .encryptAes256Gcm(key, initialisationVector) + .decryptAes256Gcm(key, initialisationVector) + .decodeToString() shouldBe "hello" + } + + @Test + fun shouldDecryptAndHandleManipulatedData() = runTest { + shouldThrow { + "78f0b297b71f8aa3df9479b2f723868198162aa4ca".unhex.decryptAes256Gcm(key, initialisationVector) + } + } + + @Test + fun shouldDecryptAndHandleWrongInfos1() = runTest { + shouldThrow { + ByteArray(0).decryptAes256Gcm(ByteArray(0), ByteArray(12)) + } + } + + @Test + fun shouldDecryptAndHandleWrongInfos2() = runTest { + shouldThrow { + Random.Default.nextBytes(ByteArray(1)) + .decryptAes256Gcm(ByteArray(0), Random.Default.nextBytes(ByteArray(12))) + } + } + + @Test + fun shouldDecryptAndHandleWrongInfos3() = runTest { + shouldThrow { + Random.Default.nextBytes(ByteArray(1)).decryptAes256Gcm( + Random.Default.nextBytes(ByteArray(12)), + ByteArray(12) + ) + } + } +} diff --git a/trixnity-crypto-core/src/jsMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt b/trixnity-crypto-core/src/jsMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt index a2e8980ce..3ab82dd5d 100644 --- a/trixnity-crypto-core/src/jsMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt +++ b/trixnity-crypto-core/src/jsMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt @@ -1,6 +1,7 @@ package net.folivo.trixnity.crypto.core import createCipheriv +import createDecipheriv import crypto import io.ktor.util.* import kotlinx.coroutines.await @@ -79,7 +80,7 @@ actual fun ByteArrayFlow.decryptAes256Ctr( flow { try { val decipher = - createCipheriv("aes-256-ctr", key.toInt8Array(), initialisationVector.toInt8Array()) + createDecipheriv("aes-256-ctr", key.toInt8Array(), initialisationVector.toInt8Array()) filterNotEmpty().collect { input -> emit(decipher.update(input.toInt8Array()).toByteArray()) } diff --git a/trixnity-crypto-core/src/jsMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt b/trixnity-crypto-core/src/jsMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt new file mode 100644 index 000000000..d0e01895f --- /dev/null +++ b/trixnity-crypto-core/src/jsMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt @@ -0,0 +1,82 @@ +package net.folivo.trixnity.crypto.core + +import createCipheriv +import createDecipheriv +import crypto +import io.ktor.util.* +import kotlinx.coroutines.await +import kotlin.js.json + +actual suspend fun ByteArray.encryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray { + require(initialisationVector.size == 12) { "initialization vector must have size 12" } + return if (PlatformUtils.IS_BROWSER) { + val crypto = crypto.subtle + val aesKey = crypto.importKey( + "raw", + key.toInt8Array().buffer, + json("name" to "AES-GCM"), + false, + arrayOf("encrypt", "decrypt"), + ).await() + crypto.encrypt( + json( + "name" to "AES-GCM", + "iv" to initialisationVector.toInt8Array().buffer, + "tagLength" to 128, + ), + aesKey, + this.toInt8Array().buffer, + ).await().toByteArray() + } else { + val cipher = + createCipheriv("aes-256-gcm", key.toInt8Array(), initialisationVector.toInt8Array()) + cipher.update(this.toInt8Array()).toByteArray() + + cipher.final().toByteArray() + + cipher.getAuthTag().toByteArray() + } +} + +actual suspend fun ByteArray.decryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray { + require(initialisationVector.size == 12) { "initialization vector must have size 12" } + return if (PlatformUtils.IS_BROWSER) { + try { + val crypto = crypto.subtle + val aesKey = crypto.importKey( + "raw", + key.toInt8Array().buffer, + json("name" to "AES-GCM"), + false, + arrayOf("encrypt", "decrypt"), + ).await() + crypto.decrypt( + json( + "name" to "AES-GCM", + "iv" to initialisationVector.toInt8Array().buffer, + "tagLength" to 128, + ), + aesKey, + this.toInt8Array().buffer, + ).await().toByteArray() + } catch (exception: Throwable) { + throw AesDecryptionException(exception) + } + } else { + try { + val data = this.dropLast(16).toByteArray() + val authTag = this.takeLast(16).toByteArray() + val decipher = + createDecipheriv("aes-256-gcm", key.toInt8Array(), initialisationVector.toInt8Array()) + decipher.setAuthTag(authTag.toInt8Array()) + decipher.update(data.toInt8Array()).toByteArray() + + decipher.final().toByteArray() + } catch (exception: Throwable) { + throw AesDecryptionException(exception) + } + } +} \ No newline at end of file diff --git a/trixnity-crypto-core/src/jsMain/kotlin/nodeCrypto.kt b/trixnity-crypto-core/src/jsMain/kotlin/nodeCrypto.kt index f95f65c42..a9f83d0c8 100644 --- a/trixnity-crypto-core/src/jsMain/kotlin/nodeCrypto.kt +++ b/trixnity-crypto-core/src/jsMain/kotlin/nodeCrypto.kt @@ -39,11 +39,13 @@ external fun createHash( external interface Cipher { fun update(data: Int8Array): ArrayBuffer fun final(): ArrayBuffer + fun getAuthTag(): ArrayBuffer } external interface Decipher { fun update(data: Int8Array): ArrayBuffer fun final(): ArrayBuffer + fun setAuthTag(tag: Int8Array) } external interface HMAC { diff --git a/trixnity-crypto-core/src/jvmMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt b/trixnity-crypto-core/src/jvmMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt new file mode 100644 index 000000000..f382840fe --- /dev/null +++ b/trixnity-crypto-core/src/jvmMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt @@ -0,0 +1,36 @@ +package net.folivo.trixnity.crypto.core + +import java.security.Key +import javax.crypto.Cipher +import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.SecretKeySpec + +actual suspend fun ByteArray.encryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray { + require(initialisationVector.size == 12) { "initialization vector must have size 12" } + val cipher = Cipher.getInstance("AES/GCM/NoPadding") + val keySpec: Key = SecretKeySpec(key, "AES") + val gcmSpec = GCMParameterSpec(128, initialisationVector) + + cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec) + return cipher.doFinal(this) +} + +actual suspend fun ByteArray.decryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray { + require(initialisationVector.size == 12) { "initialization vector must have size 12" } + val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding") + try { + val keySpec: Key = SecretKeySpec(key, "AES") + val gcmSpec = GCMParameterSpec(128, initialisationVector) + + cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec) + return cipher.doFinal(this) + } catch (exception: Exception) { + throw AesDecryptionException(exception) + } +} \ No newline at end of file diff --git a/trixnity-crypto-core/src/opensslMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt b/trixnity-crypto-core/src/opensslMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt index d0a146660..84b2c8258 100644 --- a/trixnity-crypto-core/src/opensslMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt +++ b/trixnity-crypto-core/src/opensslMain/kotlin/net/folivo/trixnity/crypto/core/aesCtr.kt @@ -76,8 +76,8 @@ actual fun ByteArrayFlow.decryptAes256Ctr( val context = EVP_CIPHER_CTX_new() val cipher = EVP_aes_256_ctr() try { - check(key.isNotEmpty()) { "key must not be empty" } - check(initialisationVector.isNotEmpty()) { "initialisationVector must not be empty" } + require(key.isNotEmpty()) { "key must not be empty" } + require(initialisationVector.isNotEmpty()) { "initialisationVector must not be empty" } key.asUByteArray().usePinned { pinnedKey -> initialisationVector.asUByteArray().usePinned { pinnedInitialisationVector -> checkError( diff --git a/trixnity-crypto-core/src/opensslMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt b/trixnity-crypto-core/src/opensslMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt new file mode 100644 index 000000000..a76ffcff8 --- /dev/null +++ b/trixnity-crypto-core/src/opensslMain/kotlin/net/folivo/trixnity/crypto/core/aesGcm.kt @@ -0,0 +1,153 @@ +package net.folivo.trixnity.crypto.core + +import checkError +import kotlinx.cinterop.* +import org.openssl.* + +actual suspend fun ByteArray.encryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray { + require(initialisationVector.size == 12) { "initialization vector must have size 12" } + val context = EVP_CIPHER_CTX_new() + val cipher = EVP_aes_256_gcm() + try { + key.asUByteArray().usePinned { pinnedKey -> + initialisationVector.asUByteArray().usePinned { pinnedInitialisationVector -> + checkError( + EVP_EncryptInit_ex2( + ctx = context, + cipher = cipher, + key = pinnedKey.addressOf(0), + iv = pinnedInitialisationVector.addressOf(0), + params = null + ) + ) + } + } + val input = this + val blockSize = checkError(EVP_CIPHER_CTX_get_block_size(context)) + return (if (input.isNotEmpty()) input.asUByteArray().usePinned { pinnedInput -> + memScoped { + val output = ByteArray(input.size + blockSize) + val outputLength = alloc() + output.asUByteArray().usePinned { pinnedOutput -> + checkError( + EVP_EncryptUpdate( + ctx = context, + out = pinnedOutput.addressOf(0), + outl = outputLength.ptr, + `in` = pinnedInput.addressOf(0), + inl = input.size + ) + ) + } + output.wrapSizeTo(outputLength.value) + } + } else ByteArray(0)) + memScoped { + val output = ByteArray(blockSize) + val outputLength = alloc() + output.asUByteArray().usePinned { pinnedOutput -> + checkError( + EVP_EncryptFinal_ex( + ctx = context, + out = pinnedOutput.addressOf(0), + outl = outputLength.ptr, + ) + ) + } + output.wrapSizeTo(outputLength.value) + } + memScoped { + val output = ByteArray(16) + output.asUByteArray().usePinned { pinnedOutput -> + checkError( + EVP_CIPHER_CTX_ctrl( + ctx = context, + type = EVP_CTRL_GCM_GET_TAG, + arg = 16, + ptr = pinnedOutput.addressOf(0), + ) + ) + } + output + } + } finally { + EVP_CIPHER_CTX_free(context) + EVP_CIPHER_free(cipher) + } +} + +actual suspend fun ByteArray.decryptAes256Gcm( + key: ByteArray, + initialisationVector: ByteArray +): ByteArray { + require(initialisationVector.size == 12) { "initialization vector must have size 12" } + val context = EVP_CIPHER_CTX_new() + val cipher = EVP_aes_256_gcm() + try { + require(key.isNotEmpty()) { "key must not be empty" } + require(initialisationVector.isNotEmpty()) { "initialisationVector must not be empty" } + key.asUByteArray().usePinned { pinnedKey -> + initialisationVector.asUByteArray().usePinned { pinnedInitialisationVector -> + checkError( + EVP_DecryptInit_ex2( + ctx = context, + cipher = cipher, + key = pinnedKey.addressOf(0), + iv = pinnedInitialisationVector.addressOf(0), + params = null + ) + ) + } + } + val input = this.dropLast(16).toByteArray() + val authTag = this.takeLast(16).toByteArray() + authTag.asUByteArray().usePinned { pinnedAuthTag -> + checkError( + EVP_CIPHER_CTX_ctrl( + ctx = context, + type = EVP_CTRL_GCM_SET_TAG, + arg = 16, + ptr = pinnedAuthTag.addressOf(0), + ) + ) + } + val blockSize = checkError(EVP_CIPHER_CTX_get_block_size(context)) + return input.asUByteArray().usePinned { pinnedInput -> + memScoped { + val output = ByteArray(input.size + blockSize) + val outputLength = alloc() + output.asUByteArray().usePinned { pinnedOutput -> + checkError( + EVP_DecryptUpdate( + ctx = context, + out = pinnedOutput.addressOf(0), + outl = outputLength.ptr, + `in` = pinnedInput.addressOf(0), + inl = input.size + ) + ) + } + output.wrapSizeTo(outputLength.value) + } + } + memScoped { + val output = ByteArray(blockSize) + val outputLength = alloc() + output.asUByteArray().usePinned { pinnedOutput -> + checkError( + EVP_DecryptFinal_ex( + ctx = context, + outm = pinnedOutput.addressOf(0), + outl = outputLength.ptr + ) + ) + } + output.wrapSizeTo(outputLength.value) + } + } catch (exception: Exception) { + throw AesDecryptionException(exception) + } finally { + EVP_CIPHER_CTX_free(context) + EVP_CIPHER_free(cipher) + } +} \ No newline at end of file -- GitLab