Skip to content

Commit 908107e

Browse files
committed
diceware: capitalise words and embed numeral options added
1 parent c1e2044 commit 908107e

7 files changed

Lines changed: 83 additions & 6 deletions

File tree

app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ import android.content.Context
1111
import android.content.SharedPreferences
1212
import android.graphics.Typeface
1313
import android.os.Bundle
14+
import android.widget.CheckBox
15+
import androidx.annotation.IdRes
1416
import androidx.core.content.edit
1517
import androidx.fragment.app.DialogFragment
1618
import androidx.fragment.app.setFragmentResult
1719
import androidx.lifecycle.lifecycleScope
1820
import app.passwordstore.R
1921
import app.passwordstore.databinding.FragmentPwgenDicewareBinding
2022
import app.passwordstore.passgen.diceware.DicewarePassphraseGenerator
23+
import app.passwordstore.passgen.diceware.PasswordOption
2124
import app.passwordstore.ui.crypto.PasswordCreationActivity
2225
import app.passwordstore.util.crypto.AESEncryption
2326
import app.passwordstore.util.crypto.AESEncryption.KeyType
@@ -30,6 +33,7 @@ import javax.inject.Inject
3033
import kotlinx.coroutines.flow.merge
3134
import kotlinx.coroutines.launch
3235
import reactivecircus.flowbinding.android.widget.afterTextChanges
36+
import reactivecircus.flowbinding.android.widget.checkedChanges
3337

3438
@AndroidEntryPoint
3539
class DicewarePasswordGeneratorDialogFragment : DialogFragment() {
@@ -60,8 +64,13 @@ class DicewarePasswordGeneratorDialogFragment : DialogFragment() {
6064
)
6165
binding.passwordText.typeface = Typeface.MONOSPACE
6266

67+
binding.includeNumeral.isChecked = prefs.getBoolean(PasswordOption.Numeral.key, false)
68+
binding.capitalise.isChecked = prefs.getBoolean(PasswordOption.Capitalise.key, false)
69+
6370
lifecycleScope.launch {
6471
merge(
72+
binding.includeNumeral.checkedChanges().skipInitialValue(),
73+
binding.capitalise.checkedChanges().skipInitialValue(),
6574
binding.passwordLengthText.afterTextChanges(),
6675
binding.passwordSeparatorText.afterTextChanges(),
6776
)
@@ -93,11 +102,24 @@ class DicewarePasswordGeneratorDialogFragment : DialogFragment() {
93102
private fun generatePassword(binding: FragmentPwgenDicewareBinding) {
94103
val length = binding.passwordLengthText.text?.toString()?.toIntOrNull() ?: 5
95104
val separator = binding.passwordSeparatorText.text?.toString()?.getOrNull(0) ?: '-'
96-
setPreferences(length, separator)
97-
binding.passwordText.text = dicewareGenerator.generatePassphrase(length, separator)
105+
val includeNumeral = binding.includeNumeral.isChecked
106+
val capitalise = binding.capitalise.isChecked
107+
108+
setPreferences(length, separator, includeNumeral, capitalise)
109+
binding.passwordText.text =
110+
dicewareGenerator.generatePassphrase(length, separator, includeNumeral, capitalise)
111+
}
112+
113+
private fun isChecked(@IdRes id: Int): Boolean {
114+
return requireDialog().findViewById<CheckBox>(id).isChecked
98115
}
99116

100-
private fun setPreferences(length: Int, separator: Char) {
117+
private fun setPreferences(
118+
length: Int,
119+
separator: Char,
120+
includeNumeral: Boolean,
121+
capitalise: Boolean,
122+
) {
101123
prefs.edit {
102124
putString(
103125
DICEWARE_LENGTH,
@@ -111,6 +133,8 @@ class DicewarePasswordGeneratorDialogFragment : DialogFragment() {
111133
String(it)
112134
},
113135
)
136+
putBoolean(PasswordOption.Numeral.key, includeNumeral)
137+
putBoolean(PasswordOption.Capitalise.key, capitalise)
114138
}
115139
}
116140
}

app/src/main/res/layout/fragment_pwgen_diceware.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,21 @@
7070
android:maxLength="1" />
7171
</com.google.android.material.textfield.TextInputLayout>
7272

73+
<com.google.android.material.checkbox.MaterialCheckBox
74+
android:id="@+id/capitalise"
75+
android:layout_width="wrap_content"
76+
android:layout_height="wrap_content"
77+
android:layout_marginTop="8dp"
78+
android:text="@string/pwgen_capitalise"
79+
app:layout_constraintStart_toStartOf="parent"
80+
app:layout_constraintTop_toBottomOf="@id/password_separator" />
81+
82+
<com.google.android.material.checkbox.MaterialCheckBox
83+
android:id="@+id/include_numeral"
84+
android:layout_width="wrap_content"
85+
android:layout_height="wrap_content"
86+
android:text="@string/pwgen_include_numeral"
87+
app:layout_constraintStart_toStartOf="parent"
88+
app:layout_constraintTop_toBottomOf="@id/capitalise" />
89+
7390
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/values-de/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@
170170
<string name="pwgen_length_too_short_error">Länge zu kurz für die gewählten Kriterien.</string>
171171
<string name="pwgen_max_iterations_exceeded">Fehler beim Erstellen eines Passworts, das den Anforderungen genügt. Versuchen Sie es mit einem längeren Passwort.</string>
172172
<string name="pwgen_separator">Trennzeichen</string>
173+
<string name="pwgen_capitalise">Großschreibung</string>
174+
<string name="pwgen_include_numeral">Ziffer einbetten</string>
173175

174176
<!-- Password generator prefs -->
175177
<string name="pref_password_generator_type_title">Passwortgenerator</string>

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@
171171
<string name="pwgen_length_too_short_error">Length too short for selected criteria</string>
172172
<string name="pwgen_max_iterations_exceeded">Failed to generate a password satisfying the constraints. Try to increase the length.</string>
173173
<string name="pwgen_separator">Separator</string>
174+
<string name="pwgen_capitalise">Capitalise</string>
175+
<string name="pwgen_include_numeral">Include numeral</string>
174176

175177
<!-- Password generator prefs -->
176178
<string name="pref_password_generator_type_title">Password generator type</string>

passgen/diceware/src/main/kotlin/app/passwordstore/passgen/diceware/DicewarePassphraseGenerator.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package app.passwordstore.passgen.diceware
77

88
import java.io.InputStream
9+
import java.security.SecureRandom
10+
import java.util.Locale
911
import javax.inject.Inject
1012

1113
/**
@@ -17,12 +19,28 @@ public class DicewarePassphraseGenerator
1719
constructor(private val die: Die, wordList: InputStream) {
1820

1921
private val wordMap = WordListParser.parse(wordList)
22+
private val random = SecureRandom()
2023

2124
/** Generates a passphrase with [wordCount] words. */
22-
public fun generatePassphrase(wordCount: Int, separator: Char): String {
25+
public fun generatePassphrase(
26+
wordCount: Int,
27+
separator: Char,
28+
includeNumeral: Boolean,
29+
capitalise: Boolean,
30+
): String {
2331
return buildString {
32+
val numeralPos = random.nextInt(wordCount)
2433
repeat(wordCount) { idx ->
25-
append(wordMap[die.rollMultiple(DIGITS)])
34+
append(
35+
wordMap[die.rollMultiple(DIGITS)]?.let {
36+
if (capitalise)
37+
it.replaceFirstChar {
38+
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
39+
}
40+
else it
41+
} ?: throw NullPointerException()
42+
)
43+
if (idx == numeralPos && includeNumeral) append("${random.nextInt(10)}")
2644
if (idx < wordCount - 1) append(separator)
2745
}
2846
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright © 2014-2026 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
6+
package app.passwordstore.passgen.diceware
7+
8+
public enum class PasswordOption(public val key: String) {
9+
Numeral("N"),
10+
Capitalise("C"),
11+
}

passgen/diceware/src/test/kotlin/app/passwordstore/passgen/diceware/DicewarePassphraseGeneratorTest.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class DicewarePassphraseGeneratorTest {
2121

2222
val generator = DicewarePassphraseGenerator(die, WordListParserTest.getDefaultWordList())
2323

24-
assertEquals("salvation_cozily_croon_trustee_fidgety", generator.generatePassphrase(5, '_'))
24+
assertEquals(
25+
"Salvation_Cozily_Croon_Trustee_Fidgety",
26+
generator.generatePassphrase(5, '_', false, true),
27+
)
2528
}
2629
}

0 commit comments

Comments
 (0)