可信设备SDK接入指南更新时间:2025-08-22 15:46:00
一、集成SDK
1、添加AAR
添加小米安全开放服务SDK到项目Module(app)的libs路径中
SDK文件:
- 名称:sdk-xsof-20250731-v1.0.3-release.aar
- 地址:https://kpan.mioffice.cn/webfolder/ext/Gi20aypUKX3%24uVm31GQvyw%40%40?n=0.5882221379068245
- 密码:8ho8

注:图片仅供参考,以实际版本号为准
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);  
    }  
}初始化后即可使用小米安全开放服务提供的各项服务。
目前小米安全开放服务支持三项检测:
- 设备可信检测
- 恶意应用检测
- 模拟点击检测
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 "";   
    }   
}上一篇:安全相机开发指南
下一篇:可信设备SDK后台使用指南
文档内容是否有帮助?
有帮助
无帮助