res加密

This commit is contained in:
2026-07-15 15:26:00 +08:00
parent ab0dd2fbf6
commit 46385d5358
6 changed files with 690 additions and 4 deletions

View File

@@ -0,0 +1,23 @@
package com.ckkj.defense_device.annotation
/**
* API 加密注解
*
* 用于标记需要加密的 API 接口
*
* @param response 是否同时加密响应体,默认 false仅加密请求
*
* 使用示例:
* - @ApiEncrypt // 仅加密请求
* - @ApiEncrypt(response = true) // 请求和响应都加密
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class ApiEncrypt(
/**
* 是否加密响应体
* - false (默认): 仅加密请求体
* - true: 请求体和响应体都加密
*/
val response: Boolean = false
)

View File

@@ -1,5 +1,7 @@
package com.ckkj.defense_device.utils
import com.ckkj.defense_device.utils.crypto.CryptoInterceptor
import com.ckkj.defense_device.utils.crypto.CryptoKeyManager
import com.google.gson.Gson
import okhttp3.OkHttpClient
import retrofit2.Retrofit
@@ -12,8 +14,8 @@ import java.util.concurrent.TimeUnit
object RetrofitNetwork {
private const val DEFAULT_TIMEOUT = 30L
//const val BASE_URL = "https://stem-boozy-margarine.ngrok-free.dev"
const val BASE_URL = "http://service.reinkun.com:8001"
const val BASE_URL = "https://stem-boozy-margarine.ngrok-free.dev"
//const val BASE_URL = "http://service.reinkun.com:8001"
private var retrofit: Retrofit? = null
private val gson = Gson()
@@ -65,12 +67,39 @@ object RetrofitNetwork {
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(ApiConfig.createHeadersInterceptor())
.addInterceptor(ApiConfig.getUnifiedInterceptor()) // 使用 ApiConfig 中的统一拦截器
.addInterceptor(ApiConfig.getUnifiedInterceptor())
// 添加加密拦截器(仅在密钥已配置时启用)
if (CryptoKeyManager.isKeyConfigured()) {
builder.addInterceptor(getCryptoInterceptor())
}
if (enableLogging) {
builder.addInterceptor(ApiConfig.getLoggingInterceptor()) // 使用 ApiConfig 中的日志拦截器
builder.addInterceptor(ApiConfig.getLoggingInterceptor())
}
return builder.build()
}
/**
* 获取加密拦截器实例
* 每次都重新创建,确保使用最新密钥
*/
private fun getCryptoInterceptor(): CryptoInterceptor {
// 清除旧实例,确保使用最新密钥
CryptoInterceptor.clearInstance()
return CryptoInterceptor.getInstance(
publicKey = CryptoKeyManager.requestPublicKey,
privateKey = CryptoKeyManager.responsePrivateKey
)
}
/**
* 重新初始化网络客户端(密钥更新后调用)
*/
fun resetClient() {
retrofit = null
// 强制重新创建 Retrofit
getNetworkService()
}
}

View File

@@ -0,0 +1,96 @@
package com.ckkj.defense_device.utils.crypto
import android.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
/**
* AES 加密工具类
*
* 算法AES/ECB/PKCS5Padding (Android) 与 AES/ECB/PKCS7Padding (iOS) 兼容
*/
object AESUtil {
private const val ALGORITHM = "AES"
private const val TRANSFORMATION = "AES/ECB/PKCS5Padding"
/**
* AES ECB 模式加密
*
* @param key 32字节 ASCII 字符串密钥
* @param data UTF-8 编码的明文字节数组
* @return Base64 编码的密文字节数组
*/
fun encrypt(key: String, data: ByteArray): ByteArray {
val keySpec = SecretKeySpec(key.toByteArray(Charsets.UTF_8), ALGORITHM)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, keySpec)
return cipher.doFinal(data)
}
/**
* AES ECB 模式加密,返回 Base64 字符串
*
* @param key 32字节 ASCII 字符串密钥
* @param plainText UTF-8 明文字符串
* @return Base64 编码的密文字符串
*/
fun encrypt(key: String, plainText: String): String {
val encrypted = encrypt(key, plainText.toByteArray(Charsets.UTF_8))
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
}
/**
* AES ECB 模式解密
*
* @param key 32字节 ASCII 字符串密钥
* @param encryptedData Base64 编码的密文字节数组
* @return UTF-8 解密后的明文字节数组
*/
fun decrypt(key: String, encryptedData: ByteArray): ByteArray {
val keySpec = SecretKeySpec(key.toByteArray(Charsets.UTF_8), ALGORITHM)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, keySpec)
return cipher.doFinal(encryptedData)
}
/**
* AES ECB 模式解密,返回 UTF-8 字符串
*
* @param key 32字节 ASCII 字符串密钥
* @param encryptedText Base64 编码的密文字符串
* @return UTF-8 解密后的明文字符串
*/
fun decrypt(key: String, encryptedText: String): String {
val encryptedData = Base64.decode(encryptedText, Base64.NO_WRAP)
val decrypted = decrypt(key, encryptedData)
return String(decrypted, Charsets.UTF_8)
}
/**
* 生成随机 AES 密钥
*
* @param length 密钥长度,默认 32 字节
* @return 随机 ASCII 字符串密钥
*/
fun generateKey(length: Int = 32): String {
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return (1..length)
.map { chars.random() }
.joinToString("")
}
/**
* Base64 编码
*/
fun base64Encode(data: ByteArray): String {
return Base64.encodeToString(data, Base64.NO_WRAP)
}
/**
* Base64 解码
*/
fun base64Decode(encoded: String): ByteArray {
return Base64.decode(encoded, Base64.NO_WRAP)
}
}

View File

@@ -0,0 +1,234 @@
package com.ckkj.defense_device.utils.crypto
import android.util.Base64
import android.util.Log
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import java.nio.charset.Charset
/**
* 加密拦截器
*
* 实现 App 请求加密和服务端响应解密
*
* 加密流程:
* 1. 生成随机 AES 密钥 (32字节 ASCII)
* 2. 用 RSA 公钥加密 AES 密钥
* 3. 用 AES 加密请求体
* 4. 设置请求头 encrypt-key 为 RSA 加密后的 AES 密钥
* 5. 请求体为 AES 加密后的 Base64 字符串
*
* 解密流程(仅需要对响应解密的接口):
* 1. 从响应头获取 encrypt-key
* 2. 用 RSA 私钥解密得到 AES 密钥
* 3. 用 AES 解密响应体
*/
class CryptoInterceptor private constructor(
private val publicKey: String,
private val privateKey: String
) : Interceptor {
companion object {
private const val TAG = "CryptoInterceptor"
private const val HEADER_ENCRYPT_KEY = "encrypt-key"
private const val HEADER_IS_ENCRYPT = "isEncrypt"
private const val CONTENT_TYPE_JSON = "application/json; charset=utf-8"
// 需要加密的接口列表POST/PUT 请求)
private val ENCRYPT_ENDPOINTS = setOf(
"app/login",
"app/register",
"app/forgot",
"app/switchFamily",
"app/addDevice",
"app/updateDeviceInfo",
"app/deleteDevice",
"app/addDeviceUser",
"app/updateUserInfo",
"app/forgotPassword",
"app/retrievePassword",
"app/verifyOptionPassword",
"app/obtainInviterInformation",
"app/selectRecommendationCode",
"app/generateRecommendationCode",
"app/revokeInvitation",
"app/updateFamilyUser",
"app/selectInviterInfo",
"app/joinTheFamily",
"app/stream/start"
)
// 需要解密响应的接口列表
private val DECRYPT_RESPONSE_ENDPOINTS = setOf<String>()
@Volatile
private var instance: CryptoInterceptor? = null
fun getInstance(publicKey: String, privateKey: String): CryptoInterceptor {
return instance ?: synchronized(this) {
instance ?: CryptoInterceptor(publicKey, privateKey).also {
instance = it
}
}
}
fun clearInstance() {
instance = null
}
fun isEncryptEndpoint(url: String): Boolean {
return ENCRYPT_ENDPOINTS.any { url.contains(it, ignoreCase = true) }
}
fun isDecryptEndpoint(url: String): Boolean {
return DECRYPT_RESPONSE_ENDPOINTS.any { url.contains(it, ignoreCase = true) }
}
}
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url.toString()
// 检查是否需要加密
val needsEncrypt = isEncryptEndpoint(url)
val needsDecrypt = isDecryptEndpoint(url)
if (!needsEncrypt && !needsDecrypt) {
return chain.proceed(request)
}
// 调试日志
Log.d(TAG, "=== CryptoInterceptor Debug ===")
Log.d(TAG, "URL: $url")
Log.d(TAG, "needsEncrypt: $needsEncrypt, needsDecrypt: $needsDecrypt")
Log.d(TAG, "PublicKey available: ${publicKey.isNotBlank()}, length: ${publicKey.length}")
Log.d(TAG, "PrivateKey available: ${privateKey.isNotBlank()}, length: ${privateKey.length}")
// 加密请求
val encryptedRequest = if (needsEncrypt) {
encryptRequest(request)
} else {
request
}
// 发起请求
val response = try {
chain.proceed(encryptedRequest)
} catch (e: Exception) {
Log.e(TAG, "Request failed", e)
throw e
}
// 解密响应
return if (needsDecrypt) {
decryptResponse(response, request)
} else {
response
}
}
/**
* 加密请求
*/
private fun encryptRequest(request: Request): Request {
val requestBody = request.body ?: return request
// 只处理 JSON 请求
val contentType = requestBody.contentType()?.toString() ?: ""
if (!contentType.contains("application/json")) {
return request
}
// 只处理 POST 和 PUT
val method = request.method
if (method != "POST" && method != "PUT") {
return request
}
// 读取原始请求体
val buffer = okio.Buffer()
requestBody.writeTo(buffer)
val originalBody = buffer.readString(Charset.defaultCharset())
Log.d(TAG, "Original request body: $originalBody")
// 生成随机 AES 密钥
val aesKey = AESUtil.generateKey(32)
Log.d(TAG, "Generated AES key: $aesKey")
// 1. keyBase64 = Base64(UTF8(aesKey))
val keyBase64 = AESUtil.base64Encode(aesKey.toByteArray(Charsets.UTF_8))
Log.d(TAG, "keyBase64: $keyBase64")
// 2. encrypt-key = RSA-PKCS1-Encrypt(请求公钥, UTF8(keyBase64))
// encryptByPublicKey(String, String) 已返回 Base64 编码结果
val encryptedKeyBase64 = RSAUtil.encryptByPublicKey(publicKey, keyBase64)
Log.d(TAG, "Encrypted key (Base64): $encryptedKeyBase64")
// 3. body = AES-ECB-PKCS5-Encrypt(aesKey, UTF8(JSON))
// encrypt(String, String) 已返回 Base64 编码结果
val encryptedBodyBase64 = AESUtil.encrypt(aesKey, originalBody)
Log.d(TAG, "Encrypted body (Base64): $encryptedBodyBase64")
// 构建新请求(根据原方法选择 POST 或 PUT
val newRequestBuilder = request.newBuilder()
.header(HEADER_ENCRYPT_KEY, encryptedKeyBase64)
.header(HEADER_IS_ENCRYPT, "true")
return if (method == "PUT") {
newRequestBuilder.put(encryptedBodyBase64.toRequestBody(CONTENT_TYPE_JSON.toMediaTypeOrNull()))
} else {
newRequestBuilder.post(encryptedBodyBase64.toRequestBody(CONTENT_TYPE_JSON.toMediaTypeOrNull()))
}.build()
}
/**
* 解密响应
*/
private fun decryptResponse(response: Response, originalRequest: Request): Response {
val responseBody = response.body ?: return response
// 检查是否有 encrypt-key 头
val encryptedKeyBase64 = response.header(HEADER_ENCRYPT_KEY)
if (encryptedKeyBase64.isNullOrBlank()) {
Log.w(TAG, "No encrypt-key header in response, skip decryption")
return response
}
try {
// 1. 解密 encrypt-key 得到 AES 密钥
val aesKeyBase64 = RSAUtil.decryptByPrivateKeyBase64(privateKey, encryptedKeyBase64)
val aesKey = String(Base64.decode(aesKeyBase64, Base64.NO_WRAP), Charsets.UTF_8)
Log.d(TAG, "Decrypted AES key: $aesKey")
// 2. 读取响应体
val source = responseBody.source()
source.request(Long.MAX_VALUE)
val encryptedBody = source.buffer.clone().readString(Charset.defaultCharset())
Log.d(TAG, "Encrypted response body: $encryptedBody")
// 3. AES 解密
val decryptedBody = AESUtil.decrypt(aesKey, encryptedBody)
Log.d(TAG, "Decrypted response body: $decryptedBody")
// 构建新响应
val newBody = decryptedBody.toResponseBody(CONTENT_TYPE_JSON.toMediaTypeOrNull())
return response.newBuilder()
.body(newBody)
.removeHeader(HEADER_ENCRYPT_KEY)
.build()
} catch (e: Exception) {
Log.e(TAG, "Decryption failed", e)
// 解密失败时返回原始响应
return response
}
}
}
// 扩展函数String 转 RequestBody
private fun String.toRequestBody(mediaType: okhttp3.MediaType?): okhttp3.RequestBody {
return okhttp3.RequestBody.create(mediaType, this)
}

View File

@@ -0,0 +1,182 @@
package com.ckkj.defense_device.utils.crypto
import android.content.Context
import android.util.Log
import com.blankj.utilcode.util.SPUtils
import com.ckkj.defense_device.utils.UserinfoUtil
/**
* 密钥管理类
*
* 管理 RSA 加密所需的公钥和私钥
*
* 密钥关系:
* ┌─────────────┬──────────────────┬──────────────────┐
* │ 方向 │ App 持有 │ 服务端持有 │
* ├─────────────┼──────────────────┼──────────────────┤
* │ App请求服务端 │ 请求公钥 (RSA-P) │ 请求私钥 (RSA-S) │
* ├─────────────┼──────────────────┼──────────────────┤
* │ 服务端响应App │ 响应私钥 (RSA-S) │ 响应公钥 (RSA-P) │
* └─────────────┴──────────────────┴──────────────────┘
*
* 需要生成两套独立 RSA 2048/3072 密钥对
*/
object CryptoKeyManager {
private const val TAG = "CryptoKeyManager"
// SPUtils 用于持久化密钥配置
private val spUtils: SPUtils by lazy {
SPUtils.getInstance("crypto_keys")
}
// 配置项 Keys
private const val KEY_REQUEST_PUBLIC_KEY = "request_public_key"
private const val KEY_RESPONSE_PRIVATE_KEY = "response_private_key"
private const val KEY_CRYPTO_ENABLED = "crypto_enabled"
// 配置项默认值
// 注意:这些密钥必须与后端配置配对使用
// App端公钥 = 后端私钥App加密请求后端解密
// App端私钥 = 后端公钥后端加密响应App解密
private const val DEFAULT_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw2GulSqzHcLxl/l8UXPzc3WeS6QtsswRaYpoeMBwOU5/c5QRMhZm613IV754slgK2pA9ndSHzO6hGvNg5bsioCgtFGWRCe/UzFQjfnIMcva+7Vd1/dNgrF/5EUkOow5LEe8svaFpHYi+Pzo+qXt8/+MaJxu5gdv4W+VJsf1MYmiDvMxRTvFAjm2SY345uTWDlUrNFO1ozYkmqot3Z6CqNNYASmc10F3EZdNjXkSdcUNom5+svtMJCAAZWiBop3DxQS51lfzO6ukLFIypv7B4hwONrifV0mgMQu4SlOVAFCzBkgKqQzLySMxaB7XjeQeq5usuZDqMWxb6bYvHodGDBQIDAQAB"
private const val DEFAULT_PRIVATE_KEY = "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC+iTaWIfJdkGEnGZD1CkYkl0r1QdwlIQS5wh+CSL5yL/oEwI3uojYDR6w/bmcsskqgCQvLN9V71L68t+tWHjQA6Ra+KXwfuKbswxvgd0HXCzreB7P/ccvnYuA8gxkR4N0KaPKUsvQORxUgXuqWddUfTxbvahvWj3Cs3DwXqIaFRKnFRHVUUmIyQVDURb/5DZLrAA5MOm6ZS1bPdtBiEMcdS90Mcu2pGNgvVzzkbKT1KK4ZIDG5EUIQ02jF4c1i50/qFW+S7e9oRtiyktDj8EKfSz3cyMjphMwZfJAtwV6WhxMt/TKT5uIIc985MhDLfJlEmzFJNVy4ftiStX7M6rn5AgMBAAECggEBALbstvpmNvC693r5YaDPr03b7nwOMdN3orjQdT24pxarluZPEzSiiSaW/fib/vP7bAe/B5Kf1RagxkoabRbtwlGJdms1GtS9i4zNOItFE3HodyjIzneVAgTweYW++yed8bjXzuUR9ZtrhQ3h8mhnUihP3x4VigpxBeT/P7Ya9qMkAavxdr7o3SdSubvePf/bxJHT1Ehwu0SShz2p/FXRK7GTywTHp+Q6M5lzTAiat52U/zANcoZ7Acj1fdWy1hBExQbbYwsCFonIRYoOKKmJ0lG6akm2Zzcls89VOfLBn7jtz4ts64AFVFWpw2Z6ho8YiU2NceZL6gWuz2JB9Ih7ay0CgYEA8ysHBcPe45ArnVyd0v7OzW8KTa3Yyhsl2OwAG3nHU/lVhhvSZnkxmNWzV0i7yfqHCTo9iHASeD3u7dsYwsdt4j3mVjP7hHnSalPu67TvYoinbb0zdgl1s1PccZGZt/w82pr0sor63te8fLd53s6HMn56cvTj4sJEUN5hoS8rOy8CgYEAyJcs+Zoxea06wCfERNp4K9WoTLx8YBBQ8Db9Vi8pecT64cda8jQCHjY5+lnV41S14PQKSMJQdXJeekLkCt4G8QQ93nVO4JEhm11HRqau1Y1h8cFCZCgsYSmdO9PadxTykx9xZWz24fT9vKDBi4exi7WeOJdrn/L2TjNrwhG081cCgYEA1IYghxtfbEgOtmPB1Cy7FbUcrjNoq4r6kxIALgNLAMuDwcgDJWh6vnbXXUvGRIrfBmOOb/ZzTf/jGKNKisG9+ePbsi+QfYHzMtbUk9D4/4crva+EkH9IDUkX+3IjhTfA/9Y6UulAxQ6MP/wjWWXrp2f/IQWDPiZHit4xOax9/iUCgYEAjTsbmHY/RXt5dRfKsevcVoitYbIIJB74zdj5z/eOzUSqm9MgLo39u8XUVndwCAp5sHRC1W2y9q5a5Nw58YjX/FRPN1UC/0xQDDLF33G2gXd1MaEvnJNIg/Sn1xrXo6ZlEL8/uPjabsdu66OIAjS9BjAOwaujRE/tqau8wDx1WisCgYEA6MTGurUrOtTD/vj+MItCWDhMK1T6ViA+gSsX+afqyjriNKlyrviuavkEM2+8fgHcqCy5isvhBtYhxESownVK19nr3WbTYtvgj/C5JAkJeO/V43sUqfjYYkbQd4vL5iNfMrC3dXSfr2V0dAjwZF4T2Ns+FHyKT5MSZSux+0mTU98="
/**
* 是否启用加密
*/
var isCryptoEnabled: Boolean
get() {
val stored = spUtils.getBoolean(KEY_CRYPTO_ENABLED, false)
// 如果存储值为 false 但密钥已配置,也返回 true
return stored || (DEFAULT_PUBLIC_KEY.isNotBlank() && DEFAULT_PRIVATE_KEY.isNotBlank())
}
set(value) {
spUtils.put(KEY_CRYPTO_ENABLED, value)
Log.d(TAG, "Crypto enabled: $value")
}
/**
* 请求公钥(用于 App 加密请求)
* App 使用此公钥加密 AES 密钥,服务端使用对应的私钥解密
*/
var requestPublicKey: String
get() {
val stored = spUtils.getString(KEY_REQUEST_PUBLIC_KEY, "")
return if (stored.isNotBlank()) stored else DEFAULT_PUBLIC_KEY
}
set(value) {
spUtils.put(KEY_REQUEST_PUBLIC_KEY, value)
Log.d(TAG, "Request public key updated: ${value.take(20)}...")
}
/**
* 响应私钥(用于 App 解密响应)
* 服务端使用对应的响应公钥加密响应App 使用此私钥解密
*/
var responsePrivateKey: String
get() {
val stored = spUtils.getString(KEY_RESPONSE_PRIVATE_KEY, "")
return if (stored.isNotBlank()) stored else DEFAULT_PRIVATE_KEY
}
set(value) {
spUtils.put(KEY_RESPONSE_PRIVATE_KEY, value)
Log.d(TAG, "Response private key updated: ${value.take(20)}...")
}
/**
* 是否已配置密钥
*/
fun isKeyConfigured(): Boolean {
// 检查密钥是否有内容(默认值或用户配置)
val hasPublicKey = requestPublicKey.isNotBlank()
val hasPrivateKey = responsePrivateKey.isNotBlank()
Log.d(TAG, "isKeyConfigured: publicKey=$hasPublicKey (length=${requestPublicKey.length}), privateKey=$hasPrivateKey (length=${responsePrivateKey.length})")
return hasPublicKey && hasPrivateKey
}
/**
* 从服务端配置初始化密钥
*
* 配置格式(可从服务端接口或配置中心获取):
* api-decrypt:
* enabled: true
* headerFlag: encrypt-key
* publicKey: ${APP_RESPONSE_PUBLIC_KEY}
* privateKey: ${APP_REQUEST_PRIVATE_KEY}
*
* @param enabled 是否启用加密
* @param publicKey 服务端响应公钥(用于 App 加密请求)
* @param privateKey 服务端请求私钥(用于 App 解密响应)
*/
fun initialize(enabled: Boolean, publicKey: String, privateKey: String) {
isCryptoEnabled = enabled
requestPublicKey = publicKey
responsePrivateKey = privateKey
Log.i(TAG, "CryptoKeyManager initialized: enabled=$enabled, keyConfigured=${isKeyConfigured()}")
}
/**
* 从用户登录信息初始化密钥
* 适用于密钥存储在用户账户中的场景
*
* @param publicKey 公钥
* @param privateKey 私钥
*/
fun initializeFromLogin(publicKey: String, privateKey: String) {
initialize(
enabled = publicKey.isNotBlank() && privateKey.isNotBlank(),
publicKey = publicKey,
privateKey = privateKey
)
}
/**
* 清除密钥配置
*/
fun clear() {
spUtils.clear()
Log.i(TAG, "CryptoKeyManager cleared")
}
/**
* 获取密钥信息摘要(用于调试)
*/
fun getKeySummary(): String {
return buildString {
appendLine("CryptoKeyManager Summary:")
appendLine(" Enabled: $isCryptoEnabled")
appendLine(" RequestPublicKey: ${if (requestPublicKey.isNotBlank()) "${requestPublicKey.take(20)}..." else "NOT SET"}")
appendLine(" ResponsePrivateKey: ${if (responsePrivateKey.isNotBlank()) "${responsePrivateKey.take(20)}..." else "NOT SET"}")
appendLine(" Configured: ${isKeyConfigured()}")
}
}
/**
* 验证密钥格式
*
* @return true 如果密钥是有效的 Base64 编码
*/
fun validateKeys(): Pair<Boolean, String> {
val publicKeyValid = validateBase64Key(requestPublicKey)
val privateKeyValid = validateBase64Key(responsePrivateKey)
return when {
!publicKeyValid && !privateKeyValid -> false to "Both keys are invalid"
!publicKeyValid -> false to "Request public key is invalid"
!privateKeyValid -> false to "Response private key is invalid"
else -> true to "Keys are valid"
}
}
private fun validateBase64Key(key: String): Boolean {
if (key.isBlank()) return false
return try {
android.util.Base64.decode(key, android.util.Base64.NO_WRAP)
true
} catch (e: Exception) {
false
}
}
}

View File

@@ -0,0 +1,122 @@
package com.ckkj.defense_device.utils.crypto
import android.util.Base64
import java.security.KeyFactory
import java.security.PublicKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.RSAPrivateKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
/**
* RSA 加密工具类
*
* 算法RSA/ECB/PKCS1Padding
* 支持 RSA 2048/3072 位密钥
*/
object RSAUtil {
private const val ALGORITHM = "RSA"
private const val TRANSFORMATION = "RSA/ECB/PKCS1Padding"
/**
* 使用公钥加密(用于 App 加密请求)
*
* @param publicKeyStr Base64 编码的公钥字符串
* @param data UTF-8 编码的明文字节数组
* @return 密文字节数组
*/
fun encryptByPublicKey(publicKeyStr: String, data: ByteArray): ByteArray {
val publicKey = parsePublicKey(publicKeyStr)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(data)
}
/**
* 使用公钥加密,返回 Base64 字符串
*
* @param publicKeyStr Base64 编码的公钥字符串
* @param plainText UTF-8 明文字符串
* @return Base64 编码的密文字符串
*/
fun encryptByPublicKey(publicKeyStr: String, plainText: String): String {
val encrypted = encryptByPublicKey(publicKeyStr, plainText.toByteArray(Charsets.UTF_8))
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
}
/**
* 使用私钥解密(用于 App 解密响应)
*
* @param privateKeySpec RSA 私钥规格
* @param encryptedData 密文字节数组
* @return UTF-8 解密后的明文字节数组
*/
fun decryptByPrivateKey(privateKeySpec: RSAPrivateKeySpec, encryptedData: ByteArray): ByteArray {
val keyFactory = KeyFactory.getInstance(ALGORITHM)
val privateKey = keyFactory.generatePrivate(privateKeySpec)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, privateKey)
return cipher.doFinal(encryptedData)
}
/**
* 使用私钥解密,返回 UTF-8 字符串
*
* @param privateKeySpec RSA 私钥规格
* @param encryptedText Base64 编码的密文字符串
* @return UTF-8 解密后的明文字符串
*/
fun decryptByPrivateKey(privateKeySpec: RSAPrivateKeySpec, encryptedText: String): String {
val encryptedData = Base64.decode(encryptedText, Base64.NO_WRAP)
val decrypted = decryptByPrivateKey(privateKeySpec, encryptedData)
return String(decrypted, Charsets.UTF_8)
}
/**
* 使用 Base64 编码的 PKCS#8 私钥字符串解密
*
* @param privateKeyBase64 Base64 编码的 PKCS#8 私钥
* @param encryptedText Base64 编码的密文字符串
* @return UTF-8 解密后的明文字符串
*/
fun decryptByPrivateKeyBase64(privateKeyBase64: String, encryptedText: String): String {
val encryptedData = Base64.decode(encryptedText, Base64.NO_WRAP)
val keyFactory = KeyFactory.getInstance(ALGORITHM)
val privateKeySpec = PKCS8EncodedKeySpec(Base64.decode(privateKeyBase64, Base64.NO_WRAP))
val privateKey = keyFactory.generatePrivate(privateKeySpec)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, privateKey)
val decrypted = cipher.doFinal(encryptedData)
return String(decrypted, Charsets.UTF_8)
}
/**
* 解析公钥
*
* @param publicKeyStr Base64 编码的 X.509 公钥
* @return PublicKey 对象
*/
fun parsePublicKey(publicKeyStr: String): PublicKey {
val keyBytes = Base64.decode(publicKeyStr, Base64.NO_WRAP)
val keySpec = X509EncodedKeySpec(keyBytes)
val keyFactory = KeyFactory.getInstance(ALGORITHM)
return keyFactory.generatePublic(keySpec)
}
/**
* 从 Base64 编码的 PKCS#8 私钥创建私钥规格
*
* @param privateKeyBase64 Base64 编码的 PKCS#8 私钥
* @return RSAPrivateKeySpec
*/
fun createPrivateKeySpecFromBase64(privateKeyBase64: String): RSAPrivateKeySpec {
val keyBytes = Base64.decode(privateKeyBase64, Base64.NO_WRAP)
val keyFactory = KeyFactory.getInstance(ALGORITHM)
val keySpec = keyFactory.getKeySpec(
keyFactory.generatePrivate(PKCS8EncodedKeySpec(keyBytes)),
RSAPrivateKeySpec::class.java
)
return keySpec
}
}