#!/usr/bin/perl -w package zeitform::captcha; use Crypt::CBC; use Digest::SHA1 qw(sha1_base64); use GD::SecurityImage; use MIME::Base64; our $VERSION = "0.01"; ## internal { my $_defaults = { _secret => 'my secret key', ## Crypt::CBC options _crypt_algo => 'Blowfish', ## cipher _crypt_salt => 1, ## salt _captcha_length => 6, _captcha_opts => { width => 120, height => 40, lines => 10, angle => 3, font =>"/usr/X11R6/lib/X11/fonts/truetype/Vera.ttf", }, }; sub _defaults { $_defaults; } } ## constructor sub new { my ($caller, %args) = @_; my $class = ref($caller) || $caller; return unless $args{secret}; ## force secret! my $self = bless { }, $class; my $defaults = $self->_defaults; foreach my $key (map { s/^_//; $_ } keys %$defaults) { $self->{"_$key"} = $args{$key} || $defaults->{"_$key"}; } return $self; } sub encrypt { my ($self, $string, %args) = @_; return unless $string; my $cipher = Crypt::CBC->new(-key => $self->{_secret}, -cipher => $self->{_crypt_algo}, -salt => $self->{_crypt_salt}); return $self->_encode($cipher->encrypt($string)); } sub decrypt { my ($self, $string, %args) = @_; return unless $string; my $cipher = Crypt::CBC->new(-key => $self->{_secret}, -cipher => $self->{_crypt_algo}, -salt => $self->{_crypt_salt}); return $cipher->decrypt($self->_decode($string)); } sub captcha_text { my ($self, $cipher, %args) = @_; return unless $cipher; return lc(substr(sha1_base64($self->{_secret} . $cipher . ($args{salt}||"")), 0, $self->{_captcha_length})); } sub captcha_image { my ($self, $cipher, %args) = @_; return unless $cipher; my $image = GD::SecurityImage->new( %{ $self->{_captcha_opts} } ); $image->random($self->captcha_text($cipher, %args)); $image->create("ttf", 'circle', "#f37325", "#ffc75a"); ## make me config #$image->particle(2400, 1); ## make me config return $image->out; } sub _encode { my ($self, $string) = @_; return unless $string; $string = encode_base64($string); $string =~ s/\s//g; ## remove whitespace $string =~ s/\+/-/g; ## convert for uri "+" -> "-" $string =~ s/\//_/g; ## convert for uri "/" -> "_" $string =~ s/=+$//; ## remove padding return $string; } sub _decode { my ($self, $string) = @_; return unless $string; $string =~ s/-/+/g; ## revert for uri "+" -> "-" $string =~ s/_/\//g; ## revert for uri "/" -> "_" $string .= "=" x (4 - length($string) % 4) if length($string) % 4; ## add padding $string = decode_base64($string); return $string; } 1; ############################################################################### package main; use strict; use CGI; use HTML::Template; my $c = zeitform::captcha->new(secret => 'my secret key'); my $q = CGI->new; my $type = $q->param("type") || ""; my $id = $q->param("id"); my $captcha = $q->param("c"); my $rnd = $q->param("rnd"); ## generate image if ($type eq "image" && $id) { my($image_data, $mime_type, $random_number) = $c->captcha_image($id, salt => $rnd); print $q->header($mime_type); print $image_data; } ## generate captcha form and validate else { my $template = HTML::Template->new(filehandle => *DATA, die_on_bad_params => 0); $template->param(self => $q->url()); $template->param(id => $id ) if $id; $template->param(rnd => $rnd ) if $rnd; if ($id && $captcha) { ### captcha matches if ($captcha eq $c->captcha_text($id, salt => $rnd)) { $template->param(verified => $c->decrypt($id)); } ## captcha is wrong else { $template->param(rnd => int(rand(1000))); $template->param(failure => 1); } } elsif ($id) { ## output captcha form $template->param(new => int(rand(1000))); } else { ### debug - would print genereic error here!!!! #$template->param(error => 1); $template->param(error => $c->encrypt('ulf@zeitform.de')); } print $q->header; print $template->output; } ## HTML-Template follows __DATA__ Captcha Test

Test this LINK: click

Try again!