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