forked from sergeych/crypto2
Container started
This commit is contained in:
parent
e4e3b5ba8b
commit
e2916d0fe2
@ -7,6 +7,7 @@ import com.ionspin.kotlin.crypto.scalarmult.ScalarMultiplication
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
|
import net.sergeych.bipack.BipackEncoder
|
||||||
import net.sergeych.crypto2.Asymmetric.Message
|
import net.sergeych.crypto2.Asymmetric.Message
|
||||||
import net.sergeych.crypto2.Asymmetric.PublicKey
|
import net.sergeych.crypto2.Asymmetric.PublicKey
|
||||||
import net.sergeych.crypto2.Asymmetric.SecretKey
|
import net.sergeych.crypto2.Asymmetric.SecretKey
|
||||||
@ -32,7 +33,7 @@ object Asymmetric {
|
|||||||
/**
|
/**
|
||||||
* Encrypted message holder.
|
* Encrypted message holder.
|
||||||
*
|
*
|
||||||
* Do not instantiate it directly, use [SecretKey.encrypt], [PublicKey.encryptAnonymously] or [createMessage]
|
* Do not instantiate it directly, use [PublicKey.encryptMessage], [PublicKey.encryptAnonymousMessage], etc.
|
||||||
* instead. Also [SecretKey.decrypt] can be used to decrypt it same as [decrypt] or [decryptWithSenderKey].
|
* 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.
|
* To successfully decrypt the message, it is necessary to know a sender public key, and non-secret nonce.
|
||||||
@ -42,13 +43,12 @@ object Asymmetric {
|
|||||||
class Message(
|
class Message(
|
||||||
private val nonce: UByteArray,
|
private val nonce: UByteArray,
|
||||||
private val encryptedMessage: UByteArray,
|
private val encryptedMessage: UByteArray,
|
||||||
val senderPublicKey: PublicKey? = null,
|
val senderPublicKey: PublicKey,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Decrypt the message, same as [SecretKey.decrypt]
|
* Decrypt the message, same as [SecretKey.decrypt]
|
||||||
*/
|
*/
|
||||||
fun decrypt(recipientKey: SecretKey): UByteArray {
|
fun decrypt(recipientKey: SecretKey): UByteArray {
|
||||||
check(senderPublicKey != null)
|
|
||||||
return decryptWithSenderKey(senderPublicKey, recipientKey)
|
return decryptWithSenderKey(senderPublicKey, recipientKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,11 +58,15 @@ object Asymmetric {
|
|||||||
*/
|
*/
|
||||||
fun decryptWithSenderKey(senderKey: PublicKey, recipientKey: SecretKey): UByteArray {
|
fun decryptWithSenderKey(senderKey: PublicKey, recipientKey: SecretKey): UByteArray {
|
||||||
return try {
|
return try {
|
||||||
|
WithFill.decode(
|
||||||
Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
|
Box.openEasy(encryptedMessage, nonce, senderKey.keyBytes, recipientKey.keyBytes)
|
||||||
|
)
|
||||||
} catch (_: BoxCorruptedOrTamperedDataException) {
|
} catch (_: BoxCorruptedOrTamperedDataException) {
|
||||||
throw DecryptionFailedException()
|
throw DecryptionFailedException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val encoded: UByteArray by lazy { BipackEncoder.encode(this).toUByteArray() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,30 +76,27 @@ object Asymmetric {
|
|||||||
* The authenticated encryption is used, is the message _is successfully decrypted_, it also means that
|
* 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.
|
* 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].
|
* When it is important not to provide senders' key, use [PublicKey.encryptAnonymousMessage].
|
||||||
*
|
*
|
||||||
* @param from the senders' secret key.
|
* @param from the senders' secret key.
|
||||||
* @param recipient the recipients' public key.
|
* @param recipient the recipients' public key.
|
||||||
* @param plainData data to encrypt
|
* @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(
|
private fun createMessage(
|
||||||
from: SecretKey, recipient: PublicKey, plainData: UByteArray,
|
from: SecretKey, recipient: PublicKey, plainData: UByteArray,
|
||||||
excludeSenderKey: Boolean = false,
|
nonce: UByteArray = randomNonce(),
|
||||||
): Message {
|
): Message {
|
||||||
val nonce = randomNonce()
|
|
||||||
return Message(
|
return Message(
|
||||||
nonce,
|
nonce,
|
||||||
Box.easy(plainData, nonce, recipient.keyBytes, from.keyBytes),
|
Box.easy(plainData, nonce, recipient.keyBytes, from.keyBytes),
|
||||||
if (excludeSenderKey) null else from.publicKey
|
from.publicKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The generated key pair. See [generateKeys]
|
* The generated key pair. See [generateKeys]
|
||||||
*/
|
*/
|
||||||
data class KeyPair(val secretKey: SecretKey, val senderKey: PublicKey)
|
data class KeyPair(val secretKey: SecretKey, val publicKey: PublicKey)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a new random pair of public and secret keys.
|
* Generate a new random pair of public and secret keys.
|
||||||
@ -114,11 +115,18 @@ object Asymmetric {
|
|||||||
val nonceBytesLength = crypto_box_NONCEBYTES
|
val nonceBytesLength = crypto_box_NONCEBYTES
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The public key used as the recipient for [Message] (see [SecretKey.encrypt], etc.). It also
|
* The public key: [encryptMessage] so only a secret key owner can read it. Allows
|
||||||
* could be used to encrypt anonymous messages with [encryptAnonymously]
|
* anonymous [encryptAnonymousMessage] and signed [encryptMessage] encryption.
|
||||||
|
*
|
||||||
|
* Anonymous encryption is very slow in comparison.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
class PublicKey(val keyBytes: UByteArray) {
|
class PublicKey(val keyBytes: UByteArray) {
|
||||||
|
|
||||||
|
val tag: KeyTag by lazy {
|
||||||
|
KeyTag(KeysMagickNumbers.defaultAssymmetric, blake2b(keyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is PublicKey) return false
|
if (other !is PublicKey) return false
|
||||||
@ -126,19 +134,57 @@ object Asymmetric {
|
|||||||
return keyBytes contentEquals other.keyBytes
|
return keyBytes contentEquals other.keyBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return keyBytes.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an anonymous message that could be decrypted only with the [SecretKey] that corresponds this.
|
* 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
|
* Anonymous message uses one-time secret key, the public part of which is included into the
|
||||||
* [Message], so the sender could not be identified.
|
* [Message], so the sender could not be identified.
|
||||||
|
*
|
||||||
|
* __Anonymous encryption is much slower__ as it generates new keys every time, use [encryptMessage]
|
||||||
|
* when possible
|
||||||
|
*
|
||||||
* The authentication is used despite the anonymity, and the fact of the successful decryption
|
* The authentication is used despite the anonymity, and the fact of the successful decryption
|
||||||
* proves that the message was not altered after creation.
|
* proves that the message was not altered after creation.
|
||||||
*/
|
*/
|
||||||
fun encryptAnonymously(plainData: UByteArray): Message =
|
fun encryptAnonymousMessage(plainData: UByteArray, randomFill: IntRange? = null): Message =
|
||||||
createMessage(generateKeys().secretKey, this, plainData)
|
encryptMessage(plainData,randomFill=randomFill)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anonymous encryption, see [encryptAnonymousMessage], to binary data. Sender could not be identified.
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
fun encrypt(plainData: UByteArray, randomFill: IntRange? = null): UByteArray =
|
||||||
|
encryptMessage(plainData, randomFill = randomFill).encoded
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal public-key encryption. Note that message authenticity is guaranteed if the decryption is successful
|
||||||
|
* whether [senderKey] is provider, the latter only allow to positively identify the sender.
|
||||||
|
*
|
||||||
|
* @param plainData data to encrypt
|
||||||
|
* @param nonce allows specifying exact nonce, default to random (safe)
|
||||||
|
* @param senderKey key to authenticate sending party. It is safe and much faster to specify it,
|
||||||
|
* otherwise an anonymous key will be created for each encryption, also safe and anonymous, but slow.
|
||||||
|
*/
|
||||||
|
fun encryptMessage(
|
||||||
|
plainData: UByteArray,
|
||||||
|
nonce: UByteArray = randomNonce(),
|
||||||
|
senderKey: SecretKey = randomSecretKey(),
|
||||||
|
randomFill: IntRange? = null,
|
||||||
|
) = createMessage(senderKey, this, WithFill.encode(plainData, randomFill), nonce)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt message using the specified secret key as sender authentication. Recipient, the party having
|
||||||
|
* [SecretKey] corresponding to this one, will be able to decrypt the message and be sure that [senderKey]
|
||||||
|
* was the author and the message was not altered.
|
||||||
|
*/
|
||||||
|
fun encryptMessage(plainData: UByteArray,
|
||||||
|
senderKey: SecretKey,
|
||||||
|
randomFill: IntRange? = null): Message =
|
||||||
|
createMessage(senderKey, this, WithFill.encode(plainData, randomFill))
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return keyBytes.hashCode()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,19 +197,10 @@ object Asymmetric {
|
|||||||
val _cachedPublicKey: PublicKey? = null,
|
val _cachedPublicKey: PublicKey? = null,
|
||||||
) : DecryptingKey {
|
) : DecryptingKey {
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* 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
|
* 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,
|
* the [PublicKey.encryptAnonymousMessage] was used to create a message, if it is successfully decrypted,
|
||||||
* it is guaranteed that the message was not altered after creation.
|
* 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,
|
* @throws DecryptionFailedException If the message is tampered (changed after creation) or was not intended for us,
|
||||||
@ -213,9 +250,7 @@ object Asymmetric {
|
|||||||
return message.decrypt(this)
|
return message.decrypt(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val decryptingTag: KeyTag by lazy {
|
override val tag: KeyTag by lazy { publicKey.tag }
|
||||||
KeyTag(KeysMagickNumbers.defaultAssymmetric, blake2b(publicKey.keyBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
override val nonceBytesLength: Int
|
override val nonceBytesLength: Int
|
||||||
get() = 0
|
get() = 0
|
@ -1,9 +1,148 @@
|
|||||||
//package net.sergeych.crypto2
|
package net.sergeych.crypto2
|
||||||
//
|
|
||||||
//import kotlinx.serialization.Serializable
|
import kotlinx.serialization.SerialName
|
||||||
//
|
import kotlinx.serialization.Serializable
|
||||||
//@Serializable
|
import kotlinx.serialization.Transient
|
||||||
//sealed class Container {
|
import net.sergeych.bipack.BipackDecoder
|
||||||
|
import net.sergeych.bipack.BipackEncoder
|
||||||
|
|
||||||
|
/*
|
||||||
|
The problem with container is the following. When we encrypt it with asymmetric key,
|
||||||
|
we provide public key as the recipient. But public key alone is not effective
|
||||||
|
as it is restricted to anonymous encryption which is very slow.
|
||||||
|
|
||||||
|
We might need an alternative to specify the (sender key,recipient key) pair also. We need a good
|
||||||
|
solution for it.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed class Container {
|
||||||
|
|
||||||
|
class InvalidContainerException : Crypto2Exception("the container is invalid")
|
||||||
|
|
||||||
|
abstract fun decryptWith(keyRing: UniversalRing): UByteArray?
|
||||||
|
|
||||||
|
fun decryptWith(vararg decryptingKeys: DecryptingKey): UByteArray? =
|
||||||
|
decryptWith(UniversalRing(*decryptingKeys))
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
var decryptedData: UByteArray? = null
|
||||||
|
|
||||||
|
val isDecrypted: Boolean get() = decryptedData != null
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("1")
|
||||||
|
class Single(val keyTag: KeyTag, val encryptedMessage: UByteArray) : Container() {
|
||||||
|
override fun decryptWith(keyRing: UniversalRing): UByteArray? {
|
||||||
|
decryptedData?.let { return it }
|
||||||
|
for (k in keyRing) {
|
||||||
|
if (k.tag == keyTag) {
|
||||||
|
kotlin.runCatching { k.decrypt(encryptedMessage) }.getOrNull()?.let {
|
||||||
|
decryptedData = it
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("*")
|
||||||
|
class Multi(val encryptedKeys: List<EncryptedKey>, val encryptedMessage: UByteArray) : Container() {
|
||||||
|
@Serializable
|
||||||
|
class EncryptedKey(val tag: KeyTag, val cipherData: UByteArray)
|
||||||
|
|
||||||
|
private var mainKey: SymmetricKey? = null
|
||||||
|
|
||||||
|
override fun decryptWith(keyRing: UniversalRing): UByteArray? {
|
||||||
|
decryptedData?.let { return it }
|
||||||
|
for (key in keyRing) {
|
||||||
|
val tag = key.tag
|
||||||
|
for (encryptedKey in encryptedKeys) {
|
||||||
|
if (tag == encryptedKey.tag) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
BipackDecoder.decode<SymmetricKey>(
|
||||||
|
key.decrypt(encryptedKey.cipherData).toByteArray()
|
||||||
|
)
|
||||||
|
}.getOrNull()?.let { k ->
|
||||||
|
if (kotlin.runCatching { decryptedData = k.decrypt(encryptedKey.cipherData) }.isFailure)
|
||||||
|
throw InvalidContainerException()
|
||||||
|
mainKey = k
|
||||||
|
}
|
||||||
|
if (decryptedData != null) return decryptedData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val encoded: UByteArray by lazy {
|
||||||
|
BipackEncoder.encode(this).toUByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
class Builder internal constructor(private val plainData: UByteArray) {
|
||||||
|
private val plainKeys = mutableListOf<EncryptingKey>()
|
||||||
|
private val keyPairs = mutableListOf<Pair<Asymmetric.SecretKey?,Asymmetric.PublicKey>>()
|
||||||
|
private var fillRange: IntRange? = null
|
||||||
|
|
||||||
|
fun key(vararg keys: EncryptingKey) { plainKeys.addAll(keys) }
|
||||||
|
|
||||||
|
fun key(vararg pairs: Pair<Asymmetric.SecretKey,Asymmetric.PublicKey>) {
|
||||||
|
keyPairs.addAll(pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun key(vararg publicKeys: Asymmetric.PublicKey) {
|
||||||
|
keyPairs.addAll(publicKeys.map { null to it })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun fill(range: IntRange) {
|
||||||
|
require(range.first >= 0 ) { "range must be positive"}
|
||||||
|
fillRange = range
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): Container {
|
||||||
|
return when( plainKeys.size + keyPairs.size ) {
|
||||||
|
0 -> throw IllegalArgumentException("Container needs at least one key")
|
||||||
|
1 -> {
|
||||||
|
plainKeys.firstOrNull()?.let {
|
||||||
|
Single(it.tag, it.encrypt(plainData, fillRange))
|
||||||
|
} ?: run {
|
||||||
|
val (sk, pk) = keyPairs.first()
|
||||||
|
Single(pk.tag, pk.encryptMessage(plainData,
|
||||||
|
senderKey = sk ?: Asymmetric.randomSecretKey(),
|
||||||
|
randomFill = fillRange).encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
TODO("multikey")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(plainData: UByteArray,builder: Builder.()->Unit) =
|
||||||
|
Builder(plainData).also { it.builder() }.build()
|
||||||
|
|
||||||
|
fun create(plainData: UByteArray, vararg keys: EncryptingKey): Container =
|
||||||
|
create(plainData) { key(*keys) }
|
||||||
|
|
||||||
|
fun create(plainData: UByteArray, vararg keys: Pair<Asymmetric.SecretKey,Asymmetric.PublicKey>) =
|
||||||
|
create(plainData) { key(*keys) }
|
||||||
|
|
||||||
|
fun decode(encoded: UByteArray): Container {
|
||||||
|
return BipackDecoder.decode(encoded.toByteArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
|
@ -28,7 +28,7 @@ interface DecryptingKey : NonceBased {
|
|||||||
|
|
||||||
fun decryptString(cipherData: UByteArray): String = decrypt(cipherData).decodeFromUByteArray()
|
fun decryptString(cipherData: UByteArray): String = decrypt(cipherData).decodeFromUByteArray()
|
||||||
|
|
||||||
val decryptingTag: KeyTag
|
val tag: KeyTag
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ interface EncryptingKey : NonceBased {
|
|||||||
fun encrypt(plainText: String,randomFill: IntRange? = null): UByteArray =
|
fun encrypt(plainText: String,randomFill: IntRange? = null): UByteArray =
|
||||||
encrypt(plainText.encodeToUByteArray(),randomFill)
|
encrypt(plainText.encodeToUByteArray(),randomFill)
|
||||||
|
|
||||||
val encryptingTag: KeyTag
|
val tag: KeyTag
|
||||||
|
|
||||||
fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange? = null): UByteArray
|
fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange? = null): UByteArray
|
||||||
}
|
}
|
||||||
|
@ -47,11 +47,14 @@ class SafeKeyExchange {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val sessionTag: UByteArray by lazy {
|
val sessionTag: UByteArray by lazy {
|
||||||
if (!isClient)
|
if (!isClient)
|
||||||
blake2b(decryptingTag.tag + encryptingTag.tag)
|
blake2b(tag.tag + tag.tag)
|
||||||
else
|
else
|
||||||
blake2b(encryptingTag.tag + decryptingTag.tag)
|
blake2b(tag.tag + tag.tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val tag: KeyTag
|
||||||
|
get() = if( isClient ) sendingKey.tag else receivingKey.tag
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is SessionKey) return false
|
if (other !is SessionKey) return false
|
||||||
|
@ -3,8 +3,6 @@ package net.sergeych.crypto2
|
|||||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
||||||
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
|
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import net.sergeych.bipack.BipackDecoder
|
|
||||||
import net.sergeych.bipack.BipackEncoder
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Symmetric key implements authenticated encrypting with random nonce and optional fill.
|
* Symmetric key implements authenticated encrypting with random nonce and optional fill.
|
||||||
@ -32,36 +30,18 @@ class SymmetricKey(
|
|||||||
val nonce: UByteArray,
|
val nonce: UByteArray,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* @suppress
|
|
||||||
* add some random data to the end of the plain message
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class WithFill(
|
|
||||||
val data: UByteArray,
|
|
||||||
val safetyFill: UByteArray? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray {
|
override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray {
|
||||||
require(nonce.size == nonceByteLength)
|
require(nonce.size == nonceByteLength)
|
||||||
|
return SecretBox.easy(WithFill.encode(plainData, randomFill), nonce, keyBytes)
|
||||||
val fill = randomFill?.let {
|
|
||||||
require(it.start >= 0)
|
|
||||||
randomUBytes(randomInt(it))
|
|
||||||
}
|
|
||||||
val filled = BipackEncoder.encode(WithFill(plainData, fill))
|
|
||||||
return SecretBox.easy(filled.toUByteArray(), nonce, keyBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val nonceBytesLength: Int = nonceByteLength
|
override val nonceBytesLength: Int = nonceByteLength
|
||||||
|
|
||||||
override val encryptingTag by lazy { KeyTag(KeysMagickNumbers.defaultSymmetric,blake2b3l(keyBytes)) }
|
override val tag by lazy { KeyTag(KeysMagickNumbers.defaultSymmetric,blake2b3l(keyBytes)) }
|
||||||
override val decryptingTag get() = encryptingTag
|
|
||||||
|
|
||||||
override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =
|
override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =
|
||||||
protectDecryption {
|
protectDecryption {
|
||||||
BipackDecoder.decode<WithFill>(SecretBox.openEasy(cipherData, nonce, keyBytes).toByteArray())
|
WithFill.decode(SecretBox.openEasy(cipherData, nonce, keyBytes))
|
||||||
.data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
@ -7,7 +7,7 @@ import kotlinx.serialization.Transient
|
|||||||
@Serializable
|
@Serializable
|
||||||
sealed class UniversalKey : DecryptingKey {
|
sealed class UniversalKey : DecryptingKey {
|
||||||
|
|
||||||
abstract val tag: KeyTag
|
// abstract val tag: KeyTag
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ sealed class UniversalKey : DecryptingKey {
|
|||||||
@SerialName("sy")
|
@SerialName("sy")
|
||||||
data class Symmetric(val key: SymmetricKey) : UniversalKey(), EncryptingKey by key, DecryptingKey by key {
|
data class Symmetric(val key: SymmetricKey) : UniversalKey(), EncryptingKey by key, DecryptingKey by key {
|
||||||
@Transient
|
@Transient
|
||||||
override val tag: KeyTag = key.encryptingTag
|
override val tag: KeyTag = key.tag
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
override val nonceBytesLength: Int = key.nonceBytesLength
|
override val nonceBytesLength: Int = key.nonceBytesLength
|
||||||
@ -28,7 +28,7 @@ sealed class UniversalKey : DecryptingKey {
|
|||||||
data class Session(val key: SafeKeyExchange.SessionKey) : UniversalKey(), EncryptingKey by key,
|
data class Session(val key: SafeKeyExchange.SessionKey) : UniversalKey(), EncryptingKey by key,
|
||||||
DecryptingKey by key {
|
DecryptingKey by key {
|
||||||
@Transient
|
@Transient
|
||||||
override val tag: KeyTag = key.encryptingTag
|
override val tag: KeyTag = key.tag
|
||||||
@Transient
|
@Transient
|
||||||
override val nonceBytesLength: Int = key.nonceBytesLength
|
override val nonceBytesLength: Int = key.nonceBytesLength
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ sealed class UniversalKey : DecryptingKey {
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("se")
|
@SerialName("se")
|
||||||
data class Secret(val key: Asymmetric.SecretKey) : UniversalKey(), DecryptingKey by key {
|
data class Secret(val key: Asymmetric.SecretKey) : UniversalKey(), DecryptingKey by key {
|
||||||
override val tag: KeyTag by lazy { key.decryptingTag }
|
override val tag: KeyTag by lazy { key.tag }
|
||||||
override fun toString() = "U.Sec:$tag"
|
override fun toString() = "U.Sec:$tag"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ class UniversalRing(
|
|||||||
private val keys: Collection<UniversalKey>
|
private val keys: Collection<UniversalKey>
|
||||||
): Collection<UniversalKey> by keys {
|
): Collection<UniversalKey> by keys {
|
||||||
constructor(vararg keys: UniversalKey) : this(keys.toSet())
|
constructor(vararg keys: UniversalKey) : this(keys.toSet())
|
||||||
|
constructor(vararg keys: DecryptingKey) : this(keys.map { UniversalKey.from(it) }.toSet())
|
||||||
@Transient
|
@Transient
|
||||||
val keySet = if( keys is Set<UniversalKey> ) keys else keys.toSet()
|
val keySet = if( keys is Set<UniversalKey> ) keys else keys.toSet()
|
||||||
|
|
||||||
|
37
src/commonMain/kotlin/net/sergeych/crypto2/WithFill.kt
Normal file
37
src/commonMain/kotlin/net/sergeych/crypto2/WithFill.kt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.sergeych.bipack.BipackDecoder
|
||||||
|
import net.sergeych.bipack.BipackEncoder
|
||||||
|
import net.sergeych.crypto2.WithFill.Companion.decode
|
||||||
|
import net.sergeych.crypto2.WithFill.Companion.encode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to optionally extend a message with random bytes added _before the message bytes_ that
|
||||||
|
* reduces chances to effective cryptanalysis from the first block. Use
|
||||||
|
* [encode] and [decode] to process filled data.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
class WithFill private constructor(
|
||||||
|
@Suppress("unused")
|
||||||
|
val safetyFill: UByteArray = ubyteArrayOf(),
|
||||||
|
val data: UByteArray,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create binary data with specified fill
|
||||||
|
*/
|
||||||
|
fun encode(data: UByteArray,range: IntRange? = null) =
|
||||||
|
BipackEncoder.encode(WithFill(range?.let {
|
||||||
|
require(range.first >= 0) { "range should not be negative" }
|
||||||
|
randomUBytes(randomInt(it))
|
||||||
|
} ?: ubyteArrayOf(), data)).toUByteArray()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extract binary data from filled
|
||||||
|
*/
|
||||||
|
fun decode(data: UByteArray): UByteArray =
|
||||||
|
BipackDecoder.decode<WithFill>(data.toByteArray()).data
|
||||||
|
}
|
||||||
|
}
|
86
src/commonTest/kotlin/ContainerTest.kt
Normal file
86
src/commonTest/kotlin/ContainerTest.kt
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.crypto2.Asymmetric
|
||||||
|
import net.sergeych.crypto2.Container
|
||||||
|
import net.sergeych.crypto2.SymmetricKey
|
||||||
|
import net.sergeych.crypto2.initCrypto
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class ContainerTest {
|
||||||
|
@Test
|
||||||
|
fun testSingle() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val syk1 = SymmetricKey.random()
|
||||||
|
val syk2 = SymmetricKey.random()
|
||||||
|
val data = "sergeych, ohm many.".encodeToUByteArray()
|
||||||
|
|
||||||
|
val c = Container.create(data, syk1)
|
||||||
|
assertFalse { c.isDecrypted }
|
||||||
|
val c1 = Container.decode(c.encoded)
|
||||||
|
assertFalse { c.isDecrypted }
|
||||||
|
assertIs<Container.Single>(c)
|
||||||
|
|
||||||
|
assertNull(c1.decryptWith(syk2))
|
||||||
|
val d = c1.decryptWith(syk2, syk1)
|
||||||
|
assertNotNull(d)
|
||||||
|
assertContentEquals(data, d)
|
||||||
|
assertTrue { c1.isDecrypted }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSinglePair() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val p1 = Asymmetric.generateKeys()
|
||||||
|
val p2 = Asymmetric.generateKeys()
|
||||||
|
val p3 = Asymmetric.generateKeys()
|
||||||
|
val data = "sergeych, ohm many.".encodeToUByteArray()
|
||||||
|
|
||||||
|
val c = Container.create(data, p1.secretKey to p2.publicKey)
|
||||||
|
assertFalse { c.isDecrypted }
|
||||||
|
val c1 = Container.decode(c.encoded)
|
||||||
|
assertFalse { c.isDecrypted }
|
||||||
|
assertIs<Container.Single>(c)
|
||||||
|
|
||||||
|
assertNull(c1.decryptWith(p3.secretKey))
|
||||||
|
val d = c1.decryptWith(p3.secretKey, p2.secretKey)
|
||||||
|
assertNotNull(d)
|
||||||
|
assertContentEquals(data, d)
|
||||||
|
assertTrue { c1.isDecrypted }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSingleAsymmetric() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
// val p1 = Asymmetric.generateKeys()
|
||||||
|
val p2 = Asymmetric.generateKeys()
|
||||||
|
val p3 = Asymmetric.generateKeys()
|
||||||
|
val data = "sergeych, ohm many.".encodeToUByteArray()
|
||||||
|
|
||||||
|
val c = Container.create(data) { key(p2.publicKey) }
|
||||||
|
assertFalse { c.isDecrypted }
|
||||||
|
val c1 = Container.decode(c.encoded)
|
||||||
|
assertFalse { c.isDecrypted }
|
||||||
|
assertIs<Container.Single>(c)
|
||||||
|
|
||||||
|
assertNull(c1.decryptWith(p3.secretKey))
|
||||||
|
val d = c1.decryptWith(p3.secretKey, p2.secretKey)
|
||||||
|
assertNotNull(d)
|
||||||
|
assertContentEquals(data, d)
|
||||||
|
assertTrue { c1.isDecrypted }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMultipleSymmetric() = runTest {
|
||||||
|
initCrypto()
|
||||||
|
val syk1 = SymmetricKey.random()
|
||||||
|
val syk2 = SymmetricKey.random()
|
||||||
|
// val syk3 = SymmetricKey.random()
|
||||||
|
val data = "Translating the name 'Sergey Chernov' from Russian to archaic Sanskrit would be 'Ramo Krishna'"
|
||||||
|
.encodeToUByteArray()
|
||||||
|
|
||||||
|
val c = Container.create(data, syk1, syk2)
|
||||||
|
assertFalse { c.isDecrypted }
|
||||||
|
val c1 = Container.decode(c.encoded)
|
||||||
|
assertFalse { c1.isDecrypted }
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import net.sergeych.bipack.BipackEncoder
|
|
||||||
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
|
||||||
@ -140,25 +139,24 @@ class KeysTest {
|
|||||||
|
|
||||||
val plain = "The fake vaccine kills".encodeToUByteArray()
|
val plain = "The fake vaccine kills".encodeToUByteArray()
|
||||||
|
|
||||||
var m = Asymmetric.createMessage(sk0, pk1, plain)
|
var m = pk1.encryptMessage(plain, sk1)
|
||||||
assertContentEquals(plain, m.decrypt(sk1))
|
assertContentEquals(plain, m.decrypt(sk1))
|
||||||
assertThrows<DecryptionFailedException> {
|
assertThrows<DecryptionFailedException> {
|
||||||
assertContentEquals(plain, m.decrypt(sk2))
|
assertContentEquals(plain, m.decrypt(sk2))
|
||||||
}
|
}
|
||||||
|
|
||||||
m = pk2.encryptAnonymously(plain)
|
m = pk2.encryptAnonymousMessage(plain)
|
||||||
assertContentEquals(plain, m.decrypt(sk2))
|
assertContentEquals(plain, m.decrypt(sk2))
|
||||||
assertContentEquals(plain, sk2.decrypt(m))
|
assertContentEquals(plain, sk2.decrypt(m))
|
||||||
assertContentEquals(plain, sk2.decrypt(sk1.encrypt(plain, pk2)))
|
// assertContentEquals(plain, sk2.decrypt(sk1.encrypt(plain, pk2)))
|
||||||
assertThrows<DecryptionFailedException> {
|
assertThrows<DecryptionFailedException> {
|
||||||
assertContentEquals(plain, m.decrypt(sk1))
|
assertContentEquals(plain, m.decrypt(sk1))
|
||||||
}
|
}
|
||||||
|
|
||||||
val x1 = BipackEncoder.encode(Asymmetric.createMessage(sk0, pk1, plain))
|
val x1 = pk1.encryptMessage(plain, sk1).encoded
|
||||||
val x2 = BipackEncoder.encode(Asymmetric.createMessage(sk0, pk1, plain))
|
val x2 = pk1.encryptMessage(plain, sk1).encoded
|
||||||
|
|
||||||
assertFalse { x1 contentEquals x2 }
|
assertFalse { x1 contentEquals x2 }
|
||||||
|
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun asymmetricKeySerializationTest() = runTest {
|
fun asymmetricKeySerializationTest() = runTest {
|
||||||
|
@ -52,4 +52,9 @@ class RingTest {
|
|||||||
assertEquals(r, r2)
|
assertEquals(r, r2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
fun testKeysWithSameTags() {
|
||||||
|
// it should be able to keep keys with same tags
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user