Home > PHP > Checksum

Checksum

Ich habe vor einiger Zeit die PHP-Klasse Checksum veröffentlicht.

Diese Klasse generiert eine 40 Zeichen lange Prüfsumme, welche ich allen Hyperlinks
als Parameter anhänge oder einem Formular als verstecktes Feld beifüge. In diese Prüfsumme fließen folgende Informationen ein:
- Anwendungskontext (context, module, action)
- geheimer Systemschlüssel (servergebundene Konstante)
- aktuelle Systemzeit (time)


Checksum::build("context","module","action");

Bei einer Serveranfrage prüft jetzt ein Programm, ob die eingehende Anfrage gestellt werden durfte,
indem es den Bau der Prüfsumme wiederholt und Einzelprüfungen durchführt.

Checksum::proof("incoming-checksum", "context","module","action");

Aus den eingehenden Anwendungsparametern (z.B. context,module,action;GET) wird eine neue Prüfsumme generiert. Diese weicht stets von der eingehenden Prüfsumme ab, da die Systemzeit einfließt.

Standardmäßig besitzt jede Prüfsumme eine Gültigkeitsdauer von 3600 Sekunden (1 Stunde, Konstante). Die Prüfmethode zerlegt die eingehende Prüfsumme nach einem datumsabhängigen Anordnungsschema in neun Blöcke zu je vier Zeichen:
7 Anteile Anwendungsparameter,
2 Anteile Systemzeit.

Die Gültigkeitsdauer wird anhand der extrahierten Werte ermittelt. Die allgemeine Integrität der Zeichenkette wird durch den geheimen Systemschlüssel und die übergebenen Parameter bestimmt.

Es können beliebig viele Parameter in die Erstellung einer Prüfsumme einfließen. Der ausgelieferte Hyperlink muss jedoch alle Parameter, bis auf den geheimen Systemschlüssel und die -zeit mitführen.

Wenn ein System über eine derartige Institution verfügt, dann können Verweise auf Seiten (z.B. Email) mit begrenzter Lebensdauer ausgehändigt werden, ohne dass eine vollständige Anwendungsprüfung erfolgen muss.
Bei der Anfrage von sicherheitskritischen Anwendungen können zusätzliche Aspekte, wie REFERER, in die Generierung der Prüfsumme einfließen.

Durch derartige Prüfsummen wird eine Anwendung zum einen sicherer, zum anderen können durch verschiedene Prüfungen viele unberechtigte Anfragen abgewehrt werden, ohne das System großartig zu belasten.


define("SYSTEMKEY", "qfe78dsd3sd"); // your secret password
define("USE_DYNAMIC_PATTERN", false); // use dynamically build date patterns

/**
* Checksum
*
* @author Thomas Schäfer
* @desc time limited checksum builder
* @example
*
* # build
* // injected unlimited number of parameter arguments as string or integer
* $checksumToLink = Transfer_Checksum:: build ("YourFirstParameter"," YourSecondParameter"); // sample output aeeb0da600ab1d9934f1f065498c7497479cbd12
* output view link:
*
*
* incoming request (e.g.):
* $url = parse_url($_SERVER["REQUEST_URI"]);
* parse_str($url["query"], $query);
*
* # proof
* // inject the same number of parameter arguments as above with
* // leading checksum string
* $boolProof = Checksum::proof($query["csm"],$query["a"],$query["b"]);
*
* if($boolProof) echo “valid”;
* else echo “invalid”;
*
* features:
* - build
* - proof
* - setTimestamp
* - setValidTimeRange (seconds)
* - setValidTimeRangeInDays (days)
*
*/
class Checksum
{
/**
* @const time shift constant in seconds for valid checksum
* desc should correspond with session validation time
*/
const DYN_PATTERN = USE_DYNAMIC_PATTERN; // true = dynamically build, false = individual pattern array
private static $iCounter = 0;
private static $strTimestamp = null;
private static $strTimestampTo = null;
private static $validTimeRange = 3600; // one hour valid
/**
* checksum
* @desc build query checksum
* @return string new checksum
*/
public static function build()
{
$argCounter = func_num_args();
if ($argCounter == 0)
{
throw new Exception(”You need to define some string parameters for being able to build a checksum”);
}
self::$iCounter = $argCounter;
$arguments = func_get_args();
$string = call_user_func(array (
“self”,
“getRawKeyString”
),
$arguments
);
$time = self::getRawKeyTime();
// value key

$key = self::keyPattern($string, $time);
return implode(”", $key);
}
/**
* proof
* @desc proof checksum on string and time parts
* @param string reference referencing checksum
* @return bool
*/
public static function proof()
{
// checks
$isValidChecksum = false;
$isValidString = false;
$isValidTime = false;
$reference_checksum = null;
if (func_num_args() <= 1)
{
throw new Exception("You need to define more then a single parameters");
}

$internal_arguments = array ();
$arguments = func_get_args();
foreach ($arguments as $argument)
{
if (preg_match('/[a-z0-9]{32}/', $argument))
{
$reference_checksum = $argument;
}
else
{
$internal_arguments[] = $argument;
}
}
// new values
$string = call_user_func(array (
"self",
"getRawKeyString"
),
$internal_arguments
);
$time = self::getRawKeyTime(); // time part

// new value key
$keyPattern = self::keyPattern($string, $time);

// old values
$timeParts = self::getTimeParts(); // positions of time parts (8,9)
$stringReference = str_split($reference_checksum, 4);
$timeReference = $stringReference[$timeParts[8]] . $stringReference[$timeParts[9]]; //
$decodedTime = hexdec($timeReference);

// begin validate time range
$referingTime = time();
if(
!empty(Transfer_Checksum::$strTimestamp) and
!empty(Transfer_Checksum::$strTimestampTo) and
$referingTime >= Transfer_Checksum::$strTimestamp and
$referingTime <= Transfer_Checksum::$strTimestampTo
) {
$isValidTime = true;
} elseif(empty($isValidTime) and $referingTime - Transfer_Checksum::$validTimeRange < $decodedTime){
$isValidTime = true;
}
// end validate time range

// check parts of old and new string value
$isValidString = Transfer_Checksum::validate($string, $time, $keyPattern, $stringReference);
// check secondary conditions

if ($isValidString and $isValidTime)
{
$isValidChecksum = true; // set primary condition => checksum is valid
}

print_r(get_defined_vars());
return $isValidChecksum;
}
/**
* setTimestamp
* @param integer $timestamp
* @desc change the timestamp
*/
public static function setTimestamp($timestamp)
{
if (is_string($timestamp)){
self::$strTimestamp = strtotime($timestamp);
} else {
throw new Exception(”Timestamp has to be of type string, a valid date format which can be converted to unix time.”);
}
}
/**
* specify individual ranges
* @param integer $validTimeRange
*/
public static function setValidTimeRange($validTimeRange = null)
{
if (is_integer($validTimeRange) and $validTimeRange > -1) {
self::$validTimeRange = $validTimeRange;
} else {
throw new Exception(”Range has to be of type integer and greater than -1.”);
}
}

/**
* setValidTimeRangeInDays
* @param integer $validDays
*/
public static function setValidTimeRangeInDays($validDays = null)
{
selfValidTimeRange(3600*24*$validDays);
}

/**
* isValidFromTo
* @param string $dateFrom a valid timestamp, e.g.: 2009-01-01
* @param string $dateTo a valid timestamp, e.g.: 2009-02-01
*/
public static function isValidFromTo($dateFrom = null, $dateTo = null)
{
if (is_string($dateFrom) and is_string($dateTo))
{
$df = strtotime($dateFrom);
$dt = strtotime($dateTo);
if($dt>$df) {
self::$strTimestamp = strtotime($dateFrom);
self::$strTimestampTo = strtotime($dateTo);
} else {
throw new Exception(”DateTo has to be greater than DateFrom”);
}
}
else
{
throw new Exception(”Range has to be of type integer and greater than -1.”);
}
}

/**
* getRawKeyTime
* @desc splits hex time string into parts
* @return array
*/
private static function getRawKeyTime()
{
if (!empty (self::$strTimestamp))
{
return str_split(dechex(self::$strTimestamp), 4);
}
else
{
return str_split(dechex(time()), 4);
}
}
/**
* getRawKeyString
* @desc creates hash (system key added) and splits md5 string into parts
* @return array
*/
private static function getRawKeyString()
{
$strRawKey = “”;
if (func_num_args() > 0)
{
$arguments = func_get_args();
foreach ($arguments[0] as $argument)
{
$strRawKey .= $argument;
}
}
else
{
throw Exception(”No parameters had been injected.”);
}
$strRawKey .= SYSTEMKEY;
$string = str_split(md5($strRawKey), 4);
return $string;
}
/**
* drawPatternByDay
* @desc returns pattern orders for each day
* @param integer $indexDayPattern
* @return array
*/
private static function drawPatternByDay($indexDayPattern)
{
switch (self::DYN_PATTERN)
{
// dynamically build date pattern
case true :
$num = date(”Ym”);
$t = date(”t”);
$range = array ();
$arrSort = array ();
$pattern = array ();
for ($i = 1; $i <= $t; $i++)
{
$date = strtotime(date("Ym") . str_pad($i, 2, "0", STR_PAD_LEFT));
$str = str_split(md5($date), 4);
foreach ($str as $index => $val)
{
$range[$i][$index] = $val;
}
$weekday = date(”w”, $date);
$parts = str_split(md5(strtotime(date(”Ym”))), 4);
$range[$i][8] = $str[8] = $parts[$weekday];
$range[$i][9] = $str[9] = $parts[$weekday +1];
sort($str);
$array = array_flip($range[$i]);
foreach ($str as $key => $val)
{
$pattern[$i][$key] = $array[$val];
}
}
return ($pattern[$indexDayPattern -1]);
default :
/*
* positions: 0 - 7 => string parts 8 - 9 => time parts shifting parts
* by reordering range
*/
$patterns = array(
array(0,2,4,6,8,1,3,5,7,9),
array(1,3,5,7,9,2,4,6,8,0),
array(2,4,6,8,0,3,5,7,9,1),
array(3,5,7,9,1,2,4,6,8,0),
array(2,4,6,8,1,3,5,7,9,0),
array(7,9,0,2,4,6,8,1,3,5),
array(1,2,4,6,8,0,3,5,7,9),
array(8,0,3,5,7,1,2,4,6,9),
array(5,7,2,4,6,8,0,3,9,1),
array(6,8,0,3,5,7,2,4,9,1),
array(8,0,3,5,6,7,2,4,9,1),
array(7,2,4,9,8,0,3,5,6,1),
array(2,0,4,6,8,1,3,5,7,9),
array(1,3,7,5,9,2,4,6,8,0),
array(2,4,8,6,0,3,5,7,9,1),
array(3,5,9,7,1,2,4,6,8,0),
array(2,4,6,8,1,5,3,7,9,0),
array(7,9,0,2,6,4,8,1,3,5),
array(1,2,4,6,8,0,5,3,7,9),
array(8,0,3,5,7,1,2,4,6,9),
array(5,7,2,4,6,3,0,8,9,1),
array(6,8,4,3,5,7,2,0,9,1),
array(8,2,3,5,6,7,0,4,9,1),
array(7,2,1,9,5,0,3,8,6,4),
array(5,7,2,4,1,8,0,3,9,6),
array(6,8,9,3,5,2,7,4,0,1),
array(8,0,5,3,6,7,4,2,9,1),
array(4,2,6,0,8,3,5,7,9,1),
array(3,1,5,2,9,7,4,6,8,0),
array(1,2,4,0,8,6,3,5,7,9),
array(0,8,3,5,7,2,1,4,6,9),
);

return ($patterns[$indexDayPattern-1]);
}
}
/**
* keyPattern
* @desc re-orders the key map by pattern
* @param array $string
* @param array $key
* @return array
*/
private static function keyPattern($string, $time)
{
$indexDayPattern = self::drawPatternByDay(date(”d”));
$merge = array_merge($string, $time);
$newPatternOrder = array ();
foreach ($indexDayPattern as $index)
{
$newPatternOrder[] = $merge[$index];
}
return $newPatternOrder;
}
/**
* getTimeParts
* @desc returns array of positions of time parts
* return array
*/
private static function getTimeParts()
{
$pattern = self::drawPatternByDay(date(”d”));
$timeParts = array ();
foreach ($pattern as $key => $index)
{
switch ($index)
{
// time parts
case 9 :
case 8 :
$timeParts[$index] = $key;
break;
// string parts
default :
break;
}
}
return $timeParts;
}
/**
* validate
* @desc checks against pattern if the checksum is valid
* @param array $string
* @param array $time
* @param array $key new value key
* @param array $stringReference old value key
* return bool
*/
private static function validate($string, $time, $key, $stringReference)
{
$pattern = self::drawPatternByDay(date(”d”));
$intCounter = 1;
foreach ($pattern as $index)
{
switch ($index)
{
// time parts
case 9 :
case 8 :
break;
// string parts
default :
if ($key[$index] == $stringReference[$index])
{
$intCounter++;
}
break;
}
}
if ($intCounter >= 8)
{ // all string parts are equal
return true;
}
return false;
}
}

Categories: PHP Tags:
  1. Bisher keine Kommentare
  1. Bisher keine Trackbacks