Extended Guide to SafetyNet
April 17th, 2019 | By Karan Gandhi | 7 min read
SafetyNet is a set of APIs 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 to function correctly.
SafetyNet Attestation is the API commonly associated with SafetyNet. The attestation API checks the devices' integrity and allows us to filter the apps. Additionally, we can use the attestation API to verify client integrity. SafetyNet can be implemented in Android Pay, Netflix, Pokemon Go, and other apps.
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.
Device and Client Integrity
Android users can root their devices.
By doing so, they grant apps that 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 Androids' 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. It is mandatory to ensure that interaction between the app and the server occurs in the legitimate app.
Once a file has been tampered with, its SHA hash changes. Using this knowledge, we can easily verify the apps' integrity.
SafetyNet is a part of Google Play Services installed in all Google-certified devices. When an app requests a SafetyNet attestation, the service gathers device details, compares them with the devices' ctsProfile (which we will explain below), and provides the calling app with 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 firmware that has not passed the manufacturers' CTS (Compatibility Test Suite); usually, custom ROMs and beta firmware don't pass the CTS.
Who should use SafetyNet?
While SafetyNet should ideally run on any app to ensure its integrity, it matters in applications that handle sensitive user data or perform vital operations. This includes:
Apps conforming to standards like PCI - Mobile / PSD2 / HIPAA
Banking Apps / Crypto Wallets / Payment Wallets
E-Commerce Storefronts
Chat Clients
Limitations
SafetyNet can be bypassed by tools like Magisk Hide or SU Hide.
Use SafetyNet in conjunction with other root-detecting tools like RootBeer or JailMonkey.
There are also limits set in place: 10,000 requests per day per app (which can be extended upon request) and a hard limit of 5 app requests per minute.
Also, an alternative course of action should be in place if there is an API outage.
Using SafetyNet
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 necessary 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.
Obtaining the API key
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.
Understanding Attestation Response
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 passed 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 that 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.
Integrating SafetyNet on Cross-Platform Mobile Apps
Let's check some plugins to integrate SafetyNet into mobile apps.
Cordova and Ionic 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 verifying apps and listing 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 below:
window.safetynet.attest(nonce ,API_Key ,function(success) {
console.log(success);
} , function(error){
console.error(error);
});
NativeScript Apps
NativeScript's SafetyNet helper plugin uses a SafetyNet library at Github.
The plugin handles nonce generation, and 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 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.
React Native Apps
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 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
});
Preparing NodeJS Server for Attestation
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 user-id + device
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.
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);
Conclusion
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 that 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 ArticlesMust read next
JavaScript Obfuscation: The Definitive Guide for 2024
JavaScript obfuscation is a series of code transformations that make JS extremely hard to understand and reverse-engineer. This definitive guide explores it in-depth.
January 30, 2024 | By Jscrambler | 18 min read
10 Classic Games Recreated in JavaScript
Childhood memories associated with video games can be revived with the help of JavaScript. Fall into nostalgia and find out more!
May 17, 2022 | By Jscrambler | 4 min read