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