aviv-official / xvault-web

Web frontend for XVault
https://aviv-official.github.io/xvault-web/
GNU Lesser General Public License v3.0
0 stars 0 forks source link

Adding logoff / login and dealing with security issues. #7

Open sshelton76 opened 5 years ago

sshelton76 commented 5 years ago

@moondancer762 made some insightful comments that I think demonstrate we may have more than a UX issue on our hands, but a potential security issue as well.

Balancing security and usability is always a challenge fraught with tradeoffs. I have tried to strike the best balance I can in order to ship a minimally viable product quickly while not exposing anything serious as far as security is concerned. But usability and user experience need a rethink because people want to be able to log on and log off as they are accustomed to.

At present we store the encrypted wallet in the browser using localStorage, when we want to decrypt it, we pass in a pin, the pin is stretched using a PBKDF to re-generate the key and the wallet is returned decrypted for further use.

Because the wallet is stored and managed client side and because there is no back-end server the wallet's presence is used to maintain a "logged in" state and we have no log off option, because to actually logoff would necessitate deleting the wallet and if the user does not have the mnemonic handy, then they would lose everything.

The wallet is deterministic and can be regenerated on the fly from the mnemonic alone, hence in settings we allow you to view your mnemonic but at present you cannot change it. I can upgrade that so it works the same way it does in AIM, meaning you can change mnemonics in order to swap accounts, but at present this isn't there yet.

I have hesitated to do this because, storing the mnemonic online anywhere, even encrypted on the client, is a security issue and this isn't limited to the mnemonic, there's an issue with the wallet file too. AIM being a plugin has it's own space for localStorage that isn't accessible from the web. But XVault is a website not a plugin and thus what works safely and securely for AIM isn't necessarily the best option for XVault, because plugins could potentially access local storage surreptitiously.

Ethereum protects the encrypted private key with a default of 8192 rounds of password+salted scrypt.

This is reasonably secure vs brute force, if the password had normal amounts of entropy. But considering we are only using the PIN here, the effective password entropy is only 3.5 bits per digit. Ergo a 10 digit PIN is only secure against 8 million attempts while a 13 digit PIN is only secure against 11 million attempts and a 16 digit PIN is secure against 500 million attempts.
This is why we stretch the PIN with 50,000 rounds of SHA512 first before presenting it to the ethereum wallet.

Nevertheless, an offline attacker with an asic could crack a wallet secured with nothing more than a 16 digit PIN in less than a week versus a year or more with a normal password. So our current method is not secure against compromise of the wallet sitting in localStorage and as a result we shouldn't be using it this way.

So here is my thinking...

We should no longer store the mnemonic nor the wallet. We will keep the mnemonic only long enough for the user to verify they have it by typing it into a form and then we discard it.

Secondly, we should only cache public ethereum addresses clientside, not the wallet, not the mnemonic and not the private keys, not even encrypted.

This means that logging in needs to worked out, because we will need to regenerate certain pieces and we don't have a server sitting somewhere that we use to access these values.

Furthermore, it is a design goal to allow people to log in at an ATM or a McDonald's using nothing more than a PIN pad in order to pull out cash or spend offline without an app when needed. So whatever we do here HAS to be pin pad compatible. But as mentioned earlier a PIN only provides 3.5bits^digits of entropy. And therefore we need to have a system that will remain strong in the face of a brute force attack even vs a determined attacker with a distributed set of ASICs.

2FA is not possible here because we lack any centralized servers and thus we need to rely exclusively on the blockchain.

So I think we need to rethink the user sign up process. We can use the signup information to very, very carefully construct a method of storing the private keys within the blockchain itself.

The first thing we should do is collect a lot more information than just a pin. We should collect, email address, telephone number in addition to the pin.

Keep in mind, ansible is structured thusly...

map[eth address]=>(k,v)

Because we want to make login from a pin pad a snap, we only use telephone number + pin as elements of our encryption key which are used as factors to secure the private keys which are then stored in ansible.

The simplest way is to use the hash of the pin+telephone as an index into ansible.

Each ethereum address has it's own unique k,v store under ansible. If we use a hash of telephone+pin as k1 this will allow us to look up the user's v1, even if we don't know which address is indexed for them inside ansible, nor do we need to input the email address since the contents of v1 would be a cipher text of the user's email address, which is used later.

Finding k1 also means we have the user's address and can consider them "logged in" for the purposes of viewing account details, but we still don't have access to their private key and this key will be needed to sign transactions.

The key to v1 would be computed using telephone+pin+x with n rounds of some PBKDF such as Argon2 with n corresponding to the PIN as as a number.

Example if the PIN were 1234 and the telephone were 8015551234 The key would be the result of

let telephone = 8015551234;
let PIN = 1234;
let secret = telephone;
let salt = PIN;
for(let x = 0; x <= PIN; x++){
   secret = hash(secret+salt+x);
}

This would then be fed as key material to a secretbox function.

Any 8 digit or greater PIN should be enough since telephone numbers are always at least 10 digits so we have (8 million phone numbers per country code)*(10^PIN digits) or 791 trillion possible options with an 8 digit PIN or 79,199,000,000,000,000,000,000 with a 16 digit PIN.

I would like to recommend that people use an old expired gift card as their PIN source. This way the PIN is handy, but is never particularly obvious. 16 digits is the standard length of a credit / debit / gift card.

However hashing anything 10^16 or 10 quadrillion times would not be viable. So in this case, we would limit the hash count to 100 million, perhaps by truncating PIN for counting purposes to no more than 8 digits, while leaving it in tact for salt purposes.

Doing this weakens security slightly, and it's all in the name of making things pin pad compatible, which is of course vital if we want the ability to use this with an ATM, or on a PIN pad at McDonalds or whatever. However most people will be using XVault via their computer or their cellphone

Therefore the plain text contents of v1 should not be a private key.

Instead v1 at k1 should be the user's email address and the private keys should be located at the k = hash(address+email+telephone) encrypted with email+telephone+PIN+x as follows.

let telephone = 8015551234;
let PIN = 12345678;
let email = "johny@test.test";
let secret = telephone;
let salt = PIN;
for(let x = 0; x <= PIN; x++){
   secret = hash(secret+email+salt+x);
}

This does leave open the possibility of a brute force attack on v2, albeit a slow one. Hence having a large PIN known only to the user is vital and the PIN should be changed out at least once every 6 months. Each time the PIN is changed, the thing the PIN is protecting, i.e. the private key, should also be discarded, deleted and never used again. This can be as simple as transferring all funds to a new address and then ceasing to use the old one.

moondancer762 commented 5 years ago

First, we DO need to collect users' email addresses - as well as other info.Aren't we going to have to do AML/KYC? Besides, we need some way to alert them to anything which may affect their account (in the ultimately unthinkable case it should happen).

Second, will we automatically change their addresses and force people to change PINs after a certain length of time? I know some banks do force PIN changes. Or, will we simply instruct users this needs to be performed by them, voluntarily, and send them an email, or reminder on their screen?

PS, what is "secret" and "salt?" Can these words simply be changed (and then forgotten) by the customer every so often? Can we just explain to the user we need two random words from them to change some background security features, and they do not need to remember these words, but this will change the private key and they should record the new key? Or, are the words global in nature?

If changing the "secret" and "salt" words is feasible on a per-user basis, PINs don't necessarily need to be changed (but still can be). I hate to change PINs. We have SO many to remember anymore and they are becoming difficult to remember when they have to constantly be changed. We can also set it up so people can change their "secret" and "salt" words (and therefore their private key) any time they like - and still keep their PIN (a novel idea)!

Would doing this keep from having to move funds to another address as well?

sshelton76 commented 5 years ago

First, we DO need to collect users' email addresses - as well as other info.Aren't we going to have to do AML/KYC? Besides, we need some way to alert them to anything which may affect their account (in the ultimately unthinkable case it should happen).

That's more of a business use case than a software thing. AML/KYC isn't relevant to the XVault software, but we have hooks in to manage accounts for people who are AML/KYC in the future. For now though, this is just about the secrecy of your private keys which is of paramount importance.

Second, will we automatically change their addresses and force people to change PINs after a certain length of time? I know some banks do force PIN changes. Or, will we simply instruct users this needs to be performed by them, voluntarily, and send them an email, or reminder on their screen?

There isn't much we could automate here. But PINs are local to the device. They never leave it. It's just a thing to help securely encrypt the private keys when not using them.

PS, what is "secret" and "salt?" Can these words simply be changed (and then forgotten) by the customer every so often? Can we just explain to the user we need two random words from them to change some background security features, and they do not need to remember these words, but this will change the private key and they should record the new key? Or, are the words global in nature?

These are cryptographic terms. A hash function will always provide the same output for any given plaintext. So we call the input to the hash function a "secret", in this case your PIN. Since each hash function by definition always outputs the same output for any given secret, it becomes possible to pre-image, that is to calculate ahead of time all the possible inputs for any common input such as a password or a PIN. With these pre-computed outputs it becomes possible bypass security if the hash is known. Therefore you append something to the secret to make it far less likely to be something that could be pre-computed. This is called salting and the thing you append is called a salt.

Imagine I have a stupidly simple PIN of 0000000000 , the SHA512 hash of that is going to be 7D59108361C6C587B91C2B024EA74CC88F4AC12609F5D77A778FCD439E7D15288139FD60FB5C47A60115C03EFBB830686AE4BE9FBD236AAE6D064F789501DF97

This is long and complicated, but anyone who sees that hash somewhere can rest assured that the input to the SHA512 function was 0000000000.

Yet if I salt it by appending the creation time or something, it turns into 1A7BA7BE19E149C9749BF24CA8E11F77FFE73078DD7462E47F3F6DDE98BF4CBFA026731A27E4BFD590A358A031767CBF03F2C6A4C233779D4E1417B3DD7C910C

I can store the creation time safely for later lookup and no one who encounters the hash is going to be able to re-compute it in order to discover the initial secret, without trying a whole lot of combinations again.

If changing the "secret" and "salt" words is feasible on a per-user basis, PINs don't necessarily need to be changed (but still can be). I hate to change PINs. We have SO many to remember anymore and they are becoming difficult to remember when they have to constantly be changed. We can also set it up so people can change their "secret" and "salt" words (and therefore their private key) any time they like - and still keep their PIN (a novel idea)!

Again, your PIN is entirely local at the moment. But by doing this, the hash of your PIN is public. Eventually someone is going to recover it by brute force. Thus changing it before that can happen is of paramount importance.

Would doing this keep from having to move funds to another address as well?

No because moving funds around serves a number of purposes. First off, it allows for blockchain pruning. Once money is moved, old state information can be pruned out. Secondly each time you sign a transaction with a private key, a little tiny bit of that key leaks. We don't really know how much at the moment, but all digital signature algorithms leak at least a little. It is entirely possible that after a year or so of daily use, that enough of your key has leaked that reconstructing it becomes trivial. Therefore it is important to move funds to a new address before that can happen. Satoshi knew this and it's why Bitcoin addresses are supposed to be one time use only. Good key hygiene would dictate the same for ethereum, but ethereum doesn't practice good key hygiene so it is upon us to ensure that our own keys remain protected by changing them out from time to time.