MENU

[Authcode]Python3,PHP字符串加解密

April 19, 2019 • Read: 1886 • 技术杂谈


2019年5月12日凌晨1点37分

前两天看到一句话,大概意思是永远不要想一个人去设计某种的加密方式;emmm... 我觉得对我这种正在学习过程中的人来讲,的确是个不错的建议;所以下面的代码或者思路大家用来学习就行,“切勿当真”。
77491d6994ee231eb84de0f1034d48ec.jpg


可能是自己无聊,也可能闲得慌,改写了一个比较简单的字符串加解密类,支持中英文特殊字符串加解密,支持设置有效性。

先简单介绍一下Authcode这个函数,Authcode这个函数很多人都使用,这函数来自Discuz程序,用于加密解密字符串,可以设置钥匙(key)和过期时间,在很多时候都用得着。

emmm... 大佬下面就不用看了吧... 菜鸡[脸红]

有道云的源码里面,又被其作者重整成一个类,可直接调用;以前自己菜,只知道这个函数特别牛X,相同内容每次加密密文都不同,也不知道其原理是什么;这次查了一些资料也引入了自己的一些想法。

Authcode如果用于字符串简单加密是一个不错的选择,想着要是python3 里面有类似函数就很棒,然而查了一下并没有类似作品,索性自己再造一次轮子。

再当python3 写完加密部分之后测试发现,即便相同字符串用相同密匙加密,用php解密函数竟然无法解密???,之后发现原因是两种语言的编码存在问题。

加解密过程中两种语言均会用到chr()和ord()函数,ASCII转换前126位正常,但超过126之后,py chr()是按照返回字符的unicode十进制值,然而PHP就开始返回乱码,原因是对中文和特殊字符会自动截断;思考之后决定PHP计算也直接unicode,unicode包括所有的字符(中文);所以有了上一篇博文,PHP unicode 单字符转换。

加解密原理:

其实吧,特别简单的一个计算方法,“异或”计算,如若不懂,这里不做介绍;针对于此种加密,个人的看法是,增加破解难度的最直接的办法就是提高密匙的复杂度,重改密匙簿的计算方法

说再多也是废话,直接上效果演示。


PHP加解密效果:
PHP.png

Python3加解密效果:
py3.png

两者混用:
1.png
2.png
混用:相同密匙的情况下,利用py3对字符串进行加密,再用PHP进行解密,很完美,当然反过来利用php加密Py解密效果也相同。

最后留下源码:

PHP

<?php

class Mcrypt {
    public static $default_key = 'a!takA:dlmcldEv,e';
    
    /*
     * 字符加解密,一次一密,可定时解密有效
     * 
     */
    public static function encode($string, $key = '', $expiry = 0) {
        $ckeyLength = 16;
        $key = md5($key ? $key : self::$default_key); 
        $key = md5(substr($key, 0, 16).$key.md5(substr($key, 16, 16))); 
        $keya = md5(substr($key, 0, 16)); 
        $keyb = md5(substr($key, 16, 16)); 
        $keyc = substr(md5(microtime()), -$ckeyLength);
        $cryptkey = $keya . md5($keya . $keyc);
        $keyLength = mb_strlen($cryptkey);
        $string = sprintf('%010d', $expiry ? $expiry + intval(time()) : 0) . substr(md5($string . $keyb), 0, 16) . $string;
        $stringLength = mb_strlen($string);
        
        
        $rndkey = array();
        for ($i = 0; $i <= 255; $i++) {
            $rndkey[$i] = self::char_unicode($cryptkey[$i % $keyLength], False);
        }
        
        
        $box = range(0, 255);
        for ($j = $i = 0; $i < 256; $i++) {
            $j = ($j + $box[$i] + $rndkey[$i]) % 256;
            $tmp = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }
        
        
        $result = '';
        for ($a = $j = $i = 0; $i < $stringLength; $i++) {
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            $result .= self::char_unicode(self::char_unicode(mb_substr($string, $i, 1, 'UTF-8'), False) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }

        $result = $keyc . str_replace('=', '', base64_encode($result));
        $result = str_replace(array(
            '+',
            '/',
            '='
        ), array(
            '-',
            '_',
            '.'
        ), $result);
        return $result;
    }
    
    /**
     * 字符加解密,一次一密,可定时解密有效
     * 
     */
    public static function decode($string, $key = '') {
        $string = str_replace(array(
            '-',
            '_',
            '.'
        ), array(
            '+',
            '/',
            '='
        ), $string);
        $ckeyLength = 16;
        $key = md5($key ? $key : self::$default_key); 
        $key = md5(substr($key, 0, 16).$key.md5(substr($key, 16, 16))); 
        $keya = md5(substr($key, 0, 16)); 
        $keyb = md5(substr($key, 16, 16)); 
        $keyc = substr($string, 0, $ckeyLength);
        $cryptkey = $keya . md5($keya . $keyc);
        $keyLength = strlen($cryptkey);
        $string = base64_decode(substr($string, $ckeyLength));
        $stringLength = mb_strlen($string);
        
        
        $rndkey = array();
        for ($i = 0; $i <= 255; $i++) {
            $rndkey[$i] = self::char_unicode($cryptkey[$i % $keyLength], False);
        }
        $box = range(0, 255);
        
        for ($j = $i = 0; $i < 256; $i++) {
            $j = ($j + $box[$i] + $rndkey[$i]) % 256;
            $tmp = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }
        
        $result = '';
        for ($a = $j = $i = 0; $i < $stringLength; $i++) {
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            $result .= self::char_unicode(self::char_unicode(mb_substr($string, $i, 1, 'UTF-8'), False) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }
        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 '';
        }
    }
    
    public static function char_unicode($str, $DECODE = True) {
        $result = '';
        if ($DECODE === False) {
            $unicodestr = intval(base_convert(bin2hex(iconv('utf-8', 'UCS-4', $str)), 16, 10));
            $result = $unicodestr;
        } else {
            $temp = intval($str);
            $result = iconv('UCS-2BE', 'utf-8', ($temp < 256) ? chr(0) . chr($temp) : chr($temp / 256) . chr($temp % 256));
        }
        return $result;
    }
}

Python3

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : Mcrypt.class.py
# @Author: MoMing
# @Date  : 2019/4/15
# @Desc  : 字符串加解密


class Mcrypt:
    default_key = 'a!takA:dlmcldEv,e'

    @staticmethod
    def encode(string='', key='', expiry=0):
        """
        :param string: 待加密字符串
        :param key: 加密密匙
        :param expiry: 有效期,0->永久
        :return:
        """
        import time
        from base64 import b64encode
        ckeylength = 16
        if key == '':
            key = Mcrypt.default_key
        key = Mcrypt.__md5(key)
        key = Mcrypt.__md5(key[0:16] + key + Mcrypt.__md5(key[16:]))
        keya = Mcrypt.__md5(key[0:16])
        keyb = Mcrypt.__md5(key[16:])
        keyc = Mcrypt.__md5(str(int(time.time()) * 10000))[-ckeylength:]
        cryptkey = keya + Mcrypt.__md5(keya + keyc)
        keylength = len(cryptkey)
        if expiry != 0:
            string = str(expiry + int(time.time())) + Mcrypt.__md5(string + keyb)[0:16] + string
        else:
            string = '0000000000' + Mcrypt.__md5(string + keyb)[0:16] + string
        stringlength = len(string)

        rndkey = dict()
        for i in range(256):
            rndkey[i] = ord(cryptkey[i % keylength])

        box = dict()
        for i in range(256):
            box[i] = i

        j = int(0)
        for i in range(256):
            j = (j + box[i] + rndkey[i]) % 256
            tmp = box[i]
            box[i] = box[j]
            box[j] = tmp

        result = ''
        a = j = int(0)

        for i in range(stringlength):
            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]))

        base64 = str(b64encode(result.encode('utf-8')), 'utf-8')
        result = keyc + base64.replace('=', '')
        old = ['+', '/', '=']
        new = ['-', '_', '.']
        return Mcrypt.__str_replace(result, old, new)

    @staticmethod
    def decode(string='', key=''):
        """
        :param string: 加密字符串
        :param key: 加密密匙
        :return: 解密失败返回空字符
        """
        import time
        from base64 import b64decode
        ckeylength = 16
        new = ['+', '/', '=']
        old = ['-', '_', '.']
        string = Mcrypt.__str_replace(string, old, new)
        if key == '':
            key = Mcrypt.default_key
        key = Mcrypt.__md5(key)
        key = Mcrypt.__md5(key[0:16] + key + Mcrypt.__md5(key[16:]))
        keya = Mcrypt.__md5(key[0:16])
        keyb = Mcrypt.__md5(key[16:])
        keyc = string[:ckeylength]
        string = string[16:]
        missing_padding = 4 - len(string) % 4
        if missing_padding:
            string += '=' * missing_padding
        string = str(b64decode(string), 'utf-8')
        cryptkey = keya + Mcrypt.__md5(keya + keyc)
        keylength = len(cryptkey)
        stringlength = len(string)

        rndkey = dict()
        for i in range(256):
            rndkey[i] = ord(cryptkey[i % keylength])

        box = dict()
        for i in range(256):
            box[i] = i

        j = int(0)
        for i in range(256):
            j = (j + box[i] + rndkey[i]) % 256
            tmp = box[i]
            box[i] = box[j]
            box[j] = tmp

        result = ''
        a = j = int(0)
        for i in range(stringlength):
            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]))
        try:
            if (int(result[:10]) == 0 or int(result[:10]) - int(time.time()) > 0) \
                    and result[10:10 + ckeylength] == Mcrypt.__md5(result[10 + ckeylength:] + keyb)[0:16]:
                return result[10 + ckeylength:]
            else:
                return ''
        except ValueError:
            return ''

    @staticmethod
    def __md5(string=str()):
        """
        :param string: hash 字符串
        :return: md5
        """
        import hashlib
        m = hashlib.md5()
        m.update(string.encode('utf-8'))
        return m.hexdigest()

    @staticmethod
    def __str_replace(string, old=[], new=[]):
        """
        old 和 new 长度必须相同,新旧必须对应
        :param old: 将被替换的子字符列表。
        :param new: 新字符串,用于替换old子字符列表
        :param string: 被替换成字符串
        :return: 新字符串
        """
        for i in range(len(old)):
            string = string.replace(old[i], new[i])
        return string

使用方法:

PHP

<?php
header('Content-type:application/json;;charset=UTF-8');
require_once __dir__ . DIRECTORY_SEPARATOR . 'Mcrypt.class.php';

$str = '123456789[密文]';

$enstr = Mcrypt::encode($str);
$destr = Mcrypt::decode($enstr);

echo '原文:'.$str.PHP_EOL;
echo '加密:'.$enstr.PHP_EOL;
echo '解密:'.$destr.PHP_EOL;

Python3

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : main.py
# @Author: MoMing
# @Date  : 2019/4/14
# @Desc  : Mcrypt Main


from Mcrypt import Mcrypt

string = 'ABC1234567[密文]'

enstr = Mcrypt.encode(string)
destr = Mcrypt.decode(enstr)

print('原文:', string)
print('加密:', enstr)
print('解密:', destr)
Last Modified: June 13, 2020