From b267321d540984f2f5023f7eefcc475c30d8a0c1 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 2 Jan 2019 19:30:33 +0530 Subject: Rewrite pwgen package in Kotlin (#463) * Rename classes to not be ugly as all hell Signed-off-by: Harsh Shandilya * PgpActivity: Cleanup Signed-off-by: Harsh Shandilya * Rewrite pwgen package in Kotlin Signed-off-by: Harsh Shandilya * PRNGFixes: Remove constant conditions Signed-off-by: Harsh Shandilya --- .../pwdstore/PasswordGeneratorDialogFragment.java | 135 +++++++++ .../java/com/zeapo/pwdstore/PasswordStore.java | 2 +- .../java/com/zeapo/pwdstore/crypto/PgpActivity.kt | 48 ++- .../java/com/zeapo/pwdstore/pwgen/PRNGFixes.java | 333 --------------------- .../java/com/zeapo/pwdstore/pwgen/PRNGFixes.kt | 310 +++++++++++++++++++ .../com/zeapo/pwdstore/pwgen/PasswordGenerator.kt | 125 ++++++++ .../main/java/com/zeapo/pwdstore/pwgen/Phonemes.kt | 208 +++++++++++++ .../zeapo/pwdstore/pwgen/RandomNumberGenerator.kt | 26 ++ .../pwdstore/pwgen/RandomPasswordGenerator.kt | 72 +++++ .../java/com/zeapo/pwdstore/pwgen/pw_phonemes.java | 206 ------------- .../java/com/zeapo/pwdstore/pwgen/pw_rand.java | 70 ----- .../main/java/com/zeapo/pwdstore/pwgen/pwgen.java | 138 --------- .../java/com/zeapo/pwdstore/pwgen/randnum.java | 26 -- .../com/zeapo/pwdstore/pwgenDialogFragment.java | 135 --------- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 21 files changed, 907 insertions(+), 941 deletions(-) create mode 100644 app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java delete mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.java create mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/PRNGFixes.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/PasswordGenerator.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/Phonemes.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/RandomNumberGenerator.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/RandomPasswordGenerator.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/pw_phonemes.java delete mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/pw_rand.java delete mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/pwgen.java delete mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgen/randnum.java delete mode 100644 app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java (limited to 'app/src') diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java b/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java new file mode 100644 index 00000000..1a51f6f2 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java @@ -0,0 +1,135 @@ +package com.zeapo.pwdstore; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.graphics.Typeface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import com.zeapo.pwdstore.pwgen.PasswordGenerator; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; + + +/** + * A placeholder fragment containing a simple view. + */ +public class PasswordGeneratorDialogFragment extends DialogFragment { + + public PasswordGeneratorDialogFragment() { + } + + + @NotNull + @SuppressLint("SetTextI18n") + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final Activity callingActivity = getActivity(); + LayoutInflater inflater = callingActivity.getLayoutInflater(); + @SuppressLint("InflateParams") final View view = inflater.inflate(R.layout.fragment_pwgen, null); + Typeface monoTypeface = Typeface.createFromAsset(callingActivity.getAssets(), "fonts/sourcecodepro.ttf"); + + builder.setView(view); + + SharedPreferences prefs + = getActivity().getApplicationContext().getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE); + + CheckBox checkBox = view.findViewById(R.id.numerals); + checkBox.setChecked(!prefs.getBoolean("0", false)); + + checkBox = view.findViewById(R.id.symbols); + checkBox.setChecked(prefs.getBoolean("y", false)); + + checkBox = view.findViewById(R.id.uppercase); + checkBox.setChecked(!prefs.getBoolean("A", false)); + + checkBox = view.findViewById(R.id.ambiguous); + checkBox.setChecked(!prefs.getBoolean("B", false)); + + checkBox = view.findViewById(R.id.pronounceable); + checkBox.setChecked(!prefs.getBoolean("s", true)); + + TextView textView = view.findViewById(R.id.lengthNumber); + textView.setText(Integer.toString(prefs.getInt("length", 20))); + + ((TextView) view.findViewById(R.id.passwordText)).setTypeface(monoTypeface); + + builder.setPositiveButton(getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + EditText edit = callingActivity.findViewById(R.id.crypto_password_edit); + TextView generate = view.findViewById(R.id.passwordText); + edit.setText(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(this.getResources().getString(R.string.pwgen_title)).create(); + ad.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + setPreferences(); + TextView textView = view.findViewById(R.id.passwordText); + textView.setText(PasswordGenerator.INSTANCE.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 = view.findViewById(R.id.passwordText); + textView.setText(PasswordGenerator.INSTANCE.generate(callingActivity.getApplicationContext()).get(0)); + } + }); + } + }); + return ad; + } + + private void setPreferences () { + ArrayList 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"); + } + EditText editText = getDialog().findViewById(R.id.lengthNumber); + try { + int length = Integer.valueOf(editText.getText().toString()); + PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences, length); + } catch(NumberFormatException e) { + 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() { 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 + * + * + * + * + * + * + * + * +
OptionDescription
0don't include numbers
Adon't include uppercase letters
Bdon't include ambiguous charactersl
sgenerate completely random passwords
vdon't include vowels
yinclude at least one symbol
* + * @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, 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 { + 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() + 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 + * + * + * + * + * + * +
BitCondition
0include at least one number
1include at least one uppercase letter
2include at least one symbol
3don't include ambiguous characters
* + * @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 <= n < 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 + * + * + * + * + * + * + * +
BitCondition
0include at least one number
1include at least one uppercase letter
2include at least one symbol
3don't include ambiguous characters
4don't include vowels
* + * @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 - * - * - * - * - * - * - *
BitCondition
0include at least one number
1include at least one uppercase letter
2include at least one symbol
3don't include ambiguous characters
- * @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 - * - * - * - * - * - * - * - *
BitCondition
0include at least one number
1include at least one uppercase letter
2include at least one symbol
3don't include ambiguous characters
4don't include vowels
- * @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 - * - * - * - * - * - * - * - * - *
OptionDescription
0don't include numbers
Adon't include uppercase letters
Bdon't include ambiguous charactersl
sgenerate completely random passwords
vdon't include vowels
yinclude at least one symbol
- * @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 - */ - public static boolean setPrefs(Context ctx, ArrayList 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 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 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 <= 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 deleted file mode 100644 index d7d1ae45..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/pwgenDialogFragment.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.zeapo.pwdstore; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.graphics.Typeface; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.CheckBox; -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 org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; - - -/** - * A placeholder fragment containing a simple view. - */ -public class pwgenDialogFragment extends DialogFragment { - - public pwgenDialogFragment() { - } - - - @NotNull - @SuppressLint("SetTextI18n") - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - final Activity callingActivity = getActivity(); - LayoutInflater inflater = callingActivity.getLayoutInflater(); - @SuppressLint("InflateParams") final View view = inflater.inflate(R.layout.fragment_pwgen, null); - Typeface monoTypeface = Typeface.createFromAsset(callingActivity.getAssets(), "fonts/sourcecodepro.ttf"); - - builder.setView(view); - - SharedPreferences prefs - = getActivity().getApplicationContext().getSharedPreferences("pwgen", Context.MODE_PRIVATE); - - CheckBox checkBox = view.findViewById(R.id.numerals); - checkBox.setChecked(!prefs.getBoolean("0", false)); - - checkBox = view.findViewById(R.id.symbols); - checkBox.setChecked(prefs.getBoolean("y", false)); - - checkBox = view.findViewById(R.id.uppercase); - checkBox.setChecked(!prefs.getBoolean("A", false)); - - checkBox = view.findViewById(R.id.ambiguous); - checkBox.setChecked(!prefs.getBoolean("B", false)); - - checkBox = view.findViewById(R.id.pronounceable); - checkBox.setChecked(!prefs.getBoolean("s", true)); - - TextView textView = view.findViewById(R.id.lengthNumber); - textView.setText(Integer.toString(prefs.getInt("length", 20))); - - ((TextView) view.findViewById(R.id.passwordText)).setTypeface(monoTypeface); - - builder.setPositiveButton(getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - EditText edit = callingActivity.findViewById(R.id.crypto_password_edit); - TextView generate = view.findViewById(R.id.passwordText); - edit.setText(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(this.getResources().getString(R.string.pwgen_title)).create(); - ad.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialog) { - setPreferences(); - 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 = view.findViewById(R.id.passwordText); - textView.setText(pwgen.generate(callingActivity.getApplicationContext()).get(0)); - } - }); - } - }); - return ad; - } - - private void setPreferences () { - ArrayList 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"); - } - EditText editText = getDialog().findViewById(R.id.lengthNumber); - try { - int length = Integer.valueOf(editText.getText().toString()); - pwgen.setPrefs(getActivity().getApplicationContext(), preferences, length); - } catch(NumberFormatException e) { - pwgen.setPrefs(getActivity().getApplicationContext(), preferences); - } - } -} - 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 @@ تصدير كلمات السر النسخة - + توليد تحتوي على الطول 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 @@ Použít externí repozitář hesel Vybrat externí repozitář - + Generovat Vložit Čísla 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 @@ Exportiert die verschlüsselten Passwörter in ein externes Verzeichnis Version - + Generieren Include Nummern 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 @@ Utilise un dépôt externe pour les mots de passe Choisissez un dépôt externe - + Générer Inclure Longueur 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 @@ 外部パスワードリポジトリを使用する 外部リポジトリを選択 - + 生成 含める 長さ 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 @@ Экспортировать пароли в открытом виде во внешнее хранилище Версия - + Сгенерировать Включая Длина 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 @@ Exports the encrypted passwords to an external directory Version - + Generate Password Generate Include -- cgit v1.2.3