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:
- react-native-keychain to store encryption key
- react-native-notifications (or @react-native-firebase/messaging, @react-native-community/push-notification-ios) to generate Encryption Key and subscribe to Push Notifications
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:
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