Extended Guide to SafetyNet
April 17th, 2019 | By Karan Gandhi | 7 min read
SafetyNet is a set of APIs provided by Google to safeguard Android apps against device tampering and harmful apps. Currently, the SafetyNet API provides Attestation, Safe Browsing, reCAPTCHA and Verify Apps API. This set of APIs requires Google Play Services in order to function correctly.
SafetyNet Attestation is the API commonly associated with SafetyNet. The attestation API checks the device integrity and allows us to filter the apps. Additionally, we can use the attestation API to verify client integrity. You can see SafetyNet implemented in apps like Android Pay, Netflix, and Pokemon Go.
In this article, we will cover SafetyNet in detail, including its integrations with Ionic, NativeScript and React Native. Also, we will be checking out the server-side code with NodeJS.
Android users can root their device. By doing so, they grant apps which use root privileges a deep level of access to the Android OS System. As a result, rooted devices can circumvent the security laid out by the OS developer.
Also, Android users can install custom ROMs on their devices. Custom ROMs are firmware created by the community based on Android's AOSP project or a rebranded manufacturer ROM. Usually, Custom ROMs have root privileges too.
Having root privileges puts the security of a device in the hands of an end user. As app developers, we would like to avoid such a situation.
Android apps are distributed in app stores and via websites. We would like to make sure that interaction between the app and the server is made by the legitimate app that we distributed. Once a file has been tampered with, its SHA hash changes. Using this bit of knowledge, we can easily verify the app’s integrity.
SafetyNet is a part of Google Play Services which are installed in all Google certified devices. When an app requests a SafetyNet attestation, the service gathers device details, compares with the device’s ctsProfile (which we’ll explain below) and provides the calling app with the response. Using the response, we can deduce whether:
The calling app has been tampered with.
The calling app is running on a rooted device.
The calling app is running on a firmware which has not passed the manufacturer’s CTS (Compatibility Test Suite); usually, custom ROMs and beta firmware don't pass the CTS.
While SafetyNet should ideally run on any app to ensure its integrity, it’s especially important in applications that handle sensitive user data or perform important operations. This includes:
Apps conforming to standards like PCI - Mobile / PSD2 / HIPAA
Banking Apps / Crypto Wallets / Payment Wallets
Games
E-Commerce Storefronts
Chat Clients
SafetyNet can be bypassed by some tools like Magisk Hide or SU Hide. It's advisable to use SafetyNet in conjunction with other root detecting tools like RootBeer or JailMonkey.
There are also some limits set in place. Specifically, a limit of 10000 requests per day per app (which can be extended upon request) and a hard limit of 5 requests per app per minute.
Also, an alternative course of action should be in place if there is an API outage. This actually happened recently.
The recommended method to implement attestation is:
Generate nonce
Request attestation response using API key/nonce
Transfer the JWS response from the device to the server
Validate the response
Execute relevant logic based on the response
This method is useful when performing a spot check while executing a critical functionality like placing an order or transferring funds. The nonce can be used as an additional check.
Some helper libraries generate a nonce and validate the response on the app side. The workflow for such libraries can be:
Generate nonce
Request and verify attestation response
Execute relevant logic based on the response
This method is useful if you are performing a check on app boot or while logging the user in.
We can obtain the API key from the Google Developer Console. Enable Android Device Verification in your existing or new project. The API key is available in the Credentials section. We can restrict API usage by specifying package names & SHA-1 certificate fingerprints.
A decoded response received from the attestation API is below:
{
"nonce": "YWc=",
"timestampMs": 1553088490946,
"apkPackageName": "app.cordova.safetynettest",
"apkDigestSha256": "IUhOmEabdvHmLmIk5DD5fdfs6OPHUwAA+uGgYDVrcsU=",
"ctsProfileMatch": true,
"apkCertificateDigestSha256": [
"EmGH9u67SiSyLuvZCoAN+R+NU/yHP29gSmoUgvNtehk="
],
"basicIntegrity": true
}
Let’s go over the meaning of each item:
nonce: base64 encoded nonce string we originally pass to the attestation method.
apkPackageName: package name of the calling app.
apkDigestSha256: base64 encoded SHA256 hash of the APK.
apkCertificateDigestSha256: base64 encoded SHA256 hash of the signing certificates used.
ctsProfileMatch: Every device firmware which has Google Play has to pass CTS. The CTS profile match is used to identify the profiles which have passed CTS. In general, apps used on manufacturer OS usually have CTS true. Usually, this is false if:
The bootloader is unlocked
The device is running an official Beta Firmware
The device is running custom ROM like LineageOS
basicIntegrity has failed
basicIntegrity: Basic Integrity is a root/emulator check. It returns false if:
The device is rooted
THe app is running on an emulator
There is no device (protocol emulating script)
There are signs of attacks like API hooking.
timestampMs: This is a timestamp generated by Google’s servers when the attestation response was generated.
To ensure device integrity, we require ctsProfileMatch and basicIntegrity to be true. Similarly, to ensure application integrity, apkPackageName, apkDigestSha256, and apkCertificateDigestSha256 must match our original app. Additional checks can be introduced using a nonce.
Let's check some plugins to integrate SafetyNet in mobile apps.
You can use SafetyNet in Ionic/Cordova apps using the SafetyNet plugin. The attest function accepts nonce and API_Key as parameters. On callback success, a JWS token can be obtained. Apart from attestation, methods for Verify Apps and list harmful apps are also available.
To install this plugin, you should use the command below:
cordova plugin add cordova-plugin-android-safetynet
Use the plugin as below:
window.safetynet.attest(nonce ,API_Key ,function(success) {
console.log(success);
} , function(error){
console.error(error);
});
NativeScript's SafetyNet helper plugin uses a SafetyNet library. The plugin handles nonce generation, decodes and validates the response captured from the SafetyNet API. The request method accepts API_Key as a parameter.
To install this plugin, you should use the command below:
tns plugin add nativescript-safetynet-helper
Use the plugin as below:
import { SafetyNetHelper } from 'nativescript-safetynet-helper';
let helper = new SafetyNetHelper(this._apiKey);
helper.requestTest((err, result) => {
if (err) {
console.log(err);
return;
}
console.log(`Basic Integrity - ${result.basicIntegrity}, CTS Profile Match - ${result.ctsProfileMatch}`)
});
N.B.: The library uses an older version of SafetyNet.
You can use SafetyNet in React Native with a SafetyNet Plugin. The plugin has separate methods for checking Play Services, generating a nonce, calling attestation API, and verifying the attestation response. The attestation method accepts nonce and API_Key as parameters.
To install this plugin, you should use the command below:
npm install react-native-google-safetynet --save
react-native link react-native-google-safetynet
Use the plugin as below:
import RNGoogleSafetyNet from 'react-native-google-safetynet';
RNGoogleSafetyNet.generateNonce(LENGTH).then((res) => {
/// Generate nonce of length LENGTH. Result is nonce
});
RNGoogleSafetyNet.sendAttestationRequest(nonce, API_KEY).then((res) => {
// Decoded JSON response
});
RNGoogleSafetyNet.verifyAttestationResponse(nonce, JSON_RESPONSE).then((ver) => {
/// Verifies JSON response. Result is a boolean
});
This is a walkthrough for preparing a NodeJS instance for SafetyNet Attestation. You can use this for all types of Android apps. On the server, we need to carry out these tasks:
Generate a nonce token
Verify the JWS response
Parse the JWS token
Depending on our use case, we can use a cryptographic random number, or partial device and server nonce.
Using a cryptographic random number is one of the simplest methods to generate a nonce. We will use the crypto module of NodeJS to create a 32-byte nonce:
const crypto = require('crypto');
let nonce = crypto.randomBytes(32).toString('base64');
As for partial device and server nonce, this can vary from case to case. As a general rule:
Generate a server hash with timestamp + unique identifier + some random data
Generate a device hash with userid + deviceid
Combine the hashes to form a nonce
This requires quite a bit of effort to implement. Depending on the usable data of the nonce, you would reduce the risk of replay attacks on your app.
/*Server Side*/
const crypto = require('crypto');
let timestamp = new Date().getTime();
let timeStampHash = crypto.createHash('sha256').update(timestamp).digest('hex');
let order = '1010101010';
let orderHash = crypto.createHash('sha256').update(order).digest('hex');
let nonceServer = timeStampHash + '.' + orderHash
/*Client Side*/
let userHash = 'userHash';
let deviceHash = 'deviceHash';
let serverHash = nonceServer;
let nonce = userHash + '.' + deviceHash + '.' + serverHash;
A random number nonce is useful if you are performing a check at:
App boot
Login
Scheduled check
Such nonce is useful while performing a spot check like transferring currency, placing an order, or executing a critical piece of code.
Now, let's verify the JWS response with Google servers. We will be using axios to send an HTTP request to verify the JWS message.
npm i axios --save
axios.post('https://www.googleapis.com/androidcheck/v1/attestations/verify?key=API_Key', {
"signedAttestation": JWS_MESSAGE
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
The signature verification is validated with the isValidSignature key.
To decode the JWS, we will be using the jws module.
npm install jws --save
const jws = require('jws');
let decodedJSON = jws.decode(req.body.jws);
In this article, we covered the benefits and challenges of implementing SafetyNet, its integrations with JavaScript App frameworks, and its server-side integration with NodeJS.
Using SafetyNet to validate application and device integrity is especially important for apps which handle sensitive user data, such as processing payments, private information, or Personally Identifiable Information (PII).
Lastly, if you're developing your mobile app using JavaScript, don't forget that your code will be completely readable by any end-user and someone can redistribute your app or tamper with it. The best way to prevent this is by protecting your code with several layers. Start your free Jscrambler trial and protect your source code today!
Jscrambler
The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.
View All Articles