diff --git a/app/build.gradle b/app/build.gradle
index c68f3e9..796d5e4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -41,6 +41,9 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0'
+ /* Password strengh library */
+ compile 'com.nulab-inc:zxcvbn:1.3.0'
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
diff --git a/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt b/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt
index dacd5bc..4faead8 100644
--- a/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt
+++ b/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt
@@ -27,12 +27,16 @@ import com.google.zxing.BarcodeFormat
import com.google.zxing.integration.android.IntentIntegrator
import com.google.zxing.integration.android.IntentResult
import com.journeyapps.barcodescanner.BarcodeEncoder
+import com.nulabinc.zxcvbn.Zxcvbn
import dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator
import kotlinx.android.synthetic.main.activity_create.*
+import kotlinx.android.synthetic.main.activity_generator.*
+import kotlinx.android.synthetic.main.activity_generator.view.*
import kotlinx.android.synthetic.main.dialogpassphrase.view.*
import kotlinx.coroutines.*
import java.io.ByteArrayOutputStream
import java.lang.IllegalArgumentException
+import java.lang.StringBuilder
import java.util.*
import kotlin.collections.ArrayList
@@ -168,54 +172,70 @@ class CreateActivity : AppCompatActivity() {
finish()
}
print_button.setOnClickListener {
- // Ask user for passhprase and hint
- val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.PassphraseDialog))
+ fun continueAfterCheck() {
+ // Ask user for passhprase and hint
+ val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.PassphraseDialog))
- with(builder) {
- val editTextLayout = layoutInflater.inflate(R.layout.dialogpassphrase, null)
- setView(editTextLayout)
- setTitle("Set Passphrase")
- setMessage("Final step is it to set your passphrase. Minimum are 8 characters.")
- setPositiveButton("Print", DialogInterface.OnClickListener { dialogInterface, i ->
- val passphrase = editTextLayout.passphrase_input.text.toString();
- if (passphrase == "") {
- // Make a new alert, telling the user that passphrase must not be null.
- dialogInterface.cancel()
- showError("Empty passphrase", "Passphrase must not be null.")
- return@OnClickListener
- }
- if (passphrase.length < 8) {
- // Make a new alert, telling the user that his passphrase doesn't meet the minimum.
- dialogInterface.cancel()
- showError("Weak passphrase", "Passphrase has to be at least 8 characters long.")
- return@OnClickListener
- }
- if (passphrase != editTextLayout.passphrase2_input.text.toString()) {
- // Make a new alert, telling the user that his passphrase doesn't match the repeat field.
- dialogInterface.cancel()
- showError("Passphrase mismatch", "Both passphrases do not match.")
- return@OnClickListener
- }
+ with(builder) {
+ val editTextLayout = layoutInflater.inflate(R.layout.dialogpassphrase, null)
+ setView(editTextLayout)
+ setTitle("Set Passphrase")
+ setMessage("Final step is it to set your passphrase. Minimum are 8 characters.")
+ setPositiveButton("Print", DialogInterface.OnClickListener { dialogInterface, i ->
+ val passphrase = editTextLayout.passphrase_input.text.toString();
+ if (passphrase == "") {
+ // Make a new alert, telling the user that passphrase must not be null.
+ dialogInterface.cancel()
+ showError("Empty passphrase", "Passphrase must not be null.")
+ return@OnClickListener
+ }
+ if (passphrase.length < 8) {
+ // Make a new alert, telling the user that his passphrase doesn't meet the minimum.
+ dialogInterface.cancel()
+ showError("Weak passphrase", "Passphrase has to be at least 8 characters long.")
+ return@OnClickListener
+ }
+ if (passphrase != editTextLayout.passphrase2_input.text.toString()) {
+ // Make a new alert, telling the user that his passphrase doesn't match the repeat field.
+ dialogInterface.cancel()
+ showError("Passphrase mismatch", "Both passphrases do not match.")
+ return@OnClickListener
+ }
- // Check if password is found in our offline password list
- if(passwordKnown(passphrase)) {
- // Make a new alert, telling the user that his passphrase was found in our local wordlist.
- dialogInterface.cancel()
- showError("Passphrase is compromised!", "Passphrase got found in a wordlist of bad passwords. " +
- "If you already used it somewhere, change it immediately!")
- return@OnClickListener
- }
+ // Check if password is found in our offline password list
+ if(passwordKnown(passphrase)) {
+ // Make a new alert, telling the user that his passphrase was found in our local wordlist.
+ dialogInterface.cancel()
+ showError("Passphrase is compromised!", "Passphrase got found in a wordlist of bad passwords. " +
+ "If you already used it somewhere, change it immediately!")
+ return@OnClickListener
+ }
- doPrint(editTextLayout.passphrase_input.text.toString(),
+ doPrint(editTextLayout.passphrase_input.text.toString(),
editTextLayout.hint_input.text.toString())
- })
- setNegativeButton("Go back", DialogInterface.OnClickListener { dialogInterface, i ->
- dialogInterface.dismiss()
- })
- show()
+ })
+ setNegativeButton("Go back", DialogInterface.OnClickListener { dialogInterface, i ->
+ dialogInterface.dismiss()
+ })
+ show()
+ }
}
+ // First check if entered password is compromised
+ if (passwordKnown(password_input.text.toString())) {
+ val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.ErrorDialog))
+ with(builder) {
+ setTitle("Password compromised!")
+ setMessage("Your password is weak and should be changed as it got found in a list of weak passwords.")
+ setPositiveButton("I'll change it", DialogInterface.OnClickListener { dialogInterface, i ->
+ })
+ setNegativeButton("I don't care", DialogInterface.OnClickListener { dialogInterface, i ->
+ continueAfterCheck()
+ })
+ show()
+ }
+ }
}
password_hide.setOnClickListener {
if (password_input.transformationMethod == HideReturnsTransformationMethod.getInstance()) {
@@ -227,6 +247,121 @@ class CreateActivity : AppCompatActivity() {
}
}
+ password_random.setOnClickListener {
+ val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.PassphraseGenerator))
+
+ with (builder) {
+ val layout = layoutInflater.inflate(R.layout.activity_generator, null)
+ val zxcvbn = Zxcvbn()
+ var length: Int = 8
+ setTitle("Generate password")
+ setView(layout)
+
+ fun newPassword() {
+ var combined = ""
+ val upper_case = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ val lower_case = "abcdefghijklmnopqrstuvwxyz"
+ val speicals = "!\";#$%&'()*+,-./:;<=>?@[/]^_`"
+ val digits = "1234567890"
+ val emojis = arrayOf(0x1F600, 0x1F603, 0x1F601, 0x1F911, 0x1F910, 0x1F637, 0x1F47D, 0x1F480, 0x1F916)
+ val rand: Random = Random()
+
+ if (layout.AZ.isChecked) combined += upper_case
+ if (layout.az.isChecked) combined += lower_case
+ if (layout.zero_nine.isChecked) combined += digits
+ if (layout.emojis.isChecked) {
+ emojis.forEach {
+ combined += String(Character.toChars(it))
+ }
+ }
+ if (layout.special.isChecked) combined += speicals
+
+ val sb: StringBuilder = StringBuilder(length)
+ for (x in 1..length) {
+ sb.append(combined[rand.nextInt(combined.length)])
+ }
+
+ // Adapt text size
+ if (sb.length > 10) {
+ layout.random_string.textSize = 24.0f
+ } else if (sb.length > 16) {
+ layout.random_string.textSize = 18.0f
+ }
+
+ // Measure strengh
+ layout.progressBar.progress = zxcvbn.measure(sb.toString())
+ .score
+
+ if (layout.progressBar.progress == 1) {
+ layout.progressBar.progressTintList = ColorStateList.valueOf(Color.RED)
+ } else if (layout.progressBar.progress == 2) {
+ layout.progressBar.progressTintList = ColorStateList.valueOf(Color.YELLOW)
+ } else if (layout.progressBar.progress == 3) {
+ layout.progressBar.progressTintList = ColorStateList.valueOf(Color.GREEN)
+ } else {
+ layout.progressBar.progressTintList = ColorStateList.valueOf(Color.GREEN)
+ }
+
+ layout.random_string.setText(sb.toString())
+ }
+
+ // Initial set
+ layout.length_label.setText(length.toString())
+ newPassword()
+
+ // Add length
+ layout.add_button.setOnClickListener {
+ length++
+ layout.length_label.setText(length.toString())
+ newPassword()
+ }
+ layout.add_button.setOnLongClickListener {
+ length += 10
+ layout.length_label.setText(length.toString())
+ newPassword()
+
+ true
+ }
+
+ // Subtract length
+ layout.subtract_button.setOnClickListener {
+ length--
+ layout.length_label.setText(length.toString())
+ newPassword()
+ }
+ layout.subtract_button.setOnLongClickListener {
+ length -= 10
+ layout.length_label.setText(length.toString())
+ newPassword()
+
+ true
+ }
+
+ // Refresh
+ layout.refresh_button.setOnClickListener {
+ newPassword()
+ }
+
+ // Copy password
+ layout.copy_button.setOnClickListener {
+ // Copy code if already scanend
+ val clip: ClipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ clip.setPrimaryClip(ClipData.newPlainText("Random password", layout.random_string.text.toString()))
+ Toast.makeText(applicationContext, "Copied!", Toast.LENGTH_SHORT).show()
+ }
+
+ setPositiveButton("Use this",
+ DialogInterface.OnClickListener { dialog, id ->
+ password_input.setText(layout.random_string.text)
+ })
+ setNegativeButton("Go back",
+ DialogInterface.OnClickListener { dialog, id ->
+ dialog.dismiss()
+ })
+ show()
+ }
+
+ }
fa_input.isFocusable = false
diff --git a/app/src/main/java/com/github/mondei1/offpass/GeneratorActivity.kt b/app/src/main/java/com/github/mondei1/offpass/GeneratorActivity.kt
index 777b106..f67fcfb 100644
--- a/app/src/main/java/com/github/mondei1/offpass/GeneratorActivity.kt
+++ b/app/src/main/java/com/github/mondei1/offpass/GeneratorActivity.kt
@@ -2,10 +2,27 @@ package com.github.mondei1.offpass
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
+import android.util.Log
+import kotlinx.android.synthetic.main.activity_generator.*
class GeneratorActivity : AppCompatActivity() {
+ var length: Int = 12
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_generator)
+
+ length_label.setText(length.toString())
+ Log.i("Generator", "Length: $length")
+ add_button.setOnClickListener {
+ Log.i("Generator", "Length: $length")
+ length++
+ length_label.setText(length.toString())
+ }
+ subtract_button.setOnClickListener {
+ Log.i("Generator", "Length: $length")
+ length--
+ length_label.setText(length.toString())
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_add.png b/app/src/main/res/drawable-hdpi/ic_add.png
new file mode 100644
index 0000000..4969fca
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_content_copy.png b/app/src/main/res/drawable-hdpi/ic_content_copy.png
new file mode 100644
index 0000000..5692b30
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_content_copy.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_refresh.png b/app/src/main/res/drawable-hdpi/ic_refresh.png
new file mode 100644
index 0000000..f27935e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_refresh.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_remove.png b/app/src/main/res/drawable-hdpi/ic_remove.png
new file mode 100644
index 0000000..9862826
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_remove.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_add.png b/app/src/main/res/drawable-mdpi/ic_add.png
new file mode 100644
index 0000000..2eab138
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_add.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_content_copy.png b/app/src/main/res/drawable-mdpi/ic_content_copy.png
new file mode 100644
index 0000000..ce6d1ca
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_content_copy.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_refresh.png b/app/src/main/res/drawable-mdpi/ic_refresh.png
new file mode 100644
index 0000000..63edb6a
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_refresh.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_remove.png b/app/src/main/res/drawable-mdpi/ic_remove.png
new file mode 100644
index 0000000..2ecdd65
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_remove.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_add.png b/app/src/main/res/drawable-xhdpi/ic_add.png
new file mode 100644
index 0000000..61c63eb
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_add.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_content_copy.png b/app/src/main/res/drawable-xhdpi/ic_content_copy.png
new file mode 100644
index 0000000..22b0d7d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_content_copy.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh.png b/app/src/main/res/drawable-xhdpi/ic_refresh.png
new file mode 100644
index 0000000..d937505
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_refresh.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_remove.png b/app/src/main/res/drawable-xhdpi/ic_remove.png
new file mode 100644
index 0000000..c2838b4
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_remove.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_add.png b/app/src/main/res/drawable-xxhdpi/ic_add.png
new file mode 100644
index 0000000..ec4c7be
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_add.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_content_copy.png b/app/src/main/res/drawable-xxhdpi/ic_content_copy.png
new file mode 100644
index 0000000..0717571
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_content_copy.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh.png b/app/src/main/res/drawable-xxhdpi/ic_refresh.png
new file mode 100644
index 0000000..096b21a
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_refresh.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_remove.png b/app/src/main/res/drawable-xxhdpi/ic_remove.png
new file mode 100644
index 0000000..c574c92
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_remove.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_add.png b/app/src/main/res/drawable-xxxhdpi/ic_add.png
new file mode 100644
index 0000000..d89882c
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_add.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_content_copy.png b/app/src/main/res/drawable-xxxhdpi/ic_content_copy.png
new file mode 100644
index 0000000..8fdc606
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_content_copy.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_refresh.png b/app/src/main/res/drawable-xxxhdpi/ic_refresh.png
new file mode 100644
index 0000000..ee85a4d
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_refresh.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_remove.png b/app/src/main/res/drawable-xxxhdpi/ic_remove.png
new file mode 100644
index 0000000..5794285
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_remove.png differ
diff --git a/app/src/main/res/layout/activity_generator.xml b/app/src/main/res/layout/activity_generator.xml
index 2fb6fd1..3b05d41 100644
--- a/app/src/main/res/layout/activity_generator.xml
+++ b/app/src/main/res/layout/activity_generator.xml
@@ -1,9 +1,171 @@
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_view.xml b/app/src/main/res/layout/activity_view.xml
index 33dce6d..7381b1f 100644
--- a/app/src/main/res/layout/activity_view.xml
+++ b/app/src/main/res/layout/activity_view.xml
@@ -22,7 +22,9 @@
+ android:orientation="horizontal"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
bold
- @android:color/holo_red_light
+
\ No newline at end of file