睿诚科技协会

Android网络请求如何加密实现?

  1. 传输安全:确保数据在客户端和服务器之间传输时不会被窃听或篡改,这是最基本也是最重要的安全措施。
  2. 数据加密:在客户端将敏感数据(如密码、Token)进行加密后再发送,或者在服务器端对存储的敏感数据进行加密。

下面我将详细讲解这两个层面,并提供具体的实现方案和最佳实践。

Android网络请求如何加密实现?-图1
(图片来源网络,侵删)

传输安全 - HTTPS

这是必须要做的,是所有网络请求安全的基础,HTTPS = HTTP + SSL/TLS。

为什么必须使用 HTTPS?

  • 机密性:通过 SSL/TLS 加密,中间人(如黑客、运营商)无法窃听你传输的内容。
  • 完整性:通过消息认证码(MAC)确保数据在传输过程中没有被篡改。
  • 身份认证:通过服务器证书验证服务器的真实身份,防止你连接到了一个假冒的“钓鱼”服务器。

如何在 Android 中实现 HTTPS?

现代 Android 开发中,使用 HTTPS 非常简单,主要有两种方式:

使用现代网络库(推荐)

OkHttpRetrofit 这样的现代网络库,默认就支持 HTTPS,并且对 Android 系统的信任库(TrustStore)有很好的兼容性,你只需要确保你的 URL 是 https:// 开头的即可。

Android网络请求如何加密实现?-图2
(图片来源网络,侵删)
// 使用 OkHttp 的示例
val client = OkHttpClient.Builder()
    .build()
val request = Request.Builder()
    .url("https://api.example.com/data")
    .build()
client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        // 处理失败
    }
    override fun onResponse(call: Call, response: Response) {
        // 处理成功
        response.body?.string()
    }
})

处理自签名证书或不受信任的证书(特殊情况)

在某些开发或测试环境中,服务器可能使用自签名证书,或者证书不受 Android 系统默认信任,这时,直接请求会抛出 SSLHandshakeException

⚠️ 安全警告: 不要在生产环境中绕过证书验证! 这会使你的应用完全暴露在中间人攻击之下,此方法仅适用于受控的测试环境。

如果必须处理,可以这样配置 OkHttp:

Android网络请求如何加密实现?-图3
(图片来源网络,侵删)
// 创建一个信任所有证书的 TrustManager
val trustAllCerts = arrayOf(object : X509TrustManager {
    override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {}
    override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {}
    override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
})
// 安装这个信任管理器
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
val sslSocketFactory = sslContext.socketFactory
// 配置 OkHttp
val client = OkHttpClient.Builder()
    .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
    // 禁用主机名验证(同样危险!)
    .hostnameVerifier { _, _ -> true }
    .build()

正确的做法是让服务器管理员将证书安装到所有 Android 设备的信任存储中,或者让服务器使用由公共 CA(如 Let's Encrypt, DigiCert)签名的证书。


数据加密

当数据本身非常敏感时(如用户密码、支付信息),即使使用了 HTTPS,有时我们仍然希望在应用层对其进行额外的加密,这通常被称为“端到端加密”。

常见加密场景和方案

场景 1:用户密码

  • 最佳实践永远不要在前端对密码进行可逆加密!
  • 正确做法:用户注册时,在客户端直接明文将密码发送到服务器,服务器接收到密码后,使用单向哈希算法(如 SHA-256)+ 盐 进行处理,然后将哈希值存储在数据库中。
  • 为什么? 因为单向哈希是不可逆的,即使数据库泄露,攻击者也无法直接得到原始密码,加盐是为了防止彩虹表攻击。
  • 登录流程:客户端将明文密码发送 -> 服务器用同样的哈希算法处理 -> 与数据库中存储的哈希值比对。

场景 2:敏感请求参数(如手机号、身份证号)

API 的某个参数包含敏感信息,而你又希望即使服务器的日志被泄露,这些信息也是不可读的,那么可以在客户端进行加密。

步骤:

  1. 客户端加密:在发送请求前,使用约定的加密算法对敏感数据进行加密。
  2. 服务器解密:服务器接收到请求后,使用对应的解密算法进行解密,然后处理业务逻辑。

常用加密算法:

  • AES (Advanced Encryption Standard):对称加密算法,加密和解密使用同一个密钥,速度快,适合大量数据加密。
  • RSA:非对称加密算法,使用公钥加密,私钥解密,密钥分发方便,但速度较慢,通常用于加密 AES 的密钥(密钥交换)。

示例:使用 AES 加密敏感数据

假设我们要加密一个手机号。

添加依赖

app/build.gradle 中添加:

implementation 'org.bouncycastle:bcprov-jdk15on:1.70' // 一个强大的加密库

加密工具类

import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import java.util.Base64
object AESUtils {
    // AES 加密
    fun encrypt(data: String, secretKey: String): String {
        // 密钥必须是 16, 24, 或 32 字节长,对应 AES-128, AES-192, or AES-256
        val key = SecretKeySpec(secretKey.toByteArray(Charsets.UTF_8), "AES")
        val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding") // 选择加密模式和填充
        cipher.init(Cipher.ENCRYPT_MODE, key)
        val encryptedBytes = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
        return Base64.getEncoder().encodeToString(encryptedBytes)
    }
    // AES 解密
    fun decrypt(encryptedData: String, secretKey: String): String {
        val key = SecretKeySpec(secretKey.toByteArray(Charsets.UTF_8), "AES")
        val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
        cipher.init(Cipher.DECRYPT_MODE, key)
        val decodedBytes = Base64.getDecoder().decode(encryptedData)
        val decryptedBytes = cipher.doFinal(decodedBytes)
        return String(decryptedBytes, Charsets.UTF_8)
    }
}

在网络请求中使用

val phoneNumber = "13800138000"
val secretKey = "ThisIsASecretKey123" // 这个密钥必须与服务器约定好!
// 加密
val encryptedPhone = AESUtils.encrypt(phoneNumber, secretKey)
println("加密后的手机号: $encryptedPhone")
// 使用 Retrofit 发送请求
// @Field("phone") encryptedPhone

服务器端需要有对应的 AES 解密代码,使用相同的 secretKey 来解密 encryptedPhone


  1. 强制 HTTPS:所有生产环境的网络请求必须使用 HTTPS,这是底线。
  2. 使用现代网络库:优先选择 OkHttpRetrofit,它们已经处理了大部分 SSL/TLS 的复杂性。
  3. 不要在前端加密密码:让服务器使用 SHA-256 + 盐来处理密码哈希。
  4. 敏感数据端到端加密(可选):对于非密码但极其敏感的数据(如金融信息),可以在客户端和服务器端约定使用 AES 进行加密/解密。
    • 密钥管理是关键:客户端的加密密钥(secretKey)如何安全地存储和管理是一个难题,硬编码在代码中是不安全的,可以考虑使用 Android 的 Keystore 系统,但实现复杂,这个密钥可以通过服务器下发,或者与用户登录态绑定。
  5. 混淆代码:使用 ProGuard 或 R8 混淆你的代码,增加逆向工程的难度,保护你的加密逻辑和密钥不被轻易发现。
  6. 安全审计:定期对你的应用进行安全审计,检查是否存在已知漏洞。

完整流程示例(Retrofit + HTTPS + AES 加密)

目标:向服务器提交一个包含加密手机号的请求。

定义 API 接口

interface ApiService {
    @FormUrlEncoded
    @POST("user/register")
    suspend fun register(
        @Field("username") username: String,
        @Field("phone") phone: String // 这里将传入加密后的手机号
    ): Response<RegisterResponse>
}

创建 Retrofit 实例

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
分享:
扫描分享到社交APP
上一篇
下一篇