Are Redis ACL password protections weak?

Earlier this year, I decided to explore Redis functionality a bit more deeply than my typical use-cases would require. Mostly due to curiosity, but also to have better knowledge of this tool in my “tool belt”.

Curiously, a few months later, the whole ecosystem started boiling. Now we have Redis, Valkey, Redict, Garnet, and perhaps a few more. The space is hot right now and forks/alternatives are popping up like mushrooms.

One common thing inherited from Redis is storing user passwords as SHA256 hashes. When I learned about this, I found it odd, since it goes against common best practices. The algorithm is very fast to brute force, does not protect against the usage of rainbow tables, etc.

Instead of judging too fast, a better approach is to understand the reasoning for this decision, the limitations imposed by the use-cases and the threats such application might face.

But first, let’s take a look at a more standard approach.

Best practices for storing user passwords

According to OWASP’s documentation on the subject, the following measures are important for applications storing user’s passwords:

  1. Use a strong and slow key derivation function (KDF).
  2. Add salt (if the KDF doesn’t include it already).
  3. Add pepper

The idea for that computing a single hash should have a non-trivial cost (in time and memory), to decrease the speed at which an attacker can attempt to crack the stolen records.

Adding a “salt” protects against the usage of “rainbow tables”, in other words, doesn’t let the attacker simply compare the values with precomputed hashes of common passwords.

The “pepper” (a common random string used in all records), adds an extra layer of protection, given that, unlike the “salt”, it is not stored with the data, so the attacker will be missing that piece of information.

Why does Redis use SHA256

To store user passwords, Redis relies on a vanilla SHA256 hash. No multiple iterations for stretching, no salt, no pepper, nor any other measures.

Since SHA256 is meant to be very fast and lightweight, it will be easier for an attacker to crack the hash.

So why this decision? Understanding the use-cases of Redis gives us the picture that establishing and authenticating connections needs to be very, very fast. The documentation is clear about it:

Using SHA256 provides the ability to avoid storing the password in clear text while still allowing for a very fast AUTH command, which is a very important feature of Redis and is coherent with what clients expect from Redis.

Redis Documentation

So this is a constraint that rules out the usage of standard KDF algorithms.

For this reason, slowing down the password authentication, in order to use an algorithm that uses time and space to make password cracking hard, is a very poor choice. What we suggest instead is to generate strong passwords, so that nobody will be able to crack it using a dictionary or a brute force attack even if they have the hash.

Redis Documentation

So far, understandable. However, my agreement ends in the last sentence of the above quote.

How can it be improved?

The documentation leaves to the user (aka server administrator) the responsibility of setting strong passwords. In their words, if you set passwords that are lengthy and not guessable, you are safe.

In my opinion, this approach doesn’t fit well with the “Secure by default” principle, which, I think, is essential nowadays.

It leaves to the user the responsibility to not only set a strong password, but to also ensure that the password is almost uncrackable (a 32 bytes random string, in their docs). Experience tells me that most users and admins won’t be aware of it or won’t do it.

Another point made to support the “vanilla SHA256” approach is:

Often when you are able to access the hashed password itself, by having full access to the Redis commands of a given server, or corrupting the system itself, you already have access to what the password is protecting: the Redis instance stability and the data it contains.

Redis Documentation

Which is not entirely true, since ACL rules and users can be set as configuration files and managed externally. These files contain the SHA256 hashes. This means that in many setups and scenarios, the hashes won’t live only on the Redis server. This kind of configuration will be managed and stored elsewhere.

I’m not the only one who thinks the current approach is not enough, teams implementing compatible but alternative implementations seem to share the concerns.

So, after so many words and taking much of your precious time, you might ask, “what do you propose?”.

Given the requirements for extremely fast connections and authentication, the first and main improvement would be to start using a “salt”. It is simple and won’t have any performance impact.

The “salt” would make the hashes of not so strong passwords harder to crack, given that each password would have an extra random string that would have to be considered individually. Furthermore, this change could be made backwards compatible and added to existing external configuration files.

Then, I would consider picking a key stretching approach or a more appropriate KDF to generate the hashes. This one would need to be carefully benchmarked, to minimize the performance impact. A small percentage of the time it takes for the whole process of initiating an authenticated connection, could be a good compromise.

I would skip for now the usage of a “pepper”, since it is not clear how this could be done and managed from the user’s side. Pushing this responsibility to the user (Redis server operator), would create more complexity than it would be beneficial.

An alternative approach, that could also be easy to implement and would be more secure than the current one, would be to automatically generate the “password” for the users by default. It would work like regular API keys, since it seems this is how Redis sees them:

However ACL passwords are not really passwords. They are shared secrets between the server and the client, because the password is not an authentication token used by a human being.

Redis Documentation

The code already exists:

…there is a special ACL command ACL GENPASS that generates passwords using the system cryptographic pseudorandom generator: …

The command outputs a 32-byte (256-bit) pseudorandom string converted to a 64-byte alphanumerical string.

Redis Documentation

So it could be just a matter of requiring the user to explicitly bypass this automatic “API key” generation, to set up his own custom password.

Summing it up

To simply answer the question asked in the title: yes, I do think the user passwords could be better protected.

Given the requirements and use-cases, it is understandable that there is a need to be fast. However, Redis should do more to protect the users’ passwords or at least ensure that users know what they are doing and pick an almost “uncrackable” password.

So I ended up proposing:

  • An easy improvement: Add a salt.
  • A better improvement: Switch to a more appropriate KDF, with low work factor for performance reasons.
  • A different approach: Automatically generate by default a strong password for the ACL users.

By Gonçalo Valério

Software developer and owner of this blog. More in the "about" page.

5 replies on “Are Redis ACL password protections weak?”

@content Nice read! I don't think Valkey is going to change the default KDF or adding salt by default, it's too much of a breaking change. We added the ability to implement a custom authentication mechanism though modules, so I'm curious to see if anyone uses that to add a stronger authentication mechanism.

Interesting, I will look into that approach. I wasn’t aware of it. Do you think my third proposal, of automatically generating a strong password by default for new ACL users, just like an API key, is also “too much of a breaking change”?

Thanks for the feedback.

@content We could do it pretty easily in a non-breaking way. Either have `ACL SETUSER foo <something>` generate a password, set it, and return it for the user to store. We could update all of our docs to make that the "preferred" way to generate a password, so nobody tries to set one themselves. By default we don't generate any password.

Leave a Reply

Your email address will not be published. Required fields are marked *