PBKD with tests. Some improvements in keys listing

This commit is contained in:
Sergey Chernov 2024-06-25 09:48:50 +07:00
parent 46a8b5bc06
commit 9efa763b45
10 changed files with 94 additions and 51 deletions

View File

@ -125,7 +125,9 @@ object Asymmetric {
@SerialName("encp")
class PublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingKey {
override val magick: KeysMagickNumber = KeysMagickNumber.defaultAssymmetric
override val magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric
@Transient
override val label: String = "pub"
/**
* Create an anonymous message that could be decrypted only with the [SecretKey] that corresponds this.
@ -193,6 +195,8 @@ object Asymmetric {
val _cachedPublicKey: PublicKey? = null,
) : DecryptingKey, UniversalKey() {
@Transient
override val label: String = "sec"
/**
* Decrypt with authentication checks the message which must have [Message.senderPublicKey] set.
* Use [decryptWithSenderKey] otherwise. Note that the authenticated encryption is always use, even if
@ -238,7 +242,7 @@ object Asymmetric {
return message.decrypt(this)
}
override val magick: KeysMagickNumber = KeysMagickNumber.defaultAssymmetric
override val magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric
override val id: KeyId by lazy { publicKey.id }
override val nonceBytesLength: Int

View File

@ -17,7 +17,7 @@ class BinaryId private constructor (
class IncomparableException(text: String) : IllegalArgumentException(text)
@Transient
val magick: Int = run {
val magic: Int = run {
if (id.size < 4) throw InvalidException("BinaryId is too short")
val crc = id.last()
val rest = id.dropLast(1).toUByteArray()
@ -32,7 +32,7 @@ class BinaryId private constructor (
override fun toString(): String = id.encodeToBase64Url()
override fun compareTo(other: BinaryId): Int {
if (other.magick != magick) throw IncomparableException("Mask mismatch (my=$magick their=${other.magick})")
if (other.magic != magic) throw IncomparableException("Mask mismatch (my=$magic their=${other.magic})")
val id1 = other.id
if (id1.size != id.size) throw IncomparableException("different sizes")
for ((a, b) in innerData.zip(other.innerData)) {
@ -66,11 +66,11 @@ class BinaryId private constructor (
BinaryId(str.decodeBase64Url().toUByteArray())
fun createFromBytes(magick: Int, bytes: ByteArray): BinaryId = createFromUBytes(magick, bytes.toUByteArray())
fun createFromBytes(magic: Int, bytes: ByteArray): BinaryId = createFromUBytes(magic, bytes.toUByteArray())
fun createFromUBytes(magick: Int, bytes: UByteArray): BinaryId {
fun createFromUBytes(magic: Int, bytes: UByteArray): BinaryId {
val crc = CRC8()
val mn = magick.toUByte()
val mn = magic.toUByte()
crc.update(bytes)
crc.update(mn)
return BinaryId(UByteArray(bytes.size + 2).also {
@ -82,13 +82,13 @@ class BinaryId private constructor (
}
@Suppress("unused")
fun createRandom(magickNumber: Int, size: Int=16) =
createFromBytes(magickNumber, Random.Default.nextBytes(size-2))
fun createRandom(magicNumber: Int, size: Int=16) =
createFromBytes(magicNumber, Random.Default.nextBytes(size-2))
/**
* Encode a string as UTF and create a binaryId from its bytes and provided magick.
* Encode a string as UTF and create a binaryId from its bytes and provided magic.
*/
fun createFromString(magickNumber: Int, text: String): BinaryId =
createFromUBytes(magickNumber, text.encodeToUByteArray())
fun createFromString(magicNumber: Int, text: String): BinaryId =
createFromUBytes(magicNumber, text.encodeToUByteArray())
}
}

View File

@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
/**
* Key Identity. Can be used to find matching keys for decryption or verifying, etc. The identity
* may contain [KDF] parameters if the corresponding key could be derived from a password.
* Note that [kdf] part is not respected in [equals].
* Note that [kdp] part is not respected in [equals].
*
* Important. `KeyId` of matching keys are the same, so you can use it to identify
* and find matching keys in the [UniversalRing], etc. For example:
@ -16,16 +16,23 @@ import kotlinx.serialization.Serializable
* - [SigningSecretKey] and corresponding [VerifyingKey] have the same `KeyId`. Use it to pick a proper key for
* signing from a ring with [UniversalRing.findKey]
*
* See [PBKD.Params.deriveKey] for deriving keys from id.
*
* @param id actual id used in equality test amd hash code generation. `Id` of the matching keys is the same.
* @param kdp optional key derivation parameters. Does not affect equality. Allow deriving the key from proper
* password, see above.
*/
@Serializable
data class KeyId(val id: BinaryId, val kdf: PBKD.Params?=null ) {
data class KeyId(val id: BinaryId, val kdp: PBKD.Params?=null ) {
/**
* Binary array representation of the [id], not including the [kdf]. Used in [SafeKeyExchange]
* Binary array representation of the [id], not including the [kdp]. Used in [SafeKeyExchange]
* and other key exchanges to generate session tokens, etc.
*/
val binaryTag: UByteArray by lazy { id.id }
val keymagic: KeysmagicNumber by lazy { KeysmagicNumber.entries[id.magic]}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is KeyId) return false
@ -38,6 +45,6 @@ data class KeyId(val id: BinaryId, val kdf: PBKD.Params?=null ) {
override fun toString() = id.toString()
constructor(magickNumber: KeysMagickNumber, keyData: UByteArray, kdp: PBKD.Params?=null)
: this(BinaryId.createFromUBytes(magickNumber.number, blake2b3l(keyData)), kdp)
constructor(magicNumber: KeysmagicNumber, keyData: UByteArray, kdp: PBKD.Params?=null)
: this(BinaryId.createFromUBytes(magicNumber.ordinal, blake2b3l(keyData)), kdp)
}

View File

@ -1,18 +1,11 @@
package net.sergeych.crypto2
enum class KeysMagickNumber(val number: Int) {
defaultAssymmetric(0),
defaultSymmetric(1),
defaultSession(2),
defaultSigning(3),
defaultVerifying(4),
enum class KeysmagicNumber(val label: String) {
Unknown( "???"),
defaultAssymmetric( "asm"),
defaultSymmetric( "sym"),
defaultSession( "ssn"),
defaultVerifying( "ver"),
;
companion object {
val forNumber = entries.map { it.number to it }.toMap()
@Suppress("unused")
fun findFor(binaryId: BinaryId): KeysMagickNumber? = forNumber[binaryId.magick]
}
}

View File

@ -47,8 +47,8 @@ object PBKD {
fun deriveKey(password: String, keyId: KeyId? = null): SymmetricKey {
// 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")
if (it.id.magic != KeysmagicNumber.defaultSymmetric.ordinal) {
throw NotSupportedException("deriving key of type ${it.id.magic} is not implemented")
}
}
// this will not be ok when we support other key types:
@ -56,11 +56,7 @@ object PBKD {
throw NotSupportedException("can't create key from $length byte(s), required ${SymmetricKey.keyLength}")
// derive
val key = SymmetricKey(
deriveUBytes(password)
.sliceArray(startOffset..<startOffset + SymmetricKey.keyLength),
this
)
val key = SymmetricKey(deriveUBytes(password),this)
// check if we can
if (keyId != null && keyId != key.id)
@ -121,6 +117,15 @@ object PBKD {
entry.data!!
}
}
/**
* Generated cache bytes are cached and present in memory probably longer than needed.
* After this call, the password derivation cache is clear and the new deriving will
* be performed.
*/
fun clearCache() {
op.invoke { cache.clear() }
}
}

View File

@ -22,8 +22,6 @@ class SigningPublicKey(override val keyBytes: UByteArray) : UniversalKey(), Veri
false
}
override fun toString(): String = "Pub:${super.toString()}"
@Transient
override val magick: KeysMagickNumber = KeysMagickNumber.defaultVerifying
override val magic: KeysmagicNumber = KeysmagicNumber.defaultVerifying
}

View File

@ -28,12 +28,10 @@ class SigningSecretKey(
override fun seal(message: UByteArray, expiresAt: Instant?): Seal =
Seal.create(this, message, now(), expiresAt)
override fun toString(): String = "Sct:${super.toString()}"
override val id: KeyId = verifyingKey.id
@Transient
override val magick = KeysMagickNumber.defaultSigning
override val label: String
get() = "sig"
companion object {

View File

@ -23,10 +23,10 @@ import kotlinx.serialization.Transient
class SymmetricKey(
override val keyBytes: UByteArray,
@Transient
val pbkdfParams: PBKD.Params?=null
private val pbkdfParams: PBKD.Params?=null
) : EncryptingKey, DecryptingKey, UniversalKey() {
override val magick: KeysMagickNumber = KeysMagickNumber.defaultSymmetric
override val magic: KeysmagicNumber = KeysmagicNumber.defaultSymmetric
/**
* @suppress
@ -46,7 +46,7 @@ class SymmetricKey(
override val nonceBytesLength: Int = nonceLength
override val id by lazy {
KeyId(KeysMagickNumber.defaultSymmetric,blake2b3l(keyBytes), pbkdfParams)
KeyId(KeysmagicNumber.defaultSymmetric,blake2b3l(keyBytes), pbkdfParams)
}
override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =

View File

@ -1,14 +1,22 @@
package net.sergeych.crypto2
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
sealed class UniversalKey: KeyInstance {
abstract val keyBytes: UByteArray
abstract val magick: KeysMagickNumber
override val id by lazy { KeyId(magick, keyBytes) }
@Transient
open val magic: KeysmagicNumber = KeysmagicNumber.Unknown
override val id by lazy { KeyId(magic, keyBytes) }
// Important: id can be overridden, so we use it, not magic:
open val label by lazy { id.keymagic.label }
override fun equals(other: Any?): Boolean {
return other is UniversalKey && other.keyBytes contentEquals keyBytes
@ -18,7 +26,10 @@ sealed class UniversalKey: KeyInstance {
return keyBytes.contentHashCode()
}
override fun toString(): String = keyBytes.encodeToBase64Url()
override fun toString(): String {
val pwdSign = if (id.kdp != null) '*' else '#'
return "🗝${label}$pwdSign$id"
}
companion object {
fun newSecretKey() = Asymmetric.newSecretKey()

View File

@ -1,9 +1,36 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.crypto2.PBKD
import net.sergeych.crypto2.initCrypto
import kotlin.test.Test
import kotlin.test.assertEquals
class PBKDTest {
@Test
fun testDerive() {
fun testDerive() = runTest {
initCrypto()
val (k1, k2, k3) = PBKD.deriveMultipleKeys("foobar", 3)
PBKD.clearCache()
println(k1)
println(k2)
println(k3)
// println("------------------- Secret ket encryption!")
// val s = UniversalKey.newSecretKey()
// println(s)
// println(s.publicKey)
// println("------------------- public key signing")
// val i = UniversalKey.newSigningKey()
// println(i)
// println(i.verifyingKey)
// println("------------------- symmetric")
// println(UniversalKey.newSymmetricKey())
for( k in listOf(k1,k2,k3)) {
val i = k.id
val kx = i.kdp!!.deriveKey("foobar")
assertEquals(k, kx)
assertEquals(i, kx.id)
assertEquals(i.kdp, kx.id.kdp)
}
}
}