优化res加密

This commit is contained in:
2026-07-18 17:24:37 +08:00
parent 655279a8a1
commit 195fb73620
4 changed files with 109 additions and 30 deletions

View File

@@ -39,6 +39,25 @@ import retrofit2.http.POST
import retrofit2.http.PUT import retrofit2.http.PUT
import retrofit2.http.Query import retrofit2.http.Query
// ┏┓   ┏┓
//┏┛┻━━━┛┻┓
//┃       ┃
//┃   ━   ┃
//┃ ┳┛ ┗┳ ┃
//┃       ┃
//┃   ┻   ┃
//┃       ┃
//┗━┓   ┏━┛
// ┃   ┃
// ┃   ┃
// ┃   ┗━━━┓
// ┃       ┣┓
// ┃       ┏┛
// ┗┓┓┏━┳┓┏┛
// ┃┫┫ ┃┫┫
// ┗┻┛ ┗┻┛
//神兽护体代码零Bug需求不改一次编译通过
interface ApiService { interface ApiService {
/** /**
* 获取登录验证码 * 获取登录验证码

View File

@@ -69,8 +69,8 @@ object RetrofitNetwork {
.addInterceptor(ApiConfig.createHeadersInterceptor()) .addInterceptor(ApiConfig.createHeadersInterceptor())
.addInterceptor(ApiConfig.getUnifiedInterceptor()) .addInterceptor(ApiConfig.getUnifiedInterceptor())
// 添加加密拦截器(仅在密钥已配置时启用 // 加密拦截器(密钥就绪即启用,按开关自动判断
if (CryptoKeyManager.isKeyConfigured()) { if (CryptoKeyManager.isCryptoEnabled && CryptoKeyManager.isKeyConfigured()) {
builder.addInterceptor(getCryptoInterceptor()) builder.addInterceptor(getCryptoInterceptor())
} }

View File

@@ -2,6 +2,7 @@ package com.ckkj.defense_device.utils.crypto
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.google.gson.JsonParser
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request import okhttp3.Request
@@ -61,8 +62,20 @@ class CryptoInterceptor private constructor(
"app/stream/start" "app/stream/start"
) )
// 需要解密响应的接口列表 // 需要解密响应的接口列表(默认与加密列表对称 —— 加密请求的接口响应也会被加密)
private val DECRYPT_RESPONSE_ENDPOINTS = setOf<String>() // 额外加上 GET 接口GET 无 body 不加密请求,但后端仍可能加密响应)
private val DECRYPT_RESPONSE_ENDPOINTS: Set<String> = ENCRYPT_ENDPOINTS + setOf(
"app/selectFamily", // 获取家庭列表GET
"app/getdeviceList", // 获取设备列表GET
"app/deviceInfo", // 获取设备信息GET
"app/appMessage/list", // 消息列表GET
"app/appMessageInfo", // 告警消息详情GET
"app/appMessage/deviceMeslist", // 设备告警日志GET
"app/familyMembers/list", // 家庭成员列表GET
"app/deviceUser/list", // 设备关联用户列表GET
"app/userInfo", // 当前用户信息GET
"app/deleteMessage" // 删除消息DELETE
)
@Volatile @Volatile
private var instance: CryptoInterceptor? = null private var instance: CryptoInterceptor? = null
@@ -186,44 +199,92 @@ class CryptoInterceptor private constructor(
/** /**
* 解密响应 * 解密响应
*
* 兼容三种后端响应形态:
* 1. 普通 JSON无 encrypt-key 头)→ 原样放行
* 2. 整 body 加密(响应头有 encrypt-key 且 body 不是合法 JSON→ 整体 AES 解密
* 3. 嵌套加密(响应头有 encrypt-keybody 是 {code,msg,data:"加密串"}
* → 只解 data 字段,保持外层 JSON 结构(与 iOS 端一致)
*/ */
private fun decryptResponse(response: Response, originalRequest: Request): Response { private fun decryptResponse(response: Response, originalRequest: Request): Response {
val responseBody = response.body ?: return response val responseBody = response.body ?: return response
// 检查是否有 encrypt-key 头
val encryptedKeyBase64 = response.header(HEADER_ENCRYPT_KEY) val encryptedKeyBase64 = response.header(HEADER_ENCRYPT_KEY)
if (encryptedKeyBase64.isNullOrBlank()) { if (encryptedKeyBase64.isNullOrBlank()) {
Log.w(TAG, "No encrypt-key header in response, skip decryption")
return response return response
} }
try { val rawBody: String = try {
// 1. 解密 encrypt-key 得到 AES 密钥 responseBody.source().apply { request(Long.MAX_VALUE) }
.buffer.clone().readString(Charset.defaultCharset())
} catch (e: Exception) {
Log.e(TAG, "Read response body failed", e)
return response
}
// 先拿到 AES 密钥RSA 私钥解密 encrypt-key 头)
val aesKey = try {
val aesKeyBase64 = RSAUtil.decryptByPrivateKeyBase64(privateKey, encryptedKeyBase64) val aesKeyBase64 = RSAUtil.decryptByPrivateKeyBase64(privateKey, encryptedKeyBase64)
val aesKey = String(Base64.decode(aesKeyBase64, Base64.NO_WRAP), Charsets.UTF_8) String(Base64.decode(aesKeyBase64, Base64.NO_WRAP), Charsets.UTF_8)
Log.d(TAG, "Decrypted AES key: $aesKey") } catch (e: Exception) {
Log.e(TAG, "RSA decrypt encrypt-key failed", e)
return response
}
// 2. 读取响应体 // 形态 3嵌套加密 —— 响应是 JSONdata 字段是加密串
val source = responseBody.source() val trimmed = rawBody.trim()
source.request(Long.MAX_VALUE) if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
val encryptedBody = source.buffer.clone().readString(Charset.defaultCharset()) try {
Log.d(TAG, "Encrypted response body: $encryptedBody") val jsonElement = JsonParser().parse(trimmed)
if (jsonElement.isJsonObject) {
val obj = jsonElement.asJsonObject
if (obj.has("data") && obj.get("data").isJsonPrimitive
&& obj.get("data").asJsonPrimitive.isString
) {
val encryptedData = obj.get("data").asString
val decryptedData = AESUtil.decrypt(aesKey, encryptedData)
// 把 data 字段替换为解密后的 JSON 字符串(让 Gson 当成对象继续解析)
obj.add("data", parseDataAsJsonElement(decryptedData))
val newBody = obj.toString().toResponseBody(CONTENT_TYPE_JSON.toMediaTypeOrNull())
return response.newBuilder()
.body(newBody)
.removeHeader(HEADER_ENCRYPT_KEY)
.build()
}
}
} catch (e: Exception) {
Log.w(TAG, "Nested decrypt failed, fallback to whole-body decrypt", e)
}
}
// 3. AES 解 // 形态 2整 body 加
val decryptedBody = AESUtil.decrypt(aesKey, encryptedBody) return try {
Log.d(TAG, "Decrypted response body: $decryptedBody") val decryptedBody = AESUtil.decrypt(aesKey, rawBody)
response.newBuilder()
// 构建新响应 .body(decryptedBody.toResponseBody(CONTENT_TYPE_JSON.toMediaTypeOrNull()))
val newBody = decryptedBody.toResponseBody(CONTENT_TYPE_JSON.toMediaTypeOrNull())
return response.newBuilder()
.body(newBody)
.removeHeader(HEADER_ENCRYPT_KEY) .removeHeader(HEADER_ENCRYPT_KEY)
.build() .build()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Decryption failed", e) Log.e(TAG, "Whole-body decrypt failed, return raw response", e)
// 解密失败时返回原始响应 response
return response }
}
/**
* 把解密后的字符串解析成 JsonElement如果是合法 JSON否则当成普通字符串返回
*/
private fun parseDataAsJsonElement(text: String): com.google.gson.JsonElement {
val s = text.trim()
return try {
if ((s.startsWith("{") && s.endsWith("}")) ||
(s.startsWith("[") && s.endsWith("]"))
) {
JsonParser().parse(s)
} else {
com.google.gson.JsonPrimitive(s)
}
} catch (e: Exception) {
com.google.gson.JsonPrimitive(text)
} }
} }
} }

View File

@@ -39,9 +39,8 @@ object CryptoKeyManager {
// 注意:这些密钥必须与后端配置配对使用 // 注意:这些密钥必须与后端配置配对使用
// App端公钥 = 后端私钥App加密请求后端解密 // App端公钥 = 后端私钥App加密请求后端解密
// 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_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtVIQm+1AWMfkT1uVOyugKQwBeHtgpulABOrVfaL7Xzc1/rKSdLdjYZLZnnOU+rdE1jqz20MXDBozK22spH/j53d9nFLftHkMCRUlfc0MWEqtSla+2MdzElIHEbq4CSizZOcT8QXCRw7V2OuDqnC7XRQmnpA5Ks1WaKH5V0aoKWztjsqZV6vVWPqyDC94mvu/Q/9e6KVfkqAzR+afuo9OoBFewDGQ6jfqyu1TxoGZrfSr5lXOVeADf0CVfWCDze5y4f7er4JjPHWA2+yv8Scd9bqVJ4Q+/yR2Gd516Mi11OgBKc8GdkuA4/kfmXc7n91SMjgQP6DlTVuPc/6KnB/pyQIDAQAB"
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=" private const val DEFAULT_PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYg1dC9XDMRtKJ5IFj+Hd3bq7+r2YL1y8FM9HD111PN/YVy4K6O67cRA/TLG7+tl6eYiA73f5lzPgv1riOwBPSCmvVopxq2KiJs30uEnug0qGp6l2cwQOmjitFAQF8dOtAl4m6EM/jdXCZYbQrthJ6n3tR+vGj2IrGSJEjPrj3VRljK9I7Zw0Be0rhJn/vZw2CA4GoVfE/3gqa5bpl+3SFxMP7TwF5uZueCNqLaPnd7jK5jB1SUghI7+pq897Ng4EG6kdOGsiQe5kbHWkSPTmPEObMwgLJ18i/XCVg+LUkTDJOC/MAfB+WGcKryNHrhcbhB6+XstpoGT8JlOPOgh0BAgMBAAECggEBALZG7MH8bDguL7XTYHNPjRRJZJ4aAGlbgcR5edHMkEvPKyfyK16qPG5IBqKcN93Mnx/sMIL2Q+RkiVWNCdd0MbuU9m/m9JSnTkyPhYWyHc6pRV0NaD496Nrhud/gFuY2cI/yhArXeI1gI6mdrddW83u0pFfCUojEFyETmsz3UzU1LMkVbwCXNbZ1b3GfcVnONMn8T/7CMlRPlKyOuE0OPnTut4wddAybe5BKDQYnGzuosUdO+Q+2K3hqaTD6Bb33D4ATHjvR3Vt9itE07BiaBFjZkE2odP4x3fSsnc7Cw3A7zHQivpfgqcoBcGUaMcN1FdgpaF2mbe9/eHn/lYCpevkCgYEA4FoKq3fPOITAtA6bjwCHXvIQUjv4vQ0BZtt7OX7zwpRwqOhJfN0ZRkd7NMUqYnB10N9NgPRz0kKC6qff5duN+zkYIzjHrlm5lzdIEAouovRlrGXST4n8gAYteh5D5Or9wRXj7USQoaNyiWKB9A/07ortJvul09sPjJU7IVfzzXMCgYEA9w45Gh2bAvYvm2bWieKZRhcV6Pwy9R8587qmKLC7V/0gfVojBHx7xTmTEfKCGrt3fK4jB5v/2Dar8gDF+RM7g01u0YuFJt17jeFr/4o5KMimfXhoEulDbgjfvhxQOrtkrF5iHpKxiEXqIPiH+B7155PAnE43011AAJCBSylmTrsCgYEAkxr3PA9HFKwXHvklDtMt8BeQlBs2sd9BOAxZ9A1GECP86wPEi9b9p5NfOe6+J+XNmrOQwimHeCqcZPjGWpVnt35sUUv9wlia1Igu/DVw9vCBalUpXXYA1oE2eIg3xHZBBMYxuXXnz9S4WVT6GOoNlAwMDC+dQBi3TVrcdrSQ6/kCgYBA254ICh+ovmKvJGdMGY3thZ/94z+pdEIthyGZ6xOzvMMrxV8ODXQcycmfW4/mXrK2q6yMkdqvs2KejK9sfS0RgmGGZ19UXa7TB4vnsSziVRLIO7TuyggmufOrIBm74XhDfB+8MPykbt5RO43OiKo72mElZ69mMMPdohIfXkX6gwKBgH4L9qJRhNunWSfHO4/8dST+2meSV+Cpqf0b/9cV6v5FMS22m+szzW9XoBXnI9dbAqiEDlCHCLw34pRlC3OzBNlbT6HzNmYXLko2Ttg9thiDq7WS5tV+EHJRP6kLabYGq0Aghtq+6LIwwogbHzhRi8Q52o3CNmM9lThNWZIkzaBe"
/** /**
* 是否启用加密 * 是否启用加密
*/ */