Files
secure-device-demo/android-device/DeviceCrypto.java

335 lines
12 KiB
Java
Raw Permalink 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.
package com.secure.device;
import android.content.Context;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
/**
* 设备端安全模块Android
*
* 核心设计:
* - TEE 密钥对RSA-2048私钥不可导出用于解密服务端下发数据 + 签名
* - 每次拍照生成随机 DEKAES-256用后即弃
* - DEK 明文通过 HTTPS 传给服务端,服务端用 UK 加密后存储
*
* 安全边界:
* - 私钥永不出 TEE
* - SN 仅做身份标识,不参与加密
* - 恢复出厂 → 密钥销毁 → 新密钥对 → 用户短信验证恢复
*/
public class DeviceCrypto {
// ==================== 常量 ====================
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
private static final String KEY_ALIAS = "device_tee_key";
private static final String KEY_ALGO = KeyProperties.KEY_ALGORITHM_RSA;
private static final int RSA_KEY_SIZE = 2048;
private static final String SIGN_ALGO = "SHA256withRSA";
private static final String RSA_TRANSFORM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
private static final int AES_KEY_SIZE = 256;
private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 128;
private final Context context;
private KeyStore keyStore;
// ==================== 构造与初始化 ====================
public DeviceCrypto(Context context) {
this.context = context;
initKeyStore();
}
/**
* 初始化 AndroidKeyStore如不存在密钥对则生成
*
* 安全属性:
* - PURPOSE_DECRYPT + PURPOSE_SIGN私钥只用于解密下发数据 + 签名)
* - StrongBox 优先(硬件安全模块)
* - 不可导出
*/
private void initKeyStore() {
try {
keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
keyStore.load(null);
if (!keyStore.containsAlias(KEY_ALIAS)) {
generateTeeKeyPair();
}
} catch (Exception e) {
throw new RuntimeException("KeyStore init failed", e);
}
}
/**
* 在 TEE / StrongBox 中生成 RSA-2048 密钥对
*/
private void generateTeeKeyPair() {
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
KEY_ALGO, KEYSTORE_PROVIDER);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
KEY_ALIAS,
KeyProperties.PURPOSE_DECRYPT |
KeyProperties.PURPOSE_SIGN
)
.setKeySize(RSA_KEY_SIZE)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setUserAuthenticationRequired(false) // 无登录体系
.setIsStrongBoxBacked(true) // 优先 StrongBox
.build();
kpg.initialize(spec);
kpg.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException("TEE key gen failed", e);
}
}
// ==================== 公钥导出 ====================
/**
* 获取设备公钥Base64 编码)
* 公钥可导出,用于:
* - 上传服务端存储
* - 服务端加密下发数据
*/
public String getPublicKeyBase64() {
try {
KeyStore.PrivateKeyEntry entry =
(KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
PublicKey publicKey = entry.getCertificate().getPublicKey();
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
} catch (Exception e) {
throw new RuntimeException("Get public key failed", e);
}
}
/**
* 获取设备 SN
* 实际实现android.os.Build.getSerial()
*/
public String getDeviceSN() {
// return android.os.Build.getSerial();
return "SN-DEMO-001";
}
// ==================== 数据信封加密 ====================
/**
* 加密敏感数据(如照片)
*
* 流程:
* 1. 生成随机 AES-256 DEK
* 2. 生成随机 12 字节 IV
* 3. AES-256-GCM 加密数据
* 4. 返回密文 + IV + 明文DEKDEK 由服务端用 UK 加密存储)
*
* @return EncryptedPayload
*/
public EncryptedPayload encryptData(byte[] plaintext) {
try {
// 1. 随机 DEK
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(AES_KEY_SIZE);
SecretKey dek = kg.generateKey();
// 2. 随机 IV
byte[] iv = new byte[GCM_IV_LENGTH];
java.security.SecureRandom random = new java.security.SecureRandom();
random.nextBytes(iv);
// 3. AES-256-GCM 加密
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.ENCRYPT_MODE, dek, gcmSpec);
byte[] ciphertext = cipher.doFinal(plaintext);
return new EncryptedPayload(
Base64.getEncoder().encodeToString(ciphertext),
Base64.getEncoder().encodeToString(iv),
Base64.getEncoder().encodeToString(dek.getEncoded())
);
} catch (Exception e) {
throw new RuntimeException("Encrypt failed", e);
}
}
// ==================== 元数据签名 ====================
/**
* 对元数据做 RSA 签名(防伪造、防篡改)
*
* 签名内容示例:"SN-xxx|timestamp|photoId"
* 服务端用设备公钥验签
*/
public String signMetadata(String metadata) {
try {
KeyStore.PrivateKeyEntry entry =
(KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
PrivateKey privateKey = entry.getPrivateKey();
Signature sig = Signature.getInstance(SIGN_ALGO);
sig.initSign(privateKey);
sig.update(metadata.getBytes("UTF-8"));
byte[] signature = sig.sign();
return Base64.getEncoder().encodeToString(signature);
} catch (Exception e) {
throw new RuntimeException("Sign failed", e);
}
}
// ==================== 恢复流程 ====================
/**
* 恢复出厂后,服务端下发用新公钥加密的 Recovery Token
* 本方法用 TEE 私钥解密,获取 Token
*
* @param encryptedToken 服务端用设备公钥加密的 Token
* @return 明文 Token格式userId|deviceId|timestamp|nonce
*/
public String decryptRecoveryToken(byte[] encryptedToken) {
try {
KeyStore.PrivateKeyEntry entry =
(KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
PrivateKey privateKey = entry.getPrivateKey();
Cipher cipher = Cipher.getInstance(RSA_TRANSFORM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(encryptedToken);
return new String(decrypted, "UTF-8");
} catch (Exception e) {
throw new RuntimeException("Decrypt recovery token failed", e);
}
}
/**
* 解密服务端下发的加密 DEK
* 恢复流程中,服务端用设备公钥加密每个照片的 DEK
*
* @param encryptedDek 加密的 DEKBase64
* @return 明文 DEK 字节
*/
public byte[] decryptDek(String encryptedDekBase64) {
try {
KeyStore.PrivateKeyEntry entry =
(KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
PrivateKey privateKey = entry.getPrivateKey();
Cipher cipher = Cipher.getInstance(RSA_TRANSFORM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(Base64.getDecoder().decode(encryptedDekBase64));
} catch (Exception e) {
throw new RuntimeException("Decrypt DEK failed", e);
}
}
/**
* 用解出的 DEK 解密照片
*/
public byte[] decryptPhoto(String ciphertextBase64, String ivBase64, byte[] dekBytes) {
try {
SecretKey dek = new javax.crypto.spec.SecretKeySpec(dekBytes, "AES");
byte[] iv = Base64.getDecoder().decode(ivBase64);
byte[] ciphertext = Base64.getDecoder().decode(ciphertextBase64);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.DECRYPT_MODE, dek, spec);
return cipher.doFinal(ciphertext);
} catch (Exception e) {
throw new RuntimeException("Decrypt photo failed", e);
}
}
// ==================== 数据模型 ====================
/**
* 加密后的数据载体
*/
public static class EncryptedPayload {
public final String ciphertextBase64; // AES-GCM 密文
public final String ivBase64; // GCM IV
public final String dekBase64; // 明文 DEKHTTPS 传输给服务端)
public EncryptedPayload(String ciphertextBase64, String ivBase64, String dekBase64) {
this.ciphertextBase64 = ciphertextBase64;
this.ivBase64 = ivBase64;
this.dekBase64 = dekBase64;
}
}
/**
* 设备注册请求体
*/
public static class DeviceRegisterRequest {
public String sn;
public String publicKeyBase64;
public DeviceRegisterRequest(String sn, String publicKeyBase64) {
this.sn = sn;
this.publicKeyBase64 = publicKeyBase64;
}
}
/**
* 上传加密照片请求体
*/
public static class UploadPhotoRequest {
public String sn;
public String photoId;
public String ciphertextBase64;
public String ivBase64;
public String dekBase64;
public String metadataSignature;
public String metadata;
public UploadPhotoRequest(String sn, String photoId, String ciphertextBase64,
String ivBase64, String dekBase64,
String metadataSignature, String metadata) {
this.sn = sn;
this.photoId = photoId;
this.ciphertextBase64 = ciphertextBase64;
this.ivBase64 = ivBase64;
this.dekBase64 = dekBase64;
this.metadataSignature = metadataSignature;
this.metadata = metadata;
}
}
/**
* 恢复请求体(用户已通过短信验证)
*/
public static class RecoveryRequest {
public String sn;
public String userId;
public String smsCode;
public String newPublicKeyBase64;
public RecoveryRequest(String sn, String userId, String smsCode, String newPublicKeyBase64) {
this.sn = sn;
this.userId = userId;
this.smsCode = smsCode;
this.newPublicKeyBase64 = newPublicKeyBase64;
}
}
}