KiwiGuard SDK (Android)下载与接入文档
1.最新更新内容
更新版本:
上线KiwiGuard Android监测v1.8(SaaS平台侧)
上线KiwiGuard Android SDK v3.2.11(本页面第2节中可下载)
更新时间:
2021/9/16 12:00:00
更新内容:
① 优化了SaaS平台侧:KiwiGuard Android SDK的接入流程;
② 优化了SaaS平台侧:终端威胁异常的数据展示;
③ 优化了后端接入时:HTTP API的响应数据**(API升级至v2版本),旨在为企业提供可靠且全面的终端威胁数据源(为了更好的体验,请尽量使用v2版本的API,见5.2.1)**,
④ KiwiGuard Android SDK v3.2.11版本支持ameabi架构;
⑤ 修复了一些已知问题。
2 KiwiGuard SDK下载
**版本号:**KiwiGuard Android-v3.2.11(若无特殊要求,请下载集成最新SDK)
**发布时间:**2021/9/16 11:00:00
**版本描述:**① 优化了数据交互,旨在为企业提供可靠且全面的终端威胁数据源;② 支持ameabi架构
**版本号:**KiwiGuard Android-v3.2.10
**发布时间:**2021/7/21 18:00:00
**版本描述:**虚拟机环境监测,新增对51虚拟机和光速虚拟机的检测支持
**版本号:**KiwiGuard Android-v3.2.9
**发布时间:**2021/7/1 19:00:00
**版本描述:**修复虚拟机检测漏报VMOS pro bug ,修复其他已知bug
3 名称解释
序号 | 名词 | 描述 |
1 | 应用组 | 相同App(不同版本、不同渠道)的集合,便于更好的管理企业内的应用矩阵,首次使用时需创建应用组 |
3 | appGroupID | 应用组ID:POST请求参数,能够发送POST请求访问几维安全服务端接口,获取应用组下所有App的终端威胁信息,需搭配appGroupSecret使用 |
4 | appGroupSecret | 密钥:POST请求参数,能够发送POST请求访问几维安全服务端接口,获取应用的终端威胁信息,需搭配appGroupID或appID使用 |
5 | appID | AppID:POST请求参数,能够发送POST请求访问几维安全服务端接口,获取当前App名称、版本及渠道下的终端威胁信息,需搭配appGroupSecret使用 |
4 法规要求
您应向您的终端用户逐一明示您嵌入的第三方SDK收集使用个人信息的目的、方式和范围。
您应当在《隐私政策》中明确告知终端用户,您选择了成都盈海益讯科技有限公司作为合作方,并委托其收集、使用、加工和处理终端用户个人信息。成都盈海益讯科技有限公司建议您在《隐私政策》中的“我们如何共享、转让、公开披露您的个人信息”的表述条款中增加如下内容(关于个人信息共享给第三方说明的部分,强烈建议此部分由您专业的法务团队优化完善,详情可参考《KiwiGuard SDK隐私政策》):
为了识别客户端或终端用户的异常状态,为了对盗版应用、虚假设备、游戏外挂、环境风险、对抗事件等威胁场景进行监测与管控,我们的产品会集成第三方的SDK或类似应用程序,具体如下:
SDK名称 | KiwiGuard SDK |
所属公司名称 | 成都盈海益讯科技有限公司 |
SDK收集数据类型 | 个人常用设备信息:(1)设备MAC地址;(2)设备IMEI(3)安装软件列表;(4)唯一设备识别码(安卓ID、设备硬件序列号);(5)设备的基本配置信息(设备厂商、型号、CPU型号、系统版本、内核版本、系统语言、SDK版本)。 |
SDK用途 | (1)计算设备唯一标识码(设备指纹)(2)识别终端威胁异常信息;(3)对威胁异常进行管控。 |
5 KiwiGuard SDK手动接入流程
5.1 KiwiGuard SDK前端接入
集成KiwiGuard SDK之前:请先在几维安全《移动应用安全管理平台》创建应用组、创建App,获取appID。
5.1.1 集成KiwiGuard SDK
(1)下载KiwiGuard SDK
下载KiwiGuard SDK,下载之后得到一个KiwiGuard-SDK-v3.x.y.zip 的一个文件,解压后文件中包含.aar 和 .lic两个文件。
(2)Android Studio 集成
① 把kiwiguard.aar放到相应模块的libs目录下;
② 把kiwiguard.lic放到相应模块的assets目录下;
③ 在该模块的build.gradle中如下配置;
android {
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
repositories{
flatDir{
dirs 'libs'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation files('libs/kiwiguard.aar')
}
(3)添加SDK所需权限
编辑AndroidManifest.xml 文件,添加如下权限。
<!-- 网络权限(必选) -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- IMEI获取(必选) -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
(4)Proguard混淆配置
编辑Proguard 配置文件,所在位置一般为 app/src/proguard-rules.pro。
-dontwarn com.kiwisec.**
-keep class com.kiwisec.**{*;}
(5)API 6.0或以上动态权限申请说明
Android 6.0 及以上对于敏感权限需要弹窗申请,所以上面所需权限中的READ_PHONE_STATE 需要动态申请,申请时可按如下示例进行。
public void requestPermission() {
//权限检查
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED) {
//申请权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_PHONE_STATE
}, 1);
}
}
5.1.2 接口使用说明
(1)初始化SDK( 该步骤不可省略,setAppID()、setup() 都必须调用 )
初始化建议放到应用Application中的onCreate( ) 方法中进行。
import com.kiwisec.KiwiGuard;
public class MyApplication extends Application
{
@Override
public void onCreate()
{
super.onCreate();
// 设置KiwiGuard 必要的项目关联信息
// 在调用SDK 之前需要调用该接口设置appID
// @param appID - 从几维平台创建项目时获取
// @throws RuntimeException - appID为空或者空字串则抛出异常
KiwiGuard.setAppID("在平台创建KiwiGuard手动SDK项目的AppID");
// 初始化KiwiGuard
// 初始化 KiwiGuard SDK
KiwiGuard.setup();
}
}
(2)获取deviceToken
整个过程由于是耗时操作,必须要在非主线程上执行,否则会crash。
private void requestDeviceToken(){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
HashMap<String,String> options = new HashMap<>();
String deviceToken = KiwiGuard.getDeviceToken(options);
//TODO: 把 deviceToken 结合其他参数,发送POST请求,可以获取设备风险信息 (参考KiwiGuard HTTP接口接入)
}
});
thread.start();
}
在需要进行风险识别时调用上述的getDeviceToken,例如:
- 新用户注册后,进行风险画像查询,用于新人红包、注册礼物
等的差异化投放; - 存量用户登录后,进行风险画像查询,用于日常活动运营的差异化投放;
- 活动奖励下发前,进行风险画像查询,例如随机红包、抽奖等概率性活动;
- 每日的例行查询,进行画像库或风控引擎的数据更新。
(3)异常说明
调用 setAppID()时,appID 设置不正确 RuntimeException("appID can't be null or empty!"); 调用 setup() 前,未调用setAppID() RuntimeException("You need call KiwiGuard.setAppID() before setup KiwiGuard SDK"); 调用 getDeviceToken()时,未开子线程调用 RuntimeException("Can't call KiwiGuard.getToken in MainUI thread"); ----------- native 层会抛出以下两个异常 ---------- 初始化SDK时,appID 未设置 NullPointerException("options can't be null!"); 初始化SDK时,未获取到appID RuntimeException("fetch appID exception!");
5.2 KiwiGuard SDK后端接入
备注:本节可通过访问几维安全服务端API获取终端威胁数据源;可在App测试环节与上线后进行操作。
5.2.1 HTTP接口接入方法
(1) 接口地址(v2)
encrypt_key = 'secret:{AppGroupSecret}*{deviceToken}*{timestamp}:encrypt'.format(AppGroupSecret=AppGroupSecret, deviceToken=deviceToken, timestamp=timestamp)
备注:2021/9/16 11:00:00已将API升级至v2版本,旨在为企业提供可靠且全面的终端威胁数据源**(为了更好的体验,请尽量使用v2版本的API)**,点击查看v1版本API地址及响应的数据
(2) 请求参数
字段 | 类型 | 传入说明 | 描述 |
deviceToken | string | 必传参数 | 用户前端获取的deviceToken |
timestamp | string | 必传参数 | 调用服务的秒级时间戳 (例如:“1650934578”) |
sign | string | 必传参数 | 查询数据时服务端校验数据是否篡改的字段(通过appGroupSecret,deviceToken,timestamp指定规则字符串后通过md5字符串加密生成的32位加密后的字符串,见下方代码示例) |
appGroupID | string | 请求参数中两者必须选填一个 | 几维安全SaaS平台获取(用来获取应用组下所有接入SDK的应用终端威胁信息) |
appID | string | 前往几维安全Saas平台获取(用来获取当前应用的终端威胁信息) |
*如何生成参数sign?
【Python实例】
① 构建待加密的指定规则字符串 ;
AppGroupSecret: 几维安全SaaS平台获取( KiwiGuard > 应用管理 > 应用组AppGroupSecret )
encrypt_key = 'secret:{AppGroupSecret}*{deviceToken}*{timestamp}:encrypt'.format(AppGroupSecret=AppGroupSecret, deviceToken=deviceToken, timestamp=timestamp)
② 使用md5加密,获取加密后的数据传递给sign字段;
import hashlib
m = hashlib.md5()
m.update(encrypt_key.encode())
sign = m.hexdigest()
【Java实例】
// 封装的计算sign函数
public String getStrArrMd5Sign(String []strArr){
StringBuilder sb = new StringBuilder();
String signStr = null, signMd5 = null;
if(strArr != null && strArr.length > 0){
sb.append("secret:");
for(int i=0; i<strArr.length; i++){
sb.append(strArr[i]);
if(i != (strArr.length-1))
sb.append("*");
}
sb.append(":encrypt");
signStr = sb.toString();
Log.d(TAG,signStr);
if(!signStr.isEmpty()){
MessageDigest md = null;
try{
md = MessageDigest.getInstance("MD5");
// 计算md5函数
md.update(signStr.getBytes());
signMd5 = new BigInteger(1, md.digest()).toString(16);
// (fix)不满32位前面添加0
while(signMd5.length() < 32 ){
signMd5 = "0" + signMd5;
}
}catch (Exception e){
Log.d(TAG,"no such MD5 algorithm"+e.getMessage());
}
}
}
return signMd5;
}
// 调用上面封装的函数获取sign
String sign = getStrArrMd5Sign(new String[]{AppGroupSecret
,deviceToken,"时间戳"});//请严格按照该拼接顺序(时间戳例如:"16509345787")
// 封装json 请求体
// appGroupID 和 appID 至少任意填一个
JSONObject jsonBody = new JSONObject();
jsonBody.put("deviceToken",deviceToken);
jsonBody.put("appGroupID","");
jsonBody.put("sign",sign);
jsonBody.put("timestamp","时间戳");
jsonBody.put("appID","");
// TODO 发起网络请求
*如何发送POST请求?
【Java实例】
public class HttpUtils {
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static OkHttpClient client = new OkHttpClient();
public static Response post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url) //填写接口地址
.post(body) //填写请求参数
.build();
Response response = client.newCall(request).execute();
return response;
}
}
【Python实例】
import hashlib, requests, pprint, json
def md5_convert(string):
"""
计算字符串md5值
:param string: 输入字符串
:return: 字符串md5
"""
m = hashlib.md5()
m.update(string.encode())
return m.hexdigest()
if __name__ == '__main__':
appSecretKey = '****' # 应用管理应用组所对应的 SecretKey
deviceToken = '****' # deviceID
ts = **** # 时间戳
md5_key = 'secret:{appSecretKey}*{deviceToken}*{ts}:encrypt'.format(appSecretKey=appSecretKey, deviceToken=deviceToken, ts=ts)
sign = md5_convert(md5_key)
body = {
"appGroupID": "****", # 应用组id
,web端获取
"appID": "****", # appID,web端获取
"deviceToken": deviceToken,
"sign": sign,
"timestamp": ts
}
pprint.pprint(json.loads(requests.post(url='****', json=body).text), depth=4)
(备注:若没有查看appGroupSecret的权限,请联系几维安全工作人员。)
(3)成功响应
字段 | 类型 | 描述 |
code | int | 状态码 |
message | String | 状态描述 |
data | Json | 返回设备指纹deviceID及设备风险数据 |
成功示例:
{
"message": "success!",
"code": 1,
"data": {
"deviceID":"xxx",
"fakeRiskDevice":{
"virtualEnv":{
key1:value1,
key2:value2
}
},
"environmentRiskDevice":{
"root":{
key1:value1
}
},
"fightRiskDevice":{
"debug":{
key1:value1
}
},
"suspiciousRiskDevice":{
"gameScriptTools": {
key1:value1,
key2:value2
}
}
}
}
(4)错误响应
字段 | 类型 | 描述 |
code | int | 状态码 |
message | String | 状态描述 |
失败示例:
{
"message": '缺少查询参数!',
"code": 1880001
}
错误码 | 描述 |
1005 | 缺少认证信息,接口请求失败!请核对url |
1880001 | 缺少查询参数。详情看请求参数说明 |
1880002 | sign校验失败,md5生成的加密字符串错误 |
1880003 | appID 不存在,请检查 |
1880004 | appGroupID 不存在,请检查 |
1880005 | 设备指纹不存在,请检查 |
5.2.2 HTTP接口返回参数明细说明
成功响应时候,data字段Json类型的内容为:
字段 | 类型 | 描述 |
deviceID | String | 设备指纹 |
uploadTime | int | 服务端的处理时间 |
fakeRiskDevice | Json | 虚假设备相关标签信息 |
environmentRiskDevice | Json | 环境风险相关的标签信息 |
fightRiskDevice | Json | 对抗风险相关的标签信息 |
suspiousRiskDevice | Json | 可疑设备相关的标签信息 |
(1)fakeRiskDevice详细内容
① 模拟器检测:
字段 | 类型 | 描述 |
emulator | Json | 模拟器检测 |
emulator详细内容:
字段 | 类型 | 描述 |
checked | bool | 应用是否运行在模拟器中 |
emulatorBrand | String | 模拟器厂商 |
② 虚拟机检测:
字段 | 类型 | 描述 |
virtualEnv | Json | 虚拟机检测 |
virtualEnv详细内容:
字段 | 类型 | 描述 |
checked | bool | 应用是否运行在虚拟机中 |
virtualBrand | String | 虚拟机包名 |
virtualPackage | String | 虚拟机厂商 |
③ 云真机检测:
字段 | 类型 | 描述 |
cloudMachine | Json | 云真机检测 |
cloudMachine详细内容:
字段 | 类型 | 描述 |
checked | bool | 应用是否运行在云真机中 |
cloudMachineBrand | String | 云真机厂商 |
(2)environmentRiskDevice详细内容
① ROOT检测
字段 | 类型 | 描述 |
ROOT | Json | ROOT环境检测 |
ROOT环境详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备运行的终端是否为ROOT环境 |
② VPN检测
字段 | 类型 | 描述 |
VPN | Json | VPN检测 |
VPN详细内容:
字段 | 类型 | 描述 |
checked | bool | 是否安装VPN |
vpnPackages | array | 已安装的VPN的应用包名数组,以英文逗号分割 |
(3)fightRiskDevice详细内容
字段 | 类型 | 描述 |
debug | Json | 调试风险检测 |
debug详细内容:
字段 | 类型 | 描述 |
checked | bool | 是否存在调试行为 |
dbgJava | bool | 是否存在Java调试 |
dbgNative | bool | 是否存在Native调试 |
(4)suspiciousRiskDevice详细内容
① GG修改器检测
字段 | 类型 | 描述 |
gameGuard | Json | GG修改器检测 |
gameGuard详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否安装GG修改器 |
② 八门神器检测
字段 | 类型 | 描述 |
xdGame | Json | 八门神器检测 |
xdGame详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否安装八门神器 |
③ SIM卡状态检测
字段 | 类型 | 描述 |
SIM | Json | SIM卡状态检测 |
SIM详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备SIM卡状态是否异常 |
simStateDesc | String | 设备SIM卡异常状态描述 |
④ 调试模式检测
字段 | 类型 | 描述 |
debugMode | Json | 调试模式检测 |
debugMode详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否开启调试模式 |
⑤ 自动点击工具检测
字段 | 类型 | 描述 |
automaticTools | Json | 自动点击工具检测 |
automaticTools详情内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否安装自动点击工具 |
aidPackages | array | 已安装的自动点击工具的应用包名数组,以英文逗号分割 |
⑥ 辅助服务检测
字段 | 类型 | 描述 |
accessibilityService | Json | 辅助服务检测 |
accessibilityService详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否开启辅助服务 |
⑦ 多开工具检测
字段 | 类型 | 描述 |
multipleTools | Json | 多开工具检测 |
multipleTools详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否安装了多开工具 |
multiPackages | array | 已安装的多开工具的应用包名数组,以英文逗号分割 |
⑧ Hook检测
字段 | 类型 | 描述 |
hookDevice | Json | Hook检测 |
hookDevice详细内容:
字段 | 类型 | 描述 |
checked | bool | 进程是否被注入其他代码或库 |
hookTool | String | Hook框架 |
⑨ 工程模式检测
字段 | 类型 | 描述 |
engineeringMode | Json | 工程模式检测 |
engineeringMode详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否进入工程模式 |
⑩ 远程操作工具检测
字段 | 类型 | 描述 |
controlTools | Json | 远程操作工具检测 |
controlTools详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否安装远程操作工具 |
remoteCtrlPackages | array | 已安装的远程操作工具的应用包名数组,以英文逗号分割 |
⑪ 云设备控制工具检测
字段 | 类型 | 描述 |
cloudDeviceControlTools | Json | 云设备控制工具检测 |
cloudDeviceControlTools详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否安装了云设备控制工具 |
cloudCtrlPackages | array | 已安装的云设备控制工具的应用包名数组,以英文逗号分割 |
⑫ 程序脚本辅助工具检测
字段 | 类型 | 描述 |
gameScriptTools | Json | 程序脚本辅助工具检测 |
gameScriptTools详细内容:
字段 | 类型 | 描述 |
checked | bool | 设备是否安装了程序脚本辅助工具 |
gameScriptPackages | array | 已安装的游戏程序辅助工具的应用包名数组,以英文逗号分割 |
6 FAQ
6.1 Cleartext HTTP traffic to xxx.xxx.xxx.xxx not permitted
这个问题一般是apk 中使用了http 通信,高版本手机中默认只允许https 通信。所以如果要使用http通信,需要在AndroidManifest.xml 中添加一行配置:
android:usesCleartextTraffic="true"
在AndroidManifest.xml 中如下:
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".MyApplication"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">