add passphrase, password generator profiles, password strength estimation, closes https://github.com/PhilippC/keepass2android/issues/81

This commit is contained in:
Philipp Crocoll
2021-05-18 08:42:47 +02:00
parent 212418f11a
commit 4636f5800c
6 changed files with 8621 additions and 198 deletions

View File

@@ -16,15 +16,22 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Android.App;
using Android.App.Admin;
using Android.Content;
using Android.Content.PM;
using Android.Graphics;
using Android.OS;
using Android.Preferences;
using Android.Views;
using Android.Widget;
using Java.Util;
using KeePassLib.Cryptography;
using Newtonsoft.Json;
using OtpKeyProv;
namespace keepass2android
{
@@ -84,6 +91,48 @@ namespace keepass2android
act.StartActivityForResult(i, 0);
}
private PasswordProfiles _profiles;
private bool _updateDisabled = false;
class PasswordProfiles
{
public List<KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>> Profiles { get; set; }
public PasswordGenerator.CombinedKeyOptions LastUsedSettings { get; set; }
public int? TryFindLastUsedProfileIndex()
{
for (int i=0;i<Profiles.Count;i++)
{
var kvp = Profiles[i];
if (kvp.Value.Equals(LastUsedSettings))
return i;
}
return null;
}
public void Add(string key, PasswordGenerator.CombinedKeyOptions options)
{
for (var index = 0; index < Profiles.Count; index++)
{
var kvp = Profiles[index];
if (kvp.Key == key)
{
Profiles[index] = new KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>(key, options);
return;
}
}
Profiles.Add(new KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>(key, options));
}
public void Remove(in int profileIndex)
{
Profiles.RemoveAt(profileIndex);
}
}
protected override void OnCreate(Bundle savedInstanceState) {
_design.ApplyTheme();
@@ -95,13 +144,13 @@ namespace keepass2android
var prefs = GetPreferences(FileCreationMode.Private);
PasswordGenerator.PasswordGenerationOptions options = null;
string jsonOptions = prefs.GetString("password_generator_options", null);
string jsonOptions = prefs.GetString("password_generator_profiles", null);
if (jsonOptions != null)
{
try
{
options = JsonConvert.DeserializeObject<PasswordGenerator.PasswordGenerationOptions>(jsonOptions);
_profiles = JsonConvert.DeserializeObject<PasswordProfiles>(jsonOptions);
}
catch (Exception e)
{
@@ -110,37 +159,57 @@ namespace keepass2android
}
else
{
options = new PasswordGenerator.PasswordGenerationOptions()
PasswordGenerator.CombinedKeyOptions options = new PasswordGenerator.CombinedKeyOptions()
{
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)
PasswordGenerationOptions = 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)
}
};
_profiles = new PasswordProfiles()
{
LastUsedSettings = options,
Profiles = GetDefaultProfiles()
};
}
}
((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;
_profiles ??= new PasswordProfiles();
_profiles.LastUsedSettings ??= new PasswordGenerator.CombinedKeyOptions()
{
PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions()
{Length = 7, UpperCase = true, LowerCase = true, Digits = true}
};
_profiles.Profiles ??= new List<KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>>();
((EditText)FindViewById(Resource.Id.length)).Text = options.Length.ToString(CultureInfo.InvariantCulture);
_updateDisabled = true;
PopulateFieldsFromOptions(_profiles.LastUsedSettings);
_updateDisabled = false;
foreach (int id in _buttonLengthButtonIds) {
var profileSpinner = UpdateProfileSpinner();
profileSpinner.ItemSelected += (sender, args) =>
{
if (profileSpinner.SelectedItemPosition > 0)
{
_profiles.LastUsedSettings = _profiles.Profiles[profileSpinner.SelectedItemPosition - 1].Value;
_updateDisabled = true;
PopulateFieldsFromOptions(_profiles.LastUsedSettings);
_updateDisabled = false;
UpdatePassword();
}
};
foreach (int id in _buttonLengthButtonIds) {
Button button = (Button) FindViewById(id);
button.Click += (sender, e) =>
{
@@ -148,16 +217,34 @@ namespace keepass2android
EditText editText = (EditText) FindViewById(Resource.Id.length);
editText.Text = b.Text;
UpdatePassword();
};
}
FindViewById<EditText>(Resource.Id.length).TextChanged += (sender, args) => UpdatePassword();
FindViewById<EditText>(Resource.Id.wordcount).TextChanged += (sender, args) => UpdatePassword();
FindViewById<EditText>(Resource.Id.wordseparator).TextChanged += (sender, args) => UpdatePassword();
foreach (int id in _checkboxIds)
{
FindViewById<CheckBox>(id).CheckedChange += (sender, args) => UpdatePassword();
}
var mode_spinner = FindViewById<Spinner>(Resource.Id.spinner_password_generator_mode);
mode_spinner.ItemSelected += (sender, args) =>
{
FindViewById(Resource.Id.passphraseOptions).Visibility =
mode_spinner.SelectedItemPosition == 0 ? ViewStates.Gone : ViewStates.Visible;
FindViewById(Resource.Id.passwordOptions).Visibility =
mode_spinner.SelectedItemPosition == 1 ? ViewStates.Gone : ViewStates.Visible;
UpdatePassword();
};
FindViewById<Spinner>(Resource.Id.spinner_password_generator_case_mode).ItemSelected += (sender, args) =>
{
UpdatePassword();
};
Button genPassButton = (Button) FindViewById(Resource.Id.generate_password_button);
genPassButton.Click += (sender, e) => { UpdatePassword(); };
@@ -184,75 +271,320 @@ namespace keepass2android
Finish();
};
FindViewById(Resource.Id.btn_password_generator_profile_save)
.Click += (sender, args) =>
{
var editText = new EditText(this);
new AlertDialog.Builder(this)
.SetMessage(Resource.String.save_password_generation_profile_text)
.SetView(editText)
.SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) =>
{
_profiles.Add(editText.Text, GetOptions());
UpdateProfileSpinner();
})
.Show();
};
EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit);
txtPasswordToSet.Text = GeneratePassword();
FindViewById(Resource.Id.btn_password_generator_profile_delete)
.Click += (sender, args) =>
{
if (profileSpinner.SelectedItemPosition > 0)
{
_profiles.Remove(profileSpinner.SelectedItemPosition-1);
UpdateProfileSpinner();
}
};
EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit);
_passwordFont.ApplyTo(txtPasswordToSet);
UpdatePassword();
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
SupportActionBar.SetHomeButtonEnabled(true);
}
private Spinner UpdateProfileSpinner()
{
string[] profileNames = new List<string> {GetString(Resource.String.custom_settings)}
.Concat(_profiles.Profiles.Select(p => p.Key))
.ToArray();
ArrayAdapter<String> profileArrayAdapter = new ArrayAdapter<String>(this,
Android.Resource.Layout.SimpleSpinnerDropDownItem,
profileNames);
var profileSpinner = FindViewById<Spinner>(Resource.Id.spinner_password_generator_profile);
profileSpinner.Adapter = profileArrayAdapter;
UpdateProfileSpinnerSelection();
return profileSpinner;
}
private static List<KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>> GetDefaultProfiles()
{
return new List<KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>>()
{
new KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>(
"Simple12", new PasswordGenerator.CombinedKeyOptions()
{
PasswordGenerationOptions
= new PasswordGenerator.PasswordGenerationOptions()
{
Length = 12, AtLeastOneFromEachGroup = true, ExcludeLookAlike = true,
Digits = true, LowerCase = true, UpperCase = true
}
}
),
new KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>(
"Special12", new PasswordGenerator.CombinedKeyOptions()
{
PasswordGenerationOptions
= new PasswordGenerator.PasswordGenerationOptions()
{
Length = 12, AtLeastOneFromEachGroup = true, ExcludeLookAlike = true,
Digits = true, LowerCase = true, UpperCase = true,Specials = true,Brackets = true
}
}
),
new KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>(
"Password64", new PasswordGenerator.CombinedKeyOptions()
{
PasswordGenerationOptions
= new PasswordGenerator.PasswordGenerationOptions()
{
Length = 64, AtLeastOneFromEachGroup = true,
Digits = true, LowerCase = true, UpperCase = true, ExcludeLookAlike = false,Specials = true,Brackets = true, Minus = true, Space = true, SpecialsExtended = true,Underline = true
}
}
),
new KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>(
"Passphrase7", new PasswordGenerator.CombinedKeyOptions()
{
PassphraseGenerationOptions
= new PasswordGenerator.PassphraseGenerationOptions()
{
WordCount = 7,
CaseMode = PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode.Lowercase,
Separator = " "
}
}
),
new KeyValuePair<string, PasswordGenerator.CombinedKeyOptions>(
"Passphrase5Plus", new PasswordGenerator.CombinedKeyOptions()
{
PassphraseGenerationOptions
= new PasswordGenerator.PassphraseGenerationOptions()
{
WordCount = 5,
CaseMode = PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode.PascalCase,
Separator = " "
},
PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions()
{
Length = 2,
AtLeastOneFromEachGroup = true,
ExcludeLookAlike = true,
Digits = true,
Specials = true
}
}
)
};
}
private void PopulateFieldsFromOptions(PasswordGenerator.CombinedKeyOptions combinedOptions)
{
PasswordGenerator.PasswordGenerationOptions passwordOptions = combinedOptions.PasswordGenerationOptions;
if (passwordOptions != null)
{
((CheckBox)FindViewById(Resource.Id.cb_uppercase)).Checked = passwordOptions.UpperCase;
((CheckBox)FindViewById(Resource.Id.cb_lowercase)).Checked = passwordOptions.LowerCase;
((CheckBox)FindViewById(Resource.Id.cb_digits)).Checked = passwordOptions.Digits;
((CheckBox)FindViewById(Resource.Id.cb_minus)).Checked = passwordOptions.Minus;
((CheckBox)FindViewById(Resource.Id.cb_underline)).Checked = passwordOptions.Underline;
((CheckBox)FindViewById(Resource.Id.cb_space)).Checked = passwordOptions.Space;
((CheckBox)FindViewById(Resource.Id.cb_specials)).Checked = passwordOptions.Specials;
((CheckBox)FindViewById(Resource.Id.cb_specials_extended)).Checked = passwordOptions.SpecialsExtended;
((CheckBox)FindViewById(Resource.Id.cb_brackets)).Checked = passwordOptions.Brackets;
((CheckBox)FindViewById(Resource.Id.cb_exclude_lookalike)).Checked = passwordOptions.ExcludeLookAlike;
((CheckBox)FindViewById(Resource.Id.cb_at_least_one_from_each_group)).Checked = passwordOptions.AtLeastOneFromEachGroup;
((EditText)FindViewById(Resource.Id.length)).Text = passwordOptions.Length.ToString(CultureInfo.InvariantCulture);
FindViewById(Resource.Id.passwordOptions).Visibility = ViewStates.Visible;
}
else
{
FindViewById(Resource.Id.passwordOptions).Visibility = ViewStates.Gone;
}
var passphraseOptions = combinedOptions.PassphraseGenerationOptions;
if (passphraseOptions != null)
{
FindViewById<EditText>(Resource.Id.wordcount).Text = passphraseOptions.WordCount.ToString(CultureInfo.InvariantCulture);
FindViewById<EditText>(Resource.Id.wordseparator).Text = passphraseOptions.Separator;
FindViewById<Spinner>(Resource.Id.spinner_password_generator_case_mode)
.SetSelection((int)passphraseOptions.CaseMode);
FindViewById(Resource.Id.passphraseOptions).Visibility = ViewStates.Visible;
}
else
{
FindViewById(Resource.Id.passphraseOptions).Visibility = ViewStates.Gone;
}
int mode;
if (combinedOptions.PasswordGenerationOptions != null &&
combinedOptions.PassphraseGenerationOptions != null)
mode = 2;
else if (combinedOptions.PasswordGenerationOptions == null &&
combinedOptions.PassphraseGenerationOptions != null)
mode = 1;
else mode = 0;
FindViewById<Spinner>(Resource.Id.spinner_password_generator_mode)
.SetSelection(mode);
}
private void UpdatePassword()
{
if (_updateDisabled)
return;
String password = GeneratePassword();
EditText txtPassword = (EditText) FindViewById(Resource.Id.password_edit);
txtPassword.Text = password;
var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength);
var passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray());
progressBar.Progress = (int)passwordBits;
progressBar.Max = 128;
Color color = new Color(196, 63, 49);
if (passwordBits > 40)
{
color = new Color(219, 152, 55);
}
if (passwordBits > 64)
{
color = new Color(96, 138, 38);
}
if (passwordBits > 100)
{
color = new Color(31, 128, 31);
}
progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
UpdateProfileSpinnerSelection();
}
private void UpdateProfileSpinnerSelection()
{
int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex();
FindViewById<Spinner>(Resource.Id.spinner_password_generator_profile)
.SetSelection(lastUsedIndex != null ? lastUsedIndex.Value + 1 : 0);
}
public String GeneratePassword() {
String password = "";
try {
try
{
PasswordGenerator generator = new PasswordGenerator(this);
int length;
if (!int.TryParse(((EditText) FindViewById(Resource.Id.length)).Text, out length))
{
Toast.MakeText(this, Resource.String.error_wrong_length, ToastLength.Long).Show();
return password;
}
var options = GetOptions();
try
{
password = generator.GeneratePassword(options);
}
catch (Exception e)
{
throw new ArgumentException(GetString(Resource.String.error_pass_gen_type));
}
PasswordGenerator generator = new PasswordGenerator(this);
_profiles.LastUsedSettings = options;
var options = GetOptions(length);
password = generator.GeneratePassword(options);
var prefs = GetPreferences(FileCreationMode.Private);
prefs.Edit()
.PutString("password_generator_options", JsonConvert.SerializeObject(options))
.Commit();
} catch (ArgumentException e) {
SaveProfiles();
}
catch (Exception e)
{
Toast.MakeText(this, e.Message, ToastLength.Long).Show();
}
return password;
}
private PasswordGenerator.PasswordGenerationOptions GetOptions(int length)
private void SaveProfiles()
{
PasswordGenerator.PasswordGenerationOptions options = new PasswordGenerator.PasswordGenerationOptions()
var prefs = GetPreferences(FileCreationMode.Private);
prefs.Edit()
.PutString("password_generator_profiles", JsonConvert.SerializeObject(_profiles))
.Commit();
}
private PasswordGenerator.CombinedKeyOptions GetOptions()
{
PasswordGenerator.CombinedKeyOptions options = new PasswordGenerator.CombinedKeyOptions();
if (FindViewById(Resource.Id.passphraseOptions).Visibility == ViewStates.Visible)
{
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
};
int wordCount;
if (!int.TryParse(((EditText)FindViewById(Resource.Id.wordcount)).Text, out wordCount))
{
throw new Exception(GetString(Resource.String.error_wrong_length));
}
options.PassphraseGenerationOptions =
new PasswordGenerator.PassphraseGenerationOptions()
{
WordCount = wordCount,
Separator = FindViewById<EditText>(Resource.Id.wordseparator).Text,
CaseMode = (PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode)FindViewById<Spinner>(Resource.Id.spinner_password_generator_case_mode).SelectedItemPosition
};
}
if (FindViewById(Resource.Id.passwordOptions).Visibility == ViewStates.Visible)
{
int length;
if (!int.TryParse(((EditText) FindViewById(Resource.Id.length)).Text, out length))
{
throw new Exception(GetString(Resource.String.error_wrong_length));
}
options.PasswordGenerationOptions =
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;
}

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:background="?activityBackgroundColor"
android:layout_height="fill_parent">
android:layout_width="fill_parent"
android:background="?activityBackgroundColor"
android:layout_height="fill_parent">
<RelativeLayout
@@ -32,7 +32,7 @@ android:background="?activityBackgroundColor"
<ScrollView
android:id="@+id/ScrollView"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/bottom_bar"
android:layout_marginBottom="12dip"
@@ -40,95 +40,220 @@ android:background="?activityBackgroundColor"
android:layout_marginRight="12dip"
android:layout_marginTop="12dip"
android:layout_alignParentTop="false">
<RelativeLayout
android:id="@+id/RelativeLayout"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/password_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:singleLine="true"
android:typeface="monospace"
android:hint="@string/hint_generated_password" />
<Button
android:id="@+id/generate_password_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/generate_password" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/pb_password_strength"
android:layout_width="50dp"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:indeterminate="false"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_password_strength"
android:paddingLeft="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingBottom="1dp"
android:paddingTop="8dp"
android:text="@string/password_generation_profile"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<EditText
android:id="@+id/password_edit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:ems="10"
android:singleLine="true"
android:typeface="monospace"
android:hint="@string/hint_generated_password" />
<Button
android:id="@+id/generate_password_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/password_edit"
android:text="@string/generate_password" />
<TextView
android:id="@+id/length_label"
android:text="@string/length"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@id/generate_password_button" />
<Button
android:id="@+id/btn_length32"
android:text="32"
android:layout_alignParentRight="true"
android:layout_width="50sp"
android:orientation="horizontal"
>
<Spinner
android:id="@+id/spinner_password_generator_profile"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
/>
<ImageButton
android:id="@+id/btn_password_generator_profile_save"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_menu_save"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" />
<ImageButton
android:id="@+id/btn_password_generator_profile_delete"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_menu_close"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" />
</LinearLayout>
<Spinner
android:id="@+id/spinner_password_generator_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:layout_marginBottom="64dip"
android:entries="@array/PasswordGeneratorModes"
/>
<LinearLayout
android:id="@+id/passphraseOptions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:id="@+id/wordcountlayout">
<EditText
android:id="@+id/wordcount"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:singleLine="true"
android:text="7"
android:hint="@string/hint_wordcount" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:id="@+id/wordseparatorlayout">
<EditText
android:id="@+id/wordseparator"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:singleLine="true"
android:text=" "
android:hint="@string/hint_wordseparator" />
</android.support.design.widget.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingBottom="8dp"
android:text="@string/passphrase_capitalization"/>
<Spinner
android:id="@+id/spinner_password_generator_case_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/PasswordGeneratorCaseModes"
/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:id="@+id/passwordOptions"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<LinearLayout
android:orientation="horizontal"
android:id="@+id/pwd_buttons"
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_width="match_parent">
<android.support.design.widget.TextInputLayout
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_width="50dp"
android:layout_weight="1"
android:id="@+id/lengthlayout">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
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="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="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_length32"
android:singleLine="true"
android:text="12"
android:hint="@string/hint_length" />
<CheckBox
android:id="@+id/cb_uppercase"
android:layout_width="wrap_content"
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/btn_length6"
android:text="6"
android:layout_height="wrap_content"
android:text="@string/uppercase"
android:checked="true"
android:layout_below="@id/length" />
android:layout_width="50sp"
/>
<Button
android:id="@+id/btn_length8"
android:text="8"
android:layout_height="wrap_content"
android:layout_width="50sp"
/>
<Button
android:id="@+id/btn_length12"
android:text="12"
android:layout_height="wrap_content"
android:layout_width="50sp"
/>
<Button
android:id="@+id/btn_length16"
android:text="16"
android:layout_height="wrap_content"
android:layout_width="50sp"
/>
<Button
android:id="@+id/btn_length24"
android:text="24"
android:layout_height="wrap_content"
android:layout_width="50sp"
/>
<Button
android:id="@+id/btn_length32"
android:text="32"
android:layout_width="50sp"
android:layout_height="wrap_content"
/>
</LinearLayout>
<CheckBox
android:id="@+id/cb_uppercase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/uppercase"
android:checked="true" />
<CheckBox
android:id="@+id/cb_lowercase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lowercase"
android:checked="true"
android:layout_below="@id/cb_uppercase" />
android:checked="true" />
<CheckBox
android:id="@+id/cb_digits"
android:layout_width="wrap_content"
@@ -185,6 +310,10 @@ android:background="?activityBackgroundColor"
android:layout_height="wrap_content"
android:text="@string/at_least_one_from_each_group"
android:layout_below="@id/cb_exclude_lookalike" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</RelativeLayout>
</RelativeLayout>

View File

@@ -266,7 +266,26 @@
<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="password_generation_profile">Profile</string>
<string name="save_password_generation_profile_text">Enter the name of the profile to save. Enter an existing name to overwrite.</string>
<string name="hint_wordcount">Passphrase word count</string>
<string name="hint_wordseparator">Word separator</string>
<string-array name="PasswordGeneratorModes">
<item>Password</item>
<item>Passphrase</item>
<item>Passphrase + Password</item>
</string-array>
<string-array name="PasswordGeneratorCaseModes">
<item>lowercase</item>
<item>UPPERCASE</item>
<item>First Character Uppercase</item>
</string-array>
<string name="custom_settings">Custom settings</string>
<string name="passphrase_capitalization">Passphrase capitalization</string>
<string name="search_hint">Find what</string>
<string name="search_results">Search results</string>
<string name="search_in">Search in</string>
<string name="select_other_entry">Select another entry</string>

File diff suppressed because it is too large Load Diff

View File

@@ -283,6 +283,7 @@
<Compile Include="AttachmentContentProvider.cs" />
<Compile Include="app\AppTask.cs" />
<Compile Include="views\TextWithHelp.cs" />
<Compile Include="Wordlist.cs" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\xml\searchable.xml">

View File

@@ -20,10 +20,26 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Android.App;
using Android.Content;
namespace keepass2android
{
public static class StringExtension
{
public static string ToUpperFirstLetter(this string source)
{
if (string.IsNullOrEmpty(source))
return string.Empty;
// convert to char array of the string
char[] letters = source.ToCharArray();
// upper case the first char
letters[0] = char.ToUpper(letters[0]);
// return the array made of the new char array
return new string(letters);
}
}
/// <summary>
/// Password generator
/// </summary>
@@ -85,9 +101,64 @@ namespace keepass2android
_cxt = cxt;
}
public class CombinedKeyOptions
{
protected bool Equals(CombinedKeyOptions other)
{
return Equals(PassphraseGenerationOptions, other.PassphraseGenerationOptions) && Equals(PasswordGenerationOptions, other.PasswordGenerationOptions);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((CombinedKeyOptions) obj);
}
public override int GetHashCode()
{
return HashCode.Combine(PassphraseGenerationOptions, PasswordGenerationOptions);
}
public PassphraseGenerationOptions PassphraseGenerationOptions { get; set; }
public PasswordGenerationOptions PasswordGenerationOptions { get; set; }
}
public class PasswordGenerationOptions
{
protected bool Equals(PasswordGenerationOptions other)
{
return Length == other.Length && UpperCase == other.UpperCase && LowerCase == other.LowerCase && Digits == other.Digits && Minus == other.Minus && Underline == other.Underline && Space == other.Space && Specials == other.Specials && SpecialsExtended == other.SpecialsExtended && Brackets == other.Brackets && ExcludeLookAlike == other.ExcludeLookAlike && AtLeastOneFromEachGroup == other.AtLeastOneFromEachGroup;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((PasswordGenerationOptions) obj);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(Length);
hashCode.Add(UpperCase);
hashCode.Add(LowerCase);
hashCode.Add(Digits);
hashCode.Add(Minus);
hashCode.Add(Underline);
hashCode.Add(Space);
hashCode.Add(Specials);
hashCode.Add(SpecialsExtended);
hashCode.Add(Brackets);
hashCode.Add(ExcludeLookAlike);
hashCode.Add(AtLeastOneFromEachGroup);
return hashCode.ToHashCode();
}
public int Length { get; set; }
public bool UpperCase { get; set; }
public bool LowerCase { get; set; }
@@ -104,63 +175,141 @@ namespace keepass2android
}
public String GeneratePassword(PasswordGenerationOptions options) {
if (options.Length <= 0)
throw new ArgumentException(_cxt.GetString(Resource.String.error_wrong_length));
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)
public class PassphraseGenerationOptions
{
protected bool Equals(PassphraseGenerationOptions other)
{
foreach (var g in groups)
return CaseMode == other.CaseMode && Separator == other.Separator && WordCount == other.WordCount;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((PassphraseGenerationOptions) obj);
}
public override int GetHashCode()
{
return HashCode.Combine((int) CaseMode, Separator, WordCount);
}
public enum PassphraseCaseMode
{
Lowercase,
Uppercase,
PascalCase
};
public PassphraseCaseMode CaseMode { get; set; }
public string Separator { get; set; }
public int WordCount { get; set; }
}
public String GeneratePassword(CombinedKeyOptions options)
{
if ((options.PassphraseGenerationOptions== null || options.PassphraseGenerationOptions.WordCount == 0)
&& (options.PasswordGenerationOptions == null || options.PasswordGenerationOptions.Length == 0))
{
throw new Exception("Bad options");
}
string key = "";
Random random = new SecureRandom();
var passwordOptions = options.PasswordGenerationOptions;
var passphraseOptions = options.PassphraseGenerationOptions;
if (passphraseOptions != null && passphraseOptions.WordCount > 0)
{
var wl = new Wordlist();
string passphrase = "";
for (int i = 0; i < passphraseOptions.WordCount; i++)
{
if (g.Length > 0)
string word = wl.GetWord(random);
if (passphraseOptions.CaseMode == PassphraseGenerationOptions.PassphraseCaseMode.Uppercase)
{
buffer.Append(g[random.Next(g.Length)]);
word = word.ToUpper();
}
else if (passphraseOptions.CaseMode == PassphraseGenerationOptions.PassphraseCaseMode.Lowercase)
{
word = word.ToLower();
}
else if (passphraseOptions.CaseMode == PassphraseGenerationOptions.PassphraseCaseMode.PascalCase)
{
word = word.ToUpperFirstLetter();
}
passphrase += word;
if (i < passphraseOptions.WordCount - 1 || passwordOptions != null)
passphrase += passphraseOptions.Separator;
}
key += passphrase;
}
if (passwordOptions != null)
{
var groups = GetCharacterGroups(passwordOptions);
String characterSet = GetCharacterSet(passwordOptions, groups);
if (characterSet.Length == 0)
throw new Exception("Bad options");
int size = characterSet.Length;
StringBuilder buffer = new StringBuilder();
if (passwordOptions.AtLeastOneFromEachGroup)
{
foreach (var g in groups)
{
if (g.Length > 0)
{
buffer.Append(g[random.Next(g.Length)]);
}
}
}
}
if (size > 0)
{
while (buffer.Length < options.Length)
{
buffer.Append(characterSet[random.Next(size)]);
}
}
var password = buffer.ToString();
if (options.AtLeastOneFromEachGroup)
{
//shuffle
StringBuilder sb = new StringBuilder(password);
for (int i = (password.Length - 1); i >= 1; i--)
if (size > 0)
{
int j = random.Next(i + 1);
var tmp = sb[i];
sb[i] = sb[j];
sb[j] = tmp;
while (buffer.Length < passwordOptions.Length)
{
buffer.Append(characterSet[random.Next(size)]);
}
}
password = sb.ToString();
var password = buffer.ToString();
if (passwordOptions.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();
}
key += password;
}
return password;
return key;
}
public string GetCharacterSet(PasswordGenerationOptions options, List<string> groups)