diff --git a/app/src/main/java/sh/sar/basedbank/LockActivity.kt b/app/src/main/java/sh/sar/basedbank/LockActivity.kt index a669e4f..ef63186 100644 --- a/app/src/main/java/sh/sar/basedbank/LockActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/LockActivity.kt @@ -18,8 +18,6 @@ import kotlinx.coroutines.withContext import sh.sar.basedbank.databinding.ActivityLockBinding import sh.sar.basedbank.ui.home.HomeActivity import sh.sar.basedbank.util.CredentialStore -import java.security.MessageDigest -import java.security.SecureRandom import javax.crypto.SecretKeyFactory import javax.crypto.spec.PBEKeySpec @@ -31,7 +29,6 @@ class LockActivity : AppCompatActivity() { private lateinit var salt: String private lateinit var storedHash: String private var biometricsEnabled = false - private var isLegacyFormat = false private var isVerifying = false private val lockPrefs get() = getSharedPreferences("lock_attempts", MODE_PRIVATE) @@ -51,17 +48,9 @@ class LockActivity : AppCompatActivity() { method = prefs.getString("security_method", "pin") ?: "pin" biometricsEnabled = prefs.getBoolean("biometrics_enabled", false) - // Try new encrypted format first; fall back to legacy SHA-256 - val stored = CredentialStore(this).loadSecurityHash() - if (stored != null) { - salt = stored.first - storedHash = stored.second - isLegacyFormat = false - } else { - salt = prefs.getString("security_salt", "") ?: "" - storedHash = prefs.getString("security_hash", "") ?: "" - isLegacyFormat = true - } + val stored = CredentialStore(this).loadSecurityHash() ?: run { finish(); return } + salt = stored.first + storedHash = stored.second if (method == "pin") { binding.viewPin.visibility = View.VISIBLE @@ -150,7 +139,6 @@ class LockActivity : AppCompatActivity() { val ok = withContext(Dispatchers.Default) { verify(entered) } isVerifying = false if (ok) { - migrateIfNeeded(entered) resetFailures() proceed() } else { @@ -175,7 +163,6 @@ class LockActivity : AppCompatActivity() { val ok = withContext(Dispatchers.Default) { verify(entered) } isVerifying = false if (ok) { - migrateIfNeeded(entered) resetFailures() proceed() } else { @@ -217,30 +204,8 @@ class LockActivity : AppCompatActivity() { private fun verify(input: String): Boolean { if (storedHash.isBlank()) return false - return if (isLegacyFormat) { - sha256Legacy(salt + input) == storedHash - } else { - val saltBytes = Base64.decode(salt, Base64.NO_WRAP) - pbkdf2(input, saltBytes) == storedHash - } - } - - /** - * On the first successful unlock after legacy SHA-256 format is detected, - * transparently migrate to PBKDF2 + CredentialStore. - */ - private fun migrateIfNeeded(input: String) { - if (!isLegacyFormat) return - try { - val newSalt = ByteArray(16).also { SecureRandom().nextBytes(it) } - val newHash = pbkdf2(input, newSalt) - val saltB64 = Base64.encodeToString(newSalt, Base64.NO_WRAP) - CredentialStore(this).saveSecurityHash(saltB64, newHash) - // Remove legacy plaintext fields - getSharedPreferences("prefs", MODE_PRIVATE).edit() - .remove("security_salt").remove("security_hash").apply() - isLegacyFormat = false - } catch (_: Exception) { /* migration will retry next unlock */ } + val saltBytes = Base64.decode(salt, Base64.NO_WRAP) + return pbkdf2(input, saltBytes) == storedHash } private fun triggerBiometric() { @@ -313,9 +278,4 @@ class LockActivity : AppCompatActivity() { } } - /** Legacy: raw SHA-256(salt + input) — only used for migration path. */ - private fun sha256Legacy(input: String) = MessageDigest.getInstance("SHA-256") - .digest(input.toByteArray()).joinToString("") { "%02x".format(it) } - - } diff --git a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt index 74cb95a..bea0ee6 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt @@ -115,21 +115,23 @@ class CredentialsFragment : Fragment() { private fun updateLoginButtonState() { val username = binding.etUsername.text.toString().trim() val password = binding.etPassword.text.toString() - val otpSeed = resolveOtpSeed(binding.etOtpSeed.text.toString().trim()) + val otpSeedRaw = binding.etOtpSeed.text.toString().trim() + val otpSeed = resolveOtpSeed(otpSeedRaw) binding.btnLogin.isEnabled = when (bankType) { "FAHIPAY" -> username.isNotEmpty() && password.isNotEmpty() - else -> username.isNotEmpty() && password.isNotEmpty() && otpSeed.isNotEmpty() && password != otpSeed + else -> username.isNotEmpty() && password.isNotEmpty() && otpSeed.isNotEmpty() && password != otpSeedRaw } } private fun updateOtpDisplay() { - val seed = resolveOtpSeed(binding.etOtpSeed.text.toString().trim()) + val otpSeedRaw = binding.etOtpSeed.text.toString().trim() + val seed = resolveOtpSeed(otpSeedRaw) if (seed.isEmpty()) { binding.cardOtp.visibility = View.INVISIBLE return } val password = binding.etPassword.text.toString() - if (seed == password || seed.matches(Regex("\\d{6}"))) { + if (otpSeedRaw == password || seed.matches(Regex("\\d{6}"))) { binding.cardOtp.visibility = View.INVISIBLE return } diff --git a/app/src/main/java/sh/sar/basedbank/ui/onboarding/SecuritySetupFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/onboarding/SecuritySetupFragment.kt index 6bee1c2..71a42fe 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/onboarding/SecuritySetupFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/onboarding/SecuritySetupFragment.kt @@ -217,9 +217,6 @@ class SecuritySetupFragment : Fragment() { val hash = pbkdf2(input, salt) requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE).edit() .putString("security_method", method) - // Remove legacy plaintext fields if they exist from an old install - .remove("security_salt") - .remove("security_hash") .apply() CredentialStore(requireContext()).saveSecurityHash(saltB64, hash) } diff --git a/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt b/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt index f9565e7..b197a56 100644 --- a/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt +++ b/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt @@ -93,32 +93,10 @@ class CredentialStore(context: Context) { // ── BML login credentials (multi-login, keyed by loginId = username) ──────── fun getBmlLoginIds(): List { - val json = prefs.getString("bml_login_ids", null) - if (json != null) { - return try { - val arr = org.json.JSONArray(json) - (0 until arr.length()).map { arr.getString(it) } - } catch (_: Exception) { emptyList() } - } - // One-time migration from single-slot BML storage - val oldEncUsername = prefs.getString("bml_enc_username", null) ?: return emptyList() + val json = prefs.getString("bml_login_ids", null) ?: return emptyList() return try { - val key = getOrCreateKey() - val loginId = decrypt(oldEncUsername, key) - val edit = prefs.edit() - prefs.getString("bml_enc_password", null)?.let { edit.putString("bml_${loginId}_enc_password", it) } - prefs.getString("bml_enc_otp_seed", null)?.let { edit.putString("bml_${loginId}_enc_otp_seed", it) } - prefs.getString("bml_enc_token", null)?.let { edit.putString("bml_${loginId}_enc_token", it) } - prefs.getString("bml_enc_device_id", null)?.let { edit.putString("bml_${loginId}_enc_device_id", it) } - prefs.getString("bml_enc_profile", null)?.let { edit.putString("bml_${loginId}_enc_profile", it) } - edit.putString("bml_${loginId}_enc_username", oldEncUsername) - edit.remove("bml_enc_username").remove("bml_enc_password").remove("bml_enc_otp_seed") - .remove("bml_enc_token").remove("bml_enc_device_id") - .remove("bml_enc_profile").remove("bml_enc_full_name") - val ids = org.json.JSONArray(listOf(loginId)).toString() - edit.putString("bml_login_ids", ids) - edit.apply() - listOf(loginId) + val arr = org.json.JSONArray(json) + (0 until arr.length()).map { arr.getString(it) } } catch (_: Exception) { emptyList() } }