Skip to content

Commit ec0871b

Browse files
committed
Support encryption/decryption streaming in Openssl3 provider
1 parent 38ca350 commit ec0871b

18 files changed

+495
-738
lines changed

cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3AesCbc.kt

Lines changed: 7 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.providers.openssl3.internal.*
1010
import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*
11-
import dev.whyoleg.cryptography.random.*
12-
import kotlinx.cinterop.*
1311
import kotlin.experimental.*
1412
import kotlin.native.ref.*
1513

@@ -24,138 +22,15 @@ internal object Openssl3AesCbc : AES.CBC, Openssl3Aes<AES.CBC.Key>() {
2422
else -> error("Unsupported key size")
2523
}
2624

27-
override fun cipher(padding: Boolean): AES.IvCipher = AesCbcCipher(algorithm, key, padding)
28-
}
29-
}
30-
31-
private const val ivSizeBytes = 16 //bytes for CBC
32-
33-
private class AesCbcCipher(
34-
algorithm: String,
35-
private val key: ByteArray,
36-
private val padding: Boolean,
37-
) : AES.IvCipher {
38-
39-
private val cipher = EVP_CIPHER_fetch(null, algorithm, null)
40-
41-
@OptIn(ExperimentalNativeApi::class)
42-
private val cleaner = createCleaner(cipher, ::EVP_CIPHER_free)
43-
44-
override fun encryptBlocking(plaintext: ByteArray): ByteArray {
45-
val iv = CryptographyRandom.nextBytes(ivSizeBytes)
46-
return iv + encryptWithIvBlocking(iv, plaintext)
47-
}
48-
49-
override fun encryptWithIvBlocking(iv: ByteArray, plaintext: ByteArray): ByteArray = memScoped {
50-
require(iv.size == ivSizeBytes) { "IV size is wrong" }
51-
52-
val context = EVP_CIPHER_CTX_new()
53-
try {
54-
checkError(
55-
EVP_EncryptInit_ex2(
56-
ctx = context,
57-
cipher = cipher,
58-
key = key.refToU(0),
59-
iv = iv.refToU(0),
60-
params = null
61-
)
62-
)
63-
checkError(EVP_CIPHER_CTX_set_padding(context, if (padding) 1 else 0))
64-
65-
val blockSize = checkError(EVP_CIPHER_CTX_get_block_size(context))
66-
val ciphertextOutput = ByteArray(blockSize + plaintext.size)
67-
68-
val outl = alloc<IntVar>()
69-
70-
checkError(
71-
EVP_EncryptUpdate(
72-
ctx = context,
73-
out = ciphertextOutput.refToU(0),
74-
outl = outl.ptr,
75-
`in` = plaintext.safeRefToU(0),
76-
inl = plaintext.size
77-
)
78-
)
79-
80-
val producedByUpdate = outl.value
81-
82-
checkError(
83-
EVP_EncryptFinal_ex(
84-
ctx = context,
85-
out = ciphertextOutput.refToU(outl.value),
86-
outl = outl.ptr
87-
)
88-
)
89-
90-
val produced = producedByUpdate + outl.value
91-
ciphertextOutput.ensureSizeExactly(produced)
92-
} finally {
93-
EVP_CIPHER_CTX_free(context)
94-
}
95-
}
96-
97-
override fun decryptBlocking(ciphertext: ByteArray): ByteArray {
98-
require(ciphertext.size >= ivSizeBytes) { "Ciphertext is too short" }
99-
100-
return decrypt(
101-
iv = ciphertext,
102-
ciphertext = ciphertext,
103-
ciphertextStartIndex = ivSizeBytes,
104-
)
105-
}
106-
107-
override fun decryptWithIvBlocking(iv: ByteArray, ciphertext: ByteArray): ByteArray {
108-
require(iv.size == ivSizeBytes) { "IV size is wrong" }
109-
110-
return decrypt(
111-
iv = iv,
112-
ciphertext = ciphertext,
113-
ciphertextStartIndex = 0,
114-
)
115-
}
116-
117-
private fun decrypt(iv: ByteArray, ciphertext: ByteArray, ciphertextStartIndex: Int): ByteArray = memScoped {
118-
val context = EVP_CIPHER_CTX_new()
119-
try {
120-
checkError(
121-
EVP_DecryptInit_ex2(
122-
ctx = context,
123-
cipher = cipher,
124-
key = key.refToU(0),
125-
iv = iv.refToU(0),
126-
params = null
127-
)
128-
)
129-
checkError(EVP_CIPHER_CTX_set_padding(context, if (padding) 1 else 0))
130-
131-
val blockSize = checkError(EVP_CIPHER_CTX_get_block_size(context))
132-
val plaintextOutput = ByteArray(blockSize + ciphertext.size - ciphertextStartIndex)
133-
134-
val outl = alloc<IntVar>()
135-
136-
checkError(
137-
EVP_DecryptUpdate(
138-
ctx = context,
139-
out = plaintextOutput.refToU(0),
140-
outl = outl.ptr,
141-
`in` = ciphertext.safeRefToU(ciphertextStartIndex),
142-
inl = ciphertext.size - ciphertextStartIndex
143-
)
144-
)
25+
private val cipher = EVP_CIPHER_fetch(null, algorithm, null)
14526

146-
val producedByUpdate = outl.value
27+
@OptIn(ExperimentalNativeApi::class)
28+
private val cleaner = createCleaner(cipher, ::EVP_CIPHER_free)
14729

148-
checkError(
149-
EVP_DecryptFinal_ex(
150-
ctx = context,
151-
outm = plaintextOutput.refToU(producedByUpdate),
152-
outl = outl.ptr
153-
)
154-
)
155-
val produced = producedByUpdate + outl.value
156-
plaintextOutput.ensureSizeExactly(produced)
157-
} finally {
158-
EVP_CIPHER_CTX_free(context)
30+
override fun cipher(padding: Boolean): AES.IvCipher {
31+
return Openssl3AesIvCipher(cipher, key, ivSize = 16) { context ->
32+
checkError(EVP_CIPHER_CTX_set_padding(context, if (padding) 1 else 0))
33+
}
15934
}
16035
}
16136
}

cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3AesCtr.kt

Lines changed: 5 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ package dev.whyoleg.cryptography.providers.openssl3.algorithms
66

77
import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
9-
import dev.whyoleg.cryptography.providers.openssl3.internal.*
109
import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*
11-
import dev.whyoleg.cryptography.random.*
12-
import kotlinx.cinterop.*
1310
import kotlin.experimental.*
1411
import kotlin.native.ref.*
1512

@@ -24,135 +21,13 @@ internal object Openssl3AesCtr : AES.CTR, Openssl3Aes<AES.CTR.Key>() {
2421
else -> error("Unsupported key size")
2522
}
2623

27-
override fun cipher(): AES.IvCipher = AesCtrCipher(algorithm, key)
28-
}
29-
}
30-
31-
private const val ivSizeBytes = 16 //bytes for CTR
32-
33-
private class AesCtrCipher(
34-
algorithm: String,
35-
private val key: ByteArray,
36-
) : AES.IvCipher {
37-
38-
private val cipher = EVP_CIPHER_fetch(null, algorithm, null)
39-
40-
@OptIn(ExperimentalNativeApi::class)
41-
private val cleaner = createCleaner(cipher, ::EVP_CIPHER_free)
42-
43-
override fun encryptBlocking(plaintext: ByteArray): ByteArray {
44-
val iv = CryptographyRandom.nextBytes(ivSizeBytes)
45-
return iv + encryptWithIvBlocking(iv, plaintext)
46-
}
47-
48-
override fun encryptWithIvBlocking(iv: ByteArray, plaintext: ByteArray): ByteArray = memScoped {
49-
require(iv.size == ivSizeBytes) { "IV size is wrong" }
50-
51-
val context = EVP_CIPHER_CTX_new()
52-
try {
53-
checkError(
54-
EVP_EncryptInit_ex2(
55-
ctx = context,
56-
cipher = cipher,
57-
key = key.refToU(0),
58-
iv = iv.refToU(0),
59-
params = null
60-
)
61-
)
62-
63-
val blockSize = checkError(EVP_CIPHER_CTX_get_block_size(context))
64-
val ciphertextOutput = ByteArray(blockSize + plaintext.size)
65-
66-
val outl = alloc<IntVar>()
67-
68-
checkError(
69-
EVP_EncryptUpdate(
70-
ctx = context,
71-
out = ciphertextOutput.refToU(0),
72-
outl = outl.ptr,
73-
`in` = plaintext.safeRefToU(0),
74-
inl = plaintext.size
75-
)
76-
)
77-
78-
val producedByUpdate = outl.value
79-
80-
checkError(
81-
EVP_EncryptFinal_ex(
82-
ctx = context,
83-
out = ciphertextOutput.refToU(outl.value),
84-
outl = outl.ptr
85-
)
86-
)
87-
88-
val produced = producedByUpdate + outl.value
89-
ciphertextOutput.ensureSizeExactly(produced)
90-
} finally {
91-
EVP_CIPHER_CTX_free(context)
92-
}
93-
}
94-
95-
override fun decryptBlocking(ciphertext: ByteArray): ByteArray {
96-
require(ciphertext.size >= ivSizeBytes) { "Ciphertext is too short" }
97-
98-
return decrypt(
99-
iv = ciphertext,
100-
ciphertext = ciphertext,
101-
ciphertextStartIndex = ivSizeBytes,
102-
)
103-
}
104-
105-
override fun decryptWithIvBlocking(iv: ByteArray, ciphertext: ByteArray): ByteArray {
106-
require(iv.size == ivSizeBytes) { "IV size is wrong" }
107-
108-
return decrypt(
109-
iv = iv,
110-
ciphertext = ciphertext,
111-
ciphertextStartIndex = 0,
112-
)
113-
}
114-
115-
private fun decrypt(iv: ByteArray, ciphertext: ByteArray, ciphertextStartIndex: Int): ByteArray = memScoped {
116-
val context = EVP_CIPHER_CTX_new()
117-
try {
118-
checkError(
119-
EVP_DecryptInit_ex2(
120-
ctx = context,
121-
cipher = cipher,
122-
key = key.refToU(0),
123-
iv = iv.refToU(0),
124-
params = null
125-
)
126-
)
127-
128-
val blockSize = checkError(EVP_CIPHER_CTX_get_block_size(context))
129-
val plaintextOutput = ByteArray(blockSize + ciphertext.size - ciphertextStartIndex)
130-
131-
val outl = alloc<IntVar>()
132-
133-
checkError(
134-
EVP_DecryptUpdate(
135-
ctx = context,
136-
out = plaintextOutput.refToU(0),
137-
outl = outl.ptr,
138-
`in` = ciphertext.safeRefToU(ciphertextStartIndex),
139-
inl = ciphertext.size - ciphertextStartIndex
140-
)
141-
)
24+
private val cipher = EVP_CIPHER_fetch(null, algorithm, null)
14225

143-
val producedByUpdate = outl.value
26+
@OptIn(ExperimentalNativeApi::class)
27+
private val cleaner = createCleaner(cipher, ::EVP_CIPHER_free)
14428

145-
checkError(
146-
EVP_DecryptFinal_ex(
147-
ctx = context,
148-
outm = plaintextOutput.refToU(producedByUpdate),
149-
outl = outl.ptr
150-
)
151-
)
152-
val produced = producedByUpdate + outl.value
153-
plaintextOutput.ensureSizeExactly(produced)
154-
} finally {
155-
EVP_CIPHER_CTX_free(context)
29+
override fun cipher(): AES.IvCipher {
30+
return Openssl3AesIvCipher(cipher, key, ivSize = 16)
15631
}
15732
}
15833
}

0 commit comments

Comments
 (0)