Working encryption ctr, but decryption fails

This commit is contained in:
Ugljesa Jovanovic 2019-09-22 22:46:30 +02:00 committed by Ugljesa Jovanovic
parent a8ad00a690
commit 18ac28f3c3
No known key found for this signature in database
GPG Key ID: 33A5F353387711A5
5 changed files with 276 additions and 5 deletions

View File

@ -84,7 +84,8 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
var round = 0 var round = 0
var completed : Boolean = false
private set
fun subBytes() { fun subBytes() {
state.forEachIndexed { indexRow, row -> state.forEachIndexed { indexRow, row ->
@ -247,11 +248,16 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
expandedKey[i] = expandedKey[i - aesKey.numberOf32BitWords].mapIndexed { index, it -> expandedKey[i] = expandedKey[i - aesKey.numberOf32BitWords].mapIndexed { index, it ->
it xor temp[index] it xor temp[index]
}.toTypedArray() }.toTypedArray()
clearArray(temp)
} }
return expandedKey return expandedKey
} }
fun encrypt(): Array<UByte> { fun encrypt(): Array<UByte> {
if (completed) {
throw RuntimeException("Encrypt can only be called once per Aes instance, since the state is cleared at the " +
"end of the operation")
}
printState() printState()
addRoundKey() addRoundKey()
printState() printState()
@ -280,11 +286,16 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
transposedMatrix[i][j] = state[j][i] transposedMatrix[i][j] = state[j][i]
} }
} }
// printState(transposedMatrix) state.forEach { clearArray(it) }
completed = true
return transposedMatrix.flatten().toTypedArray() return transposedMatrix.flatten().toTypedArray()
} }
fun decrypt(): Array<UByte> { fun decrypt(): Array<UByte> {
if (completed) {
throw RuntimeException("Decrypt can only be called once per Aes instance, since the state is cleared at the " +
"end of the operation")
}
round = numberOfRounds round = numberOfRounds
printState() printState()
inverseAddRoundKey() inverseAddRoundKey()
@ -316,9 +327,15 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array<UBy
} }
} }
printState(transposedMatrix) printState(transposedMatrix)
state.forEach { clearArray(it) }
completed = true
return transposedMatrix.flatten().toTypedArray() return transposedMatrix.flatten().toTypedArray()
} }
private fun clearArray(array : Array<UByte>) {
array.indices.forEach { array[it] = 0U }
}
private fun printState() { private fun printState() {

View File

@ -125,10 +125,20 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
fun decrypt(): Array<UByte> { fun decrypt(): Array<UByte> {
val removePaddingCount = output.last().last() val removePaddingCount = output.last().last()
val removedPadding = output.last().dropLast(removePaddingCount.toInt() and 0x7F)
val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray()
return preparedOutput.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }
val removedPadding = if (removePaddingCount > 0U && removePaddingCount < 16U) {
output.last().dropLast(removePaddingCount.toInt() and 0x7F)
} else {
output.last().toList()
}
val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray()
//JS compiler freaks out here if we don't supply exact type
val reversed : List<Array<UByte>> = preparedOutput.reversed() as List<Array<UByte>>
val folded : Array<UByte> = reversed.foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc ->
acc + arrayOfUBytes }
return folded
} }
private fun appendToBuffer(array: Array<UByte>, start: Int) { private fun appendToBuffer(array: Array<UByte>, start: Int) {

View File

@ -0,0 +1,162 @@
/*
* 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.bignum.Endianness
import com.ionspin.kotlin.bignum.integer.BigInteger
import com.ionspin.kotlin.bignum.modular.ModularBigInteger
import com.ionspin.kotlin.crypto.SRNG
import com.ionspin.kotlin.crypto.chunked
import com.ionspin.kotlin.crypto.xor
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 22-Sep-2019
*/
@ExperimentalUnsignedTypes
class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCounter: Array<UByte>? = null) {
companion object {
const val BLOCK_BYTES = 16
val modularCreator = ModularBigInteger.creatorForModulo(BigInteger.ONE.shl(129) - 1)
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()
var previousEncrypted: Array<UByte> = arrayOf()
val counterStart = initialCounter ?: SRNG.getRandomBytes(16)
var blockCounter = modularCreator.fromBigInteger(BigInteger.fromUByteArray(counterStart, Endianness.BIG))
val output = MutableList<Array<UByte>>(0) { arrayOf() }
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, blockCounter)
blockCounter += 1
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)
}
}
}
}
}
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], blockCounter)
blockCounter += 1
output += consumeBlock(chunks[1], blockCounter)
} else {
output += consumeBlock(lastBlockPadded, blockCounter)
}
}
return output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }
}
fun decrypt(): Array<UByte> {
val removePaddingCount = output.last().last()
val removedPadding = output.last().dropLast(removePaddingCount.toInt() and 0x7F)
val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray()
//JS compiler freaks out here if we don't supply exact type
val reversed : List<Array<UByte>> = preparedOutput.reversed() as List<Array<UByte>>
val folded : Array<UByte> = reversed.foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc ->
acc + arrayOfUBytes }
return folded
}
private fun appendToBuffer(array: Array<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size
}
private fun consumeBlock(data: Array<UByte>, blockCount: ModularBigInteger): Array<UByte> {
return when (mode) {
Mode.ENCRYPT -> {
Aes.encrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data
}
Mode.DECRYPT -> {
Aes.decrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data
}
}
}
}

View File

@ -0,0 +1,75 @@
/*
* 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 AesCtrTest {
@Test
fun testCtrEncryption() {
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
val plaintext = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val expectedCipherText = "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
val aesCbc = AesCtr(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initialCounter = ic.hexStringToUByteArray())
aesCbc.addData(
plaintext.hexStringToUByteArray()
)
val encrypted = aesCbc.encrypt()
println("Encrypted: ${encrypted.toHexString()}")
assertTrue {
expectedCipherText == encrypted.toHexString()
}
}
@Test
fun testCtrDecryption() {
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
val cipherText = "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
val expectedPlainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val aesCbc = AesCtr(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initialCounter = ic.hexStringToUByteArray())
aesCbc.addData(cipherText.hexStringToUByteArray())
val decrypted = aesCbc.decrypt()
println("Decrypted: ${decrypted.toHexString()}")
assertTrue {
expectedPlainText == decrypted.toHexString()
}
}
}

View File

@ -23,7 +23,14 @@ package com.ionspin.kotlin.crypto
*/ */
actual object SRNG { actual object SRNG {
var counter = 0 var counter = 0
@ExperimentalUnsignedTypes
actual fun getRandomBytes(amount: Int): Array<UByte> { actual fun getRandomBytes(amount: Int): Array<UByte> {
// val runningOnNode = js("(typeof window === 'undefined')").unsafeCast<Boolean>()
// if (runningOnNode) {
// js("var crypto = require('crypto')").asDynamic().randomBytes(amount)
// } else {
// throw RuntimeException("Secure random not supported yet for non-nodejs environment")
// }
return arrayOf((counter++).toUByte()) // TODO Wow. Such random. Very entropy. return arrayOf((counter++).toUByte()) // TODO Wow. Such random. Very entropy.
} }
} }