1package com.peachpasswords.vault23import java.security.SecureRandom4567891011object PasswordGenerator {12 const val MIN_PASSPHRASE_WORDS = 413 const val DEFAULT_PASSPHRASE_WORDS = 514 const val MAX_PASSPHRASE_WORDS = 121516 private const val LOWER = "abcdefghijklmnopqrstuvwxyz"17 private const val UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"18 private const val NUMBERS = "0123456789"19 private const val SYMBOLS = "!@#$%^&*"2021 private val rng = SecureRandom()22 private val passphraseWords = EffShortWordList.words.filterNot { it.contains("-") }2324 data class Options(25 val length: Int = 20,26 val includeLowercase: Boolean = true,27 val includeUppercase: Boolean = true,28 val includeNumbers: Boolean = true,29 val includeSymbols: Boolean = true,30 val forbiddenChars: String = "",31 )3233 fun generate(options: Options = Options()): String {34 val forbidden = options.forbiddenChars.toSet()35 fun filter(pool: String) = if (forbidden.isEmpty()) pool else pool.filterNot { it in forbidden }3637 var chars = buildString {38 if (options.includeLowercase) append(filter(LOWER))39 if (options.includeUppercase) append(filter(UPPER))40 if (options.includeNumbers) append(filter(NUMBERS))41 if (options.includeSymbols) append(filter(SYMBOLS))42 }43 if (chars.isEmpty()) chars = filter(LOWER).ifEmpty { LOWER }4445 val length = options.length.coerceAtLeast(1)46 return buildString(length) {47 repeat(length) { append(chars[rng.nextInt(chars.length)]) }48 }49 }5051 fun generatePassphrase(52 wordCount: Int = DEFAULT_PASSPHRASE_WORDS,53 separator: String = "-",54 ): String {55 val count = wordCount.coerceIn(MIN_PASSPHRASE_WORDS, MAX_PASSPHRASE_WORDS)56 return List(count) { passphraseWords[rng.nextInt(passphraseWords.size)] }57 .joinToString(separator)58 }59}