The Failures of Web3 Wallet Security
An indictment of non-custodial software wallet browser extension security affecting MetaMask, Phantom, Keplr, and many others.
In this article, I explain how even the most trusted Web3 extensions are doing a disservice to their user base by failing to adhere to higher security standards. I then provide some technical details about how StarShell is attempting to greatly reduce this attack surface by introducing novel key management techniques in Web-based JavaScript.
These security issues do not apply to users who back their Web3 accounts with hardware wallets. I strongly urge any cryptocurrency user, who can afford one, to consider purchasing a reputable hardware wallet that is compatible with their existing assets.
StarShell is an upcoming Web3 wallet that will deploy as a browser extension and is introducing novel security features that attempt to address such issues.
— by Blake Regalia, Creator of StarShell
In a previous article, I pointed out a security flaw in other Web3 wallets where a malicious co-installed browser extension with minimal read/write page data permissions could perform MITM attacks by replacing page-generated transaction data in order to trick users into approving arbitrary transactions.
I have some more bad news.
Cryptography on the Web
Modern cryptographic functions can be difficult to implement properly as they typically involve more advanced mathematics such as polynomial evaluation, discrete logarithms, modular exponentiation, large primes, elliptic curves, and Galois fields, among others. Aside from the math being difficult to implement, the supporting algorithms also require extreme attention to detail as any mistakes can add up to major vulnerabilities. This is why projects usually opt to import third-party cryptography libraries that have been thoroughly evaluated for correctness.
On the Web, front-end applications have access to the Web Crypto API in today’s browsers. This is not only an enormously helpful feature, but also a crucial component for application security for a few very important reasons. The first of which is cryptographically-secure pseudorandom number generation (CSPRNG) without which, applications would be unable to produce private keys barring the need to manually introduce extra entropy.
The other crucial component of Web Crypto is the ability to import keys, where the browser is able to take advantage of platform-specific cryptographic primitives to store them in protected memory regions (either by the operating system or by a hardware-based key manager), while still allowing them to be used for things like signing, verifying, encrypting, decrypting and so on.
Cryptocurrency on the Web
Secp256k1 refers to the elliptic curve parameters used in Bitcoin, Ethereum, and many other cryptocurrencies (including all Cosmos-SDK chains). Satoshi chose secp256k1 because its parameters are non-randomly constructed, unlike the NIST P-256 curve (aka, secp256r1 or prime256v1) which some believe may have a backdoor. While Web Crypto does support NIST curves, it does not support secp256k1, nor will it ever.
This leaves any application running in the browser, most notably any Web3 wallet extension, forced to rely on JavaScript-based cryptography to perform secp256k1 operations with the private keys in plaintext.
Put another way, software wallets keep private keys in plain memory, unencrypted, and unprotected!
Albeit far less secure than any hardware-based cryptography, soft wallets do provide a greater reach in terms of making Web3 more accessible. Hardware wallets are not exactly cheap, and the burden of extra hardware can be a barrier to entry. However:
The ways in which these soft wallets manage private keys in (JavaScript) memory demonstrates a clear lack of understanding for how computer memory works, a total lack of appreciation for security best practices, and a critical failure on behalf of everyone involved with the development and security audits of such software.
It’s troubling to discover these types of neglected weaknesses. Millions of people trust these software to protect their assets and to safely mediate their connection to the decentralized platform that is Web3.
Suffice to say, the severity of these issues cannot be understated. While writing this article, I found a fantastic blog post on software wallet security written by the folks at Ledger which echoes many of the same security concerns raised here: https://blog.ledger.com/software-wallets/
Enter StarShell
A perfectly secure soft wallet, especially one that runs in the browser, is next to impossible with today’s tools that are available at developers’ disposal. Again, I strongly advise the use of hardware wallets whenever possible, but I also understand that this can be cost prohibitive for some. For those users, soft wallets should employ much stricter security practices in order to reduce the risks of key-finding attacks.
The rest of this article describes how StarShell will attempt to mitigate the aforementioned security concerns. Please note that these solutions can merely reduce the attack surface at best, and that they will be pending an independent security audit to verify their effectiveness.
Private keys in memory
In elliptic curve cryptography, a private key is essentially a very large integer. Signing messages (ECDSA) with the private key requires the ability to perform arithmetic on such very large integers. Back in the day, developers were forced to use primitives such as hex-encoded strings or arrays of numbers to represent these keys. Today, there is BigInt, which greatly simplifies the implementation of the algorithms since arithmetic operations are handled by the runtime.
However, all of those datatypes, BigInt included, are problematic for private keys. The reason comes down to JavaScript’s garbage collection and how operating systems allocate and deallocate memory.
Essentially, private key material (including any intermediate values) have the ability to live in memory long after the algorithms are done using them. As Chow et al. describe in “Shredding Your Garbage: Reducing Data Lifetime Through Secure Deallocation.” (PDF, HTML), the proper way to manage private key material is by zeroing out the memory immediately after it is no longer needed (a practice they refer to as secure deallocation).
Without using TypedArrays, JavaScript programs have no way to guarantee that private key material has been cleared from RAM. Depending on the system, private keys can continue to live in memory even after the browser is closed, or in some cases even across reboots. In those states, all a rogue program has to do is allocate some non-zeroed memory from the operating system and search it for old key material.
Combining one-time pad with secure deallocation
In StarShell, the approach I’ve taken attempts to greatly reduce the key-finding attack surface by only allowing plaintext private keys to exist in memory for very short periods of time (i.e., only when absolutely necessary). To the best of my knowledge, this approach has never been explored in a Web/JavaScript context.
The approach starts by storing the encrypted private key in memory, providing baseline security for data in use. If an attacker were to dump the browser’s memory in this state, the user’s private key would be protected. In order to use the private key however, we do need to be able to decrypt it and hold it in memory for a very short period of time.
This is where Web Crypto comes into play. Rather than completely abandon Web Crypto for secp256k1, we can still take advantage of its secure key management feature by using it to derive a one-time pad key that can be used to unwrap the encrypted version of the secp256k1 private key stored in memory. The following data flow diagram provides a high-level overview of the processes for generating and/or wrapping the private key, and subsequently unwrapping it:
In order to emulate secure deallocation in JavaScript, it is imperative that private key material never exist in any primitive datatypes other than TypedArrays. This means no BigInt since there is no guarantee the browser will zero out garbage collected data. All large integer operations must instead be implemented and performed using TypedArrays since they can be explicitly zeroed out before deallocation.
While the proposed solution is by no means perfect security, it does greatly reduce the amount of time that plaintext private key material exists in memory (especially when compared to the competition). Naturally, this should reduce the key-finding attack surface since a malicious actor would have to access the program’s memory during a very small time window in order to recover the private key.
Whew! That got rather technical at the end. This topic in particular required a lot more work behind the scenes. If you care about security and the future of Web3, please consider following us on Twitter, or joining our Discord server.
Learn more about StarShell at our website: https://starshell.net/