diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseAccountFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseAccountFragment.kt index 0a3521a..9864007 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseAccountFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseAccountFragment.kt @@ -37,7 +37,7 @@ abstract class BaseAccountFragment : ConnectedFragment() { // Stores the user selected PIN encrypted PreferenceManager.getDefaultSharedPreferences(context!!) .edit() - .putString(Constants.KEY_ENCRYPTED_PIN, encryptedPIN) + .putString(Constants.KEY_HASHED_PIN_PATTERN, encryptedPIN) .apply() // Stores the accounts this key refers to diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseSecurityLockDialog.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseSecurityLockDialog.kt index a911d75..7659bf5 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseSecurityLockDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseSecurityLockDialog.kt @@ -54,16 +54,22 @@ abstract class BaseSecurityLockDialog : DialogFragment() { /** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */ protected var mDisposables = CompositeDisposable() - /** Current encrypted version of the PIN/Pattern */ - protected var currentEncryptedPINPattern: String? = null + /** Current hashed version of the salt + PIN/Pattern */ + protected var currentHashedPINPattern: String? = null + + /** Salt used to hash the current PIN/Pattern */ + protected var currentPINPatternSalt: String? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) onAttachToParentFragment(parentFragment) - currentEncryptedPINPattern = PreferenceManager.getDefaultSharedPreferences(context) - .getString(Constants.KEY_ENCRYPTED_PIN, "")?.trim() + currentHashedPINPattern = PreferenceManager.getDefaultSharedPreferences(context) + .getString(Constants.KEY_HASHED_PIN_PATTERN, "") + + currentPINPatternSalt = PreferenceManager.getDefaultSharedPreferences(context) + .getString(Constants.KEY_PIN_PATTERN_SALT, "") currentStep = arguments?.getInt(KEY_STEP_SECURITY_LOCK) ?: -1 diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PINSecurityLockDialog.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PINSecurityLockDialog.kt index 8d7603b..180c6e5 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PINSecurityLockDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PINSecurityLockDialog.kt @@ -10,6 +10,7 @@ import com.jakewharton.rxbinding3.widget.textChanges import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.CryptoUtils +import cy.agorise.bitsybitshareswallet.utils.hideKeyboard import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.dialog_pin_security_lock.* @@ -39,16 +40,20 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { var handled = false if (actionId == EditorInfo.IME_ACTION_GO) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) { - // The user just wants to verify the current encrypted PIN/Pattern - val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim() + // The user just wants to verify the current hashed PIN/Pattern + val pin = v.text.toString().trim() + val hashedPIN = CryptoUtils.createSHA256Hash(currentPINPatternSalt + pin) - if (encryptedPIN == currentEncryptedPINPattern) { + if (hashedPIN == currentHashedPINPattern) { // PIN is correct, proceed + tietPIN.hideKeyboard() + rootView.requestFocus() dismiss() mCallback?.onPINPatternEntered(actionIdentifier) } else { tilPIN.error = getString(R.string.error__wrong_pin) } + } else if (currentStep == STEP_SECURITY_LOCK_CREATE) { // The user is trying to create a new PIN if (v.text.toString().trim().length >= Constants.MIN_PIN_LENGTH) { @@ -62,12 +67,14 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { if (pinConfirm != newPIN) { tvTitle.text = getString(R.string.title__pins_dont_match) } else { - val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim() + val salt = CryptoUtils.generateSalt() + val hashedPIN = CryptoUtils.createSHA256Hash(salt + pinConfirm) - // Stores the newly selected PIN, encrypted + // Stores the newly selected PIN, hashed PreferenceManager.getDefaultSharedPreferences(v.context).edit() - .putString(Constants.KEY_ENCRYPTED_PIN, encryptedPIN) - .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0).apply() // 0 -> PIN + .putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPIN) + .putString(Constants.KEY_PIN_PATTERN_SALT, salt) + .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 1).apply() // 1 -> PIN dismiss() mCallback?.onPINPatternChanged() diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt index bb19ec2..ea41769 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt @@ -64,8 +64,9 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { override fun onComplete(pattern: List) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) { context?.let { - val encryptedPattern = CryptoUtils.encrypt(it, getStringPattern(pattern)).trim() - if (encryptedPattern == currentEncryptedPINPattern) { + val hashedPattern = CryptoUtils.createSHA256Hash(currentPINPatternSalt + + getStringPattern(pattern)) + if (hashedPattern == currentHashedPINPattern) { // Pattern is correct, proceed dismiss() mCallback?.onPINPatternEntered(actionIdentifier) @@ -102,12 +103,14 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { btnNext.isEnabled = true btnNext.setOnClickListener { context?.let { - val encryptedPattern = CryptoUtils.encrypt(it, patternConfirm).trim() + val salt = CryptoUtils.generateSalt() + val hashedPattern = CryptoUtils.createSHA256Hash(salt + patternConfirm) // Stores the newly selected Pattern, encrypted PreferenceManager.getDefaultSharedPreferences(it).edit() - .putString(Constants.KEY_ENCRYPTED_PIN, encryptedPattern) - .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 1).apply() // 1 -> Pattern + .putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPattern) + .putString(Constants.KEY_PIN_PATTERN_SALT, salt) + .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 2).apply() // 2 -> Pattern dismiss() mCallback?.onPINPatternChanged() diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt index baf7690..58bb546 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt @@ -465,9 +465,9 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) // Security Lock Options - // 0 -> PIN - // 1 -> Pattern - // 2 -> None + // 0 -> None + // 1 -> PIN + // 2 -> Pattern // Args used for both PIN and Pattern options val args = Bundle() @@ -476,19 +476,20 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, ACTION_SEND_TRANSFER) when (securityLockSelected) { - 0 -> { /* PIN */ + 0 -> { /* None */ + startSendTransferOperation() + + } + 1 -> { /* PIN */ val pinFrag = PINSecurityLockDialog() pinFrag.arguments = args pinFrag.show(childFragmentManager, "pin_security_lock_tag") } - 1 -> { /* Pattern */ + else -> { /* Pattern */ val patternFrag = PatternSecurityLockDialog() patternFrag.arguments = args patternFrag.show(childFragmentManager, "pattern_security_lock_tag") } - else -> { /* None */ - startSendTransferOperation() - } } } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt index 52eb843..825a3c7 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt @@ -104,9 +104,9 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) // Security Lock Options - // 0 -> PIN - // 1 -> Pattern - // 2 -> None + // 0 -> None + // 1 -> PIN + // 2 -> Pattern tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected] @@ -229,9 +229,9 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) // Security Lock Options - // 0 -> PIN - // 1 -> Pattern - // 2 -> None + // 0 -> None + // 1 -> PIN + // 2 -> Pattern // Args used for both PIN and Pattern options val args = Bundle() @@ -240,21 +240,21 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, actionIdentifier) return when (securityLockSelected) { - 0 -> { /* PIN */ + 0 -> { /* None */ + false + } + 1 -> { /* PIN */ val pinFrag = PINSecurityLockDialog() pinFrag.arguments = args pinFrag.show(childFragmentManager, "pin_security_lock_tag") true } - 1 -> { /* Pattern */ + else -> { /* Pattern */ val patternFrag = PatternSecurityLockDialog() patternFrag.arguments = args patternFrag.show(childFragmentManager, "pattern_security_lock_tag") true } - else -> { /* None */ - false - } } } @@ -271,9 +271,9 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) // Security Lock Options - // 0 -> PIN - // 1 -> Pattern - // 2 -> None + // 0 -> None + // 1 -> PIN + // 2 -> Pattern tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected] } @@ -293,23 +293,23 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, -1) when (index) { - 0 -> { /* PIN */ + 0 -> { /* None */ + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0).apply() // 0 -> None + + // Call this function to update the UI + onPINPatternChanged() + } + 1 -> { /* PIN */ val pinFrag = PINSecurityLockDialog() pinFrag.arguments = args pinFrag.show(childFragmentManager, "pin_security_lock_tag") } - 1 -> { /* Pattern */ + else -> { /* Pattern */ val patternFrag = PatternSecurityLockDialog() patternFrag.arguments = args patternFrag.show(childFragmentManager, "pattern_security_lock_tag") } - else -> { /* None */ - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 2).apply() // 2 -> None - - // Call this function to update the UI - onPINPatternChanged() - } } } } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt index 47e7a8f..c96345d 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt @@ -16,6 +16,15 @@ object Constants { /** The minimum required length for a PIN number */ const val MIN_PIN_LENGTH = 6 + /** Salt used to securely generate the hash of the PIN/Pattern */ + const val KEY_PIN_PATTERN_SALT = "key_pin_pattern_salt" + + /** The user selected hashed PIN/Pattern */ + const val KEY_HASHED_PIN_PATTERN = "key_hashed_pin_pattern" + + /** Key used to store the user's selected Security Lock option */ + const val KEY_SECURITY_LOCK_SELECTED = "key_security_lock_selected" + /** Name of the account passed to the faucet as the referrer */ const val FAUCET_REFERRER = "agorise" @@ -25,9 +34,6 @@ object Constants { /** Coingecko's API URL */ const val COINGECKO_URL = "https://api.coingecko.com" - /** The user selected encrypted PIN */ - const val KEY_ENCRYPTED_PIN = "key_encrypted_pin" - /** The fee to send in every transfer (0.01%) */ const val FEE_PERCENTAGE = 0.0001 @@ -101,7 +107,4 @@ object Constants { /** Name of the external storage folder used to save files like PDF and CSV exports and Backups **/ const val EXTERNAL_STORAGE_FOLDER = "BiTSy" - - /** Key used to store the user's selected Security Lock option */ - const val KEY_SECURITY_LOCK_SELECTED = "key_security_lock_selected" } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/CryptoUtils.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/CryptoUtils.kt index 7fc8757..152947b 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/CryptoUtils.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/CryptoUtils.kt @@ -2,11 +2,16 @@ package cy.agorise.bitsybitshareswallet.utils import android.content.Context import android.preference.PreferenceManager +import android.util.Base64 import com.moldedbits.r2d2.R2d2 +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.security.SecureRandom import javax.crypto.AEADBadTagException + /** * Class that provides encryption/decryption support by using the key management framework provided * by the KeyStore system. @@ -68,5 +73,27 @@ object CryptoUtils { val r2d2 = R2d2(context) return r2d2.decryptData(ciphertext) } + + /** + * Generates a random salt string + */ + fun generateSalt(): String { + val salt = ByteArray(16) + SecureRandom().nextBytes(salt) + return Base64.encodeToString(salt, Base64.DEFAULT).trim() + } + + /** + * Creates a SHA-256 hash of the given string + */ + @Throws(NoSuchAlgorithmException::class) + fun createSHA256Hash(text: String): String { + val md = MessageDigest.getInstance("SHA-256") + + md.update(text.toByteArray()) + val digest = md.digest() + + return Base64.encodeToString(digest, Base64.DEFAULT).trim() + } } diff --git a/app/src/main/res/layout/dialog_pin_security_lock.xml b/app/src/main/res/layout/dialog_pin_security_lock.xml index 5d75a31..eeb50eb 100644 --- a/app/src/main/res/layout/dialog_pin_security_lock.xml +++ b/app/src/main/res/layout/dialog_pin_security_lock.xml @@ -3,8 +3,10 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/rootView" android:layout_width="match_parent" android:layout_height="match_parent" + android:focusableInTouchMode="true" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingStart="@dimen/activity_horizontal_margin" diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index d8f35db..62a6552 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -8,8 +8,8 @@ + @string/text__none @string/text__pin @string/text__pattern - @string/text__none \ No newline at end of file