刚刚读了一遍 Discuz 系列产品中广泛使用的加密解密算法 authcode,受益匪浅,真是设计巧妙。
为了真的理解其中的想法,用 Python 改写了一遍。
小白程序员一枚,学艺不精,望路过的各位大侠多多指点,不吝赐教。
PS:在法律层面上,原算法的使用是否受限制尚不清楚。
Python 代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import time
import base64
import hashlib
class AuthCode(object):
@classmethod
def encode(cls, string, key, expiry=0):
"""
编码
@param string: 带编码字符串
@param key: 密钥
@return:加密字符串
"""
return cls._auth_code(string, 'ENCODE', key, expiry)
@classmethod
def decode(cls, string, key, expiry=0):
"""
解码
@param string: 待解码字符串
@param key: 密钥
@return:原始字符串
"""
return cls._auth_code(string, 'DECODE', key, expiry)
@staticmethod
def _md5(source_string):
return hashlib.md5(source_string).hexdigest()
@classmethod
def _auth_code(cls, input_string, operation='DECODE', key='', expiry=3600):
"""
编码/解码
@param input_string: 原文或者密文
@param operation: 操作(加密或者解密,默认是解密)
@param key: 密钥
@param expiry: 密文有效期,单位是秒,0 表示永久有效
@return: 处理后的原文或者经过 base64_encode 处理后的密文
"""
# ----------------------- 获取随机密钥 -----------------------
rand_key_length = 4
# 随机密钥长度 取值 0-32
# 可以令密文无任何规律,即便是原文和密钥完全相同,加密结果也会每次不同,增大破解难度
# 值越大,密文变动规律越大,密文变化 = 16 的 ckey_length 次方,如果为 0,则不产生随机密钥
key = cls._md5(key)
key_a = cls._md5(key[:16])
key_b = cls._md5(key[16:])
if rand_key_length:
if operation == 'DECODE':
key_c = input_string[:rand_key_length]
else:
key_c = cls._md5(str(time.time()))[-rand_key_length:]
else:
key_c = ''
crypt_key = key_a + cls._md5(key_a + key_c)
if operation == 'DECODE':
handled_string = base64.b64decode(input_string[rand_key_length:])
else:
expiration_time = expiry + int(time.time) if expiry else 0
handled_string = '%010d' % expiration_time + cls._md5(input_string + key_b)[:16] + input_string
rand_key = list()
for i in xrange(256):
rand_key.append(ord(crypt_key[i % len(crypt_key)]))
# ----------------------------------------------------------
box = range(256)
j = 0
for i in xrange(256):
j = (j + box[i] + rand_key[i]) % 256
tmp = box[i]
box[i] = box[j]
box[j] = tmp
#for i in xrange(len(box)):
# print str(box[i]).rjust(5),
# if ((i + 1) % 10) == 0:
# print ''
result = ''
a = 0
j = 0
for i in xrange(len(handled_string)):
a = (a + 1) % 256
j = (j + box[a]) % 256
tmp = box[a]
box[a] = box[j]
box[j] = tmp
result += chr(ord(handled_string[i])^(box[(box[a]+box[j])%256]))
if operation == 'DECODE':
if (int(result[:10]) == 0 or (int(result[:10]) - time.time() > 0)) and \
(result[10:26] == cls._md5(result[26:] + key_b)[:16]):
output_string = result[26:]
else:
output_string = ''
else:
output_string = key_c + base64.b64encode(result)
return output_string
if __name__ == '__main__':
src = 'My name is Hu Ang, I\'m a programmer.'
key = 'fr1e54b8t4n4m47'
encoded_string = AuthCode.encode(src, key)
decoded_string = AuthCode.decode(encoded_string, key)
print 'Source String:', src
print 'After Encode :', encoded_string
print 'After Decode :', decoded_string
print '----------------------------------------------'
# 通过 PHP 方式加密得到的一个密文,然后用 Python 解密
# $source_string = "My name is Hu Ang.";
# $secret_key = 'fr1e54b8t4n4m47';
# $encoded_string = authcode($source_string, 'ENCODE', $secret_key, 0);
php_encoded_string = '82798mEQ6ouQo1rFrbSXT5EHVjZ0gH0WuuZDXd9us/q44JAhmPwBAFZqvwXhvnjgUOJ+5aYh5ed8zNL3cjTOGBY='
print 'Decode string encoded via php:', AuthCode.decode(php_encoded_string, key)
# PS:Python 方式加密过的字符串通过 PHP 解析也成功了。
PHP 代码:
<?php
class AuthCode {
public static function encode($str, $key) {
return self::_auth_code($str, 'ENCODE', $key, 0);
}
public static function decode($str, $key) {
return self::_auth_code($str, 'DECODE', $key, 0);
}
public static function _auth_code($string, $operation = 'DECODE', $key = '', $expiry = 3600) {
/***
* 随机密钥长度 取值 0-32;
* 加入随机密钥,可以令密文无任何规律,即便是原文和密钥完全相同,加密结果也会每次不同,增大破解难度。
* 取值越大,密文变动规律越大,密文变化 = 16 的 $ckey_length 次方
* 当此值为 0 时,则不产生随机密钥
*/
$ckey_length = 4;
$key = md5($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.base64_encode($result);
}
}
}
$source_string = "My name is Hu Ang, I'm a programmer.";
$secret_key = 'fr1e54b8t4n4m47';
echo 'Source String: ' . $source_string . "\n";
$encoded_string = AuthCode::encode($source_string, $secret_key);
echo 'After Encode : ' . $encoded_string . "\n";
$decoded_string = AuthCode::decode($encoded_string, $secret_key);
echo 'After Decode : ' . $decoded_string . "\n";
echo "----------------------------------------------\n";
$python_encoded_string = "88fcnCU6Wb+6LPREpYrhB3NcKS3OU+V8FqQ4uUklvZR170HWlyBLtPKEtP9Ui/qZp1ZqEhF9f5k6XBDixsVgEKk=";
echo 'Decode string encoded via python: ' . AuthCode::decode($python_encoded_string, $secret_key);