通过密钥对url上的参数进行签名,比如url上有三个参数,按一定顺序排列后用一个只有服务端知道的密钥签名放在<a href="http://www.xxx.com/?a=1&b=2&c=3&sig=xxxxxxxx">里,这样,如果用户改了里面参数的内容,比如把a=1改成了a=2,但不知道你的密钥,也无法模拟你的签名,后台通过接收到的参数列表,用密钥进行签名校验,就可以知道这个url是不是原始你写出去的url了。
将要提交的参数先做加密,然后把加密的信息做一次md5摘要,也就是签名,然后把摘要连同参数一起回传给服务器,服务器拿到参数后,同样的方式加密做md5摘要,然后两个摘要做对比,如果不相等参数便是被篡改了,否则可信。这种方式已经是很成熟的url参数防篡改技术了,简单易用成本低,安全系数高,并且不会大大增加url的长度,业内很多公司都在使用这种方式,比如阿里巴巴,腾讯的接口等。
|----------------------------------------
加密的GET参数
WEB驱动事件与交互的最基本的方法就是通过GET参数,我想你懂paperen说的是什么意思的。
www.example.com/goods.php?id=2
简单来说就是那个问号后面的都可以称之为GET参数,因为这种形式会让浏览器向服务器请求时将问号后面的字符都加到请求数据中,只是那不是我们要了解的事情而已,而很直接的表现就是在地址栏看到这些很长的参数。
不知道大家有没有去修改这些参数的冲动,因为修改它们实在是太容易了,而且是暴露在地址栏的,我们都可以随便修改,或者将id=2改为id=test,但正如有写过web代码的人都应该知道一件事情就是“很可能这个参数会被用来做一些事情“,最常见的就是作为一个字符或者数字拼接到一条sql里面,然后查出相应的数据,或者根据该参数让程序反应不同的结果……
而重要的是,paperen我认为大部分这种暴露get参数带来的负面影响是,一些好奇心或者恶意的浏览者会通过这些参数对您的网站进行一些检测与攻击,显然这并不是网站运营与开发者想听到的消息。
web服务器来说并不知道哪些请求是恶意的是非法的,因为服务器对每个请求都是平等的,但当然不是说100个浏览者中会有99个是带恶意的,相反,恶意性的浏览者可能只有1个,但就是这么只有1个才让我们感到害怕,因为我们不知道这个带恶意的浏览者会从我们的服务器获得什么信息,发现什么漏洞……所以这也说明了为什么我们要将我们的代码写得更加稳定更加强壮,特别是应对一些异常数据的时候,这也正是为什么在发布最后要经过一个测试的原因……好吧,下面就直接奔向主题算了。
paperen认为在一定程度上你可以去屏蔽get参数的表现方式来让那些对get参数敏感的人对你失去兴趣,you know,这并不是一个很高深的方案,别怕……
如果将
www.example.com/news/?id=3&uid=45&name=paperen
变为
www.example.com/news/?get=0qMZJevzDZaP9Wc77sh4uvhSrUXW0lYj13I4oxwNM6%
你觉得这个url给你感觉是什么……(paperen我会想OMG,这堆字符串真TMD BT了……很恶心),我们没法从这么一堆BT的字符串中想象它原来的格式是id=3&uid=45&name=paperen,没错这是个丑陋的URL,对seo有了解的人或者不推荐这么做,paperen认为在安全与seo之间你需要有个折中点,采取符合您的期望与要求的方案就可以了(不是讨论重点)。
paperen想这会在安全性上更加给力,因为大部分的浏览者(或者应该说是地球人)都很难去理解这串看上去毫无意义的参数。so下面提供一下实现的方法(其实真的很简单)。想象一下自己平时是怎样处理这些get参数的。
- <?php
- $id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : '';
- if ( $id ) {
- //do something
- }
- ?>
- <a href="example.php?id=164332">Link</a>
然后我们需要一个加密算法(它也应该是可以解密的),很明显已经直接排除了md5与crypt,因为都是单向性的。故可以使用base64来实现这个,php对应的两个加解密函数分别是base64_encode与base64_decode。
最后一点是我们不是单纯对id使用这个加密,不单单是要让url变成这样。www.example.com/news/?id=Mw==&uid=NDU=&name=cGFwZXJlbg==,这样只是对参数数据进行了加密,但是有哪些参数还是暴露在外面。我们的目标是”只有一个参数“。或者可以这样写。
- <?php
-
//是否开启URL加密模式
-
define('OPEN_URL_ENCODE', true);
-
//加密类型
-
define('GET_ENCODE_TYPE', 'base64');
-
//自定义GET接受标记
-
define('GET_TAG', 'get');
-
//需要传递的GET参数
-
$args = array(
-
'id' => 1533,
-
'type' => 2,
-
);
-
//如果开启URL加密模式需要进行一些特殊处理
-
if ( OPEN_URL_ENCODE ) {
-
$_GET = extract_arg( $_GET );
-
}
-
//整理参数
-
function format_arg( $url, $args ) {
-
return (OPEN_URL_ENCODE) ? $url . '?' . GET_TAG . '=' . make_my_urlarg( arg_arr2str( $args ) ) : $url . '?' . $tmp;
-
}
-
//还原参数
-
function extract_arg( $arr ) {
-
if ( isset( $arr[GET_TAG] ) ) {
-
return arg_arr2str( make_my_urlarg( $arr[GET_TAG] , true ), true );
-
} else {
-
return array('');
-
}
-
}
-
//数组字符串互换
-
function arg_arr2str( $data, $is_str2arr = false ) {
-
$tmp = '';
-
if ( $is_str2arr && is_string( $data ) ) {
-
$arr = explode('&', $data);
-
foreach( $arr as $index => $data ) {
-
if ( false !== strpos($data, '=') ) {
-
list($k , $val) = explode('=', $data);
-
$tmp[$k] = $val;
-
}
-
}
-
} else if( !$is_str2arr && is_array( $data ) ) {
-
foreach( $data as $k => $val ) $tmp .= '&' . $k . '=' . $val;
-
$tmp = substr($tmp, 1);
-
}
-
return $tmp;
-
}
-
//根据相应的算法加解密参数
-
function make_my_urlarg($str, $is_decode = false) {
-
if ( 'base64' == GET_ENCODE_TYPE ) {
-
return ($is_decode) ? base64_decode($str) : base64_encode($str);
-
}
-
}
-
//还是一如既往地接收GET参数,感觉这个加解密处理与我们无关
-
$id = isset( $_GET['id'] ) ? intval($_GET['id']) : '';
-
$type = isset( $_GET['type'] ) ? intval($_GET['type']) : '';
-
if ( $id ) {
-
//do something
-
}
-
$url = format_arg('example.php', $args);
-
echo "<p>$url</p>";
-
print_r( $_GET );
-
echo '<p></p>';
-
?>
-
<a href="<?php echo $url; ?>">Link</a>
你能调用的函数其实就是format_arg与extract_arg,就这样简单已经给全局变量$_GET加上一层马甲。运行效果如下。
对,我们还是可以一如既往地使用$_GET['id']来获取这个参数,但是原来的URL却变得有点不可看透了,当然从这个你可以延伸到其他超级变量上,比如POST(用在POST上不太好,原因是加甲是由服务端执行的,而大部分POST数据是变动的不像URL传参数这样是我们预定好的),SESSION,COOKIE。或许用在COOKIE上也是一个不错的选择。
如果用JS控制页面跳转到某个带参数的URL时可能要有点折腾,只要你头脑有个概念:加甲是要靠服务端程序的,应该就好办了。
如果感觉使用base64加密还是会被识穿那么你可以使用以下这个函数作为加解密核心。
-
/**
-
* authcode 摘自UChome
-
* @param string $string 明文
-
* @param string $operation ENCODE加密/DECODE解密
-
* @param string $key 密钥(可以在配置文件中定义)
-
* @param string $expiry 有效期?
-
* @return string
-
*/
-
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
-
$ckey_length = 1;
-
$key = md5($key ? $key : '');
-
$keya = md5(substr($key, 0, 16));
-
$keyb = md5(substr($key, 16, 16));
-
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
-
$cryptkey = $keya . md5($keya.$keyc);
-
$key_length = strlen($cryptkey);
-
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
-
$string_length = strlen($string);
-
$result = '';
-
$box = range(0, 255);
-
$rndkey = array();
-
for($i = 0; $i <= 255; $i++) {
-
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
-
}
-
for($j = $i = 0; $i < 256; $i++) {
-
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
-
$tmp = $box[$i];
-
$box[$i] = $box[$j];
-
$box[$j] = $tmp;
-
}
-
for($a = $j = $i = 0; $i < $string_length; $i++) {
-
$a = ($a + 1) % 256;
-
$j = ($j + $box[$a]) % 256;
-
$tmp = $box[$a];
-
$box[$a] = $box[$j];
-
$box[$j] = $tmp;
-
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
-
}
-
if($operation == 'DECODE') {
-
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
-
return substr($result, 26);
-
} else {
-
return '';
-
}
-
} else {
-
return $keyc.str_replace('=', '', base64_encode($result));
-
}
-
}
还支持密钥,也就是说密钥不对也不能解密,很给力吧~~
下面是一个完整的DEMO,希望对你有帮助。
参考资料:
http://paperen.com/post/get-encode【推荐】
http://blog.csdn.net/hashsound\_wang/article/details/8113766【推荐】
http://blog.csdn.net/sdutphp/article/details/51991738

