In a hash algorithm, only what is being hashed is secret. Everything else is public (salt, work factor, other parameters). These algorithms are designed to maintain pre-image resistance (i.e. from the hash if the password is discovered) even if everything else about it is known. Therefore, the fact that an attacker sees the dollar sign in the code is not relevant (even because by the time he got his hands on your bank he must already know everything about your system).
In addition, other hash algorithms (even "broken" ones like MD5) will also need at least one salt (to avoid Rainbow Tables), And that salt has to be stored somewhere, right? What difference does it make if it’s in the hash itself (separated by dollar sign) or another column in the same table (which the attacker already has)?
On which is the best algorithm of the three, see the other answers for a comparative. Personally, I believe that PBKDF2 with a high number of iterations should be sufficient to protect a password (but in reality the three are good enough). And if your system has high security requirements (such as those of a bank, or nearby) then consider using other means than the password to protect your customers' accounts (two-factor authentication, for example).
Updating: Let’s say, to make life more difficult for an attacker, you solve: 1) take the dollar out; 2) change the order of the elements. That way, you’re turning:
$s0$e0801$epIxT/h6HbbwHaehFnh/bw==$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=
in
e0801s07H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=epIxT/h6HbbwHaehFnh/bw==
What the attacker will do?
Create an account on your site. So he will know 1 password (his own) and the corresponding hash;
Read how scrypt works, discovering that he has:
A version number. How many s0
have in your string? Only 1:
e0801 s0 7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=epIxT/h6HbbwHaehFnh/bw==
The parameters of the algorithm. Well, in this case it’s obvious that it’s the first group;
A salt and a key. Well, in this case it was easy because the coding Base64 ended
with =
and ==
, then it’s easy to find the pieces.
e0801 s0 7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0= epIxT/h6HbbwHaehFnh/bw==
What is the salt and what is the key? Well, he knows his own password and her hash, so just do 2 tests:
$s0$e0801$epIxT/h6HbbwHaehFnh/bw==$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=
$s0$e0801$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=$epIxT/h6HbbwHaehFnh/bw==
Whatever works, it’s the format you’re using!
Although you use base 16 to make it harder to find the boundaries between the parts, the work to test all possibilities (within the same hash representation) is minimal compared to the work of trying to break the hash. If the attacker has enough money to make a hash attack, he can make that little test in a matter of minutes. You only complicated your implementation in exchange for a few minutes of the attacker’s time. It was worth it?
Jasypt seems to do a good job protecting passwords. Just remember to use a different salt for each user, and an appropriate number of iterations. That article suggests 1000 iterations, but it’s probably outdated... Try 10,000, or even 100,000, and see if it’s tolerable (since I don’t know much about Whirlpool, I won’t suggest an exact number). This should provide comparable security to PBKDF2.
– mgibsonbr