搜索
开发文档
应用开发
快应用开发
小游戏开发
开发文档/应用开发/服务能力/安全开放服务/可信设备SDK接入指南
可信设备SDK接入指南更新时间: 2025-01-22 10:42:00

一、集成SDK

1、添加AAR

添加小米安全开放服务SDK(sdk-xsof-20231229-v1.0.1-release.aar)到项目Module(app)的libs路径中

SDK文件:

上传文件

注:图片仅供参考,以实际版本号为准

2、配置Gradle

在Module所属的Gradle文件(app/build.gradle)中增加编译依赖

// 只添加小米安全开放服务对应的一个aar 
implementation files('libs/sdk-xsof-20231229-v1.0.1-release.aar')

// 添加libs文件夹下的所有aar 
implementation fileTree(dir: "libs", includes: "*.aar")

注意:上述步骤中的文件路径和Gradle配置仅供参考, 可根据项目实际情况灵活调整,只要保证aar参与到项目编译即可。

3、配置appId、appKey

应用开启小米安全开放服务后,会分配对应的appId和appKey。
如何开启小米安全开放服务,参考小米安全开放服务后台使用说明

appId和appKey在SDK初始化时会用到,需要调用方添加到项目中。

appId和appKey可以在代码中写常量,也可以在工程中增加配置文件,然后在SDK初始化时读取。

二、API使用说明

完成步骤1后,即可使用小米安全开放服务客户端SDK提供的API。

1、API简要说明

public class MiSafetyDetectClient {  
/**
* 初始化SDK
* @param appId 应用的AppId
* @param appKey 应用的AppKey
*/
public void init(String appId, String appKey);

/**
* 获取设备可信检测信息
* @param challenge 随机数
* @param onCompleteListener 接收检测结果的回调
*/
public void getTrustDeviceStatus(String challenge, OnCompleteListener<TrustDeviceStatusResponse> onCompleteListener);

/**
* 获取恶意应用检测信息
* @param onCompleteListener 接收检测结果的回调
*/
public void getRiskApps(OnCompleteListener<RiskAppResponse> onCompleteListener);

/**
* 获取模拟点击检测信息
* @param timeInterval 需要检测的时间范围
* @param onCompleteListener 接收检测结果的回调
*/
public void getSimulatedTouchDetectResult(long timeInterval, OnCompleteListener<SimulatedTouchResponse> onCompleteListener);
}

2、使用示例

2.1 SDK初始化

import com.xiaomi.security.xsof.MiSafetyDetect;  
import com.xiaomi.security.xsof.safetydetect.MiSafetyDetectClient;

ClassName {
private static final String APP_ID = "***"; // replace with your appId
private static final String APP_KEY = "***"; // replace with your appKey

// 通过MiSafetyDetect#getMiSafetyDetectClient获取MiSafetyDetectClient实例
MiSafetyDetectClient miSafetyDetectClient = MiSafetyDetect.getMiSafetyDetectClient(getContext());
// 如果是null,说明当前系统版本不支持安全开放服务相关服务
if (miSafetyDetectClient != null) {
miSafetyDetectClient.init(APP_ID, APP_KEY);
}
}

初始化后即可使用小米安全开放服务提供的各项服务。

目前小米安全开放服务支持三项检测:

  1. 设备可信检测
  2. 恶意应用检测
  3. 模拟点击检测

2.2 获取设备可信检测结果

private void getSafetyDeviceStatus() {  
/*
* challenge值为服务端下发的String类型的随机数,
* 要求:
* 1.长度为16至256个字符
* 2.不要包含换行等特殊字符
*/
String challenge = getChallengeFromServer();
/*
* 检测结果数据示例(JSON格式):
* {
* "data":{
* "package_name":"com.xiaomi.demo",
* "version_code":"1",
* "certificate_sha256":"edd320bb5a5708a818240760366c8d",
* "unlock_l1":"locked",
* "unlock_l2":"locked",
* "root_l1":"root",
* "selinux_l1":"enforcing",
* "fr_counter_l1":"0",
* "challenge":"xxxx"
* },
* "authenticity":{
* "hash":"xxxxx",
* "token":"xxxxx"
* }
* }
*
* 使用2.1中初始化得到的MiSafetyDetectClient实例进行调用
* @param challenge 随机数
* @param onCompleteListener 接收检测结果的回调
*/
miSafetyDetectClient.getTrustDeviceStatus(challenge, new OnCompleteListener<MiSafetyDetectClient.TrustDeviceStatusResponse>() {
@Override
public void onComplete(MiSafetyDetectClient.TrustDeviceStatusResponse response) {
// 成功获取到检测结果
if (response.isSuccess()) {
String resp = response.getTokenResult();
try {
JSONObject jsonObject = new JSONObject(resp);
// 获取data域
String data = jsonObject.get("data").toString();
// 获取authenticity域
JSONObject authenticity = (JSONObject) jsonObject.get("authenticity");
// 校验data域的hash值与authenticity域的hash值是否一致
Log.i(TAG, "check hash: " + HashUtils.checkHashSHA256(data, authenticity));
} catch (JSONException e) {
Log.e(TAG, "onComplete: ", e);;
}
} else {
// 获取相关信息时出现异常
final int statusCode = response.getStatusCode();
Log.w(TAG, "getTrustDeviceStatus result error, code = " + statusCode);
}
}
});
}

结果data域字段说明

字段 字段说明
data 设备可信状态数据,数据hash的原始数据。
package_name 调用方应用包名。
version_code 调用方应用版本。
certificate_sha256 调用方应用APK 签名 SHA256
unlock_l1 从REE侧获取的bootloader解锁状态
unlock_l2 从TEE级别获取的bootloader解锁状态
root_l1 从REE侧获取的系统root状态
selinux_l1 从REE侧获取的selinux状态
fr_counter_l1 从REE侧获取到的历史恢复出厂次数
challenge 调用方通过API接口传入的随机数,建议由业务服务端生成,长度为 16~256字符。
authenticity 数据签名信息。
hash 对data的value进行SHA256得到的哈希值。
token 用于数据验签,可以将该数据上传到小米的服务器进行数据验证。

HashUtils工具类

import android.text.TextUtils;   
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.security.MessageDigest;

public class HashUtils {

private static final String TAG = "HashUtils";

public static boolean checkHashSHA256(String data, JSONObject authenticity) {
String dataHash = getSHA256String(data);
String authenticityHash = "";
try {
authenticityHash = authenticity.getString("hash");
} catch (JSONException e) {
Log.e(TAG, "checkHashSHA256: ", e);
}
return dataHash != null && dataHash.equals(authenticityHash);
}

public static String getSHA256String(String originData) {
if (TextUtils.isEmpty(originData)) {
return null;
} else {
MessageDigest messageDigest;
String encodedString = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(originData.getBytes());
encodedString = byte2Hex(messageDigest.digest());
} catch (Exception e) {
Log.e(TAG, "encrypt sha256 exception", e);
}
return encodedString;
}
}

private static String byte2Hex(byte[] bytes) {
StringBuilder stringBuffer = new StringBuilder();
String temp;
for (byte aByte : bytes) {
temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
}

2.3 获取恶意应用检测结果

private void getRiskAppList() {   
/*
* 检测结果数据示例(List<AppData>格式):
* [
* AppData{
* packageName='package1',
* signatureMd5='package1_sig_md5',
* appCategory=5},
* AppData{
* packageName='package2',
* signatureMd5='package2_sig_md5',
* appCategory=5}
* ]
*
* 使用2.1中初始化得到的MiSafetyDetectClient实例进行调用
* @param onCompleteListener 接收检测结果的回调
*/
miSafetyDetectClient.getRiskApps(new OnCompleteListener<MiSafetyDetectClient.RiskAppResponse>() {
@Override
public void onComplete(MiSafetyDetectClient.RiskAppResponse riskAppResponse) {
// 成功获取到检测结果
if (riskAppResponse.isSuccess()) {
Log.i(TAG, "Installed risk apps: " + riskAppResponse.getAppsList());
} else {
// 获取相关信息时出现异常
int statusCode = riskAppResponse.getStatusCode();
Log.i(TAG, "Get risk app list error, code = " + statusCode);
}
}
});
}

2.4 获取模拟点击检测结果

private void getSimulatedTouchDetectResult() {    
/*
* 检测结果数据示例(JSON格式):
* {
* "result":false
* }
*
* 使用2.1中初始化得到的MiSafetyDetectClient实例进行调用
* @param timeInterval 需要检测的时间范围
* @param onCompleteListener 接收检测结果的回调
*/
final long timeInterval = 3 * 60 * 1000L; // 检测过去3min是否发生过模拟点击
miSafetyDetectClient.getSimulatedTouchDetectResult(timeInterval, new OnCompleteListener<MiSafetyDetectClient.SimulatedTouchResponse>() {
@Override
public void onComplete(MiSafetyDetectClient.SimulatedTouchResponse response) {
// 成功获取到检测结果
if (response.isSuccess()) {
Log.i(TAG, "Get simulated touch detect result : " + response.getTokenResult());
} else {
// 获取相关信息时出现异常
final int statusCode = response.getStatusCode();
Log.i(TAG, "Get simulated touch detect result error, code = " + statusCode);
}
}
});
}

三、设备可信服务数据签名验证

针对2.2中获取到的设备可信检测结果,可以通过如下方式验证其真实性。

1、本地hash比对

通过2.2中提供的HashUtils工具类,可以计算data域的SHA256 hash值并将其与authenticity域中的hash值做比对,如果一致,则说明data域的数据与authenticity域中的hash值匹配。

如果不一致,则说明data域中的数据被篡改。

2、通过服务端进行签名校验

本地hash比对比较快捷,但由于hash值也可以被篡改,所以还存在一定风险。

针对需要高可靠性的场景,可以通过向小米服务端发起请求来进行在线验证。

注意:

  • 在线验证需要申请网络权限。
  • 此处仅为代码示例,实际调用时,请通过三方服务端发起。

示例代码

String result = HttpUtils.post( 
context,
(JSONObject) mData2Verity.get("data"),
(JSONObject) mData2Verity.get("authenticity"),
APP_ID,
APP_KEY,
getPackageName());

HttpUtils工具类

package com.xiaomi.xsof.demo.utils;    

import android.content.Context;
import android.util.Base64;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

public class HttpUtils {

private static final String TAG = "HttpUtils";
private static final String VERIFY_HOST = "https://framework.sec.miui.com/framework/device/status";
private static final String VERIFY_SALT = "33zt9239-a34f-3ty9-eb73-75456785ns5c";

public static String post(Context context, JSONObject data, JSONObject authenticity, String appId, String appKey, String packageName) {
JSONObject params = new JSONObject();
try {
params.put("data", data);
params.put("authenticity", authenticity);
params.put("pkgName", packageName);
params.put("appId", appId);
params.put("appKey", appKey);
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
params.put("nonce", System.currentTimeMillis() + packageName);
params.put("appSignature", SignatureUtils.getSignatureSHA256(context, packageName));
params.put("sign", getParamsSignature(params));
} catch (JSONException e) {
throw new RuntimeException(e);
}

int responseCode;
InputStream is = null;
ByteArrayOutputStream bos = null;
try {
URL parsedUrl = new URL(VERIFY_HOST);
HttpURLConnection connection = openConnection(parsedUrl);
connection.setRequestMethod("POST");
addBodyIfExists(connection, params);
responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
is = connection.getInputStream();
bos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[4096];
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
String result = bos.toString();
Log.d(TAG, "request result : " + result);
return result;
}
} catch (IOException e) {
Log.e(TAG, "post: ", e);
} finally {
close(is);
close(bos);
}
return "";
}

private static void close(Closeable close) {
if (close != null) {
try {
close.close();
} catch (IOException e) {
// ignore
}
}
}

private static void addBodyIfExists(HttpURLConnection connection, JSONObject params) throws IOException {
byte[] body = params.toString().getBytes();

if (body != null) {
connection.setDoOutput(true);
connection.addRequestProperty("Content-Type", "application/json");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(body);
out.close();
}
}

private static HttpURLConnection openConnection(URL url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
int timeoutMs = 15000;
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
return connection;
}

private static String getParamsSignature(JSONObject params) throws JSONException {
StringBuilder keyBuilder = new StringBuilder();
Iterator<String> jsonKeys = params.keys();
List<String> keys = new ArrayList<>();
while (jsonKeys.hasNext()) {
keys.add(jsonKeys.next());
}
keys.sort(Comparator.naturalOrder());

for (String key : keys) {
if (params.get(key) instanceof JSONObject) {
continue;
}
Log.i(TAG, "getParamsSignature: append" + params.get(key));
keyBuilder.append(key)
.append("=")
.append(params.get(key))
.append("&");
}

keyBuilder.append(HttpUtils.VERIFY_SALT);

String keyString = keyBuilder.toString();
Log.i(TAG, "getParamsSignature: " + keyString);
byte[] keyBytes = keyString.getBytes(StandardCharsets.UTF_8);
String base64 = Base64.encodeToString(keyBytes, Base64.NO_WRAP);
return getMd5Digest(base64);
}

public static String getMd5Digest(String pInput) {
try {
MessageDigest lDigest = MessageDigest.getInstance("MD5");
lDigest.update(pInput.getBytes(StandardCharsets.UTF_8));
BigInteger lHashInt = new BigInteger(1, lDigest.digest());
return String.format("%1$032X", lHashInt);
} catch (NoSuchAlgorithmException lException) {
throw new RuntimeException(lException);
}
}
}

SignatureUtils工具类

package com.xiaomi.xsof.demo.utils;   

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

import java.security.MessageDigest;

public class SignatureUtils {
public static String getSignatureSHA256(Context context, String packageName) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
if (pi != null) {
return getPackageSHA256(pi);
}
} catch (Exception e) {
// empty
}
return "";
}

public static String getPackageSHA256(PackageInfo packageInfo) {
try {
MessageDigest localMessageDigest = MessageDigest.getInstance("SHA256");
localMessageDigest.update(packageInfo.signatures[0].toByteArray());
StringBuilder localStringBuilder = new StringBuilder();
byte[] arrayOfByte = localMessageDigest.digest();
int m = 0;
int n = arrayOfByte.length;
while (m < n) {
if (m > 0) {
localStringBuilder.append(":");
}
localStringBuilder.append(Integer.toString(256 + (0xFF & arrayOfByte[m]), 16).substring(1));
m++;
}
String str5 = localStringBuilder.toString();
return str5.toUpperCase();
} catch (Exception e) {
// ignore
}
return "";
}
}

上一篇:
下一篇: