aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/util/pwgen
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/dev/msfjarvis/aps/util/pwgen')
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt204
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt20
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt65
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt304
4 files changed, 291 insertions, 302 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt
index c9361e15..8199ebfc 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt
@@ -12,128 +12,118 @@ import dev.msfjarvis.aps.util.extensions.hasFlag
import dev.msfjarvis.aps.util.settings.PreferenceKeys
enum class PasswordOption(val key: String) {
- NoDigits("0"),
- NoUppercaseLetters("A"),
- NoAmbiguousCharacters("B"),
- FullyRandom("s"),
- AtLeastOneSymbol("y"),
- NoLowercaseLetters("L")
+ NoDigits("0"),
+ NoUppercaseLetters("A"),
+ NoAmbiguousCharacters("B"),
+ FullyRandom("s"),
+ AtLeastOneSymbol("y"),
+ NoLowercaseLetters("L")
}
object PasswordGenerator {
- const val DEFAULT_LENGTH = 16
+ const val DEFAULT_LENGTH = 16
- const val DIGITS = 0x0001
- const val UPPERS = 0x0002
- const val SYMBOLS = 0x0004
- const val NO_AMBIGUOUS = 0x0008
- const val LOWERS = 0x0020
+ const val DIGITS = 0x0001
+ const val UPPERS = 0x0002
+ const val SYMBOLS = 0x0004
+ const val NO_AMBIGUOUS = 0x0008
+ const val LOWERS = 0x0020
- const val DIGITS_STR = "0123456789"
- const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
- const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
- const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
+ const val DIGITS_STR = "0123456789"
+ const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
+ const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+ const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
- /**
- * Enables the [PasswordOption]s in [options] and sets [targetLength] as the length for
- * generated passwords.
- */
- fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean {
- ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit {
- for (possibleOption in PasswordOption.values())
- putBoolean(possibleOption.key, possibleOption in options)
- putInt("length", targetLength)
- }
- return true
- }
-
- fun isValidPassword(password: String, pwFlags: Int): Boolean {
- if (pwFlags hasFlag DIGITS && password.none { it in DIGITS_STR })
- return false
- if (pwFlags hasFlag UPPERS && password.none { it in UPPERS_STR })
- return false
- if (pwFlags hasFlag LOWERS && password.none { it in LOWERS_STR })
- return false
- if (pwFlags hasFlag SYMBOLS && password.none { it in SYMBOLS_STR })
- return false
- if (pwFlags hasFlag NO_AMBIGUOUS && password.any { it in AMBIGUOUS_STR })
- return false
- return true
+ /**
+ * Enables the [PasswordOption] s in [options] and sets [targetLength] as the length for generated
+ * passwords.
+ */
+ fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean {
+ ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit {
+ for (possibleOption in PasswordOption.values()) putBoolean(possibleOption.key, possibleOption in options)
+ putInt("length", targetLength)
}
+ return true
+ }
- /**
- * Generates a password using the preferences set by [setPrefs].
- */
- @Throws(PasswordGeneratorException::class)
- fun generate(ctx: Context): String {
- val prefs = ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
- var numCharacterCategories = 0
+ fun isValidPassword(password: String, pwFlags: Int): Boolean {
+ if (pwFlags hasFlag DIGITS && password.none { it in DIGITS_STR }) return false
+ if (pwFlags hasFlag UPPERS && password.none { it in UPPERS_STR }) return false
+ if (pwFlags hasFlag LOWERS && password.none { it in LOWERS_STR }) return false
+ if (pwFlags hasFlag SYMBOLS && password.none { it in SYMBOLS_STR }) return false
+ if (pwFlags hasFlag NO_AMBIGUOUS && password.any { it in AMBIGUOUS_STR }) return false
+ return true
+ }
- var phonemes = true
- var pwgenFlags = DIGITS or UPPERS or LOWERS
+ /** Generates a password using the preferences set by [setPrefs]. */
+ @Throws(PasswordGeneratorException::class)
+ fun generate(ctx: Context): String {
+ val prefs = ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
+ var numCharacterCategories = 0
- for (option in PasswordOption.values()) {
- if (prefs.getBoolean(option.key, false)) {
- when (option) {
- PasswordOption.NoDigits -> pwgenFlags = pwgenFlags.clearFlag(DIGITS)
- PasswordOption.NoUppercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(UPPERS)
- PasswordOption.NoLowercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(LOWERS)
- PasswordOption.NoAmbiguousCharacters -> pwgenFlags = pwgenFlags or NO_AMBIGUOUS
- PasswordOption.FullyRandom -> phonemes = false
- PasswordOption.AtLeastOneSymbol -> {
- numCharacterCategories++
- pwgenFlags = pwgenFlags or SYMBOLS
- }
- }
- } else {
- // The No* options are false, so the respective character category will be included.
- when (option) {
- PasswordOption.NoDigits,
- PasswordOption.NoUppercaseLetters,
- PasswordOption.NoLowercaseLetters -> {
- numCharacterCategories++
- }
- PasswordOption.NoAmbiguousCharacters,
- PasswordOption.FullyRandom,
- // Since AtLeastOneSymbol is not negated, it is counted in the if branch.
- PasswordOption.AtLeastOneSymbol -> {
- }
- }
- }
- }
+ var phonemes = true
+ var pwgenFlags = DIGITS or UPPERS or LOWERS
- val length = prefs.getInt(PreferenceKeys.LENGTH, DEFAULT_LENGTH)
- if (pwgenFlags.clearFlag(NO_AMBIGUOUS) == 0) {
- throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_no_chars_error))
- }
- if (length < numCharacterCategories) {
- throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_length_too_short_error))
- }
- if (!(pwgenFlags hasFlag UPPERS) && !(pwgenFlags hasFlag LOWERS)) {
- phonemes = false
- pwgenFlags = pwgenFlags.clearFlag(NO_AMBIGUOUS)
+ for (option in PasswordOption.values()) {
+ if (prefs.getBoolean(option.key, false)) {
+ when (option) {
+ PasswordOption.NoDigits -> pwgenFlags = pwgenFlags.clearFlag(DIGITS)
+ PasswordOption.NoUppercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(UPPERS)
+ PasswordOption.NoLowercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(LOWERS)
+ PasswordOption.NoAmbiguousCharacters -> pwgenFlags = pwgenFlags or NO_AMBIGUOUS
+ PasswordOption.FullyRandom -> phonemes = false
+ PasswordOption.AtLeastOneSymbol -> {
+ numCharacterCategories++
+ pwgenFlags = pwgenFlags or SYMBOLS
+ }
}
- // Experiments show that phonemes may require more than 1000 iterations to generate a valid
- // password if the length is not at least 6.
- if (length < 6) {
- phonemes = false
+ } else {
+ // The No* options are false, so the respective character category will be included.
+ when (option) {
+ PasswordOption.NoDigits, PasswordOption.NoUppercaseLetters, PasswordOption.NoLowercaseLetters -> {
+ numCharacterCategories++
+ }
+ PasswordOption.NoAmbiguousCharacters,
+ PasswordOption.FullyRandom,
+ // Since AtLeastOneSymbol is not negated, it is counted in the if branch.
+ PasswordOption.AtLeastOneSymbol -> {}
}
+ }
+ }
- var password: String?
- var iterations = 0
- do {
- if (iterations++ > 1000)
- throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_max_iterations_exceeded))
- password = if (phonemes) {
- RandomPhonemesGenerator.generate(length, pwgenFlags)
- } else {
- RandomPasswordGenerator.generate(length, pwgenFlags)
- }
- } while (password == null)
- return password
+ val length = prefs.getInt(PreferenceKeys.LENGTH, DEFAULT_LENGTH)
+ if (pwgenFlags.clearFlag(NO_AMBIGUOUS) == 0) {
+ throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_no_chars_error))
+ }
+ if (length < numCharacterCategories) {
+ throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_length_too_short_error))
+ }
+ if (!(pwgenFlags hasFlag UPPERS) && !(pwgenFlags hasFlag LOWERS)) {
+ phonemes = false
+ pwgenFlags = pwgenFlags.clearFlag(NO_AMBIGUOUS)
}
+ // Experiments show that phonemes may require more than 1000 iterations to generate a valid
+ // password if the length is not at least 6.
+ if (length < 6) {
+ phonemes = false
+ }
+
+ var password: String?
+ var iterations = 0
+ do {
+ if (iterations++ > 1000)
+ throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_max_iterations_exceeded))
+ password =
+ if (phonemes) {
+ RandomPhonemesGenerator.generate(length, pwgenFlags)
+ } else {
+ RandomPasswordGenerator.generate(length, pwgenFlags)
+ }
+ } while (password == null)
+ return password
+ }
- class PasswordGeneratorException(string: String) : Exception(string)
+ class PasswordGeneratorException(string: String) : Exception(string)
}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt
index 936f2cae..8ef490cc 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt
@@ -8,26 +8,24 @@ import java.security.SecureRandom
private val secureRandom = SecureRandom()
-/**
- * Returns a number between 0 (inclusive) and [exclusiveBound] (exclusive).
- */
+/** Returns a number between 0 (inclusive) and [exclusiveBound](exclusive). */
fun secureRandomNumber(exclusiveBound: Int) = secureRandom.nextInt(exclusiveBound)
-/**
- * Returns `true` and `false` with probablity 50% each.
- */
+/** Returns `true` and `false` with probablity 50% each. */
fun secureRandomBoolean() = secureRandom.nextBoolean()
/**
- * Returns `true` with probability [percentTrue]% and `false` with probability
- * `(100 - [percentTrue])`%.
+ * Returns `true` with probability [percentTrue]% and `false` with probability `(100 - [percentTrue]
+ * )`%.
*/
fun secureRandomBiasedBoolean(percentTrue: Int): Boolean {
- require(1 <= percentTrue) { "Probability for returning `true` must be at least 1%" }
- require(percentTrue <= 99) { "Probability for returning `true` must be at most 99%" }
- return secureRandomNumber(100) < percentTrue
+ require(1 <= percentTrue) { "Probability for returning `true` must be at least 1%" }
+ require(percentTrue <= 99) { "Probability for returning `true` must be at most 99%" }
+ return secureRandomNumber(100) < percentTrue
}
fun <T> Array<T>.secureRandomElement() = this[secureRandomNumber(size)]
+
fun <T> List<T>.secureRandomElement() = this[secureRandomNumber(size)]
+
fun String.secureRandomCharacter() = this[secureRandomNumber(length)]
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt
index 33e58228..e92a753b 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt
@@ -8,38 +8,39 @@ import dev.msfjarvis.aps.util.extensions.hasFlag
object RandomPasswordGenerator {
- /**
- * Generates a random password of length [targetLength], taking the following flags in [pwFlags]
- * into account, or fails to do so and returns null:
- *
- * - [PasswordGenerator.DIGITS]: If set, the password will contain at least one digit; if not
- * set, the password will not contain any digits.
- * - [PasswordGenerator.UPPERS]: If set, the password will contain at least one uppercase
- * letter; if not set, the password will not contain any uppercase letters.
- * - [PasswordGenerator.LOWERS]: If set, the password will contain at least one lowercase
- * letter; if not set, the password will not contain any lowercase letters.
- * - [PasswordGenerator.SYMBOLS]: If set, the password will contain at least one symbol; if not
- * set, the password will not contain any symbols.
- * - [PasswordGenerator.NO_AMBIGUOUS]: If set, the password will not contain any ambiguous
- * characters.
- */
- fun generate(targetLength: Int, pwFlags: Int): String? {
- val bank = listOfNotNull(
- PasswordGenerator.DIGITS_STR.takeIf { pwFlags hasFlag PasswordGenerator.DIGITS },
- PasswordGenerator.UPPERS_STR.takeIf { pwFlags hasFlag PasswordGenerator.UPPERS },
- PasswordGenerator.LOWERS_STR.takeIf { pwFlags hasFlag PasswordGenerator.LOWERS },
- PasswordGenerator.SYMBOLS_STR.takeIf { pwFlags hasFlag PasswordGenerator.SYMBOLS },
- ).joinToString("")
+ /**
+ * Generates a random password of length [targetLength], taking the following flags in [pwFlags]
+ * into account, or fails to do so and returns null:
+ *
+ * - [PasswordGenerator.DIGITS]: If set, the password will contain at least one digit; if not set,
+ * the password will not contain any digits.
+ * - [PasswordGenerator.UPPERS]: If set, the password will contain at least one uppercase letter;
+ * if not set, the password will not contain any uppercase letters.
+ * - [PasswordGenerator.LOWERS]: If set, the password will contain at least one lowercase letter;
+ * if not set, the password will not contain any lowercase letters.
+ * - [PasswordGenerator.SYMBOLS]: If set, the password will contain at least one symbol; if not
+ * set, the password will not contain any symbols.
+ * - [PasswordGenerator.NO_AMBIGUOUS]: If set, the password will not contain any ambiguous
+ * characters.
+ */
+ fun generate(targetLength: Int, pwFlags: Int): String? {
+ val bank =
+ listOfNotNull(
+ PasswordGenerator.DIGITS_STR.takeIf { pwFlags hasFlag PasswordGenerator.DIGITS },
+ PasswordGenerator.UPPERS_STR.takeIf { pwFlags hasFlag PasswordGenerator.UPPERS },
+ PasswordGenerator.LOWERS_STR.takeIf { pwFlags hasFlag PasswordGenerator.LOWERS },
+ PasswordGenerator.SYMBOLS_STR.takeIf { pwFlags hasFlag PasswordGenerator.SYMBOLS },
+ )
+ .joinToString("")
- var password = ""
- while (password.length < targetLength) {
- val candidate = bank.secureRandomCharacter()
- if (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
- candidate in PasswordGenerator.AMBIGUOUS_STR) {
- continue
- }
- password += candidate
- }
- return password.takeIf { PasswordGenerator.isValidPassword(it, pwFlags) }
+ var password = ""
+ while (password.length < targetLength) {
+ val candidate = bank.secureRandomCharacter()
+ if (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && candidate in PasswordGenerator.AMBIGUOUS_STR) {
+ continue
+ }
+ password += candidate
}
+ return password.takeIf { PasswordGenerator.isValidPassword(it, pwFlags) }
+ }
}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt
index e0f7e387..1b2d0fb7 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt
@@ -9,161 +9,161 @@ import java.util.Locale
object RandomPhonemesGenerator {
- private const val CONSONANT = 0x0001
- private const val VOWEL = 0x0002
- private const val DIPHTHONG = 0x0004
- private const val NOT_FIRST = 0x0008
-
- private val elements = arrayOf(
- Element("a", VOWEL),
- Element("ae", VOWEL or DIPHTHONG),
- Element("ah", VOWEL or DIPHTHONG),
- Element("ai", VOWEL or DIPHTHONG),
- Element("b", CONSONANT),
- Element("c", CONSONANT),
- Element("ch", CONSONANT or DIPHTHONG),
- Element("d", CONSONANT),
- Element("e", VOWEL),
- Element("ee", VOWEL or DIPHTHONG),
- Element("ei", VOWEL or DIPHTHONG),
- Element("f", CONSONANT),
- Element("g", CONSONANT),
- Element("gh", CONSONANT or DIPHTHONG or NOT_FIRST),
- Element("h", CONSONANT),
- Element("i", VOWEL),
- Element("ie", VOWEL or DIPHTHONG),
- Element("j", CONSONANT),
- Element("k", CONSONANT),
- Element("l", CONSONANT),
- Element("m", CONSONANT),
- Element("n", CONSONANT),
- Element("ng", CONSONANT or DIPHTHONG or NOT_FIRST),
- Element("o", VOWEL),
- Element("oh", VOWEL or DIPHTHONG),
- Element("oo", VOWEL or DIPHTHONG),
- Element("p", CONSONANT),
- Element("ph", CONSONANT or DIPHTHONG),
- Element("qu", CONSONANT or DIPHTHONG),
- Element("r", CONSONANT),
- Element("s", CONSONANT),
- Element("sh", CONSONANT or DIPHTHONG),
- Element("t", CONSONANT),
- Element("th", CONSONANT or DIPHTHONG),
- Element("u", VOWEL),
- Element("v", CONSONANT),
- Element("w", CONSONANT),
- Element("x", CONSONANT),
- Element("y", CONSONANT),
- Element("z", CONSONANT)
+ private const val CONSONANT = 0x0001
+ private const val VOWEL = 0x0002
+ private const val DIPHTHONG = 0x0004
+ private const val NOT_FIRST = 0x0008
+
+ private val elements =
+ arrayOf(
+ Element("a", VOWEL),
+ Element("ae", VOWEL or DIPHTHONG),
+ Element("ah", VOWEL or DIPHTHONG),
+ Element("ai", VOWEL or DIPHTHONG),
+ Element("b", CONSONANT),
+ Element("c", CONSONANT),
+ Element("ch", CONSONANT or DIPHTHONG),
+ Element("d", CONSONANT),
+ Element("e", VOWEL),
+ Element("ee", VOWEL or DIPHTHONG),
+ Element("ei", VOWEL or DIPHTHONG),
+ Element("f", CONSONANT),
+ Element("g", CONSONANT),
+ Element("gh", CONSONANT or DIPHTHONG or NOT_FIRST),
+ Element("h", CONSONANT),
+ Element("i", VOWEL),
+ Element("ie", VOWEL or DIPHTHONG),
+ Element("j", CONSONANT),
+ Element("k", CONSONANT),
+ Element("l", CONSONANT),
+ Element("m", CONSONANT),
+ Element("n", CONSONANT),
+ Element("ng", CONSONANT or DIPHTHONG or NOT_FIRST),
+ Element("o", VOWEL),
+ Element("oh", VOWEL or DIPHTHONG),
+ Element("oo", VOWEL or DIPHTHONG),
+ Element("p", CONSONANT),
+ Element("ph", CONSONANT or DIPHTHONG),
+ Element("qu", CONSONANT or DIPHTHONG),
+ Element("r", CONSONANT),
+ Element("s", CONSONANT),
+ Element("sh", CONSONANT or DIPHTHONG),
+ Element("t", CONSONANT),
+ Element("th", CONSONANT or DIPHTHONG),
+ Element("u", VOWEL),
+ Element("v", CONSONANT),
+ Element("w", CONSONANT),
+ Element("x", CONSONANT),
+ Element("y", CONSONANT),
+ Element("z", CONSONANT)
)
- private class Element(str: String, val flags: Int) {
-
- val upperCase = str.toUpperCase(Locale.ROOT)
- val lowerCase = str.toLowerCase(Locale.ROOT)
- val length = str.length
- val isAmbiguous = str.any { it in PasswordGenerator.AMBIGUOUS_STR }
- }
+ private class Element(str: String, val flags: Int) {
+
+ val upperCase = str.toUpperCase(Locale.ROOT)
+ val lowerCase = str.toLowerCase(Locale.ROOT)
+ val length = str.length
+ val isAmbiguous = str.any { it in PasswordGenerator.AMBIGUOUS_STR }
+ }
+
+ /**
+ * Generates a random human-readable password of length [targetLength], taking the following flags
+ * in [pwFlags] into account, or fails to do so and returns null:
+ *
+ * - [PasswordGenerator.DIGITS]: If set, the password will contain at least one digit; if not set,
+ * the password will not contain any digits.
+ * - [PasswordGenerator.UPPERS]: If set, the password will contain at least one uppercase letter;
+ * if not set, the password will not contain any uppercase letters.
+ * - [PasswordGenerator.LOWERS]: If set, the password will contain at least one lowercase letter;
+ * if not set and [PasswordGenerator.UPPERS] is set, the password will not contain any lowercase
+ * characters; if both are not set, an exception is thrown.
+ * - [PasswordGenerator.SYMBOLS]: If set, the password will contain at least one symbol; if not
+ * set, the password will not contain any symbols.
+ * - [PasswordGenerator.NO_AMBIGUOUS]: If set, the password will not contain any ambiguous
+ * characters.
+ */
+ fun generate(targetLength: Int, pwFlags: Int): String? {
+ require(pwFlags hasFlag PasswordGenerator.UPPERS || pwFlags hasFlag PasswordGenerator.LOWERS)
+
+ var password = ""
+
+ var isStartOfPart = true
+ var nextBasicType = if (secureRandomBoolean()) VOWEL else CONSONANT
+ var previousFlags = 0
+
+ while (password.length < targetLength) {
+ // First part: Add a single letter or pronounceable pair of letters in varying case.
+
+ val candidate = elements.secureRandomElement()
+
+ // Reroll if the candidate does not fulfill the current requirements.
+ if (!candidate.flags.hasFlag(nextBasicType) ||
+ (isStartOfPart && candidate.flags hasFlag NOT_FIRST) ||
+ // Don't let a diphthong that starts with a vowel follow a vowel.
+ (previousFlags hasFlag VOWEL && candidate.flags hasFlag VOWEL && candidate.flags hasFlag DIPHTHONG) ||
+ // Don't add multi-character candidates if we would go over the targetLength.
+ (password.length + candidate.length > targetLength) ||
+ (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && candidate.isAmbiguous)
+ ) {
+ continue
+ }
+
+ // At this point the candidate could be appended to the password, but we still have
+ // to determine the case. If no upper case characters are required, we don't add
+ // any.
+ val useUpperIfBothCasesAllowed =
+ (isStartOfPart || candidate.flags hasFlag CONSONANT) && secureRandomBiasedBoolean(20)
+ password +=
+ if (pwFlags hasFlag PasswordGenerator.UPPERS &&
+ (!(pwFlags hasFlag PasswordGenerator.LOWERS) || useUpperIfBothCasesAllowed)
+ ) {
+ candidate.upperCase
+ } else {
+ candidate.lowerCase
+ }
- /**
- * Generates a random human-readable password of length [targetLength], taking the following
- * flags in [pwFlags] into account, or fails to do so and returns null:
- *
- * - [PasswordGenerator.DIGITS]: If set, the password will contain at least one digit; if not
- * set, the password will not contain any digits.
- * - [PasswordGenerator.UPPERS]: If set, the password will contain at least one uppercase
- * letter; if not set, the password will not contain any uppercase letters.
- * - [PasswordGenerator.LOWERS]: If set, the password will contain at least one lowercase
- * letter; if not set and [PasswordGenerator.UPPERS] is set, the password will not contain any
- * lowercase characters; if both are not set, an exception is thrown.
- * - [PasswordGenerator.SYMBOLS]: If set, the password will contain at least one symbol; if not
- * set, the password will not contain any symbols.
- * - [PasswordGenerator.NO_AMBIGUOUS]: If set, the password will not contain any ambiguous
- * characters.
- */
- fun generate(targetLength: Int, pwFlags: Int): String? {
- require(pwFlags hasFlag PasswordGenerator.UPPERS || pwFlags hasFlag PasswordGenerator.LOWERS)
-
- var password = ""
-
- var isStartOfPart = true
- var nextBasicType = if (secureRandomBoolean()) VOWEL else CONSONANT
- var previousFlags = 0
-
- while (password.length < targetLength) {
- // First part: Add a single letter or pronounceable pair of letters in varying case.
-
- val candidate = elements.secureRandomElement()
-
- // Reroll if the candidate does not fulfill the current requirements.
- if (!candidate.flags.hasFlag(nextBasicType) ||
- (isStartOfPart && candidate.flags hasFlag NOT_FIRST) ||
- // Don't let a diphthong that starts with a vowel follow a vowel.
- (previousFlags hasFlag VOWEL && candidate.flags hasFlag VOWEL && candidate.flags hasFlag DIPHTHONG) ||
- // Don't add multi-character candidates if we would go over the targetLength.
- (password.length + candidate.length > targetLength) ||
- (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && candidate.isAmbiguous)) {
- continue
- }
-
- // At this point the candidate could be appended to the password, but we still have
- // to determine the case. If no upper case characters are required, we don't add
- // any.
- val useUpperIfBothCasesAllowed =
- (isStartOfPart || candidate.flags hasFlag CONSONANT) && secureRandomBiasedBoolean(20)
- password += if (pwFlags hasFlag PasswordGenerator.UPPERS &&
- (!(pwFlags hasFlag PasswordGenerator.LOWERS) || useUpperIfBothCasesAllowed)) {
- candidate.upperCase
- } else {
- candidate.lowerCase
- }
-
- // We ensured above that we will not go above the target length.
- check(password.length <= targetLength)
- if (password.length == targetLength)
- break
-
- // Second part: Add digits and symbols with a certain probability (if requested) if
- // they would not directly follow the first character in a pronounceable part.
-
- if (!isStartOfPart && pwFlags hasFlag PasswordGenerator.DIGITS &&
- secureRandomBiasedBoolean(30)) {
- var randomDigit: Char
- do {
- randomDigit = secureRandomNumber(10).toString(10).first()
- } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
- randomDigit in PasswordGenerator.AMBIGUOUS_STR)
-
- password += randomDigit
- // Begin a new pronounceable part after every digit.
- isStartOfPart = true
- nextBasicType = if (secureRandomBoolean()) VOWEL else CONSONANT
- previousFlags = 0
- continue
- }
-
- if (!isStartOfPart && pwFlags hasFlag PasswordGenerator.SYMBOLS &&
- secureRandomBiasedBoolean(20)) {
- var randomSymbol: Char
- do {
- randomSymbol = PasswordGenerator.SYMBOLS_STR.secureRandomCharacter()
- } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
- randomSymbol in PasswordGenerator.AMBIGUOUS_STR)
- password += randomSymbol
- // Continue the password generation as if nothing was added.
- }
-
- // Third part: Determine the basic type of the next character depending on the letter
- // we just added.
- nextBasicType = when {
- candidate.flags.hasFlag(CONSONANT) -> VOWEL
- previousFlags.hasFlag(VOWEL) || candidate.flags.hasFlag(DIPHTHONG) ||
- secureRandomBiasedBoolean(60) -> CONSONANT
- else -> VOWEL
- }
- previousFlags = candidate.flags
- isStartOfPart = false
+ // We ensured above that we will not go above the target length.
+ check(password.length <= targetLength)
+ if (password.length == targetLength) break
+
+ // Second part: Add digits and symbols with a certain probability (if requested) if
+ // they would not directly follow the first character in a pronounceable part.
+
+ if (!isStartOfPart && pwFlags hasFlag PasswordGenerator.DIGITS && secureRandomBiasedBoolean(30)) {
+ var randomDigit: Char
+ do {
+ randomDigit = secureRandomNumber(10).toString(10).first()
+ } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && randomDigit in PasswordGenerator.AMBIGUOUS_STR)
+
+ password += randomDigit
+ // Begin a new pronounceable part after every digit.
+ isStartOfPart = true
+ nextBasicType = if (secureRandomBoolean()) VOWEL else CONSONANT
+ previousFlags = 0
+ continue
+ }
+
+ if (!isStartOfPart && pwFlags hasFlag PasswordGenerator.SYMBOLS && secureRandomBiasedBoolean(20)) {
+ var randomSymbol: Char
+ do {
+ randomSymbol = PasswordGenerator.SYMBOLS_STR.secureRandomCharacter()
+ } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && randomSymbol in PasswordGenerator.AMBIGUOUS_STR)
+ password += randomSymbol
+ // Continue the password generation as if nothing was added.
+ }
+
+ // Third part: Determine the basic type of the next character depending on the letter
+ // we just added.
+ nextBasicType =
+ when {
+ candidate.flags.hasFlag(CONSONANT) -> VOWEL
+ previousFlags.hasFlag(VOWEL) || candidate.flags.hasFlag(DIPHTHONG) || secureRandomBiasedBoolean(60) ->
+ CONSONANT
+ else -> VOWEL
}
- return password.takeIf { PasswordGenerator.isValidPassword(it, pwFlags) }
+ previousFlags = candidate.flags
+ isStartOfPart = false
}
+ return password.takeIf { PasswordGenerator.isValidPassword(it, pwFlags) }
+ }
}