Compare commits

...

6 Commits

Author SHA1 Message Date
55658f2b24 add README 2025-10-04 12:40:38 +02:00
08919cc9de upgrade gradle wrapper 2025-08-16 21:27:07 +02:00
150f37649d fix parameter name 2025-08-04 13:29:10 +02:00
02eaa9ec59 implement seed 2025-08-04 13:28:08 +02:00
9480d02021 small refactor 2025-08-03 00:22:16 +02:00
f6f9625cf2 enable passing hash function to HMAC PRNG 2025-08-03 00:12:34 +02:00
7 changed files with 125 additions and 30 deletions

57
README.md Normal file
View File

@@ -0,0 +1,57 @@
# Kotlin OTP
This library provides various [Pseudorandom Number Generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator)
implementations in Kotlin Multiplatform.
## Features
* Generate random numbers using Kotlin's Random interface.
* Seed the PRNGs to receive predictable results.
* Reseed the PRNGs to add entropy to the following results.
* [HMAC DRBG](https://en.wikipedia.org/wiki/NIST_SP_800-90A#Hash_DRBG_and_HMAC_DRBG)
* Multiplatform support
* JVM
* JavaScript
* Native (Linux)
## Installation
Add the following to your `build.gradle.kts`.
```kotlin
repositories {
maven("https://git.infendro.com/api/packages/Infendro/maven")
}
dependencies {
implementation("com.infendro:random:1.0.0")
}
```
## Usage
```kotlin
import com.infendro.hash.SHA256
import com.infendro.random.PRNG
fun main() {
// create a PRNG instance
val rng = PRNG.HMAC(SHA256)
// seed the PRNG to receive predictable results
rng.seed(/* some seed */)
val a = rng.nextInt()
val b = rng.nextInt()
val c = rng.nextInt()
// reseed the PRNG to add entropy to the following results
rng.reseed(/* some random entropy */)
val d = rng.nextInt()
val e = rng.nextInt()
val f = rng.nextInt()
// each run:
// a, b, and c will be predictable
// d, e, and f will be random (assuming the entropy is also random)
}
```

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

4
gradlew vendored
View File

@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.

4
gradlew.bat vendored
View File

@@ -70,11 +70,11 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View File

@@ -1,26 +1,30 @@
package com.infendro.random package com.infendro.random
import com.infendro.bytearray.toInt import com.infendro.bytearray.addAll
import com.infendro.hash.SHA256 import com.infendro.hash.HashFunction
import com.infendro.mac.HMAC
import kotlin.random.Random
class HMAC internal constructor() : Random() { class HMAC internal constructor(
private val function: HashFunction,
) : PRNG() {
private val random = Default private val random = Default
private val hmac = HMAC(SHA256) private val hmac = com.infendro.mac.HMAC(function)
private var K: ByteArray private lateinit var K: ByteArray
private var V: ByteArray private lateinit var V: ByteArray
init { init {
K = ByteArray(32).also { val bytes = random.nextBytes(function.bytes)
random.nextBytes(it) seed(bytes)
}
V = ByteArray(32).also {
random.nextBytes(it)
}
} }
fun reseed( override fun seed(
seed: ByteArray,
) {
K = ByteArray(function.bytes) { 0x00 }
V = ByteArray(function.bytes) { 0x01 }
reseed(seed)
}
override fun reseed(
entropy: ByteArray, entropy: ByteArray,
) { ) {
K = hmac.hash(K, V + byteArrayOf(0x00) + entropy) K = hmac.hash(K, V + byteArrayOf(0x00) + entropy)
@@ -29,18 +33,21 @@ class HMAC internal constructor() : Random() {
V = hmac.hash(K, V) V = hmac.hash(K, V)
} }
override fun nextBits( override fun generate(
count: Int, count: Int,
): Int { ): ByteArray {
if (count !in 0..32) throw Exception() val bytes = buildList {
if (count == 0) return 0 while (size < count) {
V = hmac.hash(K, V)
val bytes = hmac.hash(K, V) addAll(V)
val bits = bytes.take(4).toInt() ushr (32 - count) }
}
K = hmac.hash(K, V + byteArrayOf(0x00)) K = hmac.hash(K, V + byteArrayOf(0x00))
V = hmac.hash(K, V) V = hmac.hash(K, V)
return bits return bytes
.take(count)
.toByteArray()
} }
} }

View File

@@ -1,5 +1,36 @@
package com.infendro.random package com.infendro.random
object PRNG { import com.infendro.bytearray.toInt
val HMAC = HMAC() import com.infendro.hash.HashFunction
import kotlin.random.Random
abstract class PRNG : Random() {
abstract fun seed(
seed: ByteArray,
)
abstract fun reseed(
entropy: ByteArray,
)
abstract fun generate(
count: Int,
): ByteArray
override fun nextBits(
bitCount: Int,
): Int {
if (bitCount !in 0..32) throw Exception()
if (bitCount == 0) return 0
return generate(4).toInt() ushr (32 - bitCount)
}
companion object {
fun HMAC(
function: HashFunction,
): HMAC {
return com.infendro.random.HMAC(function)
}
}
} }