What can go wrong when you develop a “secure” crypto wallet? How to eliminate typical security mistakes and build a secure app with multilayered data protection against mnemonics leakage and transaction forgery?
Cossack Labs security engineers were involved in improving the security of several large public blockchain ecosystems and their hot non-custodial crypto wallets.
Here we present some of our observations to help developers build safer and more secure crypto wallets.
First, let’s talk about the risks and threats of crypto wallets, then move to design concerns and implementation issues. In terms of financial risks, crypto wallets’ security baseline is “an old good banking app", meaning OWASP ASVS L3, MASVS L2, and PSD2 are good starting points.
- Hot vs cold crypto wallets
- Security risks and threats typical to all crypto wallets
- Application security flaws of crypto wallets
- Platform trust issues of mobile crypto wallets
- Platform trust issues of web crypto wallets
- Cryptographic flaws of crypto wallets
- Communication with decentralised apps
- User is a single point of failure
- Supply chain risks
- Final thoughts
1. Hot vs cold crypto wallets
The difference between custodial and non-custodial blockchain wallets is pretty straightforward, security-wise. Custodial crypto wallets rely on third parties (backends) to store users' private keys, requiring the users to trust them more. Non-custodial crypto wallets are fully controlled by the user, making the user responsible for the tokens’ safety. Often non-custodial crypto wallets are open sourced, as a demonstration of trust and security for the users.
Being open-source is a two-sided sword – attackers could also read details of implementation and find flaws easily.
Crypto wallets of public blockchains (Tezos, XRPL, Cardano, Bitcoin, Ethereum, etc.) don’t “store” any user data except for account keys in non-custodial wallets — all transactions are public and can be found in public ledgers.
Cold crypto wallets (offline, hardware, paper wallets) is one of the safest methods for holding crypto currency, as they are not connected to the Internet. But for convenience people prefer hot (online) wallets — mobile apps or web extensions.
Securing hot crypto wallets is a typical exercise in balancing trade offs — security vs usability.
2. Security risks and threats typical to all crypto wallets
Think about crypto wallets as “gates” to the blockchain ledgers.
The attack surface is broad: it combines security issues unique for the exact ledger, with risks and threats typical for any finance application. Additionally, crypto wallets' security heavily depends on their implementation – the selected platform (web, mobile) and developers' coding choices.
Understanding risks & threats on every level helps to find security mistakes on an overlap of cryptography, platform trust, ledgers' specifics and wallet’s exact implementation. This overlap hides the most interesting issues.
In public blockchains, all crypto transactions are public. Thus, a linkability between a user’s identity and public address (deanonymization) can lead to disclosing the user’s personality or IP addresses. See Deanonymization of clients in Bitcoin P2P network.
Even privacy-oriented systems like Zcash or Monero are affected by existing deanonymization techniques (see details in An empirical analysis of anonymity in ZCash, Privacy and linkability of mining in ZCash and A traceability analysis of Monero’s blockchain) though to a lesser degree. Public blockchains suffer from user deanonymization based on transactions graph analysis and IP-address deanonymization based on observing nodes' connections.
User deanonymization is typically overlooked by crypto wallets developers. That’s why we recommend educating users about over-the-shoulder attacks (minimize time secrets appearing on a screen) and the risks of adding friends' accounts to the address book.
Non-custodial wallets can become a component of DoS attacks on individual nodes or even the whole blockchains in some rare cases. It happens when cryptocurrency solution lacks the middleware entity (e.g. crypto wallet backend server) that can validate and filter transactions. In this case, malformed transactions go directly to the blockchain, waste nodes’ resources, and prevent valid transactions from processing.
Cryptocurrencies require combining novel cryptography with traditional data, application and product security expertise.
Looking for security guidance?
3. Application security flaws of crypto wallets
From the appsec perspective, crypto wallets are just applications.
They have similar threat vectors as any other applications: user phishing, injections, MitM, brute-forcing users’ passwords, replay attacks, reverse engineering, malicious 3rd party libraries – all the things to steal wallets’ secrets or fake transactions.
The primary purpose of all non-custodial crypto wallets is to store wallets’ secrets and sign transactions. Thus, user authentication and secure data storage are the most important security controls presented in every wallet.
Local authentication is much more than just setting a password for the wallet app.
Crypto wallets often miss crucial controls around password and authentication flow: password policy, rotation*, defences against password brute-force, additional authentication step before doing sensitive actions, biometrics verification, tying biometric authN to Keychain/Keystore, etc. Lack of these controls leads to lowering the bar for the attackers and sometimes opening opportunities for mnemonics and credentials leakage.
* Disambiguation: people on orange site have noticed obvious wrong way to read this paragraph, which relates to poor wording more than anything else. Many wallets lack means to rotate leaked passwords, data-at-rest cryptographic keys, etc. Since these are more closely intertwined than in a regular application, our initial write up mentions their deficiencies together.
Local data storage
As a non-custodial wallet needs to store the mnemonics, seed and private keys locally, it is crucial to understand how the local storage works and what are the common attacks against it.
Before storing the data, developers should get the platform-specific answers to the following questions:
- What kind of storage should be used to deliver the best security guarantees? Think about browser local storage vs session storage, Keychain/Keystore vs storing in plist/preferences.
- Is the storage accessible as a file? Is it accessible by other apps?
- Can you validate the authenticity of such storage? Is it possible to “steal” the storage from one wallet and put it in another one without problems?
- Are there any integrity checks? Or anyone can change stored data and the crypto wallet won’t notice anything.
- Does the storage provide encryption (even hardware-backed encryption), or the data is stored in clear text?
- Should application-level encryption be used to encrypt data before putting it to the storage? If so, where the encryption key will be stored and how to derive it?
4. Platform trust issues of mobile crypto wallets
The crypto wallet security largely depends on the security of its platform – web, mobile, desktop – and tight integration of security controls between app and platform. The more platforms the crypto wallet supports, the more related security issues might appear. Keeping the threat model in mind at all times, let’s look into various aspects of mobile platform security.
For example, crypto wallet mobile applications often do not check if the device is trusted: if it is rooted or jailbroken, if it has potentially harmful app or reverse engineering tools installed, etc. Existing mobile malware can be used to steal users' credentials, mnemonics or private keys from apps’ memory (see Pegasus, remote iOS and Android spyware).
OWASP MASVS L2 recommends implementing reverse engineering and tampering protection for financial, payments, and other apps operating highly sensitive data. It also recommends notifying the users that the device is not trusted because they may be not aware of it.
Mobile platforms (iOS, Android) do offer device-level security controls. Requiring users to set device-level passcode is a simple feature that creates a significant obstacle for an attacker. If the passcode is not installed, anyone can unlock the phone and steal the wallet data or even access the Keychain/Keystore. Crypto wallets should require using the device-level passcode, or at least notify the users that it should be set up.
Another good example of not-trusting the platform is encrypting wallet’s data before putting it to Keychain/Keystore – thus, even if the phone is under attack and attackers have access to the Keychain/Keystore – the data is encrypted there (see Application level encryption).
While mobile devices’ and OS’ exploits are out-of-scope for developers, they can implement security protections to “raise the bar” for attackers. For example, limiting app’s functionality on jailbroken/rooted devices, not supporting old devices, using native secure storage, limiting the lifecycle of sensitive data, thus decreasing the risks of successful attacks and data leakage.
OWASP (MASVS, MSTG) and NIST (SP 800-57, SP 800-63, SSDF, etc.) describe proven industry guidelines and best practices. Apart from them developers should always look for platform specific recommendations (see our article on React Native application security).
5. Platform trust issues of web crypto wallets
We’ve audited crypto wallets done as web extensions that rely entirely on browser security.
Web crypto wallets’ security operates under certain assumptions that stored data is safe and doesn’t leak. But the web extensions (as well as the users) have no notion of runtime code integrity, so whatever is running on the user machine can be modified at all times.
Web malware’s possibilities are unlimited: phishing users by displaying a malicious version of an “import account” or “send transaction” screen or replacing the content of the clipboard to take advantage of the copy-paste actions when the users try to send tokens to their friends.
Thanks to a browser extension sandbox, other extensions, web sites and out-of-browser processes typically can’t access wallet process memory and read sensitive data. See Breaking out of the Chrome WebExtension sandbox.
However, browsers are a target for exploits and 0days that give memory access to attackers.
According to Google Security blog, ~70% of security bugs that affect browsers are memory issues. Specter and Meltdown attacks were published in 2018 by Google Project Zero; they allow reading privileged memory by side-channel attacks. Although there are no 100% protection measures against 0days and yet-unknown exploits, there are security guidelines that help developers to decrease chances of successful exploitation of known bugs.
Chrome team recommends developers to enable site isolation, use no-sniff content-type headers, and prevent cookies from entering renders’ process memory. OWASP ASVS, WSTG, and Cheat Sheets are also good sources of inspiration for industry best practices.
6. Cryptographic flaws of crypto wallets
Non-custodial wallets perform many cryptographic operations: encrypt stored wallet’s secrets, sign transactions, and communicate with decentralised apps using secure protocols. Some cryptographic choices are dictated by the community (thus, BIP39 is often used to generate mnemonics, XRPL uses XLS-12), but the crypto wallet developers still have plenty of room to fail.
When people hear about cryptocurrency wallets, they assume that developers are experts in “crypto”, which they see as a synonym to “cryptography”.
But typically, crypto wallet developers are not the same people who are working on the blockchain’s cryptographic core.
Usually, they are regular web, mobile, desktop devs who understand cryptocurrency concepts, but they are not experts in modern applied cryptography. Thus, their cryptographic code suffers from the common design flaws and implementation mistakes. Using correct crypto primitives for a particular use case is a separate skill that requires additional knowledge and experience.
The most common cryptographic issues we’ve seen are:
Deriving cryptographic keys from the low entropy secrets without any KDF or choosing poor KDF parameters (think using PBKDF2 with 500 rounds to generate encryption key from the user password, instead of argon2, scrypt or PBKDF2 with 310000 rounds).
Improper handling of errors and exceptions leading to salt, nonce or IV misuse or leakage.
Using unsuitable crypto-primitives for a desired purpose (MD5 instead of Argon2DI, AES-OFB instead of AES-GCM, SHA-256 instead of HMAC-SHA256);
Using the unsuitable block cipher modes for data encryption (AES-CBC doesn’t provide integrity checks, prefer using AES-GCM instead).
Making typical cryptographic mistakes: AES-CBC with zero IV, home-brewing own crypto protocols, using math.random instead of crypto.random, using base64 as “encryption” and so on.
Poor key management (storing encryption keys in plaintext together with data; lack of key rotation, revocation, expiration; using cryptographic keys for several different purposes).
Poor memory management of secrets (having sensitive data “everywhere” in the application code, not limiting its lifecycle in memory, in storage and on screen).
Storing wallet’s sensitive data in plaintext (well…).
For every crypto wallet we’ve audited, we typically found 8-10 issues related purely to the cryptography – high and medium, mostly. As many non-custodial crypto wallets are open-source, cryptographic issues become much easier to identify and exploit for a prying eye.
As we do believe that software developers shouldn’t struggle with tradeoffs of writing cryptographic themselves, we strongly recommend using community-proven crypto-libraries that are designed for developers and work across many platforms, like Themis and libsodium.
Themis is a high-level cryptographic library that fits perfectly for multi-platform apps. Themis is easy-to-use and hard-to-misuse.
A combination of bad cryptographic choices leads to a disaster
Weakness 1. Weak storage scheme for wallets data
The most interesting cryptographic issues we’ve seen were a combination of design & implementation mistakes or bad choices. Let’s see how several small issues combined together could simplify uncovering wallet’s mnemonics – the core secret of all non-custodial crypto wallets.
One crypto wallet stored sensitive data in a file, encrypted but in a very human-friendly format. Each stored field had an understandable name.
The file itself was stored on a user’s machine in a folder, accessible for curious eyes and any other applications in the system.
wallet_mnemonics: encrypted_data: "4198fbf....aaca6d" iv: "e84c2e2bb7c...904f6a16bbad9" salt: "da2111aeab1182...0c30614931" password_check: encrypted_data: "12e1a2ba2c...8492e9" iv: "521a231a21a...a1bb123c86" salt: "73aac01746d4d...928da60adb4"
The crypto wallet stores the data in a file, encrypted per field.
The encryption scheme was the following:
It means that every data field was encrypted using AES-256-GCM and a unique-per-field derived encryption key. All encryption keys were derived from the same user password using different salts.
The devil is in the details. If the attacker had access to this file, they only needed to decrypt one field –
wallet_mnemonics. The attacker knows the salt and IV of the encrypted data, making brute-force very straightforward. Human-friendly names really help attackers to quickly locate required fields.
Weakness 2. Encrypting multiple fields with the same password
Brute-forcing becomes even faster with the
password_check field. It is a special utility field, which the crypto wallet uses to determine if the user has entered the correct password. If decryption of this field is successful – the user password is correct.
The original (plaintext) value of the
password_check field was “null”. So developers were actually encrypting “null” with the user’s password, storing it encrypted, then decrypting and comparing
if decrypted == null.
As all fields are encrypted by keys derived from the same password, the attacker can optimize brute-force by first decrypting the
password_check value. The attacker knows salt, IV and value of plaintext null. Brute-forcing
password_check will be faster than brute-forcing
wallet_mnemonics due to known and short plaintext.
Successful brute force will result in a user password, which the attacker will use to decrypt the
wallet_mnemonics field (already knowing its salt and IV).
Weakness 3. Poor KDF choice and weak parameters
Usually, a strong password-based KDF should protect against brute-forcing, making it very long, as KDFs are designed to be slow. However, this particular crypto wallet used a PBKDF2-HMAC-SHA256 with only 1000 iterations (instead of currently recommended by NIST and OWASP 310000 iterations).
Using so few rounds with an “old-school” PBKDF2 is a bad choice that makes brute-forcing even easier. Which KDF to use?
Weakness 4. Allowing low-entropy user passwords
The next line of defence is the user password. Low-entropy passwords, like “Password1” or “Qwerty123” are quite common. This crypto wallet used the following password rules: 8 chars length, 1 number, 1 capital and 1 lowercase letter, which seems fine.
As a result, it takes less time to brute-force a more straightforward password.
A combination of weaknesses
In this example, a combination of bad choices could lead – under unfortunate circumstances – to stealing and reversing crypto wallet’s mnemonics. Location of a file, its human-friendly format, IV and salt placed together with the encrypted data, using “null” field for a check, using encryption keys derived from the same password for every field, selecting PBKDF2 and using too few iterations for it, allowing weak passwords, and, finally, being an open-source wallet.
We provided multiple recommendations for every aspect of this scheme.
7. Communication with decentralised apps
The more features crypto wallets have, the more extensive is their attack surface and the more sophisticated is the threat model.
One of such typical wallet features is interaction with third-party decentralized applications (depending on a blockchain, they are called dApps or xApps). Some crypto wallets allow quickly interacting with a predefined list of dApps and others embed dApps as WebViews.
This communication introduces the following threat vectors:
Communication between the wallet and the dApp. Without proper authentication, data-in-transit encryption and authorization of transaction data, the attacker may intercept and modify requests – like changing transaction’s amount or recipient’s address.
Malicious dApps. Most blockchains have dozens of dApps created by the community – some of them suddenly gain popularity, others become neglected. Even if the dApp is coming from a trusted source, it doesn’t mean that it has no vulnerabilities. Also, nothing prevents attacking users via the dApp that has been modified and becomes malicious afterwards (refer to typical vulnerabilities caused by DApps).
The way dApps are integrated into the wallet. For example, if the crypto wallet is a mobile app, dApps may be opened in a WebView as regular web pages. As a result, they bring all web-related risks: injection, hijacking, and leaks.
To mitigate these threat vectors, we recommend using a strong transport encryption between crypto wallets and dApps (TLS 1.3 or specific protocols, like Beacon for Tezos ecosystem), doing a proper session management and mutual authentication, and watching the integration point.
Most blockchains we’ve audited don’t have anti-spam and anti-abuse measures other software marketplaces have – think AppStore, Google play market, AWS marketplace, etc. It makes inexperienced users vulnerable to malicious dApps. We suggest having a “complaint” button, doing periodical inspections of dApps and having a dedicated support channel for users related to dApps’ behaviour.
8. User is a single point of failure
Non-custodial crypto wallets are secure as long as the user keeps it secure. So, along with in-app security efforts, educating users about their responsibility is a mission of every crypto wallet dev team.
It doesn’t matter how strict apps’ security controls are, if the users are easily tricked by phishing attacks or simply lose their wallet’s secrets. Developers should include quick security tips and hints in the app, especially when users interact with critical features (like transferring their tokens to a new address) .
Educate the users because often the attackers do not need any technical skills to obtain someone’s sensitive information through “shoulder surfing”, i. e. observing their computer or mobile device screen and keyboard.
Users may know what blockchain is, what is the current currency exchange rate, how to send transactions, but they may not know how it works or what account mnemonics and private keys are and why they need it. Users may not know that they should treat mnemonics as credit card CVV. We suggest treating mnemonics similarly to a “memorized secret” according to NIST SP 800-63, and follow it.
When building new features in the crypto wallet, always consider how the users might misuse and abuse the app. Educate the users wherever it is possible as overwise they can be the weakest link in the wallet’s security mechanism.
We help companies to build secure systems and protect their innovations. Read how we secured notes in Bear iOS and macOS apps using Themis:
B2C mobile app
End-to-end encryption and multi-device synchronisation for 6M users
Encrypting data for Bear — the Apple Design Award-winning application while focusing on performance and usability.
9. Supply chain risks
Supply chain attacks pose a significant risk to crypto wallets. Supply chain risks are growing together with a growing amount of external dependencies used in the app.
Crypto wallets may have numerous dependencies that have access to the wallet sensitive data, like cryptographic libraries (think ECC), official libraries for particular protocols or standards used in the blockchain (think BIP39 or BIP32), and handy utility libraries for every possible purpose (think all npm).
We’ve seen crypto wallets with 110 external dependencies. As every dependency has its dependencies, the total amount of dependencies was 1838 (estimated by yarn audit).
If some dependencies are close sourced, the Dependency confusion attacks might be relevant.
A vulnerability in any of these libraries – is a potential vulnerability of the whole wallet.
Another example is that the dependency may not have required security functionality. For example, react-native-fingerprint-scanner doesn’t handle a fallback action on a biometry change and react-native-webview doesn’t clear WebView cache properly.
We recommend carefully nurturing external dependencies, patches and forks, updating them regularly, and using automation tools to support the process (NIST SSDF PW.1, PO.3).
Nine circles of dependency hell
Researching dependencies sometimes opens an unexpected hole to hell.
Let’s take a look on React Native crypto wallet that works on iOS and Android. It generates random values used for encryption and private keys generation:
const random = generateRandomValues();
According to the react-native-randombytes documentation, it supports two ways of generating pseudo random bytes: synchronous and asynchronous calls.
For async generation, the library uses native CPRNGs – SecRandomCopyBytes on iOS and SecureRandom API on Android. For sync generation, the library uses CPRNG from the SJCL library.
People had questioned this choice before. For example, in this issue, one of the react-native-crypto maintainers, confirmed the sync generation is less secure than the async one. But they plan to update using a more secure sync way of generating random values (this update hasn’t happened yet).
Right now, SJCL has many open issues, it wasn’t updated for several years and is surrounded by discussions about the security of its random function: bitwiseshiftleft/sjcl#77, bitwiseshiftleft/sjcl#178, stackoverflow.
SJCL uses random.js to collect entropy from entropy pools, relying on things like “mouse movements” and “keyboard listener”. Of course, mobile devices don’t have a mouse and keyboard in a way as desktop devices do.
All of the above leads us to think that SJCL is an unsuitable choice for CPRNG for mobile applications. It was not designed to get entropy from mobile devices, doesn’t have secure default settings, and wasn’t updated for a while.
We strongly recommend keeping an eye on sources of cryptographically pseudorandom values, as low entropy values lead to deriving weak and predictable cryptographic material, making attackers’ job easier.
The users are trusting a decent amount of their tokens (~ money) to crypto wallets. They expect the same level of security as they get from other financial applications, banking apps or better.
Some teams, having built a widely popular wallets, lack security and cryptography expertise to understand that their implementation exposes users to problems. Some of the bike-shed security controls are amazing examples of Schneier’s law. It’s not their fault, as their business is different.
Responsible developers should understand when responsibility becomes hard. Perhaps, when wallet adoption reaches some point, choosing to review and improve security should become a priority.
Here are some practical tips for improving security of crypto wallets:
- Start with understanding risks and threats: crypto wallets’ risk landscape is a combination of blockchain-related and platform-related risks, cryptography, and application security flaws.
- Educate the user. For a non-custodial crypto wallet, the user is the weakest link of the system. Do your best to prevent phishing attacks: add hints, mask fields, show warnings, ask for user password before critical actions.
- Follow the best practices in cryptography: don’t implement your own ciphers and protocols, use strong encryption, and be careful with key management.
- Do not hesitate to use out-of-the-box platform security controls: they allow “raising the bar” for attackers with minimum developers’ cost.
- Use automated static code analysis tools (SAST) in your CI pipeline.
- Continuously audit all dependencies that you are using and implement the proper fixes before the next release goes public. Use automated dependency management tools in your CI pipeline.
- Focus on massive exploits. While you can cover single cases, massive exploits may destroy the reputation of the crypto wallet and even the cryptocurrency. Cryptocurrency reputation equals its cost.
- Follow the secure coding best practices (e.g. OWASP Secure Coding Practices): data minimization, input validation and sanitization, principle of least privilege, etc.
11. Final thoughts
Crypto wallet security is a tricky beast. All cool vulnerabilities and potential issues we’ve found lie at the intersection of multiple flaws: cryptography, access to the local storage, lack of authentication, lack of input validation. Each is relatively small, but they open the unexpected attack vectors when combined.
From the defender’s point of view, the crypto wallet’s attack surface is enormous. But from the attacker’s point of view, it’s not so difficult to combine 3-4 flaws, especially if the crypto wallet’s code is open-sourced.
Therefore, it makes sense to look not only at specific flaws but at their synergies. Preventing security bugs not only with secure coding and suitable crypto primitives but with the right security design, using proven building blocks and correctly integrating them. Pushing security early and following the secure software development cycle (SSDLC) saves time and budget spent on security and keeps a clean reputation.
Crypto wallets' application security failures allow stealing money faster than from vulnerable mobile banking apps. Unlike banks, public blockchains don’t have a massive anti-fraud system or customer support able to revert transactions. Act accordingly.