From aeaf19ad746fd25e15ea7508a918d2fe8b5917cf Mon Sep 17 00:00:00 2001 From: Luca Tremamunno Date: Wed, 1 Oct 2025 13:34:28 +0200 Subject: [PATCH] Allow to provide iv and data separately for decryption --- .../com/icure/kryptom/crypto/IosAesService.kt | 215 ++++++++++++++---- .../com/icure/kryptom/crypto/AesService.kt | 5 + .../com/icure/kryptom/crypto/JsAesService.kt | 33 ++- .../kryptom/crypto/external/XCryptoService.kt | 7 + .../com/icure/kryptom/crypto/JvmAesService.kt | 17 +- .../icure/kryptom/crypto/OpensslAesService.kt | 76 +++++-- .../icure/kryptom/crypto/BCryptAesService.kt | 98 +++++--- 7 files changed, 340 insertions(+), 111 deletions(-) diff --git a/lib/src/appleMain/kotlin/com/icure/kryptom/crypto/IosAesService.kt b/lib/src/appleMain/kotlin/com/icure/kryptom/crypto/IosAesService.kt index 6a11808..f30de6f 100644 --- a/lib/src/appleMain/kotlin/com/icure/kryptom/crypto/IosAesService.kt +++ b/lib/src/appleMain/kotlin/com/icure/kryptom/crypto/IosAesService.kt @@ -3,6 +3,8 @@ package com.icure.kryptom.crypto import com.icure.kryptom.crypto.AesService.Companion.IV_BYTE_LENGTH import com.icure.kryptom.crypto.AesService.Companion.aesEncryptedSizeFor import com.icure.kryptom.utils.PlatformMethodException +import kotlinx.cinterop.MemScope +import kotlinx.cinterop.Pinned import kotlinx.cinterop.ULongVar import kotlinx.cinterop.addressOf import kotlinx.cinterop.alloc @@ -10,12 +12,18 @@ import kotlinx.cinterop.memScoped import kotlinx.cinterop.ptr import kotlinx.cinterop.usePinned import kotlinx.cinterop.value -import platform.CoreCrypto.CCCrypt +import platform.CoreCrypto.CCCryptorCreateWithMode +import platform.CoreCrypto.CCCryptorFinal +import platform.CoreCrypto.CCCryptorRefVar +import platform.CoreCrypto.CCCryptorRelease +import platform.CoreCrypto.CCCryptorUpdate import platform.CoreCrypto.kCCAlgorithmAES import platform.CoreCrypto.kCCDecrypt import platform.CoreCrypto.kCCEncrypt import platform.CoreCrypto.kCCKeySizeAES128 import platform.CoreCrypto.kCCKeySizeAES256 +import platform.CoreCrypto.kCCModeCBC +import platform.CoreCrypto.kCCModeCTR import platform.CoreCrypto.kCCOptionPKCS7Padding import platform.CoreCrypto.kCCSuccess @@ -36,83 +44,188 @@ object IosAesService : AesService { ) override suspend fun encrypt(data: ByteArray, key: AesKey<*>, iv: ByteArray?): ByteArray { - require (key.algorithm == AesAlgorithm.CbcWithPkcs7Padding) { - "Unsupported aes algorithm: ${key.algorithm}" - } if (iv != null) require(iv.size == IV_BYTE_LENGTH) { "Initialization vector must be $IV_BYTE_LENGTH bytes long (got ${iv.size})." } val generatedIv = iv ?: IosStrongRandom.randomBytes(IV_BYTE_LENGTH) val outBytes = generatedIv.copyOf(IV_BYTE_LENGTH + aesEncryptedSizeFor(data.size)) - memScoped { - val dataOutMoved = alloc() - val operationResult = data.usePinned { pinnedData -> + return memScoped { + data.usePinned { pinnedData -> generatedIv.usePinned { pinnedIv -> outBytes.usePinned { pinnedOut -> - // TODO if in future we need to support anything other than CBC we will need to use `CCCryptorCreateWithMode` key.rawKey.usePinned { pinnedKey -> - CCCrypt( + val cryptor = alloc() + CCCryptorCreateWithMode( kCCEncrypt, + getCcMode(key.algorithm), kCCAlgorithmAES, kCCOptionPKCS7Padding, + pinnedIv.addressOf(0), pinnedKey.addressOf(0), validateAndGetKeySize(key), - pinnedIv.addressOf(0), - pinnedData.addressOf(0), - data.size.toULong(), - pinnedOut.addressOf(IV_BYTE_LENGTH), - (outBytes.size - IV_BYTE_LENGTH).toULong(), - dataOutMoved.ptr - ) + null, + 0.toULong(), + 0, + 0.toUInt(), + cryptor.ptr + ).also { + if (it != kCCSuccess) throw PlatformMethodException( + "CCCryptorCreateWithMode (encrypt) failed with error code: $it", + it + ) + } + try { + val dataOutMoved = alloc() + var totalMoved = 0 + CCCryptorUpdate( + cryptor.value, + pinnedData.addressOf(0), + data.size.toULong(), + pinnedOut.addressOf(IV_BYTE_LENGTH), + (outBytes.size - IV_BYTE_LENGTH).toULong(), + dataOutMoved.ptr + ).also { + if (it != kCCSuccess) throw PlatformMethodException( + "CCCryptorUpdate (encrypt) failed with error code: $it", + it + ) + } + totalMoved += dataOutMoved.value.toInt() + CCCryptorFinal( + cryptor.value, + pinnedOut.addressOf(IV_BYTE_LENGTH + totalMoved), + (outBytes.size - IV_BYTE_LENGTH - totalMoved).toULong(), + dataOutMoved.ptr + ).also { + if (it != kCCSuccess) throw PlatformMethodException( + "CCCryptorFinal (encrypt) failed with error code: $it", + it + ) + } + totalMoved += dataOutMoved.value.toInt() + if (totalMoved != (outBytes.size - IV_BYTE_LENGTH)) throw PlatformMethodException( + "Expected ${outBytes.size - IV_BYTE_LENGTH} encrypted bytes but got $totalMoved", + null + ) + outBytes + } finally { + CCCryptorRelease(cryptor.value) + } } } } } - if (operationResult != kCCSuccess) throw PlatformMethodException( - "Encryption failed with error code $operationResult", - operationResult - ) - if (dataOutMoved.value != (outBytes.size - IV_BYTE_LENGTH).toULong()) throw PlatformMethodException( - "Expected ${outBytes.size - IV_BYTE_LENGTH} encrypted bytes but got ${dataOutMoved.value}", - null - ) } - return outBytes } override suspend fun decrypt(ivAndEncryptedData: ByteArray, key: AesKey<*>): ByteArray { - require (key.algorithm == AesAlgorithm.CbcWithPkcs7Padding) { - "Unsupported aes algorithm: ${key.algorithm}" + return memScoped { + ivAndEncryptedData.usePinned { pinnedIvAndEncryptedData -> + key.rawKey.usePinned { pinnedKey -> + doDecrypt( + algorithm = key.algorithm, + pinnedEncryptedData = pinnedIvAndEncryptedData, + encryptedDataOffset = IV_BYTE_LENGTH, + encryptedDataSize = ivAndEncryptedData.size - IV_BYTE_LENGTH, + pinnedIv = pinnedIvAndEncryptedData, + pinnedKey = pinnedKey, + keySize = validateAndGetKeySize(key) + ) + } + } } - val outBytes = ByteArray(ivAndEncryptedData.size - IV_BYTE_LENGTH) + } + + override suspend fun decrypt( + encryptedData: ByteArray, + key: AesKey<*>, + iv: ByteArray + ): ByteArray { + require(iv.size == IV_BYTE_LENGTH) { "IV must be 16 bytes long" } return memScoped { - val dataOutMoved = alloc() - val operationResult = outBytes.usePinned { pinnedOutBytes -> - ivAndEncryptedData.usePinned { pinnedIvAndEncryptedData -> + encryptedData.usePinned { pinnedEncryptedData -> + iv.usePinned { pinnedIv -> key.rawKey.usePinned { pinnedKey -> - // TODO if in future we need to support anything other than CBC we will need to use `CCCryptorCreateWithMode` - CCCrypt( - kCCDecrypt, - kCCAlgorithmAES, - kCCOptionPKCS7Padding, - pinnedKey.addressOf(0), - validateAndGetKeySize(key), - pinnedIvAndEncryptedData.addressOf(0), - pinnedIvAndEncryptedData.addressOf(IV_BYTE_LENGTH), - (ivAndEncryptedData.size - IV_BYTE_LENGTH).toULong(), - pinnedOutBytes.addressOf(0), - outBytes.size.toULong(), - dataOutMoved.ptr + doDecrypt( + algorithm = key.algorithm, + pinnedEncryptedData = pinnedEncryptedData, + encryptedDataOffset = 0, + encryptedDataSize = encryptedData.size, + pinnedIv = pinnedIv, + pinnedKey = pinnedKey, + keySize = validateAndGetKeySize(key) ) } } } + } + } + + private fun MemScope.doDecrypt( + algorithm: AesAlgorithm, + pinnedEncryptedData: Pinned, + encryptedDataOffset: Int, + encryptedDataSize: Int, + pinnedIv: Pinned, + pinnedKey: Pinned, + keySize: ULong, + ): ByteArray { + val cryptor = alloc() + CCCryptorCreateWithMode( + kCCDecrypt, + getCcMode(algorithm), + kCCAlgorithmAES, + kCCOptionPKCS7Padding, + pinnedIv.addressOf(0), + pinnedKey.addressOf(0), + keySize, + null, + 0.toULong(), + 0, + 0.toUInt(), + cryptor.ptr + ).also { // Refer to Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/CommonCrypto/CommonCryptoError.h - if (operationResult != kCCSuccess) throw PlatformMethodException( - "Decryption failed with error code: $operationResult", - operationResult + if (it != kCCSuccess) throw PlatformMethodException( + "CCCryptorCreateWithMode (decrypt) failed with error code: $it", + it ) - outBytes.copyOf(dataOutMoved.value.toInt()) + } + return try { + val outBuffer = ByteArray(encryptedDataSize) + val dataOutMoved = alloc() + var totalMoved = 0 + outBuffer.usePinned { pinnedOut -> + CCCryptorUpdate( + cryptor.value, + pinnedEncryptedData.addressOf(encryptedDataOffset), + encryptedDataSize.toULong(), + pinnedOut.addressOf(0), + encryptedDataSize.toULong(), + dataOutMoved.ptr + ).also { + if (it != kCCSuccess) throw PlatformMethodException( + "CCCryptorUpdate (decrypt) failed with error code: $it", + it + ) + } + totalMoved += dataOutMoved.value.toInt() + CCCryptorFinal( + cryptor.value, + pinnedOut.addressOf(totalMoved), + (outBuffer.size - totalMoved).toULong(), + dataOutMoved.ptr + ).also { + if (it != kCCSuccess) throw PlatformMethodException( + "CCCryptorFinal (decrypt) failed with error code: $it", + it + ) + } + totalMoved += dataOutMoved.value.toInt() + } + outBuffer.copyOf(totalMoved) + } finally { + CCCryptorRelease(cryptor.value) } } @@ -122,4 +235,10 @@ object IosAesService : AesService { AesService.KeySize.Aes256.byteSize -> kCCKeySizeAES256.toULong() else -> throw IllegalArgumentException("Invalid size for key: ${key.rawKey.size}") } + + private fun getCcMode( + algorithm: AesAlgorithm + ): UInt = when (algorithm) { + AesAlgorithm.CbcWithPkcs7Padding -> kCCModeCBC + } } \ No newline at end of file diff --git a/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/AesService.kt b/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/AesService.kt index 2684443..fa7a15a 100644 --- a/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/AesService.kt +++ b/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/AesService.kt @@ -93,4 +93,9 @@ interface AesService { * @throws IllegalArgumentException if the key is invalid (for example if the size is not good for an aes key). */ suspend fun decrypt(ivAndEncryptedData: ByteArray, key: AesKey<*>): ByteArray + + /** + * Version of decrypt where the encrypted data and iv are provided separately + */ + suspend fun decrypt(encryptedData: ByteArray, key: AesKey<*>, iv: ByteArray): ByteArray } diff --git a/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/JsAesService.kt b/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/JsAesService.kt index 624f054..1afbdda 100644 --- a/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/JsAesService.kt +++ b/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/JsAesService.kt @@ -1,5 +1,6 @@ package com.icure.kryptom.crypto +import com.icure.kryptom.crypto.AesService.Companion.IV_BYTE_LENGTH import com.icure.kryptom.js.jsCrypto import com.icure.kryptom.js.toArrayBuffer import com.icure.kryptom.js.toByteArray @@ -63,13 +64,31 @@ object JsAesService : AesService { override suspend fun decrypt(ivAndEncryptedData: ByteArray, key: AesKey<*>): ByteArray { val buffer = ivAndEncryptedData.toArrayBuffer() - val iv = buffer.slice(0, AesService.IV_BYTE_LENGTH) - val data = buffer.slice(AesService.IV_BYTE_LENGTH) - return jsCrypto.subtle.decrypt(encryptionParam(key.algorithm, iv), key.cryptoKey, data).await().toByteArray() + val iv = buffer.slice(0, IV_BYTE_LENGTH) + val encryptedData = buffer.slice(IV_BYTE_LENGTH) + return doDecrypt(encryptedData, key, iv) } - private fun encryptionParam(algorithm: AesAlgorithm, iv: ArrayBuffer) = json( - "name" to subtleAlgorithmNameFor(algorithm), - "iv" to iv - ) + override suspend fun decrypt( + encryptedData: ByteArray, + key: AesKey<*>, + iv: ByteArray + ): ByteArray { + require(iv.size == IV_BYTE_LENGTH) { "IV must be 16 bytes long" } + return doDecrypt(encryptedData.toArrayBuffer(), key, iv.toArrayBuffer()) + } + + private suspend fun doDecrypt( + encryptedData: ArrayBuffer, + key: AesKey<*>, + iv: ArrayBuffer + ): ByteArray { + return jsCrypto.subtle.decrypt(encryptionParam(key.algorithm, iv), key.cryptoKey, encryptedData).await().toByteArray() + } + + private fun encryptionParam(algorithm: AesAlgorithm, iv: ArrayBuffer) = + json( + "name" to subtleAlgorithmNameFor(algorithm), + "iv" to iv + ) } diff --git a/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/external/XCryptoService.kt b/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/external/XCryptoService.kt index 0f81d0b..e00eb4e 100644 --- a/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/external/XCryptoService.kt +++ b/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/external/XCryptoService.kt @@ -83,6 +83,13 @@ private class AesServiceAdapter( override suspend fun decrypt(ivAndEncryptedData: ByteArray, key: AesKey<*>): ByteArray = service.decrypt(ivAndEncryptedData, key.toExternal()).await() + + override suspend fun decrypt( + encryptedData: ByteArray, + key: AesKey<*>, + iv: ByteArray + ): ByteArray = + service.decrypt(iv + encryptedData, key.toExternal()).await() } private class XAesServiceAdapter( diff --git a/lib/src/jvmAndAndroidMain/kotlin/com/icure/kryptom/crypto/JvmAesService.kt b/lib/src/jvmAndAndroidMain/kotlin/com/icure/kryptom/crypto/JvmAesService.kt index e20f97c..4472df8 100644 --- a/lib/src/jvmAndAndroidMain/kotlin/com/icure/kryptom/crypto/JvmAesService.kt +++ b/lib/src/jvmAndAndroidMain/kotlin/com/icure/kryptom/crypto/JvmAesService.kt @@ -35,7 +35,20 @@ object JvmAesService : AesService { override suspend fun decrypt(ivAndEncryptedData: ByteArray, key: AesKey<*>): ByteArray { val iv = ivAndEncryptedData.sliceArray(0 until IV_BYTE_LENGTH) - val data = ivAndEncryptedData.sliceArray(IV_BYTE_LENGTH until ivAndEncryptedData.size) - return getCipher(key.algorithm).apply { init(Cipher.DECRYPT_MODE, key.key, IvParameterSpec(iv)) }.doFinal(data) + val encryptedData = ivAndEncryptedData.sliceArray(IV_BYTE_LENGTH until ivAndEncryptedData.size) + return decrypt( + encryptedData, + key, + iv + ) + } + + override suspend fun decrypt( + encryptedData: ByteArray, + key: AesKey<*>, + iv: ByteArray + ): ByteArray { + require(iv.size == IV_BYTE_LENGTH) { "IV must be 16 bytes long" } + return getCipher(key.algorithm).apply { init(Cipher.DECRYPT_MODE, key.key, IvParameterSpec(iv)) }.doFinal(encryptedData) } } \ No newline at end of file diff --git a/lib/src/linuxMain/kotlin/com/icure/kryptom/crypto/OpensslAesService.kt b/lib/src/linuxMain/kotlin/com/icure/kryptom/crypto/OpensslAesService.kt index d99e12a..55cc63e 100644 --- a/lib/src/linuxMain/kotlin/com/icure/kryptom/crypto/OpensslAesService.kt +++ b/lib/src/linuxMain/kotlin/com/icure/kryptom/crypto/OpensslAesService.kt @@ -6,11 +6,13 @@ import com.icure.kryptom.utils.OpensslErrorHandling.ensureEvpSuccess import com.icure.kryptom.utils.PlatformMethodException import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.Pinned import kotlinx.cinterop.addressOf import kotlinx.cinterop.alloc import kotlinx.cinterop.memScoped import kotlinx.cinterop.pin import kotlinx.cinterop.ptr +import kotlinx.cinterop.usePinned import kotlinx.cinterop.value import libcrypto.EVP_CIPHER import libcrypto.EVP_CIPHER_CTX_free @@ -22,7 +24,9 @@ import libcrypto.EVP_EncryptFinal_ex import libcrypto.EVP_EncryptInit_ex import libcrypto.EVP_EncryptUpdate import libcrypto.EVP_aes_128_cbc +import libcrypto.EVP_aes_128_ctr import libcrypto.EVP_aes_256_cbc +import libcrypto.EVP_aes_256_ctr @OptIn(ExperimentalForeignApi::class) object OpensslAesService : AesService { @@ -42,9 +46,6 @@ object OpensslAesService : AesService { ) override suspend fun encrypt(data: ByteArray, key: AesKey<*>, iv: ByteArray?): ByteArray { - require (key.algorithm == AesAlgorithm.CbcWithPkcs7Padding) { - "Unsupported aes algorithm: ${key.algorithm}" - } if (iv != null) require(iv.size == IV_BYTE_LENGTH) { "Initialization vector must be $IV_BYTE_LENGTH bytes long (got ${iv.size})." } @@ -95,34 +96,67 @@ object OpensslAesService : AesService { return output } + override suspend fun decrypt( + encryptedData: ByteArray, + key: AesKey<*>, + iv: ByteArray + ): ByteArray { + require(iv.size == IV_BYTE_LENGTH) { "IV must be 16 bytes long" } + return encryptedData.asUByteArray().usePinned { pinnedEncryptedData -> + iv.asUByteArray().usePinned { pinnedIv -> + doDecrypt( + key, + pinnedEncryptedData, + 0, + encryptedData.size, + pinnedIv + ) + } + } + } + override suspend fun decrypt(ivAndEncryptedData: ByteArray, key: AesKey<*>): ByteArray { - require (key.algorithm == AesAlgorithm.CbcWithPkcs7Padding) { - "Unsupported aes algorithm: ${key.algorithm}" + return ivAndEncryptedData.asUByteArray().usePinned { pinnedData -> + doDecrypt( + key, + pinnedData, + IV_BYTE_LENGTH, + ivAndEncryptedData.size - IV_BYTE_LENGTH, + pinnedData + ) } + } + + private fun doDecrypt( + key: AesKey<*>, + pinnedEncryptedData: Pinned, + encryptedDataOffset: Int, + encryptedDataSize: Int, + pinnedIv: Pinned, + ): ByteArray { val cipher = validateKeyAndGetCipher(key) + // This is a tiny bit too big, but better leave a bit of margin. + val output = ByteArray(encryptedDataSize) val ctx = EVP_CIPHER_CTX_new() ?: throw PlatformMethodException("Could not initialise context", null) - val pinnedInput = ivAndEncryptedData.asUByteArray().pin() val rawKey = key.rawKey.asUByteArray().pin() - // This is a tiny bit too big, but better leave a bit of margin. Removing the IV length should still be fine - val output = ByteArray(ivAndEncryptedData.size) val pinnedOutput = output.asUByteArray().pin() return memScoped { - val writtenBytes = alloc(0) - var totalSize = 0 try { + val writtenBytes = alloc(0) + var totalSize = 0 EVP_DecryptInit_ex( ctx, cipher, null, rawKey.addressOf(0), - pinnedInput.addressOf(0), + pinnedIv.addressOf(0), ).ensureEvpSuccess("EVP_DecryptInit_ex") EVP_DecryptUpdate( ctx, pinnedOutput.addressOf(0), writtenBytes.ptr, - pinnedInput.addressOf(IV_BYTE_LENGTH), - ivAndEncryptedData.size - IV_BYTE_LENGTH + pinnedEncryptedData.addressOf(encryptedDataOffset), + encryptedDataSize ).ensureEvpSuccess("EVP_DecryptUpdate") totalSize += writtenBytes.value check(totalSize < output.size) { "Output buffer was not big enough" } @@ -135,7 +169,6 @@ object OpensslAesService : AesService { check(totalSize < output.size) { "Output buffer was not big enough" } output.sliceArray(0 until totalSize) } finally { - pinnedInput.unpin() rawKey.unpin() pinnedOutput.unpin() EVP_CIPHER_CTX_free(ctx) @@ -143,11 +176,16 @@ object OpensslAesService : AesService { } } - private fun validateKeyAndGetCipher(key: AesKey<*>): CPointer = checkNotNull( - when (key.rawKey.size) { - AesService.KeySize.Aes128.byteSize -> EVP_aes_128_cbc() - AesService.KeySize.Aes256.byteSize -> EVP_aes_256_cbc() + private fun validateKeyAndGetCipher(key: AesKey<*>): CPointer { + val is256 = when (key.rawKey.size) { + AesService.KeySize.Aes128.byteSize -> false + AesService.KeySize.Aes256.byteSize -> true else -> throw IllegalArgumentException("Invalid size for key: ${key.rawKey.size}") } - ) { "EVP cipher is null" } + return checkNotNull( + when (key.algorithm) { + AesAlgorithm.CbcWithPkcs7Padding -> if (is256) EVP_aes_256_cbc() else EVP_aes_128_cbc() + } + ) { "EVP cipher is null" } + } } diff --git a/lib/src/mingwMain/kotlin/com/icure/kryptom/crypto/BCryptAesService.kt b/lib/src/mingwMain/kotlin/com/icure/kryptom/crypto/BCryptAesService.kt index 0275163..409518c 100644 --- a/lib/src/mingwMain/kotlin/com/icure/kryptom/crypto/BCryptAesService.kt +++ b/lib/src/mingwMain/kotlin/com/icure/kryptom/crypto/BCryptAesService.kt @@ -1,5 +1,6 @@ package com.icure.kryptom.crypto +import com.icure.kryptom.crypto.AesService.Companion.IV_BYTE_LENGTH import com.icure.kryptom.utils.PlatformMethodException import com.icure.kryptom.utils.ensureSuccess import kotlinx.cinterop.* @@ -124,48 +125,75 @@ object BCryptAesService : AesService { } } - override suspend fun decrypt(ivAndEncryptedData: ByteArray, key: AesKey<*>): ByteArray = withAlgorithmHandle(key.algorithm) { algorithmHandle -> + override suspend fun decrypt(ivAndEncryptedData: ByteArray, key: AesKey<*>): ByteArray = + ivAndEncryptedData.usePinned { + doDecrypt( + key = key, + pinnedEncryptedData = it, + encryptedDataOffset = IV_BYTE_LENGTH, + encryptedDataSize = ivAndEncryptedData.size - IV_BYTE_LENGTH, + pinnedIv = it + ) + } + + override suspend fun decrypt( + encryptedData: ByteArray, + key: AesKey<*>, + iv: ByteArray + ): ByteArray { + require(iv.size == IV_BYTE_LENGTH) { "IV must be 16 bytes long" } + return encryptedData.usePinned { pinnedEncryptedData -> + iv.usePinned { pinnedIv -> + doDecrypt( + key = key, + pinnedEncryptedData = pinnedEncryptedData, + encryptedDataOffset = 0, + encryptedDataSize = encryptedData.size, + pinnedIv = pinnedIv + ) + } + } + } + + private fun doDecrypt( + key: AesKey<*>, + pinnedEncryptedData: Pinned, + encryptedDataOffset: Int, + encryptedDataSize: Int, + pinnedIv: Pinned, + ): ByteArray = withAlgorithmHandle(key.algorithm) { algorithmHandle -> withKeyHandle(key, algorithmHandle) { keyHandle -> - val iv = ivAndEncryptedData.sliceArray(0 until AesService.IV_BYTE_LENGTH) - val pinnedIv = iv.pin() - val data = ivAndEncryptedData.sliceArray(AesService.IV_BYTE_LENGTH until ivAndEncryptedData.size) - val pinnedData = data.pin() - try { - memScoped { - val decryptedTextBufferSize = alloc() + memScoped { + val decryptedTextBufferSize = alloc() + BCryptDecrypt( + keyHandle, + pinnedEncryptedData.addressOf(encryptedDataOffset).reinterpret(), + encryptedDataSize.toUInt(), + null, + pinnedIv.addressOf(0).reinterpret(), + IV_BYTE_LENGTH.toUInt(), + null, + 0.toUInt(), + decryptedTextBufferSize.ptr, + 1.toUInt() // BCRYPT_BLOCK_PADDING + ).ensureSuccess("BCryptDecrypt get decrypted size") + val actualDecryptedSize = alloc() + val decryptedData = ByteArray(decryptedTextBufferSize.value.toInt()) + decryptedData.usePinned { pinnedDecrypted -> BCryptDecrypt( keyHandle, - pinnedData.addressOf(0).reinterpret(), - data.size.toUInt(), + pinnedEncryptedData.addressOf(encryptedDataOffset).reinterpret(), + encryptedDataSize.toUInt(), null, pinnedIv.addressOf(0).reinterpret(), - iv.size.toUInt(), - null, - 0.toUInt(), - decryptedTextBufferSize.ptr, + IV_BYTE_LENGTH.toUInt(), + pinnedDecrypted.addressOf(0).reinterpret(), + decryptedData.size.toUInt(), + actualDecryptedSize.ptr, 1.toUInt() // BCRYPT_BLOCK_PADDING - ).ensureSuccess("BCryptDecrypt get decrypted size") - val actualDecryptedSize = alloc() - val decryptedData = ByteArray(decryptedTextBufferSize.value.toInt()) - decryptedData.usePinned { pinnedDecrypted -> - BCryptDecrypt( - keyHandle, - pinnedData.addressOf(0).reinterpret(), - data.size.toUInt(), - null, - pinnedIv.addressOf(0).reinterpret(), - iv.size.toUInt(), - pinnedDecrypted.addressOf(0).reinterpret(), - decryptedData.size.toUInt(), - actualDecryptedSize.ptr, - 1.toUInt() // BCRYPT_BLOCK_PADDING - ).ensureSuccess("BCryptDecrypt do decrypt") - } - decryptedData.sliceArray(0 until actualDecryptedSize.value.toInt()) + ).ensureSuccess("BCryptDecrypt do decrypt") } - } finally { - pinnedIv.unpin() - pinnedData.unpin() + decryptedData.sliceArray(0 until actualDecryptedSize.value.toInt()) } } }