Added jvm implementation, fixed seal invalid return

This commit is contained in:
Ugljesa Jovanovic 2020-09-10 18:30:06 +02:00
parent 9cb2701715
commit 7a8640638c
No known key found for this signature in database
GPG Key ID: 33A5F353387711A5
5 changed files with 295 additions and 51 deletions

View File

@ -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,6 +29,7 @@ class BoxTest {
@Test @Test
fun testBoxEasy() { fun testBoxEasy() {
LibsodiumInitializer.initializeWithCallback {
val message = "Message message message".encodeToUByteArray() val message = "Message message message".encodeToUByteArray()
val senderKeypair = Box.keypair() val senderKeypair = Box.keypair()
val recipientKeypair = Box.keypair() val recipientKeypair = Box.keypair()
@ -47,15 +46,23 @@ class BoxTest {
Box.openEasy(tampered, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey) Box.openEasy(tampered, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey)
} }
} }
}
@Test @Test
fun testBoxEasyDetached() { fun testBoxEasyDetached() {
LibsodiumInitializer.initializeWithCallback {
val message = "Message message message".encodeToUByteArray() val message = "Message message message".encodeToUByteArray()
val senderKeypair = Box.keypair() val senderKeypair = Box.keypair()
val recipientKeypair = Box.keypair() val recipientKeypair = Box.keypair()
val messageNonce = Random(0).nextUBytes(crypto_box_NONCEBYTES) val messageNonce = Random(0).nextUBytes(crypto_box_NONCEBYTES)
val encrypted = Box.detached(message, messageNonce, recipientKeypair.publicKey, senderKeypair.secretKey) val encrypted = Box.detached(message, messageNonce, recipientKeypair.publicKey, senderKeypair.secretKey)
val decrypted = Box.openDetached(encrypted.ciphertext, encrypted.tag, messageNonce, senderKeypair.publicKey, recipientKeypair.secretKey) val decrypted = Box.openDetached(
encrypted.ciphertext,
encrypted.tag,
messageNonce,
senderKeypair.publicKey,
recipientKeypair.secretKey
)
assertTrue { assertTrue {
decrypted.contentEquals(message) decrypted.contentEquals(message)
} }
@ -63,12 +70,20 @@ class BoxTest {
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() {
LibsodiumInitializer.initializeWithCallback {
val message = "Message message message".encodeToUByteArray() val message = "Message message message".encodeToUByteArray()
val senderKeypair = Box.keypair() val senderKeypair = Box.keypair()
val recipientKeypair = Box.keypair() val recipientKeypair = Box.keypair()
@ -91,6 +106,7 @@ class BoxTest {
Box.openEasyAfterNM(tampered, messageNonce, recipientComputedSessionKey) Box.openEasyAfterNM(tampered, messageNonce, recipientComputedSessionKey)
} }
} }
}
} }

View File

@ -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
}
}

View File

@ -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])
} }

View File

@ -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
} }