x25519 key exchange

This commit is contained in:
Sergey Chernov 2024-06-11 15:55:37 +07:00
parent aad44c5af5
commit 382c3277af
3 changed files with 103 additions and 1 deletions

View File

@ -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))}
}

View File

@ -24,7 +24,7 @@ import kotlin.random.nextUBytes
@Serializable
class SymmetricKey(
val keyBytes: UByteArray
): EncryptingKey, DecryptingKey {
): CipherKey {
/**
* @suppress

View File

@ -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)))
}
}