Added support for compact encryption/decryption with caller-provided nonce in SymmetricKey and accompanying tests. Bumped version to 0.9.1-SNAPSHOT.
This commit is contained in:
parent
13dff8d760
commit
8015a4310b
@ -20,7 +20,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@ -46,13 +46,54 @@ class SymmetricKey(
|
||||
data class WithNonce(
|
||||
val cipherData: UByteArray,
|
||||
val nonce: UByteArray,
|
||||
)
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
|
||||
other as WithNonce
|
||||
|
||||
if (!cipherData.contentEquals(other.cipherData)) return false
|
||||
if (!nonce.contentEquals(other.nonce)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = cipherData.contentHashCode()
|
||||
result = 31 * result + nonce.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray {
|
||||
require(nonce.size == nonceLength)
|
||||
return SecretBox.easy(WithFill.encode(plainData, randomFill), nonce, keyBytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact authenticated encryption with a caller-provided nonce.
|
||||
*
|
||||
* This method is meant for storage formats where the nonce is derived or stored elsewhere
|
||||
* and every byte of encoded output matters, for example short encrypted file names.
|
||||
* The output is the raw SecretBox ciphertext only: [plainData] size plus 16 authentication
|
||||
* bytes. The nonce is not stored in the output and must be reproduced exactly for
|
||||
* [decryptCompactWithNonce].
|
||||
*
|
||||
* Unlike [encryptWithNonce], this method does not wrap [plainData] with [WithFill]. For
|
||||
* short payloads this usually saves only 1 byte over [encryptWithNonce], because both
|
||||
* methods already require the caller to manage the nonce separately. Use it only when this
|
||||
* small size gain is important or when the surrounding format explicitly requires raw
|
||||
* SecretBox output.
|
||||
*
|
||||
* Never reuse the same nonce with the same key for different plaintext. Reusing a
|
||||
* `(key, nonce)` pair with SecretBox breaks the security of the stream cipher.
|
||||
*/
|
||||
fun encryptCompactWithNonce(plainData: UByteArray, nonce: UByteArray): UByteArray {
|
||||
require(nonce.size == nonceLength)
|
||||
return SecretBox.easy(plainData, nonce, keyBytes)
|
||||
}
|
||||
|
||||
override val nonceBytesLength: Int = nonceLength
|
||||
|
||||
override val id by lazy {
|
||||
@ -64,6 +105,22 @@ class SymmetricKey(
|
||||
WithFill.decode(SecretBox.openEasy(cipherData, nonce, keyBytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact authenticated decryption with a caller-provided nonce.
|
||||
*
|
||||
* Decrypts data produced by [encryptCompactWithNonce]. The nonce is not read from
|
||||
* [cipherData] and must be the exact same nonce that was used for encryption. Use this
|
||||
* method only for compact formats that intentionally omit both the nonce and the [WithFill]
|
||||
* wrapper.
|
||||
*
|
||||
* @throws DecryptionFailedException if the key, nonce, or cipher data is invalid.
|
||||
*/
|
||||
fun decryptCompactWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =
|
||||
protectDecryption {
|
||||
require(nonce.size == nonceLength)
|
||||
SecretBox.openEasy(cipherData, nonce, keyBytes)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is SymmetricKey) return false
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.sergeych.bipack.BipackDecoder
|
||||
import net.sergeych.bipack.BipackEncoder
|
||||
@ -118,6 +117,44 @@ class KeysTest {
|
||||
assertContentEquals(src, k1.decrypt(k1.encrypt(src, 7..117)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun symmetricKeyCompactTest() = runTest {
|
||||
initCrypto()
|
||||
val key = SymmetricKey.new()
|
||||
val otherKey = SymmetricKey.new()
|
||||
val src = "readme.md".encodeToUByteArray()
|
||||
val nonce = key.randomNonce()
|
||||
val cipher = key.encryptCompactWithNonce(src, nonce)
|
||||
|
||||
assertEquals(src.size + 16, cipher.size)
|
||||
assertTrue(cipher.size < key.encryptWithNonce(src, nonce).size)
|
||||
assertTrue(cipher.size < key.encrypt(src).size)
|
||||
assertContentEquals(src, key.decryptCompactWithNonce(cipher, nonce))
|
||||
|
||||
assertThrows<DecryptionFailedException> {
|
||||
otherKey.decryptCompactWithNonce(cipher, nonce)
|
||||
}
|
||||
|
||||
assertThrows<DecryptionFailedException> {
|
||||
val n2 = nonce.copyOf()
|
||||
n2[4] = n2[4].inv()
|
||||
key.decryptCompactWithNonce(cipher, n2)
|
||||
}
|
||||
|
||||
assertThrows<DecryptionFailedException> {
|
||||
val c2 = cipher.copyOf()
|
||||
c2[3] = c2[3].inv()
|
||||
key.decryptCompactWithNonce(c2, nonce)
|
||||
}
|
||||
|
||||
assertThrows<IllegalArgumentException> {
|
||||
key.encryptCompactWithNonce(src, nonce.dropLast(1).toUByteArray())
|
||||
}
|
||||
assertThrows<DecryptionFailedException> {
|
||||
key.decryptCompactWithNonce(cipher, nonce.dropLast(1).toUByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyExchangeTest() = runTest {
|
||||
initCrypto()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user