forked from sergeych/crypto2
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.crypto2.Seal.Companion.create
|
||||
import net.sergeych.utools.now
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUBytes
|
||||
|
||||
/**
|
||||
* Extended public-key signature.
|
||||
@ -138,7 +136,7 @@ class Seal(
|
||||
expiresAt: Instant? = null,
|
||||
nonDeterministic: Boolean = false
|
||||
): 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()
|
||||
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.bipack.BipackDecoder
|
||||
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.
|
||||
* 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:__
|
||||
*
|
||||
@ -49,10 +45,10 @@ class SymmetricKey(
|
||||
override fun encrypt(plainData: UByteArray,randomFill: IntRange?): UByteArray {
|
||||
val fill = randomFill?.let {
|
||||
require(it.start >= 0)
|
||||
Random.nextUBytes(Random.nextInt(it))
|
||||
randomBytes(randomInt(it))
|
||||
}
|
||||
val filled = BipackEncoder.encode(WithFill(plainData, fill))
|
||||
val nonce = randomNonce()
|
||||
val nonce = randomSecretboxNonce()
|
||||
val encrypted = SecretBox.easy(filled.toUByteArray(), nonce, keyBytes)
|
||||
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: 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 {
|
||||
this < range.start -> range.start
|
||||
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.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.encodeToUByteArray
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.sergeych.crypto2.*
|
||||
import net.sergeych.utools.now
|
||||
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