Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13dff8d760 | ||
|
|
3bd06ac7ff | ||
|
|
ca9ab0f7a0 | ||
|
|
b86ac6f00b | ||
|
|
9d338d2f13 | ||
|
|
5228be33ee | ||
|
|
528439f61d | ||
|
|
7c97a843e7 | ||
|
|
1a81cd5110 | ||
|
|
7d3e396cf7 | ||
|
|
85b13ed8ca | ||
|
|
fbbe4d3a34 | ||
|
|
875c0f7a50 | ||
|
|
fe6190eb8d | ||
|
|
776f4e75ff | ||
|
|
fa7263b0e7 | ||
|
|
bd81f88dd8 | ||
|
|
6fcf7841a7 | ||
|
|
4748ea0d65 | ||
|
|
e2d4fb07ad | ||
|
|
d180da309b | ||
|
|
c9e3c57ee2 | ||
|
|
81e02ac88e | ||
|
|
e8d6b2fc02 | ||
|
|
277dc62553 | ||
|
|
7e52a72c6a | ||
|
|
7fa3ab1ca8 | ||
|
|
242cc7d0f5 | ||
|
|
db7453fbb2 | ||
|
|
cef7e4abed | ||
|
|
e8fa634640 | ||
|
|
10ec58ec08 | ||
|
|
9f7babdf58 | ||
|
|
640ceb448e | ||
|
|
194fe22afa | ||
|
|
8eed7a3de7 | ||
|
|
4cbc17334c | ||
|
|
1191de284e | ||
|
|
8e652e0421 |
No files matched your search
@@ -9,6 +9,7 @@ build/
|
|||||||
.idea/jarRepositories.xml
|
.idea/jarRepositories.xml
|
||||||
.idea/compiler.xml
|
.idea/compiler.xml
|
||||||
.idea/libraries/
|
.idea/libraries/
|
||||||
|
.idea
|
||||||
*.iws
|
*.iws
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
@@ -41,4 +42,5 @@ out/
|
|||||||
# Other
|
# Other
|
||||||
.kotlin
|
.kotlin
|
||||||
.idea
|
.idea
|
||||||
|
.gigaide
|
||||||
/kotlin-js-store/yarn.lock
|
/kotlin-js-store/yarn.lock
|
||||||
Generated
-7
@@ -1,10 +1,3 @@
|
|||||||
# Default ignored files
|
# Default ignored files
|
||||||
/shelf/
|
/shelf/
|
||||||
/workspace.xml
|
/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
@@ -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
@@ -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
@@ -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
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
Generated
+24
@@ -1,5 +1,29 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<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>
|
<ScalaCodeStyleSettings>
|
||||||
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
|
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
|
||||||
</ScalaCodeStyleSettings>
|
</ScalaCodeStyleSettings>
|
||||||
|
|||||||
Generated
+2
@@ -5,6 +5,7 @@
|
|||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleHome" value="/usr/local/Cellar/gradle/7.6/libexec" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
@@ -12,5 +13,6 @@
|
|||||||
</option>
|
</option>
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="parallelModelFetch" value="true" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
-7
@@ -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>
|
|
||||||
Generated
-6
@@ -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>
|
|
||||||
Generated
+2
-1
@@ -1,9 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</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" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,12 +2,24 @@
|
|||||||
|
|
||||||
Kotlin Multiplatform cryptographic primitives using modern strong cryptography.
|
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.
|
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.
|
All primitives meant to send over the network or store are `kotlinx.serialization` compatible, serializers included.
|
||||||
|
|
||||||
# Important notes on upgrade
|
# Important notes on upgrade
|
||||||
|
|
||||||
|
___Please upgrade to 0.7.1+___ as it has much more compact but not backward-compatible serialization format!
|
||||||
|
|
||||||
Since version __0.5.*__ key identity calculation for asymmetric keys is updated
|
Since version __0.5.*__ key identity calculation for asymmetric keys is updated
|
||||||
to make it safer for theoretic future attack on blake2b hashing. Key.id values
|
to make it safer for theoretic future attack on blake2b hashing. Key.id values
|
||||||
are incompatible with older. Sorry for inconvenience.
|
are incompatible with older. Sorry for inconvenience.
|
||||||
@@ -19,7 +31,7 @@ repositories {
|
|||||||
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
import("net.sergeych:crypto2:0.5.7")
|
import("net.sergeych:crypto2:0.8.4")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -34,6 +46,10 @@ Please see the current documentation [here](https://code.sergeych.net/docs/crypt
|
|||||||
- All moder browsers, including mobile
|
- All moder browsers, including mobile
|
||||||
- Node.js
|
- Node.js
|
||||||
|
|
||||||
|
## WasmJs
|
||||||
|
|
||||||
|
- All moder browsers, including mobile
|
||||||
|
|
||||||
## JVM
|
## JVM
|
||||||
|
|
||||||
- Android
|
- Android
|
||||||
@@ -108,4 +124,13 @@ Secret key encryption and signing/verifying uses Edwards curves 25519 algorithms
|
|||||||
- SHA3 256, 384, more are on the way.
|
- SHA3 256, 384, more are on the way.
|
||||||
- CRC-protected binary ID with magic numbers to implement human-friendly IDS with type checks
|
- CRC-protected binary ID with magic numbers to implement human-friendly IDS with type checks
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
# Licensing
|
||||||
|
|
||||||
|
This is work in progress, not yet moved to public domain;
|
||||||
|
you need to obtain a license from https://8-rays.dev or [Sergey Chernov]. For open source projects it will most be free on some special terms.
|
||||||
|
|
||||||
|
It will be moved to open source; we also guarantee that it will be moved to open source immediately if the software export restrictions will be lifted. We do not support such practices here at 8-rays.dev and assume open source must be open.
|
||||||
|
|
||||||
|
[Sergey Chernov]: https://t.me/real_sergeych
|
||||||
+10
@@ -1,4 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
./gradlew dokkaHtml
|
./gradlew dokkaHtml
|
||||||
rsync -avz ./build/dokka/* code.sergeych.net:/bigstore/sergeych_pub/code/docs/crypto2
|
rsync -avz ./build/dokka/* code.sergeych.net:/bigstore/sergeych_pub/code/docs/crypto2
|
||||||
+39
-20
@@ -1,28 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||||
|
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform") version "2.0.20"
|
kotlin("multiplatform") version "2.2.21"
|
||||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20"
|
id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21"
|
||||||
id("org.jetbrains.dokka") version "1.9.20"
|
id("org.jetbrains.dokka") version "1.9.20"
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.5.7"
|
version = "0.9.0"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://maven.universablockchain.com/")
|
maven("https://maven.universablockchain.com/")
|
||||||
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||||
|
maven("https://gitea.sergeych.net/api/packages/YoungBlood/maven")
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm {
|
jvmToolchain(21)
|
||||||
compilerOptions {
|
jvm()
|
||||||
jvmTarget = JvmTarget.JVM_11
|
|
||||||
}
|
|
||||||
}
|
|
||||||
js {
|
js {
|
||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
@@ -36,36 +46,37 @@ kotlin {
|
|||||||
iosArm64()
|
iosArm64()
|
||||||
iosSimulatorArm64()
|
iosSimulatorArm64()
|
||||||
mingwX64()
|
mingwX64()
|
||||||
// @OptIn(ExperimentalWasmDsl::class)
|
@OptIn(ExperimentalWasmDsl::class)
|
||||||
// wasmJs() //no libsodium bindings yet (strangely)
|
wasmJs {
|
||||||
// val ktor_version = "2.3.6"
|
browser()
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
all {
|
all {
|
||||||
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
|
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
|
||||||
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||||
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
||||||
|
languageSettings.optIn("kotlin.time.ExperimentalTime")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
|
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(project.dependencies.platform("org.kotlincrypto.hash:bom:0.5.1"))
|
||||||
implementation("org.kotlincrypto.hash:sha3")
|
implementation("org.kotlincrypto.hash:sha3")
|
||||||
api("com.ionspin.kotlin:bignum:0.3.9")
|
api("com.ionspin.kotlin:bignum:0.3.10")
|
||||||
api("net.sergeych:mp_bintools:0.1.7")
|
api("net.sergeych:mp_bintools:0.3.2")
|
||||||
api("net.sergeych:mp_stools:1.5.1")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("test"))
|
implementation(kotlin("test"))
|
||||||
implementation("org.slf4j:slf4j-simple:2.0.9")
|
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 {
|
val native by creating {
|
||||||
@@ -78,7 +89,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jvmTest by getting
|
val jvmTest by getting
|
||||||
for (platform in listOf(linuxMain, macosMain, iosMain, mingwMain))
|
for (platform in listOf(linuxX64Main, linuxArm64Main, macosX64Main, macosArm64Main, iosX64Main, iosArm64Main, iosSimulatorArm64Main, mingwX64Main))
|
||||||
platform { dependsOn(native) }
|
platform { dependsOn(native) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,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 {
|
tasks.dokkaHtml.configure {
|
||||||
outputDirectory.set(buildDir.resolve("dokka"))
|
outputDirectory.set(buildDir.resolve("dokka"))
|
||||||
dokkaSourceSets {
|
dokkaSourceSets {
|
||||||
|
|||||||
@@ -1 +1,15 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
kotlin.code.style=official
|
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
|
||||||
+11
-1
@@ -1,5 +1,15 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# You may use, distribute and modify this code under the
|
||||||
# you may not use this file except in compliance with the License.
|
# terms of the private license, which you must obtain from the author
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
#
|
||||||
# https://www.apache.org/licenses/LICENSE-2.0
|
# To obtain the license, contact the author: https://t.me/real_sergeych or email to
|
||||||
#
|
# real dot sergeych at gmail.
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.box.Box
|
import com.ionspin.kotlin.crypto.box.Box
|
||||||
@@ -14,10 +24,10 @@ import net.sergeych.crypto2.Asymmetric.generateKeys
|
|||||||
*
|
*
|
||||||
* ## How to
|
* ## How to
|
||||||
*
|
*
|
||||||
* - [SecretKey.new] to create a secret key that includes [SecretKey.publicKey].
|
* - [DecryptingSecretKey.new] to create a secret key that includes [DecryptingSecretKey.publicKey].
|
||||||
* [generateKeys] also makes the pair.
|
* [generateKeys] also makes the pair.
|
||||||
* - [PublicKey] provides encryption, anonymous or authenticated.
|
* - [EncryptingPublicKey] provides encryption, anonymous or authenticated.
|
||||||
* - [SecretKey] provides authenticated decryption of what [PublicKey] was encrypted with.
|
* - [DecryptingSecretKey] provides authenticated decryption of what [EncryptingPublicKey] was encrypted with.
|
||||||
* - [Message] is a serializable container with all necessary data to decrypt public-key encrypted data it.
|
* - [Message] is a serializable container with all necessary data to decrypt public-key encrypted data it.
|
||||||
*
|
*
|
||||||
* __Algorithms:__
|
* __Algorithms:__
|
||||||
@@ -31,8 +41,8 @@ object Asymmetric {
|
|||||||
/**
|
/**
|
||||||
* Encrypted message holder.
|
* Encrypted message holder.
|
||||||
*
|
*
|
||||||
* Do not instantiate it directly, use [PublicKey.encryptMessage], [PublicKey.encryptAnonymousMessage], etc.
|
* Do not instantiate it directly, use [EncryptingPublicKey.encryptMessage], [EncryptingPublicKey.encryptAnonymousMessage], etc.
|
||||||
* instead. Also [SecretKey.decrypt] can be used to decrypt it same as [decrypt] or [decryptWithSenderKey].
|
* instead. Also [DecryptingSecretKey.decrypt] can be used to decrypt it same as [decrypt] or [decryptWithSenderKey].
|
||||||
*
|
*
|
||||||
* To successfully decrypt the message, it is necessary to know a sender public key, and non-secret nonce.
|
* To successfully decrypt the message, it is necessary to know a sender public key, and non-secret nonce.
|
||||||
* This class carries all this information; serialize and pass it to the recipient.
|
* This class carries all this information; serialize and pass it to the recipient.
|
||||||
@@ -41,12 +51,12 @@ object Asymmetric {
|
|||||||
class Message(
|
class Message(
|
||||||
private val nonce: UByteArray,
|
private val nonce: UByteArray,
|
||||||
private val encryptedMessage: UByteArray,
|
private val encryptedMessage: UByteArray,
|
||||||
val senderPublicKey: PublicKey,
|
val senderPublicKey: EncryptingPublicKey,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Decrypt the message, same as [SecretKey.decrypt]
|
* Decrypt the message, same as [DecryptingSecretKey.decrypt]
|
||||||
*/
|
*/
|
||||||
fun decrypt(recipientKey: SecretKey): UByteArray {
|
fun decrypt(recipientKey: DecryptingSecretKey): UByteArray {
|
||||||
return decryptWithSenderKey(senderPublicKey, recipientKey)
|
return decryptWithSenderKey(senderPublicKey, recipientKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +64,7 @@ object Asymmetric {
|
|||||||
* Decrypt a message which is not include sender's public key (which should somehow be
|
* Decrypt a message which is not include sender's public key (which should somehow be
|
||||||
* known to the recipient). Use it if [senderPublicKey] is null.
|
* known to the recipient). Use it if [senderPublicKey] is null.
|
||||||
*/
|
*/
|
||||||
fun decryptWithSenderKey(senderKey: PublicKey, recipientKey: SecretKey): UByteArray {
|
fun decryptWithSenderKey(senderKey: EncryptingPublicKey, recipientKey: DecryptingSecretKey): UByteArray {
|
||||||
return try {
|
return try {
|
||||||
WithFill.decode(
|
WithFill.decode(
|
||||||
Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
|
Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
|
||||||
@@ -79,19 +89,19 @@ object Asymmetric {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt the [plainData] using [from] sender for [recipient] public key. Note that to decrypt it
|
* Encrypt the [plainData] using [from] sender for [recipient] public key. Note that to decrypt it
|
||||||
* the [SecretKey] that corresponds to the [recipient] public key is needed, Sender can't decrypt the message!
|
* the [DecryptingSecretKey] that corresponds to the [recipient] public key is needed, Sender can't decrypt the message!
|
||||||
*
|
*
|
||||||
* The authenticated encryption is used, is the message _is successfully decrypted_, it also means that
|
* The authenticated encryption is used, is the message _is successfully decrypted_, it also means that
|
||||||
* it was signed by the sender, whose public key is known at the decryption time.
|
* it was signed by the sender, whose public key is known at the decryption time.
|
||||||
*
|
*
|
||||||
* When it is important not to provide senders' key, use [PublicKey.encryptAnonymousMessage].
|
* When it is important not to provide senders' key, use [EncryptingPublicKey.encryptAnonymousMessage].
|
||||||
*
|
*
|
||||||
* @param from the senders' secret key.
|
* @param from the senders' secret key.
|
||||||
* @param recipient the recipients' public key.
|
* @param recipient the recipients' public key.
|
||||||
* @param plainData data to encrypt
|
* @param plainData data to encrypt
|
||||||
*/
|
*/
|
||||||
internal fun createMessage(
|
internal fun createMessage(
|
||||||
from: SecretKey, recipient: PublicKey, plainData: UByteArray,
|
from: DecryptingSecretKey, recipient: EncryptingPublicKey, plainData: UByteArray,
|
||||||
nonce: UByteArray = randomNonce(),
|
nonce: UByteArray = randomNonce(),
|
||||||
): Message {
|
): Message {
|
||||||
return Message(
|
return Message(
|
||||||
@@ -104,8 +114,8 @@ object Asymmetric {
|
|||||||
|
|
||||||
private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES)
|
private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES)
|
||||||
|
|
||||||
fun generateKeys() = SecretKey.generateKeys()
|
fun generateKeys() = DecryptingSecretKey.generateKeys()
|
||||||
fun newSecretKey() = SecretKey.new()
|
fun newSecretKey() = DecryptingSecretKey.new()
|
||||||
|
|
||||||
val nonceBytesLength = crypto_box_NONCEBYTES
|
val nonceBytesLength = crypto_box_NONCEBYTES
|
||||||
|
|
||||||
@@ -115,4 +125,4 @@ object Asymmetric {
|
|||||||
* Shortcut type: a pair of sender secret key and recipient private key could be used so
|
* Shortcut type: a pair of sender secret key and recipient private key could be used so
|
||||||
* simplify such interfaces
|
* simplify such interfaces
|
||||||
*/
|
*/
|
||||||
typealias AsymmetricEncryptionPair = Pair<SecretKey?, PublicKey>
|
typealias AsymmetricEncryptionPair = Pair<DecryptingSecretKey?, EncryptingPublicKey>
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
@@ -5,17 +15,60 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import net.sergeych.bintools.CRC
|
import net.sergeych.bintools.CRC
|
||||||
import net.sergeych.bintools.CRC8
|
import net.sergeych.bintools.CRC8
|
||||||
|
import net.sergeych.crypto2.BinaryId.Companion.createFromBytes
|
||||||
|
import net.sergeych.crypto2.BinaryId.Companion.createFromString
|
||||||
|
import net.sergeych.crypto2.BinaryId.Companion.createFromUBytes
|
||||||
|
import net.sergeych.crypto2.BinaryId.Companion.createRandom
|
||||||
|
import net.sergeych.crypto2.BinaryId.IncomparableException
|
||||||
|
import net.sergeych.crypto2.BinaryId.InvalidException
|
||||||
import net.sergeych.mp_tools.decodeBase64Url
|
import net.sergeych.mp_tools.decodeBase64Url
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary identifier with control code and magic number. To create instaance
|
||||||
|
* use one of [createFromBytes], [createFromString], [createFromUBytes],
|
||||||
|
* or [createRandom], also deserialize serialized one.
|
||||||
|
*
|
||||||
|
* Integrity is checked on instantiating automatically.
|
||||||
|
*
|
||||||
|
* It is comparable to other BinaryId as long as both have the same [magic]. Attempt to
|
||||||
|
* compare these that differ throws [IncomparableException]
|
||||||
|
*
|
||||||
|
* ### Internal structure
|
||||||
|
*
|
||||||
|
* Say we have a `BinaryId` of size `N` bytes. The inner structure will be:
|
||||||
|
*
|
||||||
|
* | offset | meaning |
|
||||||
|
* |-----------|---------|
|
||||||
|
* | 0 ..< N-2 | id bytes |
|
||||||
|
* | N-2 | magic, 0..255 |
|
||||||
|
* | N-1 | CRC8, polynomial 0xA7, as in Bluetooth |
|
||||||
|
*
|
||||||
|
* @throws InvalidException if crc check failed
|
||||||
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
open class BinaryId protected constructor (
|
open class BinaryId(
|
||||||
|
/**
|
||||||
|
* The packed binary id. Note that serialized version is one byte longer containing
|
||||||
|
* the size prefix
|
||||||
|
*/
|
||||||
val id: UByteArray,
|
val id: UByteArray,
|
||||||
) : Comparable<BinaryId> {
|
) : Comparable<BinaryId> {
|
||||||
|
|
||||||
class InvalidException(text: String) : IllegalArgumentException(text)
|
/**
|
||||||
|
* Bad format (crc does not match)
|
||||||
|
*/
|
||||||
|
class InvalidException(text: String, reason: Throwable? = null) : IllegalArgumentException(text, reason)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to compare binary ids with different magic. In this case only [equals]
|
||||||
|
* works, but [compareTo] throws this exception.
|
||||||
|
*/
|
||||||
class IncomparableException(text: String) : IllegalArgumentException(text)
|
class IncomparableException(text: String) : IllegalArgumentException(text)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* magic number (as decoded), `0..255`
|
||||||
|
*/
|
||||||
@Transient
|
@Transient
|
||||||
val magic: Int = run {
|
val magic: Int = run {
|
||||||
if (id.size < 4) throw InvalidException("BinaryId is too short")
|
if (id.size < 4) throw InvalidException("BinaryId is too short")
|
||||||
@@ -27,29 +80,41 @@ open class BinaryId protected constructor (
|
|||||||
rest.last().toInt()
|
rest.last().toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val innerData: UByteArray by lazy { id.sliceArray( 1..< id.size-1 ) }
|
private val innerData: UByteArray by lazy { id.sliceArray(1..<id.size - 1) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id body: all the bytes except check and magic. These could carry useful information.
|
* The ID body: all the bytes except check and magic. ID bytes could carry useful information.
|
||||||
|
*
|
||||||
|
* - `id.size` is [body] size + 2 (see [BinaryId] inner structure)
|
||||||
*/
|
*/
|
||||||
val body: UByteArray by lazy { id.sliceArray( 0 until id.size-2 ) }
|
val body: UByteArray by lazy { id.sliceArray(0 until id.size - 2) }
|
||||||
|
|
||||||
val asVerifyingKey: VerifyingKey by lazy {
|
val asVerifyingKey: VerifyingKey by lazy {
|
||||||
if( magic != KeysmagicNumber.defaultVerifying.ordinal)
|
if (magic != KeysmagicNumber.defaultVerifying.ordinal)
|
||||||
throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultVerifying.ordinal}")
|
throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultVerifying.ordinal}")
|
||||||
check(body.size == 32)
|
check(body.size == 32)
|
||||||
VerifyingPublicKey(body)
|
VerifyingPublicKey(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
val asPublicKey: PublicKey by lazy {
|
/**
|
||||||
if( magic != KeysmagicNumber.defaultAssymmetric.ordinal)
|
* Try to recnstruct a [EncryptingPublicKey] from [id] bytes. For such keys, [EncryptingPublicKey.id] and [DecryptingSecretKey.id]
|
||||||
|
* are made from public key bytes so it could be restored from such an ID
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
val asPublicKey: EncryptingPublicKey by lazy {
|
||||||
|
if (magic != KeysmagicNumber.defaultAssymmetric.ordinal)
|
||||||
throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultAssymmetric.ordinal}")
|
throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultAssymmetric.ordinal}")
|
||||||
check(body.size == 32)
|
check(body.size == 32)
|
||||||
PublicKey(body)
|
EncryptingPublicKey(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = id.encodeToBase64Url()
|
override fun toString(): String = id.encodeToBase64Url()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare to another ID which __must have the same [magic]__ number; note that [equals]
|
||||||
|
* works well despite magic inequity.
|
||||||
|
* @throws IncomparableException if magic id do not match
|
||||||
|
*/
|
||||||
override fun compareTo(other: BinaryId): Int {
|
override fun compareTo(other: BinaryId): Int {
|
||||||
if (other.magic != magic) throw IncomparableException("Mask mismatch (my=$magic their=${other.magic})")
|
if (other.magic != magic) throw IncomparableException("Mask mismatch (my=$magic their=${other.magic})")
|
||||||
val id1 = other.id
|
val id1 = other.id
|
||||||
@@ -81,8 +146,9 @@ open class BinaryId protected constructor (
|
|||||||
* Restore a string representation of existing BinaryId.
|
* Restore a string representation of existing BinaryId.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun restoreFromString(str: String): BinaryId =
|
fun restoreFromString(str: String): BinaryId = kotlin.runCatching {
|
||||||
BinaryId(str.decodeBase64Url().toUByteArray())
|
BinaryId(str.decodeBase64Url().toUByteArray())
|
||||||
|
}.getOrElse { throw InvalidException("can't parse binary id: $str", it) }
|
||||||
|
|
||||||
|
|
||||||
fun createFromBytes(magic: Int, bytes: ByteArray): BinaryId = createFromUBytes(magic, bytes.toUByteArray())
|
fun createFromBytes(magic: Int, bytes: ByteArray): BinaryId = createFromUBytes(magic, bytes.toUByteArray())
|
||||||
@@ -101,8 +167,8 @@ open class BinaryId protected constructor (
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun createRandom(magicNumber: Int, size: Int=16) =
|
fun createRandom(magicNumber: Int, size: Int = 16) =
|
||||||
createFromBytes(magicNumber, Random.Default.nextBytes(size-2))
|
createFromBytes(magicNumber, Random.Default.nextBytes(size - 2))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a string as UTF and create a binaryId from its bytes and provided magic.
|
* Encode a string as UTF and create a binaryId from its bytes and provided magic.
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
@@ -20,10 +30,12 @@ import net.sergeych.crypto2.Container.Companion.createWith
|
|||||||
* - [addRecipients] and various [plus] operators to add recipients
|
* - [addRecipients] and various [plus] operators to add recipients
|
||||||
* - [updateData] to change decrypted content for the same recipient keys
|
* - [updateData] to change decrypted content for the same recipient keys
|
||||||
*
|
*
|
||||||
|
* Note that container _is serialized encrypted_.
|
||||||
|
*
|
||||||
* Some rules:
|
* Some rules:
|
||||||
*
|
*
|
||||||
* When adding public key recipient, it is faster to use your known [SecretKey], but you
|
* When adding public key recipient, it is faster to use your known [DecryptingSecretKey], but you
|
||||||
* can stay anonymous by just adding [PublicKey] only.
|
* can stay anonymous by just adding [EncryptingPublicKey] only.
|
||||||
*
|
*
|
||||||
* Put your data in [SealedBox] if you need to authenticate message origin and timestamp, then put
|
* Put your data in [SealedBox] if you need to authenticate message origin and timestamp, then put
|
||||||
* the sealed box in the [Container], this will conceal signers from attack. In the case you need to
|
* the sealed box in the [Container], this will conceal signers from attack. In the case you need to
|
||||||
@@ -105,7 +117,7 @@ sealed class Container {
|
|||||||
* Add e key to the __decrypted__ container. The new container is also decrypted so you can add
|
* Add e key to the __decrypted__ container. The new container is also decrypted so you can add
|
||||||
* more keys, etc.
|
* more keys, etc.
|
||||||
*/
|
*/
|
||||||
operator fun plus(recipient: PublicKey) = addRecipients { key(recipient) }
|
operator fun plus(recipient: EncryptingPublicKey) = addRecipients { key(recipient) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add e key to the __decrypted__ container. The new container is also decrypted so you can add
|
* Add e key to the __decrypted__ container. The new container is also decrypted so you can add
|
||||||
@@ -117,7 +129,7 @@ sealed class Container {
|
|||||||
* Add e key to the __decrypted__ container. The new container is also decrypted so you can add
|
* Add e key to the __decrypted__ container. The new container is also decrypted so you can add
|
||||||
* more keys, etc.
|
* more keys, etc.
|
||||||
*/
|
*/
|
||||||
operator fun plus(pair: Pair<SecretKey, PublicKey>) = addRecipients { key(pair) }
|
operator fun plus(pair: Pair<DecryptingSecretKey, EncryptingPublicKey>) = addRecipients { key(pair) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the data in the decrypted container. It keeps the same set of keys and update
|
* Update the data in the decrypted container. It keeps the same set of keys and update
|
||||||
@@ -126,8 +138,9 @@ sealed class Container {
|
|||||||
abstract fun updateData(newPlainData: UByteArray, randomFill: IntRange? = null): Container
|
abstract fun updateData(newPlainData: UByteArray, randomFill: IntRange? = null): Container
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binary encoded version. It is desirable to include [Container] as an object, though,
|
* Binary encoded _encrypted_ version. It is desirable to include [Container] as an object, though,
|
||||||
* especially when using custom serialization (Json, Boss, etc), it is serializable.
|
* especially when using custom serialization (Json, Boss, etc.), it is serializable. Note that
|
||||||
|
* serialized data is always encrypted.
|
||||||
* Still, if you need it in binary form, this is a shortcut. You can use [decode] or call
|
* Still, if you need it in binary form, this is a shortcut. You can use [decode] or call
|
||||||
* [BipackDecoder.decode] to deserialize the binary form.
|
* [BipackDecoder.decode] to deserialize the binary form.
|
||||||
*/
|
*/
|
||||||
@@ -142,16 +155,21 @@ sealed class Container {
|
|||||||
abstract val decryptedWithKeyId: KeyId?
|
abstract val decryptedWithKeyId: KeyId?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the container _is decrypted by the [PublicKey]_, e.g., using secret key encryption,
|
* If the container _is decrypted by the [EncryptingPublicKey]_, e.g., using secret key encryption,
|
||||||
* contains the [PublicKey] that corresponds the [SecretKey] used while encrypting, this
|
* contains the [EncryptingPublicKey] that corresponds the [DecryptingSecretKey] used while encrypting, this
|
||||||
* authenticating the sender party cryptographically. This key could be used to encrypt
|
* authenticating the sender party cryptographically. This key could be used to encrypt
|
||||||
* the response to be visible to the sender only; the sender, providing it kept his secret key,
|
* the response to be visible to the sender only; the sender, providing it kept his secret key,
|
||||||
* could decrypt it.
|
* could decrypt it.
|
||||||
*/
|
*/
|
||||||
@Transient
|
@Transient
|
||||||
var authorisedByKey: PublicKey? = null
|
var authorisedByKey: EncryptingPublicKey? = null
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of [KeyId] of the keys that unlocked the container, in the same order used for encryption..
|
||||||
|
*/
|
||||||
|
abstract val keyIds: List<KeyId>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @suppress
|
* @suppress
|
||||||
@@ -172,6 +190,8 @@ sealed class Container {
|
|||||||
override val decryptedWithKeyId: KeyId?
|
override val decryptedWithKeyId: KeyId?
|
||||||
get() = decryptedWithKey?.id
|
get() = decryptedWithKey?.id
|
||||||
|
|
||||||
|
override val keyIds: List<KeyId> = listOf(keyId)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
decryptedData = creationData
|
decryptedData = creationData
|
||||||
}
|
}
|
||||||
@@ -183,7 +203,7 @@ sealed class Container {
|
|||||||
kotlin.runCatching { k.decrypt(encryptedMessage) }.getOrNull()?.let {
|
kotlin.runCatching { k.decrypt(encryptedMessage) }.getOrNull()?.let {
|
||||||
decryptedData = it
|
decryptedData = it
|
||||||
decryptedWithKey = k
|
decryptedWithKey = k
|
||||||
if( k is SecretKey) {
|
if(k is DecryptingSecretKey) {
|
||||||
authorisedByKey = Asymmetric.Message.decode(encryptedMessage).senderPublicKey
|
authorisedByKey = Asymmetric.Message.decode(encryptedMessage).senderPublicKey
|
||||||
}
|
}
|
||||||
return it
|
return it
|
||||||
@@ -206,7 +226,7 @@ sealed class Container {
|
|||||||
// otherwise, we don't know the encryption key and will try to derive it
|
// otherwise, we don't know the encryption key and will try to derive it
|
||||||
// from the decryption key:
|
// from the decryption key:
|
||||||
when (val k = decryptedWithKey!!) {
|
when (val k = decryptedWithKey!!) {
|
||||||
is SecretKey -> {
|
is DecryptingSecretKey -> {
|
||||||
key(k.publicKey)
|
key(k.publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,12 +269,12 @@ sealed class Container {
|
|||||||
constructor(key: EncryptingKey, encodeMainKey: UByteArray) :
|
constructor(key: EncryptingKey, encodeMainKey: UByteArray) :
|
||||||
this(key.id, key.encrypt(encodeMainKey))
|
this(key.id, key.encrypt(encodeMainKey))
|
||||||
|
|
||||||
constructor(sender: SecretKey?, recipient: PublicKey, encodeMainKey: UByteArray) :
|
constructor(sender: DecryptingSecretKey?, recipient: EncryptingPublicKey, encodeMainKey: UByteArray) :
|
||||||
this(
|
this(
|
||||||
recipient.id,
|
recipient.id,
|
||||||
recipient.encryptMessage(
|
recipient.encryptMessage(
|
||||||
encodeMainKey,
|
encodeMainKey,
|
||||||
senderKey = sender ?: SecretKey.new(),
|
senderKey = sender ?: DecryptingSecretKey.new(),
|
||||||
).encoded
|
).encoded
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -267,6 +287,8 @@ sealed class Container {
|
|||||||
override var decryptedWithKeyId: KeyId? = null
|
override var decryptedWithKeyId: KeyId? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
override val keyIds: List<KeyId> = encryptedKeys.map { it.tag }
|
||||||
|
|
||||||
override fun decryptWith(keyRing: UniversalRing): UByteArray? {
|
override fun decryptWith(keyRing: UniversalRing): UByteArray? {
|
||||||
decryptedData?.let { return it }
|
decryptedData?.let { return it }
|
||||||
for (key in keyRing.decryptingKeys) {
|
for (key in keyRing.decryptingKeys) {
|
||||||
@@ -282,7 +304,7 @@ sealed class Container {
|
|||||||
throw InvalidContainerException()
|
throw InvalidContainerException()
|
||||||
decryptedWithKeyId = key.id
|
decryptedWithKeyId = key.id
|
||||||
mainKey = k
|
mainKey = k
|
||||||
if( key is SecretKey) {
|
if(key is DecryptingSecretKey) {
|
||||||
authorisedByKey = Asymmetric.Message.decode(encryptedKey.cipherData).senderPublicKey
|
authorisedByKey = Asymmetric.Message.decode(encryptedKey.cipherData).senderPublicKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,6 +321,27 @@ sealed class Container {
|
|||||||
}.build()
|
}.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 {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,8 +389,8 @@ sealed class Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add one or more [SecretKey] as sender authority coupled with [PublicKey] as
|
* Add one or more [DecryptingSecretKey] as sender authority coupled with [EncryptingPublicKey] as
|
||||||
* a recipient. This is faster than anonymous usage of [PublicKey] only
|
* a recipient. This is faster than anonymous usage of [EncryptingPublicKey] only
|
||||||
*/
|
*/
|
||||||
fun key(vararg pairs: AsymmetricEncryptionPair) {
|
fun key(vararg pairs: AsymmetricEncryptionPair) {
|
||||||
keyPairs.addAll(pairs)
|
keyPairs.addAll(pairs)
|
||||||
@@ -356,7 +399,7 @@ sealed class Container {
|
|||||||
/**
|
/**
|
||||||
* Add one or more public keys as recipients. This is slower than using pairs of sender -> recipient.
|
* Add one or more public keys as recipients. This is slower than using pairs of sender -> recipient.
|
||||||
*/
|
*/
|
||||||
fun key(vararg publicKeys: PublicKey) {
|
fun key(vararg publicKeys: EncryptingPublicKey) {
|
||||||
keyPairs.addAll(publicKeys.map { null to it })
|
keyPairs.addAll(publicKeys.map { null to it })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +460,7 @@ sealed class Container {
|
|||||||
Single(
|
Single(
|
||||||
pk.id, pk.encryptMessage(
|
pk.id, pk.encryptMessage(
|
||||||
plainData,
|
plainData,
|
||||||
senderKey = sk ?: SecretKey.new(),
|
senderKey = sk ?: DecryptingSecretKey.new(),
|
||||||
randomFill = fillRange
|
randomFill = fillRange
|
||||||
).encoded,
|
).encoded,
|
||||||
plainData,
|
plainData,
|
||||||
@@ -474,6 +517,12 @@ sealed class Container {
|
|||||||
inline fun <reified T> decrypt(cipherData: UByteArray, vararg keys: DecryptingKey): T? =
|
inline fun <reified T> decrypt(cipherData: UByteArray, vararg keys: DecryptingKey): T? =
|
||||||
decryptAsUBytes(cipherData, *keys)?.let { BipackDecoder.decode<T>(it.asByteArray()) }
|
decryptAsUBytes(cipherData, *keys)?.let { BipackDecoder.decode<T>(it.asByteArray()) }
|
||||||
|
|
||||||
|
inline fun <reified T> decrypt(cipherData: UByteArray, ring: UniversalRing): T? =
|
||||||
|
decode(cipherData)
|
||||||
|
.decryptWith(ring)?.let {
|
||||||
|
BipackDecoder.decode<T>(it.asByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
fun decryptAsUBytes(cipherData: UByteArray, vararg keys: DecryptingKey): UByteArray? =
|
fun decryptAsUBytes(cipherData: UByteArray, vararg keys: DecryptingKey): UByteArray? =
|
||||||
decode(cipherData).decryptWith(*keys)
|
decode(cipherData).decryptWith(*keys)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import net.sergeych.bintools.CRC
|
import net.sergeych.bintools.CRC
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
||||||
@@ -8,7 +18,7 @@ import net.sergeych.crypto2.SymmetricKey.WithNonce
|
|||||||
/**
|
/**
|
||||||
* Some key able to perform decrypting. It is not serializable by purpose, as not all such
|
* Some key able to perform decrypting. It is not serializable by purpose, as not all such
|
||||||
* keys are wise to transfer/save. Concrete implementations are, like [SymmetricKey] or
|
* keys are wise to transfer/save. Concrete implementations are, like [SymmetricKey] or
|
||||||
* [SecretKey].
|
* [DecryptingSecretKey].
|
||||||
*/
|
*/
|
||||||
interface DecryptingKey : NonceBased, KeyInstance {
|
interface DecryptingKey : NonceBased, KeyInstance {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.generichash.GenericHash
|
import com.ionspin.kotlin.crypto.generichash.GenericHash
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import kotlinx.coroutines.channels.ReceiveChannel
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
import kotlinx.coroutines.flow.Flow
|
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_256
|
||||||
import org.kotlincrypto.hash.sha3.SHA3_384
|
import org.kotlincrypto.hash.sha3.SHA3_384
|
||||||
|
|
||||||
private interface StreamProcessor {
|
interface StreamProcessor {
|
||||||
fun update(data: UByteArray)
|
fun update(data: UByteArray)
|
||||||
fun final(): UByteArray
|
fun final(): UByteArray
|
||||||
}
|
}
|
||||||
@@ -23,7 +35,7 @@ private interface StreamProcessor {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
enum class Hash(
|
enum class Hash(
|
||||||
private val direct: ((UByteArray) -> UByteArray)? = null,
|
private val direct: ((UByteArray) -> UByteArray)? = null,
|
||||||
private val streamProcessor: () -> StreamProcessor,
|
val streamProcessor: () -> StreamProcessor,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Blake2b(
|
Blake2b(
|
||||||
@@ -112,6 +124,33 @@ enum class Hash(
|
|||||||
return sp.final()
|
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()
|
private val defaultSuffix1 = "All lay loads on a willing horse".encodeToUByteArray()
|
||||||
@@ -122,6 +161,8 @@ private val defaultSuffix2 = "A stitch in time saves nine".encodeToUByteArray()
|
|||||||
*/
|
*/
|
||||||
fun blake2b(src: UByteArray): UByteArray = Hash.Blake2b.digest(src)
|
fun blake2b(src: UByteArray): UByteArray = Hash.Blake2b.digest(src)
|
||||||
|
|
||||||
|
fun blake2b(src: ByteChunk): ByteChunk = blake2b(src.data).asChunk()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Double linked Blake2b using the default or specified suffix. This should be more hard to
|
* Double linked Blake2b using the default or specified suffix. This should be more hard to
|
||||||
* brute force.collision attack than just [blake2b]. Note that different suffixes provide different
|
* brute force.collision attack than just [blake2b]. Note that different suffixes provide different
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.LibsodiumInitializer
|
import com.ionspin.kotlin.crypto.LibsodiumInitializer
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -10,7 +20,7 @@ import kotlinx.serialization.Serializable
|
|||||||
* Important. `KeyId` of matching keys are the same, so you can use it to identify
|
* Important. `KeyId` of matching keys are the same, so you can use it to identify
|
||||||
* and find matching keys in the [UniversalRing], etc. For example:
|
* and find matching keys in the [UniversalRing], etc. For example:
|
||||||
*
|
*
|
||||||
* - [SecretKey] and [PublicKey] from the same pair have the same `KeyId`, thus the former
|
* - [DecryptingSecretKey] and [EncryptingPublicKey] from the same pair have the same `KeyId`, thus the former
|
||||||
* can decrypt what was encrypted with the latter.
|
* can decrypt what was encrypted with the latter.
|
||||||
*
|
*
|
||||||
* - [SigningSecretKey] and corresponding [VerifyingKey] have the same `KeyId`. Use it to pick a proper key for
|
* - [SigningSecretKey] and corresponding [VerifyingKey] have the same `KeyId`. Use it to pick a proper key for
|
||||||
@@ -18,6 +28,9 @@ import kotlinx.serialization.Serializable
|
|||||||
*
|
*
|
||||||
* See [PBKD.Params.deriveKey] for deriving keys from id.
|
* See [PBKD.Params.deriveKey] for deriving keys from id.
|
||||||
*
|
*
|
||||||
|
* See [id], and [BinaryId] class for more. Note that for [EncryptingPublicKey] and [VerifyingPublicKey] [BinaryId.asPublicKey]
|
||||||
|
* and [BinaryId.asVerifyingKey] restore actual keys, providing [BinaryId.magic] has proper value, see [KeysmagicNumber]]
|
||||||
|
*
|
||||||
* @param id actual id used in equality test amd hash code generation. `Id` of the matching keys is the same.
|
* @param id actual id used in equality test amd hash code generation. `Id` of the matching keys is the same.
|
||||||
* @param kdp optional key derivation parameters. Does not affect equality. Allow deriving the key from proper
|
* @param kdp optional key derivation parameters. Does not affect equality. Allow deriving the key from proper
|
||||||
* password, see above.
|
* password, see above.
|
||||||
@@ -28,6 +41,8 @@ data class KeyId(val id: BinaryId, val kdp: PBKD.Params? = null) {
|
|||||||
/**
|
/**
|
||||||
* Binary array representation of the [id], not including the [kdp]. Used in [SafeKeyExchange]
|
* Binary array representation of the [id], not including the [kdp]. Used in [SafeKeyExchange]
|
||||||
* and other key exchanges to generate session tokens, etc.
|
* and other key exchanges to generate session tokens, etc.
|
||||||
|
*
|
||||||
|
* In shortcut for packed [BinaryId], from [id]. If you need only key bytes, use [UniversalKey.keyBytes].
|
||||||
*/
|
*/
|
||||||
val binaryTag: UByteArray by lazy { id.id }
|
val binaryTag: UByteArray by lazy { id.id }
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
enum class KeysmagicNumber(val label: String) {
|
enum class KeysmagicNumber(val label: String) {
|
||||||
@@ -6,6 +16,11 @@ enum class KeysmagicNumber(val label: String) {
|
|||||||
defaultSymmetric( "sym"),
|
defaultSymmetric( "sym"),
|
||||||
defaultSession( "ssn"),
|
defaultSession( "ssn"),
|
||||||
defaultVerifying( "ver"),
|
defaultVerifying( "ver"),
|
||||||
|
|
||||||
|
defaultSigningSecret( "sig"),
|
||||||
|
|
||||||
|
defaultUniversalPublic( "pub+"),
|
||||||
|
defaultUniversalPrivate( "prv+"),
|
||||||
;
|
;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* 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.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.sergeych.bipack.Unsigned
|
||||||
|
import net.sergeych.crypto2.Multikey.AnyKey
|
||||||
|
import net.sergeych.crypto2.Multikey.Companion.allOf
|
||||||
|
import net.sergeych.crypto2.Multikey.Companion.allOfMultikeys
|
||||||
|
import net.sergeych.crypto2.Multikey.Companion.anyOf
|
||||||
|
import net.sergeych.crypto2.Multikey.Companion.anyOfMultikeys
|
||||||
|
import net.sergeych.crypto2.Multikey.Companion.invoke
|
||||||
|
import net.sergeych.crypto2.Multikey.Companion.someOf
|
||||||
|
import net.sergeych.crypto2.Multikey.Companion.someOfMultikeys
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-signed key.
|
||||||
|
* An arbitrary combination of [VerifyingPublicKey] to implement any multiple keys scenario, like N of M,
|
||||||
|
* and logical expression. Sample usage:
|
||||||
|
*
|
||||||
|
* ```kotlin
|
||||||
|
* val k1 = SigningSecretKey.new().verifyingKey
|
||||||
|
* val k2 = SigningSecretKey.new().verifyingKey
|
||||||
|
* val k3 = SigningSecretKey.new().verifyingKey
|
||||||
|
* val k4 = SigningSecretKey.new().verifyingKey
|
||||||
|
*
|
||||||
|
* val multikey = (k1 or k2) and (k3 or k4)
|
||||||
|
*
|
||||||
|
* val b: SealedBox = SealedBox.decode(someData)
|
||||||
|
*
|
||||||
|
* if( b.isSealedBy(multikey) ) {
|
||||||
|
* println("sealed box is properly sealed by a multikey")
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* To build multikeys, use `and` and `or` infix operators against [VerifyingPublicKey], [Multikey], or even
|
||||||
|
* [SigningSecretKey] instances, and shortcut methods:
|
||||||
|
*
|
||||||
|
* - [someOfMultikeys], [someOf] family for `n of M` logic
|
||||||
|
* - [anyOfMultikeys], [anyOf], [allOf], and [allOfMultikeys]
|
||||||
|
* - [invoke] for a single-key multikey
|
||||||
|
* - [AnyKey] when you need effectively match any key, useful when you need a `var` `Multikey`.
|
||||||
|
*
|
||||||
|
* __Important__. When serializing, always serialize as root [Multikey] instance to keep
|
||||||
|
* it compatible with any combination.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
sealed class Multikey {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the [keys] satisfy the condition of this instance
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
fun check(vararg verifyingKeys: VerifyingPublicKey): Boolean = check(verifyingKeys.asIterable())
|
||||||
|
|
||||||
|
infix fun or(mk: Multikey): Multikey = SomeOf(1, listOf(this, mk))
|
||||||
|
|
||||||
|
infix fun or(k: VerifyingPublicKey) = SomeOf(1, listOf(this, Multikey(k)))
|
||||||
|
|
||||||
|
infix fun or(k: SigningSecretKey) = SomeOf(1, listOf(this, Multikey(k.verifyingKey)))
|
||||||
|
|
||||||
|
infix fun and(mk: Multikey): Multikey = SomeOf(2, listOf(this, mk))
|
||||||
|
|
||||||
|
infix fun and(k: VerifyingPublicKey) = SomeOf(2, listOf(this, Multikey(k)))
|
||||||
|
|
||||||
|
infix fun and(k: SigningSecretKey) = SomeOf(2, listOf(this, Multikey(k.verifyingKey)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multikey instance implementing `m of N` logic against [VerifyingPublicKey] set. Do not use
|
||||||
|
* it directly, use any [Multikey.someOfMultikeys] functions instead.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@SerialName("k")
|
||||||
|
class Keys internal constructor(
|
||||||
|
@Unsigned
|
||||||
|
val requiredMinimum: Int,
|
||||||
|
val validKeys: Set<VerifyingPublicKey>,
|
||||||
|
) : Multikey() {
|
||||||
|
override fun check(keys: Iterable<VerifyingPublicKey>): Boolean {
|
||||||
|
var matches = 0
|
||||||
|
for (signer in keys) {
|
||||||
|
if (signer in validKeys) {
|
||||||
|
if (++matches >= requiredMinimum) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mentionedKeys(): Set<VerifyingPublicKey> {
|
||||||
|
return validKeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multikey instance implementing `m of N` logic against other [Multikey] instances. Do not use
|
||||||
|
* it directly, use any [Multikey.someOfMultikeys] functions instead.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@SerialName("n")
|
||||||
|
class SomeOf internal constructor(
|
||||||
|
@Unsigned
|
||||||
|
val requiredMinimum: Int,
|
||||||
|
val validKeys: List<Multikey>,
|
||||||
|
) : Multikey() {
|
||||||
|
override fun check(keys: Iterable<VerifyingPublicKey>): Boolean {
|
||||||
|
var matches = 0
|
||||||
|
for (k in validKeys) {
|
||||||
|
if (k.check(keys)) {
|
||||||
|
if (++matches >= requiredMinimum) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mentionedKeys(): Set<VerifyingPublicKey> {
|
||||||
|
return validKeys.flatMap { it.mentionedKeys() }.toSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special `AnyKey`: no restrictions, any key will satisfy this. In the rare case to mark
|
||||||
|
* publicly available operations, etc. Please note it is an object, not a class, and can't
|
||||||
|
* be instantiated.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
object AnyKey : Multikey() {
|
||||||
|
override fun check(keys: Iterable<VerifyingPublicKey>): Boolean = true
|
||||||
|
override fun mentionedKeys(): Set<VerifyingPublicKey> = emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
operator fun invoke(k: SigningSecretKey): Multikey = Keys(1, setOf(k.verifyingKey))
|
||||||
|
operator fun invoke(k: VerifyingPublicKey): Multikey = Keys(1, setOf(k))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires some keys from a list
|
||||||
|
*/
|
||||||
|
fun someOf(requiredMinimum: Int, vararg keys: VerifyingPublicKey): Multikey =
|
||||||
|
Keys(requiredMinimum, keys.toSet())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires some keys from a list
|
||||||
|
*/
|
||||||
|
fun someOfMultikeys(requiredMinimum: Int, vararg keys: Multikey): Multikey =
|
||||||
|
SomeOf(requiredMinimum, keys.toList())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires some keys from a list
|
||||||
|
*/
|
||||||
|
fun someOfMultikeys(requiredMinimum: Int, keys: List<Multikey>): Multikey =
|
||||||
|
SomeOf(requiredMinimum, keys)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires some keys from a list
|
||||||
|
*/
|
||||||
|
fun someOf(requiredMinimum: Int, keys: List<VerifyingPublicKey>): Multikey =
|
||||||
|
Keys(requiredMinimum, keys.toSet())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires any key from a list
|
||||||
|
*/
|
||||||
|
fun anyOf(vararg keys: VerifyingPublicKey): Multikey = someOf(1, *keys)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires any key from a list
|
||||||
|
*/
|
||||||
|
fun anyOfMultikeys(vararg keys: Multikey): Multikey = someOfMultikeys(1, *keys)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires any key from a list
|
||||||
|
*/
|
||||||
|
fun anyOfMultikeys(keys: List<Multikey>): Multikey = someOfMultikeys(1, keys)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires any key from a list
|
||||||
|
*/
|
||||||
|
fun anyOf(keys: List<VerifyingPublicKey>): Multikey = someOf(1, keys)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires all keys from a list
|
||||||
|
*/
|
||||||
|
fun allOf(vararg keys: VerifyingPublicKey): Multikey = someOf(keys.size, *keys)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires all keys from a list
|
||||||
|
*/
|
||||||
|
fun allOfMultikeys(vararg keys: Multikey): Multikey = someOfMultikeys(keys.size, *keys)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires all keys from a list
|
||||||
|
*/
|
||||||
|
fun allOfMultikeys(keys: List<Multikey>): Multikey = someOfMultikeys(keys.size, keys)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multikey instance that requires all keys from a list
|
||||||
|
*/
|
||||||
|
fun allOf(keys: List<VerifyingPublicKey>): Multikey = someOf(keys.size, keys)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
interface NonceBased {
|
interface NonceBased {
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
@@ -114,7 +124,7 @@ object PBKD {
|
|||||||
}
|
}
|
||||||
return entry.op {
|
return entry.op {
|
||||||
if (entry.data == null) {
|
if (entry.data == null) {
|
||||||
entry.data = entry.kdf.derive(password)
|
entry.data = entry.kdf.deriveKey(password)
|
||||||
}
|
}
|
||||||
entry.data!!
|
entry.data!!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
import net.sergeych.bipack.decodeFromBipack
|
||||||
|
import net.sergeych.crypto2.VerifyingPublicKey.Companion.toString
|
||||||
|
import net.sergeych.mp_tools.decodeBase64Url
|
||||||
|
|
||||||
|
@Deprecated("Use EncryptingPublicKey",ReplaceWith("EncryptingPublicKey"))
|
||||||
|
typealias PublicKey = EncryptingPublicKey
|
||||||
/**
|
/**
|
||||||
* The public for public-key encryption. It encrypts messages that can only be decrypted with corresponding
|
* The public for public-key encryption. It encrypts messages that can only be decrypted with corresponding
|
||||||
* [SecretKey].
|
* [DecryptingSecretKey].
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("encp")
|
@SerialName("encp")
|
||||||
class PublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingKey {
|
class EncryptingPublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingKey {
|
||||||
|
|
||||||
override val magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric
|
override val magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric
|
||||||
@Transient
|
@Transient
|
||||||
override val label: String = "pub"
|
override val label: String = "pub"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an anonymous message that could be decrypted only with the [SecretKey] that corresponds this.
|
* Create an anonymous message that could be decrypted only with the [DecryptingSecretKey] that corresponds this.
|
||||||
* Anonymous message uses one-time secret key, the public part of which is included into the
|
* Anonymous message uses one-time secret key, the public part of which is included into the
|
||||||
* [Asymmetric.Message], so the sender could not be identified.
|
* [Asymmetric.Message], so the sender could not be identified.
|
||||||
*
|
*
|
||||||
@@ -54,21 +69,57 @@ class PublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingK
|
|||||||
fun encryptMessage(
|
fun encryptMessage(
|
||||||
plainData: UByteArray,
|
plainData: UByteArray,
|
||||||
nonce: UByteArray = randomNonce(),
|
nonce: UByteArray = randomNonce(),
|
||||||
senderKey: SecretKey = newSecretKey(),
|
senderKey: DecryptingSecretKey = newSecretKey(),
|
||||||
randomFill: IntRange? = null,
|
randomFill: IntRange? = null,
|
||||||
) = Asymmetric.createMessage(senderKey, this, WithFill.encode(plainData, randomFill), nonce)
|
) = Asymmetric.createMessage(senderKey, this, WithFill.encode(plainData, randomFill), nonce)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt message using the specified secret key as sender authentication. Recipient, the party having
|
* Encrypt message using the specified secret key as sender authentication. Recipient, the party having
|
||||||
* [SecretKey] corresponding to this one, will be able to decrypt the message and be sure that [senderKey]
|
* [DecryptingSecretKey] corresponding to this one, will be able to decrypt the message and be sure that [senderKey]
|
||||||
* was the author and the message was not altered.
|
* was the author and the message was not altered.
|
||||||
*/
|
*/
|
||||||
fun encryptMessage(
|
fun encryptMessage(
|
||||||
plainData: UByteArray,
|
plainData: UByteArray,
|
||||||
senderKey: SecretKey,
|
senderKey: DecryptingSecretKey,
|
||||||
randomFill: IntRange? = null,
|
randomFill: IntRange? = null,
|
||||||
): Asymmetric.Message =
|
): Asymmetric.Message =
|
||||||
Asymmetric.createMessage(senderKey, this, WithFill.encode(plainData, randomFill))
|
Asymmetric.createMessage(senderKey, this, WithFill.encode(plainData, randomFill))
|
||||||
|
|
||||||
override val id by lazy { KeyId(magic, keyBytes, null, true) }
|
override val id by lazy { KeyId(magic, keyBytes, null, true) }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Parse any known public key text representation, including what [toString] return (for public keys it is
|
||||||
|
* possible)
|
||||||
|
* @throws IllegalArgumentException the public key isn't recognized
|
||||||
|
*/
|
||||||
|
fun parse(text: String): EncryptingPublicKey {
|
||||||
|
val s = text.trim()
|
||||||
|
|
||||||
|
fun parseId(t: String): EncryptingPublicKey {
|
||||||
|
val id = BinaryId.restoreFromString(t)
|
||||||
|
if (id.magic != KeysmagicNumber.defaultAssymmetric.ordinal)
|
||||||
|
throw IllegalArgumentException("invalid magick ${id.magic} for PublicKey")
|
||||||
|
return id.asPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🗝sig#I1po9Y2I7p2aOxeh4nFyGPm3e0YunBEu1Mo-PmIqP84Evg
|
||||||
|
return when {
|
||||||
|
s.startsWith("\uD83D\uDDDDpub#") -> parseId(s.drop(6))
|
||||||
|
s.startsWith("pub#") -> parseId(s.drop(4))
|
||||||
|
s.startsWith("#") -> parseId(s.drop(1))
|
||||||
|
else -> {
|
||||||
|
// consider it is serialized key in base64 format
|
||||||
|
val data = s.decodeBase64Url().asUByteArray()
|
||||||
|
if (data.size == 32)
|
||||||
|
EncryptingPublicKey(data)
|
||||||
|
else {
|
||||||
|
runCatching { data.decodeFromBipack<EncryptingPublicKey>() }.getOrNull()
|
||||||
|
?: kotlin.runCatching { data.decodeFromBipack<UniversalKey>() as EncryptingPublicKey }
|
||||||
|
.getOrElse { throw IllegalArgumentException("can't parse verifying key") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
|
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
|
||||||
@@ -10,7 +20,7 @@ import net.sergeych.crypto2.SafeKeyExchange.SessionKey
|
|||||||
* Usage:
|
* Usage:
|
||||||
*
|
*
|
||||||
* 1. Create [SafeKeyExchange] on both server and client sides
|
* 1. Create [SafeKeyExchange] on both server and client sides
|
||||||
* 2. Exchange [publicKey] instances
|
* 2. Exchange [EncryptingPublicKey] instances
|
||||||
* 3. Create [serverSessionKey] and [clientSessionKey] respectively
|
* 3. Create [serverSessionKey] and [clientSessionKey] respectively
|
||||||
* 4. Use [SessionKey.sendingKey] and [SessionKey.receivingKey] to send and receive encrypted data.
|
* 4. Use [SessionKey.sendingKey] and [SessionKey.receivingKey] to send and receive encrypted data.
|
||||||
*
|
*
|
||||||
@@ -20,7 +30,7 @@ import net.sergeych.crypto2.SafeKeyExchange.SessionKey
|
|||||||
* instances as often as performance considerations allow.
|
* instances as often as performance considerations allow.
|
||||||
* - while it is possible to generate several keys "ahead", the care should be taken when storing them,
|
* - while it is possible to generate several keys "ahead", the care should be taken when storing them,
|
||||||
* encrypt it with some other key to maintain safety.
|
* encrypt it with some other key to maintain safety.
|
||||||
* - do not use [publicKey] for anything but creating session keys.
|
* - do not use [EncryptingPublicKey] for anything but creating session keys.
|
||||||
*/
|
*/
|
||||||
class SafeKeyExchange {
|
class SafeKeyExchange {
|
||||||
private val pair = KeyExchange.keypair()
|
private val pair = KeyExchange.keypair()
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
import kotlin.time.Instant
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import net.sergeych.bipack.BipackEncoder
|
import net.sergeych.bipack.BipackEncoder
|
||||||
import net.sergeych.bipack.decodeFromBipack
|
import net.sergeych.bipack.decodeFromBipack
|
||||||
import net.sergeych.crypto2.Seal.Companion.create
|
import net.sergeych.crypto2.Seal.Companion.create
|
||||||
import net.sergeych.utools.now
|
import net.sergeych.utools.now
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended public-key signature.
|
* Extended public-key signature.
|
||||||
@@ -67,7 +78,7 @@ class Seal(
|
|||||||
*/
|
*/
|
||||||
fun verify(message: UByteArray) {
|
fun verify(message: UByteArray) {
|
||||||
val n = now()
|
val n = now()
|
||||||
if (createdAt > n) throw IllegalSignatureException("signature's timestamp in the future")
|
if (createdAt - 45.seconds > n) throw IllegalSignatureException("signature's timestamp in the future: $createdAt / $n")
|
||||||
expiresAt?.let {
|
expiresAt?.let {
|
||||||
if (n >= it) throw ExpiredSignatureException("signature expired at $it")
|
if (n >= it) throw ExpiredSignatureException("signature expired at $it")
|
||||||
}
|
}
|
||||||
@@ -98,7 +109,7 @@ class Seal(
|
|||||||
* to check the authenticity of the arbitrary [message] using a public key, [VerifyingPublicKey]
|
* to check the authenticity of the arbitrary [message] using a public key, [VerifyingPublicKey]
|
||||||
* instance, using public-key signing algorithms.
|
* instance, using public-key signing algorithms.
|
||||||
*
|
*
|
||||||
* Unlike a regular binary signature, Seal contains the signer's [publicKey], and also
|
* Unlike a regular binary signature, Seal contains the signer's [EncryptingPublicKey], and also
|
||||||
* [createdAt] and [expiresAt] fields which are also signed and are guaranteed to be non-tampered
|
* [createdAt] and [expiresAt] fields which are also signed and are guaranteed to be non-tampered
|
||||||
* if the [isValid] returns true (or [verify] does not throw). See [isExpired].
|
* if the [isValid] returns true (or [verify] does not throw). See [isExpired].
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
import kotlin.time.Instant
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
import net.sergeych.bipack.BipackEncoder
|
import net.sergeych.bipack.BipackEncoder
|
||||||
import net.sergeych.bipack.decodeFromBipack
|
import net.sergeych.bipack.decodeFromBipack
|
||||||
|
import net.sergeych.utools.pack
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multi-signed data box. Do not use the constructori directly, use [SealedBox.create]
|
* Multi-signed data box. Do not use the constructori directly, use [SealedBox.create]
|
||||||
@@ -27,11 +38,22 @@ import net.sergeych.bipack.decodeFromBipack
|
|||||||
@Serializable
|
@Serializable
|
||||||
class SealedBox(
|
class SealedBox(
|
||||||
val message: UByteArray,
|
val message: UByteArray,
|
||||||
private val seals: List<Seal>,
|
/**
|
||||||
|
* [Seal] instance representing _correct signatures_ of this box. Note that if the box
|
||||||
|
* is constructed (deserialized, etc) successfully, all seals are ok. Initial check
|
||||||
|
* of signatures could be bypassed by setting [checkOnInit] to false, which should
|
||||||
|
* be avoided.
|
||||||
|
*/
|
||||||
|
val seals: List<Seal>,
|
||||||
@Transient
|
@Transient
|
||||||
private val checkOnInit: Boolean = true
|
private val checkOnInit: Boolean = true
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract [VerifyingPublicKey] from [seals].
|
||||||
|
*/
|
||||||
|
val signedByKeys: List<VerifyingPublicKey> by lazy { seals.map { it.publicKey } }
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
constructor(message: UByteArray, vararg keys: SigningKey) :
|
constructor(message: UByteArray, vararg keys: SigningKey) :
|
||||||
this(message, keys.map { it.seal(message) } )
|
this(message, keys.map { it.seal(message) } )
|
||||||
@@ -49,7 +71,7 @@ class SealedBox(
|
|||||||
* Add expiring seal, otherwise use [plus]. Overrides exising seal for [key]
|
* Add expiring seal, otherwise use [plus]. Overrides exising seal for [key]
|
||||||
* if present:
|
* if present:
|
||||||
*/
|
*/
|
||||||
fun addSeal(key: SigningKey, expiresAt: Instant): SealedBox {
|
fun addSeal(key: SigningKey, expiresAt: Instant?): SealedBox {
|
||||||
val filtered = seals.filter { it.publicKey != key.verifyingKey }
|
val filtered = seals.filter { it.publicKey != key.verifyingKey }
|
||||||
return SealedBox(message, filtered + key.seal(message, expiresAt), false)
|
return SealedBox(message, filtered + key.seal(message, expiresAt), false)
|
||||||
}
|
}
|
||||||
@@ -61,6 +83,18 @@ class SealedBox(
|
|||||||
return seals.any { it.publicKey == publicKey }
|
return seals.any { it.publicKey == publicKey }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the box is signed by enough keys to satisfy the given [Multikey].
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
fun isSealedBy(multikey: Multikey) = multikey.check(signedByKeys)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack bipack-encoded payload
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
inline fun <reified T>unpack(): T = BipackDecoder.decode(message)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (seals.isEmpty()) throw IllegalArgumentException("there should be at least one seal")
|
if (seals.isEmpty()) throw IllegalArgumentException("there should be at least one seal")
|
||||||
if (checkOnInit) {
|
if (checkOnInit) {
|
||||||
@@ -85,6 +119,19 @@ class SealedBox(
|
|||||||
return SealedBox(data, keys.map { it.seal(data) }, false)
|
return SealedBox(data, keys.map { it.seal(data) }, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance serializing given data with Bipack and some
|
||||||
|
* keys. At least one key is required to disallow providing not-signed
|
||||||
|
* instances, e.g. [SealedBox] is guaranteed to be properly sealed when
|
||||||
|
* successfully instantiated.
|
||||||
|
*
|
||||||
|
* @param payload an object to serialize and sign
|
||||||
|
* @param keys a list of keys to sign with, should be at least one key.
|
||||||
|
* @throws IllegalArgumentException if keys are not specified.
|
||||||
|
*/
|
||||||
|
inline fun <reified T>new(payload: T,vararg keys: SigningKey): SealedBox =
|
||||||
|
create(pack(payload), *keys)
|
||||||
|
|
||||||
inline fun <reified T>encode(value: T, vararg keys: SigningKey): UByteArray =
|
inline fun <reified T>encode(value: T, vararg keys: SigningKey): UByteArray =
|
||||||
create(BipackEncoder.encode(value).toUByteArray(), *keys).encoded
|
create(BipackEncoder.encode(value).toUByteArray(), *keys).encoded
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.box.Box
|
import com.ionspin.kotlin.crypto.box.Box
|
||||||
@@ -6,24 +16,28 @@ import kotlinx.serialization.SerialName
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
|
@Deprecated("Use DecryptingSecretKey instead",ReplaceWith("DecryptingSecretKey"))
|
||||||
|
typealias SecretKey = DecryptingSecretKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The secret key used in public-key encryption; it is used to _decrypt_ data encrypted with its
|
* The secret key used in public-key encryption; it is used to _decrypt_ data encrypted with its
|
||||||
* public counterpart, see [publicKey].
|
* public counterpart, see [EncryptingPublicKey].
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("encs")
|
@SerialName("encs")
|
||||||
class SecretKey(
|
class DecryptingSecretKey(
|
||||||
override val keyBytes: UByteArray,
|
override val keyBytes: UByteArray,
|
||||||
@Transient
|
@Transient
|
||||||
val _cachedPublicKey: PublicKey? = null,
|
val _cachedPublicKey: EncryptingPublicKey? = null,
|
||||||
) : DecryptingKey, UniversalKey() {
|
) : DecryptingKey, UniversalKey() {
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
override val label: String = "sec"
|
override val label: String = "sec"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt with authentication checks the message which must have [Asymmetric.Message.senderPublicKey] set.
|
* Decrypt with authentication checks the message which must have [Asymmetric.Message.senderPublicKey] set.
|
||||||
* Use [decryptWithSenderKey] otherwise. Note that the authenticated encryption is always use, even if
|
* Use [decryptWithSenderKey] otherwise. Note that the authenticated encryption is always use, even if
|
||||||
* the [PublicKey.encryptAnonymousMessage] was used to create a message, if it is successfully decrypted,
|
* the [EncryptingPublicKey.encryptAnonymousMessage] was used to create a message, if it is successfully decrypted,
|
||||||
* it is guaranteed that the message was not altered after creation.
|
* it is guaranteed that the message was not altered after creation.
|
||||||
*
|
*
|
||||||
* @throws DecryptionFailedException If the message is tampered (changed after creation) or was not intended for us,
|
* @throws DecryptionFailedException If the message is tampered (changed after creation) or was not intended for us,
|
||||||
@@ -35,20 +49,20 @@ class SecretKey(
|
|||||||
* Decrypt using [senderPublicKey] as a sender key (overriding the [Asymmetric.Message.senderPublicKey] if set).
|
* Decrypt using [senderPublicKey] as a sender key (overriding the [Asymmetric.Message.senderPublicKey] if set).
|
||||||
* See [decrypt] for more.
|
* See [decrypt] for more.
|
||||||
*/
|
*/
|
||||||
fun decryptWithSenderKey(message: Asymmetric.Message, senderPublicKey: PublicKey): UByteArray =
|
fun decryptWithSenderKey(message: Asymmetric.Message, senderPublicKey: EncryptingPublicKey): UByteArray =
|
||||||
message.decryptWithSenderKey(senderPublicKey, this)
|
message.decryptWithSenderKey(senderPublicKey, this)
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var cachedPublicKey: PublicKey? = _cachedPublicKey
|
private var cachedPublicKey: EncryptingPublicKey? = _cachedPublicKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The corresponding public key
|
* The corresponding public key
|
||||||
*/
|
*/
|
||||||
val publicKey: PublicKey by lazy {
|
val publicKey: EncryptingPublicKey by lazy {
|
||||||
if (cachedPublicKey != null)
|
if (cachedPublicKey != null)
|
||||||
cachedPublicKey!!
|
cachedPublicKey!!
|
||||||
else
|
else
|
||||||
PublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
|
EncryptingPublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
|
||||||
.also { cachedPublicKey = it }
|
.also { cachedPublicKey = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,18 +85,18 @@ class SecretKey(
|
|||||||
get() = 0
|
get() = 0
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
data class KeyPair(val secretKey: SecretKey, val publicKey: PublicKey)
|
data class KeyPair(val secretKey: DecryptingSecretKey, val publicKey: EncryptingPublicKey)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a new random pair of public and secret keys.
|
* Generate a new random pair of public and secret keys.
|
||||||
*/
|
*/
|
||||||
fun generateKeys(): KeyPair {
|
fun generateKeys(): KeyPair {
|
||||||
val p = Box.keypair()
|
val p = Box.keypair()
|
||||||
val pk = PublicKey(p.publicKey)
|
val pk = EncryptingPublicKey(p.publicKey)
|
||||||
return KeyPair(SecretKey(p.secretKey, pk), pk)
|
return KeyPair(DecryptingSecretKey(p.secretKey, pk), pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun new(): SecretKey = generateKeys().secretKey
|
fun new(): DecryptingSecretKey = generateKeys().secretKey
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
interface SigningKey: KeyInstance {
|
interface SigningKey: KeyInstance {
|
||||||
val verifyingKey: VerifyingPublicKey
|
val verifyingKey: VerifyingPublicKey
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.signature.Signature
|
import com.ionspin.kotlin.crypto.signature.Signature
|
||||||
import kotlinx.datetime.Instant
|
import kotlin.time.Instant
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
@@ -23,16 +33,45 @@ class SigningSecretKey(
|
|||||||
VerifyingPublicKey(Signature.ed25519SkToPk(keyBytes)).also { cachedPublicKey = it }
|
VerifyingPublicKey(Signature.ed25519SkToPk(keyBytes)).also { cachedPublicKey = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val magic: KeysmagicNumber = KeysmagicNumber.defaultSigningSecret
|
||||||
|
|
||||||
override fun sign(message: UByteArray): UByteArray = Signature.detached(message, keyBytes)
|
override fun sign(message: UByteArray): UByteArray = Signature.detached(message, keyBytes)
|
||||||
|
|
||||||
override fun seal(message: UByteArray, expiresAt: Instant?): Seal =
|
override fun seal(message: UByteArray, expiresAt: Instant?): Seal =
|
||||||
Seal.create(this, message, now(), expiresAt)
|
Seal.create(this, message, now(), expiresAt)
|
||||||
|
|
||||||
|
@Transient
|
||||||
override val id: KeyId = verifyingKey.id
|
override val id: KeyId = verifyingKey.id
|
||||||
|
|
||||||
override val label: String
|
override val label: String
|
||||||
get() = "sig"
|
get() = "sig"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this or [other] key
|
||||||
|
*/
|
||||||
|
infix fun or(other: VerifyingPublicKey) = Multikey(this) or other
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this or [other] key
|
||||||
|
*/
|
||||||
|
infix fun or(other: SigningSecretKey) = Multikey(this) or other
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this or [other] key
|
||||||
|
*/
|
||||||
|
infix fun or(other: Multikey) = Multikey(this) or other
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this and [other] key
|
||||||
|
*/
|
||||||
|
infix fun and(other: VerifyingPublicKey) = Multikey(this) and other
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this and [other] key
|
||||||
|
*/
|
||||||
|
infix fun and(other: SigningSecretKey) = Multikey(this) and other
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this and [other] key
|
||||||
|
*/
|
||||||
|
infix fun and(other: Multikey) = Multikey(this) and other
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
data class SigningKeyPair(val secretKey: SigningSecretKey, val publicKey: VerifyingPublicKey)
|
data class SigningKeyPair(val secretKey: SigningSecretKey, val publicKey: VerifyingPublicKey)
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
||||||
@@ -46,7 +56,7 @@ class SymmetricKey(
|
|||||||
override val nonceBytesLength: Int = nonceLength
|
override val nonceBytesLength: Int = nonceLength
|
||||||
|
|
||||||
override val id by lazy {
|
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 =
|
override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =
|
||||||
|
|||||||
@@ -1,18 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
import net.sergeych.bipack.BipackEncoder
|
||||||
|
import net.sergeych.bipack.decodeFromBipack
|
||||||
|
import net.sergeych.mp_tools.decodeBase64Compact
|
||||||
|
import net.sergeych.mp_tools.encodeToBase64Compact
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed class UniversalKey: KeyInstance {
|
sealed class UniversalKey : KeyInstance {
|
||||||
|
|
||||||
abstract val keyBytes: UByteArray
|
abstract val keyBytes: UByteArray
|
||||||
|
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
open val magic: KeysmagicNumber = KeysmagicNumber.Unknown
|
open val magic: KeysmagicNumber = KeysmagicNumber.Unknown
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key ID positively identify key from the point of view of _decrypting or verifying_. So matching [VerifyingKey]
|
||||||
|
* and [SigningKey] will have the same id, same as matching [EncryptingPublicKey] and [DecryptingSecretKey].
|
||||||
|
*
|
||||||
|
* KeyId is based on [BinaryId] which includes checksum (crc8) and magick number for additional security,
|
||||||
|
* see [KeysmagicNumber].
|
||||||
|
*
|
||||||
|
* Also "public" keys can be restored from id using [BinaryId.asPublicKey] and [BinaryId.asVerifyingKey].
|
||||||
|
*/
|
||||||
override val id by lazy { KeyId(magic, keyBytes, null) }
|
override val id by lazy { KeyId(magic, keyBytes, null) }
|
||||||
|
|
||||||
// Important: id can be overridden, so we use it, not magic:
|
// Important: id can be overridden, so we use it, not magic:
|
||||||
@@ -32,14 +53,36 @@ sealed class UniversalKey: KeyInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newSecretKey() = SecretKey.new()
|
fun newSecretKey() = DecryptingSecretKey.new()
|
||||||
fun newSigningKey() = SigningSecretKey.new()
|
fun newSigningKey() = SigningSecretKey.new()
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun newSymmetricKey() = SymmetricKey.new()
|
fun newSymmetricKey() = SymmetricKey.new()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse all known string representations of the universal key
|
||||||
|
* @throws IllegalArgumentException if it can't parse any key.
|
||||||
|
*/
|
||||||
|
fun parseString(text: String): UniversalKey {
|
||||||
|
val s = text.trim()
|
||||||
|
return when {
|
||||||
|
s.startsWith("\uD83D\uDDDDpub#") || s.startsWith("pub#") ->
|
||||||
|
EncryptingPublicKey.parse(s)
|
||||||
|
s.startsWith("\uD83D\uDDDDver#") || s.startsWith("ver#") ->
|
||||||
|
VerifyingPublicKey.parse(s)
|
||||||
|
else -> {
|
||||||
|
s.decodeBase64Compact().decodeFromBipack<UniversalKey>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : UniversalKey> T.asString() =
|
||||||
|
BipackEncoder.encode<T>(this).encodeToBase64Compact()
|
||||||
|
|
||||||
|
|
||||||
open class IllegalSignatureException(text: String = "signed data is tampered or signature is corrupted") :
|
open class IllegalSignatureException(text: String = "signed data is tampered or signature is corrupted") :
|
||||||
IllegalStateException(text)
|
IllegalStateException(text)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combination of private/secret keys suitable for both decryption and signing.
|
||||||
|
*
|
||||||
|
* It contains two cryptographically independent keys to raise security to a maximum.
|
||||||
|
* Any converted keys poses a threat while technically possible so we avoid it.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@SerialName("uprv")
|
||||||
|
class UniversalPrivateKey(
|
||||||
|
val signingKey: SigningSecretKey,
|
||||||
|
val decryptingKey: DecryptingSecretKey
|
||||||
|
) : UniversalKey(), DecryptingKey by decryptingKey, SigningKey by signingKey {
|
||||||
|
|
||||||
|
override val keyBytes by lazy { signingKey.keyBytes + decryptingKey.keyBytes }
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
override val magic = KeysmagicNumber.defaultUniversalPrivate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Important! Private key combines signing and decrypting keys, but uses
|
||||||
|
* it of the decrypting one to be used in keyring.
|
||||||
|
*/
|
||||||
|
@Transient
|
||||||
|
override val id: KeyId = decryptingKey.id
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corresponding public key able to verify amd encrypt data created by this
|
||||||
|
* private key.
|
||||||
|
*/
|
||||||
|
val publicKey by lazy {
|
||||||
|
UniversalPublicKey(signingKey.verifyingKey, decryptingKey.publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Generate 2 new random keys (4 key pairs under the hood) to securely signd and
|
||||||
|
* decrypt data.
|
||||||
|
*/
|
||||||
|
fun new() = UniversalPrivateKey(SigningSecretKey.new(), DecryptingSecretKey.new())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combination of public keys suitable for both encryption and verification. A counterpart
|
||||||
|
* of the [UniversalPrivateKey], available also as [UniversalPrivateKey.publicKey].
|
||||||
|
*
|
||||||
|
* When using [UniversalRing] and [Container], data encrypted with instances og this class
|
||||||
|
* can be decrypted with rings containing the corresponding [UniversalPrivateKey].
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@SerialName("upub")
|
||||||
|
class UniversalPublicKey(
|
||||||
|
val verifyingKey: VerifyingPublicKey,
|
||||||
|
val encryptingKey: EncryptingPublicKey
|
||||||
|
): UniversalKey(), VerifyingKey by verifyingKey, EncryptingKey by encryptingKey{
|
||||||
|
|
||||||
|
override val keyBytes by lazy { verifyingKey.keyBytes + encryptingKey.keyBytes }
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
override val magic = KeysmagicNumber.defaultUniversalPublic
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Important! Private key combines signing and decrypting keys, but uses
|
||||||
|
* it of the decrypting one to be used in keyring.
|
||||||
|
*/
|
||||||
|
@Transient
|
||||||
|
override val id: KeyId = encryptingKey.id
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -38,7 +48,7 @@ class UniversalRing(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a key of the specified type that matches the id. In general, you require key implementations like
|
* Find a key of the specified type that matches the id. In general, you require key implementations like
|
||||||
* [SecretKey], [PublicKey], [VerifyingPublicKey], [SigningSecretKey] and [SymmetricKey],
|
* [DecryptingSecretKey], [EncryptingPublicKey], [VerifyingPublicKey], [SigningSecretKey] and [SymmetricKey],
|
||||||
* or just key interfaces: [EncryptingKey], [DecryptingKey], [SigningKey] and [VerifyingKey].
|
* or just key interfaces: [EncryptingKey], [DecryptingKey], [SigningKey] and [VerifyingKey].
|
||||||
*
|
*
|
||||||
* Note that key interfaces are not serializable as for now, you should try to cast to a serializable
|
* Note that key interfaces are not serializable as for now, you should try to cast to a serializable
|
||||||
@@ -62,28 +72,34 @@ class UniversalRing(
|
|||||||
* Get all keys for the specified id (normally it could be 0, 1 or 2). See [KeyId] about
|
* Get all keys for the specified id (normally it could be 0, 1 or 2). See [KeyId] about
|
||||||
* matching id keys.
|
* 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]
|
* 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) {
|
for (e in keyWithTags.entries) {
|
||||||
if (tags.any { it in e.value }) yield(e.key)
|
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]
|
* 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.
|
* Get keys of the specified type having any of the specified tags associated.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
inline fun <reified T> keysByAnyTag(vararg tags: String): Sequence<UniversalKey> =
|
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.
|
* Get all keys with a given id. Note that _matching keys_ have the same id, see [KeyId] for more.
|
||||||
@@ -134,6 +150,7 @@ class UniversalRing(
|
|||||||
/**
|
/**
|
||||||
* Add key and tags to the ring. If the key already exists, tags are merged.
|
* 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 =
|
fun add(key: UniversalKey, tags: Collection<String>): UniversalRing =
|
||||||
UniversalRing(keyWithTags + (key to tags.toSet()))
|
UniversalRing(keyWithTags + (key to tags.toSet()))
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
interface VerifyingKey: KeyInstance {
|
interface VerifyingKey: KeyInstance {
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
|
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
|
||||||
@@ -5,6 +15,8 @@ import com.ionspin.kotlin.crypto.signature.Signature
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
import net.sergeych.bipack.decodeFromBipack
|
||||||
|
import net.sergeych.mp_tools.decodeBase64Url
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public key to verify signatures only
|
* Public key to verify signatures only
|
||||||
@@ -26,4 +38,71 @@ class VerifyingPublicKey(override val keyBytes: UByteArray) : UniversalKey(), Ve
|
|||||||
override val magic: KeysmagicNumber = KeysmagicNumber.defaultVerifying
|
override val magic: KeysmagicNumber = KeysmagicNumber.defaultVerifying
|
||||||
|
|
||||||
override val id by lazy { KeyId(magic, keyBytes, null, true) }
|
override val id by lazy { KeyId(magic, keyBytes, null, true) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this or [other] key
|
||||||
|
*/
|
||||||
|
infix fun or(other: VerifyingPublicKey) = Multikey(this) or other
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this or [other] key
|
||||||
|
*/
|
||||||
|
infix fun or(other: SigningSecretKey) = Multikey(this) or other
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this or [other] key
|
||||||
|
*/
|
||||||
|
infix fun or(other: Multikey) = Multikey(this) or other
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this and [other]
|
||||||
|
*/
|
||||||
|
infix fun and(other: VerifyingPublicKey) = Multikey(this) and other
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this and [other]
|
||||||
|
*/
|
||||||
|
infix fun and(other: SigningSecretKey) = Multikey(this) and other
|
||||||
|
/**
|
||||||
|
* Create a [Multikey] that requires presence of this and [other]
|
||||||
|
*/
|
||||||
|
infix fun and(other: Multikey) = Multikey(this) and other
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Parse any known public key text representation, including what [toString] return (for public keys it is
|
||||||
|
* possible)
|
||||||
|
* @throws IllegalArgumentException the public key isn't recognized, in particular [BinaryId.InvalidException]
|
||||||
|
* if the text is corrupt
|
||||||
|
*/
|
||||||
|
fun parse(text: String): VerifyingPublicKey {
|
||||||
|
val s = text.trim()
|
||||||
|
|
||||||
|
fun parseId(t: String): VerifyingPublicKey {
|
||||||
|
// assume it is an id:
|
||||||
|
val id = BinaryId.restoreFromString(t)
|
||||||
|
return if (id.magic == KeysmagicNumber.defaultVerifying.ordinal)
|
||||||
|
id.asVerifyingKey as VerifyingPublicKey
|
||||||
|
else throw IllegalArgumentException("Invalid magick: ${id.magic} when parsing[$t]")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🗝sig#I1po9Y2I7p2aOxeh4nFyGPm3e0YunBEu1Mo-PmIqP84Evg
|
||||||
|
return when {
|
||||||
|
s.startsWith("\uD83D\uDDDDver#") -> parseId(s.drop(6))
|
||||||
|
s.startsWith("ver#") -> parseId(s.drop(4))
|
||||||
|
s.startsWith("#") -> parseId(s.drop(1))
|
||||||
|
else -> {
|
||||||
|
// consider it is serialized key in base64 format
|
||||||
|
val data = s.decodeBase64Url().asUByteArray()
|
||||||
|
if (data.size == 32)
|
||||||
|
VerifyingPublicKey(data)
|
||||||
|
else if( data.size == 34) {
|
||||||
|
// raw id
|
||||||
|
BinaryId(data).asVerifyingKey as VerifyingPublicKey
|
||||||
|
}else {
|
||||||
|
runCatching { data.decodeFromBipack<VerifyingPublicKey>() }.getOrNull()
|
||||||
|
?: kotlin.runCatching { data.decodeFromBipack<UniversalKey>() as VerifyingPublicKey }
|
||||||
|
.getOrElse { throw IllegalArgumentException("can't parse verifying key") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.pwhash.*
|
import com.ionspin.kotlin.crypto.pwhash.*
|
||||||
@@ -27,12 +37,48 @@ sealed class KDF {
|
|||||||
Moderate,
|
Moderate,
|
||||||
FixedHigher,
|
FixedHigher,
|
||||||
Sensitive,
|
Sensitive,
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create [KDF] of the corresponding strength suitable to derive [numberOfKeys] symmetric keys.
|
||||||
|
*
|
||||||
|
* Random salt of proper size is used
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
* [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,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, salt: UByteArray): SymmetricKey = deriveMultiple(password, 1, salt).first()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun derive(password: String): UByteArray
|
/**
|
||||||
|
* Derive a single key from the password, same as [deriveMultipleKeys] with `count==1`
|
||||||
|
*/
|
||||||
|
abstract fun deriveKey(password: String): UByteArray
|
||||||
|
|
||||||
fun deriveMultiple(password: String, count: Int): List<SymmetricKey> {
|
/**
|
||||||
val bytes = derive(password)
|
* 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 deriveMultipleKeys(password: String, count: Int): List<SymmetricKey> {
|
||||||
|
val bytes = deriveKey(password)
|
||||||
val ks = SymmetricKey.keyLength
|
val ks = SymmetricKey.keyLength
|
||||||
check(ks * count <= bytes.size) { "KDF is too short for $count keys: ${bytes.size} we need ${ks * count}" }
|
check(ks * count <= bytes.size) { "KDF is too short for $count keys: ${bytes.size} we need ${ks * count}" }
|
||||||
return (0..<count).map {
|
return (0..<count).map {
|
||||||
@@ -54,6 +100,7 @@ sealed class KDF {
|
|||||||
val keySize: Int,
|
val keySize: Int,
|
||||||
) : KDF(), Comparable<Argon> {
|
) : KDF(), Comparable<Argon> {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Very abstract strength comparison. If a1 > a2 it generally means that its complexity, and, hopefully,
|
* Very abstract strength comparison. If a1 > a2 it generally means that its complexity, and, hopefully,
|
||||||
* strength is higher.
|
* strength is higher.
|
||||||
@@ -81,7 +128,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)
|
PasswordHash.pwhash(keySize, password, salt, instructionsComplexity, memComplexity, algorithm.code)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -111,6 +158,17 @@ sealed class KDF {
|
|||||||
|
|
||||||
fun randomSalt() = randomUBytes(saltSize)
|
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 {
|
fun create(complexity: Complexity, salt: UByteArray, keySize: Int): Argon {
|
||||||
require(salt.size == saltSize) { "The salt size should be $saltSize" }
|
require(salt.size == saltSize) { "The salt size should be $saltSize" }
|
||||||
require(keySize > minKeySize) { "The key size should be at least $keySize bytes" }
|
require(keySize > minKeySize) { "The key size should be at least $keySize bytes" }
|
||||||
@@ -135,6 +193,7 @@ sealed class KDF {
|
|||||||
268435456,
|
268435456,
|
||||||
salt, keySize
|
salt, keySize
|
||||||
)
|
)
|
||||||
|
|
||||||
Moderate -> Argon(
|
Moderate -> Argon(
|
||||||
Alg.default,
|
Alg.default,
|
||||||
crypto_pwhash_OPSLIMIT_MODERATE,
|
crypto_pwhash_OPSLIMIT_MODERATE,
|
||||||
@@ -142,14 +201,14 @@ sealed class KDF {
|
|||||||
salt, keySize
|
salt, keySize
|
||||||
)
|
)
|
||||||
|
|
||||||
Complexity.Sensitive -> Argon(
|
Sensitive -> Argon(
|
||||||
Alg.default,
|
Alg.default,
|
||||||
crypto_pwhash_OPSLIMIT_SENSITIVE,
|
crypto_pwhash_OPSLIMIT_SENSITIVE,
|
||||||
crypto_pwhash_MEMLIMIT_SENSITIVE,
|
crypto_pwhash_MEMLIMIT_SENSITIVE,
|
||||||
salt, keySize
|
salt, keySize
|
||||||
)
|
)
|
||||||
|
|
||||||
Complexity.FixedHigher -> Argon(
|
FixedHigher -> Argon(
|
||||||
V2id_13,
|
V2id_13,
|
||||||
4UL,
|
4UL,
|
||||||
1073741824,
|
1073741824,
|
||||||
@@ -162,10 +221,10 @@ sealed class KDF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("unused")
|
|
||||||
fun creteDefault(keySize: Int, complexity: Complexity, salt: UByteArray = Argon.randomSalt()): KDF {
|
fun creteDefault(keySize: Int, complexity: Complexity, salt: UByteArray = Argon.randomSalt()): KDF {
|
||||||
return Argon.create(complexity, salt, keySize)
|
return Argon.create(complexity, salt, keySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Instance(val kdf: KDF, val password: String)
|
data class Instance(val kdf: KDF, val password: String)
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.sergeych.crypto2
|
package net.sergeych.crypto2
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.sergeych.crypto2
|
package net.sergeych.crypto2
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.tools
|
package net.sergeych.tools
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.tools
|
package net.sergeych.tools
|
||||||
|
|
||||||
import net.sergeych.synctools.ProtectedOp
|
import net.sergeych.synctools.ProtectedOp
|
||||||
@@ -5,7 +15,7 @@ import net.sergeych.synctools.invoke
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Multiplatform (JS and battery included) atomically mutable value.
|
* Multiplatform (JS and battery included) atomically mutable value.
|
||||||
* Actual value can be either changed in a block of [mutuate] when
|
* Actual value can be either changed in a block of [mutate] when
|
||||||
* new value _depends on the current value_ or use a same [value]
|
* new value _depends on the current value_ or use a same [value]
|
||||||
* property that is thread-safe where there are threads and just safe
|
* property that is thread-safe where there are threads and just safe
|
||||||
* otherwise ;)
|
* otherwise ;)
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.sergeych.tools
|
package net.sergeych.tools
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.tools
|
package net.sergeych.tools
|
||||||
|
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.sergeych.utools
|
package net.sergeych.utools
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.utools
|
package net.sergeych.utools
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.sergeych.utools
|
package net.sergeych.utools
|
||||||
|
|
||||||
import kotlinx.datetime.Clock
|
import kotlin.time.Clock
|
||||||
import kotlinx.datetime.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
fun now(): Instant = Clock.System.now()
|
fun now(): Instant = Clock.System.now()
|
||||||
fun nowToSeconds(): Instant = Clock.System.now().truncateToSeconds()
|
fun nowToSeconds(): Instant = Clock.System.now().truncateToSeconds()
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.komputing.khash.keccak
|
package org.komputing.khash.keccak
|
||||||
|
|
||||||
import com.ionspin.kotlin.bignum.integer.BigInteger
|
import com.ionspin.kotlin.bignum.integer.BigInteger
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package org.komputing.khash.keccak
|
package org.komputing.khash.keccak
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.komputing.khash.keccak.extensions
|
package org.komputing.khash.keccak.extensions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
package org.komputing.khash.keccak.extensions
|
package org.komputing.khash.keccak.extensions
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.initCrypto
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertContentEquals
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class BinaryIdTest {
|
||||||
|
@Test
|
||||||
|
fun testSizes() {
|
||||||
|
val a = BinaryId.createRandom(5, 4)
|
||||||
|
// println(a.id.toDump())
|
||||||
|
// println(pack(a).toDump())
|
||||||
|
assertEquals(2, a.body.size)
|
||||||
|
assertEquals(5, a.magic)
|
||||||
|
assertEquals(4, a.id.size)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testByteChunkSizes() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val x = ByteChunk.random(3)
|
||||||
|
assertEquals(3, x.data.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.asByteArray), BipackEncoder.encode(x))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.datetime.Clock
|
import kotlin.time.Clock
|
||||||
import net.sergeych.crypto2.Hash
|
import net.sergeych.crypto2.Hash
|
||||||
import net.sergeych.crypto2.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
import kotlin.random.nextUBytes
|
import kotlin.random.nextUBytes
|
||||||
import kotlin.test.*
|
import kotlin.test.Ignore
|
||||||
|
import kotlin.test.Test
|
||||||
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
|
import kotlin.test.assertContentEquals
|
||||||
suspend fun <T> sw(label: String, f: suspend () -> T): T {
|
import kotlin.test.assertEquals
|
||||||
val t1 = Clock.System.now()
|
import kotlin.test.assertFalse
|
||||||
val result = f()
|
|
||||||
val t2 = Clock.System.now()
|
|
||||||
// println("$label: ${t2 - t1}")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class HashTest {
|
class HashTest {
|
||||||
@Test
|
@Test
|
||||||
@@ -46,5 +51,31 @@ class HashTest {
|
|||||||
assertContentEquals(Hash.Blake2b.digest(a), p1)
|
assertContentEquals(Hash.Blake2b.digest(a), p1)
|
||||||
assertContentEquals(Hash.Sha3_384.digest(a), p2)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.crypto2.KDF
|
import net.sergeych.crypto2.KDF
|
||||||
import net.sergeych.crypto2.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
@@ -38,4 +48,11 @@ class KDFTest {
|
|||||||
assertEquals(set2, set1)
|
assertEquals(set2, set1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun complexityTest() = runTest{
|
||||||
|
initCrypto()
|
||||||
|
val kk = KDF.Complexity.Interactive.kdfForSize(3).deriveMultipleKeys("lala", 3)
|
||||||
|
assertEquals(3, kk.size)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import net.sergeych.bipack.BipackDecoder
|
||||||
|
import net.sergeych.bipack.BipackEncoder
|
||||||
import net.sergeych.crypto2.*
|
import net.sergeych.crypto2.*
|
||||||
import net.sergeych.tools.bipack
|
import net.sergeych.tools.bipack
|
||||||
import net.sergeych.tools.biunpack
|
import net.sergeych.tools.biunpack
|
||||||
@@ -15,9 +27,9 @@ class KeysTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testSigningCreationAndMap() = runTest {
|
fun testSigningCreationAndMap() = runTest {
|
||||||
initCrypto()
|
initCrypto()
|
||||||
val (stk,pbk) = SigningSecretKey.generatePair()
|
val (stk, pbk) = SigningSecretKey.generatePair()
|
||||||
|
|
||||||
val x = mapOf( stk to "STK!", pbk to "PBK!")
|
val x = mapOf(stk to "STK!", pbk to "PBK!")
|
||||||
assertEquals("STK!", x[stk])
|
assertEquals("STK!", x[stk])
|
||||||
val s1 = SigningSecretKey(stk.keyBytes)
|
val s1 = SigningSecretKey(stk.keyBytes)
|
||||||
assertEquals(stk, s1)
|
assertEquals(stk, s1)
|
||||||
@@ -52,7 +64,7 @@ class KeysTest {
|
|||||||
fun testNonDeterministicSeals() = runTest {
|
fun testNonDeterministicSeals() = runTest {
|
||||||
initCrypto()
|
initCrypto()
|
||||||
val data = "Welcome to the crazy new world!".encodeToUByteArray()
|
val data = "Welcome to the crazy new world!".encodeToUByteArray()
|
||||||
val (sk,_) = SigningSecretKey.generatePair()
|
val (sk, _) = SigningSecretKey.generatePair()
|
||||||
val t = now()
|
val t = now()
|
||||||
val s1 = Seal.create(sk, data, createdAt = t)
|
val s1 = Seal.create(sk, data, createdAt = t)
|
||||||
val s2 = Seal.create(sk, data, createdAt = t)
|
val s2 = Seal.create(sk, data, createdAt = t)
|
||||||
@@ -60,11 +72,11 @@ class KeysTest {
|
|||||||
val s3 = Seal.create(sk, data, createdAt = t, nonDeterministic = true)
|
val s3 = Seal.create(sk, data, createdAt = t, nonDeterministic = true)
|
||||||
val s4 = Seal.create(sk, data, createdAt = t, nonDeterministic = true)
|
val s4 = Seal.create(sk, data, createdAt = t, nonDeterministic = true)
|
||||||
|
|
||||||
for( seal in listOf(s1,s2,s3,s4)) {
|
for (seal in listOf(s1, s2, s3, s4)) {
|
||||||
assertTrue { seal.isValid(data) }
|
assertTrue { seal.isValid(data) }
|
||||||
assertTrue { Seal.unpack(seal.packed).isValid(data) }
|
assertTrue { Seal.unpack(seal.packed).isValid(data) }
|
||||||
}
|
}
|
||||||
assertFalse { s2bad.isValid(data)}
|
assertFalse { s2bad.isValid(data) }
|
||||||
assertContentEquals(s1.packed, s2.packed)
|
assertContentEquals(s1.packed, s2.packed)
|
||||||
assertFalse { s1.packed contentEquals s3.packed }
|
assertFalse { s1.packed contentEquals s3.packed }
|
||||||
assertFalse { s4.packed contentEquals s3.packed }
|
assertFalse { s4.packed contentEquals s3.packed }
|
||||||
@@ -97,7 +109,7 @@ class KeysTest {
|
|||||||
assertContentEquals(src, k1.decryptWithNonce(k1.encryptWithNonce(src, nonce), nonce))
|
assertContentEquals(src, k1.decryptWithNonce(k1.encryptWithNonce(src, nonce), nonce))
|
||||||
assertThrows<DecryptionFailedException> {
|
assertThrows<DecryptionFailedException> {
|
||||||
val n2 = nonce.copyOf()
|
val n2 = nonce.copyOf()
|
||||||
n2[4] = n2[4].inv()
|
n2[4] = n2[4].inv()
|
||||||
k1.decryptWithNonce(k1.encryptWithNonce(src, nonce), n2)
|
k1.decryptWithNonce(k1.encryptWithNonce(src, nonce), n2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,9 +185,9 @@ class KeysTest {
|
|||||||
val (sk0, pk0) = Asymmetric.generateKeys()
|
val (sk0, pk0) = Asymmetric.generateKeys()
|
||||||
|
|
||||||
// println(sk0.publicKey)
|
// println(sk0.publicKey)
|
||||||
val j = Json { prettyPrint = true}
|
val j = Json { prettyPrint = true }
|
||||||
|
|
||||||
val sk1 = j.decodeFromString<SecretKey>(j.encodeToString(sk0))
|
val sk1 = j.decodeFromString<DecryptingSecretKey>(j.encodeToString(sk0))
|
||||||
assertEquals(sk0, sk1)
|
assertEquals(sk0, sk1)
|
||||||
assertEquals(pk0, sk1.publicKey)
|
assertEquals(pk0, sk1.publicKey)
|
||||||
// println(j.encodeToString(sk1))
|
// println(j.encodeToString(sk1))
|
||||||
@@ -204,9 +216,9 @@ class KeysTest {
|
|||||||
assertEquals(usy2, usy1)
|
assertEquals(usy2, usy1)
|
||||||
assertFalse { usy1 == usy3 }
|
assertFalse { usy1 == usy3 }
|
||||||
|
|
||||||
val sk1 = SecretKey.new()
|
val sk1 = DecryptingSecretKey.new()
|
||||||
val sk2 = SecretKey(sk1.keyBytes)
|
val sk2 = DecryptingSecretKey(sk1.keyBytes)
|
||||||
val sk3 = SecretKey.new()
|
val sk3 = DecryptingSecretKey.new()
|
||||||
|
|
||||||
assertEquals(sk1, sk2)
|
assertEquals(sk1, sk2)
|
||||||
assertEquals(sk2, sk1)
|
assertEquals(sk2, sk1)
|
||||||
@@ -274,6 +286,188 @@ class KeysTest {
|
|||||||
assertContentEquals(k.verifyingKey.keyBytes, dk2.id.binaryTag.take(32).toUByteArray())
|
assertContentEquals(k.verifyingKey.keyBytes, dk2.id.binaryTag.take(32).toUByteArray())
|
||||||
assertContentEquals(k.verifyingKey.keyBytes, dk1.id.binaryTag.take(32).toUByteArray())
|
assertContentEquals(k.verifyingKey.keyBytes, dk1.id.binaryTag.take(32).toUByteArray())
|
||||||
// and restored from id should be the same:
|
// and restored from id should be the same:
|
||||||
assertEquals( k.verifyingKey, dk2.id.id.asVerifyingKey)
|
assertEquals(k.verifyingKey, dk2.id.id.asVerifyingKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiKeyTestSome() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val k1 = SigningSecretKey.new()
|
||||||
|
val k2 = SigningSecretKey.new()
|
||||||
|
val k3 = SigningSecretKey.new()
|
||||||
|
val k4 = SigningSecretKey.new()
|
||||||
|
val k5 = SigningSecretKey.new()
|
||||||
|
// val k6 = SigningSecretKey.new()
|
||||||
|
val mk: Multikey = Multikey.Keys(1, setOf(k1.verifyingKey))
|
||||||
|
val mk23: Multikey = Multikey.Keys(2, setOf(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey))
|
||||||
|
val mk13: Multikey = Multikey.Keys(1, setOf(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey))
|
||||||
|
|
||||||
|
assertTrue { mk.check(k1.verifyingKey) }
|
||||||
|
assertFalse { mk.check(k2.verifyingKey) }
|
||||||
|
|
||||||
|
assertTrue { mk23.check(k1.verifyingKey, k2.verifyingKey, k4.verifyingKey) }
|
||||||
|
assertTrue { mk23.check(k3.verifyingKey, k2.verifyingKey, k4.verifyingKey) }
|
||||||
|
assertFalse { mk23.check(k4.verifyingKey, k2.verifyingKey, k5.verifyingKey) }
|
||||||
|
assertTrue { mk13.check(k4.verifyingKey, k2.verifyingKey, k5.verifyingKey) }
|
||||||
|
|
||||||
|
println(pack(mk23).toDump())
|
||||||
|
println(pack(mk23).size)
|
||||||
|
|
||||||
|
val smk23: Multikey = Multikey.someOf(2, k1.verifyingKey, k2.verifyingKey, k3.verifyingKey)
|
||||||
|
// val smk13: Multikey = Multikey.Keys(1, setOf(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey))
|
||||||
|
|
||||||
|
assertTrue { smk23.check(k1.verifyingKey, k2.verifyingKey, k4.verifyingKey) }
|
||||||
|
assertTrue { smk23.check(k3.verifyingKey, k2.verifyingKey, k4.verifyingKey) }
|
||||||
|
assertFalse { smk23.check(k4.verifyingKey, k2.verifyingKey, k5.verifyingKey) }
|
||||||
|
// assertTrue { smk13.check(k4.verifyingKey, k2.verifyingKey, k5.verifyingKey) }
|
||||||
|
|
||||||
|
println(pack(smk23).toDump())
|
||||||
|
println(pack(smk23).size)
|
||||||
|
|
||||||
|
val s1 = k1 or k2 or k3
|
||||||
|
println(pack(s1).toDump())
|
||||||
|
println(pack(s1).size)
|
||||||
|
assertTrue { s1.check(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey) }
|
||||||
|
assertTrue { s1.check(k1.verifyingKey) }
|
||||||
|
assertTrue { s1.check(k2.verifyingKey) }
|
||||||
|
assertTrue { s1.check(k3.verifyingKey) }
|
||||||
|
assertFalse { s1.check(k4.verifyingKey) }
|
||||||
|
|
||||||
|
val s2 = (k1 or k2) and k3
|
||||||
|
println(pack(s2).toDump())
|
||||||
|
println(pack(s2).size)
|
||||||
|
assertTrue { s2.check(k1.verifyingKey, k3.verifyingKey) }
|
||||||
|
assertTrue { s2.check(k2.verifyingKey, k3.verifyingKey) }
|
||||||
|
assertTrue { s2.check(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey) }
|
||||||
|
assertFalse { s2.check(k4.verifyingKey) }
|
||||||
|
assertFalse { s2.check(k1.verifyingKey) }
|
||||||
|
assertFalse { s2.check(k2.verifyingKey) }
|
||||||
|
assertFalse { s2.check(k3.verifyingKey) }
|
||||||
|
assertFalse { s2.check(k1.verifyingKey, k2.verifyingKey) }
|
||||||
|
|
||||||
|
val s3 = (k1 and k2) or k3
|
||||||
|
println(pack(s3).toDump())
|
||||||
|
println(pack(s3).size)
|
||||||
|
assertTrue { s3.check(k1.verifyingKey, k3.verifyingKey) }
|
||||||
|
assertTrue { s3.check(k3.verifyingKey) }
|
||||||
|
assertTrue { s3.check(k2.verifyingKey, k1.verifyingKey) }
|
||||||
|
assertFalse { s3.check(k1.verifyingKey) }
|
||||||
|
assertFalse { s3.check(k2.verifyingKey) }
|
||||||
|
assertFalse { s3.check(k1.verifyingKey, k4.verifyingKey) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiKeyTestAny() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val k1 = SigningSecretKey.new()
|
||||||
|
val k2 = SigningSecretKey.new()
|
||||||
|
val k3 = SigningSecretKey.new()
|
||||||
|
val k4 = SigningSecretKey.new()
|
||||||
|
val k5 = SigningSecretKey.new()
|
||||||
|
// val k6 = SigningSecretKey.new()
|
||||||
|
val mk: Multikey = Multikey.AnyKey
|
||||||
|
assertTrue { mk.check(k1.verifyingKey) }
|
||||||
|
assertTrue { mk.check(k2.verifyingKey) }
|
||||||
|
assertTrue { mk.check(k3.verifyingKey) }
|
||||||
|
assertTrue { mk.check(k4.verifyingKey) }
|
||||||
|
assertTrue { mk.check(k5.verifyingKey) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCombinedKeys() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val k1 = UniversalPrivateKey.new()
|
||||||
|
val k2 = UniversalPrivateKey.new()
|
||||||
|
val k3: UniversalPrivateKey = unpack(pack(k1))
|
||||||
|
assertEquals(k1, k3)
|
||||||
|
assertEquals(k1.publicKey, k3.publicKey)
|
||||||
|
assertEquals(k1.signingKey, k3.signingKey)
|
||||||
|
assertEquals(k1.verifyingKey, k3.verifyingKey)
|
||||||
|
|
||||||
|
val k4: UniversalPublicKey = unpack(pack(k1.publicKey))
|
||||||
|
assertEquals(k1.publicKey, k4)
|
||||||
|
assertEquals(k1.publicKey.encryptingKey, k4.encryptingKey)
|
||||||
|
assertEquals(k1.publicKey.verifyingKey, k4.verifyingKey)
|
||||||
|
|
||||||
|
val data =
|
||||||
|
"""We hold these truths to be self-evident, that all men are created equal,
|
||||||
|
|that they are endowed by their Creator with certain unalienable Rights,
|
||||||
|
|that among these are Life, Liberty and the pursuit of Happiness."""
|
||||||
|
.trimMargin()
|
||||||
|
|
||||||
|
val kr1 = UniversalRing.from(k1)
|
||||||
|
val kr2: UniversalRing = UniversalRing.from(k2)
|
||||||
|
|
||||||
|
val bytes = Container.encrypt(data, k2.publicKey)
|
||||||
|
assertNull(Container.decrypt<String>(bytes, kr1))
|
||||||
|
assertEquals(data, Container.decrypt<String>(bytes, kr2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEncodedSizes() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val x = SigningSecretKey.new()
|
||||||
|
// println("key bytes: ${x.keyBytes.size}:\n${x.keyBytes.toDump()}")
|
||||||
|
val y = BipackEncoder.encode(x)
|
||||||
|
// println("packed: ${y.size}: ${y.toDump()}")
|
||||||
|
assertTrue { x.keyBytes.size + 5 > y.size }
|
||||||
|
assertEquals(x, BipackDecoder.decode<SigningSecretKey>(y))
|
||||||
|
assertContentEquals(x.keyBytes, BipackDecoder.decode<SigningSecretKey>(y).keyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEncodedSizes2() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val x = DecryptingSecretKey.new()
|
||||||
|
// println("key bytes: ${x.keyBytes.size}:\n${x.keyBytes.toDump()}")
|
||||||
|
val y = BipackEncoder.encode(x)
|
||||||
|
// println("packed: ${y.size}: ${y.toDump()}")
|
||||||
|
assertTrue { x.keyBytes.size + 5 > y.size }
|
||||||
|
assertEquals(x, BipackDecoder.decode<DecryptingSecretKey>(y))
|
||||||
|
assertContentEquals(x.keyBytes, BipackDecoder.decode<DecryptingSecretKey>(y).keyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStringRepresentationAndParse() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val k1 = SigningSecretKey.new()
|
||||||
|
val k2 = k1.verifyingKey
|
||||||
|
val k3 = DecryptingSecretKey.new()
|
||||||
|
val k4 = k3.publicKey
|
||||||
|
|
||||||
|
val k5 = UniversalPrivateKey.new()
|
||||||
|
val k6 = k5.publicKey
|
||||||
|
|
||||||
|
assertEquals(32, k2.keyBytes.size)
|
||||||
|
assertContentEquals(k2.keyBytes, k2.id.id.body)
|
||||||
|
|
||||||
|
val k7 = SymmetricKey.new()
|
||||||
|
val k8 = KDF.Complexity.Interactive.derive("super", KDF.Argon.randomSalt())
|
||||||
|
|
||||||
|
fun testToString(k: UniversalKey) {
|
||||||
|
val s = k.toString()
|
||||||
|
val kx = UniversalKey.parseString(s)
|
||||||
|
assertEquals(kx::class, k::class)
|
||||||
|
assertContentEquals(k.keyBytes, kx.keyBytes)
|
||||||
|
assertEquals(k.id, kx.id)
|
||||||
|
assertEquals(k, kx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAsString(k: UniversalKey) {
|
||||||
|
val s = k.asString()
|
||||||
|
val kx = UniversalKey.parseString(s)
|
||||||
|
assertEquals(kx::class, k::class)
|
||||||
|
assertContentEquals(k.keyBytes, kx.keyBytes)
|
||||||
|
assertEquals(k.id, kx.id)
|
||||||
|
assertEquals(k, kx)
|
||||||
|
}
|
||||||
|
|
||||||
|
testToString(k2)
|
||||||
|
testToString(k4)
|
||||||
|
|
||||||
|
for( i in listOf(k1, k2, k3, k4, k5, k6, k7, k8)) testAsString(i)
|
||||||
|
|
||||||
|
val x = VerifyingPublicKey.parse("I1po9Y2I7p2aOxeh4nFyGPm3e0YunBEu1Mo-PmIqP84Evg")
|
||||||
|
println(x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.crypto2.PBKD
|
import net.sergeych.crypto2.PBKD
|
||||||
import net.sergeych.crypto2.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
@@ -33,7 +43,7 @@ class PBKDTest {
|
|||||||
assertEquals(i.kdp, kx.id.kdp)
|
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))) {
|
for( (a,b) in listOf(y1,y2,y3).zip(listOf(k1,k2,k3))) {
|
||||||
assertEquals(a,b)
|
assertEquals(a,b)
|
||||||
assertNotNull(a.id.kdp)
|
assertNotNull(a.id.kdp)
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.datetime.Instant
|
import kotlin.time.Instant
|
||||||
import net.sergeych.crypto2.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
import net.sergeych.utools.nowToSeconds
|
import net.sergeych.utools.nowToSeconds
|
||||||
import net.sergeych.utools.pack
|
import net.sergeych.utools.pack
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.bintools.toDump
|
import net.sergeych.bintools.toDump
|
||||||
@@ -17,7 +27,7 @@ class RingTest {
|
|||||||
assertEquals(y1, y2)
|
assertEquals(y1, y2)
|
||||||
|
|
||||||
val e1 = Asymmetric.newSecretKey()
|
val e1 = Asymmetric.newSecretKey()
|
||||||
val e2: SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1))
|
val e2: DecryptingSecretKey = BipackDecoder.decode(BipackEncoder.encode(e1))
|
||||||
assertEquals(e1, e2)
|
assertEquals(e1, e2)
|
||||||
|
|
||||||
val k1 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) as UniversalKey
|
val k1 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) as UniversalKey
|
||||||
@@ -154,17 +164,17 @@ class RingTest {
|
|||||||
|
|
||||||
var r1 = ra + rb + rc + rd
|
var r1 = ra + rb + rc + rd
|
||||||
|
|
||||||
assertEquals(a, r1.findKey<SecretKey>(a.id))
|
assertEquals(a, r1.findKey<DecryptingSecretKey>(a.id))
|
||||||
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
|
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
|
||||||
assertEquals(b, r1.findKey<SigningKey>(b.id))
|
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))
|
r1 = UniversalRing.join(listOf(ra, rb, rc, rd))
|
||||||
|
|
||||||
assertEquals(a, r1.findKey<SecretKey>(a.id))
|
assertEquals(a, r1.findKey<DecryptingSecretKey>(a.id))
|
||||||
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
|
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
|
||||||
assertEquals(b, r1.findKey<SigningKey>(b.id))
|
assertEquals(b, r1.findKey<SigningKey>(b.id))
|
||||||
assertEquals(c, r1.keysById(c.id).first())
|
assertEquals(c, r1.findById(c.id).first())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()}")
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.bintools.encodeToHex
|
import net.sergeych.bintools.encodeToHex
|
||||||
import net.sergeych.crypto2.BinaryId
|
import net.sergeych.crypto2.BinaryId
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
|
|
||||||
inline fun <reified T: Throwable>assertThrows(f: ()->Unit): T {
|
inline fun <reified T: Throwable>assertThrows(f: ()->Unit): T {
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
import net.sergeych.bipack.BipackEncoder
|
import net.sergeych.bipack.BipackEncoder
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user