include some more options in the password generator (extended special group, at least one from every group, exclude look-alike), closes some items of https://github.com/PhilippC/keepass2android/issues/81

This commit is contained in:
Philipp Crocoll
2021-05-17 11:56:16 +02:00
parent edc650a66e
commit 212418f11a
4 changed files with 304 additions and 107 deletions

View File

@@ -24,14 +24,43 @@ using Android.OS;
using Android.Preferences;
using Android.Views;
using Android.Widget;
using Newtonsoft.Json;
namespace keepass2android
{
[Activity(Label = "@string/app_name", Theme = "@style/MyTheme_ActionBar", WindowSoftInputMode = SoftInput.StateHidden, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden)]
public class GeneratePasswordActivity : LockCloseActivity {
private readonly int[] _buttonIds = new[] {Resource.Id.btn_length6, Resource.Id.btn_length8, Resource.Id.btn_length12, Resource.Id.btn_length16};
public class GeneratePasswordActivity :
#if DEBUG
LifecycleAwareActivity
#else
LockCloseActivity
#endif
private ActivityDesign _design;
{
private readonly int[] _buttonLengthButtonIds = new[] {Resource.Id.btn_length6,
Resource.Id.btn_length8,
Resource.Id.btn_length12,
Resource.Id.btn_length16,
Resource.Id.btn_length24,
Resource.Id.btn_length32};
private readonly int[] _checkboxIds = new[] {Resource.Id.cb_uppercase,
Resource.Id.cb_lowercase,
Resource.Id.cb_digits,
Resource.Id.cb_minus,
Resource.Id.cb_underline,
Resource.Id.cb_space,
Resource.Id.cb_specials,
Resource.Id.cb_specials_extended,
Resource.Id.cb_brackets,
Resource.Id.cb_at_least_one_from_each_group,
Resource.Id.cb_exclude_lookalike
};
PasswordFont _passwordFont = new PasswordFont();
private ActivityDesign _design;
public GeneratePasswordActivity()
{
_design = new ActivityDesign(this);
@@ -47,7 +76,10 @@ namespace keepass2android
{
Intent i = new Intent(act, typeof(GeneratePasswordActivity));
#if DEBUG
#else
i.PutExtra(NoLockCheck, true);
#endif
act.StartActivityForResult(i, 0);
}
@@ -61,17 +93,54 @@ namespace keepass2android
SetResult(KeePass.ExitNormal);
var prefs = GetPreferences(FileCreationMode.Private);
((CheckBox) FindViewById(Resource.Id.cb_uppercase)).Checked = prefs.GetBoolean("cb_uppercase", true);
((CheckBox)FindViewById(Resource.Id.cb_lowercase)).Checked = prefs.GetBoolean("cb_lowercase", true);
((CheckBox)FindViewById(Resource.Id.cb_digits)).Checked = prefs.GetBoolean("cb_digits", true);
((CheckBox)FindViewById(Resource.Id.cb_minus)).Checked = prefs.GetBoolean("cb_minus", false);
((CheckBox)FindViewById(Resource.Id.cb_underline)).Checked = prefs.GetBoolean("cb_underline", false);
((CheckBox)FindViewById(Resource.Id.cb_space)).Checked = prefs.GetBoolean("cb_space", false);
((CheckBox)FindViewById(Resource.Id.cb_specials)).Checked = prefs.GetBoolean("cb_specials", false);
((CheckBox)FindViewById(Resource.Id.cb_brackets)).Checked = prefs.GetBoolean("cb_brackets", false);
((EditText)FindViewById(Resource.Id.length)).Text = prefs.GetInt("length", 12).ToString(CultureInfo.InvariantCulture);
PasswordGenerator.PasswordGenerationOptions options = null;
string jsonOptions = prefs.GetString("password_generator_options", null);
if (jsonOptions != null)
{
try
{
options = JsonConvert.DeserializeObject<PasswordGenerator.PasswordGenerationOptions>(jsonOptions);
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
}
else
{
options = new PasswordGenerator.PasswordGenerationOptions()
{
Length = prefs.GetInt("length", 12),
UpperCase = prefs.GetBoolean("cb_uppercase", true),
LowerCase = prefs.GetBoolean("cb_lowercase", true),
Digits = prefs.GetBoolean("cb_digits", true),
Minus = prefs.GetBoolean("cb_minus", false),
Underline = prefs.GetBoolean("cb_underline", false),
Space = prefs.GetBoolean("cb_space", false),
Specials = prefs.GetBoolean("cb_specials", false),
SpecialsExtended = false,
Brackets = prefs.GetBoolean("cb_brackets", false)
};
}
((CheckBox)FindViewById(Resource.Id.cb_uppercase)).Checked = options.UpperCase;
((CheckBox)FindViewById(Resource.Id.cb_lowercase)).Checked = options.LowerCase;
((CheckBox)FindViewById(Resource.Id.cb_digits)).Checked = options.Digits;
((CheckBox)FindViewById(Resource.Id.cb_minus)).Checked = options.Minus;
((CheckBox)FindViewById(Resource.Id.cb_underline)).Checked = options.Underline;
((CheckBox)FindViewById(Resource.Id.cb_space)).Checked = options.Space;
((CheckBox)FindViewById(Resource.Id.cb_specials)).Checked = options.Specials;
((CheckBox)FindViewById(Resource.Id.cb_specials_extended)).Checked = options.SpecialsExtended;
((CheckBox)FindViewById(Resource.Id.cb_brackets)).Checked = options.Brackets;
((CheckBox)FindViewById(Resource.Id.cb_exclude_lookalike)).Checked = options.ExcludeLookAlike;
((CheckBox)FindViewById(Resource.Id.cb_at_least_one_from_each_group)).Checked = options.AtLeastOneFromEachGroup;
((EditText)FindViewById(Resource.Id.length)).Text = options.Length.ToString(CultureInfo.InvariantCulture);
foreach (int id in _buttonIds) {
foreach (int id in _buttonLengthButtonIds) {
Button button = (Button) FindViewById(id);
button.Click += (sender, e) =>
{
@@ -79,17 +148,18 @@ namespace keepass2android
EditText editText = (EditText) FindViewById(Resource.Id.length);
editText.Text = b.Text;
UpdatePassword();
};
}
Button genPassButton = (Button) FindViewById(Resource.Id.generate_password_button);
genPassButton.Click += (sender, e) => {
String password = GeneratePassword();
EditText txtPassword = (EditText) FindViewById(Resource.Id.password_edit);
txtPassword.Text = password;
};
foreach (int id in _checkboxIds)
{
FindViewById<CheckBox>(id).CheckedChange += (sender, args) => UpdatePassword();
}
Button genPassButton = (Button) FindViewById(Resource.Id.generate_password_button);
genPassButton.Click += (sender, e) => { UpdatePassword(); };
@@ -118,12 +188,22 @@ namespace keepass2android
EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit);
txtPasswordToSet.Text = GeneratePassword();
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
_passwordFont.ApplyTo(txtPasswordToSet);
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
SupportActionBar.SetHomeButtonEnabled(true);
}
public String GeneratePassword() {
private void UpdatePassword()
{
String password = GeneratePassword();
EditText txtPassword = (EditText) FindViewById(Resource.Id.password_edit);
txtPassword.Text = password;
}
public String GeneratePassword() {
String password = "";
try {
@@ -137,29 +217,15 @@ namespace keepass2android
PasswordGenerator generator = new PasswordGenerator(this);
password = generator.GeneratePassword(length,
((CheckBox) FindViewById(Resource.Id.cb_uppercase)).Checked,
((CheckBox) FindViewById(Resource.Id.cb_lowercase)).Checked,
((CheckBox) FindViewById(Resource.Id.cb_digits)).Checked,
((CheckBox) FindViewById(Resource.Id.cb_minus)).Checked,
((CheckBox) FindViewById(Resource.Id.cb_underline)).Checked,
((CheckBox) FindViewById(Resource.Id.cb_space)).Checked,
((CheckBox) FindViewById(Resource.Id.cb_specials)).Checked,
((CheckBox) FindViewById(Resource.Id.cb_brackets)).Checked);
var options = GetOptions(length);
password = generator.GeneratePassword(options);
var prefs = GetPreferences(FileCreationMode.Private);
prefs.Edit()
.PutBoolean("cb_uppercase", ((CheckBox) FindViewById(Resource.Id.cb_uppercase)).Checked)
.PutBoolean("cb_lowercase", ((CheckBox) FindViewById(Resource.Id.cb_lowercase)).Checked)
.PutBoolean("cb_digits", ((CheckBox) FindViewById(Resource.Id.cb_digits)).Checked)
.PutBoolean("cb_minus", ((CheckBox) FindViewById(Resource.Id.cb_minus)).Checked)
.PutBoolean("cb_underline", ((CheckBox) FindViewById(Resource.Id.cb_underline)).Checked)
.PutBoolean("cb_space", ((CheckBox) FindViewById(Resource.Id.cb_space)).Checked)
.PutBoolean("cb_specials", ((CheckBox) FindViewById(Resource.Id.cb_specials)).Checked)
.PutBoolean("cb_brackets", ((CheckBox) FindViewById(Resource.Id.cb_brackets)).Checked)
.PutInt("length", length)
.Commit();
.PutString("password_generator_options", JsonConvert.SerializeObject(options))
.Commit();
@@ -170,6 +236,26 @@ namespace keepass2android
return password;
}
private PasswordGenerator.PasswordGenerationOptions GetOptions(int length)
{
PasswordGenerator.PasswordGenerationOptions options = new PasswordGenerator.PasswordGenerationOptions()
{
Length = length,
UpperCase = ((CheckBox) FindViewById(Resource.Id.cb_uppercase)).Checked,
LowerCase = ((CheckBox) FindViewById(Resource.Id.cb_lowercase)).Checked,
Digits = ((CheckBox) FindViewById(Resource.Id.cb_digits)).Checked,
Minus = ((CheckBox) FindViewById(Resource.Id.cb_minus)).Checked,
Underline = ((CheckBox) FindViewById(Resource.Id.cb_underline)).Checked,
Space = ((CheckBox) FindViewById(Resource.Id.cb_space)).Checked,
Specials = ((CheckBox) FindViewById(Resource.Id.cb_specials)).Checked,
SpecialsExtended = ((CheckBox) FindViewById(Resource.Id.cb_specials_extended)).Checked,
Brackets = ((CheckBox) FindViewById(Resource.Id.cb_brackets)).Checked,
ExcludeLookAlike = ((CheckBox) FindViewById(Resource.Id.cb_exclude_lookalike)).Checked,
AtLeastOneFromEachGroup = ((CheckBox) FindViewById(Resource.Id.cb_at_least_one_from_each_group)).Checked
};
return options;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
@@ -181,7 +267,7 @@ namespace keepass2android
}
return false;
}
}
}
}

View File

@@ -65,39 +65,53 @@ android:background="?activityBackgroundColor"
android:layout_width="fill_parent"
android:layout_below="@id/generate_password_button" />
<Button
android:id="@+id/btn_length16"
android:text="16"
android:layout_alignParentRight="true"
android:layout_width="60sp"
android:layout_height="wrap_content"
android:layout_below="@id/length_label" />
android:id="@+id/btn_length32"
android:text="32"
android:layout_alignParentRight="true"
android:layout_width="50sp"
android:layout_height="wrap_content"
android:layout_below="@id/length_label" />
<Button
android:id="@+id/btn_length24"
android:text="24"
android:layout_toLeftOf="@id/btn_length32"
android:layout_height="wrap_content"
android:layout_width="50sp"
android:layout_alignTop="@id/btn_length32" />
<Button
android:id="@+id/btn_length16"
android:text="16"
android:layout_toLeftOf="@id/btn_length24"
android:layout_height="wrap_content"
android:layout_width="50sp"
android:layout_alignTop="@id/btn_length32" />
<Button
android:id="@+id/btn_length12"
android:text="12"
android:layout_toLeftOf="@id/btn_length16"
android:layout_height="wrap_content"
android:layout_width="60sp"
android:layout_alignTop="@id/btn_length16" />
android:layout_width="50sp"
android:layout_alignTop="@id/btn_length32" />
<Button
android:id="@+id/btn_length8"
android:text="8"
android:layout_toLeftOf="@id/btn_length12"
android:layout_height="wrap_content"
android:layout_width="60sp"
android:layout_alignTop="@id/btn_length16" />
android:layout_width="50sp"
android:layout_alignTop="@id/btn_length32" />
<Button
android:id="@+id/btn_length6"
android:text="6"
android:layout_toLeftOf="@id/btn_length8"
android:layout_height="wrap_content"
android:layout_width="60sp"
android:layout_alignTop="@id/btn_length16" />
android:layout_width="50sp"
android:layout_alignTop="@id/btn_length32" />
<EditText
android:id="@+id/length"
android:layout_width="fill_parent"
android:layout_toLeftOf="@id/btn_length6"
android:layout_height="wrap_content"
android:layout_alignTop="@id/btn_length16"
android:layout_alignTop="@id/btn_length32"
android:singleLine="true"
android:text="12"
android:hint="@string/hint_length" />
@@ -146,12 +160,31 @@ android:background="?activityBackgroundColor"
android:layout_height="wrap_content"
android:text="@string/special"
android:layout_below="@id/cb_space" />
<CheckBox
android:id="@+id/cb_specials_extended"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/special_extended"
android:layout_below="@id/cb_specials" />
<CheckBox
android:id="@+id/cb_brackets"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/brackets"
android:layout_below="@id/cb_specials" />
android:layout_below="@id/cb_specials_extended" />
<CheckBox
android:id="@+id/cb_exclude_lookalike"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/exclude_lookalike"
android:layout_below="@id/cb_brackets" />
<CheckBox
android:id="@+id/cb_at_least_one_from_each_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/at_least_one_from_each_group"
android:layout_below="@id/cb_exclude_lookalike" />
</RelativeLayout>
</ScrollView>
</RelativeLayout>

View File

@@ -263,6 +263,9 @@
<string name="special">Special</string>
<string name="special_extended">Extended Special</string>
<string name="at_least_one_from_each_group">At least one from each group</string>
<string name="exclude_lookalike">Exclude look-alike characters</string>
<string name="search_hint">Find what</string>
<string name="search_results">Search results</string>
<string name="search_in">Search in</string>

View File

@@ -16,6 +16,8 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Android.Content;
@@ -33,6 +35,7 @@ namespace keepass2android
private const String UnderlineChars = "_";
private const String SpaceChars = " ";
private const String SpecialChars = "!\"#$%&'*+,./:;=?@\\^`";
private const String ExtendedChars = "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
private const String BracketChars = "[]{}()<>";
private readonly Context _cxt;
@@ -47,7 +50,7 @@ namespace keepass2android
{
var data = new byte[sizeof(int)];
_rng.GetBytes(data);
return BitConverter.ToInt32(data, 0) & (int.MaxValue - 1);
return BitConverter.ToInt32(data, 0) & (Int32.MaxValue - 1);
}
public override int Next(int maxValue)
@@ -69,7 +72,7 @@ namespace keepass2android
var data = new byte[sizeof(uint)];
_rng.GetBytes(data);
var randUint = BitConverter.ToUInt32(data, 0);
return randUint / (uint.MaxValue + 1.0);
return randUint / (UInt32.MaxValue + 1.0);
}
public override void NextBytes(byte[] data)
@@ -81,64 +84,136 @@ namespace keepass2android
public PasswordGenerator(Context cxt) {
_cxt = cxt;
}
public String GeneratePassword(int length, bool upperCase, bool lowerCase, bool digits, bool minus, bool underline, bool space, bool specials, bool brackets) {
if (length <= 0)
public class PasswordGenerationOptions
{
public int Length { get; set; }
public bool UpperCase { get; set; }
public bool LowerCase { get; set; }
public bool Digits { get; set; }
public bool Minus { get; set; }
public bool Underline { get; set; }
public bool Space { get; set; }
public bool Specials { get; set; }
public bool SpecialsExtended { get; set; }
public bool Brackets { get; set; }
public bool ExcludeLookAlike { get; set; }
public bool AtLeastOneFromEachGroup { get; set; }
}
public String GeneratePassword(PasswordGenerationOptions options) {
if (options.Length <= 0)
throw new ArgumentException(_cxt.GetString(Resource.String.error_wrong_length));
if (!upperCase && !lowerCase && !digits && !minus && !underline && !space && !specials && !brackets)
throw new ArgumentException(_cxt.GetString(Resource.String.error_pass_gen_type));
String characterSet = GetCharacterSet(upperCase, lowerCase, digits, minus, underline, space, specials, brackets);
var groups = GetCharacterGroups(options);
String characterSet = GetCharacterSet(options, groups);
if (characterSet.Length == 0)
throw new ArgumentException(_cxt.GetString(Resource.String.error_pass_gen_type));
int size = characterSet.Length;
StringBuilder buffer = new StringBuilder();
Random random = new SecureRandom();
if (options.AtLeastOneFromEachGroup)
{
foreach (var g in groups)
{
if (g.Length > 0)
{
buffer.Append(g[random.Next(g.Length)]);
}
}
}
if (size > 0)
{
for (int i = 0; i < length; i++)
while (buffer.Length < options.Length)
{
char c = characterSet[random.Next(size)];
buffer.Append(c);
buffer.Append(characterSet[random.Next(size)]);
}
}
return buffer.ToString();
}
public String GetCharacterSet(bool upperCase, bool lowerCase, bool digits, bool minus, bool underline, bool space, bool specials, bool brackets) {
StringBuilder charSet = new StringBuilder();
if (upperCase)
charSet.Append(UpperCaseChars);
if (lowerCase)
charSet.Append(LowerCaseChars);
if (digits)
charSet.Append(DigitChars);
if (minus)
charSet.Append(MinusChars);
if (underline)
charSet.Append(UnderlineChars);
if (space)
charSet.Append(SpaceChars);
if (specials)
charSet.Append(SpecialChars);
if (brackets)
charSet.Append(BracketChars);
return charSet.ToString();
}
}
var password = buffer.ToString();
if (options.AtLeastOneFromEachGroup)
{
//shuffle
StringBuilder sb = new StringBuilder(password);
for (int i = (password.Length - 1); i >= 1; i--)
{
int j = random.Next(i + 1);
var tmp = sb[i];
sb[i] = sb[j];
sb[j] = tmp;
}
password = sb.ToString();
}
return password;
}
public string GetCharacterSet(PasswordGenerationOptions options, List<string> groups)
{
var characterSet = String.Join("", groups);
return characterSet;
}
private static List<string> GetCharacterGroups(PasswordGenerationOptions options)
{
List<string> groups = new List<string>();
if (options.UpperCase)
groups.Add(UpperCaseChars);
if (options.LowerCase)
groups.Add(LowerCaseChars);
if (options.Digits)
groups.Add(DigitChars);
if (options.Minus)
groups.Add(MinusChars);
if (options.Underline)
groups.Add(UnderlineChars);
if (options.Space)
groups.Add(SpaceChars);
if (options.Specials)
groups.Add(SpecialChars);
if (options.SpecialsExtended)
groups.Add(ExtendedChars);
if (options.Brackets)
groups.Add(BracketChars);
if (options.ExcludeLookAlike)
{
for (int i = 0; i < groups.Count; i++)
{
groups[i] = String.Join("", groups[i].Except("Il1|8B6GO0"));
}
}
return groups;
}
}
}