Skip to content

Commit cc83781

Browse files
committed
重构 PasswordStorageManager
1 parent 952dde7 commit cc83781

4 files changed

Lines changed: 119 additions & 32 deletions

File tree

app/build.gradle.kts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
13
plugins {
24
alias(libs.plugins.android.application)
35
alias(libs.plugins.kotlin.android)
4-
// alias(libs.plugins.kotlin.compose)
56
}
67

78
android {
89
namespace = "cn.lc6464.fileencryptor"
9-
compileSdk = 35
10+
compileSdk = 36
1011

1112
defaultConfig {
1213
applicationId = "cn.lc6464.fileencryptor"
1314
minSdk = 31
14-
targetSdk = 35
15+
targetSdk = 36
1516
versionCode = 2
1617
versionName = "2.0"
1718

@@ -32,8 +33,10 @@ android {
3233
sourceCompatibility = JavaVersion.VERSION_11
3334
targetCompatibility = JavaVersion.VERSION_11
3435
}
35-
kotlinOptions {
36-
jvmTarget = "11"
36+
kotlin {
37+
compilerOptions {
38+
jvmTarget = JvmTarget.JVM_11
39+
}
3740
}
3841
buildFeatures {
3942
viewBinding = true
@@ -54,8 +57,6 @@ dependencies {
5457

5558
implementation(libs.androidx.lifecycle.runtime.ktx)
5659

57-
implementation(libs.androidx.security.crypto)
58-
5960
testImplementation(libs.junit)
6061
androidTestImplementation(libs.androidx.junit)
6162
androidTestImplementation(libs.androidx.espresso.core)

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools">
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
43

54
<application
65
android:icon="@mipmap/ic_launcher"
76
android:label="@string/app_name"
8-
android:roundIcon="@mipmap/ic_launcher_round"
97
android:supportsRtl="true"
108
android:theme="@style/Theme.FileEncryptor">
119
<activity
Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,127 @@
11
package cn.lc6464.fileencryptor
22

33
import android.content.Context
4-
import androidx.security.crypto.EncryptedSharedPreferences
5-
import androidx.security.crypto.MasterKey
4+
import android.security.keystore.KeyGenParameterSpec
5+
import android.security.keystore.KeyProperties
66
import androidx.core.content.edit
7+
import java.nio.charset.StandardCharsets
8+
import java.security.KeyStore
9+
import javax.crypto.Cipher
10+
import javax.crypto.KeyGenerator
11+
import javax.crypto.SecretKey
12+
import javax.crypto.spec.GCMParameterSpec
713

14+
/**
15+
* 这是一个辅助类,负责所有与 Android Keystore 交互的加密和解密操作。
16+
* 它将密钥安全地存储在硬件支持的 Keystore 中。
17+
*/
18+
private class CryptoHelper {
19+
20+
private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
21+
load(null)
22+
}
23+
24+
private fun getSecretKey(alias: String): SecretKey {
25+
return (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry)?.secretKey
26+
?: generateSecretKey(alias)
27+
}
28+
29+
private fun generateSecretKey(alias: String): SecretKey {
30+
val keyGenerator = KeyGenerator.getInstance(
31+
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
32+
)
33+
val parameterSpec = KeyGenParameterSpec.Builder(
34+
alias,
35+
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
36+
)
37+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
38+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
39+
.setKeySize(256)
40+
.build()
41+
keyGenerator.init(parameterSpec)
42+
return keyGenerator.generateKey()
43+
}
44+
45+
fun encrypt(data: String, keyAlias: String): Pair<ByteArray, ByteArray> {
46+
val key = getSecretKey(keyAlias)
47+
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
48+
cipher.init(Cipher.ENCRYPT_MODE, key)
49+
val encryptedData = cipher.doFinal(data.toByteArray(StandardCharsets.UTF_8))
50+
return Pair(cipher.iv, encryptedData)
51+
}
52+
53+
fun decrypt(iv: ByteArray, encryptedData: ByteArray, keyAlias: String): String {
54+
val key = getSecretKey(keyAlias)
55+
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
56+
val spec = GCMParameterSpec(128, iv) // GCM 认证标签长度为 128 位
57+
cipher.init(Cipher.DECRYPT_MODE, key, spec)
58+
val decryptedData = cipher.doFinal(encryptedData)
59+
return String(decryptedData, StandardCharsets.UTF_8)
60+
}
61+
}
62+
63+
64+
/**
65+
* 使用平台原生 API (AndroidKeyStore 和 SharedPreferences) 来安全地存储密码。
66+
* 这取代了已弃用的 EncryptedSharedPreferences。
67+
*/
868
class PasswordStorageManager(context: Context) {
969

10-
// 使用 MasterKey.Builder 替换已弃用的 MasterKeys.getOrCreate
11-
// 这是 Android Security 库推荐的现代方法。
12-
private val masterKey = MasterKey.Builder(context)
13-
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
14-
.build()
15-
16-
// 使用接受 MasterKey 对象的 create 方法重载
17-
// 注意参数顺序也发生了变化 (context 现在是第一个参数)。
18-
private val sharedPreferences = EncryptedSharedPreferences.create(
19-
context, // 第一个参数是 Context
20-
"secret_shared_prefs", // 第二个参数是文件名
21-
masterKey, // 第三个参数是 MasterKey 对象
22-
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
23-
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
24-
)
70+
private val cryptoHelper = CryptoHelper()
71+
private val sharedPreferences =
72+
context.getSharedPreferences("secure_prefs", Context.MODE_PRIVATE)
2573

2674
companion object {
27-
private const val KEY_PASSWORD = "password"
75+
private const val KEY_ALIAS_PASSWORD = "password_key_alias"
76+
private const val PREF_KEY_ENCRYPTED_PASSWORD = "encrypted_password"
77+
private const val PREF_KEY_PASSWORD_IV = "password_iv"
2878
}
2979

3080
fun savePassword(password: String) {
31-
sharedPreferences.edit { putString(KEY_PASSWORD, password) }
81+
try {
82+
val (iv, encryptedPassword) = cryptoHelper.encrypt(password, KEY_ALIAS_PASSWORD)
83+
sharedPreferences.edit {
84+
putString(
85+
PREF_KEY_PASSWORD_IV,
86+
android.util.Base64.encodeToString(iv, android.util.Base64.NO_WRAP)
87+
)
88+
putString(
89+
PREF_KEY_ENCRYPTED_PASSWORD,
90+
android.util.Base64.encodeToString(
91+
encryptedPassword,
92+
android.util.Base64.NO_WRAP
93+
)
94+
)
95+
}
96+
} catch (e: Exception) {
97+
// 在这里处理加密失败的异常,例如记录日志
98+
e.printStackTrace()
99+
}
32100
}
33101

34102
fun getPassword(): String? {
35-
return sharedPreferences.getString(KEY_PASSWORD, null)
103+
return try {
104+
val ivString = sharedPreferences.getString(PREF_KEY_PASSWORD_IV, null)
105+
val encryptedPasswordString =
106+
sharedPreferences.getString(PREF_KEY_ENCRYPTED_PASSWORD, null)
107+
108+
if (ivString != null && encryptedPasswordString != null) {
109+
val iv = android.util.Base64.decode(ivString, android.util.Base64.NO_WRAP)
110+
val encryptedPassword =
111+
android.util.Base64.decode(encryptedPasswordString, android.util.Base64.NO_WRAP)
112+
cryptoHelper.decrypt(iv, encryptedPassword, KEY_ALIAS_PASSWORD)
113+
} else {
114+
null
115+
}
116+
} catch (e: Exception) {
117+
// 在这里处理解密失败的异常
118+
e.printStackTrace()
119+
// 如果解密失败(例如密钥已更改或数据损坏),最好清除旧数据
120+
sharedPreferences.edit {
121+
remove(PREF_KEY_PASSWORD_IV)
122+
remove(PREF_KEY_ENCRYPTED_PASSWORD)
123+
}
124+
null
125+
}
36126
}
37127
}

gradle/libs.versions.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ constraintlayout = "2.2.1"
1212
material = "1.12.0"
1313
kotlinxCorutinesCore = "1.10.2"
1414
kotlinxCorutinesAndroid = "1.10.2"
15-
securityCrypto = "1.1.0"
1615

1716
[libraries]
1817
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -26,7 +25,6 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
2625
androidx-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
2726
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCorutinesCore" }
2827
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCorutinesAndroid" }
29-
androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "securityCrypto" }
3028

3129
[plugins]
3230
android-application = { id = "com.android.application", version.ref = "agp" }

0 commit comments

Comments
 (0)