diff --git a/build.gradle.kts b/build.gradle.kts index f506a39..03fbc61 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "net.sergeych" -version = "0.2.2-SNAPSHOT" +version = "0.3.1-SNAPSHOT" repositories { mavenCentral() diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/AsymmetricKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/AsymmetricKey.kt index 4ff6758..c29719d 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/AsymmetricKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/AsymmetricKey.kt @@ -6,6 +6,7 @@ import com.ionspin.kotlin.crypto.box.crypto_box_NONCEBYTES import com.ionspin.kotlin.crypto.scalarmult.ScalarMultiplication import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +import net.sergeych.bipack.BipackDecoder import net.sergeych.crypto2.Asymmetric.Message import net.sergeych.crypto2.Asymmetric.PublicKey import net.sergeych.crypto2.Asymmetric.SecretKey @@ -94,7 +95,7 @@ object Asymmetric { /** * The generated key pair. See [generateKeys] */ - data class KeyPair(val secretKey: SecretKey,val senderKey: PublicKey) + data class KeyPair(val secretKey: SecretKey, val senderKey: PublicKey) /** * Generate a new random pair of public and secret keys. @@ -107,6 +108,11 @@ object Asymmetric { private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES) + fun randomSecretKey(): SecretKey = generateKeys().secretKey + + @Suppress("unused") + val nonceBytesLength = crypto_box_NONCEBYTES + /** * The public key used as the recipient for [Message] (see [SecretKey.encrypt], etc.). It also * could be used to encrypt anonymous messages with [encryptAnonymously] @@ -143,7 +149,7 @@ object Asymmetric { val keyBytes: UByteArray, @Transient val _cachedPublicKey: PublicKey? = null, - ) { + ) : DecryptingKey { /** * Encrypt the message for [recipient]. The [Message] will include the our [publicKey] @@ -151,7 +157,7 @@ object Asymmetric { * * The message is authenticated by this secret key. */ - fun encrypt(plainData: UByteArray,recipient: PublicKey): Message = + fun encrypt(plainData: UByteArray, recipient: PublicKey): Message = createMessage(this, recipient, plainData) /** @@ -169,9 +175,10 @@ object Asymmetric { * Decrypt using [senderPublicKey] as a sender key (overriding the [Message.senderPublicKey] if set). * See [decrypt] for more. */ - fun decryptWithSenderKey(message: Message,senderPublicKey: PublicKey): UByteArray = - message.decryptWithSenderKey(senderPublicKey,this) + fun decryptWithSenderKey(message: Message, senderPublicKey: PublicKey): UByteArray = + message.decryptWithSenderKey(senderPublicKey, this) + @Transient private var cachedPublicKey: PublicKey? = _cachedPublicKey /** @@ -192,6 +199,26 @@ object Asymmetric { override fun hashCode(): Int { return keyBytes.hashCode() } + + /** + * Nonce-based decryption is impossible, it is already included in message + */ + override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray = decrypt(cipherData) + + /** + * Decrypt without a nonce as edwards curve decryption does not need it + */ + override fun decrypt(cipherData: UByteArray): UByteArray { + val message: Message = BipackDecoder.decode(cipherData.toByteArray()) + return message.decrypt(this) + } + + override val decryptingTag: KeyTag by lazy { + KeyTag(KeysMagickNumbers.defaultAssymmetric, blake2b(publicKey.keyBytes)) + } + + override val nonceBytesLength: Int + get() = 0 } } \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt b/src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt new file mode 100644 index 0000000..868b690 --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt @@ -0,0 +1,85 @@ +package net.sergeych.crypto2 + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import net.sergeych.bintools.CRC +import net.sergeych.bintools.CRC8 +import net.sergeych.mp_tools.decodeBase64Url +import kotlin.random.Random + +@Serializable +class BinaryId( + val id: UByteArray, +) : Comparable { + + class InvalidException(text: String) : IllegalArgumentException(text) + class IncomparableException(text: String) : IllegalArgumentException(text) + + @Transient + val magick: Int = run { + if (id.size < 4) throw InvalidException("BinaryId is too short") + val crc = id.last() + val rest = id.dropLast(1).toUByteArray() + if (CRC.crc8(rest) != crc) + throw InvalidException("Bad BinaryId CRC") + + rest.last().toInt() + } + + private val innerData: UByteArray by lazy { id.sliceArray( 1..< id.size-1 ) } + + override fun toString(): String = id.encodeToBase64Url() + + override fun compareTo(other: BinaryId): Int { + if (other.magick != magick) throw IncomparableException("Mask mismatch (my=$magick their=${other.magick})") + val id1 = other.id + if (id1.size != id.size) throw IncomparableException("different sizes") + for ((a, b) in innerData.zip(other.innerData)) { + if (a < b) return -1 + if (a > b) return 1 + } + return 0 + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BinaryId) return false + + if (!id.contentEquals(other.id)) return false + + return true + } + + override fun hashCode(): Int { + return id.contentHashCode() + } + + + companion object { + + @Suppress("unused") + fun fromString(str: String): BinaryId = + BinaryId(str.decodeBase64Url().toUByteArray()) + + + fun createFromBytes(magick: Int, bytes: ByteArray): BinaryId = createFromUBytes(magick, bytes.toUByteArray()) + + fun createFromUBytes(magick: Int, bytes: UByteArray): BinaryId { + val crc = CRC8() + val mn = magick.toUByte() + crc.update(bytes) + crc.update(mn) + return BinaryId(UByteArray(bytes.size + 2).also { + bytes.copyInto(it, 0) + val n = bytes.size + it[n] = mn + it[n + 1] = crc.value + }) + } + + @Suppress("unused") + fun createRandom(magickNumber: Int, size: Int=16) = + createFromBytes(magickNumber, Random.Default.nextBytes(size-2)) + + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt new file mode 100644 index 0000000..cd6bcf1 --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt @@ -0,0 +1,214 @@ +//package net.sergeych.crypto2 +// +//import kotlinx.serialization.Serializable +// +//@Serializable +//sealed class Container { +// +// +// /** +// * General error while unpacking and decrypting the container +// */ +// open class Error(reason: String = "failed to process the container") : IllegalArgumentException(reason) +// +// /** +// * Decryption failed +// */ +// class DecryptionError : Error("failed to decrypt the container") +// +// /** +// * Packed container seems to be corrupted +// */ +// class StructureError : Error("illegal container structure") +// +// /** +// * Key Ids for the key that can decrypt the container +// */ +// abstract val keyIds: Set +// abstract fun decrypt(key: DecryptingKey): ByteArray +// +// abstract fun update(keyRing: IdentifiableKeyring, newData: ByteArray): ByteArray? +// +// fun decrypt(keyRing: IdentifiableKeyring): Pair? { +// // Fast search: look for a key that shurely should +// for (k in keyRing.byIdAndSymmetrics(keyIds)) { +// try { +// if( k is DecryptingKey ) +// return k to decrypt(k) +// } catch (x: Throwable) { +// // This means our key is not ok. It a rare but possible collision +// } +// } +//// // some symmetric key can sometimes decrypt even if their ID is wrong, so +//// // we check them all: +//// for (k in keyRing.keys) { +//// if (k is SymmetricKey) { +//// try { +//// return k to decrypt(k) +//// } catch (x: Throwable) { +//// // it's ok. we just tried +//// } +//// } +//// } +// return null +// } +// +// @Serializable +// @SerialName("single") +// data class Single(val keyId: KeyIdentity, val ciphertext: ByteArray) : Container() { +// override val keyIds by lazy { setOf(keyId) } +// override fun decrypt(key: DecryptingKey): ByteArray = try { +// key.etaDecrypt(ciphertext) +// } catch (x: Exception) { +// throw DecryptionError() +// } +// +// override fun update(keyRing: IdentifiableKeyring, newData: ByteArray): ByteArray? { +// return decrypt(keyRing)?.let { (k, _) -> +// if (k !is EncryptingKey) throw IllegalArgumentException("key is not suitable for re-encryption") +// encryptData(newData, k) +// } +// } +// } +// +// @Serializable +// data class EncryptedKey(val id: KeyIdentity, val encryptedKey: ByteArray) +// +// @Serializable +// @SerialName("multi") +// data class Multiple(val keys: List, val ciphertext: ByteArray) : Container() { +// override val keyIds: Set by lazy { keys.map { it.id }.toSet() } +// +// override fun decrypt(key: DecryptingKey): ByteArray { +// for (k in keys) { +// // Simple: we just use ID +// // Complex: wrong ID with symmetric key that can decrypt: +// if (key is SymmetricKey || k.id == key.id) { +// try { +// val dataKey = SymmetricKeys.create(key.etaDecrypt(k.encryptedKey), k.id) +// return dataKey.etaDecrypt(ciphertext) +// } catch (x: Throwable) { +// // might be wrong symmetric key id +// if( key !is SymmetricKey) throw DecryptionError() +// } +// } +// } +// throw IllegalArgumentException("the key does not open this container") +// } +// +// override fun update(keyRing: IdentifiableKeyring, newData: ByteArray): ByteArray? { +// for (k in keys) { +// for (key in keyRing[k.id]) { +// if (key is DecryptingKey) { +// val dataKey = SymmetricKeys.create(key.etaDecrypt(k.encryptedKey), k.id) +// return BossEncoder.encode(Multiple(keys, dataKey.etaEncrypt(newData)) as Container) +// } +// } +// } +// return null +// } +// } +// +// +//// return decrypt(keyRing)?.let { (key,_) -> +//// if(key !is EncryptingKey) throw IllegalArgumentException("key is not suitable for re-encryption") +//// if(key !is DecryptingKey) throw IllegalArgumentException("key is not suitable for re-encryption (not decrypting!)") +//// val dataKey = SymmetricKeys.create( key.etaDecrypt(k.encryptedKey), k.id) +//// encryptData(newData,k) +//// } +//// } +//// } +// +// companion object : Loggable by LogTag("CRCON") { +// /** +// * Create single-key container. Most often you should call [encrypt] instead. +// */ +// fun single(payload: ByteArray, key: EncryptingKey): ByteArray { +// return BossEncoder.encode(Single(key.id, key.etaEncrypt(payload)) as Container) +// } +// +// /** +// * Create nultiple keys container event with one key. Most often you should not call it but +// * use [encrypt] instead. +// */ +// fun multi(payload: ByteArray, keys: List): ByteArray { +// val dataKey = SymmetricKeys.random() +// return BossEncoder.encode( +// Multiple( +// keys.map { EncryptedKey(it.id, it.etaEncrypt(dataKey.packed)) }, +// dataKey.etaEncrypt(payload) +// ) as Container +// ) +// } +// +// /** +// * Preferred method to create encrypted container. Selects the type automatically. Decrypt it with +// * any of the provided key (their [DecryptingKey] counterparts) with [decrypt] +// * +// * @param payload data to encrypt. Could be `String`, `ByteArray` or any serializable type. Can't be null. +// * @param keys one or more keys to encrypt with. +// * @return encryped and packed container +// */ +// inline fun encrypt(payload: T, vararg keys: EncryptingKey): ByteArray = +// encryptData(BossEncoder.encode(payload), *keys) +// +// /** +// * Encrypt payload as is, without boss serialization. See [encrypt] for details. +// * @return encrypted packed container +// */ +// fun encryptData(payload: ByteArray, vararg keys: EncryptingKey): ByteArray = +// when (keys.size) { +// 0 -> throw IllegalArgumentException("provide at least one key") +// 1 -> single(payload, keys[0]) +// else -> multi(payload, keys.toList()) +// } +// +// /** +// * Update packed container using proper key in the ring with a new payload. This function os useful +// * when the container could be multiple (but it works with any) and we know only one of its keys, +// * but want to change the data. It is possible as any of the keys could be used to re-encrypt data +// * that _will be available to all other keys_. +// * @param payload packed container +// * @param keyRing keyring which should contain at least one key that can decrypt the contatiner +// * @param packed new payload to encrypt +// * @return packed encrypted container with new payload or null if keyring can't decrypt it +// */ +// inline fun update(packed: ByteArray, keyRing: IdentifiableKeyring, payload: T): ByteArray? { +// return packed.decodeBoss().update(keyRing, BossEncoder.encode(payload)) +// } +// +// /** +// * Try to decrypt a container using some keys. +// * +// * @param keys to try to open the container. +// * @return the decrypted paylaod on success or null if no keys could open it. +// */ +// inline fun decrypt(packed: ByteArray, vararg keys: DecryptingKey): T? = +// decrypt(packed, Keyring(*keys)) +// +// /** +// * decrypt the container using leys in a keyring. +// * @return recrypted payload or null if no key from a keyring can open it. +// */ +// inline fun decrypt(packed: ByteArray, ring: IdentifiableKeyring): T? { +// return decryptAsBytes(packed, ring)?.decodeBoss() +// } +// +// fun decryptAsBytes(packed: ByteArray, ring: IdentifiableKeyring): ByteArray? = +// protect { +// packed.decodeBoss().decrypt(ring)?.second +// } +// +// fun decryptAsBytes(packed: ByteArray, vararg keys: DecryptingKey): ByteArray? = +// protect { +// packed.decodeBoss().decrypt(Keyring(*keys))?.second +// } +// +// private inline fun protect(f: () -> T): T = +// try { +// f() +// } catch (t: Throwable) { +// throw StructureError() +// } +// } +//} diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/DecryptingKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/DecryptingKey.kt index 19db003..c8d6827 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/DecryptingKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/DecryptingKey.kt @@ -28,7 +28,7 @@ interface DecryptingKey : NonceBased { fun decryptString(cipherData: UByteArray): String = decrypt(cipherData).decodeFromUByteArray() - val decryptingTag: UByteArray + val decryptingTag: KeyTag } diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/EncryptingKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/EncryptingKey.kt index 6b8a4fd..66f7df1 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/EncryptingKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/EncryptingKey.kt @@ -27,7 +27,7 @@ interface EncryptingKey : NonceBased { fun encrypt(plainText: String,randomFill: IntRange? = null): UByteArray = encrypt(plainText.encodeToUByteArray(),randomFill) - val encryptingTag: UByteArray + val encryptingTag: KeyTag fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange? = null): UByteArray } diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Hash.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Hash.kt index 63c8cfe..e1524b5 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Hash.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Hash.kt @@ -14,3 +14,4 @@ enum class Hash(val perform: (UByteArray)->UByteArray) { fun blake2b(src: UByteArray): UByteArray = Hash.Blake2b.perform(src) fun blake2b2l(src: UByteArray): UByteArray = blake2b(blake2b(src) + src) +fun blake2b3l(src: UByteArray): UByteArray = blake2b(blake2b2l(src) + src) diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/KeyDerivationParams.kt b/src/commonMain/kotlin/net/sergeych/crypto2/KeyDerivationParams.kt new file mode 100644 index 0000000..c6ddd9d --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/KeyDerivationParams.kt @@ -0,0 +1,6 @@ +package net.sergeych.crypto2 + +import kotlinx.serialization.Serializable + +@Serializable +class KeyDerivationParams() \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/KeyTag.kt b/src/commonMain/kotlin/net/sergeych/crypto2/KeyTag.kt new file mode 100644 index 0000000..5cbe0da --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/KeyTag.kt @@ -0,0 +1,28 @@ +package net.sergeych.crypto2 + +import kotlinx.serialization.Serializable + +/** + * Tag that identifies in some way the _decrypting key_. Is used in keyrings and + * containers to fast find a proper key + */ +@Serializable +data class KeyTag(val id: BinaryId,val kdp: KeyDerivationParams?=null ) { + + val tag: UByteArray by lazy { id.id } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is KeyTag) return false + return id == other.id + } + + override fun hashCode(): Int { + return id.hashCode() + } + + override fun toString() = id.toString() + + constructor(magickNumber: KeysMagickNumbers, data: UByteArray,kdp: KeyDerivationParams?=null) + : this(BinaryId.createFromUBytes(magickNumber.number, data), kdp) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt b/src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt new file mode 100644 index 0000000..14ee677 --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt @@ -0,0 +1,16 @@ +package net.sergeych.crypto2 + +enum class KeysMagickNumbers(val number: Int) { + defaultAssymmetric(0), + defaultSymmetric(1), + defaultSession(2), + ; + + companion object { + + val forNumber = entries.map { it.number to it }.toMap() + + @Suppress("unused") + fun findFor(binaryId: BinaryId): KeysMagickNumbers? = forNumber[binaryId.magick] + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SafeKeyExchange.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SafeKeyExchange.kt index 84cc297..2906140 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SafeKeyExchange.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SafeKeyExchange.kt @@ -4,14 +4,6 @@ import com.ionspin.kotlin.crypto.keyexchange.KeyExchange import kotlinx.serialization.Serializable import net.sergeych.crypto2.SafeKeyExchange.SessionKey -/** - * Bidirectional key, not necessarily symmetric. It could be asymmetric too. Examples: - * - * - [SafeKeyExchange] provides _asymmetric_ [SafeKeyExchange.SessionKey] - * - [SymmetricKey] is the symmetric cipher key - */ -interface CipherKey : EncryptingKey, DecryptingKey - /** * Safe (and typesafe) key exchange based on the x25519 protocol. * @@ -42,7 +34,7 @@ class SafeKeyExchange { val sendingKey: EncryptingKey, val receivingKey: DecryptingKey, val isClient: Boolean, - ) : CipherKey, EncryptingKey by sendingKey, DecryptingKey by receivingKey { + ) : EncryptingKey by sendingKey, DecryptingKey by receivingKey { override val nonceBytesLength: Int = sendingKey.nonceBytesLength @@ -55,11 +47,30 @@ class SafeKeyExchange { @Suppress("unused") val sessionTag: UByteArray by lazy { if (!isClient) - blake2b(decryptingTag + encryptingTag) + blake2b(decryptingTag.tag + encryptingTag.tag) else - blake2b(encryptingTag + decryptingTag) + blake2b(encryptingTag.tag + decryptingTag.tag) } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is SessionKey) return false + + if (sendingKey != other.sendingKey) return false + if (receivingKey != other.receivingKey) return false + if (isClient != other.isClient) return false + + return true + } + + override fun hashCode(): Int { + var result = sendingKey.hashCode() + result = 31 * result + receivingKey.hashCode() + result = 31 * result + isClient.hashCode() + return result + } + + } /** diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt index 906144a..03e56aa 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt @@ -20,7 +20,7 @@ import net.sergeych.bipack.BipackEncoder @Serializable class SymmetricKey( val keyBytes: UByteArray, -) : CipherKey { +) : EncryptingKey, DecryptingKey { /** * @suppress @@ -55,8 +55,8 @@ class SymmetricKey( override val nonceBytesLength: Int = nonceByteLength - override val encryptingTag: UByteArray by lazy { blake2b2l(keyBytes) } - override val decryptingTag: UByteArray get() = encryptingTag + override val encryptingTag by lazy { KeyTag(KeysMagickNumbers.defaultSymmetric,blake2b3l(keyBytes)) } + override val decryptingTag get() = encryptingTag override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray = protectDecryption { @@ -64,6 +64,18 @@ class SymmetricKey( .data } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is SymmetricKey) return false + + return keyBytes contentEquals other.keyBytes + } + + override fun hashCode(): Int { + return keyBytes.hashCode() + } + + companion object { /** * Create a secure random symmetric key. diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt new file mode 100644 index 0000000..5d96a5a --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt @@ -0,0 +1,55 @@ +package net.sergeych.crypto2 + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +sealed class UniversalKey : DecryptingKey { + + abstract val tag: KeyTag + + + + @Serializable + @SerialName("sy") + data class Symmetric(val key: SymmetricKey) : UniversalKey(), EncryptingKey by key, DecryptingKey by key { + @Transient + override val tag: KeyTag = key.encryptingTag + + @Transient + override val nonceBytesLength: Int = key.nonceBytesLength + + override fun toString() = "U.Sym:$tag" + } + + @Serializable + @SerialName("sn") + data class Session(val key: SafeKeyExchange.SessionKey) : UniversalKey(), EncryptingKey by key, + DecryptingKey by key { + @Transient + override val tag: KeyTag = key.encryptingTag + @Transient + override val nonceBytesLength: Int = key.nonceBytesLength + } + + @Serializable + @SerialName("se") + data class Secret(val key: Asymmetric.SecretKey) : UniversalKey(), DecryptingKey by key { + override val tag: KeyTag by lazy { key.decryptingTag } + override fun toString() = "U.Sec:$tag" + } + + + + companion object { + fun from(key: DecryptingKey) = + when (key) { + is Asymmetric.SecretKey -> Secret(key) + is SymmetricKey -> Symmetric(key) + is SafeKeyExchange.SessionKey -> Session(key) + else -> throw UnsupportedOperationException("can't create universal key from ${key::class.simpleName}") + } + } + +} diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt new file mode 100644 index 0000000..59509fb --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt @@ -0,0 +1,44 @@ +package net.sergeych.crypto2 + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +class UniversalRing( + private val keys: Collection +): Collection by keys { + constructor(vararg keys: UniversalKey) : this(keys.toSet()) + @Transient + val keySet = if( keys is Set ) keys else keys.toSet() + + private val byTag by lazy { keySet.associateBy { it.tag } } + + operator fun get(keyTag: KeyTag): UniversalKey? = byTag[keyTag] + + override operator fun contains(element: UniversalKey): Boolean = byTag.containsKey(element.tag) + + operator fun plus(key: UniversalKey): UniversalRing = + if( key in this ) this else UniversalRing(keySet + key ) + + operator fun minus(key: UniversalKey): UniversalRing = + if( key in this ) UniversalRing(keySet.filter { it.tag != key.tag }) else this + + operator fun minus(keyTag: KeyTag): UniversalRing = + if( keyTag in byTag ) UniversalRing(keySet.filter { it.tag != keyTag }) else this + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is UniversalRing) return false + + return size == other.size && keySet == other.keySet + } + + override fun toString(): String { + return "Kr[${keys.joinToString(",")}]" + } + + override fun hashCode(): Int { + return keySet.hashCode() + } + +} diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/exceptions.kt b/src/commonMain/kotlin/net/sergeych/crypto2/exceptions.kt index fdec44a..f19823b 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/exceptions.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/exceptions.kt @@ -8,4 +8,8 @@ open class Crypto2Exception(text: String,cause: Throwable?=null): RuntimeExcepti class DecryptionFailedException(text: String="can't decrypt: wrong key or tampered message", cause: Throwable?=null): Crypto2Exception(text,cause) -class NonceOutOfBoundsException(text: String): Crypto2Exception(text) \ No newline at end of file +class NonceOutOfBoundsException(text: String): Crypto2Exception(text) + +@Suppress("unused") +class NotSupportedException(text: String="operation is not supported for this object") + : Crypto2Exception(text, null) \ No newline at end of file diff --git a/src/commonTest/kotlin/KeysTest.kt b/src/commonTest/kotlin/KeysTest.kt index 786aa2c..1b18381 100644 --- a/src/commonTest/kotlin/KeysTest.kt +++ b/src/commonTest/kotlin/KeysTest.kt @@ -3,6 +3,7 @@ import com.ionspin.kotlin.crypto.util.encodeToUByteArray import kotlinx.coroutines.test.runTest import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import net.sergeych.bipack.BipackEncoder import net.sergeych.crypto2.* import net.sergeych.utools.now import net.sergeych.utools.pack @@ -153,6 +154,11 @@ class KeysTest { assertContentEquals(plain, m.decrypt(sk1)) } + val x1 = BipackEncoder.encode(Asymmetric.createMessage(sk0, pk1, plain)) + val x2 = BipackEncoder.encode(Asymmetric.createMessage(sk0, pk1, plain)) + + assertFalse { x1 contentEquals x2 } + } @Test fun asymmetricKeySerializationTest() = runTest { diff --git a/src/commonTest/kotlin/RingTest.kt b/src/commonTest/kotlin/RingTest.kt new file mode 100644 index 0000000..19f45e7 --- /dev/null +++ b/src/commonTest/kotlin/RingTest.kt @@ -0,0 +1,55 @@ +import com.ionspin.kotlin.crypto.util.encodeToUByteArray +import kotlinx.coroutines.test.runTest +import net.sergeych.bintools.toDump +import net.sergeych.bipack.BipackDecoder +import net.sergeych.bipack.BipackEncoder +import net.sergeych.crypto2.* +import kotlin.test.* + +class RingTest { + + @Test + fun testCreationAndSerialization() = runTest { + initCrypto() + + val y1 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) + val y2 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) + assertEquals(y1, y2) + + val e1 = Asymmetric.randomSecretKey() + val e2: Asymmetric.SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1)) + assertEquals(e1, e2) + + val k1 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())) + val k11 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())) + + assertEquals(k1, k11) + + + val k2 = UniversalKey.from(Asymmetric.randomSecretKey()) + val k3 = UniversalKey.from(Asymmetric.randomSecretKey()) +// + val r = UniversalRing(k1, k2) +// val r = UniversalRing(k1) + assertContains(r, k2) + assertContains(r, k1) + assertFalse { k3 in r } + + println(Asymmetric.randomSecretKey().keyBytes.size) + println(BipackEncoder.encode(Asymmetric.randomSecretKey()).size) + val encoded = BipackEncoder.encode(r) + println(encoded.toDump()) + println(encoded.size) + assertTrue { encoded.size < 80 } + val r2: UniversalRing = BipackDecoder.decode(encoded) + assertContains(r2, k2) + assertContains(r2, k1) + assertFalse { k3 in r2 } + + println(r) + println(r2) + + assertEquals(r, r2) + } + +} \ No newline at end of file diff --git a/src/commonTest/kotlin/ToolsTest.kt b/src/commonTest/kotlin/ToolsTest.kt index 4319a5f..d50f3a6 100644 --- a/src/commonTest/kotlin/ToolsTest.kt +++ b/src/commonTest/kotlin/ToolsTest.kt @@ -1,5 +1,8 @@ import kotlinx.coroutines.test.runTest -import net.sergeych.crypto2.* +import net.sergeych.crypto2.NumericNonce +import net.sergeych.crypto2.createContrail +import net.sergeych.crypto2.initCrypto +import net.sergeych.crypto2.isValidContrail import kotlin.test.* class ToolsTest { @@ -20,8 +23,8 @@ class ToolsTest { val counter = 1031 val x1 = nn.withInt(0) val x2 = nn.withInt(counter) - println(x1.toDump()) - println(x2.toDump()) +// println(x1.toDump()) +// println(x2.toDump()) val t = (x1[0] xor x2[0]).toInt() + ((x1[1] xor x2[1]).toInt() shl 8) assertEquals(counter, t) assertContentEquals(x1.drop(2), x2.drop(2))