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"
|
group = "net.sergeych"
|
||||||
version = "0.9.0"
|
version = "0.9.1-SNAPSHOT"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|||||||
@ -46,13 +46,54 @@ class SymmetricKey(
|
|||||||
data class WithNonce(
|
data class WithNonce(
|
||||||
val cipherData: UByteArray,
|
val cipherData: UByteArray,
|
||||||
val nonce: 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 {
|
override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray {
|
||||||
require(nonce.size == nonceLength)
|
require(nonce.size == nonceLength)
|
||||||
return SecretBox.easy(WithFill.encode(plainData, randomFill), nonce, keyBytes)
|
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 nonceBytesLength: Int = nonceLength
|
||||||
|
|
||||||
override val id by lazy {
|
override val id by lazy {
|
||||||
@ -64,6 +105,22 @@ class SymmetricKey(
|
|||||||
WithFill.decode(SecretBox.openEasy(cipherData, nonce, keyBytes))
|
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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is SymmetricKey) return false
|
if (other !is SymmetricKey) return false
|
||||||
@ -85,4 +142,4 @@ class SymmetricKey(
|
|||||||
val keyLength = crypto_secretbox_KEYBYTES
|
val keyLength = crypto_secretbox_KEYBYTES
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
import net.sergeych.bipack.BipackEncoder
|
import net.sergeych.bipack.BipackEncoder
|
||||||
@ -118,6 +117,44 @@ class KeysTest {
|
|||||||
assertContentEquals(src, k1.decrypt(k1.encrypt(src, 7..117)))
|
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
|
@Test
|
||||||
fun keyExchangeTest() = runTest {
|
fun keyExchangeTest() = runTest {
|
||||||
initCrypto()
|
initCrypto()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user