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 {
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue