用 Python 改写 PHP 加密解密算法 authcode

刚刚读了一遍 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);