Hacker News new | ask | show | jobs
by emidln 4462 days ago
I'm pretty sure the intention behind something like this:

    (def a-password (-> pass
                        (hmac server-stored-key)
                        (scrypt-kdf work-factor))
is to prevent all information necessary to calculate the stored hash being on the same system where the hash is stored. By HMAC'ing and not storing that key in the database (but on the app server), you now make the attacker pull off two attacks.

You could replace HMAC with encrypt with AES (or scrypt for that matter) to get the same practical effect.

Edit: clarification

2 comments

I think this is actually works. Might want a configurable work factor for the kdf.

    (ns passwords
      (:require [pandect.core :refer [sha256-hmac]]
                [crypto.password.scrypt :as scrypt]
                [environ.core :refer [env]])) 

    (def SECRET-KEY (if-let [key (env :secret-key)]
                       key
                       (throw "Set your SECRET_KEY!!!")))

    (defn encrypt-password 
      [pass] 
      (-> pass (sha256-hmac SECRET-KEY) scrypt/encrypt))
Edit: update crypto-password code to actually call encrypt.
The problem of lookup table attacks is solved by the public salt I already mentioned. HMAC protects against attacks like length extension (google it), which aren't applicable for discovering a password from a hash.

Encryption also does nothing here, because it's reversible.

Your encryption key is somewhere on your server, so whatever you encrypted (either the plain text password, or the hash) can be decrypted once, and then if it's a hash you just attack the bare hash as usual (brute force, dictionary etc.).

Instead of encryption you can just store part of the salt outside the DB, but with a good hash this is usually not needed and just pointless obfuscation.

Consider this scenario (which is extremely common):

Public access to a SQL database. Input isn't properly sanitized while building some complicated query. $badnick executes a SQL injection. Luckily, the database user from the public site only has SELECT privileges (yay SOA or something). This is an extremely common enterprise situation.

The attacker can dump data (like password hashes), but in an HMAC'd password setup, cannot actually gain access to modify or login as a user via brute-forcing. They cannot pivot the user account to access to another system. The attacker must find a different hole to gain access to the app/login server.

Well first of all your "common scenario" terrifies me, and betrays an app developed by incompetent devs who wouldn't even know what a "hash" is, because in any sane situation:

1) The SQL database is not public (any more than it's common to have public anonymous FTP to your server's app deployment). It listens on a specific interface, specific port, from specific IPs with a specific user and password (which is not "root"/"").

2) Input isn't properly sanitized? Hello? If that's "common" then fix that first. It's not 1998 anymore, no one has an excuse for that level of ignorance. First of all "sanitize" is an incorrect term for what needs to be done. You either escape an SQL literal, or you pass it as a parameter to a prepared statement. You do this to all data, all DB layers provide escaping, and it's absolutely trivial to do. And for prepared statements, there is no way to do it wrong, because it's not you who does it. Data and query in prepared statements are separated at the protocol itself and injection is impossible, you can't possibly mess it up. All of this is "using databases 101". Heck, your app will break even with innocent data containing, say, a quote, if you fail those.

3) I mentioned twice already, there's public salt for hashing the password, and there's irreducibly slow hash to be used to protect against a weak password (like Blowfish). This is what breaks brute-force attacks, not HMAC.

4) Let's assume your exact scenario is indeed terrifyingly common. Still, HMAC does nothing at all here, that keeping part of the salt off-the-DB won't do. HMAC doesn't mean "when you have a key outside the DB". You don't need HMAC to keep part of the salt outside the DB. Just... keep part of the salt outside the DB, without HMAC.

But if you feel the need to obfuscate your code like this, it means you're working in hell and some people don't deserve their jobs, and better fix that, before you start thinking about compensating for it at the wrong place of the app.

I should clarify that when I say "public access to", I mean specifically that a public person can access an application talking to the SQL database.

I.e. User <-> App <-> Database

Input is simply not properly sanitized in the real world because mistakes happen. This isn't by design. This is a source of remote execution. The remote execution only yields read access since the app only needs read access (and thus the fewest privileges possible are read access). The attacker can read password hashes, but cannot update them.

As I mentioned before, HMAC is just an example of keeping all information necessary to login to the site out of the stored hash. Any non-constant value per password will do (feel free to maintain a lookup table of unique lists to active passwords in your app if you feel you must, I'll be HMAC'ing the user's input password against a constant secret key before sending to KDF). The idea is that an attacker who can read your database (but not the application code) cannot gain access as a user without first also compromising the application code.

Once again there's no way to make a mistake with a prepared statement. The only way to do it is using poor practices like using half baked "sanitizing" functions which no competent developer will use.

And once again, you don't need HMAC to store salt outside the hash. You just don't. HMAC doesn't dictate where you store your salt. You're bundling these two things together as if they're inseparable, but they're two completely separate things.

Security's something you do in-depth. You might build a perfect app, which correctly sanitizes your DB inputs (and you're right, it's easy these days). That won't necessarily stop $developer making a little PHP interface long after you leave, which exposes your DB again.

I'm not up to the play with HMAC so can't confidently comment on that, but the point being made is that there is lots of SQL injection out there. Keeping the salt out of the DB seems like a not-insane way to help mitigate a compromise.

in any sane situation:

1) The SQL database is not public (any more than it's common to have public anonymous FTP to your source code). It listens to a specific interface, from specific IPs with a specific user and password (which is not "root"/"").

Yes, mostly true, but in the common case passwords are shared between users, and in corporate apps there are some widely shared "admin" accounts that are user accounts with some added capabilities, or some such bad practice.

you pass it as a parameter to a prepared statement. You do this to all data, all DB layers provide escaping, and it's absolutely trivial to do. And for prepared statements, there is no way to do it wrong

Yeah, so what exactly is wrong with SQL Korma? I thought that would be one succinct and abstracted way of making sure all requests are through prepared statements.