Math Captcha for DjangoTags: web, python, django, captcha — 27th of September 2009

Why?

The purpose of Captcha is to distinguish humans from machines. And the popular way of doing it is to ask the user to write down what he see's. But then the machines got better, so the captcha were made harder to read. Except now its harder for both machines and humans to read.
Examples:
http://www.robotnine.com/2009/05/captcha-words-worst-ones-ever.html
http://www.johnmwillis.com/other/top-10-worst-captchas/

How?

There's 3 parts to this
  1. Creating the Math Question & Answer
  2. Creating the Image from the Math Question
  3. Verifying if the user responded properly
To create the actual image we'll use Python Imaging Library
Now define a view in django that will output our image and the code for the view is
from django.conf import settings
from django.http import HttpResponse
from StringIO import StringIO
import random, os
import Image, ImageFont, ImageDraw

def index(request):
    
    # Load a random font
    fontslocation = settings.MEDIA_ROOT + "notes/captcha/"
    fonts = os.listdir(fontslocation)
    font = ImageFont.truetype(fontslocation + random.choice(fonts), 25)


    # Create the image with 400x50 size
    image = Image.new("RGBA", (400, 50), (255,255,255, 0))
    draw = ImageDraw.Draw(image)

    # Draw the text we get from getBasicMath
    draw.text((0, 0), getBasicMath(request), font=font, fill="#000000")

    # Create a StringIO that we can use to save the image
    data = StringIO()
    image.save(data, format="PNG")

    # Return the image directly from memory
    data.seek(0)
    return HttpResponse(data.read(), mimetype="image/png")

def getBasicMath(request):
    # The 3 types of operators we'll support
    operator = random.choice(["+", "-", "x"])

    # If its multiplication, lets use smaller numbers
    if (operator == "x"):
        num1 = random.randint(0, 10)
        num2 = random.randint(0, 10)

        # Store the correct answer in session
        request.session['captcha_answer'] = num1 * num2
    else:
        num1 = random.randint(20, 40)
        num2 = random.randint(0, 20)

        # Store the correct answer in session        
        if (operator == "+"):
            request.session['captcha_answer'] = num1 + num2
        else:
            request.session['captcha_answer'] = num1 - num2

    # This is our output
    return str(num1) + operator + str(num2) + "=";

At the beginning we try to load a random font, you need to find some ttf fonts to use and place them in a folder in the MEDIA_ROOT. As you can see I put all my fonts in the "notes/captcha/" folder. To find free ttf fonts you can try 1001 Free Fonts.
And that is all you need to set up, you can change the questions in the getBasicMath function.

To verify the answer

In the getBasicMath function we set the answer in session "captcha_answer", so next time someone answers the captcha question just verify if their answer matches with that session object.

Example


Improvement

There is a LOT of room for improvement
  • Add a background image
  • Transform the image
  • Changing from numbers to words. Ex: five + ten =