5

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.

9
  • 3
    First problem you need to think about is what happens when the user changes his password. This basically invalidates the whole scheme.
    – AviD
    Commented Oct 16, 2013 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. Commented Oct 16, 2013 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
    Commented Oct 16, 2013 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
    Commented Oct 16, 2013 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. Commented Oct 17, 2013 at 14:49

2 Answers 2

1

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.

5
  • 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. Commented Oct 21, 2013 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
    Commented Oct 28, 2013 at 19:03
  • @Rook for example? And please don't just link to a list. Be specific.
    – tylerl
    Commented Oct 28, 2013 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
    Commented Oct 28, 2013 at 22:20
  • 1
    @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
    Commented Oct 29, 2013 at 1:24
2

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.
9
  • 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? Commented Oct 17, 2013 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
    Commented Oct 17, 2013 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? Commented Oct 17, 2013 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
    Commented Oct 17, 2013 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 :-) Commented Oct 17, 2013 at 20:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .