应对隐私检测的各种姿势

1,519 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

背景

去年11月,我们团队就已经宣布解散,但是由于隐私监管的问题,我们还得维护最后一个版本,满足隐私监管的要求。 我们团队的隐私问题主要是由我来负责,回想起这一年,真是被各种隐私问题折腾得死去活来, 所幸最后一个版本终于上线了,现针对隐私问题总结一下各种应对的方法。

隐私检测的流程

流程 1.0

在刚开始被要求隐私检测的时候,我们的流程是这样的: 在这里插入图片描述

在定包前提交隐私检测,然后根据检测结果进行相应的更改,但是在实际操作的过程中发现有以下问题:

  1. 提交隐私检测之后有新的代码提交,可能会造成新隐私问题;
  2. 检测出问题之后再改,测试时间短,风险大,可能会造成定包延期;
  3. 在定包前进行修改,测试没有足够的人力来支持;

因此在和 PM 讨论之后,我们将流程进行了一些变更。

流程 2.0

我们在每个版本定包之后系统测试的时候提交隐私检测,然后将检测出来的问题转换成下个版本的需求。 在这里插入图片描述

然而在实行一段时间之后,发现仍然有问题。 我们团队分为北京和深圳两个团队,一般情况下我们是每两周发一个版本,深圳和北京交替发布,比如深圳发布了 4.7.0,接下来北京发布 4.7.5。 通常情况下,我们在等隐私检测结果出来后,下一个版本的需求早就排好。如下图所示,隐私检测大概需要三天左右,当我们针对 4.7.0 版本提交隐私检测,拿到检测结果之后,4.7.5 版本的需求早就已经排好,此时想要跟 4.7.5 版本就比较困难,只能将需求排进 4.8.0 版本。 在这里插入图片描述

所以第二个流程仍然有以下问题:

  1. 发版较为频繁,470 检测出来的问题,475 无法解决,需延迟到 480 才能解决;
  2. 470 检测出来的问题,475 检测出来仍然有同样的问题,浪费检测资源;
  3. 在系统测试期间,需要手动提交隐私检测,增加测试工作量;

在和隐私检测的同事讨论之后,我们对流程又进行了一些变更。

流程 3.0

既然隐私检测无法避免,与其被动的接受检测,不如主动的拥抱变化; 在这里插入图片描述

具体包含以下几项:

  1. 针对存量的隐私问题,我们仍然使用流程 2.0 的方式解决;
  2. 针对新的需求,我们和 PM 讨论之后,在产品内审需求评审阶段要求增加隐私自查,尽可能将隐私问题提前暴露,包括以下几项:
检查项
1该需求是否需要调用权限,是已有权限还是之前没有标注写明过的权限,同时需要在需求文档陈述若用户拒绝授权后的流程
2该需求会不会接入新的 sdk,需要进行 sdk 明示
3该需求是否有其他的数据分享/共享情况,需要文字补充
  1. 流水线引入隐私检测 我们在release版的流水线增加提交隐私检测的步骤, 这一步会在首次自动化测试通过之后执行,会首先判断该版本是否已经提交检测,若已经提交,则不再提交,若未提交,则会将对应的 apk 上传至检测平台。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PQAMkypW-1643341168971)(https://km.woa.com/gkm/api/img/cos-file-url?url=https%3A%2F%2Fkm-pro-1258638997.cos.ap-guangzhou.myqcloud.com%2Ffiles%2Fphotos%2Fpictures%2F202201%2F1643285449-5883-61f28bc98fa2d-461977.png&is_redirect=1)]

除了流程上的问题,还有各种技术层面的问题。

应对隐私检测的各种姿势

1. 提前获取数据和获取频率

针对提前获取数据的这种情况,我创建了一个类 PrivacyInit, 在用户未授权时,先将需要初始化的代码放到 PrivacyCallBack 里面,等用户授权之后再初始化; 若用户已经授权,则直接执行。

//用户授权之后调用
public static void privacyAllInit() {
    for (PrivacyCallBack callBack : callBackList) {
        try {
            callBack.call();
        } catch (Exception e) {
            e.printStackTrace();
            ABCLog.e(TAG, e.getMessage());
        }
    }
}

//判断是否授权,若未授权则先不初始化
public static void privacyInit(PrivacyCallBack callBack) {
    if (isAuthorized()) {
        try {
            if (callBack != null) {
                callBack.call();
            }
        } catch (Exception e) {
            e.printStackTrace();
            ABCLog.e(TAG, e.getMessage());
        }
    } else {
        callBackList.add(callBack);
    }
}

//隐私回调接口
public interface PrivacyCallBack {
    void call();
}

针对获取频率的问题就比较简单,直接使用一个全局的静态变量或者单例保存相关的隐私数据即可,避免多次获取。

2. 权限问题

新的隐私规范要求我们每次使用某个权限的时候,均需要给出弹窗提示,然后才能真正的进行权限申请。 如果针对每个场景均进行覆盖和修改,显然成本比较大,不过好在之前针对权限申请做过收拢操作。 Android 原生申请权限的方式如下: 通常我们在某个地方申请了权限,然后在对应的 onRequestPermissionsResult 里面拿到权限申请的结果

public class xxxActivity extends AppCompatActivity {
    
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //申请权限
        this.requestPermissions(permissions, 100);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
            //获取权限申请的结果     
    }
}

这样做的坏处就是申请权限和获取结果分散在不同的地方,另外不同的 Activity 申请权限,不方便统一管理。 因此我们创建了一个透明的 Activity 专门用于申请权限,然后申请到的结果通过回调的方式返回。

<activity
    android:name="***.***.***.PermissionActivity"
    android:theme="@style/Transparent" >
</activity>

最后实现的结果如下:

//申请权限
PermissionHelper.with(context).permission(Manifest.permission.CAMERA)
        .callBack((int requestCode, String[] permissions, int[] grantResults) -> {
            //得到权限申请的结果
        }).request();

同时,由于所有的权限申请被收拢在一个地方,因此可以很方便的应对权限监管的各种花里胡哨的需求。

3. sdk问题

如果隐私检测出 sdk 有隐私问题,我们都会直接找到对应的团队,让他们帮忙解决,更新 sdk 的版本。 但是也有一些特殊的情况,比如 sdk 已经不维护了。

反编译

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PHOMYtZf-1643341168972)(https://km.woa.com/gkm/api/img/cos-file-url?url=https%3A%2F%2Fkm-pro-1258638997.cos.ap-guangzhou.myqcloud.com%2Ffiles%2Fphotos%2Fpictures%2F202201%2F1643338182-9285-61f359c6e2b19-807402.png&is_redirect=1)]

遇到这种情况,内心特别崩溃,若是去掉 sdk,可能会引发现有功能的异常,若是引入新的 sdk,针对最后一个版本的场景,改动成本会比较大, 如何以最小的成本来解决隐私问题呢? 我想到了反编译。 可以将jar包反编译成 smali,修改 smali,然后将其编译成新的 jar 包,替换掉原来的 jar 包就可以了。 不过这不是一个常规的手段,慎用!

终极大招

反编译毕竟不是一个常规的手段,而且需要修改 smali 代码,对应的语法也不熟悉,但是没有关系,我们还可以使用 Transfrom,直接修改 sdk 里面的代码。 通常情况,我们遇到 sdk 的隐私问题可以分为两类,访问了隐私相关的变量或者访问了隐私相关的方法。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    var btn = findViewById<Button>(R.id.btn)
    btn.setOnClickListener {
        println("getdevice:"+Build.DEVICE)//访问了隐私相关的变量
        println("getImei:"+getImei())//访问了隐私相关的方法
    }
}

1. 访问隐私相关的变量 针对这种情况,我们可以在 AdviceAdapter 当中复写 visitFieldInsn 方法,当访问到对应的字段时可以直接返回我们自己设定的变量

public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
    if ("android.os.Build".equals(owner) || "DEVICE".equals(name) ) {
        //替换成我们自己的变量
        super.visitFieldInsn(opcode, "***/***/***/Privacy", "device", "Ljava/lang/String;");
    } else {
        super.visitFieldInsn(opcode, owner, name, descriptor);
    }
}

经过替换,我们可以发现,访问 android.os.Build.Device 字段已经被替换成 Privacy.device 了 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QBdghfyc-1643341168972)(https://km.woa.com/gkm/api/img/cos-file-url?url=https%3A%2F%2Fkm-pro-1258638997.cos.ap-guangzhou.myqcloud.com%2Ffiles%2Fphotos%2Fpictures%2F202201%2F1643286685-9888-61f2909df16ea-11646.png&is_redirect=1)]

2. 访问隐私相关方法 假定 MainActivity.getImei() 方法是一个隐私相关的方法,我们想把它替换成 Privacy.newGetImei() 方法, 我们可以在 AdviceAdapter 当中复写 visitMethodInsn 方法,如下所示,可以替换成我们想要的方法。

@Override
public void visitMethodInsn(int opcode, String owner, String methodName, String descriptor, boolean isInterface) {
    if ("getImei".equals(methodName) && "()Ljava/lang/String;".equals(descriptor)&&""***/"***/"***/MainActivity".equals(owner)) {
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "***/"***/"***/Privacy", "newGetImei", "()Ljava/lang/String;", false);
    } else {
        super.visitMethodInsn(opcode, owner, methodName, descriptor, isInterface);
    }
}

经过替换,我们发现访问的方法已经变成 Privacy.newGetImei() 了 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VFlXd56b-1643341168973)(https://km.woa.com/gkm/api/img/cos-file-url?url=https%3A%2F%2Fkm-pro-1258638997.cos.ap-guangzhou.myqcloud.com%2Ffiles%2Fphotos%2Fpictures%2F202201%2F1643286746-1683-61f290da291d0-112131.png&is_redirect=1)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ItXqyxSY-1643341168973)(https://km.woa.com/gkm/api/img/cos-file-url?url=https%3A%2F%2Fkm-pro-1258638997.cos.ap-guangzhou.myqcloud.com%2Ffiles%2Fphotos%2Fpictures%2F202201%2F1643286804-4919-61f29114781a7-724252.png&is_redirect=1)]

总结

隐私合规已经告一段落,这中间让我感到很崩溃的是,隐私的政策一直在变,比如刚开始并未把 AndroidID 纳入隐私字段,后面加上了,再后来访问频次也加入了检测。 还有就是隐私检测每次检测出来的问题都不一样,比如第一次检测出 ABC 问题,我们改了之后,又检测出 DE 问题,导致问题很难得到收敛。 不过,最黑暗的时刻已经过去了。 另外感谢一直并肩作战的产品同学,虽然都已经活水到了其他部门,但是我们坚持到了最后,这个需求我感觉我们已经做出了革命的友谊😄 。