Public-key encryption
This commit is contained in:
		
							parent
							
								
									98cba5b129
								
							
						
					
					
						commit
						a71a666568
					
				@ -6,22 +6,55 @@ 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.crypto2.Asymmetric.Message
 | 
			
		||||
import net.sergeych.crypto2.Asymmetric.PublicKey
 | 
			
		||||
import net.sergeych.crypto2.Asymmetric.SecretKey
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Public-key encryption implementation. Generally should be libsodium-compatible.
 | 
			
		||||
 *
 | 
			
		||||
 * ## 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.
 | 
			
		||||
 *
 | 
			
		||||
 * __Algorithms:__
 | 
			
		||||
 *
 | 
			
		||||
 * - Key exchange: X25519.
 | 
			
		||||
 * - Encryption: XSalsa20
 | 
			
		||||
 * - Authentication: Poly1305
 | 
			
		||||
 */
 | 
			
		||||
object Asymmetric {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encrypted message holder.
 | 
			
		||||
     *
 | 
			
		||||
     * Do not instantiate it directly, use [SecretKey.encrypt], [PublicKey.encryptAnonymously] or [createMessage]
 | 
			
		||||
     * 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.
 | 
			
		||||
     * This class carries all this information; serialize and pass it to the recipient.
 | 
			
		||||
     */
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class Message(
 | 
			
		||||
        private val nonce: UByteArray,
 | 
			
		||||
        private val encryptedMessage: UByteArray,
 | 
			
		||||
        val senderPublicKey: PublicKey? = null,
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Decrypt the message, same as [SecretKey.decrypt]
 | 
			
		||||
         */
 | 
			
		||||
        fun decrypt(recipientKey: SecretKey): UByteArray {
 | 
			
		||||
            check(senderPublicKey != null)
 | 
			
		||||
            return decryptWithSenderKey(senderPublicKey, recipientKey)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Decrypt a message which is not include sender's public key (which should somehow be
 | 
			
		||||
         * known to the recipient). Use it if [senderPublicKey] is null.
 | 
			
		||||
         */
 | 
			
		||||
        fun decryptWithSenderKey(senderKey: PublicKey, recipientKey: SecretKey): UByteArray {
 | 
			
		||||
            return try {
 | 
			
		||||
                Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
 | 
			
		||||
@ -31,6 +64,21 @@ object Asymmetric {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encrypt the [plainData] using [from] sender for [recipient] public key. Note that to decrypt it
 | 
			
		||||
     * the [SecretKey] that corresponds to the [recipient] public key is needed, Sender can't decrypt the message!
 | 
			
		||||
     *
 | 
			
		||||
     * 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].
 | 
			
		||||
     *
 | 
			
		||||
     * @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(
 | 
			
		||||
        from: SecretKey, recipient: PublicKey, plainData: UByteArray,
 | 
			
		||||
        excludeSenderKey: Boolean = false,
 | 
			
		||||
@ -43,8 +91,14 @@ object Asymmetric {
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data class KeyPair(val secretKey: SecretKey, public val senderKey: PublicKey)
 | 
			
		||||
    /**
 | 
			
		||||
     * The generated key pair. See [generateKeys]
 | 
			
		||||
     */
 | 
			
		||||
    data class KeyPair(val secretKey: SecretKey,val senderKey: PublicKey)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generate a new random pair of public and secret keys.
 | 
			
		||||
     */
 | 
			
		||||
    fun generateKeys(): KeyPair {
 | 
			
		||||
        val p = Box.keypair()
 | 
			
		||||
        val pk = PublicKey(p.publicKey)
 | 
			
		||||
@ -53,6 +107,10 @@ object Asymmetric {
 | 
			
		||||
 | 
			
		||||
    private fun randomNonce(): UByteArray = randomBytes(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]
 | 
			
		||||
     */
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class PublicKey(val keyBytes: UByteArray) {
 | 
			
		||||
        override fun equals(other: Any?): Boolean {
 | 
			
		||||
@ -62,11 +120,24 @@ object Asymmetric {
 | 
			
		||||
            return keyBytes contentEquals other.keyBytes
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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.
 | 
			
		||||
         * 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)
 | 
			
		||||
 | 
			
		||||
        override fun hashCode(): Int {
 | 
			
		||||
            return keyBytes.hashCode()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The secret key
 | 
			
		||||
     */
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class SecretKey(
 | 
			
		||||
        val keyBytes: UByteArray,
 | 
			
		||||
@ -74,8 +145,38 @@ object Asymmetric {
 | 
			
		||||
        val _cachedPublicKey: PublicKey? = null,
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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,
 | 
			
		||||
         * 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)
 | 
			
		||||
 | 
			
		||||
        private var cachedPublicKey: PublicKey? = _cachedPublicKey
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The corresponding public key
 | 
			
		||||
         */
 | 
			
		||||
        val publicKey: PublicKey by lazy {
 | 
			
		||||
            PublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
 | 
			
		||||
                .also { cachedPublicKey = it }
 | 
			
		||||
 | 
			
		||||
@ -124,6 +124,14 @@ class KeysTest {
 | 
			
		||||
            assertContentEquals(plain, m.decrypt(sk2))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m = pk2.encryptAnonymously(plain)
 | 
			
		||||
        assertContentEquals(plain, m.decrypt(sk2))
 | 
			
		||||
        assertContentEquals(plain, sk2.decrypt(m))
 | 
			
		||||
        assertContentEquals(plain, sk2.decrypt(sk1.encrypt(plain, pk2)))
 | 
			
		||||
        assertThrows<DecryptionFailedException> {
 | 
			
		||||
            assertContentEquals(plain, m.decrypt(sk1))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    @Test
 | 
			
		||||
    fun asymmetricKeySerializationTest() = runTest {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user