Aes implemented

This commit is contained in:
Ugljesa Jovanovic 2019-09-23 23:14:55 +02:00 committed by Ugljesa Jovanovic
parent 69c81ec8e9
commit ec3b87db49
No known key found for this signature in database
GPG Key ID: 33A5F353387711A5
5 changed files with 302 additions and 111 deletions

View File

@ -7,6 +7,9 @@ Kotlin Multiplatform Crypto is a library for various cryptographic applications.
This is an extremely early release, currently only consisting of Blake2b and SHA256 and 512.
API is very opinionated, ment to be used on both encrypting and decrypting side. The idea is that API leaves less room for
errors when using it.
## Notes & Roadmap
**The API will move fast and break often until v1.0**
@ -28,8 +31,6 @@ No.
This is an experimental implementation, mostly for expanding personal understanding of cryptography.
It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure.
## Supported
## Hashing functions
* Blake2b
* SHA512
@ -37,6 +38,7 @@ It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be
## Symmetric cipher
* AES
* Modes: CBC, CTR
More to come.
@ -129,6 +131,58 @@ val sha512 = Sha512()
sha512.update("abc")
val result = sha512.digest()
```
### Symmetric encryption
#### AES
Aes is available with CBC and CTR mode through `AesCbc` and `AesCtr` classes/objects.
Similarly to hashes you can either use stateless or updateable version.
Initialization vector, or counter states are chosen by the SDK automaticaly, and returned alongside encrypted data
##### Stateless AesCbc and AesCtr
AesCtr
```kotlin
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCtr.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initialCounter
)
plainText == decrypted.toHexString()
```
AesCbc
```kotlin
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val encryptedDataAndInitializationVector = AesCbc.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCbc.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initilizationVector
)
plainText == decrypted.toHexString()
```

View File

@ -21,6 +21,12 @@ import com.ionspin.kotlin.crypto.chunked
import com.ionspin.kotlin.crypto.xor
/**
* Advanced encryption standard with cipher block chaining and PKCS #5
*
* For bulk encryption/decryption use [AesCbc.encrypt] and [AesCbc.decrypt]
*
* To get an instance of AesCbc and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor]
*
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 21-Sep-2019
@ -30,13 +36,39 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
companion object {
const val BLOCK_BYTES = 16
/**
* Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all
* data call [encrypt]
*/
fun createEncryptor(aesKey: AesKey) : AesCbc {
return AesCbc(aesKey, Mode.ENCRYPT)
}
/**
* Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all
* data call [decrypt]
*/
fun createDecryptor(aesKey : AesKey) : AesCbc {
return AesCbc(aesKey, Mode.DECRYPT)
}
fun encrypt(aesKey: AesKey, data: Array<UByte>): Array<UByte> {
/**
* Bulk encryption, returns encrypted data and a random initialization vector
*/
fun encrypt(aesKey: AesKey, data: Array<UByte>): EncryptedDataAndInitializationVector {
val aesCbc = AesCbc(aesKey, Mode.ENCRYPT)
aesCbc.addData(data)
return aesCbc.encrypt()
}
/**
* Bulk decryption, returns decrypted data
*/
fun decrypt(aesKey: AesKey, data: Array<UByte>, initialCounter: Array<UByte>? = null): Array<UByte> {
val aesCbc = AesCbc(aesKey, Mode.DECRYPT, initialCounter)
aesCbc.addData(data)
return aesCbc.decrypt()
}
private fun padToBlock(unpadded: Array<UByte>): Array<UByte> {
val paddingSize = 16 - unpadded.size
if (unpadded.size == BLOCK_BYTES) {
@ -109,7 +141,11 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
}
fun encrypt(): Array<UByte> {
/**
* Encrypt fed data and return it alongside the randomly chosen initialization vector
* @return Encrypted data and initialization vector
*/
fun encrypt(): EncryptedDataAndInitializationVector {
if (bufferCounter > 0) {
val lastBlockPadded = padToBlock(buffer)
if (lastBlockPadded.size > BLOCK_BYTES) {
@ -120,9 +156,16 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
output += consumeBlock(lastBlockPadded)
}
}
return output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }
return EncryptedDataAndInitializationVector(
output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes },
iv
)
}
/**
* Decrypt data
* @return Decrypted data
*/
fun decrypt(): Array<UByte> {
val removePaddingCount = output.last().last()
@ -169,4 +212,25 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa
}
}
@ExperimentalUnsignedTypes
data class EncryptedDataAndInitializationVector(val encryptedData : Array<UByte>, val initilizationVector : Array<UByte>) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as EncryptedDataAndInitializationVector
if (!encryptedData.contentEquals(other.encryptedData)) return false
if (!initilizationVector.contentEquals(other.initilizationVector)) return false
return true
}
override fun hashCode(): Int {
var result = encryptedData.contentHashCode()
result = 31 * result + initilizationVector.contentHashCode()
return result
}
}

View File

@ -21,9 +21,17 @@ 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.symmetric.AesCtr.Companion.encrypt
import com.ionspin.kotlin.crypto.xor
/**
*
* Advanced encryption standard with counter mode
*
* For bulk encryption/decryption use [AesCtr.encrypt] and [AesCtr.decrypt]
*
* To get an instance of AesCtr and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor]
*
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 22-Sep-2019
@ -34,38 +42,38 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
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()
val modularCreator = ModularBigInteger.creatorForModulo(BigInteger.ONE.shl(128) - 1)
/**
* Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all
* data call [encrypt]
*/
fun createEncryptor(aesKey: AesKey) : AesCtr {
return AesCtr(aesKey, Mode.ENCRYPT)
}
/**
* Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all
* data call [decrypt]
*/
fun createDecryptor(aesKey : AesKey) : AesCtr {
return AesCtr(aesKey, Mode.DECRYPT)
}
/**
* Bulk encryption, returns encrypted data and a random initial counter
*/
fun encrypt(aesKey: AesKey, data: Array<UByte>): EncryptedDataAndInitialCounter {
val aesCtr = AesCtr(aesKey, Mode.ENCRYPT)
aesCtr.addData(data)
return aesCtr.encrypt()
}
/**
* Bulk decryption, returns decrypted data
*/
fun decrypt(aesKey: AesKey, data: Array<UByte>, initialCounter: Array<UByte>? = null): Array<UByte> {
val aesCtr = AesCtr(aesKey, Mode.DECRYPT, initialCounter)
aesCtr.addData(data)
return aesCtr.decrypt()
}
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()
@ -115,34 +123,32 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
}
}
fun encrypt(): Array<UByte> {
/**
* Encrypt fed data and return it alongside the randomly chosen initial counter state
* @return Encrypted data and initial counter state
*/
fun encrypt(): EncryptedDataAndInitialCounter {
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)
}
output += consumeBlock(buffer, blockCounter)
}
return output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }
return EncryptedDataAndInitialCounter(
output.reversed().foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes },
counterStart
)
}
/**
* Decrypt data
* @return Decrypted data
*/
fun decrypt(): Array<UByte> {
val removePaddingCount = output.last().last()
val removedPadding = if (removePaddingCount > 0U && removePaddingCount < 16U) {
output.last().dropLast(removePaddingCount.toInt() and 0x7F)
} else {
output.last().toList()
if (bufferCounter > 0) {
output += consumeBlock(buffer, blockCounter)
}
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 }
val reversed: List<Array<UByte>> = output.reversed() as List<Array<UByte>>
val folded: Array<UByte> = reversed.foldRight(Array<UByte>(0) { 0U }) { arrayOfUBytes, acc ->
acc + arrayOfUBytes
}
return folded
}
@ -163,4 +169,25 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou
}
}
@ExperimentalUnsignedTypes
data class EncryptedDataAndInitialCounter(val encryptedData : Array<UByte>, val initialCounter : Array<UByte>) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as EncryptedDataAndInitializationVector
if (!encryptedData.contentEquals(other.encryptedData)) return false
if (!initialCounter.contentEquals(other.initilizationVector)) return false
return true
}
override fun hashCode(): Int {
var result = encryptedData.contentHashCode()
result = 31 * result + initialCounter.contentHashCode()
return result
}
}

View File

@ -31,43 +31,67 @@ 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("Encrypted: ${encrypted.toHexString()}")
assertTrue {
expectedCipherText == encrypted.toHexString()
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("Encrypted: ${encrypted.encryptedData.toHexString()}")
expectedCipherText == encrypted.encryptedData.toHexString() &&
iv == encrypted.initilizationVector.toHexString()
}
assertTrue {
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val encryptedDataAndInitializationVector = AesCbc.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCbc.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initilizationVector
)
plainText == decrypted.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("Decrypted: ${decrypted.toHexString()}")
assertTrue {
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("Decrypted: ${decrypted.toHexString()}")
expectedPlainText == decrypted.toHexString()
}
assertTrue {
val key = "4278b840fb44aaa757c1bf04acbe1a3e"
val iv = "57f02a5c5339daeb0a2908a06ac6393f"
val cipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7"
val expectedPlainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val decrypted = AesCbc.decrypt(AesKey.Aes128Key(key), cipherText.hexStringToUByteArray(), iv.hexStringToUByteArray())
println("Decrypted: ${decrypted.toHexString()}")
expectedPlainText == decrypted.toHexString()
}
}
}

View File

@ -31,45 +31,67 @@ 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()
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
val plaintext =
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val expectedCipherText =
"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
val aesCtr = AesCtr(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initialCounter = ic.hexStringToUByteArray())
aesCtr.addData(
plaintext.hexStringToUByteArray()
)
val encrypted = aesCtr.encrypt()
println("Encrypted: ${encrypted.encryptedData.toHexString()}")
expectedCipherText == encrypted.encryptedData.toHexString() &&
ic == encrypted.initialCounter.toHexString()
}
assertTrue {
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCtr.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initialCounter
)
plainText == decrypted.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 {
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
val cipherText =
"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
val expectedPlainText =
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val aesCtr = AesCtr(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initialCounter = ic.hexStringToUByteArray())
aesCtr.addData(cipherText.hexStringToUByteArray())
val decrypted = aesCtr.decrypt()
println("Decrypted: ${decrypted.toHexString()}")
expectedPlainText == decrypted.toHexString()
}
assertTrue {
val key = "2b7e151628aed2a6abf7158809cf4f3c"
val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
val cipherText =
"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
val expectedPlainText =
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val decrypted = AesCtr.decrypt(AesKey.Aes128Key(key), cipherText.hexStringToUByteArray(), ic.hexStringToUByteArray())
println("Decrypted: ${decrypted.toHexString()}")
expectedPlainText == decrypted.toHexString()
}
}
}