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