+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()))
|
||||
|
||||
|
@ -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