added numeric nonce calculator

This commit is contained in:
Sergey Chernov 2024-06-14 11:46:26 +07:00
parent 77911867a1
commit 9027bb0e88
9 changed files with 94 additions and 18 deletions

View File

@ -23,7 +23,7 @@ kotlin {
// linuxArm64()
// macosX64()
// macosArm64()
// macosArm64() ?: break
// iosX64()
// iosArm64()
// iosSimulatorArm64()

View File

@ -105,7 +105,7 @@ object Asymmetric {
return KeyPair(SecretKey(p.secretKey, pk), pk)
}
private fun randomNonce(): UByteArray = randomBytes(crypto_box_NONCEBYTES)
private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES)
/**
* The public key used as the recipient for [Message] (see [SecretKey.encrypt], etc.). It also

View File

@ -4,4 +4,4 @@ interface NonceBased {
val nonceBytesLength: Int
}
fun NonceBased.randomNonce(): UByteArray = randomBytes(nonceBytesLength)
fun NonceBased.randomNonce(): UByteArray = randomUBytes(nonceBytesLength)

View File

@ -0,0 +1,60 @@
package net.sergeych.crypto2
/**
* Numeric nonce is most often used to compress nonce data up to several bytes long counter,
* or even recalculate counter on both sides synchronously. This class combines (XORs) a number
* with a byte-array nonce specified on creation. This "extends" number to the desired size.
*/
class NumericNonce(val base: UByteArray) {
/**
* Combines [base] by XORing its byte representation with a [base]
* @throws NonceOutOfBoundsException if the argument is too big to be kept in the nonce. For example,
* nonce of 4 bytes [base] can't be combined with a number > 2^32, etc.
*/
fun withULong(numericNonce: ULong): UByteArray {
val result = base.copyOf()
var x = numericNonce
var i = 0
while (x > 0u) {
result[i] = result[i] xor (x and 0xFFu).toUByte()
x = x shr 8
if( i++ >= base.size)
throw NonceOutOfBoundsException("this nonce has only ${base.size} bytes length and can hold nonce $numericNonce")
}
return result
}
/**
* Combines [base] by XORing its byte representation with a [base], see [withULong]
*/
@Suppress("unused")
fun withLong(intNonce: Long): UByteArray {
require( intNonce >= 0 )
return withULong(intNonce.toULong())
}
/**
* Combines [base] by XORing its byte representation with a [base], see [withULong]
*/
fun withInt(intNonce: Int): UByteArray {
require( intNonce >= 0 )
return withULong(intNonce.toULong())
}
/**
* Combines [base] by XORing its byte representation with a [base], see [withULong]
*/
@Suppress("unused")
fun withUInt(intNonce: UInt): UByteArray {
return withULong(intNonce.toULong())
}
companion object {
/**
* Create a random numeric nonce with a specified size.
*/
fun random(size: Int) = NumericNonce(randomUBytes(size))
}
}

View File

@ -136,7 +136,7 @@ class Seal(
expiresAt: Instant? = null,
nonDeterministic: Boolean = false
): Seal {
val nonce = if( nonDeterministic ) randomBytes(32) else null
val nonce = if( nonDeterministic ) randomUBytes(32) else null
val data = BipackEncoder.encode(SealedData(message, nonce, createdAt, expiresAt)).toUByteArray()
return Seal(key.publicKey, key.sign(data), nonce, createdAt, expiresAt)
}

View File

@ -47,7 +47,7 @@ class SymmetricKey(
val fill = randomFill?.let {
require(it.start >= 0)
randomBytes(randomInt(it))
randomUBytes(randomInt(it))
}
val filled = BipackEncoder.encode(WithFill(plainData, fill))
return SecretBox.easy(filled.toUByteArray(), nonce, keyBytes)

View File

@ -0,0 +1,11 @@
package net.sergeych.crypto2
/**
* Any exception initiated by crypto2 library has this base class
*/
open class Crypto2Exception(text: String,cause: Throwable?=null): RuntimeException(text, cause)
class DecryptionFailedException(text: String="can't decrypt: wrong key or tampered message",
cause: Throwable?=null): Crypto2Exception(text,cause)
class NonceOutOfBoundsException(text: String): Crypto2Exception(text)

View File

@ -8,10 +8,6 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.channels.ReceiveChannel
import net.sergeych.bintools.DataSource
class DecryptionFailedException(text: String="can't decrypt: wrong key or tampered message",
cause: Throwable?=null) : RuntimeException(text, cause) {
}
suspend fun readVarUnsigned(input: ReceiveChannel<UByte>): UInt {
var result = 0u
@ -38,9 +34,9 @@ fun encodeVarUnsigned(value: UInt): UByteArray {
}
fun randomBytes(n: Int): UByteArray = if (n > 0) LibsodiumRandom.buf(n) else ubyteArrayOf()
fun randomUBytes(n: Int): UByteArray = if (n > 0) LibsodiumRandom.buf(n) else ubyteArrayOf()
fun randomBytes(n: UInt): UByteArray = if (n > 0u) LibsodiumRandom.buf(n.toInt()) else ubyteArrayOf()
fun randomUBytes(n: UInt): UByteArray = if (n > 0u) LibsodiumRandom.buf(n.toInt()) else ubyteArrayOf()
/**
* Uniform random in `0 ..< max` range

View File

@ -1,11 +1,6 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.crypto2.createContrail
import net.sergeych.crypto2.initCrypto
import net.sergeych.crypto2.isValidContrail
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import net.sergeych.crypto2.*
import kotlin.test.*
class ToolsTest {
@Test
@ -17,4 +12,18 @@ class ToolsTest {
c[2] = 11u
assertFalse { isValidContrail(c) }
}
@Test
fun numericNonceTest() = runTest{
initCrypto()
val nn = NumericNonce.random(32)
val counter = 1031
val x1 = nn.withInt(0)
val x2 = nn.withInt(counter)
println(x1.toDump())
println(x2.toDump())
val t = (x1[0] xor x2[0]).toInt() + ((x1[1] xor x2[1]).toInt() shl 8)
assertEquals(counter, t)
assertContentEquals(x1.drop(2), x2.drop(2))
}
}