Files
mkdocs/docs/GoogleAndroid开发文档体系/最佳实践/安全最佳实践.md
2026-01-15 16:42:52 +08:00

864 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 安全最佳实践
Android应用安全是开发过程中必须重视的方面。本文档提供Android应用安全开发的最佳实践指南。
## 目录
- [安全开发原则](#安全开发原则)
- [数据安全](#数据安全)
- [网络安全](#网络安全)
- [代码安全](#代码安全)
- [安全测试](#安全测试)
---
## 安全开发原则
### 1. 最小权限原则
只申请和使用应用真正需要的权限,避免过度申请权限。
#### 权限申请原则
```java
// ❌ 错误:申请不必要的权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_SMS" />
// 如果应用不需要这些功能,不应该申请
// ✅ 正确:只申请必要的权限
<uses-permission android:name="android.permission.CAMERA" />
// 只在需要相机功能时申请
```
#### 运行时权限检查
```java
// 使用权限前检查
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// 解释为什么需要权限
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// 显示解释对话框
showPermissionRationale();
} else {
// 直接申请权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CODE_CAMERA);
}
} else {
// 权限已授予,使用功能
useCamera();
}
```
### 2. 纵深防御
采用多层安全防护机制,不依赖单一安全措施。
#### 多层防护示例
```java
// 第一层:权限检查
if (checkPermission()) {
// 第二层:输入验证
if (validateInput(data)) {
// 第三层:数据加密
String encrypted = encrypt(data);
// 第四层:安全存储
saveSecurely(encrypted);
}
}
```
### 3. 安全设计
从设计阶段就考虑安全问题,而不是事后补救。
#### 安全设计要点
- **威胁建模**:识别潜在的安全威胁
- **安全架构**:设计安全的应用架构
- **安全编码**:遵循安全编码规范
- **安全测试**:进行安全测试和审计
### 4. 数据最小化
只收集和存储必要的数据,及时删除不需要的数据。
```java
// ❌ 错误:收集过多数据
class User {
String name;
String email;
String phone;
String address;
String idCard; // 不需要身份证号
String bankCard; // 不需要银行卡号
}
// ✅ 正确:只收集必要数据
class User {
String name;
String email;
// 只在需要时收集其他信息
}
```
### 5. 默认安全
默认采用安全配置,而不是默认开放。
```xml
<!-- ❌ 错误:默认导出组件 -->
<activity
android:name=".SensitiveActivity"
android:exported="true" />
<!-- ✅ 正确:默认不导出,需要时显式设置 -->
<activity
android:name=".SensitiveActivity"
android:exported="false" />
```
---
## 数据安全
### 1. 敏感数据加密
对敏感数据进行加密存储,包括密码、密钥、个人信息等。
#### 使用Android密钥库
```java
// 使用Android密钥库存储密钥
public class KeyStoreHelper {
private static final String KEYSTORE_ALIAS = "my_key";
private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
public static void generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
KEYSTORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
keyGenerator.init(spec);
keyGenerator.generateKey();
}
public static byte[] encrypt(String data) throws Exception {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
KeyStore.SecretKeyEntry secretKeyEntry =
(KeyStore.SecretKeyEntry) keyStore.getEntry(KEYSTORE_ALIAS, null);
SecretKey secretKey = secretKeyEntry.getSecretKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] iv = cipher.getIV();
byte[] encrypted = cipher.doFinal(data.getBytes());
// 将IV和加密数据组合
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(iv);
outputStream.write(encrypted);
return outputStream.toByteArray();
}
}
```
#### 数据加密存储
```java
// 加密敏感数据
public class SecureStorage {
public static void saveEncryptedData(String key, String value) {
try {
byte[] encrypted = KeyStoreHelper.encrypt(value);
// 存储加密后的数据
SharedPreferences prefs = getSharedPreferences("secure_prefs", MODE_PRIVATE);
String base64 = Base64.encodeToString(encrypted, Base64.DEFAULT);
prefs.edit().putString(key, base64).apply();
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getDecryptedData(String key) {
try {
SharedPreferences prefs = getSharedPreferences("secure_prefs", MODE_PRIVATE);
String base64 = prefs.getString(key, null);
if (base64 == null) return null;
byte[] encrypted = Base64.decode(base64, Base64.DEFAULT);
// 解密数据
return KeyStoreHelper.decrypt(encrypted);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
```
### 2. 安全存储
使用安全的存储方式,避免明文存储敏感信息。
#### SharedPreferences安全使用
```java
// ❌ 错误:明文存储敏感信息
SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
prefs.edit().putString("password", password).apply();
// ✅ 正确:加密后存储
String encryptedPassword = encrypt(password);
prefs.edit().putString("password", encryptedPassword).apply();
```
#### 使用EncryptedSharedPreferencesAndroid 6.0+
```java
// 使用EncryptedSharedPreferences
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences encryptedPrefs = EncryptedSharedPreferences.create(
context,
"encrypted_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
encryptedPrefs.edit()
.putString("sensitive_data", data)
.apply();
```
### 3. 数据清理
及时清理不再需要的敏感数据。
```java
// 清理敏感数据
public void clearSensitiveData() {
// 清理内存中的数据
password = null;
apiKey = null;
// 清理SharedPreferences
SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
prefs.edit().clear().apply();
// 清理文件
File sensitiveFile = new File(getFilesDir(), "sensitive.txt");
if (sensitiveFile.exists()) {
// 安全删除:覆盖后删除
secureDelete(sensitiveFile);
}
}
// 安全删除文件
private void secureDelete(File file) {
if (file.exists()) {
// 覆盖文件内容
RandomAccessFile raf = new RandomAccessFile(file, "rws");
raf.setLength(0);
raf.close();
// 删除文件
file.delete();
}
}
```
### 4. 日志安全
避免在日志中输出敏感信息。
```java
// ❌ 错误:日志中输出敏感信息
Log.d("TAG", "User password: " + password);
Log.d("TAG", "API key: " + apiKey);
Log.d("TAG", "User token: " + token);
// ✅ 正确:敏感信息脱敏或移除
Log.d("TAG", "User login attempt");
// 或使用条件编译
if (BuildConfig.DEBUG) {
Log.d("TAG", "Debug info: " + debugInfo);
}
```
### 5. 内存安全
避免在内存中长时间保存敏感数据。
```java
// 使用后立即清理
public void processSensitiveData(String data) {
try {
// 处理数据
process(data);
} finally {
// 清理内存
data = null;
System.gc(); // 建议垃圾回收
}
}
// 使用char[]而不是String存储密码可被清理
public void handlePassword(char[] password) {
try {
// 使用密码
authenticate(password);
} finally {
// 清理密码
Arrays.fill(password, '\0');
}
}
```
---
## 网络安全
### 1. HTTPS通信
使用HTTPS进行网络通信避免HTTP明文传输。
#### 配置网络安全
```xml
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<!-- 默认配置只允许HTTPS -->
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- 特定域名配置 -->
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="system" />
<!-- 自定义证书 -->
<certificates src="@raw/my_ca" />
</trust-anchors>
</domain-config>
</network-security-config>
```
```xml
<!-- AndroidManifest.xml -->
<application
android:networkSecurityConfig="@xml/network_security_config"
... />
```
#### 证书固定Certificate Pinning
```xml
<!-- 证书固定配置 -->
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2025-12-31">
<!-- SHA-256指纹 -->
<pin digest="SHA-256">base64==</pin>
<!-- 备用证书 -->
<pin digest="SHA-256">base64==</pin>
</pin-set>
</domain-config>
```
### 2. 输入验证
验证所有网络输入,防止注入攻击。
```java
// 输入验证
public class InputValidator {
// 验证URL
public static boolean isValidUrl(String url) {
if (url == null || url.isEmpty()) {
return false;
}
try {
URL urlObj = new URL(url);
// 只允许HTTPS
return "https".equals(urlObj.getProtocol());
} catch (MalformedURLException e) {
return false;
}
}
// 验证输入长度
public static boolean isValidLength(String input, int maxLength) {
return input != null && input.length() <= maxLength;
}
// 验证输入格式
public static boolean isValidEmail(String email) {
if (email == null || email.isEmpty()) {
return false;
}
String pattern = "^[A-Za-z0-9+_.-]+@(.+)$";
return email.matches(pattern);
}
// 防止SQL注入
public static String sanitizeSql(String input) {
if (input == null) return "";
// 转义特殊字符
return input.replace("'", "''")
.replace(";", "")
.replace("--", "");
}
}
```
### 3. 请求签名
对重要请求进行签名,防止篡改。
```java
// 请求签名
public class RequestSigner {
public static String signRequest(String data, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
secret.getBytes(), "HmacSHA256");
mac.init(secretKey);
byte[] hash = mac.doFinal(data.getBytes());
return Base64.encodeToString(hash, Base64.NO_WRAP);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static boolean verifyRequest(String data, String signature, String secret) {
String calculatedSignature = signRequest(data, secret);
return signature != null && signature.equals(calculatedSignature);
}
}
```
### 4. 超时和重试
设置合理的超时和重试机制。
```java
// 配置OkHttp超时
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
```
### 5. 敏感信息传输
避免在URL或请求头中传输敏感信息。
```java
// ❌ 错误敏感信息在URL中
String url = "https://api.example.com/login?username=user&password=pass";
// ✅ 正确使用POST请求体
RequestBody body = new FormBody.Builder()
.add("username", username)
.add("password", password)
.build();
Request request = new Request.Builder()
.url("https://api.example.com/login")
.post(body)
.build();
```
---
## 代码安全
### 1. 组件安全
正确配置组件权限和导出状态。
#### Activity安全配置
```xml
<!-- AndroidManifest.xml -->
<!-- ❌ 错误:导出且无权限保护 -->
<activity
android:name=".SensitiveActivity"
android:exported="true" />
<!-- ✅ 正确:不导出或设置权限 -->
<activity
android:name=".SensitiveActivity"
android:exported="false"
android:permission="com.example.PERMISSION" />
```
#### Service安全配置
```xml
<!-- Service安全配置 -->
<service
android:name=".MyService"
android:exported="false"
android:permission="com.example.SERVICE_PERMISSION" />
```
#### BroadcastReceiver安全配置
```xml
<!-- 动态注册更安全 -->
<!-- 静态注册需要设置权限 -->
<receiver
android:name=".MyReceiver"
android:exported="false"
android:permission="com.example.BROADCAST_PERMISSION">
<intent-filter>
<action android:name="com.example.ACTION" />
</intent-filter>
</receiver>
```
#### ContentProvider安全配置
```xml
<!-- ContentProvider安全配置 -->
<provider
android:name=".MyProvider"
android:authorities="com.example.provider"
android:exported="false"
android:readPermission="com.example.READ_PERMISSION"
android:writePermission="com.example.WRITE_PERMISSION" />
```
### 2. Intent安全
验证Intent的来源和内容。
```java
// Intent安全处理
public class IntentValidator {
// 验证Intent来源
public static boolean isValidIntent(Intent intent) {
// 检查组件是否存在
ComponentName component = intent.getComponent();
if (component == null) {
return false;
}
// 验证包名
String packageName = component.getPackageName();
if (!packageName.equals(getPackageName())) {
return false;
}
return true;
}
// 验证Intent数据
public static boolean isValidIntentData(Intent intent) {
Uri data = intent.getData();
if (data == null) {
return false;
}
// 验证scheme
String scheme = data.getScheme();
if (!"https".equals(scheme) && !"content".equals(scheme)) {
return false;
}
return true;
}
// 安全启动Activity
public static void startActivitySafely(Context context, Intent intent) {
if (isValidIntent(intent) && isValidIntentData(intent)) {
context.startActivity(intent);
} else {
// 记录安全事件
Log.w("Security", "Invalid intent blocked");
}
}
}
```
### 3. 代码混淆
使用ProGuard或R8进行代码混淆。
#### ProGuard配置
```proguard
# proguard-rules.pro
# 保留必要的类
-keep class com.example.model.** { *; }
# 移除日志
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
}
# 混淆配置
-dontpreverify
-repackageclasses ''
-allowaccessmodification
-optimizations !code/simplification/arithmetic
```
#### build.gradle配置
```gradle
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}
```
### 4. 反调试保护
防止应用被调试和分析。
```java
// 检测调试器
public class AntiDebug {
public static boolean isDebugging() {
// 检查调试标志
if (Debug.isDebuggerConnected()) {
return true;
}
// 检查tracerpid
try {
BufferedReader reader = new BufferedReader(
new FileReader("/proc/self/status"));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("TracerPid:")) {
int tracerPid = Integer.parseInt(line.split("\\s+")[1]);
if (tracerPid != 0) {
return true;
}
}
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
// 检测模拟器
public static boolean isEmulator() {
return Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| "google_sdk".equals(Build.PRODUCT);
}
}
```
### 5. 输入验证
验证所有用户输入,防止注入攻击。
```java
// 输入验证工具
public class InputValidator {
// 验证文件名
public static boolean isValidFileName(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return false;
}
// 防止路径遍历
if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
return false;
}
return true;
}
// 验证JSON
public static boolean isValidJson(String json) {
try {
new JSONObject(json);
return true;
} catch (JSONException e) {
return false;
}
}
// 验证XML
public static boolean isValidXml(String xml) {
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser parser = factory.newPullParser();
parser.setInput(new StringReader(xml));
while (parser.next() != XmlPullParser.END_DOCUMENT) {
// 解析XML
}
return true;
} catch (Exception e) {
return false;
}
}
}
```
---
## 安全测试
### 1. 静态代码分析
使用工具进行静态代码分析,发现潜在安全问题。
#### 使用工具
- **Android Lint**Android官方代码检查工具
- **FindBugs/SpotBugs**Java代码缺陷检测
- **SonarQube**:代码质量分析平台
- **Checkmarx**:安全漏洞扫描
#### Lint配置
```gradle
android {
lintOptions {
// 将警告视为错误
warningsAsErrors true
// 检查所有问题
checkAllWarnings true
// 忽略某些警告
disable 'UnusedResources'
// 输出报告
htmlReport true
htmlOutput file("$project.buildDir/reports/lint/lint.html")
}
}
```
### 2. 动态安全测试
在运行时测试应用的安全性。
#### 权限测试
```java
// 测试权限检查
@Test
public void testPermissionCheck() {
// 测试权限未授予时的行为
when(ContextCompat.checkSelfPermission(any(), anyString()))
.thenReturn(PackageManager.PERMISSION_DENIED);
// 验证应用正确处理权限拒绝
assertFalse(hasPermission());
}
```
#### 网络安全测试
```java
// 测试HTTPS连接
@Test
public void testHttpsConnection() {
// 验证只使用HTTPS
String url = getApiUrl();
assertTrue(url.startsWith("https://"));
// 测试证书验证
// ...
}
```
### 3. 渗透测试
模拟攻击者进行渗透测试。
#### 测试项目
- **组件导出测试**:检查是否有不必要的组件导出
- **权限测试**:测试权限是否正确配置
- **数据泄露测试**:检查是否有敏感信息泄露
- **注入攻击测试**测试SQL注入、XSS等
- **加密测试**:验证数据是否正确加密
### 4. 安全审计
定期进行安全审计。
#### 审计清单
- [ ] 权限使用是否合理
- [ ] 敏感数据是否加密
- [ ] 网络通信是否使用HTTPS
- [ ] 组件是否正确配置
- [ ] 输入是否验证
- [ ] 日志是否包含敏感信息
- [ ] 代码是否混淆
- [ ] 依赖库是否安全
### 5. 漏洞扫描
使用自动化工具扫描已知漏洞。
#### 工具推荐
- **OWASP Mobile Top 10**:移动应用安全风险清单
- **MobSF (Mobile Security Framework)**:移动应用安全测试框架
- **QARK (Quick Android Review Kit)**Android应用安全分析工具
---
## 总结
Android应用安全是一个持续的过程需要
1. **遵循安全原则**:最小权限、纵深防御、安全设计
2. **保护数据安全**:加密存储、安全传输、及时清理
3. **确保网络安全**HTTPS通信、输入验证、请求签名
4. **加强代码安全**:组件安全、代码混淆、输入验证
5. **进行安全测试**:静态分析、动态测试、渗透测试
建议:
- 定期进行安全审计
- 关注安全更新和漏洞公告
- 使用官方推荐的安全实践
- 参与安全社区,学习最新安全知识
---
*最后更新2024年*