Take the 2-minute tour ×
Information Security Stack Exchange is a question and answer site for Information security professionals. It's 100% free, no registration required.

I'm designing an API in PHP/MySQL that, by its design, will not store a user's password in the database and thus does not generate authorization tokens for the client to hold. The reason for this is to prevent any possibility of sensitive, unencrypted information being obtained in the event of a database compromise.

The user's sensitive data is encrypted using their own password, so that the decryption of the data cannot be done with anything stored within the (potentially compromised) database. I've implemented some security features currently:

  1. As previously described, the sensitive data is encrypted using AES using the user's SHA-512 hashed password (the password is provided with every request as form data), plus some extra salt.
  2. HTTPS is required, and only POST requests are permitted.
  3. User accounts are identified by e-mail addresses (trimmed and lower-cased in storage) and require verification before use.
  4. User passwords are required to be a minimum of 8 characters long.
  5. Eventually, an API token will be required for third-party app usage via the Authorization header. Third-party apps are restricted in that they cannot create, verify or delete accounts.

My question is, is this enough? I'm hardly a security expert and the last thing I want to do is design something with a major oversight. My main concern is the user's password being provided with every request, even though it would be only through SSL.

Edit:

To clarify, users will be able to change their passwords provided they still have their current one. A request is made containing the user's e-mail address, the current password, and the new password. The request is authenticated like it normally is, using the e-mail address and the current password. Validation is done on the new password (enforcing the 8-character minimum) as well, before continuing.

First, just as it's done when the user wishes to retrieve their sensitive information, the information is decrypted using their current password. In its decrypted state, the information is then encrypted using the new password, and the sensitive data is overwritten in the database.

At this point, there is no stored record of the previous password and it can no longer be used. A password change cannot be done by an administrator or, in any case, without the password that was used to previously encrypt the information. If the password is lost, then nothing can be done.

share|improve this question
3  
First problem you need to think about is what happens when the user changes his password. This basically invalidates the whole scheme. –  AviD Oct 16 '13 at 22:55
    
I should have mentioned that. Currently, the user will provide their e-mail, their current password and their new password. The data is decrypted with the current password and re-encrypted with the new password. –  William Thomas Oct 16 '13 at 23:05
1  
Not that simple. You would need to re-encrypt ALL the previous data. And, you are now reusing the encryption key for multiple purposes. Perhaps you can share more context? –  AviD Oct 16 '13 at 23:38
3  
I would also point out "I'm hardly a security expert and the last thing I want to do is design something with a major oversight" - stop 4 words sooner. If you are not a security expert, don't be designing a security architecture at all, just reuse one that was already designed by security experts. Heck, even security experts should usually not design a new cryptographic scheme, either. –  AviD Oct 16 '13 at 23:39
2  
An obvious mistake is that you use SHA-2, not a proper password hash. Calling an API that only uses POST a REST API is a bit dubious as well. –  CodesInChaos Oct 17 '13 at 14:49
show 4 more comments

3 Answers

up vote 1 down vote accepted

Your idea is not unreasonable. Encrypting a user's data using a key that can only be generated when the user is present is a reasonable tactic, and is in fact used in a number of high-security environments. This can provide significant safety if done right. But I would introduce the following caveats and modifications:

  • Stateless authentication should be avoided. It's categorically worse than session-limited authentication. Yes, always. There are multiple reaons, but one of which is that if passwords are sent with every request, then passwords are cached somewhere. Ideally, the user should log in, which "unlocks" the datastore and gives the user a time-limited session key which can be used to access the "unlocked" datastore for a limited time only. This allows you to expire authentication sessions and helps prevent a number of potential attacks. Note that this requires a subtle design change that I'll detail below.

  • If the client's data can be encrypted client-side, then do that. If all you ever see of the user's data is an encrypted blob of bits, then at no point could your server ever put that data at risk.

  • You should consider using a two-step encryption chain. Rather than encrypting the user's data using the password-derived key, you should instead encrypt the valuable data using a totally random key, and then encrypt that totally random key using your user's password-derived key. This allows you to (a) easily change user passwords, (b) introduce more complex authentication schemes (e.g. two-factor), and (c) removes the requirement of sending the password with every request. Simply re-encrypt the datastore key using a temporary key which you derive from a random session token which you give to the user at login. You should also tie in the current time, user's IP, and any other session-specific information that you want to use to prevent sessions-stealing.

  • Use a reasonable key derivation formula. A single pass of any hashing function is not sufficient to prevent an attacker from brute-forcing your encryption key if he gets the encrypted data. Instead, use a function like PBKDF2 or scrypt with a resonable number of rounds. This dramatically increases the amount of work an attacker would need to do to guess a user's password.

share|improve this answer
    
This sounds like the proper schema while maintaining my plan of keeping the user's private data encrypted. My only issue would be the client-side encryption, since if this ever does expand to third-party developers I would worry that the encryption may not be done correctly. However, keeping the plaintext from the server is also a great point. –  William Thomas Oct 21 '13 at 17:48
    
-1 This post does not cover all of the defects present in the proposed design and may provide a false sense of security. –  Rook Oct 28 '13 at 19:03
    
@Rook for example? And please don't just link to a list. Be specific. –  tylerl Oct 28 '13 at 19:35
    
@tylerl To be general, all of the session related vulnerabilities. To be specific, Size of the keyspace for the session id, and session expiration as well as others. –  Rook Oct 28 '13 at 22:20
    
@Rook That was covered, actually. This answer says that while it is reasonable to use the user's password to unlock a private data store, session tracking and login should be done separately. explicitly addressed where session IDs and expiration. Please only downvote an answer after you've actually read it. –  tylerl Oct 29 '13 at 1:24
add comment

Before reinventing the wheel and building your own session manager, read the OWASP Session management Cheatsheet, which will help you avoid common mistakes.

The problem is that the proposed authentication token sha512(password) used to maintain the authenticated state, it is in fact a very understandable session token for a number of reasons:

  1. The worst problem is that this proposed session id has a very small key space. It is lot easier for an attacker to guess sha512(password) than to guess 16 bytes of /dev/urandom.
  2. Encryption != Authentication. Multiple keys can act upon cipher text to yield some result, only the correct key will actually yield the real plain-text. You still have to verify that you have received the exact key and AES-CBC won't do that. The correctness of this key could be verified with a hash function, a CMAC or HMAC. AES in an authenticated mode of operation would work.
  3. CWE-613 states that session ids or "tokens" must expire. By using the password hash, in effect a new password has been created that is sha512(password) that doesn't expire and can be used to authenticate without knowing the plain-text password. Once this value is compromised an attacker can access the account.
  4. SHA512 is bad Key Derivation Function (KDF) and a bad way to store passwords in general (CWE-916). bcrypt, pbkdf2, scrypt are all good options.
  5. If you are being RESTful, then use the proper VERB! Using POST for everything is confusing. Enforcing POST doesn't make the system more secure, these requests are just as vulnerable as GET to attacks like CSRF or other attacks.
share|improve this answer
    
From what I understand, the problem of most of your items is related to using the password both as a "session identifier" and to encrypt the data. In my humble answer, I think I've addressed both. Of course, you did provide far more details on the "why", but I'd be curious to know if there are any other flaws if the suggestions I've made were to be implemented. Also, from what I understand, he's not storing the password anywhere on his side, invalidating your point 4, no? –  jpkrohling Oct 17 '13 at 18:17
    
@jpkrohling An attacker can bruteforce the password used to generate a given ciphertext if the attacker knows what the resulting plaintext will be. Such as searching for an English word or the date. Its still possible brute force the password using an FPGA or GPU if you are only storing ciphertext and not a password hash. –  Rook Oct 17 '13 at 18:20
    
I understand that, but if he is not storing the password, there's no password to be bruteforced. The only option is to brute force the passphrase for the private keys, which is far slower than hashing something with SHA-2. Or am I missing something? –  jpkrohling Oct 17 '13 at 19:27
    
@jpkrohling The term "private key" is reserved for asymmetric cryptography. You just mean secret key, AES's key scheduler and SHA-512 are both very fast and require little memory, thus the CWE-916 violation. –  Rook Oct 17 '13 at 19:35
    
I do mean private key, as in the asymmetric cryptography :-) The password being the passphrase to unlock it, the public key being used to encrypt data before persisting it into the database. I wonder if you have read my answer, to be honest :-) –  jpkrohling Oct 17 '13 at 20:21
show 4 more comments

I think you have a nice architecture there. I think it might prove impractical and/or too slow for real usage, but you mentioned that this is for a personal project, to use as a test bed for learning. In this context, I suppose you should go ahead. You'll learn a lot. :-)

Of course, without looking at the code, it's hard to say whether or not there's a flaw. From what you've described, it seems fine to me. A few comments, though:

  • I wouldn't compromise the RESTfulness of your API. I understand that you don't want to send the passwords via query string in the POST requests, but if you control the client (as I think you do), then you can just send the password via HTTP header, instead of form parameter. This way, you can still do your GET/HEAD/DELETE calls without exposing the password in the URL. Remember that pretty much everything is encrypted in an HTTPS session, even the query string, so, I suppose that the problem is the user "seeing" his password in the URL.

  • I'm not really a big fan of sending the passwords in every request. If you would be willing to introduce a "state" in the API (REST APIs are supposed to be stateless), then you could agree to a pair of keys at the beginning of the session and use the public key to encrypt the password before sending.

  • Also, instead of encrypting your user's data with a password, you could generate a private key with a passphrase, matching the user's password. This way, you can change your user's password without the need to re-encrypt all the data. The disadvantage is that if an attacker gets hold of your database and of your keystore, then a brute-force attack on the keys might compromise some of your user's data. But I guess that if an attacker gets your database and application server, then there's not much you can do to protect your user's data, specially for the users that had some activity during the time of the attack.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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