我正在基于Google SafetyNet sample和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 +
'}';
}
}
通过在活动内调用以下代码,我们可以轻松地调用上述可行代码:
new SafetyNetVerifier(this);
结果是:
SafetyNetResponse{
nonce='Xc4dSnAjAqf9KWDZokwK2TdBw9Td+ZILU2FmZXR5IE5ldCBTYW1wbGU6IDE0ODcxODQyMjYwNjc=',
timestampMs=1487184225994,
apkPackageName='null',
apkCertificateDigestSha256=[],
apkDigestSha256='null',
ctsProfileMatch=false,
basicIntegrity=false
}
时间戳正确解析。我认为我已经成功获得Safetynet响应。但是我不知道为什么
apkPackageName
总是为null,而我显示的其他字段为空。请帮我。 最佳答案
在您的SafetyNetResponse
对象中,您会注意到basicIntegrity
为false。这表明已检测到某种类型的系统篡改其他修改(生根就是一个例子)。
这提供了有关为何缺少APK信息字段的线索。如documentation中所述:
apkPackageName
,apkCertificateDigestSha256
和apkDigestSha256
字段提供有关APK的信息,可用于验证调用应用程序的身份。如果API无法可靠地确定APK信息,则这些字段将不存在。
您的代码似乎正常工作。您可以通过在运行经批准的Android版本的未修改设备上进行测试来验证这一点-然后应包含缺少的信息。