Added aes cbc

This commit is contained in:
Ugljesa Jovanovic 2019-09-21 22:20:05 +02:00 committed by Ugljesa Jovanovic
parent d3ed17b1c0
commit 1345125252
No known key found for this signature in database
GPG Key ID: 33A5F353387711A5
8 changed files with 226 additions and 70 deletions

View File

@ -150,7 +150,6 @@ kotlin {
val nativeMain by creating {
dependsOn(commonMain)
dependencies {
implementation(Deps.Native.coroutines)
}
}
val nativeTest by creating {

View File

@ -72,3 +72,19 @@ infix fun Array<UByte>.xor(other : Array<UByte>) : Array<UByte> {
}
return this.copyOf().mapIndexed { index, it -> it xor other[index] }.toTypedArray()
}
@ExperimentalUnsignedTypes
fun String.hexStringToUByteArray() : Array<UByte> {
return this.chunked(2).map { it.toUByte(16) }.toTypedArray()
}
@ExperimentalUnsignedTypes
fun Array<UByte>.toHexString() : String {
return this.joinToString(separator = "") {
if (it <= 0x0FU) {
"0${it.toString(16)}"
} else {
it.toString(16)
}
}
}

View File

@ -17,6 +17,7 @@
package com.ionspin.kotlin.crypto.symmetric
import com.ionspin.kotlin.crypto.SRNG
import com.ionspin.kotlin.crypto.chunked
import com.ionspin.kotlin.crypto.xor
/**
@ -25,86 +26,133 @@ import com.ionspin.kotlin.crypto.xor
* on 21-Sep-2019
*/
@ExperimentalUnsignedTypes
class AesCbc (val aesKey: AesKey) {
class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializationVector: Array<UByte>? = null) {
companion object {
val BLOCK_BYTES = 16
const val BLOCK_BYTES = 16
fun encrypt(aesKey: AesKey, data: Array<UByte>): Array<UByte> {
val aesCbc = AesCbc(aesKey, Mode.ENCRYPT)
aesCbc.addData(data)
return aesCbc.encrypt()
}
private fun padToBlock(unpadded: Array<UByte>): Array<UByte> {
val paddingSize = 16 - unpadded.size
if (unpadded.size == BLOCK_BYTES) {
return unpadded
}
if (unpadded.size == BLOCK_BYTES) {
return Array(BLOCK_BYTES) {
BLOCK_BYTES.toUByte()
}
}
if (unpadded.size > BLOCK_BYTES) {
throw IllegalStateException("Block larger than 128 bytes")
}
return Array(BLOCK_BYTES) {
when (it) {
in unpadded.indices -> unpadded[it]
else -> paddingSize.toUByte()
}
}
}
}
var currentOutput: Array<UByte> = arrayOf()
val iv = SRNG.getRandomBytes(16)
var previousEncrypted: Array<UByte> = arrayOf()
val iv = initializationVector ?: SRNG.getRandomBytes(16)
val output = MutableList<Array<UByte>>(0) { arrayOf() }
val buffer : Array<UByte> = UByteArray(16) { 0U }.toTypedArray()
var buffer: Array<UByte> = UByteArray(16) { 0U }.toTypedArray()
var bufferCounter = 0
fun addData(data: Array<UByte>) {
//Padding
when {
bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter)
bufferCounter + data.size >= BLOCK_BYTES -> {
val chunked = data.chunked(BLOCK_BYTES)
chunked.forEach { chunk ->
if (bufferCounter + chunk.size < BLOCK_BYTES) {
appendToBuffer(chunk, bufferCounter)
} else {
chunk.copyInto(
destination = buffer,
destinationOffset = bufferCounter,
startIndex = 0,
endIndex = BLOCK_BYTES - bufferCounter
)
output += consumeBlock(buffer)
buffer = Array<UByte>(BLOCK_BYTES) {
when (it) {
in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> {
chunk[it + (BLOCK_BYTES - bufferCounter)]
}
else -> {
0U
}
}
// fun addData(data : UByteArray) {
// //Padding
// when {
// bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter)
// bufferCounter + data.size >= BLOCK_BYTES -> {
// val chunked = data.chunked(BLOCK_BYTES)
// chunked.forEach { chunk ->
// if (bufferCounter + chunk.size < BLOCK_BYTES) {
// appendToBuffer(chunk, bufferCounter)
// } else {
// chunk.copyInto(
// destination = buffer,
// destinationOffset = bufferCounter,
// startIndex = 0,
// endIndex = BLOCK_BYTES - bufferCounter
// )
// counter += BLOCK_BYTES
// consumeBlock(buffer)
// buffer = Array<UByte>(BLOCK_BYTES) {
// when (it) {
// in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> {
// chunk[it + (BLOCK_BYTES - bufferCounter)]
// }
// else -> {
// 0U
// }
// }
//
// }
// bufferCounter = chunk.size - (BLOCK_BYTES - bufferCounter)
// }
// }
//
// }
// data.size < 16 -> {
// val paddingSize = 16 - data.size
// val padding = UByteArray(16 - data.size) { paddingSize.toUByte() }
// output += processBlock(data + padding)
// }
// data.size == 16 -> {
//
// }
// data.size > 16 -> {
//
// }
// }
//
// if (data.size < 16) {
//
// }
//
// }
}
bufferCounter = chunk.size - (BLOCK_BYTES - bufferCounter)
}
}
}
}
}
fun encrypt(): Array<UByte> {
if (bufferCounter > 0) {
val lastBlockPadded = padToBlock(buffer)
if (lastBlockPadded.size > BLOCK_BYTES) {
val chunks = lastBlockPadded.chunked(BLOCK_BYTES)
output += consumeBlock(chunks[0])
output += consumeBlock(chunks[1])
} else {
output += consumeBlock(lastBlockPadded)
}
}
return output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }
}
fun decrypt(): Array<UByte> {
return output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }
}
private fun appendToBuffer(array: Array<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size
}
private fun processBlock(data : Array<UByte>) : Array<UByte> {
if (currentOutput.isEmpty()) {
currentOutput = Aes.encrypt(aesKey, data xor iv)
private fun consumeBlock(data: Array<UByte>): Array<UByte> {
return when (mode) {
Mode.ENCRYPT -> {
currentOutput = if (currentOutput.isEmpty()) {
Aes.encrypt(aesKey, data xor iv)
} else {
currentOutput = Aes.encrypt(aesKey, data xor currentOutput)
Aes.encrypt(aesKey, data xor currentOutput)
}
currentOutput
}
Mode.DECRYPT -> {
if (currentOutput.isEmpty()) {
currentOutput = Aes.decrypt(aesKey, data) xor iv
previousEncrypted = data
} else {
currentOutput = Aes.decrypt(aesKey, data) xor previousEncrypted
}
currentOutput
}
return currentOutput
}
}
}

View File

@ -16,13 +16,19 @@
package com.ionspin.kotlin.crypto
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
actual object SRNG {
actual fun getRandomBytes(amount: Int): Array<UByte> {
TODO("not implemented yet")
class SRNGTest {
@Test
fun testSrng() {
val randomBytes1 = SRNG.getRandomBytes(10)
val randomBytes2 = SRNG.getRandomBytes(10)
assertTrue { !randomBytes1.contentEquals(randomBytes2) }
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2019 Ugljesa Jovanovic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ionspin.kotlin.crypto.symmetric
import com.ionspin.kotlin.crypto.hexStringToUByteArray
import com.ionspin.kotlin.crypto.toHexString
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
@ExperimentalUnsignedTypes
class AesCbcTest {
@Test
fun testCbcEncryption() {
val key = "4278b840fb44aaa757c1bf04acbe1a3e"
val iv = "57f02a5c5339daeb0a2908a06ac6393f"
val plaintext = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val expectedCipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7"
val aesCbc = AesCbc(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initializationVector = iv.hexStringToUByteArray())
aesCbc.addData(plaintext.hexStringToUByteArray())
val encrypted = aesCbc.encrypt()
println("Decrypted: ${encrypted.toHexString()}")
assertTrue {
expectedCipherText == encrypted.toHexString()
}
}
@Test
fun testCbcDecryption() {
val key = "4278b840fb44aaa757c1bf04acbe1a3e"
val iv = "57f02a5c5339daeb0a2908a06ac6393f"
val cipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7"
val expectedPlainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val aesCbc = AesCbc(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initializationVector = iv.hexStringToUByteArray())
aesCbc.addData(cipherText.hexStringToUByteArray())
val decrypted = aesCbc.decrypt()
println("Encrypted: ${decrypted.toHexString()}")
assertTrue {
expectedPlainText == decrypted.toHexString()
}
}
}

View File

@ -22,7 +22,8 @@ package com.ionspin.kotlin.crypto
* on 21-Sep-2019
*/
actual object SRNG {
var counter = 0
actual fun getRandomBytes(amount: Int): Array<UByte> {
TODO("not implemented yet")
return arrayOf((counter++).toUByte()) // TODO Wow. Such random. Very entropy.
}
}

View File

@ -16,13 +16,19 @@
package com.ionspin.kotlin.crypto
import java.security.SecureRandom
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
@ExperimentalUnsignedTypes
actual object SRNG {
val secureRandom = SecureRandom()
actual fun getRandomBytes(amount: Int): Array<UByte> {
TODO("not implemented yet")
val byteArray = ByteArray(amount)
secureRandom.nextBytes(byteArray)
return byteArray.toUByteArray().toTypedArray()
}
}

View File

@ -16,13 +16,26 @@
package com.ionspin.kotlin.crypto
import kotlinx.cinterop.*
import platform.posix.*
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
*/
actual object SRNG {
@Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
actual fun getRandomBytes(amount: Int): Array<UByte> {
TODO("not implemented yet")
memScoped {
val array = allocArray<UByteVar>(amount)
val urandomFile = fopen("/dev/urandom", "rb")
if (urandomFile != null) {
fread(array, 1, amount.convert(), urandomFile)
}
return Array(amount) {
array[it]
}
}
}
}