Matomo

React Native libraries: Security considerations | Cossack Labs

đŸ‡ș🇩 We stand with Ukraine, and we stand for Ukraine. We offer free assessment and mitigation services to improve Ukrainian companies security resilience.

List of blogposts

React Native libraries: Security considerations

React Native is a cross-platform framework that allows developers to write native mobile applications using JavaScript. Supporting multiple platforms means dealing with each platform’s issue (React Native, iOS, Android). Not long ago, we described security challenges in React Native apps from an app architecture perspective.

Unfortunately, React Native ecosystem brings the JavaScript dependency hell into the mobile application world, and we often see issues in third party libraries that drastically affect the security of the main application, and not in a particularly good way.

But trust no word of a stranger, let’s take a look together!


  1. React Native guides recommend insecure libraries
  2. React Native libraries: Improper platform usage
  3. Easy-to-misuse crypto APIs in React Native
  4. React Native security code: Why avoid the reactive approach
  5. React Native security: What developers need to know
  6. How to select a good cryptographic library
  7. How to keep your dependencies secure and up-to-date
  8. Conclusions

1. React Native guides recommend insecure libraries #

Security issues in open-source libraries are a hot topic these days.

On the one hand, developers are regular people who can make mistakes, even of a critical severity. For example, as it happened with the Log4j library.

On the other hand, someone may intentionally break a commonly used library causing lots of troubles for many companies. Imagine if it happens to a security-related or cryptographic library.

Examples in this article will show you that you probably don’t even have to imagine that. The React Native world is riddled with scary things in cryptographic code.

rn crypto hell this is fine cossack labs

It’s a common thing among JavaScript developers to use open-source libraries to save development time and effort. React Native documentation recommends plenty of community supported libraries for various features, including security and cryptography libraries.

The Official React Native documentation recommends multiple community libraries for secure data storage using encryption:

React Native Keychain #

react-native-keychain configured incorrectly, it may use a Facebook-conceal library for encrypted storage, abandoned in 2017. Also, it uses AES in CBC mode which opens a “null IV” threat vector and generally is not the best option.

React Native Sensitive Info #

react-native-sensitive-info library may use AES in CBC mode re-using the same IV which results in weak cryptographic protection and opens a possibility of attacks similar to BEAST.

React Native Encrypted Storage #

react-native-encrypted-storage uses questionable default accessibility options for Keychain items making them available for devices without passcodes. Moreover, the library doesn’t allow changing the accessibility options for more secure and suitable, and doesn’t provide biometry support.

Expo SecureStore #

expo-secure-store makes your app dependent on Expo modules and design approaches which may not fit the app architecture. We’ve outlined the security concerns about Expo SecureStore in our React Native Security article

The security issues inside each library are not critical themselves, but these are the libraries in the official list! They should satisfy at least common platform security and cryptographic requirements and Apple / Google best practises. For example, Android documentation recommends using AES in GCM mode instead of CBC.


2. React Native libraries: Improper platform usage #

Many React Native libraries are ported from the JavaScript ecosystem. The train of thought is understandable: If the library is written in JS, why not wrap it as a RN package. However, many of these libraries were created for the web frontend or web backend (Node.js) platforms and are not suitable for mobile apps.

Mobile platforms have their unique features: access to hardware, sandboxing, touch gesture recognition, attention to accessibility and battery life, and iOS / Android OS specifics. Every mobile library should be “created for mobile”. Porting libraries blindly regardless of mobile specifics could lead to security risks. OWASP recognises “M1 Improper platform usage” as #1 security risk for mobile apps.

At Cossack Labs we mastered improving mobile applications security from designing end-to-end encrypted protocols to DRM-like protections for ML models.

Not suitable CPRNG #

mvayngrib/react-native-crypto library is a good (and scary) example to explore. It’s typically used for cryptography-related matters, as name implies: It provides API for encryption, hashing, and generation of cryptographically strong pseudo random variables.

However, the underlying CPRNG is not a good fit for mobile applications and can lead to predicting the random values and rendering encryption less secure. Unfortunately, developers might not be aware of the problem because nothing looks suspicious at the first glance.

Read a line from a real React Native application that uses mvayngrib/react-native-crypto:

const secret = generateRandomValues();

What is wrong here?

In our article Crypto wallets security as seen by security engineers, we researched the dependency tree and found that the line above uses CPRNG of a pure JavaScript library SJCL. It’s a legacy library created for web apps that collects entropy relying on things like “mouse movements” and “keyboard presses”.

Of course, mobile devices don’t have a mouse and keyboard in a way as desktop devices do. Using SJCL might lead to generating low entropy, potentially predictable, secrets. It’s an unsuitable choice for mobile applications nowadays.

Mobile apps can access the mobile OS and have more capabilities than browsers. Generally, we recommend using the platform‘s native CPRNGs: CommonCrypto SecRandomCopyBytes for iOS and Java SecureRandom for Android. Both are optimised for mobile devices and take into account all platform’s features.

If you want to use React Native libraries for security-sensitive functionality, we strongly recommend researching the library and its dependencies to ensure that it’s suitable for mobile.

The story continues… #

While looking at the indirect dependencies of the recent versions of tradle/react-native-crypto you can spot the crypto-browserify/randomfill library. It can be used to generate random values, including encryption keys.

Follow the idea. crypto-browserify/randomfill depends on crypto-browserify/randombytes. The last one is described as “randombytes from node that works in the browser”. Similarly to SJCL, it doesn’t look like a proper tool for mobile devices.

Also, crypto-browserify/randombytes utilises web browser API crypto.getRandomValues. The irony is that a comment in a source code has a link to the documentation that says not to use crypto.getRandomValues to generate encryption keys.

Many libraries that depend on crypto-browserify/randombytes do not mention that randomBytes() is web-oriented and should not be used for key generation.


3. Easy-to-misuse cryptographic APIs in React Native #

The next issue awaits RN app developers when they start using the selected cryptographic libraries.

The truth is that many libraries are created by people who don’t specialise in designing good cryptographic APIs.

The study Why does cryptographic software fail? shows that lots of developers introduce security issues in their apps by misusing the cryptographic libraries.

Many previous generation crypto libraries (think of OpenSSL) were designed for crypto engineers but are used by people who don’t have much experience in crypto. New gen libraries (think of Themis, LibSodium, Tink, CryptoKit) are initially designed to be easy-to-use for people without crypto experience.

Many JavaScript libraries still provide easy-to-misuse API and docs. No blame, but this bcrypt package is a good example:

react native libraries security: nodejs docs are sometimes not helpful - cossack labs

Bcrypt API docs pointed out by @eleanor on Twitter. For those who don’t get frustrated: bcrypt is a hashing algorithm, it doesn’t “encrypt” the data but hashes it.

Unexpected params #

Let’s take a look at another popular React Native cryptographic library: react-native-aes-crypto.

Developers might use it to generate a hash from the user’s password by using PBKDF2.

const hash = await NativeModules.Aes.pbkdf2(password, salt, 10000, 256);

PBKDF2 is a password-based key derivation function that takes low entropy password and salt as input params, then performs X rounds of hashing operation. The number of rounds depends on the desired security guarantees.

According to the latest NIST recommendations and OWASP guidelines, PBKDF2 should be used with 120k-720k rounds depending on the underlying digest function. In the target application the number of rounds is significantly lower than recommended—10000, but that’s a separate issue.

PBKDF2 can use several digest functions: HMAC-SHA1, HMAC-SHA256, HMAC-SHA512.

Looking at the line above, a reader might think that “256” means that PBKDF2 is configured to use HMAC-SHA256. But it doesn’t.

Let’s take a look at the method definition in react-native-aes-crypto (see index.d.ts#L5):

function pbkdf2(password: string, salt: string, cost: number, length: number): Promise<string>;

The parameters are: password, salt, number of rounds, output length. The last param, the output length, equals “256” in the target application.

The digest function is actually hardcoded inside the react-native-aes-crypto and set to HMAC-SHA512 for both iOS (using native CommonCrypto, AesCrypt) and Android (using SpongyCastle library, RCTAes.java#L167).

Take a look at the iOS code:

+ (NSString *) pbkdf2:(NSString *)password salt: (NSString *)salt cost: (NSInteger)cost length: (NSInteger)length {
    // Data of String to generate Hash key(hexa decimal string).
    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
    NSData *saltData = [salt dataUsingEncoding:NSUTF8StringEncoding];

    // Hash key (hexa decimal) string data length.
    NSMutableData *hashKeyData = [NSMutableData dataWithLength:length/8];

    // Key Derivation using PBKDF2 algorithm.
    int status = CCKeyDerivationPBKDF(
                    kCCPBKDF2,
                    passwordData.bytes,
                    passwordData.length,
                    saltData.bytes,
                    saltData.length,
                    kCCPRFHmacAlgSHA512,
                    cost,
                    hashKeyData.mutableBytes,
                    hashKeyData.length);

The output length is divided by 8, meaning that originally the length param requires bits, not bytes. The target application has a length of 256, resulting in the 32 (256 / 8) bytes, or 64 hexa symbols.

So, the developers might be tricked into thinking that they control the digest function via a parameter that actually controls the length of the output.

The length of the output also brings confusion. First, it’s in bits, not bytes (developers familiar with the iOS ecosystem could be confused because CCKeyDerivationPBKDF works with bytes). The only way to learn about bits is to read the library’s iOS or Android code, or the docs for BouncyCastle generateDerivedParameters method. The library doesn’t check the length param and could cause the division by zero error.

Being crypto library designers ourselves, we recommend providing either (1) a high-level API, hardcoding the digest and the output length, or (2) a low-level API, making the digest function a parameter too. Regardless of options, all params should be documented.

Our recommendation for developers is: Brace yourselves, reading docs and code of the libraries you are adding to your projects.


We help companies with mobile security engineering and end-to-end encryption. Read how we secured notes in Bear iOS and macOS apps using Themis:


Useless params #

Here is another example of the questionable API design of the react-native-aes-crypto library. This time the target application used the library to encrypt piece of data:

const encrypt$ = (value: string, key: string, iv: string) =>
  from<string>(NativeModules.Aes.encrypt(value, key, iv, 'aes-256-cbc'));

This call is defined as the following in the index.d.ts#L6 of react-native-aes-crypto:

type Algorithms = 'aes-128-cbc' | 'aes-192-cbc' | 'aes-256-cbc'

function encrypt(text: string, key: string, iv: string, algorithm: Algorithms): Promise<string>

Under the hood, this call translates into two different calls: Using CommonCrypto for iOS (see AESCrypt.m) and using javax.crypto for Android (see RCTAes.java).

The iOS version respects the “algorithm” value, the Android version fully ignores it and completely depends on the length of the provided key (see RCTAes.java#L58):

public void encrypt(String data, String key, String iv, String algorithm, Promise promise) {
    try {
        String result = encrypt(data, key, iv);
        promise.resolve(result);

This means that while the React Native code has the same line, iOS and Android apps might behave differently.

AES-256-CBC settings doesn’t affect the cipher selection for Android apps but affects iOS apps. Developers should pay attention to this while writing code comments and unit tests—to ensure no sudden change in behaviour.

As the Android app cipher choice depends on the key length, generated on the previous steps, developers should test the whole “key generation -> encryption -> decryption” chain. The change of the key generation method might change the key length, making iOS and Android encryption methods incompatible.


4. React Native security controls: Why avoid the reactive approach #

Many developers follow a reactive approach when creating React Native applications. In general, it has its benefits and downsides. It has a non-linear execution of code that increases its cyclomatic complexity and cognitive complexity. Debugging becomes a challenge with non-useful stack traces, breakpoints, and variables that frequently appear to be out of scope. Reactive code might be difficult to maintain.

When developers choose to implement security controls using the reactive approach, the resulting logic may be hard to follow. For example, it’s a common feature that the application stays locked until the user enters the password.

In the reactive Java code snippet below, the password is stored in passwordPublisher. Each time the user enters the password, it goes to the passwordPublisher. Then, if the password is correct–the app is unlocked. Otherwise, it stays locked.

react native libraries security: complicated to support reactive code - cossack labs

Reactive cryptographic code might be difficult to test and maintain.

The logic seems to be pretty straightforward unless you need to extend password-related code. For example, if you need to lock the app after the timeout, you are to pass an incorrect password value into the passwordPublisher. And what about password change functionality? It doesn’t look straightforward anymore and goes against KISS design principle.

Often, developers apply the reactive approach to security controls because they use a similar approach across the app. However, it’s not always a good idea. Implementing security controls without heavily relying on the reactive approach may save developers many hours of debugging and fixing potential vulnerabilities.


5. React Native security: What developers need to know #

Instead of enumerating all security issues in all libraries, let’s take a look at the whole picture.

Our team is working on improving the security of applications that deal with sensitive data. For example, applications that operate on the user’s financial resources. These apps require a higher code quality and security baseline.

The following real-world example (that looks like an common one for us) demonstrates the current state of React Native app security.

We’ve analysed the dependencies (100+ direct dependencies with 16+ crypto libraries), and the average results across projects are disturbing.

While the apps operate with highly sensitive data, they still have high severity vulnerabilities in their dependencies. For example, 12.6% of the examined libraries have known vulnerabilities. 17.2% look abandoned: they do not have docs or commit activities for more than a year. 16.1% have more than 100 opened issues and no tests, while 2.3% even more than 500 unresolved issues. 2.3% of libraries are supported just by a few contributors (in some repos we counted less than 5 ones), that don’t look good from a future maintenance perspective.

react native libraries security: good and problematic libraries - cossack labs

Good and problematic dependencies of one typical fintech app.

A lot of green colour on the diagram should not confuse you: 50% of “good dependencies” is still pretty bad. Another half—the abandoned dependencies with lots of issues or just a few contributors—are the first candidates to become vulnerable.

Take a closer look at the cryptographic dependencies that are meant to protect the most valuable data, and the situation becomes even worse. 25% of them look abandoned, while 37.9% contain already known vulnerabilities and 12.5% count more than 100 reported and unresolved issues.

react native libraries security: bad cryptographic libraries - cossack labs

Good and problematic cryptographic dependencies of one typical fintech app.

Let’s see some examples of the libraries with known issues and commonly used across a large number of React Native applications:

Deprecated or abandoned dependencies #

The JavaScript and React Native communities move fast: libraries appear, get adoption, stabilise, and slowly degrade. It’s a typical lifecycle: if an open-source library is driven by one person, sooner or later, that person might become tired of maintaining it.

Even when the application developers are actively working on the project, it doesn’t mean that they have planned enough time to update all the dependencies. A large number of dependencies in the project lead to a constant need for updates. Our statistics show that typically ~35% of apps’ dependencies are already outdated.

Abandoned react-native-level-fs #

The react-native-level-fs library that manages file storage in the native filesystem is a perfect example to showcase that the dependency management problem is widespread and it’s common to use outdated and vulnerable libraries.

react-native-level-fs had only three contributors and they just stopped supporting it in 2019. One high and two medium vulnerabilities were reported for the latest version and there’s no chance anyone will fix them.

The scary news is that right now it has nearly 6000 weekly downloads, and it looks like a typical thing in the React Native ecosystem.

Abandoned SpongyCastle #

Let’s talk about react-native-aes-crypto library again. The above-mentioned PBKDF2 from this library relies on the SpongyCastle library on Android, see RCTAes.java#L167.

SpongyCastle is a famous BouncyCastle library repacked for Android. Its latest release was on 30 Aug 2017. SpongyCastle hasn’t been updated for three years, has 30 open issues, including CVE-2019-17359 that was fixed in the newer versions of BouncyCastle.

But the most important thing is that SpongyCastle is not supported anymore.

BouncyCastle itself is considered deprecated on Android platform since 2018: The Bouncy Castle implementations of many algorithms are deprecated.

When react-native-aes-crypto library was founded, SpongyCastle was still an active library, so it was an understandable choice. But 4 years are a long term in a world of an always-evolving ecosystem.

The PBKDF2 is a crucial function used to derive hashes from the user passwords. Having PBKDF2 source from the old & outdated library puts at risk the main sensitive assets of the app.

Abandoned ed25519-hd #

Another example of a quite popular but abandoned crypto library is ed25519-hd-key. That’s an implementation of key derivation according to the SLIP-0010 protocol.

For the last year nothing has been going on with this library, only lonely DependaBot PRs asking to update the outdated dependencies.

Generating master keypair is the root security procedure in non-custodial crypto wallet applications. The security weaknesses and risks associated with key generation directly affect security of the whole crypto wallet application and users wallets.


Struggling with mobile app security?
Get assistance from our engineers.


6. How to select a good cryptographic library #

We discussed how scary and complicated things might be for React Native app developers. Let’s add some constructive suggestions to this picture.

react native libraries security: how to select a good cryptographic library - cossack labs

A slide from Anastasiia’s talk Use cryptography; don’t learn it.

  1. Looks alive: Manually review the library before adding it to your project. Prefer libraries that are supported by more than one person and are active (have the latest releases). Github stars are a bad metric to follow: for example, UI libraries have 100x stars more than any cryptography or security libs.
  2. Built for mobile: Prefer libraries that are optimised for mobile platforms, e.g. use native CPRNGs and cryptographic APIs, or are based on multi-platform libraries like OpenSSL / BoringSSL. Using web browsers API isn’t the best choice for mobile apps.
  3. Easy-to-use and hard-to-misuse API: Developers often don’t have cryptographic knowledge and might not be aware about cipher modes and requirements to the IV. Prefer libraries that have understandable and documented API and work similarly on iOS and Android. As crypto library developers ourselves, we often talk about how complicated it is to maintain the crypto library. If curious, take a look at the Maintaining cryptographic library for 12 languages talk.
  4. Tests: The library should have tests (unit and integration between platforms) on the main functions it provides. Without tests, the maintainers might accidentally break some features without even realising it. As cryptographic libraries work with the most sensitive data in the app, they should be well supported and resistant to human mistakes.
  5. Documentation: Nothing frustrates developers more than a need to read source code to understand what each function does. Documentation is the queen of open-source libraries. See if you can find out from the docs what crypto primitives the lib uses.
  6. Secure: Review GitHub issues and PRs: the library maintainers should react to found security issues and fix them. Perfectly, the library would be supported by people with background in security and cryptography, and audited by a third party. The crypto code should have protections against side-channel attacks, memory leakage, follow the crypto-coding guidelines, etc.
  7. Performance: speed, memory footprint.
  8. Licence: Pay attention to the open-source licence, as open source != free to use.

We recommend using mature cryptographic libraries for React Native apps. Either native implementations (CryptoKit, Javax.crypto) or high-level libraries like libsodium or Themis. Both Themis and libsodium are high-level libraries that hide some cryptographic details under the hood and work similarly across multiple platforms.

On the moment of writing this post, Themis supports same cryptographi API across 14 languages and platforms. Themis ReactNative wrapper is being recently released, making it the 15th supported platform.


7. How to keep your dependencies secure and up-to-date #

Adding a library to the project is only the first step. We strongly recommend setting up the dependency management and vulnerability management process to prevent or quickly detect and mitigate potential security weaknesses. Vulnerable and Outdated Components is one of the Top10 security risks in 2021 according to the OWASP.

“Dependency management is critical to the safe operation of any application of any type. Failure to keep up to date with outdated or insecure dependencies is the root cause of the largest and most expensive attacks to date.” — from OWASP ASVS V14.2.

react native libraries security: owasp dependency management - cossack labs

Extract from NIST SP 800-218 SSDF on dependency management.

NIST SP 800-218 SSDF v1.1 PW.4.1 emphasises the need to use well-secured libraries and periodically review them as a part of the SSDLC process. It also references other standards with similar requirements.

We recommend the following steps:

  1. Manually review dependencies from time to time and update them. Plan migration procedures to stop using deprecated or abandoned dependencies. A good idea is to use “refactoring sprints” once in a while to update dependencies consciously.
  2. Set up the automated dependency management process using tools like DependaBot, Snyk or even automate a simple yarn audit. These tools will notify you about new releases, if the used library has known vulnerabilities and if the patch is available. It’s important to configure automated tools precisely and to “monitor the monitoring” aka review and react to their suggestions.
  3. If you want to update a dependency but you can’t do it right away, communicate your intent to the team / management / users and plan your work for the future. If the dependency has a known vulnerability, analyse if it affects your app and document your findings for better visibility.
  4. Maintain the asset catalogue: which libraries are used, what are their licences, etc. Services like WhiteSourceSoftware might be of help.
  5. If security controls, sensitive data processing or any other critical action in your application relies on a third-party dependency, then cover this dependency usage with tests. It allows verifying the correctness of dependency behaviour.

8. Conclusions #

When developers add a new library into their apps, they should consider not only a direct value that a library brings, but a long tail of potential issues. Not a well-thought-out decision to add yet another library makes JavaScript dependency hell become a reality.

We build open-source cryptographic libraries, and we’ve met all the described above issues we’ve both as maintainers and users.

While we admire the power of the open-source community, we believe that critical security functionality should come from the proven libraries that are easy-to-use, hard-to-misuse, supported, audited, tested, documented, etc.

If you are looking for a library to “just encrypt the data” without diving into cryptographic details, take a look at Themis. Themis works across 14 different languages and platforms and is a perfect fit for cross-platform apps.


Contact us

Get whitepaper

Apply for the position

Our team will review your resume and provide feedback
within 5 business days

Thank you!
We’ve received your request and will respond soon.
Your resume has been sent!
Our team will review your resume and provide feedback
within 5 business days