Container started
This commit is contained in:
		
							parent
							
								
									e4e3b5ba8b
								
							
						
					
					
						commit
						e2916d0fe2
					
				@ -7,6 +7,7 @@ import com.ionspin.kotlin.crypto.scalarmult.ScalarMultiplication
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.Transient
 | 
			
		||||
import net.sergeych.bipack.BipackDecoder
 | 
			
		||||
import net.sergeych.bipack.BipackEncoder
 | 
			
		||||
import net.sergeych.crypto2.Asymmetric.Message
 | 
			
		||||
import net.sergeych.crypto2.Asymmetric.PublicKey
 | 
			
		||||
import net.sergeych.crypto2.Asymmetric.SecretKey
 | 
			
		||||
@ -32,7 +33,7 @@ object Asymmetric {
 | 
			
		||||
    /**
 | 
			
		||||
     * Encrypted message holder.
 | 
			
		||||
     *
 | 
			
		||||
     * Do not instantiate it directly, use [SecretKey.encrypt], [PublicKey.encryptAnonymously] or [createMessage]
 | 
			
		||||
     * Do not instantiate it directly, use [PublicKey.encryptMessage], [PublicKey.encryptAnonymousMessage], etc.
 | 
			
		||||
     * instead. Also [SecretKey.decrypt] can be used to decrypt it same as [decrypt] or [decryptWithSenderKey].
 | 
			
		||||
     *
 | 
			
		||||
     * To successfully decrypt the message, it is necessary to know a sender public key, and non-secret nonce.
 | 
			
		||||
@ -42,13 +43,12 @@ object Asymmetric {
 | 
			
		||||
    class Message(
 | 
			
		||||
        private val nonce: UByteArray,
 | 
			
		||||
        private val encryptedMessage: UByteArray,
 | 
			
		||||
        val senderPublicKey: PublicKey? = null,
 | 
			
		||||
        val senderPublicKey: PublicKey,
 | 
			
		||||
    ) {
 | 
			
		||||
        /**
 | 
			
		||||
         * Decrypt the message, same as [SecretKey.decrypt]
 | 
			
		||||
         */
 | 
			
		||||
        fun decrypt(recipientKey: SecretKey): UByteArray {
 | 
			
		||||
            check(senderPublicKey != null)
 | 
			
		||||
            return decryptWithSenderKey(senderPublicKey, recipientKey)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -58,11 +58,15 @@ object Asymmetric {
 | 
			
		||||
         */
 | 
			
		||||
        fun decryptWithSenderKey(senderKey: PublicKey, recipientKey: SecretKey): UByteArray {
 | 
			
		||||
            return try {
 | 
			
		||||
                Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
 | 
			
		||||
                WithFill.decode(
 | 
			
		||||
                    Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
 | 
			
		||||
                )
 | 
			
		||||
            } catch (_: BoxCorruptedOrTamperedDataException) {
 | 
			
		||||
                throw DecryptionFailedException()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val encoded: UByteArray by lazy { BipackEncoder.encode(this).toUByteArray() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -72,30 +76,27 @@ object Asymmetric {
 | 
			
		||||
     * The authenticated encryption is used, is the message _is successfully decrypted_, it also means that
 | 
			
		||||
     * it was signed by the sender, whose public key is known at the decryption time.
 | 
			
		||||
     *
 | 
			
		||||
     * When it is important not to provide senders' key, use [PublicKey.encryptAnonymously].
 | 
			
		||||
     * When it is important not to provide senders' key, use [PublicKey.encryptAnonymousMessage].
 | 
			
		||||
     *
 | 
			
		||||
     * @param from the senders' secret key.
 | 
			
		||||
     * @param recipient the recipients' public key.
 | 
			
		||||
     * @param plainData data to encrypt
 | 
			
		||||
     * @param excludeSenderKey if set to true, senders' public key will not be included in the message.
 | 
			
		||||
     *      In this case, the recipient must know it from other sources to be able to decrypt the message.
 | 
			
		||||
     */
 | 
			
		||||
    fun createMessage(
 | 
			
		||||
    private fun createMessage(
 | 
			
		||||
        from: SecretKey, recipient: PublicKey, plainData: UByteArray,
 | 
			
		||||
        excludeSenderKey: Boolean = false,
 | 
			
		||||
        nonce: UByteArray = randomNonce(),
 | 
			
		||||
    ): Message {
 | 
			
		||||
        val nonce = randomNonce()
 | 
			
		||||
        return Message(
 | 
			
		||||
            nonce,
 | 
			
		||||
            Box.easy(plainData, nonce, recipient.keyBytes, from.keyBytes),
 | 
			
		||||
            if (excludeSenderKey) null else from.publicKey
 | 
			
		||||
            from.publicKey
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The generated key pair. See [generateKeys]
 | 
			
		||||
     */
 | 
			
		||||
    data class KeyPair(val secretKey: SecretKey, val senderKey: PublicKey)
 | 
			
		||||
    data class KeyPair(val secretKey: SecretKey, val publicKey: PublicKey)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generate a new random pair of public and secret keys.
 | 
			
		||||
@ -114,11 +115,18 @@ object Asymmetric {
 | 
			
		||||
    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]
 | 
			
		||||
     * The public key: [encryptMessage] so only a secret key owner can read it. Allows
 | 
			
		||||
     * anonymous [encryptAnonymousMessage] and signed [encryptMessage] encryption.
 | 
			
		||||
     *
 | 
			
		||||
     * Anonymous encryption is very slow in comparison.
 | 
			
		||||
     */
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class PublicKey(val keyBytes: UByteArray) {
 | 
			
		||||
 | 
			
		||||
        val tag: KeyTag by lazy {
 | 
			
		||||
            KeyTag(KeysMagickNumbers.defaultAssymmetric, blake2b(keyBytes))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override fun equals(other: Any?): Boolean {
 | 
			
		||||
            if (this === other) return true
 | 
			
		||||
            if (other !is PublicKey) return false
 | 
			
		||||
@ -126,19 +134,57 @@ object Asymmetric {
 | 
			
		||||
            return keyBytes contentEquals other.keyBytes
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override fun hashCode(): Int {
 | 
			
		||||
            return keyBytes.hashCode()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Create an anonymous message that could be decrypted only with the [SecretKey] that corresponds this.
 | 
			
		||||
         * Anonymous message uses one-time secret key, the public part of which is included into the
 | 
			
		||||
         * [Message], so the sender could not be identified.
 | 
			
		||||
         *
 | 
			
		||||
         * __Anonymous encryption is much slower__ as it generates new keys every time, use [encryptMessage]
 | 
			
		||||
         * when possible
 | 
			
		||||
         *
 | 
			
		||||
         * The authentication is used despite the anonymity, and the fact of the successful decryption
 | 
			
		||||
         * proves that the message was not altered after creation.
 | 
			
		||||
         */
 | 
			
		||||
        fun encryptAnonymously(plainData: UByteArray): Message =
 | 
			
		||||
            createMessage(generateKeys().secretKey, this, plainData)
 | 
			
		||||
        fun encryptAnonymousMessage(plainData: UByteArray, randomFill: IntRange? = null): Message =
 | 
			
		||||
            encryptMessage(plainData,randomFill=randomFill)
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Anonymous encryption, see [encryptAnonymousMessage], to binary data. Sender could not be identified.
 | 
			
		||||
         */
 | 
			
		||||
        @Suppress("unused")
 | 
			
		||||
        fun encrypt(plainData: UByteArray, randomFill: IntRange? = null): UByteArray =
 | 
			
		||||
            encryptMessage(plainData, randomFill = randomFill).encoded
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Universal public-key encryption. Note that message authenticity is guaranteed if the decryption is successful
 | 
			
		||||
         * whether [senderKey] is provider, the latter only allow to positively identify the sender.
 | 
			
		||||
         *
 | 
			
		||||
         * @param plainData data to encrypt
 | 
			
		||||
         * @param nonce allows specifying exact nonce, default to random (safe)
 | 
			
		||||
         * @param senderKey key to authenticate sending party. It is safe and much faster to specify it,
 | 
			
		||||
         *      otherwise an anonymous key will be created for each encryption, also safe and anonymous, but slow.
 | 
			
		||||
         */
 | 
			
		||||
        fun encryptMessage(
 | 
			
		||||
            plainData: UByteArray,
 | 
			
		||||
            nonce: UByteArray = randomNonce(),
 | 
			
		||||
            senderKey: SecretKey = randomSecretKey(),
 | 
			
		||||
            randomFill: IntRange? = null,
 | 
			
		||||
        ) = createMessage(senderKey, this, WithFill.encode(plainData, randomFill), nonce)
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Encrypt message using the specified secret key as sender authentication. Recipient, the party having
 | 
			
		||||
         * [SecretKey] corresponding to this one, will be able to decrypt the message and be sure that [senderKey]
 | 
			
		||||
         * was the author and the message was not altered.
 | 
			
		||||
         */
 | 
			
		||||
        fun encryptMessage(plainData: UByteArray,
 | 
			
		||||
                           senderKey: SecretKey,
 | 
			
		||||
                           randomFill: IntRange? = null): Message =
 | 
			
		||||
            createMessage(senderKey, this, WithFill.encode(plainData, randomFill))
 | 
			
		||||
 | 
			
		||||
        override fun hashCode(): Int {
 | 
			
		||||
            return keyBytes.hashCode()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -151,19 +197,10 @@ object Asymmetric {
 | 
			
		||||
        val _cachedPublicKey: PublicKey? = null,
 | 
			
		||||
    ) : DecryptingKey {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Encrypt the message for [recipient]. The [Message] will include the our [publicKey]
 | 
			
		||||
         * and thus ready to be decrypted.
 | 
			
		||||
         *
 | 
			
		||||
         * The message is authenticated by this secret key.
 | 
			
		||||
         */
 | 
			
		||||
        fun encrypt(plainData: UByteArray, recipient: PublicKey): Message =
 | 
			
		||||
            createMessage(this, recipient, plainData)
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Decrypt with authentication checks the message which must have [Message.senderPublicKey] set.
 | 
			
		||||
         * Use [decryptWithSenderKey] otherwise. Note that the authenticated encryption is always use, even if
 | 
			
		||||
         * the [PublicKey.encryptAnonymously] was used to create a message, if it is successfully decrypted,
 | 
			
		||||
         * the [PublicKey.encryptAnonymousMessage] was used to create a message, if it is successfully decrypted,
 | 
			
		||||
         * it is guaranteed that the message was not altered after creation.
 | 
			
		||||
         *
 | 
			
		||||
         * @throws DecryptionFailedException If the message is tampered (changed after creation) or was not intended for us,
 | 
			
		||||
@ -213,9 +250,7 @@ object Asymmetric {
 | 
			
		||||
            return message.decrypt(this)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override val decryptingTag: KeyTag by lazy {
 | 
			
		||||
            KeyTag(KeysMagickNumbers.defaultAssymmetric, blake2b(publicKey.keyBytes))
 | 
			
		||||
        }
 | 
			
		||||
        override val tag: KeyTag by lazy { publicKey.tag }
 | 
			
		||||
 | 
			
		||||
        override val nonceBytesLength: Int
 | 
			
		||||
            get() = 0
 | 
			
		||||
@ -1,9 +1,148 @@
 | 
			
		||||
//package net.sergeych.crypto2
 | 
			
		||||
//
 | 
			
		||||
//import kotlinx.serialization.Serializable
 | 
			
		||||
//
 | 
			
		||||
//@Serializable
 | 
			
		||||
//sealed class Container {
 | 
			
		||||
package net.sergeych.crypto2
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.SerialName
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.Transient
 | 
			
		||||
import net.sergeych.bipack.BipackDecoder
 | 
			
		||||
import net.sergeych.bipack.BipackEncoder
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
The problem with container is the following. When we encrypt it with asymmetric key,
 | 
			
		||||
we provide public key as the recipient. But public key alone is not effective
 | 
			
		||||
as it is restricted to anonymous encryption which is very slow.
 | 
			
		||||
 | 
			
		||||
We might need an alternative to specify the (sender key,recipient key) pair also. We need a good
 | 
			
		||||
solution for it.
 | 
			
		||||
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
sealed class Container {
 | 
			
		||||
 | 
			
		||||
    class InvalidContainerException : Crypto2Exception("the container is invalid")
 | 
			
		||||
 | 
			
		||||
    abstract fun decryptWith(keyRing: UniversalRing): UByteArray?
 | 
			
		||||
 | 
			
		||||
    fun decryptWith(vararg decryptingKeys: DecryptingKey): UByteArray? =
 | 
			
		||||
        decryptWith(UniversalRing(*decryptingKeys))
 | 
			
		||||
 | 
			
		||||
    @Transient
 | 
			
		||||
    var decryptedData: UByteArray? = null
 | 
			
		||||
 | 
			
		||||
    val isDecrypted: Boolean get() = decryptedData != null
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    @SerialName("1")
 | 
			
		||||
    class Single(val keyTag: KeyTag, val encryptedMessage: UByteArray) : Container() {
 | 
			
		||||
        override fun decryptWith(keyRing: UniversalRing): UByteArray? {
 | 
			
		||||
            decryptedData?.let { return it }
 | 
			
		||||
            for (k in keyRing) {
 | 
			
		||||
                if (k.tag == keyTag) {
 | 
			
		||||
                    kotlin.runCatching { k.decrypt(encryptedMessage) }.getOrNull()?.let {
 | 
			
		||||
                        decryptedData = it
 | 
			
		||||
                        return it
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    @SerialName("*")
 | 
			
		||||
    class Multi(val encryptedKeys: List<EncryptedKey>, val encryptedMessage: UByteArray) : Container() {
 | 
			
		||||
        @Serializable
 | 
			
		||||
        class EncryptedKey(val tag: KeyTag, val cipherData: UByteArray)
 | 
			
		||||
 | 
			
		||||
        private var mainKey: SymmetricKey? = null
 | 
			
		||||
 | 
			
		||||
        override fun decryptWith(keyRing: UniversalRing): UByteArray? {
 | 
			
		||||
            decryptedData?.let { return it }
 | 
			
		||||
            for (key in keyRing) {
 | 
			
		||||
                val tag = key.tag
 | 
			
		||||
                for (encryptedKey in encryptedKeys) {
 | 
			
		||||
                    if (tag == encryptedKey.tag) {
 | 
			
		||||
                        kotlin.runCatching {
 | 
			
		||||
                            BipackDecoder.decode<SymmetricKey>(
 | 
			
		||||
                                key.decrypt(encryptedKey.cipherData).toByteArray()
 | 
			
		||||
                            )
 | 
			
		||||
                        }.getOrNull()?.let { k ->
 | 
			
		||||
                            if (kotlin.runCatching { decryptedData = k.decrypt(encryptedKey.cipherData) }.isFailure)
 | 
			
		||||
                                throw InvalidContainerException()
 | 
			
		||||
                            mainKey = k
 | 
			
		||||
                        }
 | 
			
		||||
                        if (decryptedData != null) return decryptedData
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    val encoded: UByteArray by lazy {
 | 
			
		||||
        BipackEncoder.encode(this).toUByteArray()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        class Builder internal constructor(private val plainData: UByteArray) {
 | 
			
		||||
            private val plainKeys = mutableListOf<EncryptingKey>()
 | 
			
		||||
            private val keyPairs = mutableListOf<Pair<Asymmetric.SecretKey?,Asymmetric.PublicKey>>()
 | 
			
		||||
            private var fillRange: IntRange? = null
 | 
			
		||||
 | 
			
		||||
            fun key(vararg keys: EncryptingKey) { plainKeys.addAll(keys) }
 | 
			
		||||
 | 
			
		||||
            fun key(vararg pairs: Pair<Asymmetric.SecretKey,Asymmetric.PublicKey>) {
 | 
			
		||||
                keyPairs.addAll(pairs)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fun key(vararg publicKeys: Asymmetric.PublicKey) {
 | 
			
		||||
                keyPairs.addAll(publicKeys.map { null to it })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Suppress("unused")
 | 
			
		||||
            fun fill(range: IntRange) {
 | 
			
		||||
                require(range.first >= 0 ) { "range must be positive"}
 | 
			
		||||
                fillRange = range
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fun build(): Container {
 | 
			
		||||
                return when( plainKeys.size + keyPairs.size ) {
 | 
			
		||||
                    0 -> throw IllegalArgumentException("Container needs at least one key")
 | 
			
		||||
                    1 -> {
 | 
			
		||||
                        plainKeys.firstOrNull()?.let {
 | 
			
		||||
                            Single(it.tag, it.encrypt(plainData, fillRange))
 | 
			
		||||
                        } ?: run {
 | 
			
		||||
                            val (sk, pk) = keyPairs.first()
 | 
			
		||||
                            Single(pk.tag, pk.encryptMessage(plainData,
 | 
			
		||||
                                senderKey = sk ?: Asymmetric.randomSecretKey(),
 | 
			
		||||
                                randomFill = fillRange).encoded)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else -> {
 | 
			
		||||
                        TODO("multikey")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun create(plainData: UByteArray,builder: Builder.()->Unit) =
 | 
			
		||||
            Builder(plainData).also { it.builder() }.build()
 | 
			
		||||
 | 
			
		||||
        fun create(plainData: UByteArray, vararg keys: EncryptingKey): Container =
 | 
			
		||||
            create(plainData) { key(*keys) }
 | 
			
		||||
 | 
			
		||||
        fun create(plainData: UByteArray, vararg keys: Pair<Asymmetric.SecretKey,Asymmetric.PublicKey>) =
 | 
			
		||||
            create(plainData) { key(*keys) }
 | 
			
		||||
 | 
			
		||||
        fun decode(encoded: UByteArray): Container {
 | 
			
		||||
            return BipackDecoder.decode(encoded.toByteArray())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
//    /**
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@ interface DecryptingKey : NonceBased {
 | 
			
		||||
 | 
			
		||||
    fun decryptString(cipherData: UByteArray): String = decrypt(cipherData).decodeFromUByteArray()
 | 
			
		||||
 | 
			
		||||
    val decryptingTag: KeyTag
 | 
			
		||||
    val tag: KeyTag
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ interface EncryptingKey : NonceBased {
 | 
			
		||||
    fun encrypt(plainText: String,randomFill: IntRange? = null): UByteArray =
 | 
			
		||||
        encrypt(plainText.encodeToUByteArray(),randomFill)
 | 
			
		||||
 | 
			
		||||
    val encryptingTag: KeyTag
 | 
			
		||||
    val tag: KeyTag
 | 
			
		||||
 | 
			
		||||
    fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange? = null): UByteArray
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -47,11 +47,14 @@ class SafeKeyExchange {
 | 
			
		||||
        @Suppress("unused")
 | 
			
		||||
        val sessionTag: UByteArray by lazy {
 | 
			
		||||
            if (!isClient)
 | 
			
		||||
                blake2b(decryptingTag.tag + encryptingTag.tag)
 | 
			
		||||
                blake2b(tag.tag + tag.tag)
 | 
			
		||||
            else
 | 
			
		||||
                blake2b(encryptingTag.tag + decryptingTag.tag)
 | 
			
		||||
                blake2b(tag.tag + tag.tag)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override val tag: KeyTag
 | 
			
		||||
            get() = if( isClient ) sendingKey.tag else receivingKey.tag
 | 
			
		||||
 | 
			
		||||
        override fun equals(other: Any?): Boolean {
 | 
			
		||||
            if (this === other) return true
 | 
			
		||||
            if (other !is SessionKey) return false
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,6 @@ package net.sergeych.crypto2
 | 
			
		||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
 | 
			
		||||
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import net.sergeych.bipack.BipackDecoder
 | 
			
		||||
import net.sergeych.bipack.BipackEncoder
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Symmetric key implements authenticated encrypting with random nonce and optional fill.
 | 
			
		||||
@ -32,36 +30,18 @@ class SymmetricKey(
 | 
			
		||||
        val nonce: UByteArray,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @suppress
 | 
			
		||||
     * add some random data to the end of the plain message
 | 
			
		||||
     */
 | 
			
		||||
    @Serializable
 | 
			
		||||
    data class WithFill(
 | 
			
		||||
        val data: UByteArray,
 | 
			
		||||
        val safetyFill: UByteArray? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray {
 | 
			
		||||
        require(nonce.size == nonceByteLength)
 | 
			
		||||
 | 
			
		||||
        val fill = randomFill?.let {
 | 
			
		||||
            require(it.start >= 0)
 | 
			
		||||
            randomUBytes(randomInt(it))
 | 
			
		||||
        }
 | 
			
		||||
        val filled = BipackEncoder.encode(WithFill(plainData, fill))
 | 
			
		||||
        return SecretBox.easy(filled.toUByteArray(), nonce, keyBytes)
 | 
			
		||||
        return SecretBox.easy(WithFill.encode(plainData, randomFill), nonce, keyBytes)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val nonceBytesLength: Int = nonceByteLength
 | 
			
		||||
 | 
			
		||||
    override val encryptingTag by lazy { KeyTag(KeysMagickNumbers.defaultSymmetric,blake2b3l(keyBytes)) }
 | 
			
		||||
    override val decryptingTag get() = encryptingTag
 | 
			
		||||
    override val tag by lazy { KeyTag(KeysMagickNumbers.defaultSymmetric,blake2b3l(keyBytes)) }
 | 
			
		||||
 | 
			
		||||
    override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =
 | 
			
		||||
        protectDecryption {
 | 
			
		||||
            BipackDecoder.decode<WithFill>(SecretBox.openEasy(cipherData, nonce, keyBytes).toByteArray())
 | 
			
		||||
                .data
 | 
			
		||||
            WithFill.decode(SecretBox.openEasy(cipherData, nonce, keyBytes))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    override fun equals(other: Any?): Boolean {
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ import kotlinx.serialization.Transient
 | 
			
		||||
@Serializable
 | 
			
		||||
sealed class UniversalKey : DecryptingKey {
 | 
			
		||||
 | 
			
		||||
    abstract val tag: KeyTag
 | 
			
		||||
//    abstract val tag: KeyTag
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ sealed class UniversalKey : DecryptingKey {
 | 
			
		||||
    @SerialName("sy")
 | 
			
		||||
    data class Symmetric(val key: SymmetricKey) : UniversalKey(), EncryptingKey by key, DecryptingKey by key {
 | 
			
		||||
        @Transient
 | 
			
		||||
        override val tag: KeyTag = key.encryptingTag
 | 
			
		||||
        override val tag: KeyTag = key.tag
 | 
			
		||||
 | 
			
		||||
        @Transient
 | 
			
		||||
        override val nonceBytesLength: Int = key.nonceBytesLength
 | 
			
		||||
@ -28,7 +28,7 @@ sealed class UniversalKey : DecryptingKey {
 | 
			
		||||
    data class Session(val key: SafeKeyExchange.SessionKey) : UniversalKey(), EncryptingKey by key,
 | 
			
		||||
        DecryptingKey by key {
 | 
			
		||||
        @Transient
 | 
			
		||||
        override val tag: KeyTag = key.encryptingTag
 | 
			
		||||
        override val tag: KeyTag = key.tag
 | 
			
		||||
        @Transient
 | 
			
		||||
        override val nonceBytesLength: Int = key.nonceBytesLength
 | 
			
		||||
    }
 | 
			
		||||
@ -36,7 +36,7 @@ sealed class UniversalKey : DecryptingKey {
 | 
			
		||||
    @Serializable
 | 
			
		||||
    @SerialName("se")
 | 
			
		||||
    data class Secret(val key: Asymmetric.SecretKey) : UniversalKey(), DecryptingKey by key {
 | 
			
		||||
        override val tag: KeyTag by lazy { key.decryptingTag }
 | 
			
		||||
        override val tag: KeyTag by lazy { key.tag }
 | 
			
		||||
        override fun toString() = "U.Sec:$tag"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ class UniversalRing(
 | 
			
		||||
    private val keys: Collection<UniversalKey>
 | 
			
		||||
): Collection<UniversalKey> by keys {
 | 
			
		||||
    constructor(vararg keys: UniversalKey) : this(keys.toSet())
 | 
			
		||||
    constructor(vararg keys: DecryptingKey) : this(keys.map { UniversalKey.from(it) }.toSet())
 | 
			
		||||
    @Transient
 | 
			
		||||
    val keySet = if( keys is Set<UniversalKey> ) keys else keys.toSet()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										37
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/WithFill.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/WithFill.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
package net.sergeych.crypto2
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import net.sergeych.bipack.BipackDecoder
 | 
			
		||||
import net.sergeych.bipack.BipackEncoder
 | 
			
		||||
import net.sergeych.crypto2.WithFill.Companion.decode
 | 
			
		||||
import net.sergeych.crypto2.WithFill.Companion.encode
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utility class to optionally extend a message with random bytes added _before the message bytes_ that
 | 
			
		||||
 * reduces chances to effective cryptanalysis from the first block. Use
 | 
			
		||||
 * [encode] and [decode] to process filled data.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
class WithFill private constructor(
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    val safetyFill: UByteArray = ubyteArrayOf(),
 | 
			
		||||
    val data: UByteArray,
 | 
			
		||||
) {
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Create binary data with specified fill
 | 
			
		||||
         */
 | 
			
		||||
        fun encode(data: UByteArray,range: IntRange? = null) =
 | 
			
		||||
            BipackEncoder.encode(WithFill(range?.let {
 | 
			
		||||
                require(range.first >= 0) { "range should not be negative" }
 | 
			
		||||
                randomUBytes(randomInt(it))
 | 
			
		||||
            } ?: ubyteArrayOf(), data)).toUByteArray()
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * extract binary data from filled
 | 
			
		||||
         */
 | 
			
		||||
        fun decode(data: UByteArray): UByteArray =
 | 
			
		||||
            BipackDecoder.decode<WithFill>(data.toByteArray()).data
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								src/commonTest/kotlin/ContainerTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/commonTest/kotlin/ContainerTest.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
			
		||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
 | 
			
		||||
import kotlinx.coroutines.test.runTest
 | 
			
		||||
import net.sergeych.crypto2.Asymmetric
 | 
			
		||||
import net.sergeych.crypto2.Container
 | 
			
		||||
import net.sergeych.crypto2.SymmetricKey
 | 
			
		||||
import net.sergeych.crypto2.initCrypto
 | 
			
		||||
import kotlin.test.*
 | 
			
		||||
 | 
			
		||||
class ContainerTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testSingle() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val syk1 = SymmetricKey.random()
 | 
			
		||||
        val syk2 = SymmetricKey.random()
 | 
			
		||||
        val data = "sergeych, ohm many.".encodeToUByteArray()
 | 
			
		||||
 | 
			
		||||
        val c = Container.create(data, syk1)
 | 
			
		||||
        assertFalse { c.isDecrypted }
 | 
			
		||||
        val c1 = Container.decode(c.encoded)
 | 
			
		||||
        assertFalse { c.isDecrypted }
 | 
			
		||||
        assertIs<Container.Single>(c)
 | 
			
		||||
 | 
			
		||||
        assertNull(c1.decryptWith(syk2))
 | 
			
		||||
        val d = c1.decryptWith(syk2, syk1)
 | 
			
		||||
        assertNotNull(d)
 | 
			
		||||
        assertContentEquals(data, d)
 | 
			
		||||
        assertTrue { c1.isDecrypted }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testSinglePair() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val p1 = Asymmetric.generateKeys()
 | 
			
		||||
        val p2 = Asymmetric.generateKeys()
 | 
			
		||||
        val p3 = Asymmetric.generateKeys()
 | 
			
		||||
        val data = "sergeych, ohm many.".encodeToUByteArray()
 | 
			
		||||
 | 
			
		||||
        val c = Container.create(data, p1.secretKey to p2.publicKey)
 | 
			
		||||
        assertFalse { c.isDecrypted }
 | 
			
		||||
        val c1 = Container.decode(c.encoded)
 | 
			
		||||
        assertFalse { c.isDecrypted }
 | 
			
		||||
        assertIs<Container.Single>(c)
 | 
			
		||||
 | 
			
		||||
        assertNull(c1.decryptWith(p3.secretKey))
 | 
			
		||||
        val d = c1.decryptWith(p3.secretKey, p2.secretKey)
 | 
			
		||||
        assertNotNull(d)
 | 
			
		||||
        assertContentEquals(data, d)
 | 
			
		||||
        assertTrue { c1.isDecrypted }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testSingleAsymmetric() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
//        val p1 = Asymmetric.generateKeys()
 | 
			
		||||
        val p2 = Asymmetric.generateKeys()
 | 
			
		||||
        val p3 = Asymmetric.generateKeys()
 | 
			
		||||
        val data = "sergeych, ohm many.".encodeToUByteArray()
 | 
			
		||||
 | 
			
		||||
        val c = Container.create(data) { key(p2.publicKey) }
 | 
			
		||||
        assertFalse { c.isDecrypted }
 | 
			
		||||
        val c1 = Container.decode(c.encoded)
 | 
			
		||||
        assertFalse { c.isDecrypted }
 | 
			
		||||
        assertIs<Container.Single>(c)
 | 
			
		||||
 | 
			
		||||
        assertNull(c1.decryptWith(p3.secretKey))
 | 
			
		||||
        val d = c1.decryptWith(p3.secretKey, p2.secretKey)
 | 
			
		||||
        assertNotNull(d)
 | 
			
		||||
        assertContentEquals(data, d)
 | 
			
		||||
        assertTrue { c1.isDecrypted }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testMultipleSymmetric() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val syk1 = SymmetricKey.random()
 | 
			
		||||
        val syk2 = SymmetricKey.random()
 | 
			
		||||
//        val syk3 = SymmetricKey.random()
 | 
			
		||||
        val data = "Translating the name 'Sergey Chernov' from Russian to archaic Sanskrit would be 'Ramo Krishna'"
 | 
			
		||||
            .encodeToUByteArray()
 | 
			
		||||
 | 
			
		||||
        val c = Container.create(data, syk1, syk2)
 | 
			
		||||
        assertFalse { c.isDecrypted }
 | 
			
		||||
        val c1 = Container.decode(c.encoded)
 | 
			
		||||
        assertFalse { c1.isDecrypted }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -3,7 +3,6 @@ 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
 | 
			
		||||
@ -140,25 +139,24 @@ class KeysTest {
 | 
			
		||||
 | 
			
		||||
        val plain = "The fake vaccine kills".encodeToUByteArray()
 | 
			
		||||
 | 
			
		||||
        var m = Asymmetric.createMessage(sk0, pk1, plain)
 | 
			
		||||
        var m = pk1.encryptMessage(plain, sk1)
 | 
			
		||||
        assertContentEquals(plain, m.decrypt(sk1))
 | 
			
		||||
        assertThrows<DecryptionFailedException> {
 | 
			
		||||
            assertContentEquals(plain, m.decrypt(sk2))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m = pk2.encryptAnonymously(plain)
 | 
			
		||||
        m = pk2.encryptAnonymousMessage(plain)
 | 
			
		||||
        assertContentEquals(plain, m.decrypt(sk2))
 | 
			
		||||
        assertContentEquals(plain, sk2.decrypt(m))
 | 
			
		||||
        assertContentEquals(plain, sk2.decrypt(sk1.encrypt(plain, pk2)))
 | 
			
		||||
//        assertContentEquals(plain, sk2.decrypt(sk1.encrypt(plain, pk2)))
 | 
			
		||||
        assertThrows<DecryptionFailedException> {
 | 
			
		||||
            assertContentEquals(plain, m.decrypt(sk1))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val x1 = BipackEncoder.encode(Asymmetric.createMessage(sk0, pk1, plain))
 | 
			
		||||
        val x2 = BipackEncoder.encode(Asymmetric.createMessage(sk0, pk1, plain))
 | 
			
		||||
        val x1 = pk1.encryptMessage(plain, sk1).encoded
 | 
			
		||||
        val x2 = pk1.encryptMessage(plain, sk1).encoded
 | 
			
		||||
 | 
			
		||||
        assertFalse { x1 contentEquals x2 }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    @Test
 | 
			
		||||
    fun asymmetricKeySerializationTest() = runTest {
 | 
			
		||||
 | 
			
		||||
@ -52,4 +52,9 @@ class RingTest {
 | 
			
		||||
        assertEquals(r, r2)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Ignore
 | 
			
		||||
    fun testKeysWithSameTags() {
 | 
			
		||||
        // it should be able to keep keys with same tags
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user