Added jvm implementation, fixed seal invalid return
This commit is contained in:
parent
9cb2701715
commit
7a8640638c
@ -87,4 +87,4 @@ expect object Box {
|
|||||||
|
|
||||||
fun sealOpen(ciphertext: UByteArray, recipientsSecretKey: UByteArray) : UByteArray
|
fun sealOpen(ciphertext: UByteArray, recipientsSecretKey: UByteArray) : UByteArray
|
||||||
|
|
||||||
}
|
}
|
@ -1,9 +1,7 @@
|
|||||||
package com.ionspin.kotlin.crypto.box
|
package com.ionspin.kotlin.crypto.box
|
||||||
|
|
||||||
import com.ionspin.kotlin.bignum.integer.util.hexColumsPrint
|
|
||||||
import com.ionspin.kotlin.crypto.LibsodiumInitializer
|
import com.ionspin.kotlin.crypto.LibsodiumInitializer
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import com.ionspin.kotlin.crypto.util.toHexString
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
import kotlin.random.nextUBytes
|
import kotlin.random.nextUBytes
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -31,64 +29,82 @@ class BoxTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBoxEasy() {
|
fun testBoxEasy() {
|
||||||
val message = "Message message message".encodeToUByteArray()
|
LibsodiumInitializer.initializeWithCallback {
|
||||||
val senderKeypair = Box.keypair()
|
val message = "Message message message".encodeToUByteArray()
|
||||||
val recipientKeypair = Box.keypair()
|
val senderKeypair = Box.keypair()
|
||||||
val messageNonce = Random(0).nextUBytes(crypto_box_NONCEBYTES)
|
val recipientKeypair = Box.keypair()
|
||||||
val encrypted = Box.easy(message, messageNonce, recipientKeypair.publicKey, senderKeypair.secretKey)
|
val messageNonce = Random(0).nextUBytes(crypto_box_NONCEBYTES)
|
||||||
val decrypted = Box.openEasy(encrypted, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey)
|
val encrypted = Box.easy(message, messageNonce, recipientKeypair.publicKey, senderKeypair.secretKey)
|
||||||
assertTrue {
|
val decrypted = Box.openEasy(encrypted, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey)
|
||||||
decrypted.contentEquals(message)
|
assertTrue {
|
||||||
}
|
decrypted.contentEquals(message)
|
||||||
|
}
|
||||||
|
|
||||||
assertFailsWith<BoxCorruptedOrTamperedDataException>() {
|
assertFailsWith<BoxCorruptedOrTamperedDataException>() {
|
||||||
val tampered = encrypted.copyOf()
|
val tampered = encrypted.copyOf()
|
||||||
tampered[1] = 0U
|
tampered[1] = 0U
|
||||||
Box.openEasy(tampered, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey)
|
Box.openEasy(tampered, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBoxEasyDetached() {
|
fun testBoxEasyDetached() {
|
||||||
val message = "Message message message".encodeToUByteArray()
|
LibsodiumInitializer.initializeWithCallback {
|
||||||
val senderKeypair = Box.keypair()
|
val message = "Message message message".encodeToUByteArray()
|
||||||
val recipientKeypair = Box.keypair()
|
val senderKeypair = Box.keypair()
|
||||||
val messageNonce = Random(0).nextUBytes(crypto_box_NONCEBYTES)
|
val recipientKeypair = Box.keypair()
|
||||||
val encrypted = Box.detached(message, messageNonce, recipientKeypair.publicKey, senderKeypair.secretKey)
|
val messageNonce = Random(0).nextUBytes(crypto_box_NONCEBYTES)
|
||||||
val decrypted = Box.openDetached(encrypted.ciphertext, encrypted.tag, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey)
|
val encrypted = Box.detached(message, messageNonce, recipientKeypair.publicKey, senderKeypair.secretKey)
|
||||||
assertTrue {
|
val decrypted = Box.openDetached(
|
||||||
decrypted.contentEquals(message)
|
encrypted.ciphertext,
|
||||||
}
|
encrypted.tag,
|
||||||
|
messageNonce,
|
||||||
|
senderKeypair.publicKey,
|
||||||
|
recipientKeypair.secretKey
|
||||||
|
)
|
||||||
|
assertTrue {
|
||||||
|
decrypted.contentEquals(message)
|
||||||
|
}
|
||||||
|
|
||||||
assertFailsWith<BoxCorruptedOrTamperedDataException>() {
|
assertFailsWith<BoxCorruptedOrTamperedDataException>() {
|
||||||
val tampered = encrypted.ciphertext.copyOf()
|
val tampered = encrypted.ciphertext.copyOf()
|
||||||
tampered[1] = 0U
|
tampered[1] = 0U
|
||||||
Box.openDetached(tampered, encrypted.tag, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey)
|
Box.openDetached(
|
||||||
|
tampered,
|
||||||
|
encrypted.tag,
|
||||||
|
messageNonce,
|
||||||
|
senderKeypair.publicKey,
|
||||||
|
recipientKeypair.secretKey
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBeforeNonceAndMessage() {
|
fun testBeforeNonceAndMessage() {
|
||||||
val message = "Message message message".encodeToUByteArray()
|
LibsodiumInitializer.initializeWithCallback {
|
||||||
val senderKeypair = Box.keypair()
|
val message = "Message message message".encodeToUByteArray()
|
||||||
val recipientKeypair = Box.keypair()
|
val senderKeypair = Box.keypair()
|
||||||
val messageNonce = Random(0).nextUBytes(crypto_box_NONCEBYTES)
|
val recipientKeypair = Box.keypair()
|
||||||
val senderComputedSessionKey = Box.beforeNM(recipientKeypair.publicKey, senderKeypair.secretKey)
|
val messageNonce = Random(0).nextUBytes(crypto_box_NONCEBYTES)
|
||||||
val recipientComputedSessionKey = Box.beforeNM(senderKeypair.publicKey, recipientKeypair.secretKey)
|
val senderComputedSessionKey = Box.beforeNM(recipientKeypair.publicKey, senderKeypair.secretKey)
|
||||||
|
val recipientComputedSessionKey = Box.beforeNM(senderKeypair.publicKey, recipientKeypair.secretKey)
|
||||||
|
|
||||||
assertTrue {
|
assertTrue {
|
||||||
senderComputedSessionKey.contentEquals(recipientComputedSessionKey)
|
senderComputedSessionKey.contentEquals(recipientComputedSessionKey)
|
||||||
}
|
}
|
||||||
val encrypted = Box.easyAfterNM(message, messageNonce, senderComputedSessionKey)
|
val encrypted = Box.easyAfterNM(message, messageNonce, senderComputedSessionKey)
|
||||||
val decrypted = Box.openEasyAfterNM(encrypted, messageNonce, recipientComputedSessionKey)
|
val decrypted = Box.openEasyAfterNM(encrypted, messageNonce, recipientComputedSessionKey)
|
||||||
assertTrue {
|
assertTrue {
|
||||||
decrypted.contentEquals(message)
|
decrypted.contentEquals(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertFailsWith<BoxCorruptedOrTamperedDataException>() {
|
assertFailsWith<BoxCorruptedOrTamperedDataException>() {
|
||||||
val tampered = encrypted.copyOf()
|
val tampered = encrypted.copyOf()
|
||||||
tampered[1] = 0U
|
tampered[1] = 0U
|
||||||
Box.openEasyAfterNM(tampered, messageNonce, recipientComputedSessionKey)
|
Box.openEasyAfterNM(tampered, messageNonce, recipientComputedSessionKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,229 @@
|
|||||||
|
package com.ionspin.kotlin.crypto.box
|
||||||
|
|
||||||
|
import com.ionspin.kotlin.crypto.LibsodiumInitializer.sodium
|
||||||
|
|
||||||
|
actual object Box {
|
||||||
|
/**
|
||||||
|
* The crypto_box_keypair() function randomly generates a secret key and a corresponding public key.
|
||||||
|
* The public key is put into pk (crypto_box_PUBLICKEYBYTES bytes) and the secret key into
|
||||||
|
* sk (crypto_box_SECRETKEYBYTES bytes).
|
||||||
|
*/
|
||||||
|
actual fun keypair(): BoxKeyPair {
|
||||||
|
val publicKey = UByteArray(crypto_box_PUBLICKEYBYTES)
|
||||||
|
val secretKey = UByteArray(crypto_box_SECRETKEYBYTES)
|
||||||
|
sodium.crypto_box_keypair(publicKey.asByteArray(), secretKey.asByteArray())
|
||||||
|
return BoxKeyPair(publicKey, secretKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using crypto_box_seed_keypair(), the key pair can also be deterministically derived from a single key seed (crypto_box_SEEDBYTES bytes).
|
||||||
|
*/
|
||||||
|
actual fun seedKeypair(seed: UByteArray): BoxKeyPair {
|
||||||
|
val publicKey = UByteArray(crypto_box_PUBLICKEYBYTES)
|
||||||
|
val secretKey = UByteArray(crypto_box_SECRETKEYBYTES)
|
||||||
|
sodium.crypto_box_seed_keypair(publicKey.asByteArray(), secretKey.asByteArray(), seed.asByteArray())
|
||||||
|
return BoxKeyPair(publicKey, secretKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The crypto_box_easy() function encrypts a message m whose length is mlen bytes, with a recipient's public key pk, a sender's secret key sk and a nonce n.
|
||||||
|
* n should be crypto_box_NONCEBYTES bytes.
|
||||||
|
* c should be at least crypto_box_MACBYTES + mlen bytes long.
|
||||||
|
* This function writes the authentication tag, whose length is crypto_box_MACBYTES bytes, in c,
|
||||||
|
* immediately followed by the encrypted message, whose length is the same as the plaintext: mlen.
|
||||||
|
*/
|
||||||
|
actual fun easy(
|
||||||
|
message: UByteArray,
|
||||||
|
nonce: UByteArray,
|
||||||
|
recipientsPublicKey: UByteArray,
|
||||||
|
sendersSecretKey: UByteArray
|
||||||
|
): UByteArray {
|
||||||
|
val ciphertext = UByteArray(message.size + crypto_box_MACBYTES)
|
||||||
|
sodium.crypto_box_easy(
|
||||||
|
ciphertext.asByteArray(),
|
||||||
|
message.asByteArray(),
|
||||||
|
message.size.toLong(),
|
||||||
|
nonce.asByteArray(),
|
||||||
|
recipientsPublicKey.asByteArray(),
|
||||||
|
sendersSecretKey.asByteArray()
|
||||||
|
)
|
||||||
|
return ciphertext
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The crypto_box_open_easy() function verifies and decrypts a ciphertext produced by crypto_box_easy().
|
||||||
|
* c is a pointer to an authentication tag + encrypted message combination, as produced by crypto_box_easy(). clen is the length of this authentication tag + encrypted message combination. Put differently, clen is the number of bytes written by crypto_box_easy(), which is crypto_box_MACBYTES + the length of the message.
|
||||||
|
* The nonce n has to match the nonce used to encrypt and authenticate the message.
|
||||||
|
* pk is the public key of the sender that encrypted the message. sk is the secret key of the recipient that is willing to verify and decrypt it.
|
||||||
|
* The function throws [BoxCorruptedOrTamperedDataException] if the verification fails.
|
||||||
|
*/
|
||||||
|
actual fun openEasy(
|
||||||
|
ciphertext: UByteArray,
|
||||||
|
nonce: UByteArray,
|
||||||
|
sendersPublicKey: UByteArray,
|
||||||
|
recipientsSecretKey: UByteArray
|
||||||
|
): UByteArray {
|
||||||
|
val message = UByteArray(ciphertext.size - crypto_box_MACBYTES)
|
||||||
|
val validationResult = sodium.crypto_box_open_easy(
|
||||||
|
message.asByteArray(),
|
||||||
|
ciphertext.asByteArray(),
|
||||||
|
ciphertext.size.toLong(),
|
||||||
|
nonce.asByteArray(),
|
||||||
|
sendersPublicKey.asByteArray(),
|
||||||
|
recipientsSecretKey.asByteArray()
|
||||||
|
)
|
||||||
|
if (validationResult != 0) {
|
||||||
|
throw BoxCorruptedOrTamperedDataException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The crypto_box_beforenm() function computes a shared secret key given a public key pk and a secret key sk,
|
||||||
|
* and puts it into k (crypto_box_BEFORENMBYTES bytes).
|
||||||
|
*/
|
||||||
|
actual fun beforeNM(publicKey: UByteArray, secretKey: UByteArray): UByteArray {
|
||||||
|
val sessionKey = UByteArray(crypto_box_BEFORENMBYTES)
|
||||||
|
sodium.crypto_box_beforenm(sessionKey.asByteArray(), publicKey.asByteArray(), secretKey.asByteArray())
|
||||||
|
return sessionKey
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The _afternm variants of the previously described functions accept a precalculated shared secret key k instead of a key pair.
|
||||||
|
*/
|
||||||
|
actual fun easyAfterNM(
|
||||||
|
message: UByteArray,
|
||||||
|
nonce: UByteArray,
|
||||||
|
precomputedKey: UByteArray
|
||||||
|
): UByteArray {
|
||||||
|
val ciphertext = UByteArray(message.size + crypto_box_MACBYTES)
|
||||||
|
|
||||||
|
sodium.crypto_box_easy_afternm(
|
||||||
|
ciphertext.asByteArray(),
|
||||||
|
message.asByteArray(),
|
||||||
|
message.size.toLong(),
|
||||||
|
nonce.asByteArray(),
|
||||||
|
precomputedKey.asByteArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
return ciphertext
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The _afternm variants of the previously described functions accept a precalculated shared secret key k instead of a key pair.
|
||||||
|
*/
|
||||||
|
actual fun openEasyAfterNM(
|
||||||
|
ciphertext: UByteArray,
|
||||||
|
nonce: UByteArray,
|
||||||
|
precomputedKey: UByteArray
|
||||||
|
): UByteArray {
|
||||||
|
val message = UByteArray(ciphertext.size - crypto_box_MACBYTES)
|
||||||
|
val validationResult = sodium.crypto_box_open_easy_afternm(
|
||||||
|
message.asByteArray(),
|
||||||
|
ciphertext.asByteArray(),
|
||||||
|
ciphertext.size.toLong(),
|
||||||
|
nonce.asByteArray(),
|
||||||
|
precomputedKey.asByteArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (validationResult != 0) {
|
||||||
|
throw BoxCorruptedOrTamperedDataException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function encrypts a message m of length mlen with a nonce n and a secret key sk for a recipient whose
|
||||||
|
* public key is pk, and puts the encrypted message into c.
|
||||||
|
* Exactly mlen bytes will be put into c, since this function does not prepend the authentication tag.
|
||||||
|
* The tag, whose size is crypto_box_MACBYTES bytes, will be put into mac.
|
||||||
|
*/
|
||||||
|
actual fun detached(
|
||||||
|
message: UByteArray,
|
||||||
|
nonce: UByteArray,
|
||||||
|
recipientsPublicKey: UByteArray,
|
||||||
|
sendersSecretKey: UByteArray
|
||||||
|
): BoxEncryptedDataAndTag {
|
||||||
|
val ciphertext = UByteArray(message.size)
|
||||||
|
val tag = UByteArray(crypto_box_MACBYTES)
|
||||||
|
|
||||||
|
sodium.crypto_box_detached(
|
||||||
|
ciphertext.asByteArray(),
|
||||||
|
tag.asByteArray(),
|
||||||
|
message.asByteArray(),
|
||||||
|
message.size.toLong(),
|
||||||
|
nonce.asByteArray(),
|
||||||
|
recipientsPublicKey.asByteArray(),
|
||||||
|
sendersSecretKey.asByteArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
return BoxEncryptedDataAndTag(ciphertext, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The crypto_box_open_detached() function verifies and decrypts an encrypted message c whose length is clen using the recipient's secret key sk and the sender's public key pk.
|
||||||
|
* clen doesn't include the tag, so this length is the same as the plaintext.
|
||||||
|
* The plaintext is put into m after verifying that mac is a valid authentication tag for this ciphertext, with the given nonce n and key k.
|
||||||
|
* The function throws [BoxCorruptedOrTamperedDataException] if the verification fails.
|
||||||
|
*/
|
||||||
|
actual fun openDetached(
|
||||||
|
ciphertext: UByteArray,
|
||||||
|
tag: UByteArray,
|
||||||
|
nonce: UByteArray,
|
||||||
|
sendersPublicKey: UByteArray,
|
||||||
|
recipientsSecretKey: UByteArray
|
||||||
|
): UByteArray {
|
||||||
|
val message = UByteArray(ciphertext.size)
|
||||||
|
|
||||||
|
val validationResult = sodium.crypto_box_open_detached(
|
||||||
|
message.asByteArray(),
|
||||||
|
ciphertext.asByteArray(),
|
||||||
|
tag.asByteArray(),
|
||||||
|
ciphertext.size.toLong(),
|
||||||
|
nonce.asByteArray(),
|
||||||
|
sendersPublicKey.asByteArray(),
|
||||||
|
recipientsSecretKey.asByteArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (validationResult != 0) {
|
||||||
|
throw BoxCorruptedOrTamperedDataException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun seal(message: UByteArray, recipientsPublicKey: UByteArray): UByteArray {
|
||||||
|
val ciphertextWithPublicKey = UByteArray(message.size + crypto_box_SEALBYTES)
|
||||||
|
sodium.crypto_box_seal(
|
||||||
|
ciphertextWithPublicKey.asByteArray(),
|
||||||
|
message.asByteArray(),
|
||||||
|
message.size.toLong(),
|
||||||
|
recipientsPublicKey.asByteArray()
|
||||||
|
)
|
||||||
|
return ciphertextWithPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun sealOpen(ciphertext: UByteArray, recipientsSecretKey: UByteArray): UByteArray {
|
||||||
|
val message = UByteArray(ciphertext.size - crypto_box_SEALBYTES)
|
||||||
|
val senderPublicKey = UByteArray(crypto_box_SEALBYTES) {
|
||||||
|
message[ciphertext.size - crypto_box_SEALBYTES + it - 1]
|
||||||
|
}
|
||||||
|
val validationResult = sodium.crypto_box_seal_open(
|
||||||
|
message.asByteArray(),
|
||||||
|
ciphertext.asByteArray(),
|
||||||
|
ciphertext.size.toLong(),
|
||||||
|
senderPublicKey.asByteArray(),
|
||||||
|
recipientsSecretKey.asByteArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (validationResult != 0) {
|
||||||
|
throw BoxCorruptedOrTamperedDataException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -60,7 +60,7 @@ actual object SecretStream {
|
|||||||
associatedData.size.toLong()
|
associatedData.size.toLong()
|
||||||
)
|
)
|
||||||
if (validationResult != 0) {
|
if (validationResult != 0) {
|
||||||
throw SecretStreamCorrupedOrTamperedDataException()
|
throw SecretStreamCorruptedOrTamperedDataException()
|
||||||
}
|
}
|
||||||
return DecryptedDataAndTag(result, tagArray[0])
|
return DecryptedDataAndTag(result, tagArray[0])
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.ionspin.kotlin.crypto.box
|
package com.ionspin.kotlin.crypto.box
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.util.toHexString
|
|
||||||
import com.ionspin.kotlin.crypto.util.toPtr
|
import com.ionspin.kotlin.crypto.util.toPtr
|
||||||
import kotlinx.cinterop.convert
|
import kotlinx.cinterop.convert
|
||||||
import kotlinx.cinterop.pin
|
import kotlinx.cinterop.pin
|
||||||
@ -326,7 +325,7 @@ actual object Box {
|
|||||||
messagePinned.unpin()
|
messagePinned.unpin()
|
||||||
recipientsPublicKeyPinned.unpin()
|
recipientsPublicKeyPinned.unpin()
|
||||||
|
|
||||||
return message
|
return ciphertextWithPublicKey
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,4 +360,4 @@ actual object Box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user