howto-captcha

Simple captcha without temporal data

What are a captchas?

Captchas are human-or-machine tests. Mostly you have to type in the text displayed on a image. There are much more possibilities. The Wikipedia-article Captcha gives more information about.

Common way

The common way for captchas is to assign a captcha-key to a user and save this information temporal in a table. This could be 127.0.0.1_LOWM_2008.05.02T12:59. "LOWM" then would be send to user and a validation-function would proof, if the captcha-code entered by the client "127.0.0.1" is valid and it entered in a period of 30 minutes. Then this entry would be deleted from the table.

For never used enries you need a periodic running function to delete these. For example every 2 hours delete all entries from table that are more than 40 minutes old.

If you don't want to have such a table with temporary data what you have to clean periodicaly, then you should read this document.

Tempdataless-captcha

flowchart

As first a Random Key is generated. It's sended to user as a hidden input in the form. (1)

After that the user request the captcha image with this Random Key as a parameter. (2)

The Generator creates (using the current date) the captcha-code and sends it as a image to the user. (3)

After a time the user sends (4) - the (public) Random Key and typed in captcha. Don't forget, that at this time the current date are not necessarily the same as at (2)-(3)!

Now two results are generated using suplied Random Key. One generates the result with current date, one with current date minus one hour. Minutes and seconds are not used! The Controller compare the typed-in captcha-code (4) with both generated results and if one are equal, than the captcha-test are positive.

How works the Generator? It builds a string from:

The whole builded string are hashed. For me I used (sha1(whole key) . md5(whole key)) to get it longer without using sha512. And after that I used for me a function to translate hex-hash-code to a set of chars I want user to type in. It is usefull if you for example want use only numbers for captcha-code. In the end of this all Generator cuts it to predefined maximal length of captcha-code.


Security aspects

As I belief, this concept is the best you could get for captcha without temporal data. An attaker could request captcha-code image with his own "Random Key". It would be useable for 1-2 hours. It would be possible to use the IP-address as a part of builded string, but sometimes users became changed IP-address on second request. On the other hand it wouldn't really stops DDoS - only disadvantage for a attaker would be that every client has to request a key. It also would be possible to add more client-specific informations such as user-agent string, but some proxies have the ability to use random user-agent on every request. On the attaker side is absolutely not a problem to send a prepared user-agent string to the server.

Advantages

This concept of captcha allows you to work without temporal data. You don't need tables in the DB for temporal date and no files with temporal data. You can use this captcha even without a DB and without writing to files. This could be very usefull for php-mailers for example.

Disadvantages

You can't block multiple use of a captcha-combination (Random Key and typed-in captcha-code). The generated captcha-code stay valid for 1-2 hours. It is a long period. You could shrink this time to 10 minutes, but it would have less usability for a human user. An evil attacer could type-in the captcha code once every hour and use it multiple times to spam.

PHP example code

The following sourcode is a part of 47quote script and under GNU GPL Version 2 licence.

function c_time_one ()
{
$out = date('YmdH', time());
return $out;
}

function c_time_two ()
{
$out = date('YmdH', time() - 3600);
return $out;
}

function c_create_key ($random_key)
{
$out = array();
$key_last = $random_key . $GLOBALS['settext']['captcha_key']['value'];
$out[] = c_time_one() . $key_last;
$out[] = c_time_two() . $key_last;
return $out;
}

function hex_to_dec ($hex)
{
$count = strlen($hex);
$out = 0;
while ($count > 0)
{
	$out = bcadd(bcpow(16, $count) * $hex[$count - 1], $out);
	$count--;
}
return $out;
}

function c_format_key ($created_key)
{
$captcha_length = 8;
$converted_key = array();
$count = 0;
$temp = 0;
$char_table = array('1','2','3','4','7','8','w','W','e','E','R','T','Z','z','U','u',
	'P','A','a','S','d','F','f','H','h','K','Y','y','X','V','B','b','N','n','M','m');
$base = count($char_table);
$hashed_key = sha1($created_key) . md5($created_key);
$dec_key = hex_to_dec($hashed_key);
while (bcpow($base, $count + 1) < $dec_key) { $count++; }
while ($count > 0)
{
	$temp = bcpow($base, $count);
	$converted_key[] = bcdiv($dec_key, $temp);
	$dec_key = bcmod($dec_key, $temp);
	$count--;
}
$count = count($converted_key);
while ($count > 0)
{
	$temp = $char_table[$converted_key[$count -1]];
	$converted_key[$count -1] = $temp;
	$count--;
}
$out = implode($converted_key);
$out = substr($out, 0, $captcha_length);
return $out;
}

/* the generator would then be: */
c_format_key(array_gimme_value(c_create_key($RandomKey), 0));
/* there array_gimme_value() simply return a value from array */

2008-04-29


Site by Blacker47 - Imprint