RC1 class scheme refactoring
This commit is contained in:
		
							parent
							
								
									9efa763b45
								
							
						
					
					
						commit
						720532e14d
					
				@ -3,25 +3,21 @@ package net.sergeych.crypto2
 | 
			
		||||
import com.ionspin.kotlin.crypto.box.Box
 | 
			
		||||
import com.ionspin.kotlin.crypto.box.BoxCorruptedOrTamperedDataException
 | 
			
		||||
import com.ionspin.kotlin.crypto.box.crypto_box_NONCEBYTES
 | 
			
		||||
import com.ionspin.kotlin.crypto.scalarmult.ScalarMultiplication
 | 
			
		||||
import kotlinx.serialization.SerialName
 | 
			
		||||
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
 | 
			
		||||
import net.sergeych.crypto2.Asymmetric.generateKeys
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Public-key encryption implementation. Generally should be libsodium-compatible.
 | 
			
		||||
 * Public-key encryption implementation tools.
 | 
			
		||||
 *
 | 
			
		||||
 * ## How to
 | 
			
		||||
 *
 | 
			
		||||
 * - [Asymmetric.generateKeys] create a key pair. Keys are serializable.
 | 
			
		||||
 * - [SecretKey] provides authenticated encryption for a [PublicKey] receiver.
 | 
			
		||||
 * - [PublicKey] provides decryption and anonymous encryption.
 | 
			
		||||
 * - [Message] is a serializable container with encrypted message and all necessary data to decrypt it.
 | 
			
		||||
 * - [SecretKey.new] to create a secret key that includes [SecretKey.publicKey].
 | 
			
		||||
 *   [generateKeys] also makes the pair.
 | 
			
		||||
 * - [PublicKey] provides encryption, anonymous or authenticated.
 | 
			
		||||
 * - [SecretKey] provides authenticated decryption of what [PublicKey] was encrypted with.
 | 
			
		||||
 * - [Message] is a serializable container with all necessary data to decrypt public-key encrypted data it.
 | 
			
		||||
 *
 | 
			
		||||
 * __Algorithms:__
 | 
			
		||||
 *
 | 
			
		||||
@ -83,7 +79,7 @@ object Asymmetric {
 | 
			
		||||
     * @param recipient the recipients' public key.
 | 
			
		||||
     * @param plainData data to encrypt
 | 
			
		||||
     */
 | 
			
		||||
    private fun createMessage(
 | 
			
		||||
    internal fun createMessage(
 | 
			
		||||
        from: SecretKey, recipient: PublicKey, plainData: UByteArray,
 | 
			
		||||
        nonce: UByteArray = randomNonce(),
 | 
			
		||||
    ): Message {
 | 
			
		||||
@ -94,162 +90,13 @@ object Asymmetric {
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The generated key pair. See [generateKeys]
 | 
			
		||||
     */
 | 
			
		||||
    data class KeyPair(val secretKey: SecretKey, val publicKey: PublicKey)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generate a new random pair of public and secret keys.
 | 
			
		||||
     */
 | 
			
		||||
    fun generateKeys(): KeyPair {
 | 
			
		||||
        val p = Box.keypair()
 | 
			
		||||
        val pk = PublicKey(p.publicKey)
 | 
			
		||||
        return KeyPair(SecretKey(p.secretKey, pk), pk)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES)
 | 
			
		||||
 | 
			
		||||
    fun newSecretKey(): SecretKey = generateKeys().secretKey
 | 
			
		||||
    fun generateKeys() = SecretKey.generateKeys()
 | 
			
		||||
    fun newSecretKey() = SecretKey.new()
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    val nonceBytesLength = crypto_box_NONCEBYTES
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
    @SerialName("encp")
 | 
			
		||||
    class PublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingKey {
 | 
			
		||||
 | 
			
		||||
        override val magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric
 | 
			
		||||
        @Transient
 | 
			
		||||
        override val label: String = "pub"
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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 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")
 | 
			
		||||
        override fun encrypt(plainData: UByteArray, randomFill: IntRange?): UByteArray =
 | 
			
		||||
            encryptMessage(plainData, randomFill = randomFill).encoded
 | 
			
		||||
 | 
			
		||||
        override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray =
 | 
			
		||||
            encryptMessage(plainData, nonce = nonce, randomFill = randomFill).encoded
 | 
			
		||||
 | 
			
		||||
        override val nonceBytesLength: Int = Asymmetric.nonceBytesLength
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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 = newSecretKey(),
 | 
			
		||||
            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))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The secret key
 | 
			
		||||
     */
 | 
			
		||||
    @Serializable
 | 
			
		||||
    @SerialName("encs")
 | 
			
		||||
    class SecretKey(
 | 
			
		||||
        override val keyBytes: UByteArray,
 | 
			
		||||
        @Transient
 | 
			
		||||
        val _cachedPublicKey: PublicKey? = null,
 | 
			
		||||
    ) : DecryptingKey, UniversalKey() {
 | 
			
		||||
 | 
			
		||||
        @Transient
 | 
			
		||||
        override val label: String = "sec"
 | 
			
		||||
        /**
 | 
			
		||||
         * 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.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,
 | 
			
		||||
         */
 | 
			
		||||
        fun decrypt(message: Message): UByteArray = message.decrypt(this)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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)
 | 
			
		||||
 | 
			
		||||
        @Transient
 | 
			
		||||
        private var cachedPublicKey: PublicKey? = _cachedPublicKey
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The corresponding public key
 | 
			
		||||
         */
 | 
			
		||||
        val publicKey: PublicKey by lazy {
 | 
			
		||||
            if (cachedPublicKey != null)
 | 
			
		||||
                cachedPublicKey!!
 | 
			
		||||
            else
 | 
			
		||||
                PublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
 | 
			
		||||
                    .also { cachedPublicKey = it }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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 magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric
 | 
			
		||||
        override val id: KeyId by lazy { publicKey.id }
 | 
			
		||||
 | 
			
		||||
        override val nonceBytesLength: Int
 | 
			
		||||
            get() = 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
@ -22,8 +22,8 @@ import net.sergeych.crypto2.Container.Companion.createWith
 | 
			
		||||
 *
 | 
			
		||||
 * Some rules:
 | 
			
		||||
 *
 | 
			
		||||
 * When adding public key recipient, it is faster to use your known [Asymmetric.SecretKey], but you
 | 
			
		||||
 * can stay anonymous by just adding [Asymmetric.PublicKey] only.
 | 
			
		||||
 * When adding public key recipient, it is faster to use your known [SecretKey], but you
 | 
			
		||||
 * can stay anonymous by just adding [PublicKey] only.
 | 
			
		||||
 *
 | 
			
		||||
 * Put your data in [SealedBox] if you need to authenticate message origin and timestamp, then put
 | 
			
		||||
 * the sealed box in the [Container], this will conceal signers from attack. In the case you need to
 | 
			
		||||
@ -105,7 +105,7 @@ sealed class Container {
 | 
			
		||||
     * Add e key to the __decrypted__ container. The new container is also decrypted so you can add
 | 
			
		||||
     * more keys, etc.
 | 
			
		||||
     */
 | 
			
		||||
    operator fun plus(recipient: Asymmetric.PublicKey) = addRecipients { key(recipient) }
 | 
			
		||||
    operator fun plus(recipient: PublicKey) = addRecipients { key(recipient) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add e key to the __decrypted__ container. The new container is also decrypted so you can add
 | 
			
		||||
@ -117,7 +117,7 @@ sealed class Container {
 | 
			
		||||
     * Add e key to the __decrypted__ container. The new container is also decrypted so you can add
 | 
			
		||||
     * more keys, etc.
 | 
			
		||||
     */
 | 
			
		||||
    operator fun plus(pair: Pair<Asymmetric.SecretKey, Asymmetric.PublicKey>) = addRecipients { key(pair) }
 | 
			
		||||
    operator fun plus(pair: Pair<SecretKey, PublicKey>) = addRecipients { key(pair) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the data in the decrypted container. It keeps the same set of keys and update
 | 
			
		||||
@ -183,7 +183,7 @@ sealed class Container {
 | 
			
		||||
                    // otherwise, we don't know the encryption key and will try to derive it
 | 
			
		||||
                    // from the decryption key:
 | 
			
		||||
                    when (val k = decryptedWithKey!!) {
 | 
			
		||||
                        is Asymmetric.SecretKey -> {
 | 
			
		||||
                        is SecretKey -> {
 | 
			
		||||
                            key(k.publicKey)
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -226,12 +226,12 @@ sealed class Container {
 | 
			
		||||
            constructor(key: EncryptingKey, encodeMainKey: UByteArray) :
 | 
			
		||||
                    this(key.id, key.encrypt(encodeMainKey))
 | 
			
		||||
 | 
			
		||||
            constructor(sender: Asymmetric.SecretKey?, recipient: Asymmetric.PublicKey, encodeMainKey: UByteArray) :
 | 
			
		||||
            constructor(sender: SecretKey?, recipient: PublicKey, encodeMainKey: UByteArray) :
 | 
			
		||||
                    this(
 | 
			
		||||
                        recipient.id,
 | 
			
		||||
                        recipient.encryptMessage(
 | 
			
		||||
                            encodeMainKey,
 | 
			
		||||
                            senderKey = sender ?: Asymmetric.newSecretKey(),
 | 
			
		||||
                            senderKey = sender ?: SecretKey.new(),
 | 
			
		||||
                        ).encoded
 | 
			
		||||
                    )
 | 
			
		||||
        }
 | 
			
		||||
@ -316,8 +316,8 @@ sealed class Container {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Add one or more [Asymmetric.SecretKey] as sender authority coupled with [Asymmetric.PublicKey] as
 | 
			
		||||
             * a recipient. This is faster than anonymous usage of [Asymmetric.PublicKey] only
 | 
			
		||||
             * Add one or more [SecretKey] as sender authority coupled with [PublicKey] as
 | 
			
		||||
             * a recipient. This is faster than anonymous usage of [PublicKey] only
 | 
			
		||||
             */
 | 
			
		||||
            fun key(vararg pairs: AsymmetricEncryptionPair) {
 | 
			
		||||
                keyPairs.addAll(pairs)
 | 
			
		||||
@ -326,7 +326,7 @@ sealed class Container {
 | 
			
		||||
            /**
 | 
			
		||||
             * Add one or more public keys as recipients. This is slower than using pairs of sender -> recipient.
 | 
			
		||||
             */
 | 
			
		||||
            fun key(vararg publicKeys: Asymmetric.PublicKey) {
 | 
			
		||||
            fun key(vararg publicKeys: PublicKey) {
 | 
			
		||||
                keyPairs.addAll(publicKeys.map { null to it })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -387,7 +387,7 @@ sealed class Container {
 | 
			
		||||
                Single(
 | 
			
		||||
                    pk.id, pk.encryptMessage(
 | 
			
		||||
                        plainData,
 | 
			
		||||
                        senderKey = sk ?: Asymmetric.newSecretKey(),
 | 
			
		||||
                        senderKey = sk ?: SecretKey.new(),
 | 
			
		||||
                        randomFill = fillRange
 | 
			
		||||
                    ).encoded,
 | 
			
		||||
                    plainData,
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import kotlinx.serialization.Serializable
 | 
			
		||||
 * Important. `KeyId` of matching keys are the same, so you can use it to identify
 | 
			
		||||
 * and find matching keys in the [UniversalRing], etc. For example:
 | 
			
		||||
 *
 | 
			
		||||
 * - [Asymmetric.SecretKey] and [Asymmetric.PublicKey] from the same pair have the same `KeyId`, thus the former
 | 
			
		||||
 * - [SecretKey] and [PublicKey] from the same pair have the same `KeyId`, thus the former
 | 
			
		||||
 *   can decrypt what was encrypted with the latter.
 | 
			
		||||
 *
 | 
			
		||||
 * - [SigningSecretKey] and corresponding [VerifyingKey] have the same `KeyId`. Use it to pick a proper key for
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										72
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/PublicKey.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/PublicKey.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
			
		||||
package net.sergeych.crypto2
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.SerialName
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.Transient
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The public for public-key encryption. It encrypts messages that can only be decrypted with corresponding
 | 
			
		||||
 * [SecretKey].
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
@SerialName("encp")
 | 
			
		||||
class PublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingKey {
 | 
			
		||||
 | 
			
		||||
    override val magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric
 | 
			
		||||
    @Transient
 | 
			
		||||
    override val label: String = "pub"
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
     * [Asymmetric.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 encryptAnonymousMessage(plainData: UByteArray, randomFill: IntRange? = null): Asymmetric.Message =
 | 
			
		||||
        encryptMessage(plainData, randomFill = randomFill)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Anonymous encryption, see [encryptAnonymousMessage], to binary data. Sender could not be identified.
 | 
			
		||||
     */
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    override fun encrypt(plainData: UByteArray, randomFill: IntRange?): UByteArray =
 | 
			
		||||
        encryptMessage(plainData, randomFill = randomFill).encoded
 | 
			
		||||
 | 
			
		||||
    override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray =
 | 
			
		||||
        encryptMessage(plainData, nonce = nonce, randomFill = randomFill).encoded
 | 
			
		||||
 | 
			
		||||
    override val nonceBytesLength: Int = Asymmetric.nonceBytesLength
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 = newSecretKey(),
 | 
			
		||||
        randomFill: IntRange? = null,
 | 
			
		||||
    ) = Asymmetric.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,
 | 
			
		||||
    ): Asymmetric.Message =
 | 
			
		||||
        Asymmetric.createMessage(senderKey, this, WithFill.encode(plainData, randomFill))
 | 
			
		||||
}
 | 
			
		||||
@ -23,7 +23,7 @@ import net.sergeych.utools.now
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
class Seal(
 | 
			
		||||
    val publicKey: SigningPublicKey,
 | 
			
		||||
    val publicKey: VerifyingPublicKey,
 | 
			
		||||
    val signature: UByteArray,
 | 
			
		||||
    val nonce: UByteArray?,
 | 
			
		||||
    val createdAt: Instant,
 | 
			
		||||
@ -95,7 +95,7 @@ class Seal(
 | 
			
		||||
         * Seal [message] with a [key].
 | 
			
		||||
         *
 | 
			
		||||
         * Seals are kotlinx-serializable and can be used
 | 
			
		||||
         * to check the authenticity of the arbitrary [message] using a public key, [SigningPublicKey]
 | 
			
		||||
         * to check the authenticity of the arbitrary [message] using a public key, [VerifyingPublicKey]
 | 
			
		||||
         * instance, using public-key signing algorithms.
 | 
			
		||||
         *
 | 
			
		||||
         * Unlike a regular binary signature, Seal contains the signer's [publicKey], and also
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@ class SealedBox(
 | 
			
		||||
    /**
 | 
			
		||||
     * Check that it is signed with a specified key.
 | 
			
		||||
     */
 | 
			
		||||
    operator fun contains(publicKey: SigningPublicKey): Boolean {
 | 
			
		||||
    operator fun contains(publicKey: VerifyingPublicKey): Boolean {
 | 
			
		||||
        return seals.any { it.publicKey == publicKey }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										90
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/SecretKey.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/SecretKey.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
package net.sergeych.crypto2
 | 
			
		||||
 | 
			
		||||
import com.ionspin.kotlin.crypto.box.Box
 | 
			
		||||
import com.ionspin.kotlin.crypto.scalarmult.ScalarMultiplication
 | 
			
		||||
import kotlinx.serialization.SerialName
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.Transient
 | 
			
		||||
import net.sergeych.bipack.BipackDecoder
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The secret key used in public-key encryption; it is used to _decrypt_ data encrypted with its
 | 
			
		||||
 * public counterpart, see [publicKey].
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
@SerialName("encs")
 | 
			
		||||
class SecretKey(
 | 
			
		||||
    override val keyBytes: UByteArray,
 | 
			
		||||
    @Transient
 | 
			
		||||
    val _cachedPublicKey: PublicKey? = null,
 | 
			
		||||
) : DecryptingKey, UniversalKey() {
 | 
			
		||||
 | 
			
		||||
    @Transient
 | 
			
		||||
    override val label: String = "sec"
 | 
			
		||||
    /**
 | 
			
		||||
     * Decrypt with authentication checks the message which must have [Asymmetric.Message.senderPublicKey] set.
 | 
			
		||||
     * Use [decryptWithSenderKey] otherwise. Note that the authenticated encryption is always use, even if
 | 
			
		||||
     * 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,
 | 
			
		||||
     */
 | 
			
		||||
    fun decrypt(message: Asymmetric.Message): UByteArray = message.decrypt(this)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decrypt using [senderPublicKey] as a sender key (overriding the [Asymmetric.Message.senderPublicKey] if set).
 | 
			
		||||
     * See [decrypt] for more.
 | 
			
		||||
     */
 | 
			
		||||
    fun decryptWithSenderKey(message: Asymmetric.Message, senderPublicKey: PublicKey): UByteArray =
 | 
			
		||||
        message.decryptWithSenderKey(senderPublicKey, this)
 | 
			
		||||
 | 
			
		||||
    @Transient
 | 
			
		||||
    private var cachedPublicKey: PublicKey? = _cachedPublicKey
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The corresponding public key
 | 
			
		||||
     */
 | 
			
		||||
    val publicKey: PublicKey by lazy {
 | 
			
		||||
        if (cachedPublicKey != null)
 | 
			
		||||
            cachedPublicKey!!
 | 
			
		||||
        else
 | 
			
		||||
            PublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
 | 
			
		||||
                .also { cachedPublicKey = it }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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: Asymmetric.Message = BipackDecoder.decode(cipherData.toByteArray())
 | 
			
		||||
        return message.decrypt(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val magic: KeysmagicNumber = KeysmagicNumber.defaultAssymmetric
 | 
			
		||||
    override val id: KeyId by lazy { publicKey.id }
 | 
			
		||||
 | 
			
		||||
    override val nonceBytesLength: Int
 | 
			
		||||
        get() = 0
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        data class KeyPair(val secretKey: SecretKey, val publicKey: PublicKey)
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Generate a new random pair of public and secret keys.
 | 
			
		||||
         */
 | 
			
		||||
        fun generateKeys(): KeyPair {
 | 
			
		||||
            val p = Box.keypair()
 | 
			
		||||
            val pk = PublicKey(p.publicKey)
 | 
			
		||||
            return KeyPair(SecretKey(p.secretKey, pk), pk)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun new(): SecretKey = generateKeys().secretKey
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -3,7 +3,7 @@ package net.sergeych.crypto2
 | 
			
		||||
import kotlinx.datetime.Instant
 | 
			
		||||
 | 
			
		||||
interface SigningKey: KeyInstance {
 | 
			
		||||
    val verifyingKey: SigningPublicKey
 | 
			
		||||
    val verifyingKey: VerifyingPublicKey
 | 
			
		||||
    fun sign(message: UByteArray): UByteArray
 | 
			
		||||
    fun seal(message: UByteArray, expiresAt: Instant? = null): Seal
 | 
			
		||||
}
 | 
			
		||||
@ -15,12 +15,12 @@ import net.sergeych.utools.now
 | 
			
		||||
class SigningSecretKey(
 | 
			
		||||
        override val keyBytes: UByteArray,
 | 
			
		||||
        @Transient
 | 
			
		||||
        private var cachedPublicKey: SigningPublicKey?=null
 | 
			
		||||
        private var cachedPublicKey: VerifyingPublicKey?=null
 | 
			
		||||
    ) : UniversalKey(), SigningKey {
 | 
			
		||||
 | 
			
		||||
    override val verifyingKey: SigningPublicKey by lazy {
 | 
			
		||||
    override val verifyingKey: VerifyingPublicKey by lazy {
 | 
			
		||||
        cachedPublicKey ?:
 | 
			
		||||
            SigningPublicKey(Signature.ed25519SkToPk(keyBytes)).also { cachedPublicKey = it }
 | 
			
		||||
            VerifyingPublicKey(Signature.ed25519SkToPk(keyBytes)).also { cachedPublicKey = it }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun sign(message: UByteArray): UByteArray = Signature.detached(message, keyBytes)
 | 
			
		||||
@ -35,11 +35,11 @@ class SigningSecretKey(
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        data class SigningKeyPair(val secretKey: SigningSecretKey, val publicKey: SigningPublicKey)
 | 
			
		||||
        data class SigningKeyPair(val secretKey: SigningSecretKey, val publicKey: VerifyingPublicKey)
 | 
			
		||||
 | 
			
		||||
        fun generatePair(): SigningKeyPair {
 | 
			
		||||
            val p = Signature.keypair()
 | 
			
		||||
            val publicKey = SigningPublicKey(p.publicKey)
 | 
			
		||||
            val publicKey = VerifyingPublicKey(p.publicKey)
 | 
			
		||||
            return SigningKeyPair(SigningSecretKey(p.secretKey, publicKey), publicKey)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ sealed class UniversalKey: KeyInstance {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun newSecretKey() = Asymmetric.newSecretKey()
 | 
			
		||||
        fun newSecretKey() = SecretKey.new()
 | 
			
		||||
        fun newSigningKey() = SigningSecretKey.new()
 | 
			
		||||
        @Suppress("unused")
 | 
			
		||||
        fun newSymmetricKey() = SymmetricKey.new()
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@ class UniversalRing(
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find a key of the specified type that matches the id. In general, you require key implementations like
 | 
			
		||||
     * [Asymmetric.SecretKey], [Asymmetric.PublicKey], [SigningPublicKey], [SigningSecretKey] and [SymmetricKey],
 | 
			
		||||
     * [SecretKey], [PublicKey], [VerifyingPublicKey], [SigningSecretKey] and [SymmetricKey],
 | 
			
		||||
     * or just key interfaces: [EncryptingKey], [DecryptingKey], [SigningKey] and [VerifyingKey].
 | 
			
		||||
     *
 | 
			
		||||
     * Note that key interfaces are not serializable as for now, you should try to cast to a serializable
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ import kotlinx.serialization.Transient
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
@SerialName("sigb")
 | 
			
		||||
class SigningPublicKey(override val keyBytes: UByteArray) : UniversalKey(), VerifyingKey {
 | 
			
		||||
class VerifyingPublicKey(override val keyBytes: UByteArray) : UniversalKey(), VerifyingKey {
 | 
			
		||||
    /**
 | 
			
		||||
     * Verify the signature and return true if it is correct.
 | 
			
		||||
     */
 | 
			
		||||
@ -168,7 +168,7 @@ class KeysTest {
 | 
			
		||||
//        println(sk0.publicKey)
 | 
			
		||||
        val j = Json { prettyPrint = true}
 | 
			
		||||
 | 
			
		||||
        val sk1 = j.decodeFromString<Asymmetric.SecretKey>(j.encodeToString(sk0))
 | 
			
		||||
        val sk1 = j.decodeFromString<SecretKey>(j.encodeToString(sk0))
 | 
			
		||||
        assertEquals(sk0, sk1)
 | 
			
		||||
        assertEquals(pk0, sk1.publicKey)
 | 
			
		||||
//        println(j.encodeToString(sk1))
 | 
			
		||||
@ -197,9 +197,9 @@ class KeysTest {
 | 
			
		||||
        assertEquals(usy2, usy1)
 | 
			
		||||
        assertFalse { usy1 == usy3 }
 | 
			
		||||
 | 
			
		||||
        val sk1 = Asymmetric.newSecretKey()
 | 
			
		||||
        val sk2 = Asymmetric.SecretKey(sk1.keyBytes)
 | 
			
		||||
        val sk3 = Asymmetric.newSecretKey()
 | 
			
		||||
        val sk1 = SecretKey.new()
 | 
			
		||||
        val sk2 = SecretKey(sk1.keyBytes)
 | 
			
		||||
        val sk3 = SecretKey.new()
 | 
			
		||||
 | 
			
		||||
        assertEquals(sk1, sk2)
 | 
			
		||||
        assertEquals(sk2, sk1)
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ class RingTest {
 | 
			
		||||
        assertEquals(y1, y2)
 | 
			
		||||
 | 
			
		||||
        val e1 = Asymmetric.newSecretKey()
 | 
			
		||||
        val e2: Asymmetric.SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1))
 | 
			
		||||
        val e2: SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1))
 | 
			
		||||
        assertEquals(e1, e2)
 | 
			
		||||
 | 
			
		||||
        val k1 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) as UniversalKey
 | 
			
		||||
@ -154,14 +154,14 @@ class RingTest {
 | 
			
		||||
 | 
			
		||||
        var r1 = ra + rb + rc + rd
 | 
			
		||||
 | 
			
		||||
        assertEquals(a, r1.findKey<Asymmetric.SecretKey>(a.id))
 | 
			
		||||
        assertEquals(a, r1.findKey<SecretKey>(a.id))
 | 
			
		||||
        assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
 | 
			
		||||
        assertEquals(b, r1.findKey<SigningKey>(b.id))
 | 
			
		||||
        assertEquals(c, r1.keysById(c.id).first())
 | 
			
		||||
 | 
			
		||||
        r1 = UniversalRing.join(listOf(ra, rb, rc, rd))
 | 
			
		||||
 | 
			
		||||
        assertEquals(a, r1.findKey<Asymmetric.SecretKey>(a.id))
 | 
			
		||||
        assertEquals(a, r1.findKey<SecretKey>(a.id))
 | 
			
		||||
        assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
 | 
			
		||||
        assertEquals(b, r1.findKey<SigningKey>(b.id))
 | 
			
		||||
        assertEquals(c, r1.keysById(c.id).first())
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user