19 Commits
Author SHA1 Message Date
sergeych 13dff8d760 v0.9.0: kotlin 2.2.21, kotlin.time fix for deprecated methods in kotlinx.datetime. Should work ok on mac/ios 2025-12-16 19:13:27 +01:00
sergeych 3bd06ac7ff added Container.decryptWithPassword 2025-12-04 12:14:07 +01:00
sergeych ca9ab0f7a0 0.8.5: upgrade kotlin & dependencies 2025-10-24 18:05:25 +02:00
sergeych b86ac6f00b 0.8.4 for all platforms 2025-03-27 00:17:59 +03:00
sergeych 9d338d2f13 merging 2025-03-27 00:14:32 +03:00
sergeych 5228be33ee fixed README.md 2025-03-26 23:45:31 +03:00
sergeych 528439f61d 0.8.3 for all platforms 2025-03-26 23:38:40 +03:00
sergeych 7c97a843e7 Merge pull request 'multiplatform-crypto-libsodium-bindings version with all targets' (#11) from YoungBlood/crypto2:master into master
Reviewed-on: sergeych/crypto2#11
2025-03-25 21:05:37 +03:00
kildishevps 1a81cd5110 multiplatform-crypto-libsodium-bindings version with all targets 2025-03-24 00:53:28 +03:00
sergeych 7d3e396cf7 dependency simplified 2025-03-16 22:29:22 +03:00
sergeych 85b13ed8ca EncryptedKVStorage: can destroy exisint creating one with a new key 2025-03-16 16:53:05 +03:00
sergeych fbbe4d3a34 EncryptedKVStorage: can destroy exisint creating one with a new key 2025-03-16 16:52:26 +03:00
sergeych 875c0f7a50 fix #10 KDF.Complexity derivation functions now require salt 2025-03-12 23:39:26 +03:00
sergeych fe6190eb8d +EncryptedKVStorage 2025-03-12 23:00:40 +03:00
sergeych 776f4e75ff 0.8.3-SNAPSHOT use updated bintools 2025-03-08 02:11:13 +03:00
sergeych fa7263b0e7 0.8.3-SNAPSHOT uaw mp_bintools bytechunk for compatibility 2025-03-08 01:53:35 +03:00
sergeych bd81f88dd8 0.8.2-SNAPSHOT support for wasmJs using youngblood, no mingw yet 2025-03-07 15:14:54 +03:00
sergeych 6fcf7841a7 Merge pull request 'Change multiplatform-crypto-libsodium-bindings version and bring wasmJs back' (#9) from YoungBlood/crypto2:master into master
Reviewed-on: sergeych/crypto2#9
2025-03-07 09:31:04 +03:00
kildishevps 4748ea0d65 Change commonMain multiplatform-crypto-libsodium-bindings version and bring wasmJs back 2025-03-03 12:48:10 +03:00
32 changed files with 331 additions and 221 deletions

No files matched your search

+1
View File
@@ -9,6 +9,7 @@ build/
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
.idea
*.iws
*.iml
*.ipr
-7
View File
@@ -1,10 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
/artifacts/crypto2_js_0_1_0_SNAPSHOT.xml
/artifacts/crypto2_jvm_0_1_0_SNAPSHOT.xml
-6
View File
@@ -1,6 +0,0 @@
<component name="ArtifactManager">
<artifact type="jar" name="crypto2-js-0.1.1-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="crypto2-js-0.1.1-SNAPSHOT.jar" />
</artifact>
</component>
-8
View File
@@ -1,8 +0,0 @@
<component name="ArtifactManager">
<artifact type="jar" name="crypto2-js-1.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="crypto2-js-1.0-SNAPSHOT.jar">
<element id="module-output" name="crypto2.jsMain" />
</root>
</artifact>
</component>
-6
View File
@@ -1,6 +0,0 @@
<component name="ArtifactManager">
<artifact type="jar" name="crypto2-jvm-0.1.1-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="crypto2-jvm-0.1.1-SNAPSHOT.jar" />
</artifact>
</component>
-8
View File
@@ -1,8 +0,0 @@
<component name="ArtifactManager">
<artifact type="jar" name="crypto2-jvm-1.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="crypto2-jvm-1.0-SNAPSHOT.jar">
<element id="module-output" name="crypto2.jvmMain" />
</root>
</artifact>
</component>
-8
View File
@@ -1,8 +0,0 @@
<component name="ArtifactManager">
<artifact type="jar" name="crypto2-wasm-js-0.1.1-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="crypto2-wasm-js-0.1.1-SNAPSHOT.jar">
<element id="module-output" name="crypto2.wasmJsMain" />
</root>
</artifact>
</component>
+24
View File
@@ -1,5 +1,29 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
<ScalaCodeStyleSettings>
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
</ScalaCodeStyleSettings>
+2
View File
@@ -5,6 +5,7 @@
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/local/Cellar/gradle/7.6/libexec" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@@ -12,5 +13,6 @@
</option>
</GradleProjectSettings>
</option>
<option name="parallelModelFetch" value="true" />
</component>
</project>
-7
View File
@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ReplaceUntilWithRangeUntil" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="StructuralWrap" enabled="false" level="TYPO" enabled_by_default="false" />
</profile>
</component>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.20" />
</component>
</project>
+2 -1
View File
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17 (5)" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="corretto-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+11 -1
View File
@@ -2,6 +2,16 @@
Kotlin Multiplatform cryptographic primitives using modern strong cryptography.
## v0.9.0 for kotlin 2.2.21, new kotlin time compatible
The primary goal was to fix kotlin-caused incompatibilities with kotlinx.datetime.Instant and Clock; the upgrade shoud be in-place
replacement providing calling code ises `kotlin.time.Instant` and
`kotlin.time.Clock` respectively. No other changes are needed.
Also we start to add small syntax sugar methods.
## v.0.8.4 is built for all platform, IOS and wasmJS included
Cryptographic API works exactly the same and compiles to any platform supported listed below with no change in source code.
All primitives meant to send over the network or store are `kotlinx.serialization` compatible, serializers included.
@@ -21,7 +31,7 @@ repositories {
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
}
dependencies {
import("net.sergeych:crypto2:0.7.1-SNAPSHOT")
import("net.sergeych:crypto2:0.8.4")
}
```
+27 -36
View File
@@ -9,33 +9,30 @@
*/
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("multiplatform") version "2.0.20"
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20"
kotlin("multiplatform") version "2.2.21"
id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21"
id("org.jetbrains.dokka") version "1.9.20"
`maven-publish`
}
group = "net.sergeych"
version = "0.7.4-SNAPSHOT"
version = "0.9.0"
repositories {
mavenCentral()
maven("https://maven.universablockchain.com/")
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
// maven("https://gitea.sergeych.net/api/packages/YoungBlood/maven")
maven("https://gitea.sergeych.net/api/packages/YoungBlood/maven")
mavenLocal()
}
kotlin {
jvm {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
jvmToolchain(21)
jvm()
js {
browser()
nodejs()
@@ -49,38 +46,37 @@ kotlin {
iosArm64()
iosSimulatorArm64()
mingwX64()
// @OptIn(ExperimentalWasmDsl::class)
// wasmJs {
// browser()
// }
val ktor_version = "2.3.6"
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
}
sourceSets {
all {
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
languageSettings.optIn("kotlin.time.ExperimentalTime")
}
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2")
implementation("net.sergeych:multiplatform-crypto-libsodium-bindings:0.9.6")
implementation(project.dependencies.platform("org.kotlincrypto.hash:bom:0.5.1"))
implementation("org.kotlincrypto.hash:sha3")
api("com.ionspin.kotlin:bignum:0.3.9")
api("net.sergeych:mp_bintools:0.1.7")
api("net.sergeych:mp_stools:1.5.1")
api("com.ionspin.kotlin:bignum:0.3.10")
api("net.sergeych:mp_bintools:0.3.2")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
implementation("org.slf4j:slf4j-simple:2.0.9")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.1")
}
}
val native by creating {
@@ -95,19 +91,6 @@ kotlin {
val jvmTest by getting
for (platform in listOf(linuxX64Main, linuxArm64Main, macosX64Main, macosArm64Main, iosX64Main, iosArm64Main, iosSimulatorArm64Main, mingwX64Main))
platform { dependsOn(native) }
// val wasmJsMain by getting {
// val wasmJsTargetRegex = Regex(pattern = "wasmJs.*")
// configurations.all {
// if (wasmJsTargetRegex.containsMatchIn(input = this.name)) {
// resolutionStrategy.dependencySubstitution {
// substitute(module("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2"))
// .using(module("net.sergeych:multiplatform-crypto-libsodium-bindings:0.9.4-SNAPSHOT"))
// .withoutClassifier()
// }
// }
// }
// }
}
}
@@ -129,6 +112,14 @@ publishing {
}
}
tasks.named<Test>("jvmTest") {
// Ignore Kotlin synthetic classes generated from files that look like tests
exclude("**/*TestKt.class")
exclude("**/*TestsKt.class")
exclude("**/*AssertThrowsKt.class")
exclude("**/*Test_toolsKt.class")
}
tasks.dokkaHtml.configure {
outputDirectory.set(buildDir.resolve("dokka"))
dokkaSourceSets {
+4
View File
@@ -9,3 +9,7 @@
#
kotlin.code.style=official
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
org.gradle.configuration-cache=true
org.gradle.caching=true
+1 -1
View File
@@ -10,6 +10,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -1,100 +0,0 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2
import kotlinx.serialization.Serializable
import net.sergeych.bintools.decodeHex
import net.sergeych.bintools.encodeToHex
import kotlin.math.min
/**
* Bytes sequence with comparison, concatenation, and string representation,
* could be used as hash keys for pure binary values, etc.
*/
@Suppress("unused")
@Serializable
class ByteChunk(val data: UByteArray): Comparable<ByteChunk> {
val size: Int get() = data.size
/**
* Per-byte comparison also of different length. From two chunks
* of different size but equal beginning, the shorter is considered
* the smaller.
*/
override fun compareTo(other: ByteChunk): Int {
val limit = min(size, other.size)
for( i in 0 ..< limit) {
val own = data[i]
val their = other.data[i]
if( own < their) return -1
else if( own > their) return 1
}
if( size < other.size ) return -1
if( size > other.size ) return 1
return 0
}
/**
* Equal chunks means content equality.
*/
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ByteChunk) return false
return data contentEquals other.data
}
/**
* Content-based hash code
*/
override fun hashCode(): Int {
return data.contentHashCode()
}
/**
* hex representation of data
*/
override fun toString(): String = base64Url
/**
* Hex encoded data
*/
val hex by lazy { data.encodeToHex() }
val base64Url by lazy { data.encodeToBase64Url() }
/**
* human-readable dump
*/
val dump by lazy { data.toDump() }
/**
* Concatenate two chunks and return new one
*/
operator fun plus(other: ByteChunk): ByteChunk = ByteChunk(data + other.data)
fun toByteArray(): ByteArray = data.asByteArray()
fun toUByteArray(): UByteArray = data
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)
@@ -321,6 +321,27 @@ sealed class Container {
}.build()
}
/**
* Decrypt the container with a password. It scans all key ids for
* these with `KDP` params, e.g., derived from password, and try to
* derive keys from the password and decrypt the container. If there are
* no derivable keys, or all of them failed to decrypt, returns null.
* It could be long operation if there are multiple derivable keys with heavy
* KDF. See [PBKD] and [KDF] for more.
*
* @return decrypted data or null
*/
@Suppress("unused")
fun decryptWithPassword(password: String): UByteArray? {
for( id in this.keyIds ) {
id.kdp?.let { kdp ->
decryptWith(kdp.deriveKey(password))?.let { return it }
}
}
return null
}
companion object {
/**
@@ -0,0 +1,105 @@
package net.sergeych.crypto2
import net.sergeych.bintools.KVStorage
import net.sergeych.bintools.MemoryKVStorage
import net.sergeych.bintools.optStored
import net.sergeych.synctools.ProtectedOp
import net.sergeych.synctools.invoke
import kotlin.random.Random
import kotlin.random.nextUBytes
/**
* Encrypted variant of [KVStorage]; the storage is encrypted with the given key
* in a given [plainStore] [KVStorage]. It is threadsafe where
* applicable. Also, it supports in-place key change [reEncrypt].
*
* Keys are stored encrypted and used hashed so it is not possible to
* retrieve them without knowing the encryption key.
*
* @param plainStore where to store encrypted data
* @param encryptionKey key to decrypt existing/encrypt new data. Can cause [DecryptionFailedException]
* if the key is wrong and the storage is already initialized with a new key and same [prefix]
* @param prefix prefix for keys to distinguish from other data in [plainStore]
* @param removeExisting if true, removes all existing data in [plainStore] if the [encryptionKey] can't
* decrypt existing encrypted data
*/
class EncryptedKVStorage(
private val plainStore: KVStorage,
private var encryptionKey: SymmetricKey,
private val prefix: String = "EKVS_",
removeExisting: Boolean
) : KVStorage {
private val op = ProtectedOp()
private val prefix2 = prefix + ":"
val seed: UByteArray
init {
var encryptedSeed by plainStore.optStored<UByteArray>("$prefix#seed")
seed = try {
encryptedSeed?.let { encryptionKey.decrypt(it) }
?: Random.nextUBytes(32).also {
encryptedSeed = encryptionKey.encrypt(it)
}
} catch (x: DecryptionFailedException) {
if (removeExisting) {
plainStore.keys.filter { it.startsWith(prefix) }.forEach {
plainStore.delete(it)
}
Random.nextUBytes(32).also {
encryptedSeed = encryptionKey.encrypt(it)
}
} else throw x
}
}
private fun mkkey(key: String): String =
blake2b(key.encodeToByteArray().asUByteArray() + seed).encodeToBase64Url()
override val keys: Set<String>
get() = op.invoke {
plainStore.keys.mapNotNull {
if (it.startsWith(prefix2))
plainStore[it]?.let { encrypted ->
encryptionKey.decrypt(encrypted.asUByteArray()).asByteArray().decodeToString()
}
else null
}.toSet()
}
override fun get(key: String): ByteArray? = op {
val k0 = mkkey(key)
val k = prefix + k0
plainStore[k]?.let { encryptionKey.decrypt(it.asUByteArray()).asByteArray() }
?.also {
val k2 = prefix2 + k0
if (k2 !in plainStore)
plainStore[k2] = encryptionKey.encrypt(key).asByteArray()
}
}
override fun set(key: String, value: ByteArray?) {
op {
val k1 = mkkey(key)
plainStore[prefix + k1] = value?.let {
encryptionKey.encrypt(it.asUByteArray()).asByteArray()
}
plainStore[prefix2 + k1] = encryptionKey.encrypt(key).asByteArray()
}
}
/**
* Re-encrypts the entire storage in-place with the given key; it is threadsafe where
* applicable.
*
* This method re-encrypts every data item so it is cryptographically secure.
*/
fun reEncrypt(newKey: SymmetricKey) {
op {
val copy = MemoryKVStorage().also { it.addAll(this) }
encryptionKey = newKey
addAll(copy)
}
}
}
@@ -14,6 +14,8 @@ import com.ionspin.kotlin.crypto.generichash.GenericHash
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.flow.Flow
import net.sergeych.bintools.ByteChunk
import net.sergeych.bintools.asChunk
import org.kotlincrypto.hash.sha3.SHA3_256
import org.kotlincrypto.hash.sha3.SHA3_384
@@ -60,6 +60,12 @@ sealed class Multikey {
*/
abstract fun check(keys: Iterable<VerifyingPublicKey>): Boolean
/**
* Step towards automated picking necessary keys.
* Return all the keys mentioned in this multikey condition.
*/
abstract fun mentionedKeys(): Set<VerifyingPublicKey>
/**
* Check that [verifyingKeys] satisfy the multikey condition
*/
@@ -97,6 +103,10 @@ sealed class Multikey {
}
return false
}
override fun mentionedKeys(): Set<VerifyingPublicKey> {
return validKeys
}
}
/**
@@ -119,6 +129,10 @@ sealed class Multikey {
}
return false
}
override fun mentionedKeys(): Set<VerifyingPublicKey> {
return validKeys.flatMap { it.mentionedKeys() }.toSet()
}
}
/**
@@ -129,6 +143,7 @@ sealed class Multikey {
@Serializable
object AnyKey : Multikey() {
override fun check(keys: Iterable<VerifyingPublicKey>): Boolean = true
override fun mentionedKeys(): Set<VerifyingPublicKey> = emptySet()
}
@@ -10,7 +10,7 @@
package net.sergeych.crypto2
import kotlinx.datetime.Instant
import kotlin.time.Instant
import kotlinx.serialization.Serializable
import net.sergeych.bipack.BipackEncoder
import net.sergeych.bipack.decodeFromBipack
@@ -10,7 +10,7 @@
package net.sergeych.crypto2
import kotlinx.datetime.Instant
import kotlin.time.Instant
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.sergeych.bipack.BipackDecoder
@@ -10,7 +10,7 @@
package net.sergeych.crypto2
import kotlinx.datetime.Instant
import kotlin.time.Instant
interface SigningKey: KeyInstance {
val verifyingKey: VerifyingPublicKey
@@ -11,7 +11,7 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.signature.Signature
import kotlinx.datetime.Instant
import kotlin.time.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@@ -44,7 +44,8 @@ sealed class KDF {
*
* Random salt of proper size is used
*/
fun kdfForSize(numberOfKeys: Int): KDF = creteDefault(SymmetricKey.keyLength * numberOfKeys, this)
fun kdfForSize(numberOfKeys: Int,salt: UByteArray = Argon.randomSalt()): KDF =
creteDefault(SymmetricKey.keyLength * numberOfKeys, this, salt)
/**
* Derive multiple keys from the password. Derivation params will be included in the key ids, see
@@ -58,13 +59,13 @@ sealed class KDF {
* to change with time.
*/
@Suppress("unused")
fun deriveMultiple(password: String, count: Int): List<SymmetricKey> =
kdfForSize(count).deriveMultipleKeys(password, count)
fun deriveMultiple(password: String, count: Int,salt: UByteArray): List<SymmetricKey> =
kdfForSize(count, salt).deriveMultipleKeys(password, count)
/**
* Derive single key from password, same as [deriveMultiple] with count=1.
*/
fun derive(password: String): SymmetricKey = deriveMultiple(password, 1).first()
fun derive(password: String, salt: UByteArray): SymmetricKey = deriveMultiple(password, 1, salt).first()
}
/**
@@ -12,8 +12,8 @@
package net.sergeych.utools
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlin.time.Clock
import kotlin.time.Instant
fun now(): Instant = Clock.System.now()
fun nowToSeconds(): Instant = Clock.System.now().truncateToSeconds()
+4 -4
View File
@@ -9,10 +9,10 @@
*/
import kotlinx.coroutines.test.runTest
import net.sergeych.bintools.ByteChunk
import net.sergeych.bintools.toDump
import net.sergeych.bipack.BipackEncoder
import net.sergeych.crypto2.BinaryId
import net.sergeych.crypto2.ByteChunk
import net.sergeych.crypto2.initCrypto
import kotlin.test.Test
import kotlin.test.assertContentEquals
@@ -35,10 +35,10 @@ class BinaryIdTest {
initCrypto()
val x = ByteChunk.random(3)
assertEquals(3, x.data.size)
assertEquals(3, x.toByteArray().size)
assertEquals(3, x.toUByteArray().size)
assertEquals(3, x.asByteArray.size)
assertEquals(3, x.data.size)
println(BipackEncoder.encode(x).toDump())
assertEquals(4, BipackEncoder.encode(x).size)
assertContentEquals(BipackEncoder.encode(x.toByteArray()), BipackEncoder.encode(x))
assertContentEquals(BipackEncoder.encode(x.asByteArray), BipackEncoder.encode(x))
}
}
+12 -10
View File
@@ -10,25 +10,17 @@
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Clock
import kotlin.time.Clock
import net.sergeych.crypto2.Hash
import net.sergeych.crypto2.initCrypto
import kotlin.random.Random
import kotlin.random.nextUBytes
import kotlin.test.Ignore
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 {
val t1 = Clock.System.now()
val result = f()
val t2 = Clock.System.now()
// println("$label: ${t2 - t1}")
return result
}
class HashTest {
@Test
fun testEqualMethods() {
@@ -77,3 +69,13 @@ class HashTest {
}
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
suspend fun <T> sw(label: String, f: suspend () -> T): T {
val t1 = Clock.System.now()
val result = f()
val t2 = Clock.System.now()
// println("$label: ${t2 - t1}")
return result
}
+1 -1
View File
@@ -442,7 +442,7 @@ class KeysTest {
assertContentEquals(k2.keyBytes, k2.id.id.body)
val k7 = SymmetricKey.new()
val k8 = KDF.Complexity.Interactive.derive("super")
val k8 = KDF.Complexity.Interactive.derive("super", KDF.Argon.randomSalt())
fun testToString(k: UniversalKey) {
val s = k.toString()
+1 -1
View File
@@ -9,7 +9,7 @@
*/
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant
import kotlin.time.Instant
import net.sergeych.crypto2.initCrypto
import net.sergeych.utools.nowToSeconds
import net.sergeych.utools.pack
+87
View File
@@ -0,0 +1,87 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.bintools.*
import net.sergeych.bipack.decodeFromBipack
import net.sergeych.crypto2.DecryptionFailedException
import net.sergeych.crypto2.EncryptedKVStorage
import net.sergeych.crypto2.SymmetricKey
import net.sergeych.crypto2.initCrypto
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
class StorageTest {
@Test
fun testGetAndSet() = runTest {
initCrypto()
val plain = MemoryKVStorage()
val key = SymmetricKey.new()
val storage = EncryptedKVStorage(plain, key, removeExisting = false)
var hello by storage.optStored<String>()
assertNull(hello)
hello = "world"
assertEquals("world", storage["hello"]?.decodeFromBipack<String>())
println("plain: ${plain.keys}")
assertEquals(setOf("hello"), storage.keys)
var foo by storage.stored("bar")
assertEquals("bar", foo)
foo = "bar2"
// plain.dump()
// storage.dump()
assertEquals(setOf("hello", "foo"), storage.keys)
}
@Test
fun testReEncrypt() = runTest {
initCrypto()
fun test(x: KVStorage) {
val foo by x.stored("1")
val bar by x.stored("2")
val bazz by x.stored("3")
assertEquals("foo", foo)
assertEquals("bar", bar)
assertEquals("bazz", bazz)
}
fun setup(s: KVStorage, k: SymmetricKey): EncryptedKVStorage {
val x = EncryptedKVStorage(s, k, removeExisting = false)
var foo by x.stored("1")
var bar by x.stored("2")
var bazz by x.stored("3")
foo = "foo"
bar = "bar"
bazz = "bazz"
return x
}
val k1 = SymmetricKey.new()
val k2 = SymmetricKey.new()
val plain = MemoryKVStorage()
val s1 = setup(plain, k1)
test(s1)
s1.reEncrypt(k2)
test(s1)
// val s2 = EncryptedKVStorage(plain, k2)
// test(s2)
}
@Test
fun testDeleteExisting() = runTest {
initCrypto()
val plain = MemoryKVStorage()
val c1 = EncryptedKVStorage(plain, SymmetricKey.new(), removeExisting = false) // 1
c1.write("hello", "world")
assertFailsWith<DecryptionFailedException> {
EncryptedKVStorage(plain, SymmetricKey.new(), removeExisting = false) // 2
}
EncryptedKVStorage(plain, SymmetricKey.new(), removeExisting = true) // 2
}
}
@Suppress("unused")
fun KVStorage.dump() {
for (k in keys)
println("$k: ${this[k]?.toDump()}")
}