Keeping passwords now only as CharArray in memory as it may takes a long time till a String gets garbage collected
This commit is contained in:
parent
bc853b03e3
commit
8b2a210269
|
@ -8,6 +8,11 @@ val Char.isUpperCase: Boolean
|
|||
get() = isLowerCase == false
|
||||
|
||||
|
||||
fun CharArray.asString(): String {
|
||||
return this.joinToString("")
|
||||
}
|
||||
|
||||
|
||||
fun Date.isBefore(other: Date): Boolean {
|
||||
return compareTo(other) < 0
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import net.dankito.banking.ui.model.settings.AppSettings
|
|||
import net.dankito.banking.ui.model.tan.MobilePhoneTanMedium
|
||||
import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium
|
||||
import net.dankito.banking.util.persistence.downloadIcon
|
||||
import net.dankito.utils.multiplatform.asString
|
||||
import net.sqlcipher.database.SQLiteDatabase
|
||||
import net.sqlcipher.database.SupportFactory
|
||||
import org.slf4j.LoggerFactory
|
||||
|
@ -37,13 +38,13 @@ open class RoomBankingPersistence(protected open val applicationContext: Context
|
|||
protected lateinit var database: BankingDatabase
|
||||
|
||||
|
||||
override fun decryptData(password: String?): Boolean {
|
||||
override fun decryptData(password: CharArray): Boolean {
|
||||
return openDatabase(password)
|
||||
}
|
||||
|
||||
override fun changePassword(newPassword: String?): Boolean {
|
||||
override fun changePassword(newPassword: CharArray): Boolean {
|
||||
if (this::database.isInitialized) {
|
||||
val cursor = database.query("PRAGMA rekey = '$newPassword';", emptyArray())
|
||||
val cursor = database.query("PRAGMA rekey = '${newPassword.asString()}';", emptyArray())
|
||||
|
||||
return cursor.count == 1 // TODO: also check if first column content is 'ok' ?
|
||||
}
|
||||
|
@ -52,9 +53,9 @@ open class RoomBankingPersistence(protected open val applicationContext: Context
|
|||
}
|
||||
}
|
||||
|
||||
protected open fun openDatabase(password: String?): Boolean {
|
||||
protected open fun openDatabase(password: CharArray): Boolean {
|
||||
try {
|
||||
val passphrase = password?.let { SQLiteDatabase.getBytes(password.toCharArray()) } ?: ByteArray(0)
|
||||
val passphrase = SQLiteDatabase.getBytes(password)
|
||||
val factory = SupportFactory(passphrase)
|
||||
|
||||
database = Room.databaseBuilder(applicationContext, BankingDatabase::class.java, DatabaseName)
|
||||
|
|
|
@ -35,12 +35,12 @@ open class BankingPersistenceJson(
|
|||
}
|
||||
|
||||
|
||||
override fun decryptData(password: String?): Boolean {
|
||||
override fun decryptData(password: CharArray): Boolean {
|
||||
// TODO: may implement data decryption. But then we have to store password to be able to encrypt data
|
||||
return true
|
||||
}
|
||||
|
||||
override fun changePassword(newPassword: String?): Boolean {
|
||||
override fun changePassword(newPassword: CharArray): Boolean {
|
||||
// TODO: may implement data decryption. But then we have to store newPassword to be able to encrypt data
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -67,9 +67,7 @@ open class LoginActivity : BaseActivity() {
|
|||
protected open fun checkEnteredPasswordAndLogIn() {
|
||||
btnLogin.isEnabled = false
|
||||
|
||||
val enteredPassword = edtxtLoginPassword.text
|
||||
|
||||
if (authenticationService.authenticateUserWithPassword(enteredPassword)) {
|
||||
if (authenticationService.authenticateUserWithPassword(edtxtLoginPassword.chars)) {
|
||||
navigateToMainActivity()
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -6,6 +6,7 @@ import net.dankito.banking.persistence.IBankingPersistence
|
|||
import net.dankito.banking.ui.android.security.CryptographyManager
|
||||
import net.dankito.banking.util.ISerializer
|
||||
import net.dankito.utils.multiplatform.File
|
||||
import net.dankito.utils.multiplatform.asString
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.crypto.Cipher
|
||||
|
||||
|
@ -54,7 +55,7 @@ open class AuthenticationService(
|
|||
}
|
||||
|
||||
|
||||
open fun authenticateUserWithPassword(enteredPassword: String): Boolean {
|
||||
open fun authenticateUserWithPassword(enteredPassword: CharArray): Boolean {
|
||||
if (isCorrectUserPassword(enteredPassword)) {
|
||||
loadAuthenticationSettings()?.let { settings ->
|
||||
return openDatabase(settings, enteredPassword)
|
||||
|
@ -64,9 +65,9 @@ open class AuthenticationService(
|
|||
return false
|
||||
}
|
||||
|
||||
open fun isCorrectUserPassword(enteredPassword: String): Boolean {
|
||||
open fun isCorrectUserPassword(enteredPassword: CharArray): Boolean {
|
||||
loadAuthenticationSettings()?.let { settings ->
|
||||
val result = BCrypt.verifyer().verify(enteredPassword.toCharArray(), settings.hashedUserPassword)
|
||||
val result = BCrypt.verifyer().verify(enteredPassword, settings.hashedUserPassword)
|
||||
|
||||
return result.verified
|
||||
}
|
||||
|
@ -119,7 +120,7 @@ open class AuthenticationService(
|
|||
?: run { result(false) }
|
||||
}
|
||||
|
||||
protected open fun openDatabase(settings: AuthenticationSettings, userPassword: String? = null): Boolean {
|
||||
protected open fun openDatabase(settings: AuthenticationSettings, userPassword: CharArray? = null): Boolean {
|
||||
settings.defaultPassword?.let { encryptedPassword ->
|
||||
settings.initializationVector?.let { iv ->
|
||||
settings.salt?.let { salt ->
|
||||
|
@ -127,7 +128,7 @@ open class AuthenticationService(
|
|||
decodeFromBase64(iv), decodeFromBase64(salt))
|
||||
|
||||
if (userPassword != null) {
|
||||
return openDatabase(userPassword + "_" + defaultPassword)
|
||||
return openDatabase(concatPasswords(userPassword, defaultPassword))
|
||||
}
|
||||
else {
|
||||
return openDatabase(defaultPassword)
|
||||
|
@ -139,7 +140,7 @@ open class AuthenticationService(
|
|||
return false
|
||||
}
|
||||
|
||||
protected open fun openDatabase(password: String?): Boolean {
|
||||
protected open fun openDatabase(password: CharArray): Boolean {
|
||||
return persistence.decryptData(password)
|
||||
}
|
||||
|
||||
|
@ -148,7 +149,7 @@ open class AuthenticationService(
|
|||
saveNewAuthenticationMethod(AuthenticationType.Biometric, null)
|
||||
}
|
||||
|
||||
open fun setAuthenticationMethodToPassword(newPassword: String) {
|
||||
open fun setAuthenticationMethodToPassword(newPassword: CharArray) {
|
||||
saveNewAuthenticationMethod(AuthenticationType.Password, newPassword)
|
||||
}
|
||||
|
||||
|
@ -157,7 +158,7 @@ open class AuthenticationService(
|
|||
}
|
||||
|
||||
|
||||
protected open fun saveNewAuthenticationMethod(type: AuthenticationType, newUserPassword: String?): Boolean {
|
||||
protected open fun saveNewAuthenticationMethod(type: AuthenticationType, newUserPassword: CharArray?): Boolean {
|
||||
val settings = loadOrCreateDefaultAuthenticationSettings()
|
||||
val newDefaultPassword = generateRandomPassword()
|
||||
var newDatabasePassword = newDefaultPassword
|
||||
|
@ -182,8 +183,8 @@ open class AuthenticationService(
|
|||
settings.salt = encodeToBase64(salt)
|
||||
|
||||
if (newUserPassword != null) {
|
||||
settings.hashedUserPassword = BCrypt.withDefaults().hashToString(12, newUserPassword.toCharArray())
|
||||
newDatabasePassword = newUserPassword + "_" + newDefaultPassword
|
||||
settings.hashedUserPassword = BCrypt.withDefaults().hashToString(12, newUserPassword)
|
||||
newDatabasePassword = concatPasswords(newUserPassword, newDefaultPassword)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,6 +200,16 @@ open class AuthenticationService(
|
|||
return false
|
||||
}
|
||||
|
||||
protected open fun concatPasswords(userPassword: CharArray, defaultPassword: CharArray): CharArray {
|
||||
val concatenated = StringBuilder(userPassword.size + defaultPassword.size + 1)
|
||||
|
||||
concatenated.append(userPassword)
|
||||
concatenated.append("_")
|
||||
concatenated.append(defaultPassword)
|
||||
|
||||
return concatenated.toList().toCharArray()
|
||||
}
|
||||
|
||||
protected open fun loadOrCreateDefaultAuthenticationSettings(): AuthenticationSettings {
|
||||
return loadAuthenticationSettings() ?: AuthenticationSettings(AuthenticationType.None)
|
||||
}
|
||||
|
@ -236,19 +247,19 @@ open class AuthenticationService(
|
|||
}
|
||||
|
||||
|
||||
open fun generateRandomPassword(): String {
|
||||
open fun generateRandomPassword(): CharArray {
|
||||
return generateRandomPassword(30)
|
||||
}
|
||||
|
||||
open fun generateRandomPassword(passwordLength: Int): String {
|
||||
open fun generateRandomPassword(passwordLength: Int): CharArray {
|
||||
val dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789§±!@#$%^&*-_=+;:|/?.>,<"
|
||||
|
||||
val passwordBuilder = StringBuilder()
|
||||
IntRange(0, passwordLength).forEach {
|
||||
passwordBuilder.append(dictionary.random())
|
||||
val password = CharArray(passwordLength)
|
||||
IntRange(0, passwordLength - 1).forEach { index ->
|
||||
password[index] = dictionary.random()
|
||||
}
|
||||
|
||||
return passwordBuilder.toString()
|
||||
return password
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ open class ProtectAppSettingsDialog : SettingsDialogBase() {
|
|||
|
||||
protected open fun setAuthenticationMethod() {
|
||||
when {
|
||||
btnShowPasswordAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToPassword(edtxtPassword.text)
|
||||
btnShowPasswordAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToPassword(edtxtPassword.chars)
|
||||
btnShowBiometricAuthenticationSection.isChecked -> authenticationService.setAuthenticationMethodToBiometric()
|
||||
btnShowRemoveAppProtectionSection.isChecked -> authenticationService.removeAppProtection()
|
||||
else -> log.error("This should never occur! Has there a new authentication method been added?")
|
||||
|
|
|
@ -23,8 +23,6 @@ open class CryptographyManager {
|
|||
|
||||
const val AndroidKeyStore = "AndroidKeyStore"
|
||||
|
||||
val PasswordCharset = Charsets.UTF_8
|
||||
|
||||
private const val KeySize: Int = 256
|
||||
private const val EncryptionBlockMode = KeyProperties.BLOCK_MODE_GCM
|
||||
private const val EncryptionPadding = KeyProperties.ENCRYPTION_PADDING_NONE
|
||||
|
@ -78,33 +76,33 @@ open class CryptographyManager {
|
|||
}
|
||||
|
||||
|
||||
open fun encryptData(plaintext: String, cipher: Cipher): ByteArray {
|
||||
return cipher.doFinal(plaintext.toByteArray(PasswordCharset))
|
||||
open fun encryptData(plaintext: CharArray, cipher: Cipher): ByteArray {
|
||||
return cipher.doFinal(mapToBytes(plaintext))
|
||||
}
|
||||
|
||||
open fun decryptData(cipherText: ByteArray, cipher: Cipher): String {
|
||||
open fun decryptData(cipherText: ByteArray, cipher: Cipher): CharArray {
|
||||
val plainTextBytes = cipher.doFinal(cipherText)
|
||||
return String(plainTextBytes, PasswordCharset)
|
||||
return mapToChars(plainTextBytes)
|
||||
}
|
||||
|
||||
|
||||
open fun encryptDataWithPbe(plaintext: String, password: String, salt: ByteArray): Pair<ByteArray, ByteArray> {
|
||||
open fun encryptDataWithPbe(plaintext: CharArray, 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)
|
||||
return Pair(cipher.doFinal(mapToBytes(plaintext)), initializationVector)
|
||||
}
|
||||
|
||||
open fun decryptDataWithPbe(cipherText: ByteArray, password: String, initializationVector: ByteArray, salt: ByteArray): String {
|
||||
open fun decryptDataWithPbe(cipherText: ByteArray, password: String, initializationVector: ByteArray, salt: ByteArray): CharArray {
|
||||
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)
|
||||
return mapToChars(plainTextBytes)
|
||||
}
|
||||
|
||||
protected open fun generatePbeSecretKey(userPassword: String, salt: ByteArray): SecretKey {
|
||||
|
@ -127,6 +125,15 @@ open class CryptographyManager {
|
|||
}
|
||||
|
||||
|
||||
protected open fun mapToBytes(chars: CharArray): ByteArray {
|
||||
return chars.map { it.toByte() }.toByteArray()
|
||||
}
|
||||
|
||||
protected open fun mapToChars(plainTextBytes: ByteArray): CharArray {
|
||||
return plainTextBytes.map { it.toChar() }.toCharArray()
|
||||
}
|
||||
|
||||
|
||||
open fun findBestPbeAlgorithm(): String? {
|
||||
return findBestMatchingAlgorithm(SecurityProviderServiceType.SecretKeyFactory, "PBKDF2","PBKDF2WithHmacSHA256")
|
||||
?: findBestMatchingAlgorithm(SecurityProviderServiceType.SecretKeyFactory, "PBE")
|
||||
|
|
|
@ -52,6 +52,9 @@ open class FormEditText @JvmOverloads constructor(
|
|||
get() = textInputEditText.textString
|
||||
set(value) = textInputEditText.setText(value)
|
||||
|
||||
open val chars: CharArray
|
||||
get() = actualEditText.text.toList().toCharArray()
|
||||
|
||||
open val actualEditText: EditText
|
||||
get() = textInputEditText
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ import net.dankito.banking.ui.model.settings.AppSettings
|
|||
|
||||
interface IBankingPersistence {
|
||||
|
||||
fun decryptData(password: String?): Boolean
|
||||
fun decryptData(password: CharArray): Boolean
|
||||
|
||||
fun changePassword(newPassword: String?): Boolean
|
||||
fun changePassword(newPassword: CharArray): Boolean
|
||||
|
||||
|
||||
fun saveOrUpdateBank(bank: TypedBankData, allBanks: List<TypedBankData>)
|
||||
|
|
|
@ -6,11 +6,11 @@ import net.dankito.banking.ui.model.settings.AppSettings
|
|||
|
||||
open class NoOpBankingPersistence : IBankingPersistence {
|
||||
|
||||
override fun decryptData(password: String?): Boolean {
|
||||
override fun decryptData(password: CharArray): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun changePassword(newPassword: String?): Boolean {
|
||||
override fun changePassword(newPassword: CharArray): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue