fix #1 UniversalPrivateKey & UniversalPublicKey

This commit is contained in:
Sergey Chernov 2024-11-13 18:24:44 +07:00
parent 194fe22afa
commit 640ceb448e
9 changed files with 131 additions and 4 deletions

1
.gitignore vendored
View File

@ -41,4 +41,5 @@ out/
# Other # Other
.kotlin .kotlin
.idea .idea
.gigaide
/kotlin-js-store/yarn.lock /kotlin-js-store/yarn.lock

View File

@ -8,7 +8,7 @@ plugins {
} }
group = "net.sergeych" group = "net.sergeych"
version = "0.5.9-SNAPSHOT" version = "0.6.1-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -20,6 +20,8 @@ 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 [SecretKey], but you
@ -126,8 +128,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.
*/ */
@ -474,6 +477,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)

View File

@ -6,6 +6,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+"),
; ;
} }

View File

@ -23,6 +23,8 @@ 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 =

View File

@ -8,7 +8,6 @@ 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

View File

@ -0,0 +1,47 @@
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: SecretKey
) : 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(), SecretKey.new())
}
}

View File

@ -0,0 +1,34 @@
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: PublicKey
): 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
}

View File

@ -360,4 +360,34 @@ class KeysTest {
assertTrue { mk.check(k4.verifyingKey) } assertTrue { mk.check(k4.verifyingKey) }
assertTrue { mk.check(k5.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))
}
} }