- BinaryId is now open

- Container returns data on the keys used to decrypt it
This commit is contained in:
Sergey Chernov 2024-08-28 09:24:20 +02:00
parent d0fa74e089
commit 14a63a05c2
8 changed files with 109 additions and 17 deletions

View File

@ -6,7 +6,7 @@ plugins {
}
group = "net.sergeych"
version = "0.5.5"
version = "0.5.6-SNAPSHOT"
repositories {
mavenCentral()

View File

@ -4,6 +4,7 @@ import com.ionspin.kotlin.crypto.box.Box
import com.ionspin.kotlin.crypto.box.BoxCorruptedOrTamperedDataException
import com.ionspin.kotlin.crypto.box.crypto_box_NONCEBYTES
import kotlinx.serialization.Serializable
import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder
import net.sergeych.crypto2.Asymmetric.Message
import net.sergeych.crypto2.Asymmetric.generateKeys
@ -64,6 +65,16 @@ object Asymmetric {
}
val encoded: UByteArray by lazy { BipackEncoder.encode(this).toUByteArray() }
companion object {
fun decode(packed: UByteArray): Message = try {
BipackDecoder.decode(packed)
}
catch(x: Exception) {
throw DecryptionFailedException("can't decode message structure",x)
}
}
}
/**
@ -97,6 +108,7 @@ object Asymmetric {
fun newSecretKey() = SecretKey.new()
val nonceBytesLength = crypto_box_NONCEBYTES
}
/**

View File

@ -9,7 +9,7 @@ import net.sergeych.mp_tools.decodeBase64Url
import kotlin.random.Random
@Serializable
class BinaryId private constructor (
open class BinaryId protected constructor (
val id: UByteArray,
) : Comparable<BinaryId> {
@ -29,6 +29,25 @@ class BinaryId private constructor (
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.
*/
val body: UByteArray by lazy { id.sliceArray( 0 until id.size-2 ) }
val asVerifyingKey: VerifyingKey by lazy {
if( magic != KeysmagicNumber.defaultVerifying.ordinal)
throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultVerifying.ordinal}")
check(body.size == 32)
VerifyingPublicKey(body)
}
val asPublicKey: PublicKey by lazy {
if( magic != KeysmagicNumber.defaultAssymmetric.ordinal)
throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultAssymmetric.ordinal}")
check(body.size == 32)
PublicKey(body)
}
override fun toString(): String = id.encodeToBase64Url()
override fun compareTo(other: BinaryId): Int {

View File

@ -14,7 +14,7 @@ import net.sergeych.crypto2.Container.Companion.createWith
* decrypt it. This is sometimes very important to be able to add recipients to the message
* keeping existing recipients you know no keys of, or update the message when only one of the
* keys is known.
*
*
* - [createWith] for more on create a new container
* - [decryptWith] to decrypt
* - [addRecipients] and various [plus] operators to add recipients
@ -123,7 +123,7 @@ sealed class Container {
* Update the data in the decrypted container. It keeps the same set of keys and update
* the data only.
*/
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,
@ -135,6 +135,23 @@ sealed class Container {
BipackEncoder.encode(this).toUByteArray()
}
/**
* When the container is decrypted, the [KeyId] of the key that unlocked it,
* otherwise null.
*/
abstract val decryptedWithKeyId: KeyId?
/**
* If the container _is decrypted by the [PublicKey]_, e.g., using secret key encryption,
* contains the [PublicKey] that corresponds the [SecretKey] used while encrypting, this
* 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,
* could decrypt it.
*/
@Transient
var authorisedByKey: PublicKey? = null
protected set
/**
* @suppress
@ -152,6 +169,9 @@ sealed class Container {
@Transient
private var decryptedWithKey: DecryptingKey? = null
override val decryptedWithKeyId: KeyId?
get() = decryptedWithKey?.id
init {
decryptedData = creationData
}
@ -163,6 +183,9 @@ sealed class Container {
kotlin.runCatching { k.decrypt(encryptedMessage) }.getOrNull()?.let {
decryptedData = it
decryptedWithKey = k
if( k is SecretKey) {
authorisedByKey = Asymmetric.Message.decode(encryptedMessage).senderPublicKey
}
return it
}
}
@ -170,7 +193,7 @@ sealed class Container {
return null
}
private fun reEncrypt(data: UByteArray? = null,f: Builder.()->Unit): Container {
private fun reEncrypt(data: UByteArray? = null, f: Builder.() -> Unit): Container {
check(isDecrypted) { "container should be decrypted" }
return create(data ?: decryptedData ?: throw IllegalArgumentException("no data is provided")) {
f()
@ -204,7 +227,7 @@ sealed class Container {
reEncrypt { alwaysMulti() }
}
override fun updateData(newPlainData: UByteArray,randomFill: IntRange?): Container =
override fun updateData(newPlainData: UByteArray, randomFill: IntRange?): Container =
reEncrypt(newPlainData) {
randomFill?.let { fill(it) }
}
@ -220,7 +243,7 @@ sealed class Container {
val encryptedKeys: List<EncryptedKey>, val encryptedMessage: UByteArray,
@Transient internal var mainKey: SymmetricKey? = null,
@Transient internal var knownPlainData: UByteArray? = null,
) : Container() {
) : Container() {
@Serializable
class EncryptedKey(val tag: KeyId, val cipherData: UByteArray) {
constructor(key: EncryptingKey, encodeMainKey: UByteArray) :
@ -240,6 +263,10 @@ sealed class Container {
knownPlainData?.let { decryptedData = it }
}
@Transient
override var decryptedWithKeyId: KeyId? = null
private set
override fun decryptWith(keyRing: UniversalRing): UByteArray? {
decryptedData?.let { return it }
for (key in keyRing.decryptingKeys) {
@ -251,10 +278,13 @@ sealed class Container {
key.decrypt(encryptedKey.cipherData).toByteArray()
)
}.getOrNull()?.let { k ->
println(k)
if (kotlin.runCatching { decryptedData = k.decrypt(encryptedMessage) }.isFailure)
throw InvalidContainerException()
decryptedWithKeyId = key.id
mainKey = k
if( key is SecretKey) {
authorisedByKey = Asymmetric.Message.decode(encryptedKey.cipherData).senderPublicKey
}
}
if (decryptedData != null) return decryptedData
}
@ -438,11 +468,11 @@ sealed class Container {
@Suppress("unused")
inline fun <reified T>encrypt(plainData: T, vararg keys: EncryptingKey): UByteArray =
inline fun <reified T> encrypt(plainData: T, vararg keys: EncryptingKey): UByteArray =
createWith(BipackEncoder.encode<T>(plainData).toUByteArray(), *keys).encoded
inline fun <reified T>decrypt(cipherData: UByteArray, vararg keys: DecryptingKey): T? =
decryptAsUBytes(cipherData,*keys)?.let { BipackDecoder.decode<T>(it.asByteArray())}
inline fun <reified T> decrypt(cipherData: UByteArray, vararg keys: DecryptingKey): T? =
decryptAsUBytes(cipherData, *keys)?.let { BipackDecoder.decode<T>(it.asByteArray()) }
fun decryptAsUBytes(cipherData: UByteArray, vararg keys: DecryptingKey): UByteArray? =
decode(cipherData).decryptWith(*keys)

View File

@ -7,7 +7,8 @@ import net.sergeych.crypto2.SymmetricKey.WithNonce
/**
* 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].
* keys are wise to transfer/save. Concrete implementations are, like [SymmetricKey] or
* [SecretKey].
*/
interface DecryptingKey : NonceBased, KeyInstance {
/**

View File

@ -5,7 +5,6 @@ import com.ionspin.kotlin.crypto.scalarmult.ScalarMultiplication
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.sergeych.bipack.BipackDecoder
/**
* The secret key used in public-key encryption; it is used to _decrypt_ data encrypted with its
@ -62,8 +61,7 @@ class SecretKey(
* Decrypt without a nonce as edwards curve decryption does not need it
*/
override fun decrypt(cipherData: UByteArray): UByteArray {
val message: Asymmetric.Message = BipackDecoder.decode(cipherData.asByteArray())
return message.decrypt(this)
return Asymmetric.Message.decode(cipherData).decrypt(this)
}
override val magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric

View File

@ -50,11 +50,40 @@ class ContainerTest {
assertNotNull(d)
assertContentEquals(data, d)
assertTrue { c1.isDecrypted }
assertEquals(p2.publicKey.id, c1.decryptedWithKeyId)
assertEquals(p1.publicKey, c1.authorisedByKey)
val data2 = "To push unpushinable".encodeToUByteArray()
val c2 = Container.decode(c.updateData(data2).encoded)
assertFalse { c2.isDecrypted }
assertContentEquals(data2, c2.decryptWith(p2.secretKey))
}
@Test
fun testMultiplePair() = runTest {
initCrypto()
val p1 = Asymmetric.generateKeys()
val p2 = Asymmetric.generateKeys()
val p3 = Asymmetric.generateKeys()
val p4 = Asymmetric.generateKeys()
val data = "sergeych, ohm many.".encodeToUByteArray()
val c = Container.createWith(data, p1.secretKey to p2.publicKey, p1.secretKey to p4.publicKey)
assertTrue { c.isDecrypted }
val c1 = Container.decode(c.encoded)
assertFalse { c1.isDecrypted }
assertNull(c1.decryptWith(p3.secretKey, p1.secretKey))
val d = c1.decryptWith(p3.secretKey, p4.secretKey)
assertNotNull(d)
assertContentEquals(data, d)
assertTrue { c1.isDecrypted }
assertEquals(p4.publicKey.id, c1.decryptedWithKeyId)
assertEquals(p1.publicKey, c1.authorisedByKey)
val data2 = "To push unpushinable".encodeToUByteArray()
val c2 = Container.decode(c.updateData(data2).encoded)
assertFalse { c2.isDecrypted }
}
@Test

View File

@ -164,7 +164,9 @@ class KeysTest {
assertContentEquals(pk1.keyBytes, pk1.id.binaryTag.take(32).toUByteArray())
assertContentEquals(pk1.keyBytes, sk1.id.binaryTag.take(32).toUByteArray())
assertEquals(pk1, pk1.id.id.asPublicKey)
}
@Test
fun asymmetricKeySerializationTest() = runTest {
initCrypto()
@ -271,6 +273,7 @@ class KeysTest {
// not hashed!
assertContentEquals(k.verifyingKey.keyBytes, dk2.id.binaryTag.take(32).toUByteArray())
assertContentEquals(k.verifyingKey.keyBytes, dk1.id.binaryTag.take(32).toUByteArray())
// and restored from id should be the same:
assertEquals( k.verifyingKey, dk2.id.id.asVerifyingKey)
}
}