x25519 key exchange
This commit is contained in:
		
							parent
							
								
									aad44c5af5
								
							
						
					
					
						commit
						382c3277af
					
				@ -0,0 +1,80 @@
 | 
			
		||||
package net.sergeych.crypto2
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 *
 | 
			
		||||
 * Usage:
 | 
			
		||||
 *
 | 
			
		||||
 * 1. Create [SafeKeyExchange] on both server and client sides
 | 
			
		||||
 * 2. Exchange [publicKey] instances
 | 
			
		||||
 * 3. Create [serverSessionKey] and [clientSessionKey] respectively
 | 
			
		||||
 * 4. Use [SessionKey.sendingKey] and [SessionKey.receivingKey] to send and receive encrypted data.
 | 
			
		||||
 *
 | 
			
		||||
 * Some important rules to keep security level high:
 | 
			
		||||
 *
 | 
			
		||||
 * - do not reuse [SafeKeyExchange] after session keys are generated, at least on the client. Create new
 | 
			
		||||
 *   instances as often as performance considerations allow.
 | 
			
		||||
 * - while it is possible to generate several keys "ahead", the care should be taken when storing them,
 | 
			
		||||
 *   encrypt it with some other key to maintain safety.
 | 
			
		||||
 * - do not use [publicKey] for anything but creating session keys.
 | 
			
		||||
 */
 | 
			
		||||
class SafeKeyExchange {
 | 
			
		||||
    private val pair = KeyExchange.keypair()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The session key. It uses a pair of keys to encrypt and decrypt messages to maintain high
 | 
			
		||||
     * security level and allow using counters as nonce with no extra precautions.
 | 
			
		||||
     */
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class SessionKey internal constructor(
 | 
			
		||||
        val sendingKey: EncryptingKey,
 | 
			
		||||
        val receivingKey: DecryptingKey,
 | 
			
		||||
    ): CipherKey, EncryptingKey by sendingKey, DecryptingKey by receivingKey
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The public key; it should be transmitted to the other party, this is serializable.
 | 
			
		||||
     * Do not use it except to get [SessionKey] with [clientSessionKey] or [serverSessionKey]. Storing and reusing
 | 
			
		||||
     * it is a great danger.
 | 
			
		||||
     */
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class PublicKey(val keyBytes: UByteArray)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The public key to be sent to the other party. When received, get the session keys with [clientSessionKey]
 | 
			
		||||
     * and [serverSessionKey] respectively. Do not reuse public key, do not use it for any other reason than
 | 
			
		||||
     * to create a single session keys instance. For a new session, create new [SafeKeyExchange] at least on the
 | 
			
		||||
     * client side, and get new keys. It is recommended to change server-side [SafeKeyExchange]
 | 
			
		||||
     * instance every time or often enough.
 | 
			
		||||
     */
 | 
			
		||||
    val publicKey = PublicKey(pair.publicKey)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create asymmetric [SessionKey] to encrypt the session. It can't decrypt what is encrypted by it
 | 
			
		||||
     * but can decrypt what was encrypted with the [serverSessionKey] on the another side.
 | 
			
		||||
     */
 | 
			
		||||
    fun clientSessionKey(serverPublicKey: PublicKey): SessionKey =
 | 
			
		||||
        KeyExchange.clientSessionKeys(pair.publicKey, pair.secretKey, serverPublicKey.keyBytes)
 | 
			
		||||
            .let { SessionKey(SymmetricKey(it.sendKey), SymmetricKey(it.receiveKey))}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an asymmetric [SessionKey] instance to work with [clientSessionKey] on the other side.
 | 
			
		||||
     * Note that asymmetric means it can't decrypt what it encrypted.
 | 
			
		||||
     */
 | 
			
		||||
    fun serverSessionKey(clientPublicKey: PublicKey): SessionKey =
 | 
			
		||||
        KeyExchange.serverSessionKeys(pair.publicKey, pair.secretKey, clientPublicKey.keyBytes)
 | 
			
		||||
            .let { SessionKey(SymmetricKey(it.sendKey), SymmetricKey(it.receiveKey))}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -24,7 +24,7 @@ import kotlin.random.nextUBytes
 | 
			
		||||
@Serializable
 | 
			
		||||
class SymmetricKey(
 | 
			
		||||
    val keyBytes: UByteArray
 | 
			
		||||
): EncryptingKey, DecryptingKey {
 | 
			
		||||
): CipherKey {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @suppress
 | 
			
		||||
 | 
			
		||||
@ -83,4 +83,26 @@ class KeysTest {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun keyExchangeTest() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val ske = SafeKeyExchange()
 | 
			
		||||
        val cke = SafeKeyExchange()
 | 
			
		||||
 | 
			
		||||
        val clientSessionKey = cke.clientSessionKey(ske.publicKey)
 | 
			
		||||
        val serverSessionKey = ske.serverSessionKey(cke.publicKey)
 | 
			
		||||
 | 
			
		||||
        val src = "Hello, Dolly!"
 | 
			
		||||
        assertEquals(src, serverSessionKey.decryptString(clientSessionKey.encrypt(src)))
 | 
			
		||||
        assertEquals(src, serverSessionKey.decryptString(clientSessionKey.encrypt(src)))
 | 
			
		||||
        assertEquals(src, serverSessionKey.decryptString(clientSessionKey.encrypt(src)))
 | 
			
		||||
        assertEquals(src, serverSessionKey.decryptString(clientSessionKey.encrypt(src)))
 | 
			
		||||
 | 
			
		||||
        assertEquals(src, clientSessionKey.decryptString(serverSessionKey.encrypt(src)))
 | 
			
		||||
        assertEquals(src, clientSessionKey.decryptString(serverSessionKey.encrypt(src)))
 | 
			
		||||
        assertEquals(src, clientSessionKey.decryptString(serverSessionKey.encrypt(src)))
 | 
			
		||||
        assertEquals(src, clientSessionKey.decryptString(serverSessionKey.encrypt(src)))
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user