Skip to content

Commit

Permalink
Merge branch 'aes-siv-wasmjs'
Browse files Browse the repository at this point in the history
  • Loading branch information
luca992 committed Feb 9, 2024
2 parents 556570e + f6634ae commit a0506ad
Show file tree
Hide file tree
Showing 18 changed files with 2,493 additions and 551 deletions.
25 changes: 5 additions & 20 deletions aes-siv/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import com.vanniktech.maven.publish.SonatypeHost
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

plugins {
alias(libs.plugins.org.jetbrains.kotlin.multiplatform)
Expand All @@ -19,14 +19,12 @@ kotlin {
}
}
js(IR) {
browser {
// useEsModules()
}
browser()
nodejs()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser {
}
browser()
nodejs()
}
val darwinTargets = mutableListOf<KotlinNativeTarget>()
Expand Down Expand Up @@ -81,16 +79,12 @@ kotlin {
dependsOn(commonMain)
dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(npm("assert", "^2.1.0"))
implementation(npm("@solar-republic/cosmos-grpc", "^0.12.0"))
implementation(npm("@solar-republic/neutrino", "^1.0.3"))
}
}
val wasmJsMain by getting {
dependsOn(commonMain)
dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(npm("@solar-republic/neutrino", "^1.0.3"))
}
}
}
Expand Down Expand Up @@ -170,13 +164,4 @@ plugins.withId("com.vanniktech.maven.publish") {
}
}

//rootProject.extensions.configure<NodeJsRootExtension> {
// nodeVersion = "22.0.0-v8-canary202401102ecfc94f85"
// nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary"
//}
//
//
//tasks.withType<org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask>().configureEach {
// args.add("--ignore-engines")
//}
//

Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ expect class AesSIV() {
suspend fun encrypt(
txEncryptionKey: UByteArray,
plaintext: UByteArray,
associatedData: UByteArray
associatedData: List<UByteArray>
): UByteArray

suspend fun decrypt(
txEncryptionKey: UByteArray,
ciphertext: UByteArray,
associatedData: UByteArray
associatedData: List<UByteArray>
): UByteArray
}
69 changes: 69 additions & 0 deletions aes-siv/src/commonTest/kotlin/io/eqoty/Aes128SivTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.eqoty

import io.eqoty.kryptools.aessiv.AesSIV
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertContentEquals

class Aes128SivTests {

private val testVectors = listOf(
TestVector(
name = "Deterministic Authenticated Encryption Example",
key = "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".hexToBytes(),
ad = listOf("101112131415161718191a1b1c1d1e1f2021222324252627".hexToBytes()),
plaintext = "112233445566778899aabbccddee".hexToBytes(),
ciphertext = "85632d07c6e8f37f950acd320a2ecc9340c02b9690c4dc04daef7f6afe5c".hexToBytes()
),
TestVector(
name = "Nonce-Based Authenticated Encryption Example",
key = "7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f".hexToBytes(),
ad = listOf(
"00112233445566778899aabbccddeeffdeaddadadeaddadaffeeddccbbaa99887766554433221100".hexToBytes(),
"102030405060708090a0".hexToBytes(),
"09f911029d74e35bd84156c5635688c0".hexToBytes()
),
plaintext = "7468697320697320736f6d6520706c61696e7465787420746f20656e6372797074207573696e67205349562d414553".hexToBytes(),
ciphertext = "7bdb6e3b432667eb06f4d14bff2fbd0fcb900f2fddbe404326601965c889bf17dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d".hexToBytes()
),
TestVector(
name = "Empty Authenticated Data And Plaintext Example",
key = "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".hexToBytes(),
ad = listOf(),
plaintext = ubyteArrayOf(),
ciphertext = "f2007a5beb2b8900c588a7adf599f172".hexToBytes()
),
TestVector(
name = "Empty Authenticated Data And Block-Size Plaintext Example",
key = "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".hexToBytes(),
ad = listOf(),
plaintext = "00112233445566778899aabbccddeeff".hexToBytes(),
ciphertext = "f304f912863e303d5b540e5057c7010c942ffaf45b0e5ca5fb9a56a5263bb065".hexToBytes()
)
)

@Test
fun runTestVectors() = runTest {
val aesSIV = AesSIV()
testVectors.forEachIndexed { index, vector ->
println("Testing Vector #${index + 1}: ${vector.name}")
val decrypted = aesSIV.decrypt(vector.key, vector.ciphertext, vector.ad)
assertContentEquals(vector.plaintext, decrypted, "Decryption failed for vector: ${vector.name}")
println("decrypted: ${decrypted.joinToString("") { it.toString(16).padStart(2, '0') }}")
val encrypted = aesSIV.encrypt(vector.key, vector.plaintext, vector.ad)
println("encrypted: ${encrypted.joinToString("") { it.toString(16).padStart(2, '0') }}")
assertContentEquals(vector.ciphertext, encrypted, "Encryption failed for vector: ${vector.name}")
}
}

@OptIn(ExperimentalStdlibApi::class)
private fun String.hexToBytes() = this.hexToUByteArray()
}

data class TestVector(
val name: String,
val key: UByteArray,
val ad: List<UByteArray>,
val plaintext: UByteArray,
val ciphertext: UByteArray
)
30 changes: 0 additions & 30 deletions aes-siv/src/commonTest/kotlin/io/eqoty/Tests.kt

This file was deleted.

203 changes: 203 additions & 0 deletions aes-siv/src/jsMain/kotlin/io/eqoty/kryptools/aessiv/Aes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package io.eqoty.kryptools.aessiv

import jslibs.tsstdlib.AesCbcParams
import jslibs.tsstdlib.AesCtrParams
import jslibs.tsstdlib.Crypto
import jslibs.tsstdlib.CryptoKey
import kotlinx.coroutines.await
import org.khronos.webgl.ArrayBuffer
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
import org.khronos.webgl.set
import kotlin.experimental.xor
import kotlin.math.ceil

const val NB_AES_BLOCK = 16

val ATU8_ZERO_BLOCK get() = Uint8Array(NB_AES_BLOCK)

// https://github.com/whyoleg/cryptography-kotlin/blob/d524143a0719e6926b0ae190977a7341673fa718/cryptography-random/src/wasmJsMain/kotlin/CryptographyRandom.wasmJs.kt#L40-L54
//language=JavaScript
private fun getCrypto(): Crypto {
return js(
code = """
var isNodeJs = typeof process !== 'undefined' && process.versions != null && process.versions.node != null
if (isNodeJs) {
return (eval('require')('node:crypto').webcrypto);
} else {
return (window ? (window.crypto ? window.crypto : window.msCrypto) : self.crypto);
}
"""
).unsafeCast<Crypto>()
}

// language=JavaScript
fun createAesCbcParams(iv: Uint8Array): dynamic =
js("({ name: 'AES-CBC', iv: iv})")

// language=JavaScript
fun createAesCtrParams(atu8_iv: Uint8Array, length: Int): dynamic =
js("({ name: 'AES-CTR', counter: atu8_iv, length: length})")


suspend fun aes_key(atu8_key: Uint8Array, si_algo: String): CryptoKey {
val crypto = getCrypto()
val keyUsages = arrayOf("encrypt")
return crypto.subtle.importKey("raw", atu8_key, si_algo, false, keyUsages).await()
}

// perform AES-CBC
suspend fun aesCbc(d_key_cbc: CryptoKey, atu8_data: Uint8Array): Uint8Array {
val crypto = getCrypto()
val d_cipher = Uint8Array(
crypto.subtle.encrypt(
createAesCbcParams(ATU8_ZERO_BLOCK).unsafeCast<AesCbcParams>(), d_key_cbc, atu8_data
).await(), 0, NB_AES_BLOCK
)
return d_cipher
}

// perform AES-CTR
suspend fun aesCtr(d_key_ctr: CryptoKey, atu8_iv: Uint8Array, atu8_data: Uint8Array): Uint8Array {
val crypto = getCrypto()
val d_cipher: ArrayBuffer = crypto.subtle.encrypt(
createAesCtrParams(atu8_iv, NB_AES_BLOCK).unsafeCast<AesCtrParams>(), d_key_ctr, atu8_data
).await()
return Uint8Array(d_cipher)
}


// pseudo-constant-time select
fun select(xbValue: Int, xbRif1: Int, xbRif0: Int): Int = ((xbValue - 1).inv() and xbRif1) or ((xbValue - 1) and xbRif0)

// double block value in-place
fun doubleBlock(atu8Block: Uint8Array) {
var xbCarry = 0

for (ibEach in NB_AES_BLOCK - 1 downTo 0) {
val xbTmp = ((atu8Block[ibEach].toInt() and 0xff) ushr 7) and 0xff
atu8Block[ibEach] = (((atu8Block[ibEach].toInt() and 0xff) shl 1) or xbCarry).toByte()
xbCarry = xbTmp
}

atu8Block[NB_AES_BLOCK - 1] = ((atu8Block[NB_AES_BLOCK - 1].toInt() and 0xff) xor select(xbCarry, 0x87, 0)).toByte()
@Suppress("UNUSED_VALUE")
xbCarry = 0
}

fun xorBuffers(atu8A: Uint8Array, atu8B: Uint8Array) {
for (ibEach in 0 until atu8B.length) {
atu8A[ibEach] = (atu8A[ibEach].toInt() xor atu8B[ibEach].toInt()).toByte()
}
}

// Assuming aesCbc, doubleBlock, xorBuffers are defined similarly as suspend functions or adapted Kotlin/JS functions
suspend fun aesCmacInit(dKeyMac: CryptoKey): suspend (atu8_data: Uint8Array) -> Uint8Array {
// k1 subkey generation
val atu8K1 = aesCbc(dKeyMac, ATU8_ZERO_BLOCK)
doubleBlock(atu8K1)

// k2 subkey generation
val atu8K2 = Uint8Array(atu8K1.buffer.slice(0, atu8K1.length))
doubleBlock(atu8K2)

// Return a suspend function for CMAC computation
return { atu8Data ->
// cache data byte count
val nbData = atu8Data.byteLength

// number of blocks needed
val nlBlocks = ceil(nbData.toDouble() / NB_AES_BLOCK.toDouble()).toInt()

val atu8DataStart = if (nlBlocks == 0) 1 else nlBlocks

// last block
val atu8Last = Uint8Array(NB_AES_BLOCK)
atu8Last.set(atu8Data.subarray((atu8DataStart - 1) * NB_AES_BLOCK, atu8Data.length))

// cache size of last block
val nbLast = nbData % NB_AES_BLOCK

// last block requires padding
if (nbLast > 0 || nlBlocks == 0) {
// M_last := {ANS} XOR K2
xorBuffers(atu8Last, atu8K2)

// padding(M_n)
atu8Last[nbLast] = atu8Last[nbLast] xor 0x80.toByte()
}
// no padding needed; xor with k1
else {
// M_last := M_n XOR K1
xorBuffers(atu8Last, atu8K1)
}

var atu8Block = ATU8_ZERO_BLOCK

// for i := 1 to n-1
for (iBlock in 0 until nlBlocks - 1) {
// Y := X XOR M_i
xorBuffers(atu8Block, atu8Data.subarray(iBlock * NB_AES_BLOCK, atu8Data.length))

// X := AES-128(K,Y)
atu8Block = aesCbc(dKeyMac, atu8Block)
}

// Y := M_last XOR X
xorBuffers(atu8Block, atu8Last)

// T := AES-128(K,Y)
aesCbc(dKeyMac, atu8Block)
}
}

// performs S2V operation
suspend fun s2v(
d_key_rkd: CryptoKey, atu8_plaintext: Uint8Array, a_ad: Array<Uint8Array> = arrayOf(Uint8Array(0))
): Uint8Array {
val f_cmac = aesCmacInit(d_key_rkd)

// D = AES-CMAC(K, <zero>)
var atu8_cmac = f_cmac(ATU8_ZERO_BLOCK)

// for i = 1 to n-1
a_ad.forEach { atu8_ad ->
// dbl(D)
doubleBlock(atu8_cmac)

// D = {ANS} xor AES-CMAC(K, Si)
xorBuffers(atu8_cmac, f_cmac(atu8_ad))
}

// cache plaintext byte count
val nb_plaintext = atu8_plaintext.byteLength

// last block of plaintext
val atu8_sn = Uint8Array(NB_AES_BLOCK)

// if len(Sn) >= 128
if (nb_plaintext >= NB_AES_BLOCK) {
// Sn_end xor D
atu8_sn.set(atu8_plaintext.subarray(nb_plaintext - NB_AES_BLOCK, atu8_plaintext.length))
xorBuffers(atu8_sn, atu8_cmac)

// T = Sn xorend D
atu8_cmac = Uint8Array(atu8_plaintext.buffer.slice(0, atu8_plaintext.length))
atu8_cmac.set(atu8_sn, nb_plaintext - NB_AES_BLOCK)
} else {
// dbl(D)
doubleBlock(atu8_cmac)

// pad(Sn)
atu8_sn.set(atu8_plaintext)
atu8_sn[nb_plaintext] = 0x80.toByte()

// T = dbl(D) xor pad(Sn)
xorBuffers(atu8_cmac, atu8_sn)
}

// V = AES-CMAC(K, T)
return f_cmac(atu8_cmac)
}
Loading

0 comments on commit a0506ad

Please sign in to comment.