Implemented encrypting default password if no app protection is set

This commit is contained in:
dankito 2020-10-08 01:14:14 +02:00
parent 12d6c5a430
commit 4e27bffd09
4 changed files with 83 additions and 18 deletions

View File

@ -19,10 +19,12 @@ open class AuthenticationService(
) {
companion object {
private const val AuthenticationSettingsFilename = "s"
private const val AuthenticationSettingsFilename = "a"
private const val EncryptionKeyName = "BankingAndroidKey"
private const val DefaultPasswordEncryptionKey = "AnyData" // TODO: store in a secure place
private val log = LoggerFactory.getLogger(AuthenticationService::class.java)
}
@ -89,7 +91,7 @@ open class AuthenticationService(
val cipher = cryptographyManager.getInitializedCipherForDecryption(EncryptionKeyName, iv)
biometricAuthenticationService.authenticate(cipher) { authenticationResult ->
if (authenticationResult.successful) {
settings.encryptedUserPassword?.let {
settings.encryptedDefaultPassword?.let {
val encryptedUserPassword = decodeFromBase64(it)
val decrypted = cryptographyManager.decryptData(encryptedUserPassword, cipher)
@ -104,8 +106,19 @@ open class AuthenticationService(
?: run { result(false) }
}
protected open fun openDatabase(authenticationSettings: AuthenticationSettings) {
openDatabase(authenticationSettings.userPassword)
protected open fun openDatabase(settings: AuthenticationSettings) {
if (settings.type == AuthenticationType.None) {
settings.encryptedDefaultPassword?.let { encryptedPassword ->
settings.initializationVector?.let { iv ->
settings.salt?.let { salt ->
val decrypted = cryptographyManager.decryptDataWithPbe(decodeFromBase64(encryptedPassword), DefaultPasswordEncryptionKey,
decodeFromBase64(iv), decodeFromBase64(salt))
openDatabase(decrypted)
}
}
}
}
}
protected open fun openDatabase(password: String?): Boolean {
@ -129,22 +142,25 @@ open class AuthenticationService(
protected open fun saveNewAuthenticationMethod(type: AuthenticationType, newPassword: String): Boolean {
val settings = loadOrCreateDefaultAuthenticationSettings()
if (type == settings.type &&
((type == AuthenticationType.Password && isCorrectUserPassword(newPassword)) || settings.userPassword == newPassword)) { // nothing changed
return true
}
settings.type = type
settings.hashedUserPassword = if (type == AuthenticationType.Password) BCrypt.withDefaults().hashToString(12, newPassword.toCharArray()) else null
settings.userPassword = if (type == AuthenticationType.None) newPassword else null
settings.initializationVector = null
settings.salt = null
if (type == AuthenticationType.Biometric) {
encryptionCipherForBiometric?.let { encryptionCipher ->
val encryptedPassword = cryptographyManager.encryptData(newPassword, encryptionCipher)
settings.encryptedUserPassword = encodeToBase64(encryptedPassword)
settings.encryptedDefaultPassword = encodeToBase64(encryptedPassword)
settings.initializationVector = encodeToBase64(encryptionCipher.iv)
}
}
else if (type == AuthenticationType.None) {
val salt = cryptographyManager.generateRandomBytes(8)
val (encryptedPassword, iv) = cryptographyManager.encryptDataWithPbe(newPassword, DefaultPasswordEncryptionKey, salt)
settings.encryptedDefaultPassword = encodeToBase64(encryptedPassword)
settings.initializationVector = encodeToBase64(iv)
settings.salt = encodeToBase64(salt)
}
if (saveAuthenticationSettings(settings)) {
this.authenticationType = type

View File

@ -4,9 +4,9 @@ package net.dankito.banking.ui.android.authentication
open class AuthenticationSettings(
open var type: AuthenticationType,
open var hashedUserPassword: String? = null,
open var userPassword: String? = null,
open var encryptedUserPassword: String? = null,
open var initializationVector: String? = null
open var encryptedDefaultPassword: String? = null,
open var initializationVector: String? = null,
open var salt: String? = null
) {
internal constructor() : this(AuthenticationType.None) // for object deserializers

View File

@ -87,7 +87,11 @@ open class ProtectAppSettingsDialog : SettingsDialogBase() {
}
}
btnBiometricAuthentication.authenticationSuccessful = { btnSetAuthenticationMethod.isEnabled = true }
btnBiometricAuthentication.customButtonClickHandler = {
authenticationService.authenticateUserWithBiometricToSetAsNewAuthenticationMethod { result ->
btnSetAuthenticationMethod.isEnabled = result.successful
}
}
edtxtPassword.actualEditText.addTextChangedListener(StandardTextWatcher { checkIfEnteredPasswordsMatch() } )
edtxtPassword.actualEditText.addEnterPressedListener { checkIfEnteredPasswordsMatchAndSetAuthenticationMethod() }
@ -153,8 +157,8 @@ open class ProtectAppSettingsDialog : SettingsDialogBase() {
protected open fun setAuthenticationMethod() {
when {
btnShowBiometricAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToBiometric()
btnShowPasswordAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToPassword(edtxtPassword.text)
btnShowBiometricAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToBiometric()
btnShowRemoveAppProtectionSection.isChecked -> authenticationService.removeAppProtection()
else -> log.error("This should never occur! Has there a new authentication method been added?")
}

View File

@ -1,12 +1,18 @@
package net.dankito.banking.ui.android.security
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
open class CryptographyManager {
@ -50,8 +56,8 @@ open class CryptographyManager {
}
open fun decryptData(cipherText: ByteArray, cipher: Cipher): String {
val plaintext = cipher.doFinal(cipherText)
return String(plaintext, PasswordCharset)
val plainTextBytes = cipher.doFinal(cipherText)
return String(plainTextBytes, PasswordCharset)
}
protected open fun getOrCreateSecretKey(keyName: String): SecretKey {
@ -75,4 +81,43 @@ open class CryptographyManager {
return keyGenerator.generateKey()
}
open fun encryptDataWithPbe(plaintext: String, password: String, salt: ByteArray): Pair<ByteArray, ByteArray> {
val secret: SecretKey = generatePbeSecretKey(password, salt)
val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secret)
val initializationVector = cipher.iv
return Pair(cipher.doFinal(plaintext.toByteArray(PasswordCharset)), initializationVector)
}
open fun decryptDataWithPbe(cipherText: ByteArray, password: String, initializationVector: ByteArray, salt: ByteArray): String {
val secret: SecretKey = generatePbeSecretKey(password, salt)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, secret, IvParameterSpec(initializationVector))
val plainTextBytes = cipher.doFinal(cipherText)
return String(plainTextBytes, PasswordCharset)
}
protected open fun generatePbeSecretKey(userPassword: String, salt: ByteArray): SecretKey {
// Initialize PBE with password
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val spec = PBEKeySpec(userPassword.toCharArray(), salt, 65536, 256)
val key = factory.generateSecret(spec)
return SecretKeySpec(key.encoded, "AES")
}
open fun generateRandomBytes(countBytes: Int): ByteArray {
return ByteArray(countBytes).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SecureRandom.getInstanceStrong().nextBytes(this)
} else {
SecureRandom().nextBytes(this)
}
}
}
}