aboutsummaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
authorMohamed Zenadi <zeapo@users.noreply.github.com>2015-07-11 19:41:33 +0200
committerMohamed Zenadi <zeapo@users.noreply.github.com>2015-07-11 19:41:33 +0200
commit62c20d323cee5c543fd17cdc6e817efd48d1e47b (patch)
tree02a6e433ec610b8a3f2af780e743ec149afd5bee /app/src/main
parent97c5c5da9572de48842dc3739c260af75c4915f8 (diff)
parent32cf7f7813c89f7eff43712705b1d1453637a180 (diff)
Merge pull request #101 from wongma7/master
Adding support for password generation
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.java2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java5
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java336
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/pw_phonemes.java206
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/pw_rand.java69
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/pwgen.java138
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/randnum.java26
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java124
-rw-r--r--app/src/main/res/layout/encrypt_layout.xml10
-rw-r--r--app/src/main/res/layout/fragment_pwgen.xml108
-rw-r--r--app/src/main/res/values/strings.xml8
11 files changed, 1031 insertions, 1 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
index a99ec34a..5562f0b9 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
@@ -20,6 +20,7 @@ import android.view.View;
import com.zeapo.pwdstore.crypto.PgpHandler;
import com.zeapo.pwdstore.git.GitActivity;
import com.zeapo.pwdstore.git.GitAsyncTask;
+import com.zeapo.pwdstore.pwgen.PRNGFixes;
import com.zeapo.pwdstore.utils.PasswordItem;
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
import com.zeapo.pwdstore.utils.PasswordRepository;
@@ -49,6 +50,7 @@ public class PasswordStore extends AppCompatActivity {
setContentView(R.layout.activity_pwdstore);
settings = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
activity = this;
+ PRNGFixes.apply();
}
@Override
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java
index 28aea81e..9c0bc877 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java
@@ -1,6 +1,7 @@
package com.zeapo.pwdstore.crypto;
import android.app.Activity;
+import android.app.DialogFragment;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipboardManager;
@@ -28,6 +29,7 @@ import android.widget.Toast;
import com.google.common.primitives.Longs;
import com.zeapo.pwdstore.R;
import com.zeapo.pwdstore.UserPreference;
+import com.zeapo.pwdstore.pwgenDialogFragment;
import com.zeapo.pwdstore.utils.PasswordRepository;
import org.apache.commons.io.FileUtils;
@@ -174,6 +176,9 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
case R.id.crypto_get_key_ids:
getKeyIds(new Intent());
break;
+ case R.id.generate_password:
+ DialogFragment df = new pwgenDialogFragment();
+ df.show(getFragmentManager(), "generator");
default:
// should not happen
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java b/app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java
new file mode 100644
index 00000000..201e821d
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java
@@ -0,0 +1,336 @@
+package com.zeapo.pwdstore.pwgen;
+
+/*
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will Google be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, as long as the origin is not misrepresented.
+ */
+
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+
+/**
+ * Fixes for the output of the default PRNG having low entropy.
+ *
+ * The fixes need to be applied via {@link #apply()} before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in the
+ * application's {@code onCreate}.
+ */
+public final class PRNGFixes {
+
+ private static final int VERSION_CODE_JELLY_BEAN = 16;
+ private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+ private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
+ getBuildFingerprintAndDeviceSerial();
+
+ /** Hidden constructor to prevent instantiation. */
+ private PRNGFixes() {}
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be applied.
+ */
+ public static void apply() {
+ applyOpenSSLFix();
+ installLinuxPRNGSecureRandom();
+ }
+
+ /**
+ * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
+ * fix is not needed.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void applyOpenSSLFix() throws SecurityException {
+ if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
+ || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
+ // No need to apply the fix
+ return;
+ }
+
+ try {
+ // Mix in the device- and invocation-specific seed.
+ Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_seed", byte[].class)
+ .invoke(null, generateSeed());
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ int bytesRead = (Integer) Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_load_file", String.class, long.class)
+ .invoke(null, "/dev/urandom", 1024);
+ if (bytesRead != 1024) {
+ throw new IOException(
+ "Unexpected number of bytes read from Linux PRNG: "
+ + bytesRead);
+ }
+ } catch (Exception e) {
+ throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+ }
+ }
+
+ /**
+ * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
+ * default. Does nothing if the implementation is already the default or if
+ * there is not need to install the implementation.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void installLinuxPRNGSecureRandom()
+ throws SecurityException {
+ if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
+ // No need to apply the fix
+ return;
+ }
+
+ // Install a Linux PRNG-based SecureRandom implementation as the
+ // default, if not yet installed.
+ Provider[] secureRandomProviders =
+ Security.getProviders("SecureRandom.SHA1PRNG");
+ if ((secureRandomProviders == null)
+ || (secureRandomProviders.length < 1)
+ || (!LinuxPRNGSecureRandomProvider.class.equals(
+ secureRandomProviders[0].getClass()))) {
+ Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ SecureRandom rng1 = new SecureRandom();
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng1.getProvider().getClass())) {
+ throw new SecurityException(
+ "new SecureRandom() backed by wrong Provider: "
+ + rng1.getProvider().getClass());
+ }
+
+ SecureRandom rng2;
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecurityException("SHA1PRNG not available", e);
+ }
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng2.getProvider().getClass())) {
+ throw new SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ + " Provider: " + rng2.getProvider().getClass());
+ }
+ }
+
+ /**
+ * {@code Provider} of {@code SecureRandom} engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private static class LinuxPRNGSecureRandomProvider extends Provider {
+
+ public LinuxPRNGSecureRandomProvider() {
+ super("LinuxPRNG",
+ 1.0,
+ "A Linux-specific random number provider that uses"
+ + " /dev/urandom");
+ // Although /dev/urandom is not a SHA-1 PRNG, some apps
+ // explicitly request a SHA1PRNG SecureRandom and we thus need to
+ // prevent them from getting the default implementation whose output
+ // may have low entropy.
+ put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+ }
+ }
+
+ /**
+ * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
+ * ({@code /dev/urandom}).
+ */
+ public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+
+ /*
+ * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
+ * are passed through to the Linux PRNG (/dev/urandom). Instances of
+ * this class seed themselves by mixing in the current time, PID, UID,
+ * build fingerprint, and hardware serial number (where available) into
+ * Linux PRNG.
+ *
+ * Concurrency: Read requests to the underlying Linux PRNG are
+ * serialized (on sLock) to ensure that multiple threads do not get
+ * duplicated PRNG output.
+ */
+
+ private static final File URANDOM_FILE = new File("/dev/urandom");
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Input stream for reading from Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static DataInputStream sUrandomIn;
+
+ /**
+ * Output stream for writing to Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static OutputStream sUrandomOut;
+
+ /**
+ * Whether this engine instance has been seeded. This is needed because
+ * each instance needs to seed itself if the client does not explicitly
+ * seed it.
+ */
+ private boolean mSeeded;
+
+ @Override
+ protected void engineSetSeed(byte[] bytes) {
+ try {
+ OutputStream out;
+ synchronized (sLock) {
+ out = getUrandomOutputStream();
+ }
+ out.write(bytes);
+ out.flush();
+ } catch (IOException e) {
+ // On a small fraction of devices /dev/urandom is not writable.
+ // Log and ignore.
+ Log.w(PRNGFixes.class.getSimpleName(),
+ "Failed to mix seed into " + URANDOM_FILE);
+ } finally {
+ mSeeded = true;
+ }
+ }
+
+ @Override
+ protected void engineNextBytes(byte[] bytes) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed());
+ }
+
+ try {
+ DataInputStream in;
+ synchronized (sLock) {
+ in = getUrandomInputStream();
+ }
+ synchronized (in) {
+ in.readFully(bytes);
+ }
+ } catch (IOException e) {
+ throw new SecurityException(
+ "Failed to read from " + URANDOM_FILE, e);
+ }
+ }
+
+ @Override
+ protected byte[] engineGenerateSeed(int size) {
+ byte[] seed = new byte[size];
+ engineNextBytes(seed);
+ return seed;
+ }
+
+ private DataInputStream getUrandomInputStream() {
+ synchronized (sLock) {
+ if (sUrandomIn == null) {
+ // NOTE: Consider inserting a BufferedInputStream between
+ // DataInputStream and FileInputStream if you need higher
+ // PRNG output performance and can live with future PRNG
+ // output being pulled into this process prematurely.
+ try {
+ sUrandomIn = new DataInputStream(
+ new FileInputStream(URANDOM_FILE));
+ } catch (IOException e) {
+ throw new SecurityException("Failed to open "
+ + URANDOM_FILE + " for reading", e);
+ }
+ }
+ return sUrandomIn;
+ }
+ }
+
+ private OutputStream getUrandomOutputStream() throws IOException {
+ synchronized (sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = new FileOutputStream(URANDOM_FILE);
+ }
+ return sUrandomOut;
+ }
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private static byte[] generateSeed() {
+ try {
+ ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+ DataOutputStream seedBufferOut =
+ new DataOutputStream(seedBuffer);
+ seedBufferOut.writeLong(System.currentTimeMillis());
+ seedBufferOut.writeLong(System.nanoTime());
+ seedBufferOut.writeInt(Process.myPid());
+ seedBufferOut.writeInt(Process.myUid());
+ seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
+ seedBufferOut.close();
+ return seedBuffer.toByteArray();
+ } catch (IOException e) {
+ throw new SecurityException("Failed to generate seed", e);
+ }
+ }
+
+ /**
+ * Gets the hardware serial number of this device.
+ *
+ * @return serial number or {@code null} if not available.
+ */
+ private static String getDeviceSerialNumber() {
+ // We're using the Reflection API because Build.SERIAL is only available
+ // since API Level 9 (Gingerbread, Android 2.3).
+ try {
+ return (String) Build.class.getField("SERIAL").get(null);
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+
+ private static byte[] getBuildFingerprintAndDeviceSerial() {
+ StringBuilder result = new StringBuilder();
+ String fingerprint = Build.FINGERPRINT;
+ if (fingerprint != null) {
+ result.append(fingerprint);
+ }
+ String serial = getDeviceSerialNumber();
+ if (serial != null) {
+ result.append(serial);
+ }
+ try {
+ return result.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported");
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/pw_phonemes.java b/app/src/main/java/com/zeapo/pwdstore/pwgen/pw_phonemes.java
new file mode 100644
index 00000000..1b312232
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/pw_phonemes.java
@@ -0,0 +1,206 @@
+package com.zeapo.pwdstore.pwgen;
+
+public class pw_phonemes {
+ private static final int CONSONANT = 0x0001;
+ private static final int VOWEL = 0x0002;
+ private static final int DIPTHONG = 0x0004;
+ private static final int NOT_FIRST = 0x0008;
+
+ private static final element elements[] = {
+ new element("a", VOWEL),
+ new element("ae", VOWEL | DIPTHONG),
+ new element("ah", VOWEL | DIPTHONG),
+ new element("ai", VOWEL | DIPTHONG),
+ new element("b", CONSONANT),
+ new element("c", CONSONANT),
+ new element("ch", CONSONANT | DIPTHONG),
+ new element("d", CONSONANT),
+ new element("e", VOWEL),
+ new element("ee", VOWEL | DIPTHONG),
+ new element("ei", VOWEL | DIPTHONG),
+ new element("f", CONSONANT),
+ new element("g", CONSONANT),
+ new element("gh", CONSONANT | DIPTHONG | NOT_FIRST),
+ new element("h", CONSONANT),
+ new element("i", VOWEL),
+ new element("ie", VOWEL | DIPTHONG),
+ new element("j", CONSONANT),
+ new element("k", CONSONANT),
+ new element("l", CONSONANT),
+ new element("m", CONSONANT),
+ new element("n", CONSONANT),
+ new element("ng", CONSONANT | DIPTHONG | NOT_FIRST),
+ new element("o", VOWEL),
+ new element("oh", VOWEL | DIPTHONG),
+ new element("oo", VOWEL | DIPTHONG),
+ new element("p", CONSONANT),
+ new element("ph", CONSONANT | DIPTHONG),
+ new element("qu", CONSONANT | DIPTHONG),
+ new element("r", CONSONANT),
+ new element("s", CONSONANT),
+ new element("sh", CONSONANT | DIPTHONG),
+ new element("t", CONSONANT),
+ new element("th", CONSONANT | DIPTHONG),
+ new element("u", VOWEL),
+ new element("v", CONSONANT),
+ new element("w", CONSONANT),
+ new element("x", CONSONANT),
+ new element("y", CONSONANT),
+ new element("z", CONSONANT)
+ };
+
+ private static class element {
+ String str;
+ int flags;
+ element(String str, int flags) {
+ this.str = str;
+ this.flags = flags;
+ }
+ }
+
+ private static final int NUM_ELEMENTS = elements.length;
+
+ /**
+ * Generates a human-readable password.
+ *
+ * @param size length of password to generate
+ * @param pwFlags flag field where set bits indicate conditions the
+ * generated password must meet
+ * <table summary="bits of flag field">
+ * <tr><td>Bit</td><td>Condition</td></tr>
+ * <tr><td>0</td><td>include at least one number</td></tr>
+ * <tr><td>1</td><td>include at least one uppercase letter</td></tr>
+ * <tr><td>2</td><td>include at least one symbol</td></tr>
+ * <tr><td>3</td><td>don't include ambiguous characters</td></tr>
+ * </table>
+ * @return the generated password
+ */
+ public static String phonemes(int size, int pwFlags) {
+ String password;
+ int curSize, i, length, flags, featureFlags, prev, shouldBe;
+ boolean first;
+ String str;
+ char cha;
+
+ do {
+ password = "";
+ featureFlags = pwFlags;
+ curSize = 0;
+ prev = 0;
+ first = true;
+
+ shouldBe = randnum.number(2) == 1 ? VOWEL : CONSONANT;
+
+ while (curSize < size) {
+ i = randnum.number(NUM_ELEMENTS);
+ str = elements[i].str;
+ length = str.length();
+ flags = elements[i].flags;
+ // Filter on the basic type of the next element
+ if ((flags & shouldBe) == 0) {
+ continue;
+ }
+ // Handle the NOT_FIRST flag
+ if (first && (flags & NOT_FIRST) > 0) {
+ continue;
+ }
+ // Don't allow VOWEL followed a Vowel/Dipthong pair
+ if ((prev & VOWEL) > 0 && (flags & VOWEL) > 0
+ && (flags & DIPTHONG) > 0) {
+ continue;
+ }
+ // Don't allow us to overflow the buffer
+ if (length > size - curSize) {
+ continue;
+ }
+ // OK, we found an element which matches our criteria, let's do
+ // it
+ password += str;
+
+ // Handle UPPERS
+ if ((pwFlags & pwgen.UPPERS) > 0) {
+ if ((first || (flags & CONSONANT) > 0)
+ && (randnum.number(10) < 2)) {
+ int index = password.length() - length;
+ password = password.substring(0, index)
+ + str.toUpperCase();
+ featureFlags &= ~pwgen.UPPERS;
+ }
+ }
+
+ // Handle the AMBIGUOUS flag
+ if ((pwFlags & pwgen.AMBIGUOUS) > 0) {
+ for (char ambiguous : pwgen.AMBIGUOUS_STR.toCharArray()) {
+ if (password.contains(String.valueOf(ambiguous))) {
+ password = password.substring(0, curSize);
+ break;
+ }
+ }
+ if (password.length() == curSize)
+ continue;
+ }
+
+ curSize += length;
+
+ // Time to stop?
+ if (curSize >= size)
+ break;
+
+ // Handle DIGITS
+ if ((pwFlags & pwgen.DIGITS) > 0) {
+ if (!first && (randnum.number(10) < 3)) {
+ String val;
+ do {
+ cha = Character.forDigit(randnum.number(10), 10);
+ val = String.valueOf(cha);
+ } while ((pwFlags & pwgen.AMBIGUOUS) > 0
+ && pwgen.AMBIGUOUS_STR.contains(val));
+ password += val;
+ curSize++;
+
+ featureFlags &= ~pwgen.DIGITS;
+
+ first = true;
+ prev = 0;
+ shouldBe = randnum.number(2) == 1 ? VOWEL : CONSONANT;
+ continue;
+ }
+ }
+
+ // Handle SYMBOLS
+ if ((pwFlags & pwgen.SYMBOLS) > 0) {
+ if (!first && (randnum.number(10) < 2)) {
+ String val;
+ int num;
+ do {
+ num = randnum.number(pwgen.SYMBOLS_STR.length());
+ cha = pwgen.SYMBOLS_STR.toCharArray()[num];
+ val = String.valueOf(cha);
+ } while ((pwFlags & pwgen.AMBIGUOUS) > 0
+ && pwgen.AMBIGUOUS_STR.contains(val));
+ password += val;
+ curSize++;
+
+ featureFlags &= ~pwgen.SYMBOLS;
+ }
+ }
+
+ // OK, figure out what the next element should be
+ if (shouldBe == CONSONANT) {
+ shouldBe = VOWEL;
+ } else {
+ if ((prev & VOWEL) > 0 || (flags & DIPTHONG) > 0
+ || (randnum.number(10) > 3)) {
+ shouldBe = CONSONANT;
+ } else {
+ shouldBe = VOWEL;
+ }
+ }
+ prev = flags;
+ first = false;
+ }
+ } while ((featureFlags & (pwgen.UPPERS | pwgen.DIGITS | pwgen.SYMBOLS))
+ > 0);
+ return password;
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/pw_rand.java b/app/src/main/java/com/zeapo/pwdstore/pwgen/pw_rand.java
new file mode 100644
index 00000000..1f4acdec
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/pw_rand.java
@@ -0,0 +1,69 @@
+package com.zeapo.pwdstore.pwgen;
+
+public class pw_rand {
+
+ /**
+ * Generates a completely random password.
+ *
+ * @param size length of password to generate
+ * @param pwFlags flag field where set bits indicate conditions the
+ * generated password must meet
+ * <table summary ="bits of flag field">
+ * <tr><td>Bit</td><td>Condition</td></tr>
+ * <tr><td>0</td><td>include at least one number</td></tr>
+ * <tr><td>1</td><td>include at least one uppercase letter</td></tr>
+ * <tr><td>2</td><td>include at least one symbol</td></tr>
+ * <tr><td>3</td><td>don't include ambiguous characters</td></tr>
+ * <tr><td>4</td><td>don't include vowels</td></tr>
+ * </table>
+ * @return the generated password
+ */
+ public static String rand(int size, int pwFlags) {
+ String password = "";
+ char cha;
+ int i, featureFlags, num;
+ String val;
+
+ String bank = "";
+ if ((pwFlags & pwgen.DIGITS) > 0) {
+ bank += pwgen.DIGITS_STR;
+ }
+ if ((pwFlags & pwgen.UPPERS) > 0) {
+ bank += pwgen.UPPERS_STR;
+ }
+ bank += pwgen.LOWERS_STR;
+ if ((pwFlags & pwgen.SYMBOLS) > 0) {
+ bank += pwgen.SYMBOLS_STR;
+ }
+ do {
+ featureFlags = pwFlags;
+ i = 0;
+ while (i < size) {
+ num = randnum.number(bank.length());
+ cha = bank.toCharArray()[num];
+ val = String.valueOf(cha);
+ if ((pwFlags & pwgen.AMBIGUOUS) > 0
+ && pwgen.AMBIGUOUS_STR.contains(val)) {
+ continue;
+ }
+ if ((pwFlags & pwgen.NO_VOWELS) > 0
+ && pwgen.VOWELS_STR.contains(val)) {
+ continue;
+ }
+ password += val;
+ i++;
+ if (pwgen.DIGITS_STR.contains(val)) {
+ featureFlags &= ~pwgen.DIGITS;
+ }
+ if (pwgen.UPPERS_STR.contains(val)) {
+ featureFlags &= ~pwgen.UPPERS;
+ }
+ if (pwgen.SYMBOLS_STR.contains(val)) {
+ featureFlags &= ~pwgen.SYMBOLS;
+ }
+ }
+ } while ((featureFlags & (pwgen.UPPERS | pwgen.DIGITS | pwgen.SYMBOLS))
+ > 0);
+ return password;
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/pwgen.java b/app/src/main/java/com/zeapo/pwdstore/pwgen/pwgen.java
new file mode 100644
index 00000000..e08573f0
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/pwgen.java
@@ -0,0 +1,138 @@
+package com.zeapo.pwdstore.pwgen;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.ArrayList;
+
+public class pwgen {
+ static final int DIGITS = 0x0001;
+ static final int UPPERS = 0x0002;
+ static final int SYMBOLS = 0x0004;
+ static final int AMBIGUOUS = 0x0008;
+ static final int NO_VOWELS = 0x0010;
+
+ static final String DIGITS_STR = "0123456789";
+ static final String UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ static final String LOWERS_STR = "abcdefghijklmnopqrstuvwxyz";
+ static final String SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
+ static final String AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2";
+ static final String VOWELS_STR = "01aeiouyAEIOUY";
+
+ // No a, c, n, h, H, C, 1, N
+ private static final String pwOptions = "0ABsvy";
+
+ /**
+ * Sets password generation preferences.
+ *
+ * @param ctx context from which to retrieve SharedPreferences from
+ * preferences file 'pwgen'
+ * @param argv options for password generation
+ * <table summary="options for password generation">
+ * <tr><td>Option</td><td>Description</td></tr>
+ * <tr><td>0</td><td>don't include numbers</td></tr>
+ * <tr><td>A</td><td>don't include uppercase letters</td></tr>
+ * <tr><td>B</td><td>don't include ambiguous charactersl</td></tr>
+ * <tr><td>s</td><td>generate completely random passwords</td></tr>
+ * <tr><td>v</td><td>don't include vowels</td></tr>
+ * <tr><td>y</td><td>include at least one symbol</td></tr>
+ * </table>
+ * @param numArgv numerical options for password generation: length of
+ * generated passwords followed by number of passwords to
+ * generate
+ * @return <code>false</code> if a numerical options is invalid,
+ * <code>true</code> otherwise
+ */
+ public static boolean setPrefs(Context ctx, ArrayList<String> argv
+ , int... numArgv) {
+ SharedPreferences prefs
+ = ctx.getSharedPreferences("pwgen", Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+
+ for (char option : pwOptions.toCharArray()) {
+ if (argv.contains(String.valueOf(option))) {
+ editor.putBoolean(String.valueOf(option), true);
+ argv.remove(String.valueOf(option));
+ } else {
+ editor.putBoolean(String.valueOf(option), false);
+ }
+ }
+ for (int i = 0; i < numArgv.length && i < 2; i++) {
+ if (numArgv[i] <= 0) {
+ // Invalid password length or number of passwords
+ return false;
+ }
+ String name = i == 0 ? "length" : "num";
+ editor.putInt(name, numArgv[i]);
+ }
+ editor.apply();
+ return true;
+ }
+
+ /**
+ * Generates passwords using the preferences set by
+ * {@link #setPrefs(Context, ArrayList, int...)}.
+ *
+ * @param ctx context from which to retrieve SharedPreferences from
+ * preferences file 'pwgen'
+ * @return list of generated passwords
+ */
+ public static ArrayList<String> generate(Context ctx) {
+ SharedPreferences prefs
+ = ctx.getSharedPreferences("pwgen", Context.MODE_PRIVATE);
+
+ boolean phonemes = true;
+ int pwgenFlags = DIGITS | UPPERS;
+
+ for (char option : pwOptions.toCharArray()) {
+ if (prefs.getBoolean(String.valueOf(option), false)) {
+ switch(option) {
+ case '0':
+ pwgenFlags &= ~DIGITS;
+ break;
+ case 'A':
+ pwgenFlags &= ~UPPERS;
+ break;
+ case 'B':
+ pwgenFlags |= AMBIGUOUS;
+ break;
+ case 's':
+ phonemes = false;
+ // pwgenFlags = DIGITS | UPPERS;
+ break;
+ case 'y':
+ pwgenFlags |= SYMBOLS;
+ break;
+ case 'v':
+ phonemes = false;
+ pwgenFlags |= NO_VOWELS; // | DIGITS | UPPERS;
+ break;
+ }
+ }
+ }
+
+ int length = prefs.getInt("length", 8);
+ if (length < 5) {
+ phonemes = false;
+ }
+ if (length <= 2) {
+ pwgenFlags &= ~UPPERS;
+ }
+ if (length <= 1) {
+ pwgenFlags &= ~DIGITS;
+ }
+
+ ArrayList<String> passwords = new ArrayList<>();
+ int num = prefs.getInt("num", 1);
+ for (int i = 0; i < num; i++) {
+ if (phonemes) {
+ passwords.add(pw_phonemes.phonemes(length, pwgenFlags));
+ } else {
+ passwords.add(pw_rand.rand(length, pwgenFlags));
+ }
+ }
+ return passwords;
+ }
+
+}
+
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/randnum.java b/app/src/main/java/com/zeapo/pwdstore/pwgen/randnum.java
new file mode 100644
index 00000000..83cf4b03
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/randnum.java
@@ -0,0 +1,26 @@
+package com.zeapo.pwdstore.pwgen;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+public class randnum {
+ private static SecureRandom random;
+
+ static {
+ try {
+ random = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecurityException("SHA1PRNG not available", e);
+ }
+ }
+
+ /**
+ * Generate a random number n, where 0 &lt;= n &lt; maxNum.
+ *
+ * @param maxNum the bound on the random number to be returned
+ * @return the generated random number
+ */
+ public static int number(int maxNum) {
+ return random.nextInt(maxNum);
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java b/app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java
new file mode 100644
index 00000000..af905b5d
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java
@@ -0,0 +1,124 @@
+package com.zeapo.pwdstore;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.v7.app.AlertDialog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import com.zeapo.pwdstore.pwgen.pwgen;
+
+import java.util.ArrayList;
+
+
+/**
+ * A placeholder fragment containing a simple view.
+ */
+public class pwgenDialogFragment extends DialogFragment {
+
+ public pwgenDialogFragment() {
+ }
+
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ final View view = inflater.inflate(R.layout.fragment_pwgen, null);
+ builder.setView(view);
+
+ SharedPreferences prefs
+ = getActivity().getApplicationContext().getSharedPreferences("pwgen", Context.MODE_PRIVATE);
+
+ CheckBox checkBox = (CheckBox) view.findViewById(R.id.numerals);
+ checkBox.setChecked(!prefs.getBoolean("0", false));
+
+ checkBox = (CheckBox) view.findViewById(R.id.symbols);
+ checkBox.setChecked(prefs.getBoolean("y", false));
+
+ checkBox = (CheckBox) view.findViewById(R.id.uppercase);
+ checkBox.setChecked(!prefs.getBoolean("A", false));
+
+ checkBox = (CheckBox) view.findViewById(R.id.ambiguous);
+ checkBox.setChecked(!prefs.getBoolean("B", false));
+
+ checkBox = (CheckBox) view.findViewById(R.id.pronounceable);
+ checkBox.setChecked(!prefs.getBoolean("s", true));
+
+ TextView textView = (TextView) view.findViewById(R.id.lengthNumber);
+ textView.setText(Integer.toString(prefs.getInt("length", 20)));
+
+ builder.setPositiveButton(getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ TextView edit = (TextView) pwgenDialogFragment.this.getActivity().findViewById(R.id.crypto_password_edit);
+ TextView generate = (TextView) pwgenDialogFragment.this.getDialog().findViewById(R.id.passwordText);
+ edit.append(generate.getText());
+ }
+ });
+
+ builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ }
+ });
+
+ builder.setNeutralButton(getResources().getString(R.string.pwgen_generate), null);
+
+ final AlertDialog ad = builder.setTitle("Generate Password").create();
+ ad.setOnShowListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialog) {
+ setPreferences();
+ TextView textView = (TextView) view.findViewById(R.id.passwordText);
+ textView.setText(pwgen.generate(getActivity().getApplicationContext()).get(0));
+
+ Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
+ b.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setPreferences();
+ TextView textView = (TextView) getDialog().findViewById(R.id.passwordText);
+ textView.setText(pwgen.generate(getActivity().getApplicationContext()).get(0));
+ }
+ });
+ }
+ });
+ return ad;
+ }
+
+ private boolean setPreferences () {
+ ArrayList<String> preferences = new ArrayList<>();
+ if (!((CheckBox) getDialog().findViewById(R.id.numerals)).isChecked()) {
+ preferences.add("0");
+ }
+ if (((CheckBox) getDialog().findViewById(R.id.symbols)).isChecked()) {
+ preferences.add("y");
+ }
+ if (!((CheckBox) getDialog().findViewById(R.id.uppercase)).isChecked()) {
+ preferences.add("A");
+ }
+ if (!((CheckBox) getDialog().findViewById(R.id.ambiguous)).isChecked()) {
+ preferences.add("B");
+ }
+ if (!((CheckBox) getDialog().findViewById(R.id.pronounceable)).isChecked()) {
+ preferences.add("s");
+ }
+ TextView textView = (TextView) getDialog().findViewById(R.id.lengthNumber);
+ try {
+ int length = Integer.valueOf(textView.getText().toString());
+ return pwgen.setPrefs(getActivity().getApplicationContext(), preferences, length);
+ } catch(NumberFormatException e) {
+ return pwgen.setPrefs(getActivity().getApplicationContext(), preferences);
+ }
+ }
+}
+
diff --git a/app/src/main/res/layout/encrypt_layout.xml b/app/src/main/res/layout/encrypt_layout.xml
index 5fc93b3c..dcb6bf88 100644
--- a/app/src/main/res/layout/encrypt_layout.xml
+++ b/app/src/main/res/layout/encrypt_layout.xml
@@ -64,7 +64,15 @@
android:id="@+id/crypto_password_edit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:typeface="monospace"/>
+ android:typeface="monospace"
+ android:layout_weight="1"/>
+ <ImageButton
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_action_new"
+ android:background="@drawable/blue_rectangle"
+ android:id="@+id/generate_password"
+ android:onClick="handleClick"/>
</LinearLayout>
<TextView
diff --git a/app/src/main/res/layout/fragment_pwgen.xml b/app/src/main/res/layout/fragment_pwgen.xml
new file mode 100644
index 00000000..072a78a9
--- /dev/null
+++ b/app/src/main/res/layout/fragment_pwgen.xml
@@ -0,0 +1,108 @@
+<ScrollView
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context=".MainActivityFragment">
+
+ <EditText
+ android:id="@+id/passwordText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp"
+ android:editable="false"
+ android:textIsSelectable="true"
+ android:inputType="textVisiblePassword"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:weightSum="2">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/include"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/pwgen_include"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginBottom="8dp"/>
+
+ <CheckBox
+ android:id="@+id/numerals"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/pwgen_numerals"/>
+
+
+ <CheckBox
+ android:id="@+id/symbols"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/pwgen_symbols"/>
+
+ <CheckBox
+ android:id="@+id/uppercase"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/pwgen_uppercase"/>
+
+ <CheckBox
+ android:id="@+id/ambiguous"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/pwgen_ambiguous"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/length"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Length"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginBottom="8dp"
+ />
+
+ <EditText
+ android:id="@+id/lengthNumber"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp"
+ android:ems="10"
+ android:inputType="number"
+ />
+
+ <CheckBox
+ android:id="@+id/pronounceable"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Pronounceable"/>
+
+ </LinearLayout>
+
+
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a8db99ff..a9b03151 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -114,6 +114,14 @@
<string name="pref_recursive_filter">Recursive filtering</string>
<string name="pref_recursive_filter_hint">Recursively find passwords of the current directory.</string>
+ <!-- pwgen fragment -->
+ <string name="pwgen_generate">Generate</string>
+ <string name="pwgen_include">Include</string>
+ <string name="pwgen_numerals">Numerals</string>
+ <string name="pwgen_symbols">Symbols</string>
+ <string name="pwgen_uppercase">Uppercase</string>
+ <string name="pwgen_ambiguous">Ambiguous</string>
+
<!-- Misc -->
<string name="dialog_ok">OK</string>
<string name="dialog_yes">Yes</string>