Refactor password generation (#860)
* Refactor password generation * Update Extensions.kt * Update app/src/main/java/com/zeapo/pwdstore/pwgen/PasswordGenerator.kt Co-authored-by: Harsh Shandilya <me@msfjarvis.dev> * Address review comments Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
e25e0035a2
commit
33b3f54921
11 changed files with 414 additions and 554 deletions
|
@ -7,139 +7,131 @@ package com.zeapo.pwdstore.pwgen
|
|||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.zeapo.pwdstore.R
|
||||
import java.util.ArrayList
|
||||
import com.zeapo.pwdstore.utils.clearFlag
|
||||
import com.zeapo.pwdstore.utils.hasFlag
|
||||
|
||||
enum class PasswordOption(val key: String) {
|
||||
NoDigits("0"),
|
||||
NoUppercaseLetters("A"),
|
||||
NoAmbiguousCharacters("B"),
|
||||
FullyRandom("s"),
|
||||
AtLeastOneSymbol("y"),
|
||||
NoLowercaseLetters("L")
|
||||
}
|
||||
|
||||
object PasswordGenerator {
|
||||
internal const val DIGITS = 0x0001
|
||||
internal const val UPPERS = 0x0002
|
||||
internal const val SYMBOLS = 0x0004
|
||||
internal const val AMBIGUOUS = 0x0008
|
||||
internal const val NO_VOWELS = 0x0010
|
||||
internal const val LOWERS = 0x0020
|
||||
const val DEFAULT_LENGTH = 16
|
||||
|
||||
internal const val DIGITS_STR = "0123456789"
|
||||
internal const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
internal const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
|
||||
internal const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
internal const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
|
||||
internal const val VOWELS_STR = "01aeiouyAEIOUY"
|
||||
const val DIGITS = 0x0001
|
||||
const val UPPERS = 0x0002
|
||||
const val SYMBOLS = 0x0004
|
||||
const val NO_AMBIGUOUS = 0x0008
|
||||
const val LOWERS = 0x0020
|
||||
|
||||
// No a, c, n, h, H, C, 1, N
|
||||
private const val pwOptions = "0ABsvyL"
|
||||
const val DIGITS_STR = "0123456789"
|
||||
const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
|
||||
const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
|
||||
|
||||
/**
|
||||
* Sets password generation preferences.
|
||||
*
|
||||
* @param ctx context from which to retrieve SharedPreferences from
|
||||
* preferences file 'PasswordGenerator'
|
||||
* @param argv options for password generation
|
||||
* <table summary="options for password generation">
|
||||
* <tr><td>Option</td><td>Description</td></tr>
|
||||
* <tr><td>0</td><td>don't include numbers</td></tr>
|
||||
* <tr><td>A</td><td>don't include uppercase letters</td></tr>
|
||||
* <tr><td>B</td><td>don't include ambiguous charactersl</td></tr>
|
||||
* <tr><td>s</td><td>generate completely random passwords</td></tr>
|
||||
* <tr><td>v</td><td>don't include vowels</td></tr>
|
||||
* <tr><td>y</td><td>include at least one symbol</td></tr>
|
||||
* <tr><td>L</td><td>don't include lowercase letters</td></tr>
|
||||
</table> *
|
||||
* @param numArgv numerical options for password generation: length of
|
||||
* generated passwords followed by number of passwords to
|
||||
* generate
|
||||
* @return `false` if a numerical options is invalid,
|
||||
* `true` otherwise
|
||||
* Enables the [PasswordOption]s in [options] and sets [targetLength] as the length for
|
||||
* generated passwords.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun setPrefs(ctx: Context, argv: ArrayList<String>, vararg numArgv: Int): Boolean {
|
||||
fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean {
|
||||
ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit {
|
||||
for (option in pwOptions.toCharArray()) {
|
||||
if (argv.contains(option.toString())) {
|
||||
putBoolean(option.toString(), true)
|
||||
argv.remove(option.toString())
|
||||
} else {
|
||||
putBoolean(option.toString(), false)
|
||||
}
|
||||
}
|
||||
var i = 0
|
||||
while (i < numArgv.size && i < 2) {
|
||||
if (numArgv[i] <= 0) {
|
||||
// Invalid password length or number of passwords
|
||||
return false
|
||||
}
|
||||
val name = if (i == 0) "length" else "num"
|
||||
putInt(name, numArgv[i])
|
||||
i++
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates passwords using the preferences set by
|
||||
* [.setPrefs].
|
||||
*
|
||||
* @param ctx context from which to retrieve SharedPreferences from
|
||||
* preferences file 'PasswordGenerator'
|
||||
* @return list of generated passwords
|
||||
* Generates a password using the preferences set by [setPrefs].
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(PasswordGeneratorExeption::class)
|
||||
fun generate(ctx: Context): ArrayList<String> {
|
||||
@Throws(PasswordGeneratorException::class)
|
||||
fun generate(ctx: Context): String {
|
||||
val prefs = ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
|
||||
var numCharacterCategories = 0
|
||||
|
||||
var phonemes = true
|
||||
var pwgenFlags = DIGITS or UPPERS or LOWERS
|
||||
|
||||
for (option in pwOptions.toCharArray()) {
|
||||
if (prefs.getBoolean(option.toString(), false)) {
|
||||
for (option in PasswordOption.values()) {
|
||||
if (prefs.getBoolean(option.key, false)) {
|
||||
when (option) {
|
||||
'0' -> pwgenFlags = pwgenFlags and DIGITS.inv()
|
||||
'A' -> pwgenFlags = pwgenFlags and UPPERS.inv()
|
||||
'L' -> pwgenFlags = pwgenFlags and LOWERS.inv()
|
||||
'B' -> pwgenFlags = pwgenFlags or AMBIGUOUS
|
||||
's' -> phonemes = false
|
||||
'y' -> pwgenFlags = pwgenFlags or SYMBOLS
|
||||
'v' -> {
|
||||
phonemes = false
|
||||
pwgenFlags = pwgenFlags or NO_VOWELS // | DIGITS | UPPERS;
|
||||
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
|
||||
}
|
||||
} // pwgenFlags = DIGITS | UPPERS;
|
||||
}
|
||||
}
|
||||
|
||||
val length = prefs.getInt("length", 8)
|
||||
var numCategories = 0
|
||||
var categories = pwgenFlags and AMBIGUOUS.inv()
|
||||
|
||||
while (categories != 0) {
|
||||
if (categories and 1 == 1)
|
||||
numCategories++
|
||||
categories = categories shr 1
|
||||
}
|
||||
if (numCategories == 0) {
|
||||
throw PasswordGeneratorExeption(ctx.resources.getString(R.string.pwgen_no_chars_error))
|
||||
}
|
||||
if (length < numCategories) {
|
||||
throw PasswordGeneratorExeption(ctx.resources.getString(R.string.pwgen_length_too_short_error))
|
||||
}
|
||||
if ((pwgenFlags and UPPERS) == 0 && (pwgenFlags and LOWERS) == 0) { // Only digits and/or symbols
|
||||
phonemes = false
|
||||
pwgenFlags = pwgenFlags and AMBIGUOUS.inv()
|
||||
} else if (length < 5) {
|
||||
phonemes = false
|
||||
}
|
||||
|
||||
val passwords = ArrayList<String>()
|
||||
val num = prefs.getInt("num", 1)
|
||||
for (i in 0 until num) {
|
||||
if (phonemes) {
|
||||
passwords.add(Phonemes.phonemes(length, pwgenFlags))
|
||||
}
|
||||
} else {
|
||||
passwords.add(RandomPasswordGenerator.rand(length, pwgenFlags))
|
||||
// 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 -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return passwords
|
||||
|
||||
val length = prefs.getInt("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 PasswordGeneratorExeption(string: String) : Exception(string)
|
||||
class PasswordGeneratorException(string: String) : Exception(string)
|
||||
}
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.pwgen
|
||||
|
||||
internal object Phonemes {
|
||||
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 val NUM_ELEMENTS = elements.size
|
||||
|
||||
private class Element internal constructor(internal var str: String, internal var flags: Int)
|
||||
|
||||
/**
|
||||
* Generates a human-readable password.
|
||||
*
|
||||
* @param size length of password to generate
|
||||
* @param pwFlags flag field where set bits indicate conditions the
|
||||
* generated password must meet
|
||||
* <table summary="bits of flag field">
|
||||
* <tr><td>Bit</td><td>Condition</td></tr>
|
||||
* <tr><td>0</td><td>include at least one number</td></tr>
|
||||
* <tr><td>1</td><td>include at least one uppercase letter</td></tr>
|
||||
* <tr><td>2</td><td>include at least one symbol</td></tr>
|
||||
* <tr><td>3</td><td>don't include ambiguous characters</td></tr>
|
||||
* <tr><td>5</td><td>include at least one lowercase letter</td></tr>
|
||||
</table> *
|
||||
* @return the generated password
|
||||
*/
|
||||
fun phonemes(size: Int, pwFlags: Int): String {
|
||||
var password: String
|
||||
var curSize: Int
|
||||
var i: Int
|
||||
var length: Int
|
||||
var flags: Int
|
||||
var featureFlags: Int
|
||||
var prev: Int
|
||||
var shouldBe: Int
|
||||
var first: Boolean
|
||||
var str: String
|
||||
var cha: Char
|
||||
|
||||
do {
|
||||
password = ""
|
||||
featureFlags = pwFlags
|
||||
curSize = 0
|
||||
prev = 0
|
||||
first = true
|
||||
|
||||
shouldBe = if (RandomNumberGenerator.number(2) == 1) VOWEL else CONSONANT
|
||||
|
||||
while (curSize < size) {
|
||||
i = RandomNumberGenerator.number(NUM_ELEMENTS)
|
||||
str = elements[i].str
|
||||
length = str.length
|
||||
flags = elements[i].flags
|
||||
// Filter on the basic type of the next Element
|
||||
if (flags and shouldBe == 0) {
|
||||
continue
|
||||
}
|
||||
// Handle the NOT_FIRST flag
|
||||
if (first && flags and NOT_FIRST > 0) {
|
||||
continue
|
||||
}
|
||||
// Don't allow VOWEL followed a Vowel/Diphthong pair
|
||||
if (prev and VOWEL > 0 && flags and VOWEL > 0 &&
|
||||
flags and DIPHTHONG > 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
// Don't allow us to overflow the buffer
|
||||
if (length > size - curSize) {
|
||||
continue
|
||||
}
|
||||
// OK, we found an Element which matches our criteria, let's do
|
||||
// it
|
||||
password += str
|
||||
|
||||
// Handle UPPERS
|
||||
if (pwFlags and PasswordGenerator.UPPERS > 0) {
|
||||
if ((pwFlags and PasswordGenerator.LOWERS == 0) ||
|
||||
(first || flags and CONSONANT > 0) && RandomNumberGenerator.number(10) < 2) {
|
||||
val index = password.length - length
|
||||
password = password.substring(0, index) + str.toUpperCase()
|
||||
featureFlags = featureFlags and PasswordGenerator.UPPERS.inv()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the AMBIGUOUS flag
|
||||
if (pwFlags and PasswordGenerator.AMBIGUOUS > 0) {
|
||||
for (ambiguous in PasswordGenerator.AMBIGUOUS_STR.toCharArray()) {
|
||||
if (password.contains(ambiguous.toString())) {
|
||||
password = password.substring(0, curSize)
|
||||
|
||||
// Still have upper letters
|
||||
if ((pwFlags and PasswordGenerator.UPPERS) > 0) {
|
||||
featureFlags = featureFlags or PasswordGenerator.UPPERS
|
||||
for (upper in PasswordGenerator.UPPERS_STR.toCharArray()) {
|
||||
if (password.contains(upper.toString())) {
|
||||
featureFlags = featureFlags and PasswordGenerator.UPPERS.inv()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (password.length == curSize)
|
||||
continue
|
||||
}
|
||||
|
||||
curSize += length
|
||||
|
||||
// Time to stop?
|
||||
if (curSize >= size)
|
||||
break
|
||||
|
||||
// Handle DIGITS
|
||||
if (pwFlags and PasswordGenerator.DIGITS > 0) {
|
||||
if (!first && RandomNumberGenerator.number(10) < 3) {
|
||||
var character: String
|
||||
do {
|
||||
cha = Character.forDigit(RandomNumberGenerator.number(10), 10)
|
||||
character = cha.toString()
|
||||
} while (pwFlags and PasswordGenerator.AMBIGUOUS > 0 &&
|
||||
PasswordGenerator.AMBIGUOUS_STR.contains(character))
|
||||
password += character
|
||||
curSize++
|
||||
|
||||
featureFlags = featureFlags and PasswordGenerator.DIGITS.inv()
|
||||
|
||||
first = true
|
||||
prev = 0
|
||||
shouldBe = if (RandomNumberGenerator.number(2) == 1) VOWEL else CONSONANT
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Handle SYMBOLS
|
||||
if (pwFlags and PasswordGenerator.SYMBOLS > 0) {
|
||||
if (!first && RandomNumberGenerator.number(10) < 2) {
|
||||
var character: String
|
||||
var num: Int
|
||||
do {
|
||||
num = RandomNumberGenerator.number(PasswordGenerator.SYMBOLS_STR.length)
|
||||
cha = PasswordGenerator.SYMBOLS_STR.toCharArray()[num]
|
||||
character = cha.toString()
|
||||
} while (pwFlags and PasswordGenerator.AMBIGUOUS > 0 &&
|
||||
PasswordGenerator.AMBIGUOUS_STR.contains(character))
|
||||
password += character
|
||||
curSize++
|
||||
|
||||
featureFlags = featureFlags and PasswordGenerator.SYMBOLS.inv()
|
||||
}
|
||||
}
|
||||
|
||||
// OK, figure out what the next Element should be
|
||||
shouldBe = if (shouldBe == CONSONANT) {
|
||||
VOWEL
|
||||
} else {
|
||||
if (prev and VOWEL > 0 || flags and DIPHTHONG > 0 ||
|
||||
RandomNumberGenerator.number(10) > 3
|
||||
) {
|
||||
CONSONANT
|
||||
} else {
|
||||
VOWEL
|
||||
}
|
||||
}
|
||||
prev = flags
|
||||
first = false
|
||||
}
|
||||
} while (featureFlags and (PasswordGenerator.UPPERS or PasswordGenerator.DIGITS or PasswordGenerator.SYMBOLS) > 0)
|
||||
return password
|
||||
}
|
||||
}
|
|
@ -4,27 +4,30 @@
|
|||
*/
|
||||
package com.zeapo.pwdstore.pwgen
|
||||
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
|
||||
internal object RandomNumberGenerator {
|
||||
private var random: SecureRandom
|
||||
private val secureRandom = SecureRandom()
|
||||
|
||||
init {
|
||||
try {
|
||||
random = SecureRandom.getInstance("SHA1PRNG")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw SecurityException("SHA1PRNG not available", e)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns a number between 0 (inclusive) and [exclusiveBound] (exclusive).
|
||||
*/
|
||||
fun secureRandomNumber(exclusiveBound: Int) = secureRandom.nextInt(exclusiveBound)
|
||||
|
||||
/**
|
||||
* Generate a random number n, where 0 <= n < maxNum.
|
||||
*
|
||||
* @param maxNum the bound on the random number to be returned
|
||||
* @return the generated random number
|
||||
*/
|
||||
fun number(maxNum: Int): Int {
|
||||
return random.nextInt(maxNum)
|
||||
}
|
||||
/**
|
||||
* Returns `true` and `false` with probablity 50% each.
|
||||
*/
|
||||
fun secureRandomBoolean() = secureRandom.nextBoolean()
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
fun <T> Array<T>.secureRandomElement() = this[secureRandomNumber(size)]
|
||||
fun <T> List<T>.secureRandomElement() = this[secureRandomNumber(size)]
|
||||
fun String.secureRandomCharacter() = this[secureRandomNumber(length)]
|
||||
|
|
|
@ -4,77 +4,43 @@
|
|||
*/
|
||||
package com.zeapo.pwdstore.pwgen
|
||||
|
||||
internal object RandomPasswordGenerator {
|
||||
import com.zeapo.pwdstore.utils.hasFlag
|
||||
|
||||
object RandomPasswordGenerator {
|
||||
|
||||
/**
|
||||
* Generates a completely random password.
|
||||
* Generates a random password of length [targetLength], taking the following flags in [pwFlags]
|
||||
* into account, or fails to do so and returns null:
|
||||
*
|
||||
* @param size length of password to generate
|
||||
* @param pwFlags flag field where set bits indicate conditions the
|
||||
* generated password must meet
|
||||
* <table summary ="bits of flag field">
|
||||
* <tr><td>Bit</td><td>Condition</td></tr>
|
||||
* <tr><td>0</td><td>include at least one number</td></tr>
|
||||
* <tr><td>1</td><td>include at least one uppercase letter</td></tr>
|
||||
* <tr><td>2</td><td>include at least one symbol</td></tr>
|
||||
* <tr><td>3</td><td>don't include ambiguous characters</td></tr>
|
||||
* <tr><td>4</td><td>don't include vowels</td></tr>
|
||||
* <tr><td>5</td><td>include at least one lowercase</td></tr>
|
||||
</table> *
|
||||
* @return the generated password
|
||||
* - [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.
|
||||
* - [PasswordGenerator.NO_VOWELS]: If set, the password will not contain any vowels.
|
||||
*/
|
||||
fun rand(size: Int, pwFlags: Int): String {
|
||||
var password: String
|
||||
var cha: Char
|
||||
var i: Int
|
||||
var featureFlags: Int
|
||||
var num: Int
|
||||
var character: String
|
||||
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 bank = ""
|
||||
if (pwFlags and PasswordGenerator.DIGITS > 0) {
|
||||
bank += PasswordGenerator.DIGITS_STR
|
||||
}
|
||||
if (pwFlags and PasswordGenerator.UPPERS > 0) {
|
||||
bank += PasswordGenerator.UPPERS_STR
|
||||
}
|
||||
if (pwFlags and PasswordGenerator.LOWERS > 0) {
|
||||
bank += PasswordGenerator.LOWERS_STR
|
||||
}
|
||||
if (pwFlags and PasswordGenerator.SYMBOLS > 0) {
|
||||
bank += PasswordGenerator.SYMBOLS_STR
|
||||
}
|
||||
do {
|
||||
password = ""
|
||||
featureFlags = pwFlags
|
||||
i = 0
|
||||
while (i < size) {
|
||||
num = RandomNumberGenerator.number(bank.length)
|
||||
cha = bank.toCharArray()[num]
|
||||
character = cha.toString()
|
||||
if (pwFlags and PasswordGenerator.AMBIGUOUS > 0 &&
|
||||
PasswordGenerator.AMBIGUOUS_STR.contains(character)) {
|
||||
continue
|
||||
}
|
||||
if (pwFlags and PasswordGenerator.NO_VOWELS > 0 && PasswordGenerator.VOWELS_STR.contains(character)) {
|
||||
continue
|
||||
}
|
||||
password += character
|
||||
i++
|
||||
if (PasswordGenerator.DIGITS_STR.contains(character)) {
|
||||
featureFlags = featureFlags and PasswordGenerator.DIGITS.inv()
|
||||
}
|
||||
if (PasswordGenerator.UPPERS_STR.contains(character)) {
|
||||
featureFlags = featureFlags and PasswordGenerator.UPPERS.inv()
|
||||
}
|
||||
if (PasswordGenerator.SYMBOLS_STR.contains(character)) {
|
||||
featureFlags = featureFlags and PasswordGenerator.SYMBOLS.inv()
|
||||
}
|
||||
if (PasswordGenerator.LOWERS_STR.contains(character)) {
|
||||
featureFlags = featureFlags and PasswordGenerator.LOWERS.inv()
|
||||
}
|
||||
var password = ""
|
||||
while (password.length < targetLength) {
|
||||
val candidate = bank.secureRandomCharacter()
|
||||
if (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
|
||||
candidate in PasswordGenerator.AMBIGUOUS_STR) {
|
||||
continue
|
||||
}
|
||||
} while (featureFlags and (PasswordGenerator.UPPERS or PasswordGenerator.DIGITS or PasswordGenerator.SYMBOLS or PasswordGenerator.LOWERS) > 0)
|
||||
return password
|
||||
password += candidate
|
||||
}
|
||||
return password.takeIf { PasswordGenerator.isValidPassword(it, pwFlags) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.pwgen
|
||||
|
||||
import com.zeapo.pwdstore.utils.hasFlag
|
||||
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 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
return password.takeIf { PasswordGenerator.isValidPassword(it, pwFlags) }
|
||||
}
|
||||
}
|
|
@ -6,11 +6,11 @@ package com.zeapo.pwdstore.pwgenxkpwd
|
|||
|
||||
import android.content.Context
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator.PasswordGeneratorExeption
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator.PasswordGeneratorException
|
||||
import com.zeapo.pwdstore.pwgen.secureRandomCharacter
|
||||
import com.zeapo.pwdstore.pwgen.secureRandomElement
|
||||
import com.zeapo.pwdstore.pwgen.secureRandomNumber
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.util.ArrayList
|
||||
import java.util.Locale
|
||||
|
||||
class PasswordBuilder(ctx: Context) {
|
||||
|
@ -67,29 +67,25 @@ class PasswordBuilder(ctx: Context) {
|
|||
}
|
||||
|
||||
private fun generateRandomNumberSequence(totalNumbers: Int): String {
|
||||
val secureRandom = SecureRandom()
|
||||
val numbers = StringBuilder(totalNumbers)
|
||||
|
||||
for (i in 0 until totalNumbers) {
|
||||
numbers.append(secureRandom.nextInt(10))
|
||||
numbers.append(secureRandomNumber(10))
|
||||
}
|
||||
return numbers.toString()
|
||||
}
|
||||
|
||||
private fun generateRandomSymbolSequence(numSymbols: Int): String {
|
||||
val secureRandom = SecureRandom()
|
||||
val numbers = StringBuilder(numSymbols)
|
||||
|
||||
for (i in 0 until numSymbols) {
|
||||
numbers.append(SYMBOLS[secureRandom.nextInt(SYMBOLS.length)])
|
||||
numbers.append(SYMBOLS.secureRandomCharacter())
|
||||
}
|
||||
return numbers.toString()
|
||||
}
|
||||
|
||||
@Throws(PasswordGenerator.PasswordGeneratorExeption::class)
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@Throws(PasswordGeneratorException::class)
|
||||
fun create(): String {
|
||||
val wordBank = ArrayList<String>()
|
||||
val secureRandom = SecureRandom()
|
||||
val wordBank = mutableListOf<String>()
|
||||
val password = StringBuilder()
|
||||
|
||||
if (prependDigits != 0) {
|
||||
|
@ -101,44 +97,30 @@ class PasswordBuilder(ctx: Context) {
|
|||
try {
|
||||
val dictionary = XkpwdDictionary(context)
|
||||
val words = dictionary.words
|
||||
for (wordLength in words.keys) {
|
||||
if (wordLength in minWordLength..maxWordLength) {
|
||||
wordBank.addAll(words[wordLength]!!)
|
||||
}
|
||||
for (wordLength in minWordLength..maxWordLength) {
|
||||
wordBank.addAll(words[wordLength] ?: emptyList())
|
||||
}
|
||||
|
||||
if (wordBank.size == 0) {
|
||||
throw PasswordGeneratorExeption(context.getString(R.string.xkpwgen_builder_error, minWordLength, maxWordLength))
|
||||
throw PasswordGeneratorException(context.getString(R.string.xkpwgen_builder_error, minWordLength, maxWordLength))
|
||||
}
|
||||
|
||||
for (i in 0 until numWords) {
|
||||
val randomIndex = secureRandom.nextInt(wordBank.size)
|
||||
var s = wordBank[randomIndex]
|
||||
|
||||
if (capsType != CapsType.As_iS) {
|
||||
s = s.toLowerCase(Locale.getDefault())
|
||||
when (capsType) {
|
||||
CapsType.UPPERCASE -> s = s.toUpperCase(Locale.getDefault())
|
||||
CapsType.Sentencecase -> {
|
||||
if (i == 0) {
|
||||
s = capitalize(s)
|
||||
}
|
||||
}
|
||||
CapsType.TitleCase -> {
|
||||
s = capitalize(s)
|
||||
}
|
||||
CapsType.lowercase, CapsType.As_iS -> {
|
||||
}
|
||||
}
|
||||
val candidate = wordBank.secureRandomElement()
|
||||
val s = when (capsType) {
|
||||
CapsType.UPPERCASE -> candidate.toUpperCase(Locale.getDefault())
|
||||
CapsType.Sentencecase -> if (i == 0) candidate.capitalize(Locale.getDefault()) else candidate
|
||||
CapsType.TitleCase -> candidate.capitalize(Locale.getDefault())
|
||||
CapsType.lowercase -> candidate.toLowerCase(Locale.getDefault())
|
||||
CapsType.As_iS -> candidate
|
||||
}
|
||||
password.append(s)
|
||||
wordBank.removeAt(randomIndex)
|
||||
if (i + 1 < numWords) {
|
||||
password.append(separator)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw PasswordGeneratorExeption("Failed generating password!")
|
||||
throw PasswordGeneratorException("Failed generating password!")
|
||||
}
|
||||
if (numDigits != 0) {
|
||||
if (isAppendNumberSeparator) {
|
||||
|
@ -155,13 +137,6 @@ class PasswordBuilder(ctx: Context) {
|
|||
return password.toString()
|
||||
}
|
||||
|
||||
private fun capitalize(s: String): String {
|
||||
var result = s
|
||||
val lower = result.toLowerCase(Locale.getDefault())
|
||||
result = lower.substring(0, 1).toUpperCase(Locale.getDefault()) + result.substring(1)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SYMBOLS = "!@\$%^&*-_+=:|~?/.;#"
|
||||
}
|
||||
|
|
|
@ -5,51 +5,29 @@
|
|||
package com.zeapo.pwdstore.pwgenxkpwd
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.zeapo.pwdstore.R
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
|
||||
class XkpwdDictionary(context: Context) {
|
||||
val words: HashMap<Int, ArrayList<String>> = HashMap()
|
||||
val words: Map<Int, List<String>>
|
||||
|
||||
init {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val uri = prefs.getString("pref_key_custom_dict", "")!!
|
||||
val customDictFile = File(context.filesDir, XKPWD_CUSTOM_DICT_FILE)
|
||||
|
||||
var lines: List<String> = listOf()
|
||||
|
||||
if (prefs.getBoolean("pref_key_is_custom_dict", false)) {
|
||||
|
||||
val uri = prefs.getString("pref_key_custom_dict", "")
|
||||
|
||||
if (!TextUtils.isEmpty(uri)) {
|
||||
val customDictFile = File(context.filesDir, XKPWD_CUSTOM_DICT_FILE)
|
||||
|
||||
if (customDictFile.exists() && customDictFile.canRead()) {
|
||||
lines = customDictFile.inputStream().bufferedReader().readLines()
|
||||
}
|
||||
}
|
||||
val lines = if (prefs.getBoolean("pref_key_is_custom_dict", false) &&
|
||||
uri.isNotEmpty() && customDictFile.canRead()) {
|
||||
customDictFile.readLines()
|
||||
} else {
|
||||
context.resources.openRawResource(R.raw.xkpwdict).bufferedReader().readLines()
|
||||
}
|
||||
|
||||
if (lines.isEmpty()) {
|
||||
lines = context.resources.openRawResource(R.raw.xkpwdict).bufferedReader().readLines()
|
||||
}
|
||||
|
||||
for (word in lines) {
|
||||
if (!word.trim { it <= ' ' }.contains(" ")) {
|
||||
val length = word.trim { it <= ' ' }.length
|
||||
|
||||
if (length > 0) {
|
||||
if (!words.containsKey(length)) {
|
||||
words[length] = ArrayList()
|
||||
}
|
||||
val strings = words[length]!!
|
||||
strings.add(word.trim { it <= ' ' })
|
||||
}
|
||||
}
|
||||
}
|
||||
words = lines.asSequence()
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() && !it.contains(' ') }
|
||||
.groupBy { it.length }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
*/
|
||||
package com.zeapo.pwdstore.ui.dialogs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
|
@ -11,94 +13,87 @@ import android.os.Bundle
|
|||
import android.widget.CheckBox
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator.PasswordGeneratorExeption
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator.PasswordGeneratorException
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator.generate
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator.setPrefs
|
||||
import com.zeapo.pwdstore.pwgen.PasswordOption
|
||||
|
||||
/** A placeholder fragment containing a simple view. */
|
||||
class PasswordGeneratorDialogFragment : DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
val callingActivity = requireActivity()
|
||||
val inflater = callingActivity.layoutInflater
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
val view = inflater.inflate(R.layout.fragment_pwgen, null)
|
||||
val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf")
|
||||
builder.setView(view)
|
||||
val prefs = requireActivity().applicationContext
|
||||
.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
|
||||
|
||||
view.findViewById<CheckBox>(R.id.numerals)?.isChecked = !prefs.getBoolean("0", false)
|
||||
view.findViewById<CheckBox>(R.id.symbols)?.isChecked = prefs.getBoolean("y", false)
|
||||
view.findViewById<CheckBox>(R.id.uppercase)?.isChecked = !prefs.getBoolean("A", false)
|
||||
view.findViewById<CheckBox>(R.id.lowercase)?.isChecked = !prefs.getBoolean("L", false)
|
||||
view.findViewById<CheckBox>(R.id.ambiguous)?.isChecked = !prefs.getBoolean("B", false)
|
||||
view.findViewById<CheckBox>(R.id.pronounceable)?.isChecked = !prefs.getBoolean("s", true)
|
||||
view.findViewById<CheckBox>(R.id.numerals)?.isChecked = !prefs.getBoolean(PasswordOption.NoDigits.key, false)
|
||||
view.findViewById<CheckBox>(R.id.symbols)?.isChecked = prefs.getBoolean(PasswordOption.AtLeastOneSymbol.key, false)
|
||||
view.findViewById<CheckBox>(R.id.uppercase)?.isChecked = !prefs.getBoolean(PasswordOption.NoUppercaseLetters.key, false)
|
||||
view.findViewById<CheckBox>(R.id.lowercase)?.isChecked = !prefs.getBoolean(PasswordOption.NoLowercaseLetters.key, false)
|
||||
view.findViewById<CheckBox>(R.id.ambiguous)?.isChecked = !prefs.getBoolean(PasswordOption.NoAmbiguousCharacters.key, false)
|
||||
view.findViewById<CheckBox>(R.id.pronounceable)?.isChecked = !prefs.getBoolean(PasswordOption.FullyRandom.key, true)
|
||||
|
||||
val textView: AppCompatEditText = view.findViewById(R.id.lengthNumber)
|
||||
textView.setText(prefs.getInt("length", 20).toString())
|
||||
val passwordText: AppCompatTextView = view.findViewById(R.id.passwordText)
|
||||
passwordText.typeface = monoTypeface
|
||||
builder.setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ ->
|
||||
val edit = callingActivity.findViewById<EditText>(R.id.password)
|
||||
edit.setText(passwordText.text)
|
||||
}
|
||||
builder.setNeutralButton(resources.getString(R.string.dialog_cancel)) { _, _ -> }
|
||||
builder.setNegativeButton(resources.getString(R.string.pwgen_generate), null)
|
||||
val dialog = builder.setTitle(this.resources.getString(R.string.pwgen_title)).create()
|
||||
dialog.setOnShowListener {
|
||||
setPreferences()
|
||||
try {
|
||||
passwordText.text = generate(requireActivity().applicationContext)[0]
|
||||
} catch (e: PasswordGeneratorExeption) {
|
||||
Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
|
||||
passwordText.text = ""
|
||||
return MaterialAlertDialogBuilder(requireContext()).run {
|
||||
setTitle(R.string.pwgen_title)
|
||||
setView(view)
|
||||
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
||||
val edit = callingActivity.findViewById<EditText>(R.id.password)
|
||||
edit.setText(passwordText.text)
|
||||
}
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
|
||||
setPreferences()
|
||||
try {
|
||||
passwordText.text = generate(callingActivity.applicationContext)[0]
|
||||
} catch (e: PasswordGeneratorExeption) {
|
||||
Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
|
||||
passwordText.text = ""
|
||||
setNeutralButton(R.string.dialog_cancel) { _, _ -> }
|
||||
setNegativeButton(R.string.pwgen_generate, null)
|
||||
create()
|
||||
}.apply {
|
||||
setOnShowListener {
|
||||
generate(passwordText)
|
||||
getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
|
||||
generate(passwordText)
|
||||
}
|
||||
}
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun generate(passwordField: AppCompatTextView) {
|
||||
setPreferences()
|
||||
try {
|
||||
passwordField.text = generate(requireContext().applicationContext)
|
||||
} catch (e: PasswordGeneratorException) {
|
||||
Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
|
||||
passwordField.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun isChecked(@IdRes id: Int): Boolean {
|
||||
return requireDialog().findViewById<CheckBox>(id).isChecked
|
||||
}
|
||||
|
||||
private fun setPreferences() {
|
||||
val preferences = ArrayList<String>()
|
||||
if (!(dialog!!.findViewById<CheckBox>(R.id.numerals)).isChecked) {
|
||||
preferences.add("0")
|
||||
}
|
||||
if ((dialog!!.findViewById<CheckBox>(R.id.symbols)).isChecked) {
|
||||
preferences.add("y")
|
||||
}
|
||||
if (!(dialog!!.findViewById<CheckBox>(R.id.uppercase)).isChecked) {
|
||||
preferences.add("A")
|
||||
}
|
||||
if (!(dialog!!.findViewById<CheckBox>(R.id.ambiguous)).isChecked) {
|
||||
preferences.add("B")
|
||||
}
|
||||
if (!(dialog!!.findViewById<CheckBox>(R.id.pronounceable)).isChecked) {
|
||||
preferences.add("s")
|
||||
}
|
||||
if (!(dialog!!.findViewById<CheckBox>(R.id.lowercase)).isChecked) {
|
||||
preferences.add("L")
|
||||
}
|
||||
val editText = dialog!!.findViewById<EditText>(R.id.lengthNumber)
|
||||
try {
|
||||
val length = Integer.valueOf(editText.text.toString())
|
||||
setPrefs(requireActivity().applicationContext, preferences, length)
|
||||
} catch (e: NumberFormatException) {
|
||||
setPrefs(requireActivity().applicationContext, preferences)
|
||||
}
|
||||
val preferences = listOfNotNull(
|
||||
PasswordOption.NoDigits.takeIf { !isChecked(R.id.numerals) },
|
||||
PasswordOption.AtLeastOneSymbol.takeIf { isChecked(R.id.symbols) },
|
||||
PasswordOption.NoUppercaseLetters.takeIf { !isChecked(R.id.uppercase) },
|
||||
PasswordOption.NoAmbiguousCharacters.takeIf { !isChecked(R.id.ambiguous) },
|
||||
PasswordOption.FullyRandom.takeIf { !isChecked(R.id.pronounceable) },
|
||||
PasswordOption.NoLowercaseLetters.takeIf { !isChecked(R.id.lowercase) }
|
||||
)
|
||||
val lengthText = requireDialog().findViewById<EditText>(R.id.lengthNumber).text.toString()
|
||||
val length = lengthText.toIntOrNull()?.takeIf { it >= 0 }
|
||||
?: PasswordGenerator.DEFAULT_LENGTH
|
||||
setPrefs(requireActivity().applicationContext, preferences, length)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ class XkPasswordGeneratorDialogFragment : DialogFragment() {
|
|||
.appendNumbers(if (cbNumbers.isChecked) Integer.parseInt(spinnerNumbersCount.selectedItem as String) else 0)
|
||||
.appendSymbols(if (cbSymbols.isChecked) Integer.parseInt(spinnerSymbolsCount.selectedItem as String) else 0)
|
||||
.setCapitalization(CapsType.valueOf(spinnerCapsType.selectedItem.toString())).create()
|
||||
} catch (e: PasswordGenerator.PasswordGeneratorExeption) {
|
||||
} catch (e: PasswordGenerator.PasswordGeneratorException) {
|
||||
Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
|
||||
tag("xkpw").e(e, "failure generating xkpasswd")
|
||||
passwordText.text = FALLBACK_ERROR_PASS
|
||||
|
|
|
@ -29,6 +29,10 @@ import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirect
|
|||
import org.eclipse.jgit.api.Git
|
||||
import java.io.File
|
||||
|
||||
fun Int.clearFlag(flag: Int): Int {
|
||||
return this and flag.inv()
|
||||
}
|
||||
|
||||
infix fun Int.hasFlag(flag: Int): Boolean {
|
||||
return this and flag == flag
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@
|
|||
<string name="pwgen_pronounceable">Pronounceable</string>
|
||||
<string name="pwgen_no_chars_error">No characters included</string>
|
||||
<string name="pwgen_length_too_short_error">Length too short for selected criteria</string>
|
||||
<string name="pwgen_max_iterations_exceeded">Failed to generate a password satisfying the constraints. Try to increase the length.</string>
|
||||
|
||||
<!-- XKPWD password generator -->
|
||||
<string name="xkpwgen_title">Xkpasswd Generator</string>
|
||||
|
|
Loading…
Reference in a new issue