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!
- React Native guides recommend insecure libraries
- React Native libraries: Improper platform usage
- Easy-to-misuse crypto APIs in React Native
- React Native security code: Why avoid the reactive approach
- React Native security: What developers need to know
- How to select a good cryptographic library
- How to keep your dependencies secure and up-to-date
- 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.
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:
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.
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.
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.
Letâs see some examples of the libraries with known issues and commonly used across a large number of React Native applications:
- react-native-aes-crypto is commonly used for PBKDF2 and AES-CBC. Good thing is that it uses CommonCrypto (iOS native crypto library) and Java Crypto, Java Security (Android-native crypto libraries), but at the same time it relies upon the abandoned SpongyCastle;
- react-native-crypto has the CPRNG implemented from react-native-randombytes library that uses SJCL under the hood, unsuitable for mobile devices;
- Previous versions of react-native-crypto used crypto-browserify/randomfill library dependent on crypto-browserify/randombytes with a web-browser-oriented CPRNG. Not the best fit for mobile.
- react-native-keychain implements Keychain/KeyStore storage and by default uses the deprecated library facebook-conceal;
- node-forge is a web-backend-oriented library that may be not suitable for encryption on mobile devices.
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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- Performance: speed, memory footprint.
- 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.
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:
- 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.
- 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.
- 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.
- Maintain the asset catalogue: which libraries are used, what are their licences, etc. Services like WhiteSourceSoftware might be of help.
- 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.