Welcome to my web-based non-custodial wallet called DecentralVault, inspired by the design philosophy of the now-defunct peer-to-peer crypto exchange, Localcryptos!
Despite the unfortunate closure of Localcryptos, I have drawn inspiration from their innovative approach to building secure and user-friendly interfaces for cryptocurrency management.
When a user creates an account, a cryptographic key is generated on the device of the user, known as a "master key". This "master key" is later used to derive other keys — such as a key for encrypting other private keys used to store cryptocurrency, or a key for signing information.
When creating an account, the first step is to generate a securely random 32-byte "master key" (AccountKeyMaster
) using the Web Cryptography API.
Once the AccountKeyMaster
is created, two more keys can be derived from it:
AccountKeyIdentityPrivate
is the function ofSHA256(AccountKeyMaster, "identity")
. This will be used for digitally signing messages using elliptic curve cryptography.AccountKeyEnc
is the function ofSHA256(AccountKeyMaster, "enc")
. This key will be used to encrypt other keys using AES symmetric encryption.
The AccountKeyMaster
key must be encrypted using the user-inputted password. However, the password alone will not suffice: it must be first transformed into a secure key suitable for encryption using the key derivation function PBKDF2. This process, known as "key stretching", adds complex computational work to make password cracking very difficult. Passphrase
is stretched using PBKDF2 using at least 100,000 iterations with the salt PassphraseSalt
to create SecretKey
.
Once SecretKey
is derived, AccountKeyMaster
is encrypted using it with AES-256-CBC. The initialization vector (16 random bytes) is kept as SecretIv
.
Before the sign up process can be completed, we requires a unique message to be signed to prove that you know the private key. The browser needs to obtain the unique message needed to be signed from our API. This is known as an ephemeral registration “token” and it contains:
TokenId
— A unique identifier to the registration token.TokenNonce
— The message needed to be signed with the new key pair you are about to create.
Once obtained, a digital signature of the registration token is created using the AccountKeyIdentityPrivate
key. TokenSignature
is an ECDSA signature of the Keccak-256 hash of TokenNonce
.
The non-sensitive information is then submitted to the API and a new account is created. (Everything marked "secret" below is not uploaded.)
Passphrase
(secret) — User-inputted plain password.SecretKey
(secret) — A more secure 32-byte key derived from stretchingPassphrase
using PBKDF2 with the saltPassphraseSalt
using at least 100,000 iterations.AccountKeyMaster
(secret) — A randomly generated 32-byte account master key. This is the seed to everything in your account; it's crucial to keep this secure.AccountKeyIdentityPrivate
(secret) — A key that is the SHA-256 hash of the concatenation ofAccountKeyMaster
and "identity". Used for signing and authentication.AccountKeyEnc
(secret) — A key that is the SHA-256 hash of the concatenation ofAccountKeyMaster
and "enc". Used for encryption.AccountKeyIdentityPublic
— Using the secp256k1 curve, an ECDSA public key that corresponds toAccountKeyIdentityPrivate
. Uniqueness is enforced here; no two users can have the sameAccountKeyIdentityPublic
.PassphraseSalt
— The random 16-byte salt used for the PBKDF2 process.PassphrasePBKDF2Iterations
— The number of PBKDF2 iterations to use to stretch thePassphrase
to more secure key material. A higher number results in a slower key stretching process, weakening brute-force attempts.SecretIv
— The randomly generated 16-byte initialization vector used to encryptAccountKeyMaster
.SecretCiphertext
— The ciphertext ofAccountKeyMaster
encrypted toSecretKey
using AES-256-CBC.TokenSignature
— A digital signature of the Keccak-256 hash ofTokenNonce
byAccountKeyIdentityPrivate
, to verify ownership ofAccountKeyIdentityPublic
.- Any other account information (e.g. username and e-mail address).
Logging into an account requires two-factor authentication. E‐mail is the default two‐factor authentication method and there is optional support for time-synchronized OTP (e.g. apps like Google Authenticator). SMS is not supported as a two-factor method because SMS is not safe.
Two-factor authentication is necessary to protect the encrypted version of AccountKeyMaster
(i.e. SecretCiphertext
and SecretIv
). Although the decryption process has been slowed by the key stretching process, passwords are still theoretically susceptible to brute‐force attacks (especially dictionary‐based attacks on extremely weak passwords). Hiding the key behind a two‐factor method virtually eliminates the hypothetical attack vector of another user obtaining your passphrase by brute-force.
The two-factor authentication process is executed by the API, meaning that there is a small element of trust here. If the database were to be hacked, an attacker could get all the encrypted private keys. Still, these passwords are salted and stretched so rainbow-table attacks aren't possible and other brute-force attacks are very hard. In this hypothetical hack scenario, it would be difficult for the hacker to obtain passwords except those that are very weak (e.g. "password", or a re-used password from a separate compromised database).
This is the log-in process for two-factor authentication by e-mail:
- The user enters their username and password.
- The username is delivered to the API in a request for the associated encrypted private key. The password is ignored for now.
- An e‐mail is sent to the e‐mail address associated with the username, containing a secure link to progress the login request to the next stage.
- Once the link in the e-mail is clicked, the original log-in window receives
SecretCiphertext
,SecretIv
,PassphraseSalt
, andPassphrasePBKDF2Iterations
. - The user‐entered password is salted and stretched as required to derive
SecretKey
. The stretching process must be exactly the same as the process used when signing up. - If the
SecretKey
can be used to calculateAccountKeyMaster
correctly and henceAccountKeyIdentityPublic
, then the login attempt was successful. If not, the user is prompted to try another password and step 5 is repeated. - A nonce provided by the API is signed by
AccountKeyIdentityPrivate
to prove ownership of the key pair, and a new session is issued. AccountKeyMaster
is stored in the browser's storage for the duration of the session.
Changing an account password involves re-encrypting AccountKeyMaster
with a new SecretKey
. The AccountKeyMaster
key can never be changed, hence neither can the identity key or account encryption key be changed.