Add high level AEAD tests, implement missing initializations, implement pure decryption

This commit is contained in:
Ugljesa Jovanovic 2020-07-09 23:44:30 +02:00 committed by Ugljesa Jovanovic
parent 579c44fcc7
commit 55b5641f14
No known key found for this signature in database
GPG Key ID: 178E6DFCECCB0E0F
10 changed files with 237 additions and 22 deletions

View File

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

View File

@ -43,3 +43,7 @@ fun String.encodeToUByteArray() : UByteArray{
return encodeToByteArray().asUByteArray()
}
fun UByteArray.decodeToString() : String {
return this.asByteArray().contentToString()
}

View File

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

View File

@ -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()

View File

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

View File

@ -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")

View File

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

View File

@ -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()

View File

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

View File

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