diff options
author | Harsh Shandilya <msfjarvis@gmail.com> | 2019-01-02 19:30:33 +0530 |
---|---|---|
committer | Mohamed Zenadi <zeapo@users.noreply.github.com> | 2019-01-02 15:00:33 +0100 |
commit | b267321d540984f2f5023f7eefcc475c30d8a0c1 (patch) | |
tree | 37711e2f99c5bededee2ca762df170e335f11b56 /app/src/main | |
parent | a82fb391d3c6e9b1151664100640114286843ec7 (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>
Diffstat (limited to 'app/src/main')
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 <= 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 + * <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 <= 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/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> |