Django and Captcha images
Check out the new site at https://rkblog.dev.
14 July 2008
Comments
Captcha are images with some text that user need to write into a form field in order to send the form data. It is used as a anti-spam solutions as bots can't read text from images. In case of django/python the solution is quite easy. We can use code published by various users, for example django-captcha.
Note, that Captcha decrease site usability for people with disabilities so you should use Captcha carefully.
How does Captcha works ?
- We generate a random string and place it on a temporary image- We show that image in the form
- We make a md5 or sha1 hash out of that string and send it as a hidden field of the form or we use sessions or cookies to store it.
- When user submits the form the text he entered is hashed and compared with the original hash. If they are equal then user entered correct captcha text.
Simple Captcha with PIL
Requirements
- PIL (Python imaging library)- Simple image (a small blank banner or similar image with non aggressive background)
- A TTF font file - a bit "fantasy" font would be nice (but readable)
The Code
I used blank image called bg.jpg which I placed in the /site_media folder: I've also placed in the same folder SHERWOOD.TTF a Baldurs Gate like font file. Next I've created a simple project and app, which returned "captcha" view under / root URL.views.py code:
from django.shortcuts import render_to_response
from os import remove
def captcha(request):
# random generator
from random import choice
# PIL elements, sha for hash
import Image, ImageDraw, ImageFont, sha
# create a 5 char random strin and sha hash it, note that there is no big i
SALT = settings.SECRET_KEY[:20]
imgtext = ''.join([choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(5)])
# create hash
imghash = sha.new(SALT+imgtext).hexdigest()
# create an image with the string (media is the folder with static files accessed by /site_media)
# PIL "code" - open image, add text using font, save as new
im=Image.open('media/bg.jpg')
draw=ImageDraw.Draw(im)
font=ImageFont.truetype('media/SHERWOOD.TTF', 18)
draw.text((10,10),imgtext, font=font, fill=(100,100,50))
# save as a temporary image
# I use user IP for the filename, SITE_IMAGES_DIR_PATH - system path to folder for images
temp = settings.SITE_IMAGES_DIR_PATH + request.META['REMOTE_ADDR'] + '.jpg'
tempname = request.META['REMOTE_ADDR'] + '.jpg'
im.save(temp, "JPEG")
if request.POST:
data = request.POST.copy()
# does the captcha math ?
if data['imghash'] == sha.new(SALT+data['imgtext']).hexdigest():
# captcha ok
# save data etc.
# use another view/template in render_to_response and delete the temp captcha file:
#remove(temp)
return render_to_response('form.html', {'ok': True, 'hash': imghash, 'tempname': tempname})
else:
# captcha bad
# return the form
return render_to_response('form.html', {'error': True, 'hash': imghash, 'tempname': tempname})
# no post data, show the form
else:
return render_to_response('form.html', {'hash': imghash, 'tempname': tempname})
form.html template code:
{% if error %}
<div style="text-align:center;"><b>FAILURE</b></div>
{% endif %}
{% if ok %}
<div style="text-align:center;"><b>SUCCESS ! :)</b></div>
{% endif %}
<form action="." method="POST"><input type="hidden" value="{{ hash }}" name="imghash">
<b>Text from the image</b><br />(Use CAPITAL letters)<br />
<input type="text" size="20" name="imgtext"><br /><img src="/site_media/{{ tempname }}"><br />
<br /><input type="submit" value="Send Form" class="actiontable">
</form>
Result
Django Manipulator with Captcha validation
For old form system (Django <= 0.95) we can create our own Manipulators. A login form with Captcha could look like this:class LoginForm(forms.Manipulator):
def __init__(self):
self.fields = (forms.TextField(field_name="login", length=30, maxlength=200, is_required=True),
forms.PasswordField(field_name="password", length=30, maxlength=200, is_required=True),
forms.TextField(field_name="imgtext", is_required=True, validator_list=[self.hashcheck]),
forms.TextField(field_name="imghash", is_required=True),)
def hashcheck(self, field_data, all_data):
import sha
SALT = settings.SECRET_KEY[:20]
if not all_data['imghash'] == sha.new(SALT+field_data).hexdigest():
raise validators.ValidationError("Captcha Error.")
def loginlogout(request):
from django.contrib.auth import authenticate, login
if not request.user.is_authenticated():
temp = settings.SITE_IMAGES_DIR_PATH + request.META['REMOTE_ADDR'] + '.jpg'
tempname = request.META['REMOTE_ADDR'] + '.jpg'
# captcha image creation
from random import choice
import Image, ImageDraw, ImageFont, sha
# create a 5 char random strin and sha hash it
SALT = settings.SECRET_KEY[:20]
imgtext = ''.join([choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(5)])
imghash = sha.new(SALT+imgtext).hexdigest()
# create an image with the string
im=Image.open(settings.SITE_IMAGES_DIR_PATH + '../bg.jpg')
draw=ImageDraw.Draw(im)
font=ImageFont.truetype(settings.SITE_IMAGES_DIR_PATH + '../SHERWOOD.TTF', 24)
draw.text((10,10),imgtext, font=font, fill=(100,100,50))
im.save(temp,"JPEG")
manipulator = LoginForm()
# log in user
if request.POST:
data = request.POST.copy()
errors = manipulator.get_validation_errors(data)
if not errors:
manipulator.do_html2python(data)
user = authenticate(username=data['login'], password=data['password'])
if user is not None:
login(request, user)
remove(temp)
return HttpResponseRedirect("/user/")
else:
data['imgtext'] = ''
form = forms.FormWrapper(manipulator, data, errors)
return render_to_response('userpanel/' + settings.ENGINE + '/login.html', {'loginform': True, 'error': True, 'hash': imghash, 'form': form, 'theme': settings.THEME, 'engine': settings.ENGINE, 'temp':tempname})
# no post data, show the login forum
else:
errors = data = {}
form = forms.FormWrapper(manipulator, data, errors)
return render_to_response('userpanel/' + settings.ENGINE + '/login.html', {'loginform': True, 'hash': imghash, 'form': form, 'theme': settings.THEME, 'engine': settings.ENGINE, 'temp':tempname})
else:
# user authenticated
if request.GET:
# logout user
data = request.GET.copy()
if data['log'] == 'out':
from django.contrib.auth import logout
logout(request)
return HttpResponseRedirect("/user/")
return HttpResponseRedirect("/user/")
Bots read captchas ?
Captchas aren't perfect and spammers can create tools that read text from images. Some sites use really fuzzy images but they are fuzzy also for users. A possible solution is to use a fantasy-like font which is still readable but doesn't look like a normal font.
RkBlog
Check out the new site at https://rkblog.dev.
Comment article