aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarsh Shandilya <msfjarvis@gmail.com>2019-01-02 19:30:33 +0530
committerMohamed Zenadi <zeapo@users.noreply.github.com>2019-01-02 15:00:33 +0100
commitb267321d540984f2f5023f7eefcc475c30d8a0c1 (patch)
tree37711e2f99c5bededee2ca762df170e335f11b56
parenta82fb391d3c6e9b1151664100640114286843ec7 (diff)
Rewrite pwgen package in Kotlin (#463)
* Rename classes to not be ugly as all hell Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com> * PgpActivity: Cleanup Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com> * Rewrite pwgen package in Kotlin Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com> * PRNGFixes: Remove constant conditions Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com>
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java (renamed from app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java)16
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.java2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt48
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java333
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.kt310
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/PasswordGenerator.kt125
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/Phonemes.kt208
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/RandomNumberGenerator.kt26
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/pwgen/RandomPasswordGenerator.kt72
-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.java70
-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/res/values-ar/strings.xml2
-rw-r--r--app/src/main/res/values-cs/strings.xml2
-rw-r--r--app/src/main/res/values-de/strings.xml2
-rw-r--r--app/src/main/res/values-fr/strings.xml2
-rw-r--r--app/src/main/res/values-ja/strings.xml2
-rw-r--r--app/src/main/res/values-ru/strings.xml2
-rw-r--r--app/src/main/res/values/strings.xml2
20 files changed, 780 insertions, 814 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java b/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java
index d7d1ae45..1a51f6f2 100644
--- a/app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java
@@ -16,7 +16,7 @@ import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
-import com.zeapo.pwdstore.pwgen.pwgen;
+import com.zeapo.pwdstore.pwgen.PasswordGenerator;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -25,9 +25,9 @@ import java.util.ArrayList;
/**
* A placeholder fragment containing a simple view.
*/
-public class pwgenDialogFragment extends DialogFragment {
+public class PasswordGeneratorDialogFragment extends DialogFragment {
- public pwgenDialogFragment() {
+ public PasswordGeneratorDialogFragment() {
}
@@ -44,7 +44,7 @@ public class pwgenDialogFragment extends DialogFragment {
builder.setView(view);
SharedPreferences prefs
- = getActivity().getApplicationContext().getSharedPreferences("pwgen", Context.MODE_PRIVATE);
+ = getActivity().getApplicationContext().getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE);
CheckBox checkBox = view.findViewById(R.id.numerals);
checkBox.setChecked(!prefs.getBoolean("0", false));
@@ -90,7 +90,7 @@ public class pwgenDialogFragment extends DialogFragment {
public void onShow(DialogInterface dialog) {
setPreferences();
TextView textView = view.findViewById(R.id.passwordText);
- textView.setText(pwgen.generate(getActivity().getApplicationContext()).get(0));
+ textView.setText(PasswordGenerator.INSTANCE.generate(getActivity().getApplicationContext()).get(0));
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
b.setOnClickListener(new View.OnClickListener() {
@@ -98,7 +98,7 @@ public class pwgenDialogFragment extends DialogFragment {
public void onClick(View v) {
setPreferences();
TextView textView = view.findViewById(R.id.passwordText);
- textView.setText(pwgen.generate(callingActivity.getApplicationContext()).get(0));
+ textView.setText(PasswordGenerator.INSTANCE.generate(callingActivity.getApplicationContext()).get(0));
}
});
}
@@ -126,9 +126,9 @@ public class pwgenDialogFragment extends DialogFragment {
EditText editText = getDialog().findViewById(R.id.lengthNumber);
try {
int length = Integer.valueOf(editText.getText().toString());
- pwgen.setPrefs(getActivity().getApplicationContext(), preferences, length);
+ PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences, length);
} catch(NumberFormatException e) {
- pwgen.setPrefs(getActivity().getApplicationContext(), preferences);
+ PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences);
}
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
index 2ab94bd8..a6e399ef 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
@@ -116,7 +116,7 @@ public class PasswordStore extends AppCompatActivity {
shortcutManager = getSystemService(ShortcutManager.class);
}
activity = this;
- PRNGFixes.apply();
+ PRNGFixes.INSTANCE.apply();
// If user opens app with permission granted then revokes and returns,
// prevent attempt to create password list fragment
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
index 53582973..63fff21d 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
@@ -1,8 +1,5 @@
package com.zeapo.pwdstore.crypto
-import android.annotation.SuppressLint
-import android.app.Activity
-import android.app.AlertDialog
import android.app.PendingIntent
import android.content.ClipData
import android.content.ClipboardManager
@@ -32,11 +29,12 @@ import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.zeapo.pwdstore.PasswordEntry
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.UserPreference
-import com.zeapo.pwdstore.pwgenDialogFragment
+import com.zeapo.pwdstore.PasswordGeneratorDialogFragment
import com.zeapo.pwdstore.utils.Otp
import kotlinx.android.synthetic.main.decrypt_layout.*
import kotlinx.android.synthetic.main.encrypt_layout.*
@@ -117,7 +115,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
setContentView(R.layout.encrypt_layout)
generate_password?.setOnClickListener {
- pwgenDialogFragment().show(supportFragmentManager, "generator")
+ PasswordGeneratorDialogFragment().show(supportFragmentManager, "generator")
}
title = getString(R.string.new_password_title)
@@ -324,7 +322,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val editor = settings.edit()
editor.putBoolean("hotp_remember_check", true)
editor.putBoolean("hotp_remember_choice", true)
- editor.commit()
+ editor.apply()
}
}
}
@@ -334,7 +332,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val editor = settings.edit()
editor.putBoolean("hotp_remember_check", true)
editor.putBoolean("hotp_remember_choice", false)
- editor.commit()
+ editor.apply()
}
}
val updateDialog = dialogBuilder.create()
@@ -439,7 +437,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
private fun editPassword() {
setContentView(R.layout.encrypt_layout)
generate_password?.setOnClickListener {
- pwgenDialogFragment().show(supportFragmentManager, "generator")
+ PasswordGeneratorDialogFragment().show(supportFragmentManager, "generator")
}
title = getString(R.string.edit_password_title)
@@ -545,26 +543,26 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
- Log.d(TAG, "onActivityResult resultCode: " + resultCode)
+ Log.d(TAG, "onActivityResult resultCode: $resultCode")
if (data == null) {
- setResult(Activity.RESULT_CANCELED, null)
+ setResult(AppCompatActivity.RESULT_CANCELED, null)
finish()
return
}
// try again after user interaction
- if (resultCode == Activity.RESULT_OK) {
+ if (resultCode == AppCompatActivity.RESULT_OK) {
when (requestCode) {
REQUEST_DECRYPT -> decryptAndVerify(data)
REQUEST_KEY_ID -> getKeyIds(data)
else -> {
- setResult(Activity.RESULT_OK)
+ setResult(AppCompatActivity.RESULT_OK)
finish()
}
}
- } else if (resultCode == Activity.RESULT_CANCELED) {
- setResult(Activity.RESULT_CANCELED, data)
+ } else if (resultCode == AppCompatActivity.RESULT_CANCELED) {
+ setResult(AppCompatActivity.RESULT_CANCELED, data)
finish()
}
}
@@ -581,7 +579,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
return if (shown) charSequence else super.getTransformation("12345", view)
}
- @SuppressLint("ClickableViewAccessibility")
+ @Suppress("ClickableViewAccessibility")
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
@@ -608,7 +606,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
var clearAfter = 45
try {
- clearAfter = Integer.parseInt(settings.getString("general_show_time", "45"))
+ clearAfter = Integer.parseInt(settings.getString("general_show_time", "45") as String)
} catch (e: NumberFormatException) {
// ignore and keep default
}
@@ -662,7 +660,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
return DateUtils.getRelativeTimeSpanString(this, timeStamp.toLong() * 1000, true)
}
- @SuppressLint("StaticFieldLeak")
+ @Suppress("StaticFieldLeak")
inner class DelayShow(val activity: PgpActivity) : AsyncTask<Void, Int, Boolean>() {
private val pb: ProgressBar by lazy { pbLoading }
private var skip = false
@@ -686,7 +684,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
override fun onPreExecute() {
showTime = try {
- Integer.parseInt(settings.getString("general_show_time", "45"))
+ Integer.parseInt(settings.getString("general_show_time", "45") as String)
} catch (e: NumberFormatException) {
45
}
@@ -744,7 +742,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
if (crypto_password_show != null) {
// clear password; if decrypt changed to encrypt layout via edit button, no need
if(passwordEntry?.hotpIsIncremented() == false) {
- setResult(Activity.RESULT_CANCELED)
+ setResult(AppCompatActivity.RESULT_CANCELED)
}
passwordEntry = null
crypto_password_show.text = ""
@@ -788,7 +786,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
* Gets the name of the password (excluding .gpg)
*/
fun getName(fullPath: String): String {
- return FilenameUtils.getBaseName(fullPath);
+ return FilenameUtils.getBaseName(fullPath)
}
/**
@@ -797,16 +795,16 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
@JvmStatic
fun getLongName(fullPath: String, repositoryPath: String, basename: String): String {
var relativePath = getRelativePath(fullPath, repositoryPath)
- if (relativePath.isNotEmpty() && relativePath != "/") {
+ return if (relativePath.isNotEmpty() && relativePath != "/") {
// remove preceding '/'
- relativePath = relativePath.substring(1);
+ relativePath = relativePath.substring(1)
if (relativePath.endsWith('/')) {
- return relativePath + basename
+ relativePath + basename
} else {
- return "$relativePath/$basename"
+ "$relativePath/$basename"
}
} else {
- return basename
+ basename
}
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java b/app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java
deleted file mode 100644
index aabc27b2..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java
+++ /dev/null
@@ -1,333 +0,0 @@
-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.
- */
- private static DataInputStream sUrandomIn;
-
- /**
- * Output stream for writing to Linux PRNG or {@code null} if not yet
- * opened.
- */
- 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;
- }
- }
-
- @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
- @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/PRNGFixes.kt b/app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.kt
new file mode 100644
index 00000000..98c3735f
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.kt
@@ -0,0 +1,310 @@
+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 [.apply] before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in the
+ * application's `onCreate`.
+ */
+object PRNGFixes {
+
+ private const val VERSION_CODE_JELLY_BEAN_MR2 = 18
+ private val BUILD_FINGERPRINT_AND_DEVICE_SERIAL = buildFingerprintAndDeviceSerial
+
+ private val buildFingerprintAndDeviceSerial: ByteArray
+ get() {
+ val result = StringBuilder()
+ val fingerprint = Build.FINGERPRINT
+ if (fingerprint != null) {
+ result.append(fingerprint)
+ }
+ // TODO: Build#SERIAL is deprecated and should be removed or replaced
+ val serial = Build.SERIAL
+ if (serial != null) {
+ result.append(serial)
+ }
+ try {
+ return result.toString().toByteArray(charset("UTF-8"))
+ } catch (e: UnsupportedEncodingException) {
+ throw RuntimeException("UTF-8 encoding not supported")
+ }
+ }
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be applied.
+ */
+ fun 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.
+ */
+ @Throws(SecurityException::class)
+ private fun applyOpenSSLFix() {
+ if (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", ByteArray::class.java)
+ .invoke(null, generateSeed())
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ val bytesRead = Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto"
+ )
+ .getMethod("RAND_load_file", String::class.java, Long::class.javaPrimitiveType)
+ .invoke(null, "/dev/urandom", 1024) as Int
+ if (bytesRead != 1024) {
+ throw IOException(
+ "Unexpected number of bytes read from Linux PRNG: $bytesRead"
+ )
+ }
+ } catch (e: Exception) {
+ throw SecurityException("Failed to seed OpenSSL PRNG", e)
+ }
+ }
+
+ /**
+ * Installs a Linux PRNG-backed `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.
+ */
+ @Throws(SecurityException::class)
+ private fun installLinuxPRNGSecureRandom() {
+ 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.
+ val secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG")
+ if (secureRandomProviders == null
+ || secureRandomProviders.isEmpty()
+ || LinuxPRNGSecureRandomProvider::class.java != secureRandomProviders[0].javaClass
+ ) {
+ Security.insertProviderAt(LinuxPRNGSecureRandomProvider(), 1)
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ val rng1 = SecureRandom()
+ if (LinuxPRNGSecureRandomProvider::class.java != rng1.provider.javaClass) {
+ throw SecurityException(
+ "new SecureRandom() backed by wrong Provider: " + rng1.provider.javaClass
+ )
+ }
+
+ val rng2: SecureRandom
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG")
+ } catch (e: NoSuchAlgorithmException) {
+ throw SecurityException("SHA1PRNG not available", e)
+ }
+
+ if (LinuxPRNGSecureRandomProvider::class.java != rng2.provider.javaClass) {
+ throw SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ + " Provider: " + rng2.provider.javaClass
+ )
+ }
+ }
+
+ /**
+ * `Provider` of `SecureRandom` engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private class LinuxPRNGSecureRandomProvider :
+ Provider("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + " /dev/urandom") {
+ init {
+ // 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.java.name)
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software")
+ }
+ }
+
+ /**
+ * [SecureRandomSpi] which passes all requests to the Linux PRNG
+ * (`/dev/urandom`).
+ */
+ class LinuxPRNGSecureRandom : SecureRandomSpi() {
+
+ /**
+ * 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 var mSeeded: Boolean = false
+
+ private// 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.
+ val urandomInputStream: DataInputStream
+ get() = synchronized(sLock) {
+ if (sUrandomIn == null) {
+ try {
+ sUrandomIn = DataInputStream(
+ FileInputStream(URANDOM_FILE)
+ )
+ } catch (e: IOException) {
+ throw SecurityException(
+ "Failed to open "
+ + URANDOM_FILE + " for reading", e
+ )
+ }
+ }
+ return sUrandomIn as DataInputStream
+ }
+
+ private val urandomOutputStream: OutputStream
+ @Throws(IOException::class)
+ get() = synchronized(sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = FileOutputStream(URANDOM_FILE)
+ }
+ return sUrandomOut as OutputStream
+ }
+
+ @Synchronized
+ override fun engineSetSeed(bytes: ByteArray) {
+ try {
+ val out: OutputStream = urandomOutputStream
+ out.write(bytes)
+ out.flush()
+ } catch (e: IOException) {
+ // On a small fraction of devices /dev/urandom is not writable.
+ // Log and ignore.
+ Log.w(
+ PRNGFixes::class.java.simpleName,
+ "Failed to mix seed into $URANDOM_FILE"
+ )
+ } finally {
+ mSeeded = true
+ }
+ }
+
+ @Synchronized
+ override fun engineNextBytes(bytes: ByteArray) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed())
+ }
+
+ try {
+ val `in`: DataInputStream = urandomInputStream
+ synchronized(`in`) {
+ `in`.readFully(bytes)
+ }
+ } catch (e: IOException) {
+ throw SecurityException(
+ "Failed to read from $URANDOM_FILE", e
+ )
+ }
+ }
+
+ override fun engineGenerateSeed(size: Int): ByteArray {
+ val seed = ByteArray(size)
+ engineNextBytes(seed)
+ return seed
+ }
+
+ companion object {
+
+ /*
+ * 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 val URANDOM_FILE = File("/dev/urandom")
+
+ private val sLock = Any()
+
+ /**
+ * Input stream for reading from Linux PRNG or `null` if not yet
+ * opened.
+ */
+ private var sUrandomIn: DataInputStream? = null
+
+ /**
+ * Output stream for writing to Linux PRNG or `null` if not yet
+ * opened.
+ */
+ private var sUrandomOut: OutputStream? = null
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private fun generateSeed(): ByteArray {
+ try {
+ val seedBuffer = ByteArrayOutputStream()
+ val seedBufferOut = 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 (e: IOException) {
+ throw SecurityException("Failed to generate seed", e)
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/PasswordGenerator.kt b/app/src/main/java/com/zeapo/pwdstore/pwgen/PasswordGenerator.kt
new file mode 100644
index 00000000..0374236c
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/PasswordGenerator.kt
@@ -0,0 +1,125 @@
+package com.zeapo.pwdstore.pwgen
+
+import android.content.Context
+import android.content.SharedPreferences
+
+import java.util.ArrayList
+
+object PasswordGenerator {
+ internal val DIGITS = 0x0001
+ internal val UPPERS = 0x0002
+ internal val SYMBOLS = 0x0004
+ internal val AMBIGUOUS = 0x0008
+ internal val NO_VOWELS = 0x0010
+
+ internal val DIGITS_STR = "0123456789"
+ internal val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ internal val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
+ internal val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+ internal val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
+ internal val VOWELS_STR = "01aeiouyAEIOUY"
+
+ // No a, c, n, h, H, C, 1, N
+ private val pwOptions = "0ABsvy"
+
+ /**
+ * Sets password generation preferences.
+ *
+ * @param ctx context from which to retrieve SharedPreferences from
+ * preferences file 'PasswordGenerator'
+ * @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 `false` if a numerical options is invalid,
+ * `true` otherwise
+ */
+ fun setPrefs(ctx: Context, argv: ArrayList<String>, vararg numArgv: Int): Boolean {
+ val prefs = ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
+ val editor = prefs.edit()
+
+ for (option in pwOptions.toCharArray()) {
+ if (argv.contains(option.toString())) {
+ editor.putBoolean(option.toString(), true)
+ argv.remove(option.toString())
+ } else {
+ editor.putBoolean(option.toString(), false)
+ }
+ }
+ var i = 0
+ while (i < numArgv.size && i < 2) {
+ if (numArgv[i] <= 0) {
+ // Invalid password length or number of passwords
+ return false
+ }
+ val name = if (i == 0) "length" else "num"
+ editor.putInt(name, numArgv[i])
+ i++
+ }
+ editor.apply()
+ return true
+ }
+
+ /**
+ * Generates passwords using the preferences set by
+ * [.setPrefs].
+ *
+ * @param ctx context from which to retrieve SharedPreferences from
+ * preferences file 'PasswordGenerator'
+ * @return list of generated passwords
+ */
+ fun generate(ctx: Context): ArrayList<String> {
+ val prefs = ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
+
+ var phonemes = true
+ var pwgenFlags = DIGITS or UPPERS
+
+ for (option in pwOptions.toCharArray()) {
+ if (prefs.getBoolean(option.toString(), false)) {
+ when (option) {
+ '0' -> pwgenFlags = pwgenFlags and DIGITS.inv()
+ 'A' -> pwgenFlags = pwgenFlags and UPPERS.inv()
+ 'B' -> pwgenFlags = pwgenFlags or AMBIGUOUS
+ 's' -> phonemes = false
+ 'y' -> pwgenFlags = pwgenFlags or SYMBOLS
+ 'v' -> {
+ phonemes = false
+ pwgenFlags = pwgenFlags or NO_VOWELS // | DIGITS | UPPERS;
+ }
+ }// pwgenFlags = DIGITS | UPPERS;
+ }
+ }
+
+ val length = prefs.getInt("length", 8)
+ if (length < 5) {
+ phonemes = false
+ }
+ if (length <= 2) {
+ pwgenFlags = pwgenFlags and UPPERS.inv()
+ }
+ if (length <= 1) {
+ pwgenFlags = pwgenFlags and DIGITS.inv()
+ }
+
+ val passwords = ArrayList<String>()
+ val num = prefs.getInt("num", 1)
+ for (i in 0 until num) {
+ if (phonemes) {
+ passwords.add(Phonemes.phonemes(length, pwgenFlags))
+ } else {
+ passwords.add(RandomPasswordGenerator.rand(length, pwgenFlags))
+ }
+ }
+ return passwords
+ }
+}
+
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/Phonemes.kt b/app/src/main/java/com/zeapo/pwdstore/pwgen/Phonemes.kt
new file mode 100644
index 00000000..4ec204bc
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/Phonemes.kt
@@ -0,0 +1,208 @@
+package com.zeapo.pwdstore.pwgen
+
+internal object Phonemes {
+ private const val CONSONANT = 0x0001
+ private const val VOWEL = 0x0002
+ private const val DIPTHONG = 0x0004
+ private const val NOT_FIRST = 0x0008
+
+ private val elements = arrayOf(
+ Element("a", VOWEL),
+ Element("ae", VOWEL or DIPTHONG),
+ Element("ah", VOWEL or DIPTHONG),
+ Element("ai", VOWEL or DIPTHONG),
+ Element("b", CONSONANT),
+ Element("c", CONSONANT),
+ Element("ch", CONSONANT or DIPTHONG),
+ Element("d", CONSONANT),
+ Element("e", VOWEL),
+ Element("ee", VOWEL or DIPTHONG),
+ Element("ei", VOWEL or DIPTHONG),
+ Element("f", CONSONANT),
+ Element("g", CONSONANT),
+ Element("gh", CONSONANT or DIPTHONG or NOT_FIRST),
+ Element("h", CONSONANT),
+ Element("i", VOWEL),
+ Element("ie", VOWEL or DIPTHONG),
+ Element("j", CONSONANT),
+ Element("k", CONSONANT),
+ Element("l", CONSONANT),
+ Element("m", CONSONANT),
+ Element("n", CONSONANT),
+ Element("ng", CONSONANT or DIPTHONG or NOT_FIRST),
+ Element("o", VOWEL),
+ Element("oh", VOWEL or DIPTHONG),
+ Element("oo", VOWEL or DIPTHONG),
+ Element("p", CONSONANT),
+ Element("ph", CONSONANT or DIPTHONG),
+ Element("qu", CONSONANT or DIPTHONG),
+ Element("r", CONSONANT),
+ Element("s", CONSONANT),
+ Element("sh", CONSONANT or DIPTHONG),
+ Element("t", CONSONANT),
+ Element("th", CONSONANT or DIPTHONG),
+ Element("u", VOWEL),
+ Element("v", CONSONANT),
+ Element("w", CONSONANT),
+ Element("x", CONSONANT),
+ Element("y", CONSONANT),
+ Element("z", CONSONANT)
+ )
+
+ private val NUM_ELEMENTS = elements.size
+
+ private class Element internal constructor(internal var str: String, internal var flags: Int)
+
+ /**
+ * 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
+ */
+ fun phonemes(size: Int, pwFlags: Int): String {
+ var password: String
+ var curSize: Int
+ var i: Int
+ var length: Int
+ var flags: Int
+ var featureFlags: Int
+ var prev: Int
+ var shouldBe: Int
+ var first: Boolean
+ var str: String
+ var cha: Char
+
+ do {
+ password = ""
+ featureFlags = pwFlags
+ curSize = 0
+ prev = 0
+ first = true
+
+ shouldBe = if (RandomNumberGenerator.number(2) == 1) VOWEL else CONSONANT
+
+ while (curSize < size) {
+ i = RandomNumberGenerator.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 and shouldBe == 0) {
+ continue
+ }
+ // Handle the NOT_FIRST flag
+ if (first && flags and NOT_FIRST > 0) {
+ continue
+ }
+ // Don't allow VOWEL followed a Vowel/Dipthong pair
+ if (prev and VOWEL > 0 && flags and VOWEL > 0
+ && flags and 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 and PasswordGenerator.UPPERS > 0) {
+ if ((first || flags and CONSONANT > 0) && RandomNumberGenerator.number(10) < 2) {
+ val index = password.length - length
+ password = password.substring(0, index) + str.toUpperCase()
+ featureFlags = featureFlags and PasswordGenerator.UPPERS.inv()
+ }
+ }
+
+ // Handle the AMBIGUOUS flag
+ if (pwFlags and PasswordGenerator.AMBIGUOUS > 0) {
+ for (ambiguous in PasswordGenerator.AMBIGUOUS_STR.toCharArray()) {
+ if (password.contains(ambiguous.toString())) {
+ password = password.substring(0, curSize)
+ break
+ }
+ }
+ if (password.length == curSize)
+ continue
+ }
+
+ curSize += length
+
+ // Time to stop?
+ if (curSize >= size)
+ break
+
+ // Handle DIGITS
+ if (pwFlags and PasswordGenerator.DIGITS > 0) {
+ if (!first && RandomNumberGenerator.number(10) < 3) {
+ var `val`: String
+ do {
+ cha = Character.forDigit(RandomNumberGenerator.number(10), 10)
+ `val` = cha.toString()
+ } while (pwFlags and PasswordGenerator.AMBIGUOUS > 0 && PasswordGenerator.AMBIGUOUS_STR.contains(
+ `val`
+ )
+ )
+ password += `val`
+ curSize++
+
+ featureFlags = featureFlags and PasswordGenerator.DIGITS.inv()
+
+ first = true
+ prev = 0
+ shouldBe = if (RandomNumberGenerator.number(2) == 1) VOWEL else CONSONANT
+ continue
+ }
+ }
+
+ // Handle SYMBOLS
+ if (pwFlags and PasswordGenerator.SYMBOLS > 0) {
+ if (!first && RandomNumberGenerator.number(10) < 2) {
+ var `val`: String
+ var num: Int
+ do {
+ num = RandomNumberGenerator.number(PasswordGenerator.SYMBOLS_STR.length)
+ cha = PasswordGenerator.SYMBOLS_STR.toCharArray()[num]
+ `val` = cha.toString()
+ } while (pwFlags and PasswordGenerator.AMBIGUOUS > 0 && PasswordGenerator.AMBIGUOUS_STR.contains(
+ `val`
+ )
+ )
+ password += `val`
+ curSize++
+
+ featureFlags = featureFlags and PasswordGenerator.SYMBOLS.inv()
+ }
+ }
+
+ // OK, figure out what the next Element should be
+ shouldBe = if (shouldBe == CONSONANT) {
+ VOWEL
+ } else {
+ if (prev and VOWEL > 0 || flags and DIPTHONG > 0
+ || RandomNumberGenerator.number(10) > 3
+ ) {
+ CONSONANT
+ } else {
+ VOWEL
+ }
+ }
+ prev = flags
+ first = false
+ }
+ } while (featureFlags and (PasswordGenerator.UPPERS or PasswordGenerator.DIGITS or PasswordGenerator.SYMBOLS) > 0)
+ return password
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/RandomNumberGenerator.kt b/app/src/main/java/com/zeapo/pwdstore/pwgen/RandomNumberGenerator.kt
new file mode 100644
index 00000000..e208bc75
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/RandomNumberGenerator.kt
@@ -0,0 +1,26 @@
+package com.zeapo.pwdstore.pwgen
+
+import java.security.NoSuchAlgorithmException
+import java.security.SecureRandom
+
+internal object RandomNumberGenerator {
+ private var random: SecureRandom
+
+ init {
+ try {
+ random = SecureRandom.getInstance("SHA1PRNG")
+ } catch (e: NoSuchAlgorithmException) {
+ throw 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
+ */
+ fun number(maxNum: Int): Int {
+ return random.nextInt(maxNum)
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgen/RandomPasswordGenerator.kt b/app/src/main/java/com/zeapo/pwdstore/pwgen/RandomPasswordGenerator.kt
new file mode 100644
index 00000000..632fa579
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/pwgen/RandomPasswordGenerator.kt
@@ -0,0 +1,72 @@
+package com.zeapo.pwdstore.pwgen
+
+internal object RandomPasswordGenerator {
+
+ /**
+ * 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
+ */
+ fun rand(size: Int, pwFlags: Int): String {
+ var password: String
+ var cha: Char
+ var i: Int
+ var featureFlags: Int
+ var num: Int
+ var `val`: String
+
+ var bank = ""
+ if (pwFlags and PasswordGenerator.DIGITS > 0) {
+ bank += PasswordGenerator.DIGITS_STR
+ }
+ if (pwFlags and PasswordGenerator.UPPERS > 0) {
+ bank += PasswordGenerator.UPPERS_STR
+ }
+ bank += PasswordGenerator.LOWERS_STR
+ if (pwFlags and PasswordGenerator.SYMBOLS > 0) {
+ bank += PasswordGenerator.SYMBOLS_STR
+ }
+ do {
+ password = ""
+ featureFlags = pwFlags
+ i = 0
+ while (i < size) {
+ num = RandomNumberGenerator.number(bank.length)
+ cha = bank.toCharArray()[num]
+ `val` = cha.toString()
+ if (pwFlags and PasswordGenerator.AMBIGUOUS > 0 && PasswordGenerator.AMBIGUOUS_STR.contains(
+ `val`
+ )
+ ) {
+ continue
+ }
+ if (pwFlags and PasswordGenerator.NO_VOWELS > 0 && PasswordGenerator.VOWELS_STR.contains(`val`)) {
+ continue
+ }
+ password += `val`
+ i++
+ if (PasswordGenerator.DIGITS_STR.contains(`val`)) {
+ featureFlags = featureFlags and PasswordGenerator.DIGITS.inv()
+ }
+ if (PasswordGenerator.UPPERS_STR.contains(`val`)) {
+ featureFlags = featureFlags and PasswordGenerator.UPPERS.inv()
+ }
+ if (PasswordGenerator.SYMBOLS_STR.contains(`val`)) {
+ featureFlags = featureFlags and PasswordGenerator.SYMBOLS.inv()
+ }
+ }
+ } while (featureFlags and (PasswordGenerator.UPPERS or PasswordGenerator.DIGITS or PasswordGenerator.SYMBOLS) > 0)
+ return password
+ }
+}
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
deleted file mode 100644
index 297c21c5..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/pwgen/pw_phonemes.java
+++ /dev/null
@@ -1,206 +0,0 @@
-package com.zeapo.pwdstore.pwgen;
-
-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
deleted file mode 100644
index 9afd5e4a..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/pwgen/pw_rand.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.zeapo.pwdstore.pwgen;
-
-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 {
- password = "";
- 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
deleted file mode 100644
index e08573f0..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/pwgen/pwgen.java
+++ /dev/null
@@ -1,138 +0,0 @@
-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
deleted file mode 100644
index 583d0863..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/pwgen/randnum.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.zeapo.pwdstore.pwgen;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-
-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/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 95c95f7f..172e8949 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -104,7 +104,7 @@
<string name="prefs_export_passwords_title">تصدير كلمات السر</string>
<string name="prefs_version">النسخة</string>
- <!-- pwgen fragment -->
+ <!-- PasswordGenerator fragment -->
<string name="pwgen_generate">توليد</string>
<string name="pwgen_include">تحتوي على</string>
<string name="pwgen_length">الطول</string>
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 407b6367..b9fba7ef 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -106,7 +106,7 @@
<string name="pref_external_repository_summary">Použít externí repozitář hesel</string>
<string name="pref_select_external_repository">Vybrat externí repozitář</string>
- <!-- pwgen fragment -->
+ <!-- PasswordGenerator fragment -->
<string name="pwgen_generate">Generovat</string>
<string name="pwgen_include">Vložit</string>
<string name="pwgen_numerals">Čísla</string>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index bfa35daf..c60da262 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -131,7 +131,7 @@
<string name="prefs_export_passwords_summary">Exportiert die verschlüsselten Passwörter in ein externes Verzeichnis</string>
<string name="prefs_version">Version</string>
- <!-- pwgen fragment -->
+ <!-- PasswordGenerator fragment -->
<string name="pwgen_generate">Generieren</string>
<string name="pwgen_include">Include</string>
<string name="pwgen_numerals">Nummern</string>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index a3905ced..00aadc80 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -110,7 +110,7 @@
<string name="pref_external_repository_summary">Utilise un dépôt externe pour les mots de passe</string>
<string name="pref_select_external_repository">Choisissez un dépôt externe</string>
- <!-- pwgen fragment -->
+ <!-- PasswordGenerator fragment -->
<string name="pwgen_generate">Générer</string>
<string name="pwgen_include">Inclure</string>
<string name="pwgen_length">Longueur</string>
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index ca1c08da..b5c19706 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -110,7 +110,7 @@
<string name="pref_external_repository_summary">外部パスワードリポジトリを使用する</string>
<string name="pref_select_external_repository">外部リポジトリを選択</string>
- <!-- pwgen fragment -->
+ <!-- PasswordGenerator fragment -->
<string name="pwgen_generate">生成</string>
<string name="pwgen_include">含める</string>
<string name="pwgen_length">長さ</string>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 6863d800..e0214ea3 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -133,7 +133,7 @@
<string name="prefs_export_passwords_summary">Экспортировать пароли в открытом виде во внешнее хранилище</string>
<string name="prefs_version">Версия</string>
- <!-- pwgen fragment -->
+ <!-- PasswordGenerator fragment -->
<string name="pwgen_generate">Сгенерировать</string>
<string name="pwgen_include">Включая</string>
<string name="pwgen_length">Длина</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 825533db..3f229f1d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -172,7 +172,7 @@
<string name="prefs_export_passwords_summary">Exports the encrypted passwords to an external directory</string>
<string name="prefs_version">Version</string>
- <!-- pwgen fragment -->
+ <!-- PasswordGenerator fragment -->
<string name="pwgen_title">Generate Password</string>
<string name="pwgen_generate">Generate</string>
<string name="pwgen_include">Include</string>