Hey Tristen, welcome to the site.
As I'm sure you're well aware, one-way hashing algorithms produce identical output given the same input. Two-way encryption such as my
cryptastic class, on the other hand, can produce unique output given the same input. With this in mind, our goal in using a one-way hashing algorithm is to produce a hash that is computationally very difficult, in terms of the time it takes to crack the hash. This is why the pbkdf2 performs so many iterations.
If an attacker is able to gain access to both the processing server (such as a web server) and the storage server (database) they will in most cases be able to easily crack the passwords, in the same way you're "checking" passwords. Without crypto hardware/devices this is just the way it goes. If security is extremely important and you have to the play the game with the deck stacked against you, so to speak, all you can do is make it as difficult and time consuming as possible for an attacker to obtain the necessary pieces of the equation to crack your passwords. This gives you time to detect and respond to the intrusion, thereby limiting the damage caused.
An attacker will want to figure out a few things. What hashing algorithm(s) is being used? Is a salt being used and if so, what is it? How many iterations are being performed? Is the salt embedded in the stored hash? So on and so forth. You will have to investigate your system(s) to determine how easily an attacker can obtain this information. If an attacker penetrates your web server and they're able to view your source code, they'll obtain all of this information except the hash itself. Then we have to ask the question, can the attacker easily query the database from the web server to obtain the hashes? Etc, etc, etc...
After taking a look at your code, I see you're using a unique salt for each hashing process. This is good in the sense it reduces the chances of two identical hashes being stored in the database by users who have the same password and it also makes it that much more difficult to crack passwords in certain cases.
However, by storing the plain text salts appended to the hashes, you're losing a good deal of the gain. If the attacker is very good, they'll be knowledgeable with hashing, encryption and so on. They'll take one look at that long hash and possibly discern a salt is being stored in it. It also won't be all that difficult for them to figure out what kind of hashing process is being used, even if it's a somewhat "custom" mix and match.
The big danger I'm seeing here is storing the plain text salt. I would not do that. You'd be better off in my opinion using a static salt that doesn't have to be stored along with hashes.
What I would say is 1) do everything you can to protect your web server (keep it patched, write secure code, and have your web app source code encrypted and anything else that will make it difficult for an intruder to get a look at the actual source) and 2) don't store plain text salts in your database.
One other thing is that you might want to use a less obvious iteration count for pbkdf2, such as 20123 or some such. I hope I've been of some assistance.
Cheers!