diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index ce1937998145de3a9f5816f35fabb5045ba03895..a74ff9c72136d1e088477e78162506aef16794b9 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -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> diff --git a/briar-android/src/org/briarproject/android/SetupActivity.java b/briar-android/src/org/briarproject/android/SetupActivity.java index f5b3c227f2a832580aa17c94ce244ce777f68fff..a9e56819049597f700e52d9c9d8e34a24b110581 100644 --- a/briar-android/src/org/briarproject/android/SetupActivity.java +++ b/briar-android/src/org/briarproject/android/SetupActivity.java @@ -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); } diff --git a/briar-android/src/org/briarproject/android/util/StrengthMeter.java b/briar-android/src/org/briarproject/android/util/StrengthMeter.java new file mode 100644 index 0000000000000000000000000000000000000000..6bc75fb0787bb7fdbb058a194e0a7e483df9c80c --- /dev/null +++ b/briar-android/src/org/briarproject/android/util/StrengthMeter.java @@ -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)); + } +} diff --git a/briar-api/src/org/briarproject/api/crypto/PasswordStrengthEstimator.java b/briar-api/src/org/briarproject/api/crypto/PasswordStrengthEstimator.java new file mode 100644 index 0000000000000000000000000000000000000000..9ffa2dc244da42bfe0d4a675721510a640b53754 --- /dev/null +++ b/briar-api/src/org/briarproject/api/crypto/PasswordStrengthEstimator.java @@ -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); +} diff --git a/briar-core/src/org/briarproject/crypto/CryptoModule.java b/briar-core/src/org/briarproject/crypto/CryptoModule.java index 39a18c6ac0d0fe8e0cc1180c527523012e28bb5d..f0eb56f05dbb57c04238392f19c9c7d8162a247e 100644 --- a/briar-core/src/org/briarproject/crypto/CryptoModule.java +++ b/briar-core/src/org/briarproject/crypto/CryptoModule.java @@ -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 diff --git a/briar-core/src/org/briarproject/crypto/PasswordStrengthEstimatorImpl.java b/briar-core/src/org/briarproject/crypto/PasswordStrengthEstimatorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..aeec2ddc3bcc3127224796c101edd96c46efcc10 --- /dev/null +++ b/briar-core/src/org/briarproject/crypto/PasswordStrengthEstimatorImpl.java @@ -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)); + } +} diff --git a/briar-tests/build.xml b/briar-tests/build.xml index 33043bff61ed89b06934232b9e74e0f48645afd5..d6fb7a874520f9dcf73514958534c9e4f64f6832 100644 --- a/briar-tests/build.xml +++ b/briar-tests/build.xml @@ -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'/> diff --git a/briar-tests/src/org/briarproject/crypto/PasswordStrengthEstimatorTest.java b/briar-tests/src/org/briarproject/crypto/PasswordStrengthEstimatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9f703b378f7acaa13670691c8101a156279f132e --- /dev/null +++ b/briar-tests/src/org/briarproject/crypto/PasswordStrengthEstimatorTest.java @@ -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); + } +}