-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Created aes module for implementing the AES specification required by…
… most JWE implementations
- Loading branch information
Showing
5 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
|
||
plugins { | ||
kotlin("plugin.serialization") | ||
id("com.android.library") | ||
id("org.jetbrains.dokka") | ||
id("kjwt.multiplatform") | ||
id("kjwt.publish") | ||
} | ||
|
||
kotlin { | ||
sourceSets { | ||
all { | ||
// Disable warnings and errors related to these expected @OptIn annotations. | ||
// See: https://kotlinlang.org/docs/opt-in-requirements.html#module-wide-opt-in | ||
languageSettings.optIn("kotlin.RequiresOptIn") | ||
languageSettings.optIn("-Xexpect-actual-classes") | ||
} | ||
|
||
val commonMain by getting { | ||
dependencies { | ||
// Coroutines | ||
// https://github.com/Kotlin/kotlinx.coroutines | ||
implementation(KotlinX.coroutines.core) | ||
|
||
// Serialization | ||
// https://github.com/Kotlin/kotlinx.serialization | ||
api(KotlinX.serialization.json) | ||
|
||
// Time | ||
// https://github.com/Kotlin/kotlinx-datetime | ||
api(KotlinX.datetime) | ||
|
||
// Hash Functions | ||
// https://github.com/KotlinCrypto/hash | ||
implementation("org.kotlincrypto.hash:md:_") | ||
implementation("org.kotlincrypto.hash:sha1:_") | ||
implementation("org.kotlincrypto.hash:sha2:_") | ||
|
||
// HMAC Functions | ||
// https://github.com/KotlinCrypto/MACs | ||
implementation("org.kotlincrypto.macs:hmac-sha2:_") | ||
|
||
// Secure Random (CSPRNG) | ||
// https://github.com/KotlinCrypto/secure-random | ||
implementation("org.kotlincrypto:secure-random:_") | ||
|
||
// Big Numbers | ||
// https://github.com/ionspin/kotlin-multiplatform-bignum | ||
implementation("com.ionspin.kotlin:bignum:_") | ||
} | ||
} | ||
|
||
val commonTest by getting { | ||
dependencies { | ||
implementation(kotlin("test")) | ||
implementation(KotlinX.coroutines.test) | ||
} | ||
} | ||
} | ||
} | ||
|
||
android { | ||
compileSdk = LibraryConstants.Android.compileSdkVersion | ||
namespace = "com.mooncloak.kodetools.aes" | ||
|
||
defaultConfig { | ||
minSdk = LibraryConstants.Android.minSdkVersion | ||
targetSdk = LibraryConstants.Android.targetSdkVersion | ||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | ||
} | ||
|
||
buildTypes { | ||
getByName("release") { | ||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") | ||
} | ||
} | ||
|
||
compileOptions { | ||
sourceCompatibility = JavaVersion.VERSION_1_8 | ||
targetCompatibility = JavaVersion.VERSION_1_8 | ||
} | ||
|
||
tasks.withType<KotlinCompile> { | ||
kotlinOptions { | ||
jvmTarget = "1.8" | ||
// Opt-in to experimental compose APIs | ||
freeCompilerArgs = listOf( | ||
"-Xopt-in=kotlin.RequiresOptIn" | ||
) | ||
} | ||
} | ||
|
||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") | ||
sourceSets["main"].java.srcDirs("src/androidMain/kotlin") | ||
sourceSets["main"].res.srcDirs("src/androidMain/res") | ||
|
||
sourceSets["test"].java.srcDirs("src/androidTest/kotlin") | ||
sourceSets["test"].res.srcDirs("src/androidTest/res") | ||
} |
43 changes: 43 additions & 0 deletions
43
aes/src/commonMain/kotlin/com/mooncloak/kodetools/aes/Cipher.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.mooncloak.kodetools.aes | ||
|
||
/** | ||
* Represents the general encryption function for executing the AES (Advanced Encryption Standard). | ||
* | ||
* @param [input] The data input, which is a block represented as a linear array of 16 bytes. | ||
* | ||
* @param [numberOfRounds] The number of rounds, "Nr", for the instance. This value is dependent on the key bit size. | ||
* The following values are supported: AES-128: `10`, for AES-192: `12`, AES-256: `14`. | ||
* | ||
* @param [roundKeys] The keys for each round. This value must be of size [numberOfRounds]. A round key is a block that | ||
* is usually represented as a sequence of four words (16 bytes). The "KEYEXPANSION()" function, defined by the AES | ||
* Specification, takes the block cipher key as input and generates the round keys as output. | ||
* | ||
* @see [AES Specification](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf) | ||
*/ | ||
internal fun Cipher( | ||
input: ByteArray, | ||
numberOfRounds: Int, | ||
roundKeys: Array<ByteArray> | ||
): ByteArray { | ||
require(input.size == 16) { "A block input MUST be 16 bytes. Instead, ${input.size} bytes were provided." } | ||
require(numberOfRounds in arrayOf(10, 12, 14)) { | ||
"The 'numberOfRounds' value MUST be one of the following values: 10, 12, 14." | ||
} | ||
|
||
val state = State(input) | ||
|
||
state.addRoundKey(roundKeys.copyOfRange(fromIndex = 0, toIndex = 4)) | ||
|
||
for (round in 1 until numberOfRounds) { | ||
state.subBytes() | ||
state.shiftRows() | ||
state.mixColumns() | ||
state.addRoundKey(roundKeys.copyOfRange(fromIndex = 4 * round, toIndex = 4 * round + 4)) | ||
} | ||
|
||
state.subBytes() | ||
state.shiftRows() | ||
state.addRoundKey(roundKeys.copyOfRange(fromIndex = 4 * numberOfRounds, toIndex = 4 * numberOfRounds + 3)) | ||
|
||
return state.output() | ||
} |
29 changes: 29 additions & 0 deletions
29
aes/src/commonMain/kotlin/com/mooncloak/kodetools/aes/GaloisField.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.mooncloak.kodetools.aes | ||
|
||
import kotlin.experimental.xor | ||
|
||
internal infix fun Byte.plusGF256(b: Byte): Byte = this xor b | ||
|
||
internal infix fun Int.multiplyGF256(b: Int): Int { | ||
var p = 0 | ||
var aa = this | ||
var bb = b | ||
var hiBitSet: Boolean | ||
|
||
for (counter in 0..7) { | ||
if (bb and 1 != 0) { | ||
p = p xor aa | ||
} | ||
|
||
hiBitSet = aa and 0x80 != 0 | ||
aa = aa shl 1 | ||
|
||
if (hiBitSet) { | ||
aa = aa xor 0x1b // 0x11b | ||
} | ||
|
||
bb = bb shr 1 | ||
} | ||
|
||
return p | ||
} |
166 changes: 166 additions & 0 deletions
166
aes/src/commonMain/kotlin/com/mooncloak/kodetools/aes/State.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package com.mooncloak.kodetools.aes | ||
|
||
import kotlin.jvm.JvmInline | ||
|
||
/** | ||
* Represents the State object defined by the [AES Specification](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf#page=6). | ||
* | ||
* @see [AES Specification](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf#page=6) | ||
*/ | ||
@JvmInline | ||
internal value class State private constructor( | ||
// We make this value private to control the mutations of the underlying ByteArray within this class via the set function. | ||
private val value: ByteArray = ByteArray(size = 16) | ||
) { | ||
|
||
init { | ||
require(value.size == 16) { | ||
"The State, defined by the AES Specification, MUST have a size of 16. Since it is represented as a two-dimensional 4x4 array of bytes." | ||
} | ||
} | ||
|
||
internal inline operator fun get(rowIndex: Int, columnIndex: Int): Byte { | ||
require(rowIndex in 0 until 4) { | ||
"`rowIndex` property MUST be greater than or equal to 0 and less than 4." | ||
} | ||
require(columnIndex in 0 until 4) { | ||
"`columnIndex` property MUST be greater than or equal to 0 and less than 4." | ||
} | ||
|
||
// See section 3.4 of the specification | ||
return this.value[rowIndex + 4 * columnIndex] | ||
} | ||
|
||
internal inline operator fun set(rowIndex: Int, columnIndex: Int, value: Byte) { | ||
require(rowIndex in 0 until 4) { | ||
"`rowIndex` property MUST be greater than or equal to 0 and less than 4." | ||
} | ||
require(columnIndex in 0 until 4) { | ||
"`columnIndex` property MUST be greater than or equal to 0 and less than 4." | ||
} | ||
|
||
// See section 3.4 of the specification | ||
this.value[rowIndex + 4 * columnIndex] = value | ||
} | ||
|
||
internal inline fun output(): ByteArray { | ||
val output = ByteArray(size = 16) | ||
|
||
for (r in 0 until 4) { | ||
for (c in 0 until 4) { | ||
output[r + 4 * c] = this[r, c] | ||
} | ||
} | ||
|
||
return output | ||
} | ||
|
||
internal companion object { | ||
|
||
internal inline operator fun invoke(input: ByteArray): State { | ||
require(input.size == 16) { | ||
"The State, defined by the AES Specification, MUST have a size of 16. Since it is represented as a two-dimensional 4x4 array of bytes." | ||
} | ||
|
||
val state = State() | ||
|
||
for (r in 0 until 4) { | ||
for (c in 0 until 4) { | ||
state[r, c] = input[r + 4 * c] | ||
} | ||
} | ||
|
||
return state | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Performs the ShiftRows function defined by Section 5.1.2 of the | ||
* [AES Specification](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf#page=14). | ||
*/ | ||
internal inline fun State.shiftRows(): State { | ||
// The first row is kept unchanged. | ||
for (r in 1 until 4) { | ||
for (c in 0 until 4) { | ||
this[r, c] = this[r, (c + r) % 4] | ||
} | ||
} | ||
|
||
return this | ||
} | ||
|
||
internal inline fun State.mixColumns(): State { | ||
val temp = Array(4) { ByteArray(4) } | ||
|
||
for (i in 0..3) { | ||
for (j in 0..3) { | ||
temp[i][j] = ( | ||
0x02.multiplyGF256(this[0, j].toInt()) xor | ||
0x03.multiplyGF256(this[1, j].toInt()) xor | ||
this[2, j].toInt() xor | ||
this[3, j].toInt() | ||
).toByte() | ||
} | ||
} | ||
|
||
for (i in 0..3) { | ||
for (j in 0..3) { | ||
this[i, j] = ( | ||
this[i, j].toInt() xor | ||
0x02.multiplyGF256(temp[i][j].toInt()) xor | ||
0x03.multiplyGF256(temp[(i + 1) % 4][j].toInt()) xor | ||
temp[(i + 2) % 4][j].toInt() xor | ||
temp[(i + 3) % 4][j].toInt() | ||
).toByte() | ||
} | ||
} | ||
|
||
return this | ||
} | ||
|
||
internal inline fun State.addRoundKey(roundKey: Array<ByteArray>): State { | ||
for (r in 0 until 4) { | ||
for (c in 0 until 4) { | ||
this[r, c] = (this[r, c].toInt() xor roundKey[r][c].toInt()).toByte() | ||
} | ||
} | ||
|
||
return this | ||
} | ||
|
||
@OptIn(ExperimentalUnsignedTypes::class) | ||
internal fun State.subBytes(): State { | ||
for (r in 0..3) { | ||
for (c in 0..3) { | ||
val x = this[r, c].toInt() and 0x0F | ||
val y = this[r, c].toInt() shr 4 | ||
|
||
this[r, c] = sbox[y][x].toByte() | ||
} | ||
} | ||
|
||
return this | ||
} | ||
|
||
//@formatter:off | ||
@OptIn(ExperimentalUnsignedTypes::class) | ||
private val sbox = arrayOf( | ||
ubyteArrayOf(0x63u, 0x7cu, 0x77u, 0x7bu, 0xf2u, 0x6bu, 0x6fu, 0xc5u, 0x30u, 0x01u, 0x67u, 0x2bu, 0xfeu, 0xd7u, 0xabu, 0x76u), | ||
ubyteArrayOf(0xcau, 0x82u, 0xc9u, 0x7du, 0xfau, 0x59u, 0x47u, 0xf0u, 0xadu, 0xd4u, 0xa2u, 0xafu, 0x9cu, 0xa4u, 0x72u, 0xc0u), | ||
ubyteArrayOf(0xb7u, 0xfdu, 0x93u, 0x26u, 0x36u, 0x3fu, 0xf7u, 0xccu, 0x34u, 0xa5u, 0xe5u, 0xf1u, 0x71u, 0xd8u, 0x31u, 0x15u), | ||
ubyteArrayOf(0x04u, 0xc7u, 0x23u, 0xc3u, 0x18u, 0x96u, 0x05u, 0x9au, 0x07u, 0x12u, 0x80u, 0xe2u, 0xebu, 0x27u, 0xb2u, 0x75u), | ||
ubyteArrayOf(0x09u, 0x83u, 0x2cu, 0x1au, 0x1bu, 0x6eu, 0x5au, 0xa0u, 0x52u, 0x3bu, 0xd6u, 0xb3u, 0x29u, 0xe3u, 0x2fu, 0x84u), | ||
ubyteArrayOf(0x53u, 0xd1u, 0x00u, 0xedu, 0x20u, 0xfcu, 0xb1u, 0x5bu, 0x6au, 0xcbu, 0xbeu, 0x39u, 0x4au, 0x4cu, 0x58u, 0xcfu), | ||
ubyteArrayOf(0xd0u, 0xefu, 0xaau, 0xfbu, 0x43u, 0x4du, 0x33u, 0x85u, 0x45u, 0xf9u, 0x02u, 0x7fu, 0x50u, 0x3cu, 0x9fu, 0xa8u), | ||
ubyteArrayOf(0x51u, 0xa3u, 0x40u, 0x8fu, 0x92u, 0x9du, 0x38u, 0xf5u, 0xbcu, 0xb6u, 0xdau, 0x21u, 0x10u, 0xffu, 0xf3u, 0xd2u), | ||
ubyteArrayOf(0xcdu, 0x0cu, 0x13u, 0xecu, 0x5fu, 0x97u, 0x44u, 0x17u, 0xc4u, 0xa7u, 0x7eu, 0x3du, 0x64u, 0x5du, 0x19u, 0x73u), | ||
ubyteArrayOf(0x60u, 0x81u, 0x4fu, 0xdcu, 0x22u, 0x2au, 0x90u, 0x88u, 0x46u, 0xeeu, 0xb8u, 0x14u, 0xdeu, 0x5eu, 0x0bu, 0xdbu), | ||
ubyteArrayOf(0xe0u, 0x32u, 0x3au, 0x0au, 0x49u, 0x06u, 0x24u, 0x5cu, 0xc2u, 0xd3u, 0xacu, 0x62u, 0x91u, 0x95u, 0xe4u, 0x79u), | ||
ubyteArrayOf(0xe7u, 0xc8u, 0x37u, 0x6du, 0x8du, 0xd5u, 0x4eu, 0xa9u, 0x6cu, 0x56u, 0xf4u, 0xeau, 0x65u, 0x7au, 0xaeu, 0x08u), | ||
ubyteArrayOf(0xbau, 0x78u, 0x25u, 0x2eu, 0x1cu, 0xa6u, 0xb4u, 0xc6u, 0xe8u, 0xddu, 0x74u, 0x1fu, 0x4bu, 0xbdu, 0x8bu, 0x8au), | ||
ubyteArrayOf(0x70u, 0x3eu, 0xb5u, 0x66u, 0x48u, 0x03u, 0xf6u, 0x0eu, 0x61u, 0x35u, 0x57u, 0xb9u, 0x86u, 0xc1u, 0x1du, 0x9eu), | ||
ubyteArrayOf(0xe1u, 0xf8u, 0x98u, 0x11u, 0x69u, 0xd9u, 0x8eu, 0x94u, 0x9bu, 0x1eu, 0x87u, 0xe9u, 0xceu, 0x55u, 0x28u, 0xdfu), | ||
ubyteArrayOf(0x8cu, 0xa1u, 0x89u, 0x0du, 0xbfu, 0xe6u, 0x42u, 0x68u, 0x41u, 0x99u, 0x2du, 0x0fu, 0xb0u, 0x54u, 0xbbu, 0x16u) | ||
) | ||
//@formatter:on |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,5 +31,6 @@ plugins { | |
|
||
rootProject.name = "kjwt" | ||
|
||
include(":aes") | ||
include(":kjwt-core") | ||
include(":sample") |