Django password security

Update (2017)

This post is over 5 years old. Django now uses PBKDF2 by default and has pluggable password hashing. See how Django stores passwords for detail.


Revision 16453 of Django improved the security of the password algorithm for the first time since the 0.90 days of years ago. This is a brief discussion on that and Django password schemes in general.

Worth its salt

Most people know that “good” passwords are at least 8 characters and contain an uppercase, lowercase, and number at least. Let’s ignore special characters for now. This yields about 47 bits of entropy. The entire set of 8 character passwords could be reversed in about 36 hours assuming ~1B hashes per second. You could just burn your new rainbow table to a DVD and break everyone’s password. Easy as pie.

Unfortunately for password crackers, this hasn’t worked for years because of salted passwords. Django uses a system of salted passwords where when a user types in their password, the user’s random salt gets prepended to the password before being compared with the salted and hashed password which is stored in the database. It works like this:

Each user gets a different random salt and this way a leaked password database cannot be easily reversed using a rainbow table. Django switched from using a 5 character salt composed of [a-f0-9] (20 bits of entropy) to a 12 character salt made up of [a-zA-Z0-9] (over 71 bits). Formerly, the salt was simply made of the first 5 characters of a sha1 hash of a call to random.random() for about a million unique possible salts. Breaking an old password database was about a million times harder than unsalted passwords which made it prohibitive but not impossible. The new system is considerably more complex.

Remaining weaknesses

If your database leaked, salted passwords will protect the entire set of hashes from being reversed. However, it will not protect a specific hash from being reversed since the salt is stored in the clear. You do not need to reverse every hash to do some damage. You can just reverse the administrator user’s password. If you look what gets stored in Django’s User table, it looks like this:

The password field stores the method of hashing (sha1), the salt and the hashed password separated by ‘$’. Given the user’s salt, we could easily check all 8 character passwords for that salt in the same 36 hours. This doesn’t change if the salt is 5 or 12 characters. Because sha1 is designed to be “fast” since it is also used for things like checksums, it doesn’t really offer much protection here. A better solution is to use a “slow” hashing algorithm that is designed specifically for password hashing like bcrypt or PBKDF2.

More generally

There have been numerous tickets (#5787, #5600, #15367) and proposals and even a project that duckpunches Django to add bcrypt. Parts of these proposals — namely using a system source of randomness where available and a longer salt — have already been implemented as part of changeset 16453. A long term solution is to make the encryption pluggable similar to the way database backends are pluggable. This makes it easy to swap out a particular encryption algorithm if weaknesses are discovered and let different installations have different algorithms based on different requirements.

Sparklines in D3

A couple weeks ago, Protovis, a visualization library I’d been using was deprecated in favor of D3 and I thought I’d share some of the work I’d done porting visualizations from the old to the new.

One example that Protovis has for which there is no corresponding tutorial is sparklines. This sparkline shows the San Diego Padres’ first 100 games of the 2011 season. Up ticks are wins and down ticks are losses. Red ticks show shutouts. This is similar to the visualization in Tufte’s “Beautiful Evidence” p. 54.

I created another simple visualization for the National League West. This shows all five teams of the NL West on a single graphic. It is pretty easy to adapt this code to a single sparkline. So far I have been fairly pleased with D3’s performance and the ease of use.

South Asia and Electronics

Looking at the electronics available in south Asia has really opened my eyes. This experience is just not available in the USA.

There are some products, such as some knock off iPods that I saw that I wasn’t sure could even be made for the price that the vendor was selling them. I had a hard time believing that a USB cable, headphones, 2GB flash, as part of a small 2-color screen mp3 player with speakers could even be made for the equivalent of US$10. I figured it was pretty close, but then when you factor in transport costs and the vendor’s time that it would need to be sold for more. I’m sure the quality is horrendous, however, here is a device which can play mp3s for $10! Perhaps I’m overestimating costs and this thing can be made for significantly less than $10.

Where to shop

I shopped at the Golden Computer Arcade in Hong Kong which was awesome. Literally a thousand vendors are cramped into three (?) stories of this building with components stacked on top of boxes or in buckets. There are people assembling computers on top of the boxes in which they came. There was almost no wasted space. Fortunately, I am not claustrophobic. In addition, the prices were right and I didn’t even haggle much. I did not spend a lot of money and bought mostly curiosities including a SIM card reader/writer, some antennas and a USB dongle that can connect to pirate TV channels worldwide. In this mall, you can buy just about anything from off-brand photography equipment to specialized digital controllers.

In Singapore, I went to Sim Lim Square and the Digitalife Mall. Both places were fairly similar six or so story indoor malls focusing almost entirely on electronics. There were so many vendors that you could spend an entire day at each and not see everything, but for the most part they sold the same things at roughly the same prices. The top couple stories of Sim Lim Square were most interesting because the prices were more reasonable and they weren’t just selling 5000 different iPhone4 covers or other off-the-shelf products. A lot of the products in these two Singaporean malls were the same things I saw in Hong Kong but for about 2-2.5x more. Name brand products had the same prices as online.

Where this stuff comes from

So if Hong Kong and Singapore had a lot of the same off-brand products, where do they come from? It seems to me that a lot of these businesses in Hong Kong and Singapore are small import-export businesses getting components probably from China. I really wanted to go see the markets in Shenzhen, but I wasn’t able to make it. I’ve heard of markets where literally buckets of most mobile chipsets are sold and you can get custom made components if you’re willing to wait and pay for it.

This is a woman assembling some components for sale in the back of her store in Singapore.

Securing a Django Site in Production

I was setting up a Django site for somebody recently and got asked the question, “is it possible for someone to hack my site?”. The answer, of course, is yes. To some degree, this is unavoidable. If somebody is willing to expend the time, effort and money, it is almost impossible to have a complex site that is perfectly secure. Even security “experts” can get it wrong. However, this got me thinking about the steps to secure a Django site.

Django does a good job of being reasonably secure by default. Unlike some other frameworks where you have to explicitly use CSRF tokens, Django uses them unless you tell it not to. Django escapes data from your templates automatically and is generally safe from SQL injection. The framework contains the building blocks to build a secure site, but quite often the site is deployed on a shaky foundation.

Securing the admin

For maximum security, the Django admin site should probably always be deployed on a web server running HTTPS. There’s a good guide on setting up SSL for the admin. Redirecting requests for /admin to HTTPS is one way. Another way is setup the admin on a subdomain like admin.example.com and handle them like that. This is what it looks like in Nginx:

Using this, you can proxy to two different Django instances: one handles the site over HTTP and one handles just the admin over HTTPS. Depending on your exact setup, you probably also want to mark the cookie as secure.

While the admin always needs security, some sites could also benefit from security outside of the admin if they’re handling user details, email addresses or other things. As an application developer, you need to build in that security — Django doesn’t know what you need to protect. Just remember the next time you login to your Django admin screen on a wifi hotspot at Starbucks that anybody can run something like Firesheep or Wireshark and capture your credentials. It’s amazing how many notable sites get this wrong. It reminds me of the wall of sheep.

Securing the server

It is amazing how many people put out a server with an inadequate firewall. Either they leave their database port wide open, memcached port open (this is REALLY bad — see here) or in some other way greatly increase the possible attack surface. While I generally knew what Amazon Web Services (AWS) could do as far as hosting, I had never used them before recently and I was impressed by their security. AWS makes configuring the firewall super easy and by default, only port 22 is open and SSH only accepts keys not passwords. That’s fairly secure by default! It gives a simple web GUI to open select ports and only to select machines. For example, if you host your database on a different server than your web server, only the web server should be able to connect to the database, not the whole internet. Also, Amazon S3 can serve its files over HTTPS as well. It’s a rather handy feature. I expect Rackspace is fairly similar in most regards.

Django security update

There were a couple fixes and changes in Django 1.2.5, but the main change was to CSRF exceptions to AJAX requests. The decision to remove the exception — despite backwards incompatibility — was the right move considering that the assumption that XmlHttpRequests could only come from the browser is no longer true (was it ever?). However, this release makes me wonder how many site authors didn’t bother to change much and just put @csrf_exempt above their web services just to get their site working again quickly with the new version.

Note: I secured the wordpress admin using the guide here and the WordPress HTTPS plugin. It’s a self-signed cert so I’m only getting maybe 75% of the security pixie dust, but I can deal with that.

Edit (September 14, 2011): Take a look through the Django security docs which your humble blogger helped write.