forked from sergeych/crypto2
Public-key encryption
This commit is contained in:
parent
98cba5b129
commit
a71a666568
@ -6,22 +6,55 @@ import com.ionspin.kotlin.crypto.box.crypto_box_NONCEBYTES
|
||||
import com.ionspin.kotlin.crypto.scalarmult.ScalarMultiplication
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import net.sergeych.crypto2.Asymmetric.Message
|
||||
import net.sergeych.crypto2.Asymmetric.PublicKey
|
||||
import net.sergeych.crypto2.Asymmetric.SecretKey
|
||||
|
||||
|
||||
/**
|
||||
* Public-key encryption implementation. Generally should be libsodium-compatible.
|
||||
*
|
||||
* ## How to
|
||||
*
|
||||
* - [Asymmetric.generateKeys] create a key pair. Keys are serializable.
|
||||
* - [SecretKey] provides authenticated encryption for a [PublicKey] receiver.
|
||||
* - [PublicKey] provides decryption and anonymous encryption.
|
||||
* - [Message] is a serializable container with encrypted message and all necessary data to decrypt it.
|
||||
*
|
||||
* __Algorithms:__
|
||||
*
|
||||
* - Key exchange: X25519.
|
||||
* - Encryption: XSalsa20
|
||||
* - Authentication: Poly1305
|
||||
*/
|
||||
object Asymmetric {
|
||||
|
||||
/**
|
||||
* Encrypted message holder.
|
||||
*
|
||||
* Do not instantiate it directly, use [SecretKey.encrypt], [PublicKey.encryptAnonymously] or [createMessage]
|
||||
* instead. Also [SecretKey.decrypt] can be used to decrypt it same as [decrypt] or [decryptWithSenderKey].
|
||||
*
|
||||
* To successfully decrypt the message, it is necessary to know a sender public key, and non-secret nonce.
|
||||
* This class carries all this information; serialize and pass it to the recipient.
|
||||
*/
|
||||
@Serializable
|
||||
class Message(
|
||||
private val nonce: UByteArray,
|
||||
private val encryptedMessage: UByteArray,
|
||||
val senderPublicKey: PublicKey? = null,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Decrypt the message, same as [SecretKey.decrypt]
|
||||
*/
|
||||
fun decrypt(recipientKey: SecretKey): UByteArray {
|
||||
check(senderPublicKey != null)
|
||||
return decryptWithSenderKey(senderPublicKey, recipientKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a message which is not include sender's public key (which should somehow be
|
||||
* known to the recipient). Use it if [senderPublicKey] is null.
|
||||
*/
|
||||
fun decryptWithSenderKey(senderKey: PublicKey, recipientKey: SecretKey): UByteArray {
|
||||
return try {
|
||||
Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
|
||||
@ -31,6 +64,21 @@ object Asymmetric {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the [plainData] using [from] sender for [recipient] public key. Note that to decrypt it
|
||||
* the [SecretKey] that corresponds to the [recipient] public key is needed, Sender can't decrypt the message!
|
||||
*
|
||||
* The authenticated encryption is used, is the message _is successfully decrypted_, it also means that
|
||||
* it was signed by the sender, whose public key is known at the decryption time.
|
||||
*
|
||||
* When it is important not to provide senders' key, use [PublicKey.encryptAnonymously].
|
||||
*
|
||||
* @param from the senders' secret key.
|
||||
* @param recipient the recipients' public key.
|
||||
* @param plainData data to encrypt
|
||||
* @param excludeSenderKey if set to true, senders' public key will not be included in the message.
|
||||
* In this case, the recipient must know it from other sources to be able to decrypt the message.
|
||||
*/
|
||||
fun createMessage(
|
||||
from: SecretKey, recipient: PublicKey, plainData: UByteArray,
|
||||
excludeSenderKey: Boolean = false,
|
||||
@ -43,8 +91,14 @@ object Asymmetric {
|
||||
)
|
||||
}
|
||||
|
||||
data class KeyPair(val secretKey: SecretKey, public val senderKey: PublicKey)
|
||||
/**
|
||||
* The generated key pair. See [generateKeys]
|
||||
*/
|
||||
data class KeyPair(val secretKey: SecretKey,val senderKey: PublicKey)
|
||||
|
||||
/**
|
||||
* Generate a new random pair of public and secret keys.
|
||||
*/
|
||||
fun generateKeys(): KeyPair {
|
||||
val p = Box.keypair()
|
||||
val pk = PublicKey(p.publicKey)
|
||||
@ -53,6 +107,10 @@ object Asymmetric {
|
||||
|
||||
private fun randomNonce(): UByteArray = randomBytes(crypto_box_NONCEBYTES)
|
||||
|
||||
/**
|
||||
* The public key used as the recipient for [Message] (see [SecretKey.encrypt], etc.). It also
|
||||
* could be used to encrypt anonymous messages with [encryptAnonymously]
|
||||
*/
|
||||
@Serializable
|
||||
class PublicKey(val keyBytes: UByteArray) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@ -62,11 +120,24 @@ object Asymmetric {
|
||||
return keyBytes contentEquals other.keyBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an anonymous message that could be decrypted only with the [SecretKey] that corresponds this.
|
||||
* Anonymous message uses one-time secret key, the public part of which is included into the
|
||||
* [Message], so the sender could not be identified.
|
||||
* The authentication is used despite the anonymity, and the fact of the successful decryption
|
||||
* proves that the message was not altered after creation.
|
||||
*/
|
||||
fun encryptAnonymously(plainData: UByteArray): Message =
|
||||
createMessage(generateKeys().secretKey, this, plainData)
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return keyBytes.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The secret key
|
||||
*/
|
||||
@Serializable
|
||||
class SecretKey(
|
||||
val keyBytes: UByteArray,
|
||||
@ -74,8 +145,38 @@ object Asymmetric {
|
||||
val _cachedPublicKey: PublicKey? = null,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Encrypt the message for [recipient]. The [Message] will include the our [publicKey]
|
||||
* and thus ready to be decrypted.
|
||||
*
|
||||
* The message is authenticated by this secret key.
|
||||
*/
|
||||
fun encrypt(plainData: UByteArray,recipient: PublicKey): Message =
|
||||
createMessage(this, recipient, plainData)
|
||||
|
||||
/**
|
||||
* Decrypt with authentication checks the message which must have [Message.senderPublicKey] set.
|
||||
* Use [decryptWithSenderKey] otherwise. Note that the authenticated encryption is always use, even if
|
||||
* the [PublicKey.encryptAnonymously] was used to create a message, if it is successfully decrypted,
|
||||
* it is guaranteed that the message was not altered after creation.
|
||||
*
|
||||
* @throws DecryptionFailedException If the message is tampered (changed after creation) or was not intended for us,
|
||||
*/
|
||||
fun decrypt(message: Message): UByteArray = message.decrypt(this)
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt using [senderPublicKey] as a sender key (overriding the [Message.senderPublicKey] if set).
|
||||
* See [decrypt] for more.
|
||||
*/
|
||||
fun decryptWithSenderKey(message: Message,senderPublicKey: PublicKey): UByteArray =
|
||||
message.decryptWithSenderKey(senderPublicKey,this)
|
||||
|
||||
private var cachedPublicKey: PublicKey? = _cachedPublicKey
|
||||
|
||||
/**
|
||||
* The corresponding public key
|
||||
*/
|
||||
val publicKey: PublicKey by lazy {
|
||||
PublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
|
||||
.also { cachedPublicKey = it }
|
||||
|
@ -124,6 +124,14 @@ class KeysTest {
|
||||
assertContentEquals(plain, m.decrypt(sk2))
|
||||
}
|
||||
|
||||
m = pk2.encryptAnonymously(plain)
|
||||
assertContentEquals(plain, m.decrypt(sk2))
|
||||
assertContentEquals(plain, sk2.decrypt(m))
|
||||
assertContentEquals(plain, sk2.decrypt(sk1.encrypt(plain, pk2)))
|
||||
assertThrows<DecryptionFailedException> {
|
||||
assertContentEquals(plain, m.decrypt(sk1))
|
||||
}
|
||||
|
||||
}
|
||||
@Test
|
||||
fun asymmetricKeySerializationTest() = runTest {
|
||||
|
Loading…
x
Reference in New Issue
Block a user