Changed the method used to store the user selected PIN/Pattern into the shared preferences, the app now uses a hashed version of the PIN/Pattern with added salt at the beginning instead of an encrypted one. The salt is being saved into the shared preferences so that we can confirm the current PIN/Pattern when the user requires to do so.

This commit is contained in:
Severiano Jaramillo 2019-02-18 14:06:48 -06:00
parent b0811ab2c8
commit fac7bb031e
10 changed files with 104 additions and 55 deletions

View file

@ -37,7 +37,7 @@ abstract class BaseAccountFragment : ConnectedFragment() {
// Stores the user selected PIN encrypted // Stores the user selected PIN encrypted
PreferenceManager.getDefaultSharedPreferences(context!!) PreferenceManager.getDefaultSharedPreferences(context!!)
.edit() .edit()
.putString(Constants.KEY_ENCRYPTED_PIN, encryptedPIN) .putString(Constants.KEY_HASHED_PIN_PATTERN, encryptedPIN)
.apply() .apply()
// Stores the accounts this key refers to // Stores the accounts this key refers to

View file

@ -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 */ /** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */
protected var mDisposables = CompositeDisposable() protected var mDisposables = CompositeDisposable()
/** Current encrypted version of the PIN/Pattern */ /** Current hashed version of the salt + PIN/Pattern */
protected var currentEncryptedPINPattern: String? = null 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
onAttachToParentFragment(parentFragment) onAttachToParentFragment(parentFragment)
currentEncryptedPINPattern = PreferenceManager.getDefaultSharedPreferences(context) currentHashedPINPattern = PreferenceManager.getDefaultSharedPreferences(context)
.getString(Constants.KEY_ENCRYPTED_PIN, "")?.trim() .getString(Constants.KEY_HASHED_PIN_PATTERN, "")
currentPINPatternSalt = PreferenceManager.getDefaultSharedPreferences(context)
.getString(Constants.KEY_PIN_PATTERN_SALT, "")
currentStep = arguments?.getInt(KEY_STEP_SECURITY_LOCK) ?: -1 currentStep = arguments?.getInt(KEY_STEP_SECURITY_LOCK) ?: -1

View file

@ -10,6 +10,7 @@ import com.jakewharton.rxbinding3.widget.textChanges
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
import cy.agorise.bitsybitshareswallet.utils.hideKeyboard
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.dialog_pin_security_lock.* import kotlinx.android.synthetic.main.dialog_pin_security_lock.*
@ -39,16 +40,20 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
var handled = false var handled = false
if (actionId == EditorInfo.IME_ACTION_GO) { if (actionId == EditorInfo.IME_ACTION_GO) {
if (currentStep == STEP_SECURITY_LOCK_VERIFY) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
// The user just wants to verify the current encrypted PIN/Pattern // The user just wants to verify the current hashed PIN/Pattern
val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim() val pin = v.text.toString().trim()
val hashedPIN = CryptoUtils.createSHA256Hash(currentPINPatternSalt + pin)
if (encryptedPIN == currentEncryptedPINPattern) { if (hashedPIN == currentHashedPINPattern) {
// PIN is correct, proceed // PIN is correct, proceed
tietPIN.hideKeyboard()
rootView.requestFocus()
dismiss() dismiss()
mCallback?.onPINPatternEntered(actionIdentifier) mCallback?.onPINPatternEntered(actionIdentifier)
} else { } else {
tilPIN.error = getString(R.string.error__wrong_pin) tilPIN.error = getString(R.string.error__wrong_pin)
} }
} else if (currentStep == STEP_SECURITY_LOCK_CREATE) { } else if (currentStep == STEP_SECURITY_LOCK_CREATE) {
// The user is trying to create a new PIN // The user is trying to create a new PIN
if (v.text.toString().trim().length >= Constants.MIN_PIN_LENGTH) { if (v.text.toString().trim().length >= Constants.MIN_PIN_LENGTH) {
@ -62,12 +67,14 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
if (pinConfirm != newPIN) { if (pinConfirm != newPIN) {
tvTitle.text = getString(R.string.title__pins_dont_match) tvTitle.text = getString(R.string.title__pins_dont_match)
} else { } 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() PreferenceManager.getDefaultSharedPreferences(v.context).edit()
.putString(Constants.KEY_ENCRYPTED_PIN, encryptedPIN) .putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPIN)
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0).apply() // 0 -> PIN .putString(Constants.KEY_PIN_PATTERN_SALT, salt)
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 1).apply() // 1 -> PIN
dismiss() dismiss()
mCallback?.onPINPatternChanged() mCallback?.onPINPatternChanged()

View file

@ -64,8 +64,9 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
override fun onComplete(pattern: List<PatternLockView.Dot>) { override fun onComplete(pattern: List<PatternLockView.Dot>) {
if (currentStep == STEP_SECURITY_LOCK_VERIFY) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
context?.let { context?.let {
val encryptedPattern = CryptoUtils.encrypt(it, getStringPattern(pattern)).trim() val hashedPattern = CryptoUtils.createSHA256Hash(currentPINPatternSalt +
if (encryptedPattern == currentEncryptedPINPattern) { getStringPattern(pattern))
if (hashedPattern == currentHashedPINPattern) {
// Pattern is correct, proceed // Pattern is correct, proceed
dismiss() dismiss()
mCallback?.onPINPatternEntered(actionIdentifier) mCallback?.onPINPatternEntered(actionIdentifier)
@ -102,12 +103,14 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
btnNext.isEnabled = true btnNext.isEnabled = true
btnNext.setOnClickListener { btnNext.setOnClickListener {
context?.let { 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 // Stores the newly selected Pattern, encrypted
PreferenceManager.getDefaultSharedPreferences(it).edit() PreferenceManager.getDefaultSharedPreferences(it).edit()
.putString(Constants.KEY_ENCRYPTED_PIN, encryptedPattern) .putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPattern)
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 1).apply() // 1 -> Pattern .putString(Constants.KEY_PIN_PATTERN_SALT, salt)
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 2).apply() // 2 -> Pattern
dismiss() dismiss()
mCallback?.onPINPatternChanged() mCallback?.onPINPatternChanged()

View file

@ -465,9 +465,9 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
// Security Lock Options // Security Lock Options
// 0 -> PIN // 0 -> None
// 1 -> Pattern // 1 -> PIN
// 2 -> None // 2 -> Pattern
// Args used for both PIN and Pattern options // Args used for both PIN and Pattern options
val args = Bundle() val args = Bundle()
@ -476,19 +476,20 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, ACTION_SEND_TRANSFER) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, ACTION_SEND_TRANSFER)
when (securityLockSelected) { when (securityLockSelected) {
0 -> { /* PIN */ 0 -> { /* None */
startSendTransferOperation()
}
1 -> { /* PIN */
val pinFrag = PINSecurityLockDialog() val pinFrag = PINSecurityLockDialog()
pinFrag.arguments = args pinFrag.arguments = args
pinFrag.show(childFragmentManager, "pin_security_lock_tag") pinFrag.show(childFragmentManager, "pin_security_lock_tag")
} }
1 -> { /* Pattern */ else -> { /* Pattern */
val patternFrag = PatternSecurityLockDialog() val patternFrag = PatternSecurityLockDialog()
patternFrag.arguments = args patternFrag.arguments = args
patternFrag.show(childFragmentManager, "pattern_security_lock_tag") patternFrag.show(childFragmentManager, "pattern_security_lock_tag")
} }
else -> { /* None */
startSendTransferOperation()
}
} }
} }

View file

@ -104,9 +104,9 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
// Security Lock Options // Security Lock Options
// 0 -> PIN // 0 -> None
// 1 -> Pattern // 1 -> PIN
// 2 -> None // 2 -> Pattern
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected] 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) val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
// Security Lock Options // Security Lock Options
// 0 -> PIN // 0 -> None
// 1 -> Pattern // 1 -> PIN
// 2 -> None // 2 -> Pattern
// Args used for both PIN and Pattern options // Args used for both PIN and Pattern options
val args = Bundle() val args = Bundle()
@ -240,21 +240,21 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, actionIdentifier) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, actionIdentifier)
return when (securityLockSelected) { return when (securityLockSelected) {
0 -> { /* PIN */ 0 -> { /* None */
false
}
1 -> { /* PIN */
val pinFrag = PINSecurityLockDialog() val pinFrag = PINSecurityLockDialog()
pinFrag.arguments = args pinFrag.arguments = args
pinFrag.show(childFragmentManager, "pin_security_lock_tag") pinFrag.show(childFragmentManager, "pin_security_lock_tag")
true true
} }
1 -> { /* Pattern */ else -> { /* Pattern */
val patternFrag = PatternSecurityLockDialog() val patternFrag = PatternSecurityLockDialog()
patternFrag.arguments = args patternFrag.arguments = args
patternFrag.show(childFragmentManager, "pattern_security_lock_tag") patternFrag.show(childFragmentManager, "pattern_security_lock_tag")
true true
} }
else -> { /* None */
false
}
} }
} }
@ -271,9 +271,9 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
// Security Lock Options // Security Lock Options
// 0 -> PIN // 0 -> None
// 1 -> Pattern // 1 -> PIN
// 2 -> None // 2 -> Pattern
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected] 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) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, -1)
when (index) { 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() val pinFrag = PINSecurityLockDialog()
pinFrag.arguments = args pinFrag.arguments = args
pinFrag.show(childFragmentManager, "pin_security_lock_tag") pinFrag.show(childFragmentManager, "pin_security_lock_tag")
} }
1 -> { /* Pattern */ else -> { /* Pattern */
val patternFrag = PatternSecurityLockDialog() val patternFrag = PatternSecurityLockDialog()
patternFrag.arguments = args patternFrag.arguments = args
patternFrag.show(childFragmentManager, "pattern_security_lock_tag") 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()
}
} }
} }
} }

View file

@ -16,6 +16,15 @@ object Constants {
/** The minimum required length for a PIN number */ /** The minimum required length for a PIN number */
const val MIN_PIN_LENGTH = 6 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 */ /** Name of the account passed to the faucet as the referrer */
const val FAUCET_REFERRER = "agorise" const val FAUCET_REFERRER = "agorise"
@ -25,9 +34,6 @@ object Constants {
/** Coingecko's API URL */ /** Coingecko's API URL */
const val COINGECKO_URL = "https://api.coingecko.com" 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%) */ /** The fee to send in every transfer (0.01%) */
const val FEE_PERCENTAGE = 0.0001 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 **/ /** Name of the external storage folder used to save files like PDF and CSV exports and Backups **/
const val EXTERNAL_STORAGE_FOLDER = "BiTSy" 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"
} }

View file

@ -2,11 +2,16 @@ package cy.agorise.bitsybitshareswallet.utils
import android.content.Context import android.content.Context
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.util.Base64
import com.moldedbits.r2d2.R2d2 import com.moldedbits.r2d2.R2d2
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import javax.crypto.AEADBadTagException import javax.crypto.AEADBadTagException
/** /**
* Class that provides encryption/decryption support by using the key management framework provided * Class that provides encryption/decryption support by using the key management framework provided
* by the KeyStore system. * by the KeyStore system.
@ -68,5 +73,27 @@ object CryptoUtils {
val r2d2 = R2d2(context) val r2d2 = R2d2(context)
return r2d2.decryptData(ciphertext) 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()
}
} }

View file

@ -3,8 +3,10 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingStart="@dimen/activity_horizontal_margin" android:paddingStart="@dimen/activity_horizontal_margin"

View file

@ -8,8 +8,8 @@
<!-- Options used to populate the Security Lock Options Dialog in the Settings Screen --> <!-- Options used to populate the Security Lock Options Dialog in the Settings Screen -->
<string-array name="security_lock_options"> <string-array name="security_lock_options">
<item>@string/text__none</item>
<item>@string/text__pin</item> <item>@string/text__pin</item>
<item>@string/text__pattern</item> <item>@string/text__pattern</item>
<item>@string/text__none</item>
</string-array> </string-array>
</resources> </resources>