forked from sergeych/crypto2
		
	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
 | 
			
		||||
@ -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]
 | 
			
		||||
@ -172,6 +178,7 @@ object Asymmetric {
 | 
			
		||||
        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()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -9,3 +9,7 @@ class DecryptionFailedException(text: String="can't decrypt: wrong key or tamper
 | 
			
		||||
                                cause: Throwable?=null): Crypto2Exception(text,cause)
 | 
			
		||||
 | 
			
		||||
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