如何避免在日志中打印敏感信息(如 Token、密码)?
解读
在国内 Android 面试中,日志安全是“安全编码”环节的高频考点。面试官真正想确认的是:
- 你是否意识到日志会被系统统一收集(国内厂商日志组件、崩溃平台、用户主动反馈);
- 你是否具备“可审计、可灰度、可一键关闭”的工程化思维,而不仅是“不打 log”这种口号式答案;
- 你是否能把控编译期、运行期、发布期三道闸门,兼顾排查效率与合规要求(《个人信息保护法》《数据安全法》对敏感字段有明确罚则)。
因此,回答要体现“制度+技术+工具”三位一体,既给出代码层方案,也提到国内落地细节(如国内渠道包必须兼容厂商日志采集 SDK、合规审计要求等)。
知识点
- 敏感信息定义:国内监管把“身份鉴别信息(token、sessionId)、生物识别信息、精准定位、支付密码”全部列为敏感个人信息,日志中不得明文出现。
- 编译期擦除:Gradle 插件在 transform / ASM 阶段扫描 Log.* 调用,自动剔除含敏感字段的常量。
- 运行时代码规范:统一日志门面 + 占位符脱敏 + 白名单开关。
- 发布期兜底:Release 包默认编译优化 + ProGuard/R8 删除 Log 类 + 国内多渠道加固(如乐固、360)二次扫描。
- 国内合规配套:日志上传前做本地加密缓存,文件级别 AES+TEE 密钥,上传通道走 HTTPS+证书强校验,满足工信部 164 号文“数据出境评估”要求。
答案
“我们分三道闸门保证敏感信息绝不出现在日志里:
-
编译期闸门
自定义 Gradle Plugin,在 AGP 7+ 的 TransformAction 里扫描所有LDC指令,如果发现常量池含关键字token、password、pwd、secret,并且位于Log.d/i/v方法的参数位置,直接抛编译错误,CI 立即失败,开发阶段就拦死。 -
运行期闸门
a) 统一日志门面:内部只使用Timber二次封装的Logger,Logger.d(TAG, "login success, token=%s", Logger.mask(token));
b) 脱敏规则:正则匹配\b([a-zA-Z0-9_-]{12,})\b替换为******{后4位},手机号保留前三后四,身份证保留前后各二;
c) 白名单开关:依托国内移动推送常用的“灰度平台”,下发logEnable=false时,Logger 内部直接 return,防止偶发灰度包忘记关日志;
d) 日志文件本地加密:写入前使用 TEE 生成的 AES-256-GCM 密钥加密,崩溃时上传的.gz包同样加密,满足国内合规审计“先加密后出境”要求。 -
发布期闸门
a) R8 规则:-assumenosideeffects class android.util.Log { int v(...); int d(...); int i(...); }保证 Release 包所有
Log.*被剪枝;
b) 国内渠道加固:360/乐固在重签名前会做静态扫描,发现token=xxx明文直接阻断发包;
c) 合规自检:发版前跑一遍内部LogScanner.jar,对 apk 反编译后的 smali 全文搜索敏感关键字,零命中才允许上架应用商店。
通过以上制度+技术+工具链,我们线上已连续 3 年零敏感日志外泄,并通过多次监管抽查。”
拓展思考
-
如果业务强依赖日志排查,如何“可逆脱敏”?
答:本地加密日志文件里保存原始密文,密钥分两段:一段在 TEE,一段在服务端。只有用户主动反馈且授权后,客服平台才能合并两段密钥解密,兼顾排查与隐私。 -
WebView 内 H5 日志如何同步治理?
答:在WebViewClient.onConsoleMessage()里拦截console.log,同样走 Native 的 Logger 门面,脱敏规则统一,防止 H5 成为日志泄露突破口。 -
海外 SDK(如 Firebase Crashlytics)上传日志是否合规?
答:国内上架包必须关闭海外 SDK 的自动上传,改用自有通道;若业务必须跨境,需走网信办数据出境安全评估,并在隐私政策中单独列出接收方与联系方式,否则应用商店会被下架。