introducing PBKD support in keys amd generation server

This commit is contained in:
Sergey Chernov 2024-06-23 12:23:45 +07:00
parent 435604379e
commit 2cd4eaccab
11 changed files with 171 additions and 70 deletions

View File

@ -1,5 +1,6 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.sergeych.bintools.CRC
@ -8,7 +9,7 @@ import net.sergeych.mp_tools.decodeBase64Url
import kotlin.random.Random
@Serializable
class BinaryId(
class BinaryId private constructor (
val id: UByteArray,
) : Comparable<BinaryId> {
@ -57,8 +58,11 @@ class BinaryId(
companion object {
/**
* Restore a string representation of existing BinaryId.
*/
@Suppress("unused")
fun fromString(str: String): BinaryId =
fun restoreFromString(str: String): BinaryId =
BinaryId(str.decodeBase64Url().toUByteArray())
@ -81,5 +85,10 @@ class BinaryId(
fun createRandom(magickNumber: Int, size: Int=16) =
createFromBytes(magickNumber, Random.Default.nextBytes(size-2))
/**
* Encode a string as UTF and create a binaryId from its bytes and provided magick.
*/
fun createFromString(magickNumber: Int, text: String): BinaryId =
createFromUBytes(magickNumber, text.encodeToUByteArray())
}
}

View File

@ -1,6 +0,0 @@
package net.sergeych.crypto2
import kotlinx.serialization.Serializable
@Serializable
class KeyDerivationParams()

View File

@ -18,7 +18,7 @@ import kotlinx.serialization.Serializable
*
*/
@Serializable
data class KeyId(val id: BinaryId, val kdf: KeyDerivationParams?=null ) {
data class KeyId(val id: BinaryId, val kdf: PBKD.Params?=null ) {
/**
* Binary array representation of the [id], not including the [kdf]. Used in [SafeKeyExchange]
@ -38,6 +38,6 @@ data class KeyId(val id: BinaryId, val kdf: KeyDerivationParams?=null ) {
override fun toString() = id.toString()
constructor(magickNumber: KeysMagickNumber, keyData: UByteArray, kdp: KeyDerivationParams?=null)
constructor(magickNumber: KeysMagickNumber, keyData: UByteArray, kdp: PBKD.Params?=null)
: this(BinaryId.createFromUBytes(magickNumber.number, blake2b3l(keyData)), kdp)
}

View File

@ -0,0 +1,109 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.sergeych.bipack.BipackEncoder
import net.sergeych.bipack.Unsigned
import net.sergeych.synctools.ProtectedOp
import net.sergeych.synctools.invoke
object PBKD {
@Serializable
class Params(
val kdf: KDF,
@Unsigned
val startOffset: Int,
@Transient
private var precalculatedKey: SymmetricKey?=null
) {
/**
* Derive a key from the password in the idempotent manner.
* If the [keyId] is provided, it will be used to determine key type (otherwise default asymmetric)
* and to check the result, and the exception will be thrown.
*
* Note that it is reasonably effective to use with linked keys restoring (keys based on the same password).
* It can also be called repeatedly.
*
* @param password to derive from
* @param keyId optional key id, to get a key type from and check the result
* @throws IncorrectPasswordException if [keyId] is provided and does not match the derivation result
*/
fun derive(password: String, keyId: KeyId? = null): SymmetricKey {
// could be already calculated
precalculatedKey?.let { return it }
// check key id sanity
keyId?.let {
if (it.id.magick != KeysMagickNumber.defaultSymmetric.number) {
throw NotSupportedException("deriving key of type ${it.id.magick} is not implemented")
}
}
// this will not be ok when we support other key types:
val keySize = SymmetricKey.keyLength
// derive
val bytes = generate(kdf, password)
val key = SymmetricKey(bytes.sliceArray(startOffset..<startOffset + keySize), this)
// check if we can checking
if( keyId != null && keyId != key.id )
throw IncorrectPasswordException()
// save for later
precalculatedKey = key
return key
}
}
/**
* Create [PBKD.Params] to derive several keys from the single password
*/
fun createMutlipleParams(complexity: KDF.Complexity,keysCount: Int): List<Params> {
TODO()
}
private val op = ProtectedOp()
private class Entry(val kdf: KDF) {
val op = ProtectedOp()
var data: UByteArray? = null
}
/**
* Binary id is a binary hash-capable key, we use it. byte array can't be a hash key in kotlin: its value
* does not depend on the byte content.
*/
fun key(kdf: KDF, password: String): BinaryId =
BinaryId.createFromBytes(
0,
BipackEncoder.encode(kdf) + blake2b3l(password.encodeToUByteArray()).toByteArray()
)
private val cache = HashMap<BinaryId, Entry>()
/**
* Get a plain bytes by the KDF parameters specified. It implements short-term caching of
* KDF results this could be called repeatedly.
*
* When called from different threads, allows deriving in parallel, but this could deplete memory
* or CPU cores.
*/
fun generate(kdf: KDF, password: String): UByteArray {
val entry = op.invoke {
cache.getOrPut(key(kdf, password)) { Entry(kdf) }
}
return entry.op {
if (entry.data == null) {
entry.data = entry.kdf.derive(password)
}
entry.data!!
}
}
}

View File

@ -1,19 +0,0 @@
package net.sergeych.crypto2
import kotlinx.serialization.Serializable
import net.sergeych.bipack.Unsigned
@Serializable
class PBKDParams(
val kdf: KDF,
@Unsigned
val offset: UInt,
@Unsigned
val length: UInt
) {
// val key by lazy {
// SymmetricKey(derivedBytes)
// }
// val derivedBytes by lazy {kdf.derivedBytes}
}

View File

@ -1,8 +1,10 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.secretbox.SecretBox
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_KEYBYTES
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
/**
* Symmetric key implements authenticated encrypting with random nonce and optional fill.
@ -18,6 +20,8 @@ import kotlinx.serialization.Serializable
@Serializable
class SymmetricKey(
val keyBytes: UByteArray,
@Transient
val pbkdfParams: PBKD.Params?=null
) : EncryptingKey, DecryptingKey {
/**
@ -31,13 +35,15 @@ class SymmetricKey(
)
override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray {
require(nonce.size == nonceByteLength)
require(nonce.size == nonceLength)
return SecretBox.easy(WithFill.encode(plainData, randomFill), nonce, keyBytes)
}
override val nonceBytesLength: Int = nonceByteLength
override val nonceBytesLength: Int = nonceLength
override val id by lazy { KeyId(KeysMagickNumber.defaultSymmetric,blake2b3l(keyBytes)) }
override val id by lazy {
KeyId(KeysMagickNumber.defaultSymmetric,blake2b3l(keyBytes), pbkdfParams)
}
override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =
protectDecryption {
@ -62,7 +68,8 @@ class SymmetricKey(
*/
fun new() = SymmetricKey(SecretBox.keygen())
val nonceByteLength = crypto_secretbox_NONCEBYTES
val nonceLength = crypto_secretbox_NONCEBYTES
val keyLength = crypto_secretbox_KEYBYTES
}
}

View File

@ -12,4 +12,6 @@ class NonceOutOfBoundsException(text: String): Crypto2Exception(text)
@Suppress("unused")
class NotSupportedException(text: String="operation is not supported for this object")
: Crypto2Exception(text, null)
: Crypto2Exception(text, null)
class IncorrectPasswordException(): Crypto2Exception("Incorrect password")

View File

@ -4,7 +4,6 @@ import com.ionspin.kotlin.crypto.pwhash.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.sergeych.bipack.Unsigned
import net.sergeych.synctools.ProtectedOp
@Serializable
sealed class KDF {
@ -15,41 +14,7 @@ sealed class KDF {
Sensitive,
}
fun derivedCached(password: String): UByteArray =
deriveCached(this, password)
abstract protected fun derive(password: String): UByteArray
companion object {
private val op = ProtectedOp()
private val cache = HashMap<KDF, Entry>()
private class Entry(val kdf: KDF) {
val op = ProtectedOp()
var data: UByteArray? = null
// fun generateCached(): UByteArray = op {
// if (data == null)
// data = kdf.derive()
// data!!
// }
}
// fun key(kdf: KDF,password: String): KDF {
// return BipackEncoder.encode()
// }
fun deriveCached(kdf: KDF,password: String): UByteArray {
TODO()
// val entry = op {
// cache.getOrPut(kdf) { Entry(kdf) }
// }
// return entry.generateCached()
}
}
abstract fun derive(password: String): UByteArray
@Serializable
@SerialName("argon")

View File

@ -0,0 +1,9 @@
import kotlin.test.Test
class PBKDTest {
@Test
fun testDerive() {
}
}

View File

@ -70,6 +70,7 @@ class RingTest {
println(r1.findKey<EncryptingKey>(sk.id))
println(sk)
assertTrue { sk.publicKey.toUniversalKey() == r1.findKey<EncryptingKey>(sk.id) }
assertTrue { sk.publicKey.toUniversalKey() == r1.keyByTag<EncryptingKey>("foo") }
}
@Test

View File

@ -1,4 +1,6 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.bintools.encodeToHex
import net.sergeych.crypto2.BinaryId
import net.sergeych.crypto2.Contrail
import net.sergeych.crypto2.NumericNonce
import net.sergeych.crypto2.initCrypto
@ -31,4 +33,26 @@ class ToolsTest {
assertEquals(counter, t)
assertContentEquals(x1.drop(2), x2.drop(2))
}
@Test
fun testUBytesAsHashKeys() {
val k1 = BinaryId.createFromString(0,"Just the business")
val k2 = BinaryId.createFromString(0,"Just the business")
val l = mutableListOf<UByte>()
for( b in k1.id) l += b
val k3 = BinaryId.createFromUBytes(0, k1.id.dropLast(2).toUByteArray() )
assertEquals(k1, k2)
println(k1.id.encodeToHex())
println(k3.id.encodeToHex())
assertEquals(k1, k3)
val m = mutableMapOf(k1 to "foo")
m[k2] ="bar"
assertEquals("bar", m[k2])
assertEquals("bar", m[k1])
assertEquals("bar", m[k3])
}
}