Working encryption ctr, but decryption fails
This commit is contained in:
parent
a8ad00a690
commit
18ac28f3c3
@ -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() {
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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.
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user