Skip to content

Commit ed787b8

Browse files
authored
Add constructors for configuring non-default output byte length (#28)
1 parent 5be377f commit ed787b8

File tree

8 files changed

+116
-42
lines changed

8 files changed

+116
-42
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ fun main() {
4141
// should no longer be utilized because they have been broken.
4242
HmacMD5(key)
4343
HmacSHA1(key)
44+
45+
key.fill(0)
4446
}
4547
```
4648

@@ -58,6 +60,8 @@ fun main() {
5860
HmacSHA512_224(key)
5961
HmacSHA512_256(key)
6062
HmacSHA512t(key, 504)
63+
64+
key.fill(0)
6165
}
6266
```
6367

@@ -75,6 +79,8 @@ fun main() {
7579
HmacSHA3_256(key)
7680
HmacSHA3_384(key)
7781
HmacSHA3_512(key)
82+
83+
key.fill(0)
7884
}
7985
```
8086

@@ -90,8 +96,11 @@ fun main() {
9096
KMAC128(key) // as a Mac
9197
KMAC128.xOf(key) // as a Xof (Extendable-Output Function)
9298
93-
KMAC256(key)
99+
val S = "My Customization".encodeToByteArray()
100+
KMAC256(key, S, outputLength = 123)
94101
KMAC256.xOf(key)
102+
103+
key.fill(0)
95104
}
96105
```
97106
-->

build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ allprojects {
3232

3333
repositories {
3434
mavenCentral()
35+
36+
if (version.toString().endsWith("-SNAPSHOT")) {
37+
// Only allow snapshot dependencies for non-release versions.
38+
// This would cause a build failure if attempting to make a release
39+
// while depending on a -SNAPSHOT version (such as core or hash).
40+
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
41+
}
3542
}
3643

3744
}

gradle/libs.versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
binaryCompat = "0.13.0"
33
bouncyCastle = "1.72"
44
configuration = "0.1.0-beta02"
5-
cryptoCore = "0.2.3"
6-
cryptoHash = "0.2.3"
5+
cryptoCore = "0.2.4-SNAPSHOT"
6+
cryptoHash = "0.2.4-SNAPSHOT"
77
encoding = "1.2.1"
88
gradleVersions = "0.46.0"
99
kotlin = "1.8.10"

library/hmac/hmac/src/commonMain/kotlin/org/kotlincrypto/macs/Hmac.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ public abstract class Hmac: Mac {
9191
}
9292

9393
override fun update(input: Byte) { digest.update(input) }
94-
override fun update(input: ByteArray) { digest.update(input) }
9594
override fun update(input: ByteArray, offset: Int, len: Int) { digest.update(input, offset, len) }
9695

9796
override fun doFinal(): ByteArray {

library/kmac/api/kmac.api

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
public final class org/kotlincrypto/macs/KMAC128 : org/kotlincrypto/macs/Kmac {
22
public static final field Companion Lorg/kotlincrypto/macs/KMAC128$Companion;
3+
public static final field MAC_LENGTH I
34
public fun <init> ([B)V
45
public fun <init> ([B[B)V
6+
public fun <init> ([B[BI)V
57
public static final fun xOf ([B)Lorg/kotlincrypto/core/Xof;
68
public static final fun xOf ([B[B)Lorg/kotlincrypto/core/Xof;
79
}
@@ -14,8 +16,10 @@ public final class org/kotlincrypto/macs/KMAC128$Companion : org/kotlincrypto/ma
1416

1517
public final class org/kotlincrypto/macs/KMAC256 : org/kotlincrypto/macs/Kmac {
1618
public static final field Companion Lorg/kotlincrypto/macs/KMAC256$Companion;
19+
public static final field MAC_LENGTH I
1720
public fun <init> ([B)V
1821
public fun <init> ([B[B)V
22+
public fun <init> ([B[BI)V
1923
public static final fun xOf ([B)Lorg/kotlincrypto/core/Xof;
2024
public static final fun xOf ([B[B)Lorg/kotlincrypto/core/Xof;
2125
}
@@ -29,7 +33,7 @@ public final class org/kotlincrypto/macs/KMAC256$Companion : org/kotlincrypto/ma
2933
public abstract class org/kotlincrypto/macs/Kmac : org/kotlincrypto/core/Mac, org/kotlincrypto/core/XofAlgorithm {
3034
protected static final field Companion Lorg/kotlincrypto/macs/Kmac$Companion;
3135
public synthetic fun <init> (Lorg/kotlincrypto/core/Mac$Engine;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
32-
public synthetic fun <init> ([B[BILkotlin/jvm/internal/DefaultConstructorMarker;)V
36+
public synthetic fun <init> ([B[BIILkotlin/jvm/internal/DefaultConstructorMarker;)V
3337
}
3438

3539
protected final class org/kotlincrypto/macs/Kmac$Companion {

library/kmac/src/commonMain/kotlin/org/kotlincrypto/macs/KMAC128.kt

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package org.kotlincrypto.macs
1717

1818
import org.kotlincrypto.core.Mac
1919
import org.kotlincrypto.core.Xof
20+
import org.kotlincrypto.hash.sha3.CSHAKE128
2021
import kotlin.jvm.JvmOverloads
2122
import kotlin.jvm.JvmStatic
2223

@@ -28,30 +29,59 @@ import kotlin.jvm.JvmStatic
2829
public class KMAC128: Kmac {
2930

3031
/**
31-
* Creates a new [KMAC128] [Mac] instance with a fixed output [macLength].
32+
* Creates a new [KMAC128] [Mac] instance with a default output length
33+
* of 32 bytes.
3234
*
3335
* @throws [IllegalArgumentException] if [key] is empty.
3436
* */
3537
@Throws(IllegalArgumentException::class)
36-
public constructor(key: ByteArray): this(key, null)
38+
public constructor(
39+
key: ByteArray,
40+
): this(key, null)
3741

3842
/**
39-
* Creates a new [KMAC128] [Mac] instance with a fixed output [macLength].
43+
* Creates a new [KMAC128] [Mac] instance with a default output length
44+
* of 32 bytes.
4045
*
4146
* @param [S] A user selected customization bit string to define a variant
4247
* of the function. When no customization is desired, [S] is set to an
4348
* empty or null value. (e.g. "My Customization".encodeToByteArray()).
4449
* @throws [IllegalArgumentException] if [key] is empty.
4550
* */
4651
@Throws(IllegalArgumentException::class)
47-
public constructor(key: ByteArray, S: ByteArray?): super(key, S, BIT_STRENGTH_128)
52+
public constructor(
53+
key: ByteArray,
54+
S: ByteArray?,
55+
): this(key, S, MAC_LENGTH)
56+
57+
/**
58+
* Creates a new [KMAC128] [Mac] instance with a non-default output length.
59+
*
60+
* @param [S] A user selected customization bit string to define a variant
61+
* of the function. When no customization is desired, [S] is set to an
62+
* empty or null value. (e.g. "My Customization".encodeToByteArray()).
63+
* @param [outputLength] The number of bytes returned when [doFinal] is invoked
64+
* @throws [IllegalArgumentException] if [key] is empty, or [outputLength]
65+
* is negative.
66+
* */
67+
@Throws(IllegalArgumentException::class)
68+
public constructor(
69+
key: ByteArray,
70+
S: ByteArray?,
71+
outputLength: Int,
72+
): super(key, S, BIT_STRENGTH_128, outputLength)
4873

4974
private constructor(engine: Mac.Engine): super(engine)
5075

5176
protected override fun copy(engineCopy: Mac.Engine): Mac = KMAC128(engineCopy)
5277

5378
public companion object: KMACXofFactory<KMAC128>() {
5479

80+
/**
81+
* The default number of bytes output when [doFinal] is invoked (32)
82+
* */
83+
public const val MAC_LENGTH: Int = CSHAKE128.DIGEST_LENGTH
84+
5585
/**
5686
* Produces a new [Xof] (Extendable-Output Function) for [KMAC128]
5787
*
@@ -62,6 +92,6 @@ public class KMAC128: Kmac {
6292
* */
6393
@JvmStatic
6494
@JvmOverloads
65-
public fun xOf(key: ByteArray, S: ByteArray? = null): Xof<KMAC128> = KMACXof(KMAC128(key, S))
95+
public fun xOf(key: ByteArray, S: ByteArray? = null): Xof<KMAC128> = KMACXof(KMAC128(key, S, 0))
6696
}
6797
}

library/kmac/src/commonMain/kotlin/org/kotlincrypto/macs/KMAC256.kt

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package org.kotlincrypto.macs
1717

1818
import org.kotlincrypto.core.Mac
1919
import org.kotlincrypto.core.Xof
20+
import org.kotlincrypto.hash.sha3.CSHAKE256
2021
import kotlin.jvm.JvmOverloads
2122
import kotlin.jvm.JvmStatic
2223

@@ -28,30 +29,59 @@ import kotlin.jvm.JvmStatic
2829
public class KMAC256: Kmac {
2930

3031
/**
31-
* Creates a new [KMAC256] [Mac] instance with a fixed output [macLength].
32+
* Creates a new [KMAC256] [Mac] instance with a default output length
33+
* of 64 bytes.
3234
*
3335
* @throws [IllegalArgumentException] if [key] is empty.
3436
* */
3537
@Throws(IllegalArgumentException::class)
36-
public constructor(key: ByteArray): this(key, null)
38+
public constructor(
39+
key: ByteArray,
40+
): this(key, null)
3741

3842
/**
39-
* Creates a new [KMAC256] [Mac] instance with a fixed output [macLength].
43+
* Creates a new [KMAC256] [Mac] instance with a default output length
44+
* of 64 bytes.
4045
*
4146
* @param [S] A user selected customization bit string to define a variant
4247
* of the function. When no customization is desired, [S] is set to an
4348
* empty or null value. (e.g. "My Customization".encodeToByteArray()).
4449
* @throws [IllegalArgumentException] if [key] is empty.
4550
* */
4651
@Throws(IllegalArgumentException::class)
47-
public constructor(key: ByteArray, S: ByteArray?): super(key, S, BIT_STRENGTH_256)
52+
public constructor(
53+
key: ByteArray,
54+
S: ByteArray?,
55+
): this(key, S, MAC_LENGTH)
56+
57+
/**
58+
* Creates a new [KMAC256] [Mac] instance with a non-default output length.
59+
*
60+
* @param [S] A user selected customization bit string to define a variant
61+
* of the function. When no customization is desired, [S] is set to an
62+
* empty or null value. (e.g. "My Customization".encodeToByteArray()).
63+
* @param [outputLength] The number of bytes returned when [doFinal] is invoked
64+
* @throws [IllegalArgumentException] if [key] is empty, or [outputLength]
65+
* is negative.
66+
* */
67+
@Throws(IllegalArgumentException::class)
68+
public constructor(
69+
key: ByteArray,
70+
S: ByteArray?,
71+
outputLength: Int,
72+
): super(key, S, BIT_STRENGTH_256, outputLength)
4873

4974
private constructor(engine: Mac.Engine): super(engine)
5075

5176
protected override fun copy(engineCopy: Mac.Engine): Mac = KMAC256(engineCopy)
5277

5378
public companion object: KMACXofFactory<KMAC256>() {
5479

80+
/**
81+
* The default number of bytes output when [doFinal] is invoked (64)
82+
* */
83+
public const val MAC_LENGTH: Int = CSHAKE256.DIGEST_LENGTH
84+
5585
/**
5686
* Produces a new [Xof] (Extendable-Output Function) for [KMAC256]
5787
*
@@ -62,6 +92,6 @@ public class KMAC256: Kmac {
6292
* */
6393
@JvmStatic
6494
@JvmOverloads
65-
public fun xOf(key: ByteArray, S: ByteArray? = null): Xof<KMAC256> = KMACXof(KMAC256(key, S))
95+
public fun xOf(key: ByteArray, S: ByteArray? = null): Xof<KMAC256> = KMACXof(KMAC256(key, S, 0))
6696
}
6797
}

library/kmac/src/commonMain/kotlin/org/kotlincrypto/macs/Kmac.kt

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public sealed class Kmac: Mac, XofAlgorithm {
3838
key: ByteArray,
3939
S: ByteArray?,
4040
bitStrength: Int,
41-
): this(Kmac.Engine(key, S, bitStrength))
41+
outputLength: Int,
42+
): this(Kmac.Engine(key, S, bitStrength, outputLength))
4243

4344
@OptIn(InternalKotlinCryptoApi::class)
4445
protected constructor(engine: Mac.Engine): super((engine as Kmac.Engine).algorithm(), engine) {
@@ -48,8 +49,16 @@ public sealed class Kmac: Mac, XofAlgorithm {
4849
@OptIn(InternalKotlinCryptoApi::class)
4950
public sealed class KMACXofFactory<A: Kmac>: XofFactory<A>() {
5051
protected inner class KMACXof(delegate: A): XofDelegate(delegate) {
52+
53+
init {
54+
// While in XOF mode, the arbitrary output length, L, is
55+
// represented as 0.
56+
require(delegate.macLength() == 0) { "macLength must be 0" }
57+
}
58+
5159
override fun newReader(delegateCopy: A): Reader {
52-
val reader = delegateCopy.engine.padAndProvideReader(isXofMode = true)
60+
// Want to reset the copy Xof here before it gets black holed
61+
val reader = delegateCopy.engine.padAndProvideReader(resetXof = true)
5362

5463
return object : Xof<A>.Reader() {
5564
override fun readProtected(out: ByteArray, offset: Int, len: Int, bytesRead: Long) {
@@ -76,20 +85,22 @@ public sealed class Kmac: Mac, XofAlgorithm {
7685
private val outputLength: Int
7786

7887
@OptIn(InternalKotlinCryptoApi::class)
79-
constructor(key: ByteArray, S: ByteArray?, bitStrength: Int): super(key) {
88+
constructor(key: ByteArray, S: ByteArray?, bitStrength: Int, outputLength: Int): super(key) {
89+
require(outputLength >= 0) { "outputLength cannot be negative" }
90+
8091
this.bitStrength = bitStrength
92+
this.outputLength = outputLength
93+
8194
val N = KMAC.encodeToByteArray()
8295

8396
when (bitStrength) {
8497
BIT_STRENGTH_128 -> {
8598
this.xof = CSHAKE128.xOf(N, S)
8699
this.blockSize = CSHAKE128.BLOCK_SIZE
87-
this.outputLength = CSHAKE128.DIGEST_LENGTH
88100
}
89101
BIT_STRENGTH_256 -> {
90102
this.xof = CSHAKE256.xOf(N, S)
91103
this.blockSize = CSHAKE256.BLOCK_SIZE
92-
this.outputLength = CSHAKE256.DIGEST_LENGTH
93104
}
94105
else -> throw IllegalArgumentException("bitStrength must be $BIT_STRENGTH_128 or $BIT_STRENGTH_256")
95106
}
@@ -121,47 +132,31 @@ public sealed class Kmac: Mac, XofAlgorithm {
121132
}
122133

123134
override fun update(input: Byte) { xof.update(input) }
124-
override fun update(input: ByteArray) { xof.update(input) }
125135
override fun update(input: ByteArray, offset: Int, len: Int) { xof.update(input, offset, len) }
126136

127137
override fun doFinal(): ByteArray {
128138
val out = ByteArray(macLength())
129-
padAndProvideReader(isXofMode = false).use { read(out) }
139+
padAndProvideReader(resetXof = false).use { read(out) }
130140
return out
131141
}
132142

133-
fun padAndProvideReader(isXofMode: Boolean): Xof<*>.Reader {
134-
// Depending on if KMAC is being utilized as a Xof or as a Mac,
135-
// the output byte length will either be the 0 or macLength(),
136-
// respectively.
137-
//
138-
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf#4.3.1%20KMAC%20with%20Arbitrary-Length%20Output
139-
val outputByteLength = if (isXofMode) 0 else macLength()
140-
143+
fun padAndProvideReader(resetXof: Boolean): Xof<*>.Reader {
141144
@OptIn(InternalKotlinCryptoApi::class)
142-
val encL = Xof.Utils.rightEncode(outputByteLength * 8L)
145+
val encL = Xof.Utils.rightEncode(macLength() * 8L)
143146
xof.update(encL)
144-
145-
// If this is being used as a Mac, doFinal was called so no
146-
// need to reset unnecessarily here as Mac.doFinal will call
147-
// engine.reset() right after output is returned.
148-
//
149-
// If this is being used as a Xof, padAndProvideReader is
150-
// called from a copy and will be single use for that Reader,
151-
// so resetting it is needed before it gets black holed.
152-
return xof.reader(resetXof = isXofMode)
147+
return xof.reader(resetXof = resetXof)
153148
}
154149

155150
private fun ByteArray.bytepad() {
156-
update(this)
151+
xof.update(this)
157152

158153
val remainder = size % blockSize
159154

160155
// No padding is needed
161156
if (remainder == 0) return
162157

163158
repeat(blockSize - remainder) {
164-
update(0)
159+
xof.update(0)
165160
}
166161
}
167162

0 commit comments

Comments
 (0)