Skip to content

Commit

Permalink
Add SHA3 hashing (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
05nelsonm authored Apr 7, 2023
1 parent e16e7fa commit ab02d6e
Show file tree
Hide file tree
Showing 76 changed files with 2,986 additions and 124 deletions.
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ fun main() {
MD5().apply { update(random) }.digest()

SHA1().digest(random)
}
```

`SHA-2`

```kotlin
fun main() {
val random = Random.Default.nextBytes(615)

SHA224().digest(random)

Expand All @@ -64,13 +72,84 @@ fun main() {
}
```

<!-- TODO: Uncomment
`SHA-3` `Digest`s
```kotlin
fun main() {
Keccak224()
Keccak256()
Keccak384()
Keccak512()
SHA3_224()
SHA3_256()
SHA3_384()
SHA3_512()
SHAKE128()
SHAKE256()
val S = "My Customization".encodeToByteArray()
CSHAKE128(null, S)
CSHAKE256(null, S)
}
```
`SHA-3` `XOF`s (i.e. [Extendable-Output Functions][url-fips-202])
`XOF`s are very similar to `Digest` and `Mac`, except instead of calling `digest()`
or `doFinal()` which returns a fixed sized `ByteArray`, their output can be however
long you wish.
As such, `KotlinCrypto` takes the approach of making them distinctly separate from
those while implementing the same interfaces (`Algorithm`, `Copyable`, `Resettable`,
and `Updatable`). The only thing that is different is how the output is retrieved.
`Xof`s are read, instead.
More detail can be found in the documentation [HERE][url-xof].
```kotlin
fun main() {
SHAKE128.xOf()
SHAKE256.xOf()
val S = "My Customization".encodeToByteArray()
CSHAKE128.xOf(null, S)
val xof = CSHAKE256.xOf(null, S)
xof.update(Random.Default.nextBytes(615))
xof.reset()
xof.update(Random.Default.nextBytes(615))
val out1 = ByteArray(1_000)
xof.use(resetXof = false) { read(out1) }
val out2 = ByteArray(out1.size / 2)
val out3 = ByteArray(out1.size / 2)
val reader = xof.reader(resetXof = true)
reader.use { read(out2, 0, out2.size); read(out3) }
assertContentEquals(out1, out2 + out3)
}
```
-->

### Get Started

The best way to keep `KotlinCrypto` dependencies up to date is by using the
[version-catalog][url-version-catalog]. Alternatively, you can use the BOM as
shown below.

<!-- TAG_VERSION -->
<!-- TODO: Add
// Keccak-224, Keccak-256, Keccak-384, Keccak-512
// SHA3-224, SHA3-256, SHA3-384, SHA3-512
// SHAKE128, SHAKE256
// CSHAKE128, CSHAKE256
implementation("org.kotlincrypto.hash:sha3")
-->

```kotlin
// build.gradle.kts
Expand Down Expand Up @@ -125,3 +204,5 @@ dependencies {
[url-macs]: https://github.com/KotlinCrypto/MACs
[url-version-catalog]: https://github.com/KotlinCrypto/version-catalog
[url-secure-random]: https://github.com/KotlinCrypto/secure-random
[url-xof]: https://github.com/KotlinCrypto/core/blob/master/library/xof/src/commonMain/kotlin/org/kotlincrypto/core/Xof.kt
[url-fips-202]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
9 changes: 8 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
[versions]
binaryCompat = "0.13.0"
bouncyCastle = "1.72"
configuration = "0.1.0-beta02"
cryptoCore = "0.2.0"
cryptoCore = "0.2.2"
encoding = "1.2.1"
endians = "0.1.0"
gradleVersions = "0.46.0"
kotlin = "1.8.10"
publish = "0.24.0"
sponges = "0.1.0"

[libraries]
kotlincrypto-core-digest = { module = "org.kotlincrypto.core:digest", version.ref = "cryptoCore" }
kotlincrypto-core-xof = { module = "org.kotlincrypto.core:xof", version.ref = "cryptoCore" }
kotlincrypto-endians-endians = { module = "org.kotlincrypto.endians:endians", version.ref = "endians"}
kotlincrypto-sponges-keccak = { module = "org.kotlincrypto.sponges:keccak", version.ref = "sponges" }
gradle-kmp-configuration = { module = "io.matthewnelson:gradle-kmp-configuration-plugin", version.ref = "configuration" }
gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
gradle-maven-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "publish" }

# Tests
bouncyCastle = { module = "org.bouncycastle:bcprov-ext-jdk15to18", version.ref = "bouncyCastle" }
encoding-base16 = { module = "io.matthewnelson.kotlin-components:encoding-base16", version.ref = "encoding" }
encoding-base64 = { module = "io.matthewnelson.kotlin-components:encoding-base64", version.ref = "encoding" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ public class MD5: Digest {
for (i in 0 until blockSize()) {
when {
i < 16 -> {
var bI = (i * 4) + offset
var j = (i * 4) + offset
x[i] =
((input[bI++].toInt() and 0xff) ) or
((input[bI++].toInt() and 0xff) shl 8) or
((input[bI++].toInt() and 0xff) shl 16) or
((input[bI ].toInt() and 0xff) shl 24)
((input[j++].toInt() and 0xff) ) or
((input[j++].toInt() and 0xff) shl 8) or
((input[j++].toInt() and 0xff) shl 16) or
((input[j ].toInt() and 0xff) shl 24)

val g = i + 0
val f = ((b and c) or (b.inv() and d)) + a + k[i] + x[g]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ public class SHA1: Digest {
protected override fun compress(input: ByteArray, offset: Int) {
val x = x

var bI = offset
var j = offset
for (i in 0 until 16) {
x[i] =
((input[bI++].toInt() and 0xff) shl 24) or
((input[bI++].toInt() and 0xff) shl 16) or
((input[bI++].toInt() and 0xff) shl 8) or
((input[bI++].toInt() and 0xff) )
((input[j++].toInt() and 0xff) shl 24) or
((input[j++].toInt() and 0xff) shl 16) or
((input[j++].toInt() and 0xff) shl 8) or
((input[j++].toInt() and 0xff) )
}

for (i in 16 until 80) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import org.kotlincrypto.core.InternalKotlinCryptoApi
import org.kotlincrypto.core.internal.DigestState

/**
* Core abstraction for SHA-224 and SHA-256 Digest implementations.
* Core abstraction for:
* - SHA-224
* - SHA-256
* */
public sealed class Bit32Digest: Digest {

Expand All @@ -39,12 +41,6 @@ public sealed class Bit32Digest: Digest {
private val x: IntArray
private val state: IntArray

/**
* Primary constructor for creating a new [Bit32Digest] instance
*
* @throws [IllegalArgumentException] when:
* - [digestLength] is less than or equal to 0
* */
@OptIn(InternalKotlinCryptoApi::class)
@Throws(IllegalArgumentException::class)
protected constructor(
Expand All @@ -70,12 +66,6 @@ public sealed class Bit32Digest: Digest {
this.state = intArrayOf(h0, h1, h2, h3, h4, h5, h6, h7)
}

/**
* Secondary constructor for implementing [copy].
*
* Implementors of [Bit32Digest] should have a private secondary constructor
* that is utilized by its [copy] implementation.
* */
@OptIn(InternalKotlinCryptoApi::class)
protected constructor(state: DigestState, digest: Bit32Digest): super(state) {
this.h0 = digest.h0
Expand All @@ -93,13 +83,13 @@ public sealed class Bit32Digest: Digest {
protected final override fun compress(input: ByteArray, offset: Int) {
val x = x

var bI = offset
var j = offset
for (i in 0 until 16) {
x[i] =
((input[bI++].toInt() and 0xff) shl 24) or
((input[bI++].toInt() and 0xff) shl 16) or
((input[bI++].toInt() and 0xff) shl 8) or
((input[bI++].toInt() and 0xff) )
((input[j++].toInt() and 0xff) shl 24) or
((input[j++].toInt() and 0xff) shl 16) or
((input[j++].toInt() and 0xff) shl 8) or
((input[j++].toInt() and 0xff) )
}

for (i in 16 until 64) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import org.kotlincrypto.core.internal.DigestState
import kotlin.jvm.JvmField

/**
* Core abstraction for SHA-384, SHA-512, and SHA-512/t
* Digest implementations.
* Core abstraction for:
* - SHA-384
* - SHA-512
* - SHA-512/t
* */
public sealed class Bit64Digest: Digest {

Expand All @@ -41,16 +43,6 @@ public sealed class Bit64Digest: Digest {
private val x: LongArray
private val state: LongArray

/**
* Primary constructor for creating a new [Bit64Digest] instance
*
* @throws [IllegalArgumentException] when:
* - [digestLength] is less than or equal to 0
* - [t] is greater than or equal to 512
* - [t] is expressed when [d] != 512
* - [t] is not a factor of 8
* - [t] is 384
* */
@OptIn(InternalKotlinCryptoApi::class)
@Throws(IllegalArgumentException::class)
protected constructor(
Expand All @@ -64,11 +56,7 @@ public sealed class Bit64Digest: Digest {
h5: Long,
h6: Long,
h7: Long,
): super(
algorithm = "SHA-$d" + (t?.let { "/$it" } ?: ""),
blockSize = 128,
digestLength = (t ?: d) / 8,
) {
): super("SHA-$d" + (t?.let { "/$it" } ?: ""), 128, (t ?: d) / 8) {
if (t != null) {
require(d == 512) { "t can only be expressed for SHA-512" }
require(t % 8 == 0) { "t must be a factor of 8" }
Expand All @@ -88,12 +76,6 @@ public sealed class Bit64Digest: Digest {
this.state = longArrayOf(h0, h1, h2, h3, h4, h5, h6, h7)
}

/**
* Secondary constructor for implementing [copy].
*
* Implementors of [Bit64Digest] should have a private secondary constructor
* that is utilized by its [copy] implementation.
* */
@OptIn(InternalKotlinCryptoApi::class)
protected constructor(state: DigestState, digest: Bit64Digest): super(state) {
this.h0 = digest.h0
Expand All @@ -111,17 +93,17 @@ public sealed class Bit64Digest: Digest {
protected final override fun compress(input: ByteArray, offset: Int) {
val x = x

var bI = offset
var j = offset
for (i in 0 until 16) {
x[i] =
((input[bI++].toLong() and 0xff) shl 56) or
((input[bI++].toLong() and 0xff) shl 48) or
((input[bI++].toLong() and 0xff) shl 40) or
((input[bI++].toLong() and 0xff) shl 32) or
((input[bI++].toLong() and 0xff) shl 24) or
((input[bI++].toLong() and 0xff) shl 16) or
((input[bI++].toLong() and 0xff) shl 8) or
((input[bI++].toLong() and 0xff) )
((input[j++].toLong() and 0xff) shl 56) or
((input[j++].toLong() and 0xff) shl 48) or
((input[j++].toLong() and 0xff) shl 40) or
((input[j++].toLong() and 0xff) shl 32) or
((input[j++].toLong() and 0xff) shl 24) or
((input[j++].toLong() and 0xff) shl 16) or
((input[j++].toLong() and 0xff) shl 8) or
((input[j++].toLong() and 0xff) )
}

for (i in 16 until 80) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class SHA224: Bit32Digest {
h7 = -1090891868,
)

private constructor(state: DigestState, sha224: SHA224): super(state, sha224)
private constructor(state: DigestState, digest: SHA224): super(state, digest)

protected override fun copy(state: DigestState): Digest = SHA224(state, this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class SHA256: Bit32Digest {
h7 = 1541459225,
)

private constructor(state: DigestState, sha256: SHA256): super(state, sha256)
private constructor(state: DigestState, digest: SHA256): super(state, digest)

protected override fun copy(state: DigestState): Digest = SHA256(state, this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class SHA384: Bit64Digest {
h7 = 5167115440072839076L,
)

private constructor(state: DigestState, sha384: SHA384): super(state, sha384)
private constructor(state: DigestState, digest: SHA384): super(state, digest)

protected override fun copy(state: DigestState): Digest = SHA384(state, this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class SHA512: Bit64Digest {
h7 = 6620516959819538809L,
)

private constructor(state: DigestState, sha512: SHA512): super(state, sha512)
private constructor(state: DigestState, digest: SHA512): super(state, digest)

protected override fun copy(state: DigestState): Digest = SHA512(state, this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public class SHA512t: Bit64Digest {
digest()
}

private constructor(state: DigestState, sha512t: SHA512t): super(state, sha512t) {
private constructor(state: DigestState, digest: SHA512t): super(state, digest) {
isInitialized = true
}

Expand Down
1 change: 1 addition & 0 deletions library/sha3/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
Loading

0 comments on commit ab02d6e

Please sign in to comment.