diff options
Diffstat (limited to 'passgen/diceware/src/main/kotlin/app')
4 files changed, 105 insertions, 0 deletions
diff --git a/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/DicewarePassphraseGenerator.kt b/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/DicewarePassphraseGenerator.kt new file mode 100644 index 00000000..841eb31b --- /dev/null +++ b/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/DicewarePassphraseGenerator.kt @@ -0,0 +1,39 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.passgen.diceware + +import java.io.InputStream +import javax.inject.Inject + +/** + * Password generator implementing the Diceware passphrase generation mechanism. For detailed + * information on how this works, see https://theworld.com/~reinhold/diceware.html. + */ +public class DicewarePassphraseGenerator +@Inject +constructor( + private val die: Die, + wordList: InputStream, +) { + + private val wordMap = WordListParser.parse(wordList) + + /** Generates a passphrase with [wordCount] words. */ + public fun generatePassphrase(wordCount: Int, separator: Char): String { + return buildString { + repeat(wordCount) { idx -> + append(wordMap[die.rollMultiple(DIGITS)]) + if (idx < wordCount - 1) append(separator) + } + } + } + + private companion object { + + /** Number of digits used by indices in the default wordlist. */ + const val DIGITS: Int = 5 + } +} diff --git a/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/Die.kt b/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/Die.kt new file mode 100644 index 00000000..e3db4b80 --- /dev/null +++ b/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/Die.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.passgen.diceware + +import javax.inject.Inject + +/** Basic implementation of a die with configurable number of sides. */ +public class Die +@Inject +constructor( + private val sides: Int, + private val random: RandomIntGenerator, +) { + + /** Roll the die to return a single number. */ + public fun roll(): Int { + return random.get(1..sides) + } + + /** + * Roll the die multiple times, concatenating each result to obtain a number with [iterations] + * digits. + */ + public fun rollMultiple(iterations: Int): Int { + return StringBuilder().apply { repeat(iterations) { append(roll()) } }.toString().toInt() + } +} diff --git a/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/RandomIntGenerator.kt b/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/RandomIntGenerator.kt new file mode 100644 index 00000000..18a828a6 --- /dev/null +++ b/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/RandomIntGenerator.kt @@ -0,0 +1,15 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.passgen.diceware + +/** + * SAM interface that takes in an [IntRange] and returns a randomly chosen [Int] within its bounds. + * This is used as a replacement for [kotlin.random.Random] since there is no CSPRNG-backed + * implementation of it in the Kotlin stdlib. + */ +public fun interface RandomIntGenerator { + public fun get(range: IntRange): Int +} diff --git a/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/WordListParser.kt b/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/WordListParser.kt new file mode 100644 index 00000000..6c7f5310 --- /dev/null +++ b/passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/WordListParser.kt @@ -0,0 +1,21 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.passgen.diceware + +import java.io.InputStream + +internal object WordListParser { + fun parse(wordlistStream: InputStream) = + wordlistStream + .bufferedReader() + .lineSequence() + .map { line -> line.split(DELIMITER) } + .filter { items -> items.size == 2 && items[0].toIntOrNull() != null } + .map { items -> items[0].toInt() to items[1] } + .toMap() + + private const val DELIMITER = "\t" +} |