Multipart API continuation

This commit is contained in:
Ugljesa Jovanovic 2020-06-23 19:54:30 +02:00 committed by Ugljesa Jovanovic
parent f107db3312
commit 233ee1bf55
No known key found for this signature in database
GPG Key ID: 178E6DFCECCB0E0F
14 changed files with 226 additions and 102 deletions

View File

@ -0,0 +1,36 @@
package com.ionspin.kotlin.crypto.authenticated
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 22-Jun-2020
*/
interface AuthenticatedEncryption {
fun encrypt(key: UByteArray, nonce: UByteArray, message: UByteArray, additionalData: UByteArray) : UByteArray
fun decrypt(key: UByteArray, nonce: UByteArray, cipherText: UByteArray, additionalData: UByteArray) : UByteArray
}
data class EncryptedDataPart(val data : UByteArray)
data class DecryptedDataPart(val data : UByteArray)
data class MultipartEncryptedDataDescriptor(val data: UByteArray, val nonce: UByteArray)
interface MultipartAuthenticatedVerification {
fun verifyPartialData(data: EncryptedDataPart)
fun finalizeVerificationAndPrepareDecryptor() : MultipartAuthenticatedDecryption
}
interface MultipartAuthenticatedDecryption {
fun decryptPartialData(data: EncryptedDataPart) : DecryptedDataPart
}
interface MultipartAuthenticatedEncryption {
fun encryptPartialData(data: UByteArray) : EncryptedDataPart
fun finish() : MultipartEncryptedDataDescriptor
}

View File

@ -22,12 +22,12 @@ package com.ionspin.kotlin.crypto.hash
* ugljesa.jovanovic@ionspin.com * ugljesa.jovanovic@ionspin.com
* on 20-Jul-2019 * on 20-Jul-2019
*/ */
interface Hash { interface HashFunction {
val MAX_HASH_BYTES : Int val MAX_HASH_BYTES : Int
} }
interface UpdatableHash : Hash { interface MultiPartHash : HashFunction {
fun update(data : UByteArray) fun update(data : UByteArray)
fun digest() : UByteArray fun digest() : UByteArray
@ -35,7 +35,7 @@ interface UpdatableHash : Hash {
} }
interface StatelessHash : Hash { interface Hash : HashFunction {
} }

View File

@ -1,7 +1,7 @@
package com.ionspin.kotlin.crypto.hash.blake2b package com.ionspin.kotlin.crypto.hash.blake2b
import com.ionspin.kotlin.crypto.hash.StatelessHash import com.ionspin.kotlin.crypto.hash.Hash
import com.ionspin.kotlin.crypto.hash.UpdatableHash import com.ionspin.kotlin.crypto.hash.MultiPartHash
/** /**
* Created by Ugljesa Jovanovic * Created by Ugljesa Jovanovic
@ -13,12 +13,12 @@ object Blake2bProperties {
const val MAX_HASH_BYTES = 64 const val MAX_HASH_BYTES = 64
} }
interface Blake2b : UpdatableHash { interface Blake2b : MultiPartHash {
override val MAX_HASH_BYTES: Int override val MAX_HASH_BYTES: Int
get() = Blake2bProperties.MAX_HASH_BYTES get() = Blake2bProperties.MAX_HASH_BYTES
} }
interface Blake2bStateless : StatelessHash { interface Blake2bStateless : Hash {
override val MAX_HASH_BYTES: Int override val MAX_HASH_BYTES: Int
get() = Blake2bProperties.MAX_HASH_BYTES get() = Blake2bProperties.MAX_HASH_BYTES

View File

@ -1,7 +1,7 @@
package com.ionspin.kotlin.crypto.hash.sha package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.hash.StatelessHash import com.ionspin.kotlin.crypto.hash.Hash
import com.ionspin.kotlin.crypto.hash.UpdatableHash import com.ionspin.kotlin.crypto.hash.MultiPartHash
/** /**
* Created by Ugljesa Jovanovic * Created by Ugljesa Jovanovic
@ -12,11 +12,11 @@ object Sha256Properties {
const val MAX_HASH_BYTES = 32 const val MAX_HASH_BYTES = 32
} }
interface Sha256 : UpdatableHash { interface Sha256 : MultiPartHash {
override val MAX_HASH_BYTES: Int override val MAX_HASH_BYTES: Int
get() = Sha256Properties.MAX_HASH_BYTES get() = Sha256Properties.MAX_HASH_BYTES
} }
interface StatelessSha256 : StatelessHash { interface StatelessSha256 : Hash {
override val MAX_HASH_BYTES: Int override val MAX_HASH_BYTES: Int
get() = Sha256Properties.MAX_HASH_BYTES get() = Sha256Properties.MAX_HASH_BYTES

View File

@ -1,7 +1,7 @@
package com.ionspin.kotlin.crypto.hash.sha package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.hash.StatelessHash import com.ionspin.kotlin.crypto.hash.Hash
import com.ionspin.kotlin.crypto.hash.UpdatableHash import com.ionspin.kotlin.crypto.hash.MultiPartHash
/** /**
* Created by Ugljesa Jovanovic * Created by Ugljesa Jovanovic
@ -11,11 +11,11 @@ import com.ionspin.kotlin.crypto.hash.UpdatableHash
object Sha512Properties { object Sha512Properties {
const val MAX_HASH_BYTES = 64 const val MAX_HASH_BYTES = 64
} }
interface Sha512 : UpdatableHash { interface Sha512 : MultiPartHash {
override val MAX_HASH_BYTES: Int override val MAX_HASH_BYTES: Int
get() = Sha256Properties.MAX_HASH_BYTES get() = Sha256Properties.MAX_HASH_BYTES
} }
interface StatelessSha512 : StatelessHash { interface StatelessSha512 : Hash {
override val MAX_HASH_BYTES: Int override val MAX_HASH_BYTES: Int
get() = Sha512Properties.MAX_HASH_BYTES get() = Sha512Properties.MAX_HASH_BYTES

View File

@ -0,0 +1,7 @@
package com.ionspin.kotlin.crypto.util
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 22-Jun-2020
*/

View File

@ -0,0 +1,18 @@
package com.ionspin.kotlin.crypto.util
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 22-Jun-2020
*/
fun UByteArray.overwriteWithZeroes() {
for (i in 0 until size) {
this[i] = 0U
}
}
fun UIntArray.overwriteWithZeroes() {
for (i in 0 until size) {
this[i] = 0U
}
}

View File

@ -0,0 +1,56 @@
package com.ionspin.kotlin.crypto.util
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 22-Jun-2020
*/
fun Array<Byte>.hexColumsPrint() {
val printout = this.map { it.toString(16) }.chunked(16)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun Array<UByte>.hexColumsPrint(chunk : Int = 16) {
val printout = this.map { it.toString(16).padStart(2, '0') }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun UByteArray.hexColumsPrint(chunk : Int = 16) {
val printout = this.map { it.toString(16).padStart(2, '0') }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun Array<ULong>.hexColumsPrint(chunk: Int = 3) {
val printout = this.map { it.toString(16) }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun String.hexStringToTypedUByteArray() : Array<UByte> {
return this.chunked(2).map { it.toUByte(16) }.toTypedArray()
}
fun String.hexStringToUByteArray() : UByteArray {
return this.chunked(2).map { it.toUByte(16) }.toUByteArray()
}
fun Array<UByte>.toHexString() : String {
return this.joinToString(separator = "") {
if (it <= 0x0FU) {
"0${it.toString(16)}"
} else {
it.toString(16)
}
}
}
fun UByteArray.toHexString() : String {
return this.joinToString(separator = "") {
if (it <= 0x0FU) {
"0${it.toString(16)}"
} else {
it.toString(16)
}
}
}

View File

@ -23,25 +23,7 @@ package com.ionspin.kotlin.crypto.util
* ugljesa.jovanovic@ionspin.com * ugljesa.jovanovic@ionspin.com
* on 15-Jul-2019 * on 15-Jul-2019
*/ */
fun Array<Byte>.hexColumsPrint() {
val printout = this.map { it.toString(16) }.chunked(16)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun Array<UByte>.hexColumsPrint(chunk : Int = 16) {
val printout = this.map { it.toString(16).padStart(2, '0') }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun UByteArray.hexColumsPrint(chunk : Int = 16) {
val printout = this.map { it.toString(16).padStart(2, '0') }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
fun Array<ULong>.hexColumsPrint(chunk: Int = 3) {
val printout = this.map { it.toString(16) }.chunked(chunk)
printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
}
inline fun <reified T> Array<T>.chunked(sliceSize: Int): Array<Array<T>> { inline fun <reified T> Array<T>.chunked(sliceSize: Int): Array<Array<T>> {
val last = this.size % sliceSize val last = this.size % sliceSize
@ -89,36 +71,8 @@ infix fun UByteArray.xor(other : UByteArray) : UByteArray {
} }
fun String.hexStringToTypedUByteArray() : Array<UByte> {
return this.chunked(2).map { it.toUByte(16) }.toTypedArray()
}
fun String.hexStringToUByteArray() : UByteArray {
return this.chunked(2).map { it.toUByte(16) }.toUByteArray()
}
fun Array<UByte>.toHexString() : String {
return this.joinToString(separator = "") {
if (it <= 0x0FU) {
"0${it.toString(16)}"
} else {
it.toString(16)
}
}
}
fun UByteArray.toHexString() : String {
return this.joinToString(separator = "") {
if (it <= 0x0FU) {
"0${it.toString(16)}"
} else {
it.toString(16)
}
}
}
// UInt / Array utils // UInt / Array utils

View File

@ -1,7 +1,6 @@
package com.ionspin.kotlin.crypto package com.ionspin.kotlin.crypto
import com.ionspin.kotlin.crypto.authenticated.XChaCha20Poly1305Pure import com.ionspin.kotlin.crypto.authenticated.*
import com.ionspin.kotlin.crypto.hash.UpdatableHash
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bProperties import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bProperties
import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bPure import com.ionspin.kotlin.crypto.hash.blake2b.Blake2bPure
import com.ionspin.kotlin.crypto.hash.encodeToUByteArray import com.ionspin.kotlin.crypto.hash.encodeToUByteArray
@ -115,17 +114,17 @@ data class EncryptedData internal constructor(val ciphertext: UByteArray, val no
object PublicApi { object PublicApi {
object Hash { object Hashing {
fun hash(data: UByteArray, key : UByteArray = ubyteArrayOf()) : HashedData { fun hash(data: UByteArray, key : UByteArray = ubyteArrayOf()) : HashedData {
return HashedData(Blake2bPureStateless.digest(data, key)) return HashedData(Blake2bPureStateless.digest(data, key))
} }
fun updateableHash(key: UByteArray? = null) : UpdatableHash { fun multipartHash(key: UByteArray? = null) : com.ionspin.kotlin.crypto.hash.MultiPartHash {
return Blake2bPure(key) return Blake2bPure(key)
} }
} }
object Symmetric { object Encryption {
fun encrypt(key: SymmetricKey, data : Encryptable<*>, additionalData : UByteArray = ubyteArrayOf()) : EncryptedData { fun authenticatedEncryption(key: SymmetricKey, data : Encryptable<*>, additionalData : UByteArray = ubyteArrayOf()) : EncryptedData {
if (key.value.size != 32) { if (key.value.size != 32) {
throw RuntimeException("Invalid key size! Required 32, supplied ${key.value.size}") throw RuntimeException("Invalid key size! Required 32, supplied ${key.value.size}")
} }
@ -138,5 +137,49 @@ object PublicApi {
return byteArrayDeserializer(XChaCha20Poly1305Pure.decrypt(key.value, encryptedData.nonce, encryptedData.ciphertext, additionalData)) return byteArrayDeserializer(XChaCha20Poly1305Pure.decrypt(key.value, encryptedData.nonce, encryptedData.ciphertext, additionalData))
} }
fun multipartAuthenticatedEncrypt(key: SymmetricKey, additionalData: UByteArray) : MultipartAuthenticatedEncryption {
return MultipartAuthenticatedEncryptor(key, additionalData)
}
fun getMultipartVerificator(key: SymmetricKey, dataDescriptor: MultipartEncryptedDataDescriptor, additionalData: UByteArray) : MultipartAuthenticatedVerification {
return MultiplatformAuthenticatedVerificator(key, dataDescriptor, additionalData)
}
} }
} }
class MultipartAuthenticatedEncryptor internal constructor(val key : SymmetricKey, additionalData: UByteArray) : MultipartAuthenticatedEncryption {
val primitive = XChaCha20Poly1305Pure(key.value, additionalData)
override fun encryptPartialData(data: UByteArray): EncryptedDataPart {
return EncryptedDataPart(primitive.encryptPartialData(data))
}
override fun finish(): MultipartEncryptedDataDescriptor {
val finished = primitive.finish()
return MultipartEncryptedDataDescriptor(finished.first, finished.second)
}
}
class MultiplatformAuthenticatedVerificator internal constructor(key: SymmetricKey, multipartEncryptedDataDescriptor: MultipartEncryptedDataDescriptor, additionalData: UByteArray) : MultipartAuthenticatedVerification {
val primitive = XChaCha20Poly1305Pure(key.value, additionalData)
val tag = multipartEncryptedDataDescriptor.data.sliceArray(
multipartEncryptedDataDescriptor.data.size - 16 until multipartEncryptedDataDescriptor.data.size
)
override fun verifyPartialData(data: EncryptedDataPart) {
primitive.encryptPartialData(data.data)
}
override fun finalizeVerificationAndPrepareDecryptor(): MultipartAuthenticatedDecryption {
primitive.finalizeVerificationAndPrepareDecryptor(tag)
return MultipartAuthenticatedDecryptor(primitive)
}
}
class MultipartAuthenticatedDecryptor internal constructor(val encryptor: XChaCha20Poly1305Pure) : MultipartAuthenticatedDecryption {
override fun decryptPartialData(data: EncryptedDataPart): DecryptedDataPart {
encryptor.decrypt(data.data)
}
}

View File

@ -1,5 +1,6 @@
package com.ionspin.kotlin.crypto.authenticated package com.ionspin.kotlin.crypto.authenticated
import com.ionspin.kotlin.crypto.SRNG
import com.ionspin.kotlin.crypto.mac.Poly1305 import com.ionspin.kotlin.crypto.mac.Poly1305
import com.ionspin.kotlin.crypto.symmetric.ChaCha20Pure import com.ionspin.kotlin.crypto.symmetric.ChaCha20Pure
import com.ionspin.kotlin.crypto.symmetric.XChaCha20Pure import com.ionspin.kotlin.crypto.symmetric.XChaCha20Pure
@ -10,10 +11,10 @@ import com.ionspin.kotlin.crypto.util.toLittleEndianUByteArray
* ugljesa.jovanovic@ionspin.com * ugljesa.jovanovic@ionspin.com
* on 17-Jun-2020 * on 17-Jun-2020
*/ */
class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray, val additionalData: UByteArray) { class XChaCha20Poly1305Pure(val key: UByteArray, val additionalData: UByteArray) : {
companion object { companion object : AuthenticatedEncryption {
fun encrypt(key: UByteArray, nonce: UByteArray, message: UByteArray, additionalData: UByteArray) : UByteArray { override fun encrypt(key: UByteArray, nonce: UByteArray, message: UByteArray, additionalData: UByteArray) : UByteArray {
val subKey = XChaCha20Pure.hChacha(key, nonce) val subKey = XChaCha20Pure.hChacha(key, nonce)
val authKey = val authKey =
ChaCha20Pure.encrypt( ChaCha20Pure.encrypt(
@ -36,7 +37,7 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray, val addi
return cipherText + tag return cipherText + tag
} }
fun decrypt(key: UByteArray, nonce: UByteArray, cipherText: UByteArray, additionalData: UByteArray) : UByteArray { override fun decrypt(key: UByteArray, nonce: UByteArray, cipherText: UByteArray, additionalData: UByteArray) : UByteArray {
val subKey = XChaCha20Pure.hChacha(key, nonce) val subKey = XChaCha20Pure.hChacha(key, nonce)
val authKey = val authKey =
ChaCha20Pure.encrypt( ChaCha20Pure.encrypt(
@ -62,8 +63,11 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray, val addi
//4. Decrypt data //4. Decrypt data
return XChaCha20Pure.xorWithKeystream(key, nonce, cipherTextWithoutTag, 1U) return XChaCha20Pure.xorWithKeystream(key, nonce, cipherTextWithoutTag, 1U)
} }
} }
val nonce = SRNG.getRandomBytes(24)
private val updateableEncryptionPrimitive = XChaCha20Pure(key, nonce, initialCounter = 1U) private val updateableEncryptionPrimitive = XChaCha20Pure(key, nonce, initialCounter = 1U)
private val updateableMacPrimitive : Poly1305 private val updateableMacPrimitive : Poly1305
@ -93,11 +97,34 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray, val addi
fun encryptPartialData(data: UByteArray) : UByteArray { fun encryptPartialData(data: UByteArray) : UByteArray {
processedBytes += data.size processedBytes += data.size
val encrypted = updateableEncryptionPrimitive.encryptPartialData(data) val encrypted = updateableEncryptionPrimitive.xorWithKeystream(data)
processPolyBytes(encrypted) processPolyBytes(encrypted)
return encrypted return encrypted
} }
fun verifyPartialData(data: UByteArray) {
processPolyBytes(data)
}
fun finalizeVerificationAndPrepareDecryptor(expectedTag: UByteArray): MultipartAuthenticatedDecryption {
val cipherTextPad = UByteArray(16 - processedBytes % 16) { 0U }
val macData = cipherTextPad +
additionalData.size.toULong().toLittleEndianUByteArray() +
processedBytes.toULong().toLittleEndianUByteArray()
processPolyBytes(macData)
val tag = updateableMacPrimitive.finalizeMac()
if (!tag.contentEquals(expectedTag)) {
throw RuntimeException("Invalid tag") //TODO Replace with proper exception
}
}
fun decrypt(data: UByteArray) : UByteArray {
processedBytes += data.size
val decrypted = updateableEncryptionPrimitive.xorWithKeystream(data)
processPolyBytes(decrypted)
return decrypted
}
private fun processPolyBytes(data: UByteArray) { private fun processPolyBytes(data: UByteArray) {
if (polyBufferByteCounter == 0) { if (polyBufferByteCounter == 0) {
val polyBlocks = data.size / 16 val polyBlocks = data.size / 16
@ -139,12 +166,7 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray, val addi
} }
} }
fun finish() : Pair<UByteArray, UByteArray> {
fun finish() : UByteArray {
val cipherTextPad = UByteArray(16 - processedBytes % 16) { 0U } val cipherTextPad = UByteArray(16 - processedBytes % 16) { 0U }
val macData = cipherTextPad + val macData = cipherTextPad +
@ -152,9 +174,8 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray, val addi
processedBytes.toULong().toLittleEndianUByteArray() processedBytes.toULong().toLittleEndianUByteArray()
processPolyBytes(macData) processPolyBytes(macData)
val tag = updateableMacPrimitive.finalizeMac() val tag = updateableMacPrimitive.finalizeMac()
return tag return Pair(tag, nonce)
} }
}
}

View File

@ -132,7 +132,7 @@ class XChaCha20Pure(key: UByteArray, nonce: UByteArray, initialCounter: UInt = 0
hChaChaKey.overwriteWithZeroes() hChaChaKey.overwriteWithZeroes()
} }
fun encryptPartialData(data: UByteArray) : UByteArray { fun xorWithKeystream(data: UByteArray) : UByteArray {
val ciphertext = UByteArray(data.size) { 0U } val ciphertext = UByteArray(data.size) { 0U }
//First use remaining keystream //First use remaining keystream
var processedBytes = 0 var processedBytes = 0
@ -177,4 +177,4 @@ class XChaCha20Pure(key: UByteArray, nonce: UByteArray, initialCounter: UInt = 0
return ciphertext return ciphertext
} }
} }

View File

@ -344,17 +344,6 @@ fun Array<UByte>.fromBigEndianArrayToUInt() : UInt {
operator fun UInt.plus(other : UByteArray) : UByteArray { operator fun UInt.plus(other : UByteArray) : UByteArray {
return this.toLittleEndianUByteArray() + other return this.toLittleEndianUByteArray() + other
} }
fun UByteArray.overwriteWithZeroes() {
for (i in 0 until size) {
this[i] = 0U
}
}
fun UIntArray.overwriteWithZeroes() {
for (i in 0 until size) {
this[i] = 0U
}
}
//AES Flatten //AES Flatten
fun Collection<UByteArray>.flattenToUByteArray(): UByteArray { fun Collection<UByteArray>.flattenToUByteArray(): UByteArray {

View File

@ -236,9 +236,9 @@ class XChaCha20Test {
) )
val xChaCha = XChaCha20Pure(key, nonce, 1U) val xChaCha = XChaCha20Pure(key, nonce, 1U)
val firstChunk = xChaCha.encryptPartialData(message.sliceArray(0 until 5)) val firstChunk = xChaCha.xorWithKeystream(message.sliceArray(0 until 5))
val secondChunk = xChaCha.encryptPartialData(message.sliceArray(5 until 90)) val secondChunk = xChaCha.xorWithKeystream(message.sliceArray(5 until 90))
val thirdChunk = xChaCha.encryptPartialData(message.sliceArray(90 until message.size)) val thirdChunk = xChaCha.xorWithKeystream(message.sliceArray(90 until message.size))
assertTrue { assertTrue {
(firstChunk + secondChunk + thirdChunk).contentEquals(expected) (firstChunk + secondChunk + thirdChunk).contentEquals(expected)
@ -325,9 +325,9 @@ class XChaCha20Test {
) )
val xChaCha = XChaCha20Pure(key, nonce, 1U) val xChaCha = XChaCha20Pure(key, nonce, 1U)
val firstChunk = xChaCha.encryptPartialData(message.sliceArray(0 until 50)) val firstChunk = xChaCha.xorWithKeystream(message.sliceArray(0 until 50))
val secondChunk = xChaCha.encryptPartialData(message.sliceArray(50 until 200)) val secondChunk = xChaCha.xorWithKeystream(message.sliceArray(50 until 200))
val thirdChunk = xChaCha.encryptPartialData(message.sliceArray(200 until message.size)) val thirdChunk = xChaCha.xorWithKeystream(message.sliceArray(200 until message.size))
val result = (firstChunk + secondChunk + thirdChunk) val result = (firstChunk + secondChunk + thirdChunk)
assertTrue { assertTrue {
result.contentEquals(expected) result.contentEquals(expected)
@ -337,4 +337,4 @@ class XChaCha20Test {
} }
} }