In some circumstances, peppers can be helpful.
As a typical example, let's say you're building a web application. It consists of webapp code (running in some webapp framework, ASP.NET MVC, Pyramid on Python, doesn't matter) and a SQL Database for storage. The webapp and SQL DB run on different physical servers.
The most common attack against the database is a successful SQL Injection Attack. This kind of attack does not necessarily gain access to your webapp code, because the webapp runs on a different server & user-ID.
You need to store passwords securely in the database, and come up with something on the form of:
$hashed_password = hash( $salt . $password )
$salt is stored in plaintext in the database, together with the
$hashed_password representation and randomly chosen for each new or changed password.
The most important aspect of every password hashing scheme is that
hash is a slow cryptographically secure hash function, see https://security.stackexchange.com/a/31846/10727 for more background knowledge.
The question is then, given that it is almost zero effort to add a constant value to the application code, and that the application code will typically not be compromised during an SQL Injection Attack, is the following then substantially better than the above?
$hashed_password = hash( $pepper . $salt . $password )
$salt is stored in plaintext in the database, and
$pepper is a constant stored in plaintext in the application code (or configuration if the code is used on multiple servers or the source is public).
$pepper is easy -- you're just creating a constant in your code, entering a large cryptographically secure random value (for example 32byte from /dev/urandom hex or base64 encoded) into it, and using that constant in the password hashing function. If you have existing users you need a migration strategy, for example rehash the password on the next login and store a version number of the password hashing strategy alongside the hash.
$pepper does add to the strength of the password hash if compromise of the database does not imply compromise of the application. Without knowledge of the pepper the passwords remain completely secure. Because of the password specific salt you even can't find out if two passwords in the database are the same or not.
The reason is that
hash($pepper . $salt . $password) effectively build a pseudo random function with
$pepper as key and
$salt.$password as input (for sane
hash candidates like PBKDF2 with SHA*, bcrypt or scrypt). Two of the guarantees of a pseudo random function are that you cannot deduce the input from the output under a secret key and neither the output from the input without the knowledge of the key. This sounds a lot like the one-way property of hash functions, but the difference lies in the fact that with low entropy values like passwords you can effectively enumerate all possible values and compute the images under the public hash function and thus find the value whose image matches the pre-image. With a pseudo random function you cannot do so without the key (i.e. without the pepper) as you can't even compute the image of a single value without the key.
The important role of the
$salt in this setting comes into play if you have access to the database over a prolonged time and you can still normally work with the application from the outside. Without the
$salt you could set the password of an account you control to a known value
$passwordKnown and compare the hash to the password of an unknown password
hash($pepper . $passwordKnown)==hash($pepper . $passwordSecret) if and only if
$passwordKnown==$passwordSecret you can compare an unknown password against any chosen value (as a technicality I assume collision resistance of the hash function). But with the salt you get
hash($pepper . $salt1 . $passwordKnown)==hash($pepper . $salt2 . $passwordSecret) if and only if
$salt1 . $passwordKnown == $salt2 . $passwordSecret and as
$salt2 were randomly chosen for
$passwordKnown and respectively
$passwordSecret the salts will never be the same (assuming large enough random values like 256bit) and you can thus no longer compare password against each other.