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

View File

@ -4,9 +4,9 @@ package net.dankito.banking.ui.android.authentication
open class AuthenticationSettings( open class AuthenticationSettings(
open var type: AuthenticationType, open var type: AuthenticationType,
open var hashedUserPassword: String? = null, open var hashedUserPassword: String? = null,
open var userPassword: String? = null, open var encryptedDefaultPassword: String? = null,
open var encryptedUserPassword: String? = null, open var initializationVector: String? = null,
open var initializationVector: String? = null open var salt: String? = null
) { ) {
internal constructor() : this(AuthenticationType.None) // for object deserializers 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.addTextChangedListener(StandardTextWatcher { checkIfEnteredPasswordsMatch() } )
edtxtPassword.actualEditText.addEnterPressedListener { checkIfEnteredPasswordsMatchAndSetAuthenticationMethod() } edtxtPassword.actualEditText.addEnterPressedListener { checkIfEnteredPasswordsMatchAndSetAuthenticationMethod() }
@ -153,8 +157,8 @@ open class ProtectAppSettingsDialog : SettingsDialogBase() {
protected open fun setAuthenticationMethod() { protected open fun setAuthenticationMethod() {
when { when {
btnShowBiometricAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToBiometric()
btnShowPasswordAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToPassword(edtxtPassword.text) btnShowPasswordAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToPassword(edtxtPassword.text)
btnShowBiometricAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToBiometric()
btnShowRemoveAppProtectionSection.isChecked -> authenticationService.removeAppProtection() btnShowRemoveAppProtectionSection.isChecked -> authenticationService.removeAppProtection()
else -> log.error("This should never occur! Has there a new authentication method been added?") 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 package net.dankito.banking.ui.android.security
import android.os.Build
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties import android.security.keystore.KeyProperties
import java.security.KeyStore import java.security.KeyStore
import java.security.SecureRandom
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.KeyGenerator import javax.crypto.KeyGenerator
import javax.crypto.SecretKey import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
open class CryptographyManager { open class CryptographyManager {
@ -50,8 +56,8 @@ open class CryptographyManager {
} }
open fun decryptData(cipherText: ByteArray, cipher: Cipher): String { open fun decryptData(cipherText: ByteArray, cipher: Cipher): String {
val plaintext = cipher.doFinal(cipherText) val plainTextBytes = cipher.doFinal(cipherText)
return String(plaintext, PasswordCharset) return String(plainTextBytes, PasswordCharset)
} }
protected open fun getOrCreateSecretKey(keyName: String): SecretKey { protected open fun getOrCreateSecretKey(keyName: String): SecretKey {
@ -75,4 +81,43 @@ open class CryptographyManager {
return keyGenerator.generateKey() 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)
}
}
}
} }