3

I've built an API that allows users to submit encrypted data to be stored in the cloud. To retrieve their data, the API requires the user to supply only the MongoDB Object ID that they are given when intially submitted their data, plus the SHA1 hash of the password they used to encrypt the data. I chose to use both the Object ID and the password hash as a unique key to prevent users requesting any data over the API, as different Object IDs are not complex and easy to guess.

I've now switched the hash method from SHA1 to bcrypt as it is more secure, but that has presented an issue. Because bcrypt generates a different salt every time, the hash is always different meaning I can no longer use the generated hash to identify the user's data in the database, unless I force the app to use the same salt each time it generates a bcrypt hash. Note: if I use a constant salt, it will be known to the public as the source code is open source.

My question is, if I use a constant salt with bcrypt for generating password hashes, does this make the hashes easier to decrypt than using SHA1 as I was originally, and if not is it still an impractical approach to creating secure password hashes?

Alternatively, is there a better approach for specifying a unique key for the user's data in the db without asking the user for more information?


Edit: In trying to keep the question as simple as possible, I've realised that I haven't really provided enough context for why I chose the approach I have, so here's a little more information to hopefully mae things clearer and so that people can provide as accurate as possible an answer.

The intention is to create a secure and anonymous service to support the app, that can be run by anyone, anywhere. The app and API are open source and so anyone who wishes to provide their server space for other users of the app can run their own instance of the API. Users of the app can then point it at the API url of their choice, this allows them to use any public-hosted instance of the API, or even host an instance on their own server which only they can use.

Because user's data could be sitting on any public server, it is important that the data is encrypted client-side and that the user never sends their cleartext password over the wire, in case the API server owner (or anyone else) wishes to decrypt the user data being hosted. The service is anonymous for reasons of security and convenience, hence the app user does not have to provide an email address or any other identifying information, they just click a button in the app and their data is encrypted with their password, which is sent to the API along with their hashed password:

Post body example:

{
    "data":"DWCx6wR9ggPqPRrhU4O4oLN5P09onApoAULX4Xt+ckxswtFNH/QQ+Y/RgxdU+8+8/muo4jo/jKnHssSezvjq6aPvYK+EAzAoRmXenAgUwHOjbiAXFqF8gScbbuLRlF0MsTKn/puIyFnvJd...",
    "passwordHash":"d4de34f15aeadfb34bdf1fbbd57134b2baeb142c"
}

Response:

{
    "id":"507f191e810c19729de860ea",
    "lastUpdated":"2016-07-06T12:43:16.866Z"
}

The mongodb entry would look like this:

{
    "id": "507f191e810c19729de860ea",
    "passwordHash": "d4de34f15aeadfb34bdf1fbbd57134b2baeb142c",
    "data": "DWCx6wR9ggPqPRrhU4O4oLN5P09onApoAULX4Xt+ckxswtFNH/QQ+Y/RgxdU+8+8/muo4jo/jKnHssSezvjq6aPvYK+EAzAoRmXenAgUwHOjbiAXFqF8gScbbuLRlF0MsTKn/puIyFnvJd...",
    "lastUpdated": "2016-07-06T12:43:16.866Z"
}

A user can retrieve their data with a GET request to /api/data/{id}/{passwordHash}, so for the example above:

/api/data/507f191e810c19729de860ea/d4de34f15aeadfb34bdf1fbbd57134b2baeb142c

Response:

{
    "data":"DWCx6wR9ggPqPRrhU4O4oLN5P09onApoAULX4Xt+ckxswtFNH/QQ+Y/RgxdU+8+8/muo4jo/jKnHssSezvjq6aPvYK+EAzAoRmXenAgUwHOjbiAXFqF8gScbbuLRlF0MsTKn/puIyFnvJd...",
    "lastUpdated":"2016-07-06T12:43:16.866Z"
}

Since the id value (MongoDB object ID) is not overly complex, a malicious user can potentially guess someone elses id and retrieve their data over the API. For that reason I decided to use the id along with the user's password hash as the key to make it more difficult for a malicious user to guess someone else's unique key. However, if I only used the object id as the key and even if they could guess another user's id, they would only retrieve their encrypted data (not the password hash), which they would then need to decrypt before reading the data. Would this be a better alternative to storing the password hash? Ironically, this would then answer the bcrypt problem as if I were not storing the password hashes on the server, I would not need to hash the password (used to encrypt the data client-side) at all...

So in conclusion, the question as it relates to my app/api is a lot more complex than the question let's on, but I couldn't really think as to how to frame it properly (perhaps I need to change the question). However, if anyone has any opinions on the security aspects of my approach given the requirements, I'd be very grateful for the input.

  • is it possible in MongoDB to make the object ID longer? or use another database? or generate your own long random ID with a cryptographically secure random number generator? – noɥʇʎԀʎzɐɹƆ Jul 12 '16 at 14:28
6

If you're using a constant salt, then it's not bcrypt:

An important requirement of any bcrypt implementation is that it exploit the full 128-bit salt space.

— A Future-Adaptable Password Scheme

Because bcrypt generates a different salt every time, the hash is always different meaning I can no longer use the generated hash to identify the user's data in the database

Sure you can – since you already have the salt from the previous hash, use that when generating the new hash.

  • Thanks Jim, point taken. I've added more context to the original question, I'm thinking that I can avoid storing the password hash on the server at all (data is encrypted client-side) and the user just uses their id to retrieve their encrypted data before decrypting client-side. That way, I don't have to hash the password (or even send it over the wire) at all... – nero120 Jul 12 '16 at 8:35
  • 1
    @nero120 hashing ≠ encryption. encryption is two-way. hashing is one-way. – noɥʇʎԀʎzɐɹƆ Jul 12 '16 at 13:46
  • Marked as answer as I thought it was the most concise and relevant to the literal question title. – nero120 Jul 13 '16 at 10:46
6

Disclaimar: Please note that this answer was written before more details were added to the question. Some points therefore no longer applies, while others still do.

Problems with your approach

[...] of the password they used to encrypt the data.

Are you using passwords as encryption keys? That is not a good idea, since passwords picked by users have low entropy and can therefore be brute forced.

To retrieve their data, the API requires the user to supply [...] the SHA1 hash of the password [...].

The user should send the password, not the hash. Having the user sending the hashes effectively makes the hash the password, which means that you store the effective passwords (i.e. the hash) unprotected. Look up "pass the hash" for more information.

But how can you even decrypt if the password was used for encryption, and you only get the hash from the user?

Because bcrypt generates a different salt every time, the hash is always different meaning I can no longer use the generated hash to identify the user's data in the database, unless I force the app to use the same salt each time it generates a bcrypt hash.

Why would you use the hash to identify the data? You use the MongoDB object ID for that. You use the password and hash for authentication.

If you do not pass the hash, but instead pass the password, you can use bcrypt with individual salts for authentication. Generate a random one for each user and store it in the database. Then when the user sends a password you hash it using that salt, and compare. Any library that does bcrypt should have a function for this.

An outline of how to do this right

When the user saves her data to your server, she provides a password. You generate a unique random salt and store that in the database. Then you hash the password using bcrypt and that salt to get an encryption key. You use this key to encrypt the data.

When the user wants to access the data, she provides the password again. You hash it with bcrypt and the stored salt to get the key. You use that to decrypt the data. Finally you check a MAC to see that the decryption did indeed work - if the password was wrong you would get the wrong key, and then the MAC would not match.

Please do note that here are loads of fine print to this, so don't take the above two paragraphs as the complete guide.

Finally, are you sure you want to roll your own here? There are probably already existing solutions that does what you want to do. If you program your own your risk making a tiny mistake and have all your users data stolen.

  • Thanks for the response Anders. I realised after reading your response that I hadn't provided nearly enough info to ask people to suggest a better approach, so I've added more context to the original question. I'd be grateful if you could take a look and see what you think. Regarding using passwords as encryption keys for the data, I'm using a javascript implementation (crypto-js) of AES. What do you think would be a better approach than allowing the user to specify a password to encrypt the data, whilst still being convenient for the user? – nero120 Jul 12 '16 at 8:31
  • I still don't understand what the encryption key is. Would it be acceptable to have a random encryption key that the user has to store on his own? Or must the key somehow be derivable from the password alone? – Anders Jul 12 '16 at 8:36
  • The user enters a "password" in a form in the app whose value is used as the AES key to encrypt their data client-side. I chose to use the password as the encryption key out of convenience for the user more than anything. I can enforce some password constraints such as length, complexity, etc, that should help to make the encryption more secure. Is AES really that easy to decrypt with a weak key then? – nero120 Jul 12 '16 at 8:52
  • This is getting to broad. You are basically trying to create a complete protocol. That is a very big task, and it would require security review on a level that wont fi into a post here. I don't think I can provide any valuable input other than looking for existing solutions instead of rolling your own. – Anders Jul 12 '16 at 8:59
  • @nero120 are you sure you couldn't try an existing system such as BONIC? remember - don't code up your own system, those are the most insecure. – noɥʇʎԀʎzɐɹƆ Jul 12 '16 at 14:24
1

You shouldn't use either!

Using a constant salt defeats the purpose of using bcrypt. Use a variable salt! Just like SHA1, it's easily cracked. It's like asking - what's more secure, a wooden box or a thermite-proof triple-reinforced steel box with a wooden door? Excellent explanation by Tom Scott.

IMHO your code is probably vulnerable to SQL injection because of your code smells. It smells a lot. Check your code for SQL injection, XSS, CSRF, etc.

enter image description here

(all of these have been decrypted.)

Addendum

If you're going to encrypt data, use a public/private key system. However, this will prevent everyone but the user to see the data - including you the admin. Don't encrypt data with passwords - encrypt data with proper keys. I'd recommend RSA-2048 if information isn't that sensitive, RSA-4096 for guaranteed tin-foil-hat security (the NSA uses this), and RSA-8192 if you know government spies will be trying to decrypt your user's information and/or trillions of dollars will be hinging on the security of your database.

You can still use hashing to identify user data - just include the salt with the data and hash the user data with the salt.

You could have the client generate a signature to verify the identity of the user, and you could act as a certificate authority. And instead of personally identifying information, you could use a random UUID. But being able to identify the user defeats the purpose of anonymity.

  • 1
    A little snarky for a response, but I think the content (and recommendation) are sound. – HashHazard Jul 11 '16 at 17:00
  • Interesting point, thanks James. I'd suspected that about bcrypt and it felt wrong to force a constant salt when it was designed to generate a new one with each hash. I've added more context to the original question if you'd like to take a look, I would appreciate your opinion regarding the security considerations... – nero120 Jul 12 '16 at 8:38
  • @nero120 If you have crypt with a variable salt - even if the hashed passwords and salts were public it still wouldn't be possible to know the user's password. – noɥʇʎԀʎzɐɹƆ Jul 12 '16 at 14:07
  • @nero120 I highly recommend you watch the video. I would've put the same explanation in my answer if it weren't that he could explain it better. The video is essentially a part of my answer. – noɥʇʎԀʎzɐɹƆ Jul 12 '16 at 14:26
  • Thanks @JamesLu, I've realised I don't actually need to store the password hash so I'm changing my design to exclude it. Encryption will be done client-side and the client will be returned the id after submitting the encrypted data, the server doesn't need to know anything else. – nero120 Jul 13 '16 at 10:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.