2

Here are my conditions:

  • My Rest API must accept username and plain password. But, that's very bad. That's why the client must encrypt the password first and my rest API will decrypt it to get the plain password.
  • My client and rest API already set the encryption method using AES-256.
  • The key and iv for AES-256 are generated by PBKDF2 with the hash algorithm using SHA256.
  • This passphrase for PBKDF2 input is fixed and hardcoded. To prevent guessing the passphrase, I added salt to PBKDF2.
  • The salt is created using randomizer engine and create every time PBKDF2 is used.
  • The client sends the encrypted password alongside the salt appended to it so the rest API can create the key and iv for decrypting the password.

What I'm not so sure is the bold part of my conditions.

  1. Is it okay to create random salt every time PBKDF2 is used? Because when I'm looking for example use of PBKDF2, the salt is created for one password and keep alongside the password in the database. The salt will never be changed except if the user changes their password. But, if I think about it, the salt is used for creating the key and iv for AES-256, not for hashing password. So, I guess it's okay to create random salt every time PBKDF2 is used?
  2. Appending salt to the encrypted password makes me anxious because I feel like the salt isn't useful when shown plainly. Is this okay?

3 Answers 3

4

My rest API must accept username and plain password. But, that's very bad.

No, it's not. Username and plain password is how almost everything works. You just don't send them clear in the wire, but you use TLS to encrypt the connection.

Appending salt to the encrypted password makes me anxious because I feel like the salt isn't useful when shown plainly. Is this okay?

When a salt is useful (for storing hashed passwords, for example), it doesn't have to be secret. You use it to hash along with the user supplied password so you can protect the passwords when two or more users have the same password. Like the IV, they must not be derived from the password and not be reused. And the security of the password storage must not depend on the secrecy of the salt.

Adding your own security layer on top of TLS will not increase the security of the API, and will be detrimental to performance. An attacker can just throw random requests at the API and your server will have to compute a costly PBKDF2 random key and IV.

So just make sure to use TLS, don't use the deprecated SSL nor TLS v1.0 nor TLS v1.1, and you are all set.

2

Regarding your direct question: PBKDF2 already requires a salt value that you must provide: there is no need for you to add another one outside of it.

The properties required of your IV depends on the mode you use with AES but it shouldn't be derived from your password, even indirectly.

A more general critic of your scheme:

By using a hash with a "salt" that changes every time, you force your server to perform the same validation as the client using the (clear text) password as source. Unless there are other critical reasons why you want this, this is not desirable because that means you're storing all the passwords with (at best) reversble encryption on your server.

Another fault is that what you have designed is vulnerable to a "pass the hash" attack: your (salt, hash(pwd)) tuple is now equivalent to the password: that is the only thing that the attacker needs to capture in order to compromized the credentials. If you're using HTTPS (correctly), it is decently protected. If not, you offer little added security anyway by sending a hashed version of the password over sending the cleartext password (basically, you don't really have a compromized password, just a compromized account) and that advantage is aguably insuficient to compensate for the increase risk of storing all your passwords in a reversible format server side.

Finally, even if you somehow do not care about these issue, you end up needing to implement your AES-256 encryption in a secure manner which is no easy feat if you do it yourself and, if you're already using TLS is redundant and unnecessary (hurts performance, increases complexity).

TLDR: if you're using TLS correctly, what you're doing is (mostly) unnecessary and actually hurts security. If you're not, it's pointless anyway.

2

TL;DR: Drop everything you've designed here, use HTTPS, and use a better authentication flow. This scheme is irredeemably broken.


My Rest API must accept username and plain password.

Why? Generally, those credentials are only needed for things humans interact with directly, and APIs are instead interacted with through some kind of software client. It should be possible for that client to store and use a different kind of credential, such as a long-lived cryptographically random one (an API key) or a shorter-lived one received in exchange for the username+password (a session token). For minimal server-side state, people often go with a hybrid approach: a very-short-lived signed-but-stateless token (typically a JWT) plus a cryptographically random token used when the stateless token expires (a refresh token).

But, that's very bad.

There are reasons why it's not great, but they aren't the ones you seem to think they are. The main reasons are: primarily, securely hashing passwords is too computationally expensive, so you don't want to do it on every request; secondarily, you ideally don't store memorable long-term credentials (passwords) anywhere, even in the RAM of the client.

That's why the client must encrypt the password first and my rest API will decrypt it to get the plain password.

There's already a protocol for that. It's called TLS, the successor to SSL, and is the protocol used to secure HTTPS and a bunch of other stuff too. It's on its sixth publicly released version (counting SSL and TLS together), and the first four (SSL2, SSL3, TLS1.0, TLS1.1) are all deprecated due to security bugs. It's really hard to write a secure communication protocol from scratch. Do NOT attempt to do this yourself! You should really be using HTTPS anyhow... which, among other things, ensures that everything sent over the connection (including the credentials, whatever they may be) is encrypted securely, via code and protocols written and reviewed by experts.

My client and rest API already set the encryption method using AES-256.

And... what mode of operation? If it's not a stream-like mode, how are you padding and how are you avoiding padding oracles? How are you authenticating the messages? How are you authenticating the server and preventing MitM? Do you have replay protection? Are you the least bit unsure of the answer to any of these questions, or of why I asked them? If you are, STOP, you should not try to do this on your own. (You probably shouldn't anyhow, these are necessary but not sufficient considerations.) Again, HTTPS (via TLS) takes care of this for you already, assuming you use version TLSv1.3 (or 1.2 and avoid weak cipher suites).

The key and iv for AES-256 are generated by PBKDF2 with the hash algorithm using SHA256.

PBKDF2 is actually a rather old password hashing algorithm - there are much better ones available now, which provide better security against brute-forcing the passwords - but you've got far bigger problems. Also, why are you generating the IV using PBKDF2? The IV is public! It should be unique, and in some cases needs to be unpredictable, but it can and should be just generated using a secure (P)RNG and sent in plain text along with the ciphertext (in this case, the encrypted credentials).

This passphrase for PBKDF2 input is fixed and hardcoded. To prevent guessing the passphrase, I added salt to PBKDF2.

So many things wrong here!

  • Never hardcode keys. That's a terrible idea in every way. They're impossible to rotate, mean you can't have outsiders audit your code, have so many avenues for exposure anyhow, and even if you manage to avoid exposing them through some other means, anybody who gets a copy of your client can extract them from there.
  • What kind of client is this, anyhow? If it's a desktop or mobile app, it might actually require non-zero skill to reverse engineer the thing enough to extract the hardcoded key, but it still won't be hard. If it's a web app (including a packaged web app in a mobile or desktop app), a browser extension, or otherwise implemented using any scripting language, it'll be trivial to extract the key.
  • Using a random salt with a hardcoded "password" is like putting a deadbolt on a waist-high fence gate. A system is only as secure as its weakest link, and the weakest link here is not the presence or absence of salt.
  • In fact, the salt doesn't even meaningfully impede determining the hardcoded passphrase through brute force. After all, the salt will be visible to the attacker, so they can simply include it as a parameter on any attempt to brute-force your passphrase. Not that they need to, because there are easier ways to get it, as mentioned above.
  • Why is guessing/brute-forcing the hardcoded passphrase in a system like this even remotely a consideration? You are using a cryptographically random string with at least 128 bits or so of entropy, right? Right? That's de facto impossible to guess or brute force. This doesn't actually matter, because the worrying about the guessability of a hardcoded key is like worrying about the findability of a single button on the outside of your car that unlocks all the doors, disables the immobilizer, and starts the engine; why do you even have that button?
  • Given the system described here, why are you even using PBKDF2 (or any password hashing function)? You're combining a random value (the salt) with a fixed but "secret" value (the passphrase) to generate a hash digest of a particular length. There's already a cryptographic construction for that, it's called an HMAC and it's wayyyyyy faster to compute than PBKDF2. Of course, normally the slowness of PBKDF2 is part of the point, but that totally doesn't apply here.

The salt is created using randomizer engine and create every time PBKDF2 is used.

What "randomizer"? Is it cryptographically secure, or can somebody predict it having seen a few values (or just knowing the time of day)? How long is the produced salt; how much entropy is it actually adding? It's the only source of actual entropy (randomness) here, after all; the "passphrase" is fixed.

The client sends the encrypted password alongside the salt appended to it so the rest API can create the key and iv for decrypting the password.

More problems...

  • Sounds like the client is generating the salt? That probably means you have no replay protection; an attacker who sees one salt + encrypted credential pair of some other user can just keep sending it again every time, and the server will accept it every time. Have I mentioned enough times that TLS solves this problem?
  • You're making your server run PBKDF2 on every request. The server will spend some amount of time (depending on the work factor a.k.a. cost parameter of your PBKDF2) computing the encryption key each time. The client doesn't need to, though; it can precompute and cache the salt + encrypted credential pair, or even just send nonsense, so the work is asymmetric in the client's favor. That can be abused by an attacker to launch a denial-of-service attack on your API. Of course, this is a threat for all well-implemented login interfaces, but usually you can handle it by rate-limiting that one endpoint and instead you have this problem on every single endpoint.
  • One does, in fact, generally try to avoid exposing the salt if possible. But then, none of this is how anything is "generally" done. Exposing the salt is bad only because it lets the attacker start building a rainbow table (precomputed lookup table of brute-forced passphrase candidates) which is just entire irrelevant here.

What I'm not so sure is the bold part of my conditions.

It is impressive just how much those parts are not the problem, actually. Like, they're not great, but they're so incredibly far from being the weakest link, and indeed it's not clear that they're affecting the total security of the system at all.

Is it okay to create random salt every time PBKDF2 is used? Because when I'm looking for example use of PBKDF2, the salt is created for one password and keep alongside the password in the database. The salt will never be changed except if the user changes their password. But, if I think about it, the salt is used for creating the key and iv for AES-256, not for hashing password. So, I guess it's okay to create random salt every time PBKDF2 is used?

Security-wise, it's not great, but not for the reason you think. It's not adding anything, because the entire scheme is completely hopelessly broken. It's not costing you anything in terms of the security of the authentication, for the same reason. It does mean you have to run a CPU-expensive operation on the server all the time, though, which is (as mentioned above) a DoS vector.

Appending salt to the encrypted password makes me anxious because I feel like the salt isn't useful when shown plainly. Is this okay?

The way you're using it, the salt isn't doing anything. Without replay protection I don't need to know anything at all about how your auth scheme even works, just copy the entire credential blob as an opaque bag of bits and re-use it at will. However, if you had replay protection, then the salt becomes the de facto key used to encrypt the credentials (the "passphrase" is hardcoded and thus assumed known, and PBKDF2 is a deterministic function so it adds no security if the attacker knows its inputs). Unfortunately, it's necessary for this system that the client and server both know this "salt" - the classic key distribution problem - and you have here no way of solving that without exposing it to third parties. There do exist secure key derivation / distribution functions, of course. They are used in TLS. One could use them elsewhere, securely, with enough work, but... why bother?


IF YOU ARE NOT USING TLS, then even if you fixed the authentication scheme's many problems (somehow), you'd have another problem: all of your request and response data would be exposed, in plain text, to the attacker. Even without the ability to forge requests, steal passwords, or replay credentials (or even whole requests), a passive attacker could log every requested or uploaded bit of data, and every returned result. A MitM (Man-in-the-Middle) attacker could arbitrarily manipulate those requests and responses as desired, unless your anti-replay feature additionally authenticated the messages in an unspoofable way.

IF YOU ARE USING TLS (correctly, at least), it solves almost everything. You still should switch authentication schemes, for reasons outlined near the top of this answer, but aside from the DoS vs. password hashing strength risk, and the very slight risk of exposing the password if an attacker gets a look at the client, it would be secure to just send the username + password. There's definitely no point to this overcomplicated scheme that just wastes CPU cycles and introduces opportunities for error.

You must log in to answer this question.

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