diff options
author | Mohamed Zenadi <zeapo@users.noreply.github.com> | 2015-07-11 19:41:33 +0200 |
---|---|---|
committer | Mohamed Zenadi <zeapo@users.noreply.github.com> | 2015-07-11 19:41:33 +0200 |
commit | 62c20d323cee5c543fd17cdc6e817efd48d1e47b (patch) | |
tree | 02a6e433ec610b8a3f2af780e743ec149afd5bee | |
parent | 97c5c5da9572de48842dc3739c260af75c4915f8 (diff) | |
parent | 32cf7f7813c89f7eff43712705b1d1453637a180 (diff) |
Merge pull request #101 from wongma7/master
Adding support for password generation
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/PasswordStore.java | 2 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java | 5 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java | 336 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/pwgen/pw_phonemes.java | 206 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/pwgen/pw_rand.java | 69 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/pwgen/pwgen.java | 138 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/pwgen/randnum.java | 26 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java | 124 | ||||
-rw-r--r-- | app/src/main/res/layout/encrypt_layout.xml | 10 | ||||
-rw-r--r-- | app/src/main/res/layout/fragment_pwgen.xml | 108 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 8 |
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 <= n < 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> |