diff --git a/build.gradle.kts b/build.gradle.kts index 04c1269..5b21e08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } group = "net.sergeych" -version = "0.6.1-SNAPSHOT" +version = "0.6.2-SNAPSHOT" repositories { mavenCentral() diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt b/src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt index 8885fe0..ff099e2 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt @@ -60,6 +60,8 @@ class ByteChunk(val data: UByteArray): Comparable { */ val hex by lazy { data.encodeToHex() } + val base64Url by lazy { data.encodeToBase64Url() } + /** * human-readable dump */ @@ -75,9 +77,14 @@ class ByteChunk(val data: UByteArray): Comparable { companion object { fun fromHex(hex: String): ByteChunk = ByteChunk(hex.decodeHex().asUByteArray()) + fun random(sizeInBytes: Int=16) = randomUBytes(sizeInBytes).toChunk() } } +private fun UByteArray.toChunk(): ByteChunk = ByteChunk(this) +@Suppress("unused") +private fun ByteArray.toChunk(): ByteChunk = ByteChunk(this.asUByteArray()) + @Suppress("unused") fun ByteArray.asChunk() = ByteChunk(toUByteArray()) fun UByteArray.asChunk(): ByteChunk = ByteChunk(this) \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt index 0a3e59f..7ae260c 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt @@ -155,6 +155,11 @@ sealed class Container { var authorisedByKey: PublicKey? = null protected set + /** + * List of [KeyId] of the keys that unlocked the container, in the same order used for encryption.. + */ + abstract val keyIds: List + /** * @suppress @@ -175,6 +180,8 @@ sealed class Container { override val decryptedWithKeyId: KeyId? get() = decryptedWithKey?.id + override val keyIds: List = listOf(keyId) + init { decryptedData = creationData } @@ -270,6 +277,8 @@ sealed class Container { override var decryptedWithKeyId: KeyId? = null private set + override val keyIds: List = encryptedKeys.map { it.tag } + override fun decryptWith(keyRing: UniversalRing): UByteArray? { decryptedData?.let { return it } for (key in keyRing.decryptingKeys) { diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt index f8b06af..7d5fc89 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt @@ -46,7 +46,7 @@ class SymmetricKey( override val nonceBytesLength: Int = nonceLength override val id by lazy { - KeyId(KeysmagicNumber.defaultSymmetric,blake2b3l(keyBytes), pbkdfParams) + KeyId(KeysmagicNumber.defaultSymmetric,blake2b3l(keyBytes),pbkdfParams) } override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray = diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/kdf.kt b/src/commonMain/kotlin/net/sergeych/crypto2/kdf.kt index 7deadf4..95fae61 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/kdf.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/kdf.kt @@ -27,10 +27,40 @@ sealed class KDF { Moderate, FixedHigher, Sensitive, + ; + + /** + * Create [KDF] of the corresponding strength suitable to derive [numberOfKeys] symmetric keys. + * + * Random salt of proper size is used + */ + 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 + * [SymmetricKey.id] as [KeyId.kdp]. + + * Random salt of proper size is used + * + * ___Important: symmetric keys do not save key ids___. _Container do it, so it is possible to re-derive + * key to open the container, but in many cases you might need to save [KeyId.kdp] separately_. + * Having no [PBKD.Params] it would not be possible to recreate the key, as complexity defaults tend + * to change with time. + */ + @Suppress("unused") + fun deriveMultiple(password: String, count: Int): List = + kdfForSize(count).deriveMultiple(password, count) } + /** + * Derive a single key from the password, same as [deriveMultiple] with `count==1` + */ abstract fun derive(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 { val bytes = derive(password) val ks = SymmetricKey.keyLength @@ -142,14 +172,14 @@ sealed class KDF { salt, keySize ) - Complexity.Sensitive -> Argon( + Sensitive -> Argon( Alg.default, crypto_pwhash_OPSLIMIT_SENSITIVE, crypto_pwhash_MEMLIMIT_SENSITIVE, salt, keySize ) - Complexity.FixedHigher -> Argon( + FixedHigher -> Argon( V2id_13, 4UL, 1073741824, diff --git a/src/commonTest/kotlin/KDFTest.kt b/src/commonTest/kotlin/KDFTest.kt index 84b7189..371fd18 100644 --- a/src/commonTest/kotlin/KDFTest.kt +++ b/src/commonTest/kotlin/KDFTest.kt @@ -38,4 +38,11 @@ class KDFTest { assertEquals(set2, set1) } + @Test + fun complexityTest() = runTest{ + initCrypto() + val kk = KDF.Complexity.Interactive.kdfForSize(3).deriveMultiple("lala", 3) + assertEquals(3, kk.size) + } + } \ No newline at end of file