SafetyNet:包名总是 return null

SafetyNet: package name always return null

我正在基于 Google SafetyNet sample and SafetyNet Helper

实施 SafetyNet API

这是我的工作代码。第一部分是我在 SafetyNetSampleFragment:

处使用的处理代码
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Base64;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.safetynet.SafetyNet;
import com.google.android.gms.safetynet.SafetyNetApi;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;


public class SafetyNetVerifier implements GoogleApiClient.OnConnectionFailedListener {

    private final Random mRandom = new SecureRandom();
    private String mResult;
    private GoogleApiClient mGoogleApiClient;

    private FragmentActivity activity;

    public SafetyNetVerifier(FragmentActivity activity) {
        this.activity = activity;
        buildGoogleApiClient();
        sendSafetyNetRequest();
    }

    private byte[] getRequestNonce(String data) {
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        byte[] bytes = new byte[24];
        mRandom.nextBytes(bytes);
        try {
            byteStream.write(bytes);
            byteStream.write(data.getBytes());
        } catch (IOException e) {
            return null;
        }

        return byteStream.toByteArray();
    }

    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(activity)
                .addApi(SafetyNet.API)
                .enableAutoManage(activity, this)
                .build();
    }

    private void sendSafetyNetRequest() {
        Log.e("hqthao", "Sending SafetyNet API request.");

        String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
        byte[] nonce = getRequestNonce(nonceData);

        SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
                .setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {

                    @Override
                    public void onResult(SafetyNetApi.AttestationResult result) {
                        Status status = result.getStatus();
                        if (status.isSuccess()) {
                            mResult = result.getJwsResult();
                            Log.e("hqthao", "Success! SafetyNet result:\n" + mResult + "\n");
                            SafetyNetResponse response = parseJsonWebSignature(mResult);
                            Log.e("hqthao", response.toString());
                        }
                    }
                });
    }

    @Nullable
    private SafetyNetResponse parseJsonWebSignature(String jwsResult) {
        if (jwsResult == null) {
            return null;
        }
        //the JWT (JSON WEB TOKEN) is just a 3 base64 encoded parts concatenated by a . character
        final String[] jwtParts = jwsResult.split("\.");

        if (jwtParts.length == 3) {
            //we're only really interested in the body/payload
            String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT));

            return SafetyNetResponse.parse(decodedPayload);
        } else {
            return null;
        }
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.e("hqthao", "Error connecting to Google Play Services." + connectionResult.getErrorMessage());
    }

}

这是我从 SafetyNetResponse 复制的模型 SafetyNetResponse:

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

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

import java.util.Arrays;

public class SafetyNetResponse {

    private static final String TAG = SafetyNetResponse.class.getSimpleName();
    private String nonce;
    private long timestampMs;
    private String apkPackageName;
    private String[] apkCertificateDigestSha256;
    private String apkDigestSha256;
    private boolean ctsProfileMatch;
    private boolean basicIntegrity;

    //forces the parse()
    private SafetyNetResponse() {
    }

    /**
     * @return BASE64 encoded
     */
    public String getNonce() {
        return nonce;
    }

    public long getTimestampMs() {
        return timestampMs;
    }

    /**
     * @return com.package.name.of.requesting.app
     */
    public String getApkPackageName() {
        return apkPackageName;
    }

    /**
     * SHA-256 hash of the certificate used to sign requesting app
     *
     * @return BASE64 encoded
     */
    public String[] getApkCertificateDigestSha256() {
        return apkCertificateDigestSha256;
    }

    /**
     * SHA-256 hash of the app's APK
     *
     * @return BASE64 encoded
     */
    public String getApkDigestSha256() {
        return apkDigestSha256;
    }


    /**
     * If the value of "ctsProfileMatch" is true, then the profile of the device running your app matches the profile of a device that has passed Android compatibility testing.
     *
     * @return
     */
    public boolean isCtsProfileMatch() {
        return ctsProfileMatch;
    }

    /**
     * If the value of "basicIntegrity" is true, then the device running your app likely wasn't tampered with, but the device has not necessarily passed Android compatibility testing.
     *
     * @return
     */
    public boolean isBasicIntegrity() {
        return basicIntegrity;
    }

    /**
     * Parse the JSON string into populated SafetyNetResponse object
     *
     * @param decodedJWTPayload JSON String (always a json string according to JWT spec)
     * @return populated SafetyNetResponse
     */
    @Nullable
    public static SafetyNetResponse parse(@NonNull String decodedJWTPayload) {

        Log.d(TAG, "decodedJWTPayload json:" + decodedJWTPayload);

        SafetyNetResponse response = new SafetyNetResponse();
        try {
            JSONObject root = new JSONObject(decodedJWTPayload);
            if (root.has("nonce")) {
                response.nonce = root.getString("nonce");
            }

            if (root.has("apkCertificateDigestSha256")) {
                JSONArray jsonArray = root.getJSONArray("apkCertificateDigestSha256");
                if (jsonArray != null) {
                    String[] certDigests = new String[jsonArray.length()];
                    for (int i = 0; i < jsonArray.length(); i++) {
                        certDigests[i] = jsonArray.getString(i);
                    }
                    response.apkCertificateDigestSha256 = certDigests;
                }
            }

            if (root.has("apkDigestSha256")) {
                response.apkDigestSha256 = root.getString("apkDigestSha256");
            }

            if (root.has("apkPackageName")) {
                response.apkPackageName = root.getString("apkPackageName");
            }

            if (root.has("basicIntegrity")) {
                response.basicIntegrity = root.getBoolean("basicIntegrity");
            }

            if (root.has("ctsProfileMatch")) {
                response.ctsProfileMatch = root.getBoolean("ctsProfileMatch");
            }

            if (root.has("timestampMs")) {
                response.timestampMs = root.getLong("timestampMs");
            }

            return response;
        } catch (JSONException e) {
            Log.e(TAG, "problem parsing decodedJWTPayload:" + e.getMessage(), e);
        }
        return null;
    }


    @Override
    public String toString() {
        return "SafetyNetResponse{" +
                "nonce='" + nonce + '\'' +
                ", timestampMs=" + timestampMs +
                ", apkPackageName='" + apkPackageName + '\'' +
                ", apkCertificateDigestSha256=" + Arrays.toString(apkCertificateDigestSha256) +
                ", apkDigestSha256='" + apkDigestSha256 + '\'' +
                ", ctsProfileMatch=" + ctsProfileMatch +
                ", basicIntegrity=" + basicIntegrity +
                '}';
    }
}

我们可以通过在activity:

中调用这行代码来轻松调用上面的可行代码
    new SafetyNetVerifier(this);

结果是:

SafetyNetResponse{
 nonce='Xc4dSnAjAqf9KWDZokwK2TdBw9Td+ZILU2FmZXR5IE5ldCBTYW1wbGU6IDE0ODcxODQyMjYwNjc=', 
timestampMs=1487184225994, 
apkPackageName='null', 
apkCertificateDigestSha256=[], 
apkDigestSha256='null', 
ctsProfileMatch=false, 
basicIntegrity=false
}

作为正确解析的时间戳。我想我已成功获得安全网响应。但我不知道为什么 apkPackageName 总是空的,而我显示的其他字段是空的。请帮助我。

在您的 SafetyNetResponse 对象中,您会注意到 basicIntegrity 是错误的。这表明检测到某种系统篡改其他修改(root 就是其中一个例子)。

这提供了有关为什么缺少 APK 信息字段的线索。如 documentation:

中所述

The apkPackageName, apkCertificateDigestSha256, and apkDigestSha256 fields provide information about the APK that you can use to verify the identity of the calling app. These fields are absent if the API cannot reliably determine the APK information.

您的代码似乎工作正常。您可以通过在未修改的设备上测试来验证这一点 运行 已批准的 Android 版本 - 然后应该包含缺少的信息。