In a hash algorithm, only what is being done is secret. Everything else is public (salt, work factor, other parameters). These algorithms are designed to maintain the pre-image resistance (i.e. from the hash if it finds the password) even though everything else about it is known. So the fact that an attacker sees the dollar sign in the code is not relevant (even though by the time he got his hands on his bank he should already know everything about his system).
In addition, other hash algorithms (even the "broken" ones like MD5) will also need at least one salt (to avoid rainbow tables ), and that salt will have to be stored in somewhere, right? What difference does it make if it is in the hash (separated by dollar sign) or another column in the same table (which the attacker also already has)?
On the best algorithm of the three, see the other answers for a comparison. Personally, I believe that PBKDF2 with a high number of iterations should be enough to protect a password (but in reality all three are good enough). And if your system has high security requirements (such as those at or near a bank) then consider using other means than the password to protect your customers' accounts (two-factor authentication, for example).
Update: Let's say that to make an attacker's life more difficult, you can: 1) take the dollar sign from the exit; 2) change the order of the elements. That way, you're transforming:
$s0$e0801$epIxT/h6HbbwHaehFnh/bw==$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=
in
e0801s07H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=epIxT/h6HbbwHaehFnh/bw==
What will the attacker 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 it 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 that case it is obvious that it is the first group;
A salt and a key. Well, in that case it was easy because base64 encoding ended
with =
and ==
, so 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 hash, so just do 2 tests:
$s0$e0801$epIxT/h6HbbwHaehFnh/bw==$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=
$s0$e0801$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0=$epIxT/h6HbbwHaehFnh/bw==
What works, is the format you are using!
Although you use base 16 to make it harder to find the boundaries between parts, the work to test all possibilities (within a 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 do this test in a matter of minutes. You only complicated its implementation in exchange for a few minutes of the attacker's time. Was it worth it?