Features Developers Pricing
Company
Request demo Log in Sign up

Reading time 0 mins

Linked-in

Encrypted Push Notifications in React Native

 

The popularity of encrypted chat and its importance today

Encrypted chat has gained immense popularity due to rising concerns over privacy and data security. With increasing cyber threats and data breaches, users and organizations prioritize secure communication to protect sensitive information from unauthorized access.

Encryption ensures that messages can only be read by the intended recipients, safeguarding against eavesdropping and hacking. This is crucial for maintaining confidentiality, particularly in sectors such as finance, healthcare, and personal communication, where privacy is paramount.

By adopting encrypted chat, individuals and businesses can communicate with confidence, knowing their data is secure.

Encrypted push notifications: an essential component

Encrypted push notifications are a critical extension of encrypted chats, ensuring that notifications containing sensitive information are also protected from interception and unauthorized access. This is essential to maintain the integrity and confidentiality of the communication channel, providing end-to-end security and peace of mind to users.

Implement encrypted push notifications in ConnectyCube

Let’s assume you have already integrated Push Notifications into your app and now you want to make it encrypted, so no one in-between can read the content. If not – follow initial step-by-step Push Notifications Guide for initial setup of push notifications.

Implementing end-to-end encryption (E2EE) for push notifications in React Native ensures the security and privacy of notification content. This guide outlines the steps to set up E2EE for React Native push notifications on iOS and Android platforms.

Recommended React Native Libraries

For ease of implementation of E2EE Push Notifications in React Native, it’s recommended to use the following libraries:

Generate Encryption Key and Subscribe to Push Notifications

Generate a random encryption key using available cryptographic functions on the device. This key will serve to both encrypt and decrypt push notifications. Obtain the device’s push token from the respective push notification service, such as APNs for iOS or FCM for Android. Utilize this token along with the generated encryption key to subscribe the device to push notifications on your server. Upon receiving encrypted push notifications, decrypt them using the stored encryption key on the device.

iOS E2EE Push Notifications

Before implementing E2EE notifications, you need to set up and connect the notification service on iOS. This includes creating a Notification Service Extension.

Creating Notification Service Extension:

  • Open your Xcode project.

  • Go to File > New > Target.

  • Choose Notification Service Extension from the list of templates and click Next.

  • Enter a name for your extension and click Finish (e.g. “MyAppNotificationService”).

  • Xcode creates a new target in your project with the necessary files for the extension.

  • Ensure to set the mutable-content property to 1 in the notification payload to enable modifying content in newly delivered notifications. For more detailed information, refer to the Apple Developer documentation.

Creating Keychain Group

To securely store an encryption key and ensure access only to authorized entities, create a Keychain Group for iOS.

  • In your Xcode project, navigate to the Capabilities tab.

  • Enable the Keychain Sharing capability.

  • Add a Keychain Group by clicking the “+” button and entering a unique identifier for your group, typically in reverse domain notation (e.g., com.example.app.MyAppNotificationKeychain).

  • Ensure that the same  Keychain Group  identifier is used in both your main app and the Notification Service Extension.

Saving Key from React Native Using iOS Keychain Group via react-native-keychain

After creating the Keychain Group on iOS, securely save encryption keys from React Native using the react-native-keychain library. Ensure that the specified Keychain Group is used both in the main app and the Notification Service Extension.

import * as Keychain from 'react-native-keychain';

...
async Keychain.setGenericPassword(
  'encryptionKeyName',
  'eb9a79ff5ab7d0ea0ce4580aaa5eb66b43d537eedf14f6e3ddb536b536a26e5c', // a generated encrypted key
  {
   accessGroup: '1A2B3C4D5E.com.example.app.MyAppNotificationKeychain', // the keychain-access-groups from "myApp.entitlements", where the "1A2B3C4D5E" is an Apple developer team ID
   service: 'NotificationKeychainService'
  }
)

Decrypting data contained in a remote notification

Upon receiving a notification request, the didReceive method is triggered. Here, the encrypted key is fetched from the Keychain using the RNKeychainBridge class. Subsequently, the encrypted notification text is decrypted using this encrypted key.

MyApp/ios/MyAppNotificationService/NotificationService.swift:

import UserNotifications
import CryptoKit
import Foundation
import Security

class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    // Modify the payload contents
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
       self.contentHandler = contentHandler
       bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent

       if let bestAttemptContent = bestAttemptContent {
           guard let encryptedKey = RNKeychainBridge.getEncryptedKey() as String?,
                 let userInfo = bestAttemptContent.userInfo as? [String: Any], // notifications data here
                 let alert = bestAttemptContent.body else { return }
           // Decrypt notification text using the encrypted key
           let decryptedText = decryptString(encryptedKey: encryptedKey, text: alert)
           // Update notification content with decrypted text
           bestAttemptContent.body = decryptedText
           // Pass the updated content to the completion handler
           contentHandler(bestAttemptContent)
       }
    }

    func decryptString(encryptedKey: String, text: String) -> String? {
         // decrypt the text using the encryptedKey
    }
}

class RNKeychainBridge {
    static func getEncryptedKey() -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: "NotificationKeychainService",
            kSecAttrAccessGroup as String: "1A2B3C4D5E.com.example.app.MyAppNotificationKeychain",
            kSecMatchLimit as String: kSecMatchLimitOne,
            kSecReturnAttributes as String: true,
            kSecReturnData as String: true
        ]

        var item: CFTypeRef?

        guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess else {
            return "Error: Failed get encryptedKey from Keychain"
        }

        guard let existingItem = item as? [String: Any],
              let keyData = existingItem[kSecValueData as String] as? Data,
              let keyString = String(data: keyData, encoding: .utf8)
        else {
            return "Error: Unable to extract encryptedKey from Keychain item"
        }

        return keyString
    }
}

Useful links:

How to create APNS certificate and How to create APNS key guides

Android E2EE Push Notifications

To decrypt push notifications before they’re displayed in the notification center, we’ll intercept and decrypt the bundle using an encrypted key stored in SharedPreferences. This key will be securely managed within the context of our application (Context.MODE_PRIVATE).

import { NativeModules } from 'react-native';

const { CryptoNotification } = NativeModules;

...
async CryptoNotification.setKeyToSharedPreferences('eb9a79ff5ab7d0ea0ce4580aaa5eb66b43d537eedf14f6e3ddb536b536a26e5c');

Manifest Setup

Add the following service declaration to your AndroidManifest.xml to extend FirebaseMessagingService for decrypting push notification data.

MyApp/android/app/src/main/AndroidManifest.xml:

<!-- Extends FirebaseMessagingService to decrypt Push Notification data -->
<service
    android:name=".cryptonotification.CryptoNotificationFCMService"
    android:exported="true">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
    </intent-filter>
</service>

Extended Service

Implement the extended service CryptoNotificationFCMService to handle incoming push notifications and decrypt them.

MyApp/android/app/src/main/java/com/myapp/cryptonotification/CryptoNotificationFCMService.kt:

package com.myapp.cryptonotification

import android.content.Intent
import android.os.Bundle
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.wix.reactnativenotifications.BuildConfig
import com.wix.reactnativenotifications.core.notification.IPushNotification
import com.wix.reactnativenotifications.core.notification.PushNotification

class CryptoNotificationFCMService : FirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage) {
        val bundle = message.toIntent().extras

        try {
            val notification = PushNotification.get(applicationContext, bundle)
            notification.onReceived()
        } catch (e: IPushNotification.InvalidNotificationException) {
            // An FCM message, yes - but not the kind we know how to work with.
        }
    }

    override fun handleIntent(intent: Intent) {
        var bundle = intent.extras

        if (bundle?.containsKey("encryptedData") == true) {
            val cryptoNotification = CryptoNotification(applicationContext)
            val newIntent = Intent(intent)
            bundle = cryptoNotification.decryptBundle(bundle)
            newIntent.putExtras(bundle)
            super.handleIntent(newIntent)
        }
    }
}

Key Management and decryption

Add the CryptoNotification class to securely store and retrieve an encryption key using Android SharedPreferences and decryption methods.

MyApp/android/app/src/main/java/com/myapp/cryptonotification/CryptoNotification.kt:

package com.myapp.cryptonotification

import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import java.util.*
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

class CryptoNotification(private val context: Context) {
    private val MY_APP_PREF = "MY_APP_NOTIFICATION_KEY_PREF"
    private val MY_APP_KEY = "MY_APP_NOTIFICATION_KEY"

    fun setStringToSharedPreferences(str: String) {
        val preferences = context.getSharedPreferences(MY_APP_PREF, Context.MODE_PRIVATE)
        preferences.edit().putString(MY_APP_KEY, str).apply()
    }

    fun getStringFromSharedPreferences(): String? {
        val preferences = context.getSharedPreferences(MY_APP_PREF, Context.MODE_PRIVATE)
        return preferences.getString(MY_APP_KEY, null)
    }

    fun decryptBundle(bundle: Bundle): Bundle {
        val encryptedKey = getStringFromSharedPreferences()

        if (!encryptedKey.isNullOrEmpty() && bundle.containsKey("gcm.notification.body")) {
            val encryptedBody = bundle.getString("gcm.notification.body")
            val decryptedBody = decryptString(encryptedKey, encryptedBody)
            bundle.putString("gcm.notification.body", decryptedBody)
        }

        return bundle
    }

    private fun decryptString(encryptedKey: String, text: String): String? {
        // decrypt the text using the encryptedKey
    }
}

React Native Module

Create a React Native module CryptoNotificationModule to expose key management functionalities to React Native.

MyApp/android/app/src/main/java/com/myapp/cryptonotification/CryptoNotificationModule.kt:

package com.myapp.cryptonotification

import android.content.Context
import com.facebook.react.bridge.*

class CryptoNotificationModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

    override fun getName(): String {
        return "CryptoNotification"
    }

    @ReactMethod
    fun setKeyToSharedPreferences(str: String, promise: Promise) {
        val appContext: Context = reactApplicationContext.applicationContext
        val cryptoNotification = CryptoNotification(appContext)
        cryptoNotification.setStringToSharedPreferences(str)
        promise.resolve(str)
    }

    @ReactMethod
    fun getKeyFromSharedPreferences(promise: Promise) {
        val appContext: Context = reactApplicationContext.applicationContext
        val cryptoNotification = CryptoNotification(appContext)
        val str = cryptoNotification.getStringFromSharedPreferences()
        promise.resolve(str)
    }
}

React Native Package

Define a React Package CryptoNotificationPackage to integrate the native module with React Native.

MyApp/android/app/src/main/java/com/myapp/cryptonotification/CryptoNotificationPackage.kt:

package com.myapp.cryptonotification

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class CryptoNotificationPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(CryptoNotificationModule(reactContext))
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

Package Integration

Add the CryptoNotificationPackage to the list of packages in MainApplication.kt or MainApplication.java.

MyApp/android/app/src/main/java/com/myapp/MainApplication.kt:

package com.myapp

...
import com.zera.zpay.cryptonotification.CryptoNotificationPackage

class MainApplication : Application(), ReactApplication {

  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
          PackageList(this).packages.apply {
            add(CryptoNotificationPackage()) // add the CryptoNotificationPackage to PackageList
          }
...

Having all the steps above completed, the push notifications implemented in the application are now encrypted.

Conclusion

Choosing the right provider can significantly ease the implementation of push notifications in your app. ConnectyCube offers detailed instructions for setting up push notifications on all platforms, including options for encryption. This is especially beneficial for apps with encrypted chats, as it ensures that notifications are secure and maintain the privacy of the communication.

Don’t wait to enhance your app’s communication security. Start integrating encrypted push notifications with ConnectyCube’s resources and ensure your users’ data remains protected. Register your free account and dive in the ConnectyCube documentation to get started now!

Links you may find helpful:

Security and Compliance

Secure chat: implementing end-to-end encryption in Web and React Native chat apps

Secure chat: implementing end-to-end encryption in Flutter chat apps

Localization support for Push Notifications

Get started for FREE

Unlock the benefits of tomorrow by signing up today.

Don't wait, take the first step towards a brighter future with us!