mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Add a password strength meter to SetupActivity. Dev task #42.
This commit is contained in:
@@ -7,7 +7,11 @@
|
||||
<string name="choose_nickname">Choose your nickname:</string>
|
||||
<string name="choose_password">Choose your password:</string>
|
||||
<string name="confirm_password">Confirm your password:</string>
|
||||
<string name="format_min_password">Password must be at least %1$d characters long</string>
|
||||
<string name="password_too_weak">Password is too weak</string>
|
||||
<string name="password_weak">Password is weak</string>
|
||||
<string name="password_quite_weak">Password is quite weak</string>
|
||||
<string name="password_quite_strong">Password is quite strong</string>
|
||||
<string name="password_strong">Password is strong</string>
|
||||
<string name="passwords_do_not_match">Passwords do not match</string>
|
||||
<string name="enter_password">Enter your password:</string>
|
||||
<string name="try_again">Wrong password, try again:</string>
|
||||
|
||||
@@ -11,6 +11,7 @@ import static android.view.View.VISIBLE;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
|
||||
import static org.briarproject.api.crypto.PasswordStrengthEstimator.WEAK;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -18,12 +19,14 @@ import java.util.concurrent.Executor;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.StrengthMeter;
|
||||
import org.briarproject.api.AuthorFactory;
|
||||
import org.briarproject.api.LocalAuthor;
|
||||
import org.briarproject.api.android.ReferenceManager;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.api.db.DatabaseConfig;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
@@ -31,7 +34,6 @@ import roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.view.View;
|
||||
@@ -45,11 +47,11 @@ import android.widget.TextView;
|
||||
|
||||
public class SetupActivity extends RoboActivity implements OnClickListener {
|
||||
|
||||
private static final int MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
@Inject @CryptoExecutor private Executor cryptoExecutor;
|
||||
@Inject private PasswordStrengthEstimator strengthEstimator;
|
||||
private EditText nicknameEntry = null;
|
||||
private EditText passwordEntry = null, passwordConfirmation = null;
|
||||
private StrengthMeter strengthMeter = null;
|
||||
private TextView feedback = null;
|
||||
private Button continueButton = null;
|
||||
private ProgressBar progress = null;
|
||||
@@ -128,12 +130,15 @@ public class SetupActivity extends RoboActivity implements OnClickListener {
|
||||
passwordConfirmation.setInputType(inputType);
|
||||
layout.addView(passwordConfirmation);
|
||||
|
||||
strengthMeter = new StrengthMeter(this);
|
||||
strengthMeter.setPadding(30, 10, 30, 0);
|
||||
layout.addView(strengthMeter);
|
||||
|
||||
feedback = new TextView(this);
|
||||
feedback.setGravity(CENTER);
|
||||
feedback.setTextSize(14);
|
||||
feedback.setPadding(10, 10, 10, 10);
|
||||
String format = getResources().getString(R.string.format_min_password);
|
||||
feedback.setText(String.format(format, MIN_PASSWORD_LENGTH));
|
||||
feedback.setText("");
|
||||
layout.addView(feedback);
|
||||
|
||||
continueButton = new Button(this);
|
||||
@@ -157,25 +162,35 @@ public class SetupActivity extends RoboActivity implements OnClickListener {
|
||||
}
|
||||
|
||||
private void enableOrDisableContinueButton() {
|
||||
if(nicknameEntry == null || passwordEntry == null ||
|
||||
passwordConfirmation == null || continueButton == null) return;
|
||||
if(continueButton == null) return; // Not created yet
|
||||
boolean nicknameNotEmpty = nicknameEntry.getText().length() > 0;
|
||||
char[] firstPassword = getChars(passwordEntry.getText());
|
||||
char[] secondPassword = getChars(passwordConfirmation.getText());
|
||||
boolean passwordLength = firstPassword.length >= MIN_PASSWORD_LENGTH;
|
||||
boolean passwordsMatch = Arrays.equals(firstPassword, secondPassword);
|
||||
float strength = strengthEstimator.estimateStrength(firstPassword);
|
||||
for(int i = 0; i < firstPassword.length; i++) firstPassword[i] = 0;
|
||||
for(int i = 0; i < secondPassword.length; i++) secondPassword[i] = 0;
|
||||
if(!passwordLength) {
|
||||
Resources res = getResources();
|
||||
String format = res.getString(R.string.format_min_password);
|
||||
feedback.setText(String.format(format, MIN_PASSWORD_LENGTH));
|
||||
strengthMeter.setStrength(strength);
|
||||
if(firstPassword.length == 0) {
|
||||
feedback.setText("");
|
||||
} else if(secondPassword.length == 0 || passwordsMatch) {
|
||||
if(strength < PasswordStrengthEstimator.WEAK) {
|
||||
feedback.setText(R.string.password_too_weak);
|
||||
} else if(strength < PasswordStrengthEstimator.QUITE_WEAK) {
|
||||
feedback.setText(R.string.password_weak);
|
||||
} else if(strength < PasswordStrengthEstimator.QUITE_STRONG) {
|
||||
feedback.setText(R.string.password_quite_weak);
|
||||
} else if(strength < PasswordStrengthEstimator.STRONG) {
|
||||
feedback.setText(R.string.password_quite_strong);
|
||||
} else {
|
||||
feedback.setText(R.string.password_strong);
|
||||
}
|
||||
} else if(!passwordsMatch) {
|
||||
feedback.setText(R.string.passwords_do_not_match);
|
||||
} else {
|
||||
feedback.setText("");
|
||||
}
|
||||
boolean valid = nicknameNotEmpty && passwordLength && passwordsMatch;
|
||||
boolean valid = nicknameNotEmpty && passwordsMatch && strength >= WEAK;
|
||||
continueButton.setEnabled(valid);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import static android.graphics.drawable.ClipDrawable.HORIZONTAL;
|
||||
import static android.view.Gravity.LEFT;
|
||||
import static org.briarproject.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
|
||||
import static org.briarproject.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||
import static org.briarproject.api.crypto.PasswordStrengthEstimator.STRONG;
|
||||
import static org.briarproject.api.crypto.PasswordStrengthEstimator.WEAK;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ClipDrawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.RectShape;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
public class StrengthMeter extends ProgressBar {
|
||||
|
||||
private static final int MAX = 100;
|
||||
private static final int RED = Color.rgb(255, 0, 0);
|
||||
private static final int ORANGE = Color.rgb(255, 160, 0);
|
||||
private static final int YELLOW = Color.rgb(250, 255, 15);
|
||||
private static final int LIME = Color.rgb(190, 255, 0);
|
||||
private static final int GREEN = Color.rgb(7, 255, 0);
|
||||
|
||||
private final ShapeDrawable bar;
|
||||
|
||||
public StrengthMeter(Context context) {
|
||||
super(context, null, android.R.attr.progressBarStyleHorizontal);
|
||||
bar = new ShapeDrawable(new RectShape());
|
||||
bar.getPaint().setColor(Color.RED);
|
||||
ClipDrawable progress = new ClipDrawable(bar, LEFT, HORIZONTAL);
|
||||
setProgressDrawable(progress);
|
||||
setIndeterminate(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMax() {
|
||||
return MAX;
|
||||
}
|
||||
|
||||
public void setStrength(float strength) {
|
||||
if(strength < 0 || strength > 1) throw new IllegalArgumentException();
|
||||
int colour;
|
||||
if(strength < WEAK) colour = RED;
|
||||
else if(strength < QUITE_WEAK) colour = ORANGE;
|
||||
else if(strength < QUITE_STRONG) colour = YELLOW;
|
||||
else if(strength < STRONG) colour = LIME;
|
||||
else colour = GREEN;
|
||||
bar.getPaint().setColor(colour);
|
||||
setProgress((int) (strength * MAX));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.api.crypto;
|
||||
|
||||
public interface PasswordStrengthEstimator {
|
||||
|
||||
float NONE = 0;
|
||||
float WEAK = 0.4f;
|
||||
float QUITE_WEAK = 0.6f;
|
||||
float QUITE_STRONG = 0.8f;
|
||||
float STRONG = 1;
|
||||
|
||||
/**
|
||||
* Returns an estimate between 0 (weakest) and 1 (strongest), inclusive,
|
||||
* of the strength of the given password.
|
||||
*/
|
||||
float estimateStrength(char[] password);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import javax.inject.Singleton;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
@@ -40,6 +41,8 @@ public class CryptoModule extends AbstractModule {
|
||||
protected void configure() {
|
||||
bind(CryptoComponent.class).to(
|
||||
CryptoComponentImpl.class).in(Singleton.class);
|
||||
bind(PasswordStrengthEstimator.class).to(
|
||||
PasswordStrengthEstimatorImpl.class);
|
||||
}
|
||||
|
||||
@Provides @Singleton @CryptoExecutor
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.briarproject.api.crypto.PasswordStrengthEstimator;
|
||||
|
||||
class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
|
||||
|
||||
private static final int LOWER = 26;
|
||||
private static final int UPPER = 26;
|
||||
private static final int DIGIT = 10;
|
||||
private static final int OTHER = 10;
|
||||
private static final double STRONG = Math.log(Math.pow(LOWER + UPPER +
|
||||
DIGIT + OTHER, 10));
|
||||
|
||||
public float estimateStrength(char[] password) {
|
||||
HashSet<Character> unique = new HashSet<Character>();
|
||||
for(char c : password) unique.add(c);
|
||||
boolean lower = false, upper = false, digit = false, other = false;
|
||||
for(char c : unique) {
|
||||
if(Character.isLowerCase(c)) lower = true;
|
||||
else if(Character.isUpperCase(c)) upper = true;
|
||||
else if(Character.isDigit(c)) digit = true;
|
||||
else other = true;
|
||||
}
|
||||
int alphabetSize = 0;
|
||||
if(lower) alphabetSize += LOWER;
|
||||
if(upper) alphabetSize += UPPER;
|
||||
if(digit) alphabetSize += DIGIT;
|
||||
if(other) alphabetSize += OTHER;
|
||||
double score = Math.log(Math.pow(alphabetSize, unique.size()));
|
||||
return Math.min(1, (float) (score / STRONG));
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@
|
||||
<test name='org.briarproject.crypto.KeyDerivationTest'/>
|
||||
<test name='org.briarproject.crypto.KeyEncodingAndParsingTest'/>
|
||||
<test name="org.briarproject.crypto.PasswordBasedKdfTest"/>
|
||||
<test name="org.briarproject.crypto.PasswordStrengthEstimatorTest"/>
|
||||
<test name='org.briarproject.crypto.SecretKeyImplTest'/>
|
||||
<test name='org.briarproject.db.BasicH2Test'/>
|
||||
<test name='org.briarproject.db.DatabaseCleanerImplTest'/>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.briarproject.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.crypto.PasswordStrengthEstimator;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PasswordStrengthEstimatorTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testWeakPasswords() {
|
||||
PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl();
|
||||
assertTrue(e.estimateStrength("".toCharArray()) < QUITE_STRONG);
|
||||
assertTrue(e.estimateStrength("password".toCharArray()) < QUITE_STRONG);
|
||||
assertTrue(e.estimateStrength("letmein".toCharArray()) < QUITE_STRONG);
|
||||
assertTrue(e.estimateStrength("123456".toCharArray()) < QUITE_STRONG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrongPasswords() {
|
||||
PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl();
|
||||
// Industry standard
|
||||
assertTrue(e.estimateStrength("Tr0ub4dor&3".toCharArray())
|
||||
> QUITE_STRONG);
|
||||
assertTrue(e.estimateStrength("correcthorsebatterystaple".toCharArray())
|
||||
> QUITE_STRONG);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user