forked from sergeych/crypto2
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
|
@Serializable
|
||||||
class SymmetricKey(
|
class SymmetricKey(
|
||||||
val keyBytes: UByteArray
|
val keyBytes: UByteArray
|
||||||
): EncryptingKey, DecryptingKey {
|
): CipherKey {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @suppress
|
* @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