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