introducing PBKD support in keys amd generation server
This commit is contained in:
parent
435604379e
commit
2cd4eaccab
@ -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())
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class KeyDerivationParams()
|
@ -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)
|
||||
}
|
109
src/commonMain/kotlin/net/sergeych/crypto2/PBKD.kt
Normal file
109
src/commonMain/kotlin/net/sergeych/crypto2/PBKD.kt
Normal 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!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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")
|
@ -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")
|
||||
|
9
src/commonTest/kotlin/PBKDTest.kt
Normal file
9
src/commonTest/kotlin/PBKDTest.kt
Normal file
@ -0,0 +1,9 @@
|
||||
import kotlin.test.Test
|
||||
|
||||
class PBKDTest {
|
||||
|
||||
@Test
|
||||
fun testDerive() {
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user