Universal keys. fixed bugs with keys serialization and equality
This commit is contained in:
parent
9027bb0e88
commit
f64412fdc7
@ -5,7 +5,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.2.2-SNAPSHOT"
|
||||
version = "0.3.1-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
85
src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt
Normal file
85
src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt
Normal file
@ -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<BinaryId> {
|
||||
|
||||
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))
|
||||
|
||||
}
|
||||
}
|
214
src/commonMain/kotlin/net/sergeych/crypto2/Container.kt
Normal file
214
src/commonMain/kotlin/net/sergeych/crypto2/Container.kt
Normal file
@ -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<KeyIdentity>
|
||||
// abstract fun decrypt(key: DecryptingKey): ByteArray
|
||||
//
|
||||
// abstract fun update(keyRing: IdentifiableKeyring, newData: ByteArray): ByteArray?
|
||||
//
|
||||
// fun decrypt(keyRing: IdentifiableKeyring): Pair<IdentifiableKey, ByteArray>? {
|
||||
// // 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<EncryptedKey>, val ciphertext: ByteArray) : Container() {
|
||||
// override val keyIds: Set<KeyIdentity> 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<EncryptingKey>): 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 <reified T> 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 <reified T> update(packed: ByteArray, keyRing: IdentifiableKeyring, payload: T): ByteArray? {
|
||||
// return packed.decodeBoss<Container>().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 <reified T> decrypt(packed: ByteArray, vararg keys: DecryptingKey): T? =
|
||||
// decrypt<T>(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 <reified T> decrypt(packed: ByteArray, ring: IdentifiableKeyring): T? {
|
||||
// return decryptAsBytes(packed, ring)?.decodeBoss<T>()
|
||||
// }
|
||||
//
|
||||
// fun decryptAsBytes(packed: ByteArray, ring: IdentifiableKeyring): ByteArray? =
|
||||
// protect {
|
||||
// packed.decodeBoss<Container>().decrypt(ring)?.second
|
||||
// }
|
||||
//
|
||||
// fun decryptAsBytes(packed: ByteArray, vararg keys: DecryptingKey): ByteArray? =
|
||||
// protect {
|
||||
// packed.decodeBoss<Container>().decrypt(Keyring(*keys))?.second
|
||||
// }
|
||||
//
|
||||
// private inline fun <reified T> protect(f: () -> T): T =
|
||||
// try {
|
||||
// f()
|
||||
// } catch (t: Throwable) {
|
||||
// throw StructureError()
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -28,7 +28,7 @@ interface DecryptingKey : NonceBased {
|
||||
|
||||
fun decryptString(cipherData: UByteArray): String = decrypt(cipherData).decodeFromUByteArray()
|
||||
|
||||
val decryptingTag: UByteArray
|
||||
val decryptingTag: KeyTag
|
||||
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,6 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class KeyDerivationParams()
|
28
src/commonMain/kotlin/net/sergeych/crypto2/KeyTag.kt
Normal file
28
src/commonMain/kotlin/net/sergeych/crypto2/KeyTag.kt
Normal file
@ -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)
|
||||
}
|
16
src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt
Normal file
16
src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt
Normal file
@ -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]
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
55
src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt
Normal file
55
src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt
Normal file
@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
44
src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt
Normal file
44
src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt
Normal file
@ -0,0 +1,44 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
@Serializable
|
||||
class UniversalRing(
|
||||
private val keys: Collection<UniversalKey>
|
||||
): Collection<UniversalKey> by keys {
|
||||
constructor(vararg keys: UniversalKey) : this(keys.toSet())
|
||||
@Transient
|
||||
val keySet = if( keys is Set<UniversalKey> ) 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()
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
class NonceOutOfBoundsException(text: String): Crypto2Exception(text)
|
||||
|
||||
@Suppress("unused")
|
||||
class NotSupportedException(text: String="operation is not supported for this object")
|
||||
: Crypto2Exception(text, null)
|
@ -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 {
|
||||
|
55
src/commonTest/kotlin/RingTest.kt
Normal file
55
src/commonTest/kotlin/RingTest.kt
Normal file
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user