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>
This commit is contained in:
Harsh Shandilya 2019-01-02 19:30:33 +05:30 committed by Mohamed Zenadi
parent a82fb391d3
commit b267321d54
20 changed files with 780 additions and 814 deletions

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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");
}
}
}

View file

@ -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)
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,26 @@
package com.zeapo.pwdstore.pwgen
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
internal object RandomNumberGenerator {
private var random: SecureRandom
init {
try {
random = SecureRandom.getInstance("SHA1PRNG")
} catch (e: NoSuchAlgorithmException) {
throw SecurityException("SHA1PRNG not available", e)
}
}
/**
* Generate a random number n, where 0 &lt;= n &lt; maxNum.
*
* @param maxNum the bound on the random number to be returned
* @return the generated random number
*/
fun number(maxNum: Int): Int {
return random.nextInt(maxNum)
}
}

View file

@ -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
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -1,26 +0,0 @@
package com.zeapo.pwdstore.pwgen;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
class randnum {
private static SecureRandom random;
static {
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException("SHA1PRNG not available", e);
}
}
/**
* Generate a random number n, where 0 &lt;= n &lt; maxNum.
*
* @param maxNum the bound on the random number to be returned
* @return the generated random number
*/
public static int number(int maxNum) {
return random.nextInt(maxNum);
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>