Base64 in URL

转载

整个思考过程是这样的,概念过于基本,让人不齿……

url 在传递时,如果有特殊字符需要 urlencode,但是这是一种很冗长的格式,比方说“测试”二字,如果是 GBK 编码的话是 4 个字节,可 urlencode 后是“%B2%E2%CA%D4”,12 个字节,是原来的三倍,这很不爽,如果你整个参数都只有中文,可能 HEX 形式(PHP 里是 bin2hex)表示能好点,就是没了 %,成了 B2E2CAD4(这两种方式类似 UTF-8 和 Unicode 的关系),不过也有两倍长

为什么会有两倍长?因为符号没有被充分利用,每一位只用了 0-9、A-F 共 16 种,如果充分利用 A-Z,再包括大小写,加上数字共 62 种,再找两种(比方说 _ 和 .)凑到 64 种,2 的幂次数。64 ^ 2 = 16 ^ 3 = 4k,既是说两位的 64 进制数与三位的 16 进制数表示的内容相当。

于是花了一个来小时,写完后先是挺美,又有不祥预感,google 了一下 RFC,才明白其实我实现的就是 base64……大悲

区别还是稍微有点的,在结尾,base64 是用第 65 种字符“=”放上 0 - 3 个来标识,我是用的数字 1 - 3。我觉得他的这个“=”很恶,完全没必要多出来这第 65 种啊。

之所以 base64 不能用来 URL 传递,是因为邮件和网址要回避的东西不同。base64 里避免了“.”,因为是 smtp 里有歧义,不用“-”的理由是因为这东西经常被用来表示注释。而 http 里,“+”和“/”是歧义。最后只能对调,用 str_replace 了事,用“._,”替换了“+/=”

我倾向于这种编码,比 urlencode 要短了太多,这个只比原来的二进制内容多了的 8bit / 6bit - 1 = 1/3 长度。实事上由于参数的越来越多,我是把一堆参数用 \n 给 implode 起来,等接收的时候 explode,还得外加 xor + md5 防止篡改或者试我参数什么的,实际长度可能要有 1k 左右,这时候给我节省的长度就很可观了。

这算是特殊应用,实际很少能碰到需要对 url 参数做这么多要求的时候。不过还是得说,汉字多了的时候还是用 base64 + str_replace 比 urlencode 要好些。

最后,保存一下草纸吧,也算没白费这时间

[phpcode]
$s64 = "1234567890._abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$a64 = str_split($s64);
$a64u = array();
foreach ($a64 as $key => $value) {
$a64u[$value] = $key;
}

function oox_en($num) {
global $a64;
$a = floor($num / 64);
$b = $num - 64 * $a;
return $a64[$a].$a64[$b];
}

function oox_de($str) {
global $a64u;
$a = str_split($str, 1);
$i = $a64u[$a[0]] * 64 + $a64u[$a[1]];
return sprintf("%03s", dechex($i));
}

function oox_encode($sOrg) {
$iMod = 3 - fmod(strlen($sOrg), 3);
$iMod = ($iMod == 3) ? 0 : $iMod;
$sOrg .= substr("nnn", 0, $iMod);
$sHex = bin2hex($sOrg);
$a = str_split($sHex, 3);
$s = "";
foreach ($a as $key => $value) {
$s .= oox_en(hexdec($value));
}
return $s.$iMod;
}

function oox_decode($sCoded) {
$aCoded = str_split($sCoded, 2);
$i = count($aCoded) - 1;
$iMod = $aCoded[$i];
unset($aCoded[$i]);
$s = "";
foreach ($aCoded as $key => $value) {
$s .= oox_de($value);
}
return substr(pack("H*", $s), 0, -$iMod);
}

$sTest = "some code to test";
echo $sTest;
echo "<br />\n";
echo oox_encode($sTest);
echo "<br />\n";
echo oox_decode(oox_encode($sTest));
[/phpcode]

外加一种复杂保密体编码……功能是让 url 参数可以绕晕一般水平的 cracker

[phpcode]
$sPrivateKey = "213231";
$sXorKey = "2313123";

function fnBinXorEncode($sInput) {
global $sXorKey, $sPrivateKey;
$sOut = $sInput ^ str_repeat($sXorKey, ceil(strlen($sInput) /strlen($sXorKey)));
$sOut .= pack("H*", md5($sOut.$sPrivateKey));
$sOut = base64_encode($sOut);
$sOut = str_replace("+", ",", $sOut);
$sOut = str_replace("/", "_", $sOut);
$sOut = str_replace("=", ".", $sOut);
return $sOut;
}

function fnBinXorDecode($sInput) {
global $sXorKey, $sPrivateKey;
$sInput = str_replace(",", "+", $sInput);
$sInput = str_replace("_", "/", $sInput);
$sInput = str_replace(".", "=", $sInput);
$sInput = base64_decode($sInput);
$sHash = bin2hex(substr($sInput, -16));
$sInput = substr($sInput, 0, -16);
if ($sHash != md5($sInput.$sPrivateKey)) {
return false;
}
$sOut = $sInput ^ str_repeat($sXorKey, ceil(strlen($sInput) /strlen($sXorKey)));
return $sOut;
}
[/phpcode]