Web Development

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

  • Games

  • 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 Articles

Must read next

Javascript Cybersecurity

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

Javascript

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

Section Divider