Add high level AEAD tests, implement missing initializations, implement pure decryption
This commit is contained in:
parent
579c44fcc7
commit
55b5641f14
@ -62,8 +62,8 @@ interface EncryptionApi {
|
||||
}
|
||||
|
||||
interface AuthenticatedEncryption {
|
||||
fun encrypt(key: UByteArray, nonce: UByteArray, message: UByteArray, additionalData: UByteArray) : UByteArray
|
||||
fun decrypt(key: UByteArray, nonce: UByteArray, cipherText: UByteArray, additionalData: UByteArray) : UByteArray
|
||||
fun encrypt(key: UByteArray, nonce: UByteArray, message: UByteArray, additionalData: UByteArray = ubyteArrayOf()) : UByteArray
|
||||
fun decrypt(key: UByteArray, nonce: UByteArray, cipherText: UByteArray, additionalData: UByteArray = ubyteArrayOf()) : UByteArray
|
||||
|
||||
}
|
||||
|
||||
@ -75,11 +75,11 @@ data class MultipartEncryptionHeader(val nonce: UByteArray)
|
||||
class InvalidTagException : RuntimeException("Tag mismatch! Encrypted data is corrupted or tampered with.")
|
||||
|
||||
interface MultipartAuthenticatedDecryption {
|
||||
fun decryptPartialData(data: EncryptedDataPart, additionalData: UByteArray) : DecryptedDataPart
|
||||
fun decryptPartialData(data: EncryptedDataPart, additionalData: UByteArray = ubyteArrayOf()) : DecryptedDataPart
|
||||
}
|
||||
|
||||
interface MultipartAuthenticatedEncryption {
|
||||
fun encryptPartialData(data: UByteArray, additionalData: UByteArray) : EncryptedDataPart
|
||||
fun encryptPartialData(data: UByteArray, additionalData: UByteArray = ubyteArrayOf()) : EncryptedDataPart
|
||||
fun startEncryption() : MultipartEncryptionHeader
|
||||
|
||||
}
|
||||
|
@ -43,3 +43,7 @@ fun String.encodeToUByteArray() : UByteArray{
|
||||
return encodeToByteArray().asUByteArray()
|
||||
}
|
||||
|
||||
fun UByteArray.decodeToString() : String {
|
||||
return this.asByteArray().contentToString()
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ package com.ionspin.kotlin.crypto.authenticated
|
||||
* on 14-Jun-2020
|
||||
*/
|
||||
expect class XChaCha20Poly1305Delegated internal constructor() {
|
||||
internal constructor(key: UByteArray, testState : UByteArray, testHeader: UByteArray)
|
||||
internal constructor(key: UByteArray, testState : UByteArray, testHeader: UByteArray, isDecryptor: Boolean)
|
||||
companion object {
|
||||
fun encrypt(key: UByteArray, nonce: UByteArray, message: UByteArray, additionalData: UByteArray) : UByteArray
|
||||
fun decrypt(key: UByteArray, nonce: UByteArray, ciphertext: UByteArray, additionalData: UByteArray) : UByteArray
|
||||
|
@ -211,8 +211,8 @@ class XChaCha20Poly1305Test {
|
||||
0xDEU, 0xFBU, 0x5CU, 0x7FU, 0x1CU, 0x26U, 0x32U, 0x2CU, 0x51U, 0xF6U, 0xEFU, 0xC6U, 0x34U, 0xC4U, 0xACU, 0x6CU,
|
||||
0xE8U, 0xF9U, 0x4BU, 0xABU, 0xA3U,
|
||||
)
|
||||
val encryptor = XChaCha20Poly1305Delegated(key, state, header)
|
||||
val decryptor = XChaCha20Poly1305Delegated(key, state, header)
|
||||
val encryptor = XChaCha20Poly1305Delegated(key, state, header, false)
|
||||
val decryptor = XChaCha20Poly1305Delegated(key, state, header, true)
|
||||
val data = UByteArray(100) { 0U }
|
||||
val result = encryptor.encrypt(data)
|
||||
val decrypted = decryptor.decrypt(result)
|
||||
@ -228,7 +228,7 @@ class XChaCha20Poly1305Test {
|
||||
val messedUpTag = result.copyOf()
|
||||
messedUpTag[messedUpTag.size - 2] = 0U
|
||||
assertFails {
|
||||
val decryptorForWrongTag = XChaCha20Poly1305Delegated(key, state, header)
|
||||
val decryptorForWrongTag = XChaCha20Poly1305Delegated(key, state, header, true)
|
||||
val plaintext = decryptorForWrongTag.decrypt(messedUpTag)
|
||||
println("Decrypted with wrong tag -----------")
|
||||
plaintext.hexColumsPrint()
|
||||
|
@ -0,0 +1,54 @@
|
||||
package com.ionspin.kotlin.crypto.highlevel
|
||||
|
||||
import com.ionspin.kotlin.crypto.Crypto
|
||||
import com.ionspin.kotlin.crypto.Initializer
|
||||
import com.ionspin.kotlin.crypto.SymmetricKey
|
||||
import com.ionspin.kotlin.crypto.hash.decodeToString
|
||||
import com.ionspin.kotlin.crypto.hash.encodeToUByteArray
|
||||
import com.ionspin.kotlin.crypto.util.hexColumsPrint
|
||||
import com.ionspin.kotlin.crypto.util.testBlocking
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 09-Jul-2020
|
||||
*/
|
||||
class EncryptionTest {
|
||||
@Test
|
||||
fun testMultipartEncryption() = testBlocking {
|
||||
Initializer.initialize()
|
||||
val plaintext = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
|
||||
"Vestibulum maximus tincidunt urna. " +
|
||||
"Nullam sit amet erat id arcu porttitor varius ut at metus. " +
|
||||
"Nunc sit amet felis vel velit ornare gravida. " +
|
||||
"Curabitur tellus lacus, pulvinar a diam at tincidunt.").encodeToUByteArray()
|
||||
val additionalData = "Additional data 1".encodeToUByteArray()
|
||||
val keyValue = UByteArray(32) { it.toUByte() }
|
||||
val key = SymmetricKey(keyValue)
|
||||
val encryptor = Crypto.Encryption.createMultipartEncryptor(key)
|
||||
val header = encryptor.startEncryption()
|
||||
val ciphertext1 = encryptor.encryptPartialData(plaintext.sliceArray(0 until 100), additionalData)
|
||||
val ciphertext2 = encryptor.encryptPartialData(plaintext.sliceArray(100 until 200))
|
||||
val ciphertext3 = encryptor.encryptPartialData(plaintext.sliceArray(200 until 250))
|
||||
//decrypt
|
||||
val decryptor = Crypto.Encryption.createMultipartDecryptor(key, header)
|
||||
println("Initialized")
|
||||
val plaintext1 = decryptor.decryptPartialData(ciphertext1, additionalData)
|
||||
val plaintext2 = decryptor.decryptPartialData(ciphertext2)
|
||||
val plaintext3 = decryptor.decryptPartialData(ciphertext3)
|
||||
|
||||
val combinedPlaintext = plaintext1.data + plaintext2.data + plaintext3.data
|
||||
println("---- Plaintext -----")
|
||||
plaintext.hexColumsPrint()
|
||||
println("---- Plaintext result -----")
|
||||
combinedPlaintext.hexColumsPrint()
|
||||
assertTrue {
|
||||
plaintext.contentEquals(combinedPlaintext)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -54,41 +54,65 @@ actual class XChaCha20Poly1305Delegated internal actual constructor() {
|
||||
}
|
||||
|
||||
var state : dynamic = null
|
||||
var isInitialized = false
|
||||
var isEncryptor = false
|
||||
|
||||
actual fun initializeForEncryption(key: UByteArray) : UByteArray {
|
||||
println("Initializaing for encryption")
|
||||
val stateAndHeader = getSodium().crypto_secretstream_xchacha20poly1305_init_push(key.toUInt8Array())
|
||||
val state = stateAndHeader.state
|
||||
val header = stateAndHeader.header
|
||||
state = stateAndHeader.state
|
||||
val header = stateAndHeader.header as Uint8Array
|
||||
console.log(state)
|
||||
console.log(header)
|
||||
println("Done initializaing for encryption")
|
||||
return header
|
||||
isInitialized = true
|
||||
isEncryptor = true
|
||||
return header.toUByteArray()
|
||||
}
|
||||
|
||||
actual fun initializeForDecryption(key: UByteArray, header: UByteArray) {
|
||||
|
||||
println("Initializing for decryption")
|
||||
header.hexColumsPrint()
|
||||
state = getSodium().crypto_secretstream_xchacha20poly1305_init_pull(header.toUInt8Array(), key.toUInt8Array())
|
||||
console.log(state)
|
||||
isInitialized = true
|
||||
isEncryptor = false
|
||||
}
|
||||
|
||||
internal actual constructor(
|
||||
key: UByteArray,
|
||||
testState: UByteArray,
|
||||
testHeader: UByteArray
|
||||
testHeader: UByteArray,
|
||||
isDecryptor: Boolean
|
||||
) : this() {
|
||||
state = getSodium().crypto_secretstream_xchacha20poly1305_init_pull(testHeader.toUInt8Array(), key.toUInt8Array())
|
||||
console.log(state)
|
||||
println("Done initializaing test state")
|
||||
isInitialized = true
|
||||
isEncryptor = !isDecryptor
|
||||
}
|
||||
|
||||
actual fun encrypt(data: UByteArray, additionalData: UByteArray): UByteArray {
|
||||
if (!isInitialized) {
|
||||
throw RuntimeException("Not initalized!")
|
||||
}
|
||||
if (!isEncryptor) {
|
||||
throw RuntimeException("Initialized as decryptor, attempted to use as encryptor")
|
||||
}
|
||||
val encrypted = getSodium().crypto_secretstream_xchacha20poly1305_push(state, data.toUInt8Array(), additionalData.toUInt8Array(), 0U)
|
||||
return encrypted.toUByteArray()
|
||||
}
|
||||
|
||||
actual fun decrypt(data: UByteArray, additionalData: UByteArray): UByteArray {
|
||||
if (!isInitialized) {
|
||||
throw RuntimeException("Not initalized!")
|
||||
}
|
||||
if (isEncryptor) {
|
||||
throw RuntimeException("Initialized as encryptor, attempted to use as decryptor")
|
||||
}
|
||||
val decryptedWithTag = getSodium().crypto_secretstream_xchacha20poly1305_pull(state, data.toUInt8Array(), additionalData.toUInt8Array())
|
||||
val decrypted = decryptedWithTag.message as Uint8Array
|
||||
val validTag = decryptedWithTag.tag as UInt
|
||||
val validTag = decryptedWithTag.tag
|
||||
|
||||
if (validTag != 0U) {
|
||||
println("Tag validation failed")
|
||||
|
@ -60,23 +60,42 @@ actual class XChaCha20Poly1305Delegated internal actual constructor() {
|
||||
val state : SecretStream.State = SecretStream.State()
|
||||
val sodium = SodiumJava()
|
||||
|
||||
var isInitialized = false
|
||||
var isEncryptor = false
|
||||
|
||||
internal actual constructor(
|
||||
key: UByteArray,
|
||||
testState: UByteArray,
|
||||
testHeader: UByteArray
|
||||
testHeader: UByteArray,
|
||||
isDecryptor: Boolean
|
||||
) : this() {
|
||||
state.k = testState.sliceArray(0 until 32).toByteArray()
|
||||
state.nonce = testState.sliceArray(32 until 44).toByteArray()
|
||||
isInitialized = true
|
||||
isEncryptor = !isDecryptor
|
||||
}
|
||||
|
||||
actual fun initializeForEncryption(key: UByteArray) : UByteArray {
|
||||
TODO()
|
||||
val header = UByteArray(24)
|
||||
sodium.crypto_secretstream_xchacha20poly1305_init_push(state, header.asByteArray(), key.asByteArray())
|
||||
isInitialized = true
|
||||
isEncryptor = true
|
||||
return header
|
||||
}
|
||||
|
||||
actual fun initializeForDecryption(key: UByteArray, header: UByteArray) {
|
||||
sodium.crypto_secretstream_xchacha20poly1305_init_pull(state, header.asByteArray(), key.asByteArray())
|
||||
isInitialized = true
|
||||
isEncryptor = false
|
||||
}
|
||||
|
||||
actual fun encrypt(data: UByteArray, additionalData: UByteArray): UByteArray {
|
||||
if (!isInitialized) {
|
||||
throw RuntimeException("Not initalized!")
|
||||
}
|
||||
if (!isEncryptor) {
|
||||
throw RuntimeException("Initialized as decryptor, attempted to use as encryptor")
|
||||
}
|
||||
val ciphertext = ByteArray(1 + data.size + 16)
|
||||
sodium.crypto_secretstream_xchacha20poly1305_push(
|
||||
state, ciphertext, null,
|
||||
@ -88,6 +107,12 @@ actual class XChaCha20Poly1305Delegated internal actual constructor() {
|
||||
}
|
||||
|
||||
actual fun decrypt(data: UByteArray, additionalData: UByteArray): UByteArray {
|
||||
if (!isInitialized) {
|
||||
throw RuntimeException("Not initalized!")
|
||||
}
|
||||
if (isEncryptor) {
|
||||
throw RuntimeException("Initialized as encryptor, attempted to use as decryptor")
|
||||
}
|
||||
val plaintext = ByteArray(data.size - 17)
|
||||
|
||||
val validTag = sodium.crypto_secretstream_xchacha20poly1305_pull(
|
||||
|
@ -69,10 +69,14 @@ actual class XChaCha20Poly1305Delegated internal actual constructor() {
|
||||
|
||||
val header = UByteArray(crypto_secretstream_xchacha20poly1305_HEADERBYTES.toInt()) { 0U }
|
||||
|
||||
var isInitialized = false
|
||||
var isEncryptor = false
|
||||
|
||||
actual internal constructor(
|
||||
key: UByteArray,
|
||||
testState: UByteArray,
|
||||
testHeader: UByteArray
|
||||
testHeader: UByteArray,
|
||||
isDecryptor: Boolean
|
||||
) : this() {
|
||||
val pointer = state.ptr.reinterpret<UByteVar>()
|
||||
for (i in 0 until crypto_secretstream_xchacha20poly1305_state.size.toInt()) {
|
||||
@ -85,6 +89,8 @@ actual class XChaCha20Poly1305Delegated internal actual constructor() {
|
||||
testHeader.copyInto(header)
|
||||
header.hexColumsPrint()
|
||||
println("header after setting-----------")
|
||||
isInitialized = true
|
||||
isEncryptor = !isDecryptor
|
||||
}
|
||||
|
||||
|
||||
@ -103,7 +109,7 @@ actual class XChaCha20Poly1305Delegated internal actual constructor() {
|
||||
}
|
||||
|
||||
actual fun initializeForDecryption(key: UByteArray, header: UByteArray) {
|
||||
|
||||
crypto_secretstream_xchacha20poly1305_init_pull(state.ptr, header.toCValues(), key.toCValues())
|
||||
}
|
||||
|
||||
|
||||
@ -116,8 +122,8 @@ actual class XChaCha20Poly1305Delegated internal actual constructor() {
|
||||
null,
|
||||
data.toCValues(),
|
||||
data.size.convert(),
|
||||
null,
|
||||
0U,
|
||||
additionalData.toCValues(),
|
||||
additionalData.size.convert(),
|
||||
0U,
|
||||
)
|
||||
println("Encrypt partial")
|
||||
@ -141,6 +147,7 @@ actual class XChaCha20Poly1305Delegated internal actual constructor() {
|
||||
additionalData.size.convert()
|
||||
)
|
||||
plaintextPinned.unpin()
|
||||
println("tag: $validTag")
|
||||
if (validTag != 0) {
|
||||
println("Tag validation failed")
|
||||
throw InvalidTagException()
|
||||
|
@ -95,7 +95,6 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray) {
|
||||
}
|
||||
|
||||
fun streamEncrypt(data: UByteArray, additionalData: UByteArray, tag : UByte) : UByteArray {
|
||||
val result = UByteArray(1 + data.size + 16) //Tag marker, ciphertext, mac
|
||||
//get encryption state
|
||||
val block = UByteArray(64) { 0U }
|
||||
ChaCha20Pure.xorWithKeystream(calcKey, calcNonce, block, 0U).copyInto(block) // This is equivalent to the first 64 bytes of keystream
|
||||
@ -107,6 +106,9 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray) {
|
||||
}
|
||||
block[0] = tag
|
||||
ChaCha20Pure.xorWithKeystream(calcKey, calcNonce, block, 1U).copyInto(block) // This just xors block[0] with keystream
|
||||
println("encrypt block going into poly ----")
|
||||
block.hexColumsPrint()
|
||||
println("encrypt block going into poly end ----")
|
||||
processPolyBytes(poly1305, block) // but updates the mac with the full block!
|
||||
// In libsodium c code, it now sets the first byte to be a tag, we'll just save it for now
|
||||
val encryptedTag = block[0]
|
||||
@ -121,11 +123,57 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray) {
|
||||
val finalMac = additionalData.size.toULong().toLittleEndianUByteArray() + (ciphertext.size + 64).toULong().toLittleEndianUByteArray()
|
||||
processPolyBytes(poly1305, finalMac)
|
||||
val mac = poly1305.finalizeMac(polyBuffer.sliceArray(0 until polyBufferByteCounter))
|
||||
//TODO process state
|
||||
println("Ciphertext ---------")
|
||||
(ubyteArrayOf(encryptedTag) + ciphertext + mac).hexColumsPrint()
|
||||
println("Ciphertext end ---------")
|
||||
return ubyteArrayOf(encryptedTag) + ciphertext + mac
|
||||
}
|
||||
|
||||
fun streamDecrypt(data: UByteArray, additionalData: UByteArray, tag: UByte) : UByteArray {
|
||||
TODO()
|
||||
val block = UByteArray(64) { 0U }
|
||||
ChaCha20Pure.xorWithKeystream(calcKey, calcNonce, block, 0U).copyInto(block) // This is equivalent to the first 64 bytes of keystream
|
||||
val poly1305 = Poly1305(block)
|
||||
block.overwriteWithZeroes()
|
||||
if (additionalData.isNotEmpty()) {
|
||||
val additionalDataPadded = additionalData + UByteArray(16 - additionalData.size % 16) { 0U }
|
||||
processPolyBytes(poly1305, additionalDataPadded)
|
||||
}
|
||||
block[0] = data[0]
|
||||
ChaCha20Pure.xorWithKeystream(calcKey, calcNonce, block, 1U).copyInto(block)// get the keystream xored with zeroes, but also decrypteg tag marker
|
||||
val tag = block[0] //get the decrypted tag
|
||||
block[0] = data[0] // this brings it back to state that is delivered to poly in encryption function
|
||||
println("Decrypted tag $tag")
|
||||
println("decrypt block going into poly ----")
|
||||
block.hexColumsPrint()
|
||||
println("decrypt block going into poly end ----")
|
||||
processPolyBytes(poly1305, block)
|
||||
// Next we update the poly1305 with ciphertext and padding, BUT the padding in libsodium is not correctly calculated, so it doesn't
|
||||
// pad correctly. https://github.com/jedisct1/libsodium/issues/976
|
||||
// We want to use libsodium in delegated flavour, so we will use the same incorrect padding here.
|
||||
// From security standpoint there are no obvious drawbacks, as padding was initially added to decrease implementation complexity.
|
||||
val ciphertext = data.sliceArray(1 until data.size - 16)
|
||||
processPolyBytes(poly1305, ciphertext + UByteArray(((16U + ciphertext.size.toUInt() - block.size.toUInt()) % 16U).toInt()) { 0U } )
|
||||
val plaintext = ChaCha20Pure.xorWithKeystream(calcKey, calcNonce, ciphertext, 2U)
|
||||
val finalMac = additionalData.size.toULong().toLittleEndianUByteArray() + (ciphertext.size + 64).toULong().toLittleEndianUByteArray()
|
||||
processPolyBytes(poly1305, finalMac)
|
||||
val mac = poly1305.finalizeMac(polyBuffer.sliceArray(0 until polyBufferByteCounter))
|
||||
println("--- mac")
|
||||
mac.hexColumsPrint()
|
||||
println("--- mac end")
|
||||
val expectedMac = data.sliceArray(data.size - 16 until data.size)
|
||||
println("--- expectedMac")
|
||||
expectedMac.hexColumsPrint()
|
||||
println("--- expectedMac end")
|
||||
|
||||
//TODO process state
|
||||
println("Plaintext ---------")
|
||||
plaintext.hexColumsPrint()
|
||||
println("Plaintext end ---------")
|
||||
if (expectedMac.contentEquals(mac).not()){
|
||||
throw InvalidTagException()
|
||||
}
|
||||
return plaintext
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,53 @@
|
||||
package com.ionspin.kotlin.crypto.highlevel
|
||||
|
||||
import com.ionspin.kotlin.crypto.Crypto
|
||||
import com.ionspin.kotlin.crypto.SymmetricKey
|
||||
import com.ionspin.kotlin.crypto.hash.decodeToString
|
||||
import com.ionspin.kotlin.crypto.hash.encodeToUByteArray
|
||||
import com.ionspin.kotlin.crypto.util.hexColumsPrint
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Created by Ugljesa Jovanovic
|
||||
* ugljesa.jovanovic@ionspin.com
|
||||
* on 09-Jul-2020
|
||||
*/
|
||||
class EncryptionTest {
|
||||
@Test
|
||||
fun testMultipartEncryption() {
|
||||
val plaintext = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
|
||||
"Vestibulum maximus tincidunt urna. " +
|
||||
"Nullam sit amet erat id arcu porttitor varius ut at metus. " +
|
||||
"Nunc sit amet felis vel velit ornare gravida. " +
|
||||
"Curabitur tellus lacus, pulvinar a diam at tincidunt.").encodeToUByteArray()
|
||||
plaintext.hexColumsPrint()
|
||||
val additionalData = "Additional data 1".encodeToUByteArray()
|
||||
// val additionalData = ubyteArrayOf()
|
||||
val keyValue = UByteArray(32) { it.toUByte() }
|
||||
val key = SymmetricKey(keyValue)
|
||||
val encryptor = Crypto.Encryption.createMultipartEncryptor(key)
|
||||
val header = encryptor.startEncryption()
|
||||
val ciphertext1 = encryptor.encryptPartialData(plaintext.sliceArray(0 until 100), additionalData)
|
||||
val ciphertext2 = encryptor.encryptPartialData(plaintext.sliceArray(100 until 200))
|
||||
val ciphertext3 = encryptor.encryptPartialData(plaintext.sliceArray(200 until 250))
|
||||
//decrypt
|
||||
val decryptor = Crypto.Encryption.createMultipartDecryptor(key, header)
|
||||
println("Initialized")
|
||||
val plaintext1 = decryptor.decryptPartialData(ciphertext1, additionalData)
|
||||
val plaintext2 = decryptor.decryptPartialData(ciphertext2)
|
||||
val plaintext3 = decryptor.decryptPartialData(ciphertext3)
|
||||
|
||||
val combinedPlaintext = plaintext1.data + plaintext2.data + plaintext3.data
|
||||
println("---- Plaintext -----")
|
||||
plaintext.hexColumsPrint()
|
||||
println("---- Plaintext result -----")
|
||||
combinedPlaintext.hexColumsPrint()
|
||||
assertTrue {
|
||||
plaintext.contentEquals(combinedPlaintext)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user