asymmetric key cryptography basics
This commit is contained in:
parent
382c3277af
commit
98cba5b129
96
src/commonMain/kotlin/net/sergeych/crypto2/AsymmetricKey.kt
Normal file
96
src/commonMain/kotlin/net/sergeych/crypto2/AsymmetricKey.kt
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
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.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
|
|
||||||
|
object Asymmetric {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Message(
|
||||||
|
private val nonce: UByteArray,
|
||||||
|
private val encryptedMessage: UByteArray,
|
||||||
|
val senderPublicKey: PublicKey? = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun decrypt(recipientKey: SecretKey): UByteArray {
|
||||||
|
check(senderPublicKey != null)
|
||||||
|
return decryptWithSenderKey(senderPublicKey, recipientKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decryptWithSenderKey(senderKey: PublicKey, recipientKey: SecretKey): UByteArray {
|
||||||
|
return try {
|
||||||
|
Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
|
||||||
|
} catch (_: BoxCorruptedOrTamperedDataException) {
|
||||||
|
throw DecryptionFailedException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createMessage(
|
||||||
|
from: SecretKey, recipient: PublicKey, plainData: UByteArray,
|
||||||
|
excludeSenderKey: Boolean = false,
|
||||||
|
): Message {
|
||||||
|
val nonce = randomNonce()
|
||||||
|
return Message(
|
||||||
|
nonce,
|
||||||
|
Box.easy(plainData, nonce, recipient.keyBytes, from.keyBytes),
|
||||||
|
if (excludeSenderKey) null else from.publicKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class KeyPair(val secretKey: SecretKey, public val senderKey: PublicKey)
|
||||||
|
|
||||||
|
fun generateKeys(): KeyPair {
|
||||||
|
val p = Box.keypair()
|
||||||
|
val pk = PublicKey(p.publicKey)
|
||||||
|
return KeyPair(SecretKey(p.secretKey, pk), pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun randomNonce(): UByteArray = randomBytes(crypto_box_NONCEBYTES)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PublicKey(val keyBytes: UByteArray) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is PublicKey) return false
|
||||||
|
|
||||||
|
return keyBytes contentEquals other.keyBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return keyBytes.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SecretKey(
|
||||||
|
val keyBytes: UByteArray,
|
||||||
|
@Transient
|
||||||
|
val _cachedPublicKey: PublicKey? = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var cachedPublicKey: PublicKey? = _cachedPublicKey
|
||||||
|
|
||||||
|
val publicKey: PublicKey by lazy {
|
||||||
|
PublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
|
||||||
|
.also { cachedPublicKey = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is SecretKey) return false
|
||||||
|
|
||||||
|
return keyBytes contentEquals other.keyBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return keyBytes.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,8 +6,6 @@ import net.sergeych.bipack.BipackEncoder
|
|||||||
import net.sergeych.bipack.decodeFromBipack
|
import net.sergeych.bipack.decodeFromBipack
|
||||||
import net.sergeych.crypto2.Seal.Companion.create
|
import net.sergeych.crypto2.Seal.Companion.create
|
||||||
import net.sergeych.utools.now
|
import net.sergeych.utools.now
|
||||||
import kotlin.random.Random
|
|
||||||
import kotlin.random.nextUBytes
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended public-key signature.
|
* Extended public-key signature.
|
||||||
@ -138,7 +136,7 @@ class Seal(
|
|||||||
expiresAt: Instant? = null,
|
expiresAt: Instant? = null,
|
||||||
nonDeterministic: Boolean = false
|
nonDeterministic: Boolean = false
|
||||||
): Seal {
|
): Seal {
|
||||||
val nonce = if( nonDeterministic ) Random.nextUBytes(32) else null
|
val nonce = if( nonDeterministic ) randomBytes(32) else null
|
||||||
val data = BipackEncoder.encode(SealedData(message, nonce, createdAt, expiresAt)).toUByteArray()
|
val data = BipackEncoder.encode(SealedData(message, nonce, createdAt, expiresAt)).toUByteArray()
|
||||||
return Seal(key.publicKey, key.sign(data), nonce, createdAt, expiresAt)
|
return Seal(key.publicKey, key.sign(data), nonce, createdAt, expiresAt)
|
||||||
}
|
}
|
||||||
|
@ -5,16 +5,12 @@ import kotlinx.serialization.Serializable
|
|||||||
import net.sergeych.bintools.toDataSource
|
import net.sergeych.bintools.toDataSource
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
import net.sergeych.bipack.BipackEncoder
|
import net.sergeych.bipack.BipackEncoder
|
||||||
import net.sergeych.crypto2.SymmetricKey.Companion.random
|
|
||||||
import kotlin.random.Random
|
|
||||||
import kotlin.random.nextInt
|
|
||||||
import kotlin.random.nextUBytes
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Symmetric key implements authenticated encrypting with random nonce and optional fill.
|
* Symmetric key implements authenticated encrypting with random nonce and optional fill.
|
||||||
* Random fill is normally used when cryptanalysis of the message size is a threat.
|
* Random fill is normally used when cryptanalysis of the message size is a threat.
|
||||||
*
|
*
|
||||||
* Do not call this constructor directly, use [random] or deserialize it.
|
* Do not call this constructor directly, use [randomInt] or deserialize it.
|
||||||
*
|
*
|
||||||
* __Algorithms:__
|
* __Algorithms:__
|
||||||
*
|
*
|
||||||
@ -49,10 +45,10 @@ class SymmetricKey(
|
|||||||
override fun encrypt(plainData: UByteArray,randomFill: IntRange?): UByteArray {
|
override fun encrypt(plainData: UByteArray,randomFill: IntRange?): UByteArray {
|
||||||
val fill = randomFill?.let {
|
val fill = randomFill?.let {
|
||||||
require(it.start >= 0)
|
require(it.start >= 0)
|
||||||
Random.nextUBytes(Random.nextInt(it))
|
randomBytes(randomInt(it))
|
||||||
}
|
}
|
||||||
val filled = BipackEncoder.encode(WithFill(plainData, fill))
|
val filled = BipackEncoder.encode(WithFill(plainData, fill))
|
||||||
val nonce = randomNonce()
|
val nonce = randomSecretboxNonce()
|
||||||
val encrypted = SecretBox.easy(filled.toUByteArray(), nonce, keyBytes)
|
val encrypted = SecretBox.easy(filled.toUByteArray(), nonce, keyBytes)
|
||||||
return BipackEncoder.encode(WithNonce(encrypted, nonce)).toUByteArray()
|
return BipackEncoder.encode(WithNonce(encrypted, nonce)).toUByteArray()
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,13 @@ fun randomBytes(n: UInt): UByteArray = if (n > 0u) LibsodiumRandom.buf(n.toInt()
|
|||||||
fun randomUInt(max: UInt) = LibsodiumRandom.uniform(max)
|
fun randomUInt(max: UInt) = LibsodiumRandom.uniform(max)
|
||||||
fun randomUInt(max: Int) = LibsodiumRandom.uniform(max.toUInt())
|
fun randomUInt(max: Int) = LibsodiumRandom.uniform(max.toUInt())
|
||||||
|
|
||||||
|
fun randomInt(range: IntRange): Int {
|
||||||
|
val l = range.last - range.first + 1
|
||||||
|
return randomUInt(l.toUInt()).toInt() + range.first
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randomInt() = randomInt( Int.MIN_VALUE ..< Int.MAX_VALUE)
|
||||||
|
|
||||||
fun <T: Comparable<T>>T.limit(range: ClosedRange<T>) = when {
|
fun <T: Comparable<T>>T.limit(range: ClosedRange<T>) = when {
|
||||||
this < range.start -> range.start
|
this < range.start -> range.start
|
||||||
this > range.endInclusive -> range.endInclusive
|
this > range.endInclusive -> range.endInclusive
|
||||||
@ -53,6 +60,6 @@ fun <T: Comparable<T>>T.limit(range: ClosedRange<T>) = when {
|
|||||||
fun <T: Comparable<T>>T.limitMax(max: T) = if( this < max ) this else max
|
fun <T: Comparable<T>>T.limitMax(max: T) = if( this < max ) this else max
|
||||||
fun <T: Comparable<T>>T.limitMin(min: T) = if( this > min ) this else min
|
fun <T: Comparable<T>>T.limitMin(min: T) = if( this > min ) this else min
|
||||||
|
|
||||||
fun randomNonce(): UByteArray = randomBytes(crypto_secretbox_NONCEBYTES)
|
fun randomSecretboxNonce(): UByteArray = randomBytes(crypto_secretbox_NONCEBYTES)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import net.sergeych.crypto2.*
|
import net.sergeych.crypto2.*
|
||||||
import net.sergeych.utools.now
|
import net.sergeych.utools.now
|
||||||
import net.sergeych.utools.pack
|
import net.sergeych.utools.pack
|
||||||
@ -105,4 +107,36 @@ class KeysTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun asymmetricKeyTest() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val (sk0, pk0) = Asymmetric.generateKeys()
|
||||||
|
assertEquals(pk0, sk0.publicKey)
|
||||||
|
|
||||||
|
val (sk1, pk1) = Asymmetric.generateKeys()
|
||||||
|
val (sk2, pk2) = Asymmetric.generateKeys()
|
||||||
|
|
||||||
|
val plain = "The fake vaccine kills".encodeToUByteArray()
|
||||||
|
|
||||||
|
var m = Asymmetric.createMessage(sk0, pk1, plain)
|
||||||
|
assertContentEquals(plain, m.decrypt(sk1))
|
||||||
|
assertThrows<DecryptionFailedException> {
|
||||||
|
assertContentEquals(plain, m.decrypt(sk2))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun asymmetricKeySerializationTest() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val (sk0, pk0) = Asymmetric.generateKeys()
|
||||||
|
|
||||||
|
// println(sk0.publicKey)
|
||||||
|
val j = Json { prettyPrint = true}
|
||||||
|
|
||||||
|
val sk1 = j.decodeFromString<Asymmetric.SecretKey>(j.encodeToString(sk0))
|
||||||
|
assertEquals(sk0, sk1)
|
||||||
|
assertEquals(pk0, sk1.publicKey)
|
||||||
|
// println(j.encodeToString(sk1))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user