batfastad wrote:1) Should I be using the urandom rather than uniqid( mt_rand(), true) to regenerate my salt?
Of course, for anything cryptographic you should always use as strong random generators as possible.
batfastad wrote:2) Should the salt and file system pepper be generated the same way (urandom) and to the same length as each other?
As always, the stronger the better. I wouldn't use less than 24-bytes (192-bits) of strength for them. There are no answers to this, because it depends on what you want (the server resource usage, the level of security, etc). However, the "pepper" , which I refer to as the file-system key, should be a lot more stronger since it's generated once and does not really consume any CPU or memory and it plays a big role here.
batfastad wrote:3) Usernames. On some sites peoples login/usernames are their e-mail addresses and on others it's a separate string.
From a user point of view I always prefer it as my e-mail address (less stuff to remember). But I always feel its easier to recover your account (if your e-mail address changes) when you have to choose a separate username.
Are there any advantages/disadvantages to doing it either way?
From the perspective of security, it would be better to not use anything that is known, guessable or visible to other users as the login identifier. Email is just that. If they can choose a username, it's likely to be harder to crack into the account by brute force. If someone has gotten inside your database, then it obviously does not matter what you use as the "username" unless you hashed it, too. So, using a custom string, which is unknown to others, will certainly yield in a solution that is just as secure as the email approach or stronger, but not weaker. It depends a lot on the user, she's ultimately the one who creates her login credentials.
batfastad wrote:4) I've been investigating various cookies stored on my system and it seems Paypal stores my username (e-mail address) in a cookie and a few others I've found.
I know. The password is all that prevents people from using my credit card

. I have to make sure my password is strong, which I believe is what all people do since it's a bank.
batfastad wrote:I was under the impression that I shouldn't store username or password on the machine as a cookie, only the user's session cookie. Is that correct?
That's the best thing to do.
batfastad wrote:If I stored username and the session cookie then that would give me a double check
That double check is useless. You can as well increase the strength of your session identifier to achieve the same effect. It is a more sophisticated and controlled approach.
batfastad wrote:5) I'm storing my password_hash and salt in a VARCHAR field in my DB. And the pepper is just a PHP variable.
Should I be doing it differently... storing as binary/encoding somehow?
Personally, I like having a BLOB column where I store the salt and the password hash directly in binary format. I see (and have) no reasons to store them in a human readable format (except that the password hash is already in base 16). If I needed to, I can fetch the salt and pack it into hex or other format I want. This also saves some CPU and disc space. However, it's up to you to decide how you do it.
batfastad wrote:6) Session ID forgery/throttling. So I'm storing IP addresses, a counter of forgery attempts, the last session id they tried in a table.
And every time a user visits a secured page I'm looking up their IP address in that DB and comparing the session ID they've sent with the last one tried by that IP. But how do I detect forgery attempts from that?
Do I also need to store a timestamp of the last forgery attempt? Or have the counter represent the number of forgery attempts within a certain time range?
You also need to check the number in your counter and set some sort of timestamp to keep track of the last attempt. There are a few different ways to make it work. It doesn't really matter much how you implement as long as it works as expected. One way would be to update the timestamp whenever there's an attempt that failed and the counter was not too high yet. If the counter was too high, don't update the timestamp, but deny all. If the counter is too high, but the timestamp is more than x hours/days in the past, then clear the counter and set it to 0 or 1 depending on whether the last attempt succeeded or not.