Skip to content

Instantly share code, notes, and snippets.

@defuse
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save defuse/8911037 to your computer and use it in GitHub Desktop.
Save defuse/8911037 to your computer and use it in GitHub Desktop.
Side channel safe hex encoding?
<?php
// WARNING: THIS IS EXPERIMENTAL CODE. DO NOT USE IT.
// --- binary to hex encoding ---
function sc_bin2hex($binary)
{
$encoded = '';
for ($i = 0; $i < strlen($binary); $i++) {
$high_nibble = ord($binary[$i]) >> 4;
$low_nibble = ord($binary[$i]) & 0x0F;
$encoded .= sc_nibble_to_hex($high_nibble);
$encoded .= sc_nibble_to_hex($low_nibble);
}
return $encoded;
}
function sc_nibble_to_hex($nibble)
{
// Assumes ASCII. FIXME Is it always ASCII in PHP?
$dec = ord('0') + $nibble;
$hex = ord('a') + $nibble - 10;
// All ones if $nibble is less than 10. All zeroes otherwise.
$lt_ten = ($nibble - 10) >> (PHP_INT_SIZE * 8 - 1);
return chr( (~$lt_ten & $hex) | ($lt_ten & $dec) );
}
// --- hex to binary decoding ---
function sc_hex2bin($hex)
{
if (strlen($hex) % 2 !== 0) {
return false;
}
$decoded = '';
for ($i = 0; $i + 1 < strlen($hex); $i += 2) {
$high_nibble = sc_hex_to_nibble($hex[$i]);
$low_nibble = sc_hex_to_nibble($hex[$i+1]);
if ($high_nibble === false || $low_nibble === false) {
return false;
}
$decoded .= chr(($high_nibble << 4) | $low_nibble);
}
return $decoded;
}
function sc_hex_to_nibble($hex_digit)
{
$hex_digit = ord($hex_digit);
// We have to do the decimal digit checks before
// making the character lower case, otherwise it
// will accept things like chr(25).
// $hex_digit is a character that comes before '0' in ASCII.
$lt_zero = ($hex_digit - ord('0')) >> (PHP_INT_SIZE*8 - 1);
// $hex_digit is a character that comes after '9' in ASCII.
$gt_nine = (ord('9') - $hex_digit) >> (PHP_INT_SIZE*8 - 1);
// ASCII hack to make it lower case.
$hex_digit = $hex_digit | 32;
// $hex_digit is a character that comes before 'a' in ASCII.
$lt_a = ($hex_digit - ord('a')) >> (PHP_INT_SIZE*8 - 1);
// $hex_digit is a character that comes after 'f' in ASCII.
$gt_f = (ord('f') - $hex_digit) >> (PHP_INT_SIZE*8 - 1);
// Assume it's in 0-9, then the nibble value will be $dec.
$dec = $hex_digit - ord('0');
// Assume it's in a-f, then the nibble value will be $hex.
$hex = $hex_digit - ord('a') + 10;
// But which one is it?
$is_dec = ~$lt_zero & ~$gt_nine;
$is_hex = ~$lt_a & ~$gt_f;
// If it's not in either 0-9 or a-f...
if (($is_dec | $is_hex) === 0) {
return false;
}
// Use $dec if it's in 0-9, or $hex if it's in a-f.
return ($dec & $is_dec) | ($hex & $is_hex);
}
// --- testing ---
echo sc_hex2bin("48494A4b4c");
// exit;
for ($i = 1; $i < 1000; $i++) {
$test = mcrypt_create_iv($i, MCRYPT_DEV_URANDOM);
$test_hex = sc_bin2hex($test);
$test_hex_correct = bin2hex($test);
$test_hex_decode = sc_hex2bin($test_hex_correct);
if ($test_hex !== $test_hex_correct || $test_hex_decode !== $test) {
echo "IT DOES NOT WORK !!!! \n";
exit;
} else {
echo $test_hex . "\n";
}
}
echo "ALL GOOD!\n";
// // Old code
//
// function safe_string_index($string, $index)
// {
// $character = 0;
// for ($i = 0; $i < strlen($string); $i++) {
// $x = $i ^ $index;
// $mask = (((($x | ($x >> 16)) & 0xFFFF) + 0xFFFF) >> 16) - 1;
// $character |= ord($string[$i]) & $mask;
// }
// return chr($character);
// }
//
// function side_channel_safe_bin2hex($binary) {
// $hex = '0123456789abcdef';
// $encoded = '';
// for ($i = 0; $i < strlen($binary); $i++) {
// $byte = ord($binary[$i]);
// $encoded .= safe_string_index($hex, $byte >> 4);
// $encoded .= safe_string_index($hex, $byte & 0x0F);
// }
// return $encoded;
// }
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment