29、安全与隐私:位置权限、MAC 地址随机化、数据加密

各位好,今天我们来聊聊 Wi-Fi 开发中绕不开的三个字——安全与隐私。说实话,早期我做 Wi-Fi 开发时,对这块的重视程度远不如现在。那时候觉得,能连上网、不掉线就是王道。直到有一次,我负责的一个企业级项目在安全审计时被卡住了,原因就是位置权限处理不当。嗯,从那以后,我再也不敢小看这些「细枝末节」了。

这一章,我会把 Wi-Fi 安全与隐私的三个核心点掰开揉碎讲清楚:位置权限MAC 地址随机化数据加密。这三者看似独立,其实环环相扣。你想想看,没有位置权限,你连扫描都做不了;MAC 地址不随机化,用户隐私就裸奔;数据不加密,传输就是敞开的门。咱们一个一个来。

29.1 位置权限:为什么扫描 Wi-Fi 需要它?

很多刚入行的朋友会问:「我只是想扫一下周围的 Wi-Fi 列表,凭什么要位置权限?」这个问题,我当年也问过。答案其实很简单:Wi-Fi 扫描可以定位

每个 AP(接入点)都有唯一的 BSSID(MAC 地址),而 Google、Apple 这些厂商早就建好了全球的 Wi-Fi 指纹数据库。你的设备扫到几个 AP,就能通过三角定位大致知道你在哪。所以,系统把 Wi-Fi 扫描和位置信息绑定了——不给位置权限,就不让你扫。

在 Android 上,从 Android 6.0(API 23)开始,扫描 Wi-Fi 就需要 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限。到了 Android 10(API 29),要求更严了,连 WiFiManager.startScan() 都必须在有位置权限的前提下调用。

核心要点:位置权限不是「为了要你的位置」,而是「因为 Wi-Fi 扫描本身就能暴露位置」。这是系统层面的安全设计,不是厂商在搞事情。

我在项目中遇到过这样一个坑:某个功能在 Android 9 上跑得好好的,升级到 Android 10 后,扫描结果永远是空的。查了半天,发现是位置权限的运行时请求逻辑没适配。嗯,这里要注意——Android 10 之后,光在 Manifest 里声明权限是不够的,必须在运行时动态请求

29.2 MAC 地址随机化:别让设备「裸奔」

咱们先说说 MAC 地址是什么。每个 Wi-Fi 芯片出厂时都有一个全球唯一的 MAC 地址,就像身份证号。你每次连 Wi-Fi,AP 都会记录这个地址。如果一直用真实 MAC,商家就能追踪你的行动轨迹——今天在咖啡店,明天在商场,后天在医院……细思极恐。

所以,Android 从 8.0 开始引入了 MAC 地址随机化。说白了,就是每次扫描或连接时,用一个临时生成的随机 MAC 地址代替真实地址。这样,AP 只知道「有个设备来过」,但不知道「是哪个设备」。

但这里有个细节:随机化不是万能的。我记得有一次,用户反馈说「为什么我换了手机,家里的路由器还认识我?」——那是因为路由器可能通过其他方式(比如设备名称、IP 分配记录)来识别设备。MAC 随机化只防「被动追踪」,不防「主动识别」。

我的建议:在开发 Wi-Fi 相关功能时,不要依赖 MAC 地址作为设备唯一标识。Android 10 之后,WifiInfo.getMacAddress() 会返回 02:00:00:00:00:00(随机化后的值)。如果你非要获取真实 MAC,需要申请 android.permission.LOCAL_MAC_ADDRESS 权限,而且还不一定能拿到。

来看一段代码,演示如何获取当前连接的 Wi-Fi 的 MAC 地址(注意,这是随机化后的):

WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String macAddress = wifiInfo.getMacAddress();
// 在 Android 10+ 上,这个值通常是 02:00:00:00:00:00
Log.d("WiFi", "MAC Address: " + macAddress);

如果你想获取真实的 MAC 地址(比如用于企业设备管理),可以试试这个方式:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    // Android 10+ 需要特殊权限
    // 实际上,大多数情况下你拿不到真实 MAC
    // 建议改用其他标识方式,比如 Android ID 或 UUID
}

警告:千万不要把 MAC 地址当作「安全凭证」。我曾经见过一个项目,用 MAC 地址做设备认证,结果 Android 10 一升级,所有设备都认证失败了。这就是典型的「把随机值当固定值用」的坑。

29.3 数据加密:Wi-Fi 传输的最后一道防线

位置权限和 MAC 随机化解决的是「你是谁、你在哪」的问题。而数据加密解决的是「你说了什么」的问题。Wi-Fi 数据在空气中传播,本质上就是无线电波。只要有人拿着合适的设备,就能监听。

Wi-Fi 加密主要分三层:

层级 技术 说明
链路层 WPA2/WPA3 保护 Wi-Fi 连接本身,防止中间人攻击
传输层 TLS/SSL 保护应用数据,比如 HTTPS
应用层 自定义加密 开发者自己实现的加密逻辑

在 Android Wi-Fi 开发中,我们最常接触的是链路层加密。比如,配置一个 WPA2 企业级网络:

WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setIdentity("username");
enterpriseConfig.setPassword("password");
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAPV2);

WifiConfiguration config = new WifiConfiguration();
config.SSID = "\"MyEnterpriseWiFi\"";
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
config.enterpriseConfig = enterpriseConfig;

WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
int netId = wifiManager.addNetwork(config);
wifiManager.enableNetwork(netId, true);

这里要注意,密码是明文传递的。虽然 WPA2 本身会加密传输过程,但在你的应用代码里,密码是以字符串形式存在的。如果手机被 root 了,或者有恶意应用读取了你的进程内存,密码就泄露了。

我的经验:对于敏感数据,建议在应用层再加一层加密。比如,用 AES 对 Wi-Fi 密码进行加密后再存储到 SharedPreferences 中。虽然不能完全防住,但至少能挡住大部分「顺手牵羊」的攻击。

来看一个简单的 AES 加密示例:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import android.util.Base64;

public class CryptoUtil {
    private static final String ALGORITHM = "AES";
    private static final String KEY = "MySecretKey12345"; // 16字节

    public static String encrypt(String data) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encrypted = cipher.doFinal(data.getBytes());
        return Base64.encodeToString(encrypted, Base64.DEFAULT);
    }

    public static String decrypt(String encryptedData) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] decoded = Base64.decode(encryptedData, Base64.DEFAULT);
        return new String(cipher.doFinal(decoded));
    }
}

注意:上面的示例只是演示,生产环境中不要把密钥硬编码在代码里。密钥应该存储在 Android Keystore 系统中,或者通过安全的后端接口获取。

29.4 知识体系总览

说了这么多,咱们用一张图来梳理一下本章的核心逻辑。这张图展示了 Wi-Fi 安全与隐私的三个维度,以及它们之间的关系:

Wi-Fi 安全与隐私 · 三大核心 Wi-Fi 安全与隐私 位置权限 ACCESS_FINE_LOCATION 运行时动态请求 MAC 随机化 防追踪、防裸奔 数据加密 WPA2/WPA3 AES + TLS 三者关系 位置权限 → 让扫描合法 MAC 随机化 → 让身份匿名 数据加密 → 让内容安全

这张图其实就说明了一件事:安全不是单点防御,而是层层递进。位置权限是「入口」,MAC 随机化是「身份伪装」,数据加密是「内容保护」。少了任何一层,整个安全体系都有漏洞。

29.5 避坑指南与最佳实践

最后,我把自己这些年踩过的坑总结一下,希望能帮你少走弯路:

  • 位置权限别偷懒:Android 10 之后,ACCESS_FINE_LOCATIONACCESS_BACKGROUND_LOCATION 要分开申请。如果只在前台扫描,别申请后台权限,否则审核会被拒。
  • 别依赖真实 MAC:从 Android 10 开始,真实 MAC 基本拿不到了。如果你的业务逻辑依赖 MAC,赶紧改成用 UUID 或 Android ID。
  • 密码别明文存:Wi-Fi 配置中的密码,建议用 Android Keystore 加密存储。SharedPreferences 里存明文密码,等于把钥匙挂在门上。
  • WPA3 优先:如果设备支持,尽量用 WPA3。它比 WPA2 多了个「前向保密」特性,即使密码泄露,之前的通信记录也无法解密。
  • 测试要全面:不同厂商对 MAC 随机化的实现不一样。小米、华为、三星各有各的「优化」。我建议在至少 5 款不同品牌的手机上测试你的 Wi-Fi 功能。

一个小技巧:调试 Wi-Fi 相关功能时,可以用 adb shell dumpsys wifi 查看当前的扫描结果、连接状态和 MAC 地址信息。这个命令能帮你快速定位问题,比看日志高效多了。

好了,关于安全与隐私的三个核心点,咱们就聊到这儿。位置权限是「入场券」,MAC 随机化是「隐身衣」,数据加密是「保险箱」。这三样东西,缺一不可。做 Wi-Fi 开发,千万别只盯着「能不能连上」,更要关心「连上之后安不安全」。


公众号:蓝海资料掘金营,微信deep3321