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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import net.sergeych.bintools.CRC
|
import net.sergeych.bintools.CRC
|
||||||
@ -8,7 +9,7 @@ import net.sergeych.mp_tools.decodeBase64Url
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class BinaryId(
|
class BinaryId private constructor (
|
||||||
val id: UByteArray,
|
val id: UByteArray,
|
||||||
) : Comparable<BinaryId> {
|
) : Comparable<BinaryId> {
|
||||||
|
|
||||||
@ -57,8 +58,11 @@ class BinaryId(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore a string representation of existing BinaryId.
|
||||||
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun fromString(str: String): BinaryId =
|
fun restoreFromString(str: String): BinaryId =
|
||||||
BinaryId(str.decodeBase64Url().toUByteArray())
|
BinaryId(str.decodeBase64Url().toUByteArray())
|
||||||
|
|
||||||
|
|
||||||
@ -81,5 +85,10 @@ class BinaryId(
|
|||||||
fun createRandom(magickNumber: Int, size: Int=16) =
|
fun createRandom(magickNumber: Int, size: Int=16) =
|
||||||
createFromBytes(magickNumber, Random.Default.nextBytes(size-2))
|
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
|
@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]
|
* 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()
|
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)
|
: 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
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 com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Symmetric key implements authenticated encrypting with random nonce and optional fill.
|
* Symmetric key implements authenticated encrypting with random nonce and optional fill.
|
||||||
@ -18,6 +20,8 @@ import kotlinx.serialization.Serializable
|
|||||||
@Serializable
|
@Serializable
|
||||||
class SymmetricKey(
|
class SymmetricKey(
|
||||||
val keyBytes: UByteArray,
|
val keyBytes: UByteArray,
|
||||||
|
@Transient
|
||||||
|
val pbkdfParams: PBKD.Params?=null
|
||||||
) : EncryptingKey, DecryptingKey {
|
) : EncryptingKey, DecryptingKey {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,13 +35,15 @@ class SymmetricKey(
|
|||||||
)
|
)
|
||||||
|
|
||||||
override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray {
|
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)
|
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 =
|
override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =
|
||||||
protectDecryption {
|
protectDecryption {
|
||||||
@ -62,7 +68,8 @@ class SymmetricKey(
|
|||||||
*/
|
*/
|
||||||
fun new() = SymmetricKey(SecretBox.keygen())
|
fun new() = SymmetricKey(SecretBox.keygen())
|
||||||
|
|
||||||
val nonceByteLength = crypto_secretbox_NONCEBYTES
|
val nonceLength = crypto_secretbox_NONCEBYTES
|
||||||
|
val keyLength = crypto_secretbox_KEYBYTES
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -13,3 +13,5 @@ class NonceOutOfBoundsException(text: String): Crypto2Exception(text)
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class NotSupportedException(text: String="operation is not supported for this object")
|
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.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import net.sergeych.bipack.Unsigned
|
import net.sergeych.bipack.Unsigned
|
||||||
import net.sergeych.synctools.ProtectedOp
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed class KDF {
|
sealed class KDF {
|
||||||
@ -15,41 +14,7 @@ sealed class KDF {
|
|||||||
Sensitive,
|
Sensitive,
|
||||||
}
|
}
|
||||||
|
|
||||||
fun derivedCached(password: String): UByteArray =
|
abstract fun derive(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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("argon")
|
@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(r1.findKey<EncryptingKey>(sk.id))
|
||||||
println(sk)
|
println(sk)
|
||||||
assertTrue { sk.publicKey.toUniversalKey() == r1.findKey<EncryptingKey>(sk.id) }
|
assertTrue { sk.publicKey.toUniversalKey() == r1.findKey<EncryptingKey>(sk.id) }
|
||||||
|
assertTrue { sk.publicKey.toUniversalKey() == r1.keyByTag<EncryptingKey>("foo") }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.bintools.encodeToHex
|
||||||
|
import net.sergeych.crypto2.BinaryId
|
||||||
import net.sergeych.crypto2.Contrail
|
import net.sergeych.crypto2.Contrail
|
||||||
import net.sergeych.crypto2.NumericNonce
|
import net.sergeych.crypto2.NumericNonce
|
||||||
import net.sergeych.crypto2.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
@ -31,4 +33,26 @@ class ToolsTest {
|
|||||||
assertEquals(counter, t)
|
assertEquals(counter, t)
|
||||||
assertContentEquals(x1.drop(2), x2.drop(2))
|
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