+hash salt derivation for any size. better naming
This commit is contained in:
		
							parent
							
								
									9f7babdf58
								
							
						
					
					
						commit
						10ec58ec08
					
				@ -1,3 +1,4 @@
 | 
			
		||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
 | 
			
		||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
@ -8,7 +9,7 @@ plugins {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
group = "net.sergeych"
 | 
			
		||||
version = "0.6.2-SNAPSHOT"
 | 
			
		||||
version = "0.6.3-SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
    mavenCentral()
 | 
			
		||||
@ -19,6 +20,7 @@ repositories {
 | 
			
		||||
 | 
			
		||||
kotlin {
 | 
			
		||||
    jvm {
 | 
			
		||||
        @OptIn(ExperimentalKotlinGradlePluginApi::class)
 | 
			
		||||
        compilerOptions {
 | 
			
		||||
            jvmTarget = JvmTarget.JVM_11
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -111,6 +111,34 @@ enum class Hash(
 | 
			
		||||
        for (block in source) sp.update(block)
 | 
			
		||||
        return sp.final()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Derive a salt of any size from a text. ___Salt could not be used as a password key___ source as it is not
 | 
			
		||||
     * strong to brute force, but it is good when you just need to get a deterministic salt of arbitrary size.
 | 
			
		||||
     *
 | 
			
		||||
     * _Note that deriving salt of sizes less than hash block will reduce hash strength and should not be allowed
 | 
			
		||||
     * in situation where strength is of concern while extending its length above hash block size does not improve
 | 
			
		||||
     * it_. The only reason to use this function is when the desired _salt size_ is not equal to block size, or
 | 
			
		||||
     * not known beforehand.1
 | 
			
		||||
     *
 | 
			
		||||
     * To get a cryptographically safe (more or less) key from password use [KDF] classes, or [KDF.deriveKey]
 | 
			
		||||
     * and [KDF.deriveMultipleKeys].
 | 
			
		||||
     */
 | 
			
		||||
    fun deriveSalt(base: String, sizeInBytes: Int): UByteArray {
 | 
			
		||||
        require(sizeInBytes > 0)
 | 
			
		||||
        val result = mutableListOf<UByte>()
 | 
			
		||||
        var round = 0
 | 
			
		||||
        var src = base.encodeToUByteArray()
 | 
			
		||||
        do {
 | 
			
		||||
            src = "rnd_${round++}_".encodeToUByteArray() + src
 | 
			
		||||
            val hash = digest(src)
 | 
			
		||||
            if (result.size + hash.size <= sizeInBytes) result += hash
 | 
			
		||||
            else result += hash.slice(0..<(sizeInBytes - result.size))
 | 
			
		||||
        } while (result.size < sizeInBytes)
 | 
			
		||||
        return result.toUByteArray()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private val defaultSuffix1 = "All lay loads on a willing horse".encodeToUByteArray()
 | 
			
		||||
 | 
			
		||||
@ -114,7 +114,7 @@ object PBKD {
 | 
			
		||||
        }
 | 
			
		||||
        return entry.op {
 | 
			
		||||
            if (entry.data == null) {
 | 
			
		||||
                entry.data = entry.kdf.derive(password)
 | 
			
		||||
                entry.data = entry.kdf.deriveKey(password)
 | 
			
		||||
            }
 | 
			
		||||
            entry.data!!
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -62,28 +62,34 @@ class UniversalRing(
 | 
			
		||||
     * Get all keys for the specified id (normally it could be 0, 1 or 2). See [KeyId] about
 | 
			
		||||
     * matching id keys.
 | 
			
		||||
     */
 | 
			
		||||
    fun keysById(id: KeyId): List<UniversalKey> = allKeys.filter { it.id == id }
 | 
			
		||||
    fun findById(id: KeyId): List<UniversalKey> = allKeys.filter { it.id == id }
 | 
			
		||||
 | 
			
		||||
    @Deprecated("please replace", replaceWith = ReplaceWith("findById"))
 | 
			
		||||
    fun keysById(id: KeyId) = findById(id)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return sequence of keys that have at least one of the [tags]
 | 
			
		||||
     */
 | 
			
		||||
    fun keysByTags(vararg tags: String) = sequence {
 | 
			
		||||
    fun findByTags(vararg tags: String) = sequence {
 | 
			
		||||
        for (e in keyWithTags.entries) {
 | 
			
		||||
            if (tags.any { it in e.value }) yield(e.key)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated("please replace", replaceWith = ReplaceWith("findByTag"))
 | 
			
		||||
    fun keysByTag(vararg tags: String) = findByTags(*tags)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the first key of the specified type having the [tag]
 | 
			
		||||
     */
 | 
			
		||||
    inline fun <reified T> keyByTag(tag: String) = keysByTags(tag).first { it is T }
 | 
			
		||||
    inline fun <reified T> keyByTag(tag: String) = findByTags(tag).first { it is T }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get keys of the specified type having any of the specified tags associated.
 | 
			
		||||
     */
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    inline fun <reified T> keysByAnyTag(vararg tags: String): Sequence<UniversalKey> =
 | 
			
		||||
        keysByTags(*tags).filter { it is T }
 | 
			
		||||
        findByTags(*tags).filter { it is T }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all keys with a given id. Note that _matching keys_ have the same id, see [KeyId] for more.
 | 
			
		||||
@ -134,6 +140,7 @@ class UniversalRing(
 | 
			
		||||
    /**
 | 
			
		||||
     * Add key and tags to the ring. If the key already exists, tags are merged.
 | 
			
		||||
     */
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun add(key: UniversalKey, tags: Collection<String>): UniversalRing =
 | 
			
		||||
        UniversalRing(keyWithTags + (key to tags.toSet()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ sealed class KDF {
 | 
			
		||||
         *
 | 
			
		||||
         * Random salt of proper size is used
 | 
			
		||||
         */
 | 
			
		||||
        fun kdfForSize(numberOfKeys: Int): KDF = creteDefault(SymmetricKey.keyLength*numberOfKeys, this)
 | 
			
		||||
        fun kdfForSize(numberOfKeys: Int): KDF = creteDefault(SymmetricKey.keyLength * numberOfKeys, this)
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Derive multiple keys from the password. Derivation params will be included in the key ids, see
 | 
			
		||||
@ -49,20 +49,20 @@ sealed class KDF {
 | 
			
		||||
         */
 | 
			
		||||
        @Suppress("unused")
 | 
			
		||||
        fun deriveMultiple(password: String, count: Int): List<SymmetricKey> =
 | 
			
		||||
            kdfForSize(count).deriveMultiple(password, count)
 | 
			
		||||
            kdfForSize(count).deriveMultipleKeys(password, count)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Derive a single key from the password, same as [deriveMultiple] with `count==1`
 | 
			
		||||
     * Derive a single key from the password, same as [deriveMultipleKeys] with `count==1`
 | 
			
		||||
     */
 | 
			
		||||
    abstract fun derive(password: String): UByteArray
 | 
			
		||||
    abstract fun deriveKey(password: String): UByteArray
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Derive keys from lower part of bytes derived from the password. E.g., if generated size is longer than
 | 
			
		||||
     * required to create [count] keys, the first bytes will be used. It let use rest bytes for other purposes.
 | 
			
		||||
     */
 | 
			
		||||
    fun deriveMultiple(password: String, count: Int): List<SymmetricKey> {
 | 
			
		||||
        val bytes = derive(password)
 | 
			
		||||
    fun deriveMultipleKeys(password: String, count: Int): List<SymmetricKey> {
 | 
			
		||||
        val bytes = deriveKey(password)
 | 
			
		||||
        val ks = SymmetricKey.keyLength
 | 
			
		||||
        check(ks * count <= bytes.size) { "KDF is too short for $count keys: ${bytes.size} we need ${ks * count}" }
 | 
			
		||||
        return (0..<count).map {
 | 
			
		||||
@ -84,6 +84,7 @@ sealed class KDF {
 | 
			
		||||
        val keySize: Int,
 | 
			
		||||
    ) : KDF(), Comparable<Argon> {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Very abstract strength comparison. If a1 > a2 it generally means that its complexity, and, hopefully,
 | 
			
		||||
         * strength is higher.
 | 
			
		||||
@ -111,7 +112,7 @@ sealed class KDF {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override fun derive(password: String): UByteArray =
 | 
			
		||||
        override fun deriveKey(password: String): UByteArray =
 | 
			
		||||
            PasswordHash.pwhash(keySize, password, salt, instructionsComplexity, memComplexity, algorithm.code)
 | 
			
		||||
 | 
			
		||||
        override fun equals(other: Any?): Boolean {
 | 
			
		||||
@ -141,6 +142,17 @@ sealed class KDF {
 | 
			
		||||
 | 
			
		||||
            fun randomSalt() = randomUBytes(saltSize)
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Create a deterministic salt suitable fot this KDF from a given text.
 | 
			
		||||
             *
 | 
			
		||||
             * We recommend to use random salts stored, and [KeyId] of password-generated keys already
 | 
			
		||||
             * do it for you. Use this method only if you can't store [KeyId] or salt; it is generally less secure:
 | 
			
		||||
             * knowing the base text it is possible to understand, for example, that the same derived password was used
 | 
			
		||||
             * more than once (random salt makes it impossible).
 | 
			
		||||
             */
 | 
			
		||||
            @Suppress("unused")
 | 
			
		||||
            fun deriveSaltFromString(text: String) = Hash.Blake2b.deriveSalt(text, saltSize)
 | 
			
		||||
 | 
			
		||||
            fun create(complexity: Complexity, salt: UByteArray, keySize: Int): Argon {
 | 
			
		||||
                require(salt.size == saltSize) { "The salt size should be $saltSize" }
 | 
			
		||||
                require(keySize > minKeySize) { "The key size should be at least $keySize bytes" }
 | 
			
		||||
@ -165,6 +177,7 @@ sealed class KDF {
 | 
			
		||||
                        268435456,
 | 
			
		||||
                        salt, keySize
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    Moderate -> Argon(
 | 
			
		||||
                        Alg.default,
 | 
			
		||||
                        crypto_pwhash_OPSLIMIT_MODERATE,
 | 
			
		||||
@ -192,10 +205,10 @@ sealed class KDF {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        @Suppress("unused")
 | 
			
		||||
        fun creteDefault(keySize: Int, complexity: Complexity, salt: UByteArray = Argon.randomSalt()): KDF {
 | 
			
		||||
            return Argon.create(complexity, salt, keySize)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data class Instance(val kdf: KDF, val password: String)
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,10 @@ import net.sergeych.crypto2.Hash
 | 
			
		||||
import net.sergeych.crypto2.initCrypto
 | 
			
		||||
import kotlin.random.Random
 | 
			
		||||
import kotlin.random.nextUBytes
 | 
			
		||||
import kotlin.test.*
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertContentEquals
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.test.assertFalse
 | 
			
		||||
 | 
			
		||||
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
 | 
			
		||||
suspend fun <T> sw(label: String, f: suspend () -> T): T {
 | 
			
		||||
@ -46,5 +49,21 @@ class HashTest {
 | 
			
		||||
        assertContentEquals(Hash.Blake2b.digest(a), p1)
 | 
			
		||||
        assertContentEquals(Hash.Sha3_384.digest(a), p2)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun deriveSaltTest() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        for( i in 2..257 ) {
 | 
			
		||||
            val x = Hash.Sha3AndBlake.deriveSalt("base one", i)
 | 
			
		||||
            val y = Hash.Sha3AndBlake.deriveSalt("base one", i)
 | 
			
		||||
            val z = Hash.Sha3AndBlake.deriveSalt("base two", i)
 | 
			
		||||
            assertContentEquals(x, y)
 | 
			
		||||
            assertFalse { x contentEquals z }
 | 
			
		||||
            assertEquals(x.size, i)
 | 
			
		||||
            assertEquals(y.size, i)
 | 
			
		||||
            assertEquals(z.size, i)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -41,7 +41,7 @@ class KDFTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun complexityTest() = runTest{
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val kk = KDF.Complexity.Interactive.kdfForSize(3).deriveMultiple("lala", 3)
 | 
			
		||||
        val kk = KDF.Complexity.Interactive.kdfForSize(3).deriveMultipleKeys("lala", 3)
 | 
			
		||||
        assertEquals(3, kk.size)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ class PBKDTest {
 | 
			
		||||
            assertEquals(i.kdp, kx.id.kdp)
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        val (y1,y2,y3) = k1.id.kdp!!.kdf.deriveMultiple("foobar", 3)
 | 
			
		||||
        val (y1,y2,y3) = k1.id.kdp!!.kdf.deriveMultipleKeys("foobar", 3)
 | 
			
		||||
        for( (a,b) in listOf(y1,y2,y3).zip(listOf(k1,k2,k3))) {
 | 
			
		||||
            assertEquals(a,b)
 | 
			
		||||
            assertNotNull(a.id.kdp)
 | 
			
		||||
 | 
			
		||||
@ -157,14 +157,14 @@ class RingTest {
 | 
			
		||||
        assertEquals(a, r1.findKey<SecretKey>(a.id))
 | 
			
		||||
        assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
 | 
			
		||||
        assertEquals(b, r1.findKey<SigningKey>(b.id))
 | 
			
		||||
        assertEquals(c, r1.keysById(c.id).first())
 | 
			
		||||
        assertEquals(c, r1.findById(c.id).first())
 | 
			
		||||
 | 
			
		||||
        r1 = UniversalRing.join(listOf(ra, rb, rc, rd))
 | 
			
		||||
 | 
			
		||||
        assertEquals(a, r1.findKey<SecretKey>(a.id))
 | 
			
		||||
        assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
 | 
			
		||||
        assertEquals(b, r1.findKey<SigningKey>(b.id))
 | 
			
		||||
        assertEquals(c, r1.keysById(c.id).first())
 | 
			
		||||
        assertEquals(c, r1.findById(c.id).first())
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user