I’m working on a personal project at the moment that deals with user accounts. Although I could make it super simple and assume trust with everyone and not even need passwords, we don’t learn as much from taking the easy route. Where it began What started me on looking at user account authentication was watching the network traffic of an app that I use to see what formats they transfer data into their app. While I found that and it helped a great deal, I continued digging around to look at other stuff and found their user authentication scheme.
http://www.site.com/login.php?username=myusername?password=mypassword
Whats Wrong? Sending my username/password as a part of the URL allows very simple loggers to see it, such as routers that log the URLs users devices request. While most of the time this might not be an issue, if someone has access to those logs and is malicious, they have your username/password for that site. How can this part be fixed? Send the data as parameter in the POST request instead of using a GET request. When it’s a parameter of a POST request, it won’t get logged like the URL unless the packets are recorded as well. Which leads me into my next points.
No use of SSL The way that I found out about the URL the app was transmitting data with was using a program called WireShark. With it, I can record traffic between my computer and the internet. Although I would be able to see the URL on my router, this allowed me to record packets of data as well. And so can attackers. When you’ve encrypted your traffic using SSL, the sender encrypts it with a public key and the receiver decrypts it with their private key.
That’s as far as I can get without probing their server or having access to their database which I really don’t want to get into.
Rolling my own Method When it came time when I needed to implement account security, I took what I learned about how NOT to do it and explored even further. My app server uses Python as its language on the Bottle framework (well, “micro-framework”). I chose it because I typically like to keep as lightweight as possible and add features as necessary instead of all at once.
As far as I know, this is how to handle transmitting login information securely.
User enters username and password on a login page.
Create a POST request to my login script on my server and put the username and password as parameters of the request.
Wrap that POST request in SSL encryption using your public key.
Execute the request
My server has received the request.
Decrypt username and password on server using your private key.
After that you’re not quite done yet. Transmitting the data as encrypted protects one user at a time. If an attacker sniffs the traffic of unencrypted data, that one user’s data has been stolen. What if an attacker goes after your server and gains access to your login database that’s unencrypted. That’s not just one person but everyone’s usernames and passwords compromised. On top of that, many users use the same username/password at many different sites. So that attacker now has access to anywhere else that user has an account with the same login details, such as at their bank. The solution for this is hashing.
What is hashing? Hashing is a way of creating a unique value based on the input fed into it. When the same data is fed into it, the same output string is always created. If the data differs even by a single character, it’ll create an entirely different hash. Some common hashing algorithms include MD5, SHA1, SHA-256. The length of the output string depends on the hashing algorithm you use. For example, the password “password” hashes to “5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8” and will always hash to that value.
How fast is hashing? Unfortunately, the hashing algorithm is fast. As in, there is common hardware out there that can hash values at 3110.2M per second. A short password isn’t much of an issue with that kind of speed. I’m not certain of the math but at 8 alphanumeric characters, I came out with a little under 20 hours to crack every password hash. So even if you hash the password, an attacker can just run the same hashing algorithm and find the hash that matches up with your password. Also, why would they want to run the same values through the hashing algorithm multiple times for trying to find the password for each hash? A way around this are pre-computed lookup tables called Rainbow Tables. Using these tables, a user could easily do a reverse lookup for a hash and find the password that was used to create it, not needing to hash the potential password each time. I’ve read that anything under 16 characters has already been cracked. What do you do? Force your users to use 16+ character passwords?
Add a little SALT to your HASH Salt is a way of adding additional characters to your users passwords when hashing. Assume a 32 character salt. Now your users 7 character pass becomes a 39 character salt that you feed into your hashing algorithm. While the likelihood of this value being pre-computed is reduced immensely, if someone has access to your hashed passwords, they may also have access to your salt values. All they would need to do to circumvent your additional salt is to run the algorithm with the salt value and brute forced values. Remember how it would take less than a day to calculate all passwords under 8 characters? That’s back, even with the salt. Also, by using the same salt for all passwords, an attacker can tell when two users are using the same password. A way of getting around this is using unique salts for each password. Now, for an attacker to calculate a password, they would need to calculate the brute forced value for each salt, taking them a day for each password. If it’s a 9 character password? Around 60 days, assuming worst case scenario for them. You would think that would be enough, right? Eh.
To create unique salts, your OS or development language typically have secure methods for producing good random values.
Around and around we go An 8 character password with a salt takes a day right now but as technology advances it might only take a few hours. Maybe an hour. A way of slowing this down is to hash the password multiple times. If it takes a second for each password hash to be calculated, and even if there’s 2 characters, that’s 3,844 seconds or about an hour. 3 characters? 2 and 3/4s of a day. At 4 characters? 171 days per password. And that’s all assuming they know the salt value for the password. As technology becomes faster, we can increase the number of rounds of hashing to maintain that second. A second to a user while logging in is minimal but when someone is trying to crack a password, that second can be forever.
PBDKF2 Of all the things I’ve talked about, this function will handle all of the hashing needed.
In the hashing algorithm it’ll take:
The name of the hashing algorithm
Password to be hashed
A salt
Number of rounds of hashing
(Optional) Length of the key produced.
In my project, I’m using python to handle hashing passwords.
import hashlib, binascii, os
def hash_password(password, rounds=100000, salt=binascii.hexlify(os.urandom(64)), key_length=None):
return salt, binascii.hexlify(hashlib.pbkdf2_hmac("sha512", password, salt, rounds, key_length))
Example use
hashed_password, salt = hash_password(”password”.encode())
This allows me to feed in just a single password into the function while using default arguments to fill in the rest of the needed information if not supplied. I’m using 100,000 rounds of hashing by default which takes ~1 second on my laptop. The salt is generated as a 64 byte string (512 bits) when calling the function and returned as a part of the return object along with the hashed password. While I still have the option to specify the key_length, the default is based off of what the algorithm returns which in the case of sha512 is 512 bits or 64 bytes. Once it’s run through that function, I can store the username, salt, and hashed password in the database. Also note that the pbkdf2 function type byte strings rather than ascii strings so you will need to encode them, the default encoding for python is utf-8.











