Implemented encrypting default password if no app protection is set
This commit is contained in:
parent
12d6c5a430
commit
4e27bffd09
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue