Python-SecureHTTP

通过使用RSA+AES让HTTP传输更加安全,即C/S架构的加密通信! (Make HTTP transmissions more secure via RSA+AES, encrypted communication for C/S architecture.)

PyPI Pyversions implementation

安装(Installation)

使用pip安装

# 正式版(Release)
$ pip install -U SecureHTTP
# 开发版(Dev)
$ pip install -U git+https://github.com/staugur/Python-SecureHTTP.git@master

关于依赖库

SecureHTTP依赖pycryptodomex。

PyCryptodome是PyCrypto的一个分支,它为PyCrypto的最后一个正式版本(2.6.1)带来了一些增强功能,如支持pypy。 PyCryptodomex即PyCryptodome,区别在于导入包名不同,前者导入包名是Cryptodome,后者是Crypto(同pycrypto)。

注意:v0.5.0开始,已经弃用PyCrypto/PyCryptodome!

测试用例(Test)

温馨提示:完整的测试要求安装php、go以便运行多语言测试

$ git clone https://github.com/staugur/Python-SecureHTTP && cd Python-SecureHTTP
$ make dev && make test

简单示例(Demo)

  1. AES加密、解密

    from SecureHTTP import AESEncrypt, AESDecrypt
    # 加密后的密文
    ciphertext = AESEncrypt('ThisIsASecretKey', 'Hello World!', output="hex")
    # 解密后的明文
    plaintext = AESDecrypt("ThisIsASecretKey", ciphertext, input="hex)
    
  2. RSA加密、解密

    from SecureHTTP import RSAEncrypt, RSADecrypt, generate_rsa_keys
    # 生成密钥对
    (pubkey, privkey) = generate_rsa_keys(incall=True)
    # 加密后的密文
    ciphertext = RSAEncrypt(pubkey, 'Hello World!')
    # 解密后的明文
    plaintext = RSADecrypt(privkey, ciphertext)
    
  3. C/S加解密示例: 点此查看以下模拟代码的真实WEB环境示例

    # 模拟C/S请求
    from SecureHTTP import EncryptedCommunicationClient, EncryptedCommunicationServer, generate_rsa_keys
    post = {u'a': 1, u'c': 3, u'b': 2, u'data': ["a", 1, None]}
    resp = {u'msg': None, u'code': 0}
    # 生成密钥对
    (pubkey, privkey) = generate_rsa_keys(incall=True)
    # 初始化客户端类
    client = EncryptedCommunicationClient(pubkey)
    # 初始化服务端类
    server = EncryptedCommunicationServer(privkey)
    # NO.1 客户端加密数据
    c1 = client.clientEncrypt(post)
    # NO.2 服务端解密数据
    s1 = server.serverDecrypt(c1)
    # NO.3 服务端返回加密数据
    s2 = server.serverEncrypt(resp)
    # NO.4 客户端获取返回数据并解密
    c2 = client.clientDecrypt(s2)
    # 以上四个步骤即完成一次请求/响应
    
  4. B/S加解密示例: 前端使用AES+RSA加密,后端解密

加密传输通信的流程(Encrypted Transmission Process)

总体流程:客户端上传数据加密 ==> 服务端获取数据解密 ==> 服务端返回数据加密 ==> 客户端获取数据解密

NO.1 客户端上传数据加密流程:

1. 客户端随机产生一个16位的字符串,用以之后AES加密的秘钥,AESKey。
2. 使用RSA对AESKey进行公钥加密,RSAKey。
3. 参数加签,参考"加签、验签规则流程"。
4. 将明文的要上传的数据包(字典/Map)转为Json字符串,使用AESKey加密,得到JsonAESEncryptedData。
5. 封装为{key : RSAKey, value : JsonAESEncryptedData}的字典上传服务器,服务器只需要通过key和value,然后解析,获取数据即可。

NO.2 服务端获取数据解密流程:

1. 获取到RSAKey后用服务器私钥解密,获取到AESKey
2. 获取到JsonAESEncriptedData,使用AESKey解密,得到明文的客户端上传上来的数据。
3. 验签,参考"加签、验签规则流程"
4. 返回明文数据

NO.3 服务端返回数据加密流程:

1. 将要返回给客户端的数据(字典/Map)进行加签并将签名附属到数据中
2. 上一步得到的数据转成Json字符串,用AESKey加密处理,记为AESEncryptedResponseData
3. 封装数据{data : AESEncryptedResponseData}的形式返回给客户端

NO.4 客户端获取数据解密流程:

1. 客户端获取到数据后通过key为data得到服务器返回的已经加密的数据AESEncryptedResponseData
2. 对AESEncryptedResponseData使用AESKey进行解密,得到明文服务器返回的数据。

加签、验签规则流程(Signature Rule)

@加签、验签规则:

加签,即 EncryptedCommunicationClient.clientEncryptEncryptedCommunicationServer.serverEncrypt 方法,签名已经内置,支持传入 signIndex 参数生成不同签名。

验签,即 EncryptedCommunicationClient.clientDecryptEncryptedCommunicationServer.serverDecrypt 方法,验签已经内置,验签失败触发 SignError 错误。

signIndex:

False, 不签名、不验签
None, 签名数据中所有字段(目前版本,如果嵌套了无序数据类型,可能会验签失败)
str, 指定参与签名的字段,格式是"key1,key2",这是目前建议的一种方法,只针对部分核心字段签名和验签

@签名步骤:

  1. 构造规范化的请求字符串

    按照字母升序,对参数名称进行排序。

  2. 排序后的参数以”参数名=值&”的形式连接,其中参数名和值要进行URL编码,使用UTF-8字符集,编码规则是:

    2.1 对于字符 A-Z、a-z、0-9以及字符“-”、“_”、“.”、“~”不编码;
    2.2 对于其他字符编码成“%XY”的格式,其中XY是字符对应ASCII码的16进制表示。比如英文的双引号(”)对应的编码就是%22.
    2.3 英文空格( )编码为%20,而不是加号(+)。
    
  3. 对以上规范化的字符串使用摘要算法得到签名

@验签步骤:

验签同签名类似。

@注意事项:

签名规则可以参考阿里云API签名

CLI Documentation

命令行工具用于辅助性功能,目前主要是用于生成RSA密钥对,有两种方法。

1. generate_rsa_keys.py

这是Python自身生成的RSA密钥对,它支持输出到控制台或写入文件、设置私钥密码等,请查看命令帮助:

# generate_rsa_keys.py -h
usage: generate_rsa_keys.py [-h] [-v] [-l {1024,2048,3072,4096}]
                        [-p PASSPHRASE] [-w]

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         Print the SecureHTTP Version
  -l {1024,2048,3072,4096}, --length {1024,2048,3072,4096}
                        Key length, default is 2048.
  -p PASSPHRASE, --passphrase PASSPHRASE
                        The pass phrase used for protecting the private key.
  -w, --write           Write a key pair file in PEM format

2. generate_rsa_keys.sh

这是使用系统OpenSSL生成的RSA密钥对,此命令可以传递一个位置:密钥长度(默认1024),在当前目录保存4个文件,分别是pkcs1格式密钥对和pkcs8密钥对,比如:

generate_rsa_keys.sh 2048

SecureHTTP.js

说明:JS版提供了一个 SecureHTTP.js 文件封装了相关加密代码:包含AES加密解密、RSA加密解密、浏览器端加密通信封装(RSA+AES+MD5)。

版本:(version) 当前版本 v0.1.0,对应SecureHTTP的版本是 v0.2.0+

CDN: https://static.saintic.com/securehttp.js/v0.1.0/SecureHTTP.js

依赖:(github) brix/crypto-jstravist/jsencrypt,前者是AES相关、后者是RSA相关。

PS:

这只是用在浏览器环境,不适用于node.js开发中,如果您迫不及待使用node.js,依赖库可以使用crypto-js和node-jsencrypt,再行编写加密、解密等函数。

关于算法,请查看 关于通信过程加密算法的说明。

<!--
### 引入AES加密库!
关于crypto-js库,官方地址是:https://code.google.com/archive/p/crypto-js/,可是在墙外,上面给出的是github地址,两处下载的包有差异。
-->

<!--若从googlecode下载则可用以下两种方式引入:-->
<!--NO.1 引入组件源码
<script src="CryptoJS-v3.1.2/components/core-min.js"></script>
<script src="CryptoJS-v3.1.2/components/enc-base64-min.js"></script>
<script src="CryptoJS-v3.1.2/components/cipher-core-min.js"></script>
<script src="CryptoJS-v3.1.2/components/aes-min.js"></script>
<script src="CryptoJS-v3.1.2/components/md5-min.js"></script>
-->
<!--NO.2 引入独立汇总(汇总文件是在组件一个或多个文件夹拼接后压缩的,引入汇总文件无需担心它的依赖)
<script src="CryptoJS-v3.1.2/rollups/aes.js"></script>
<script src="CryptoJS-v3.1.2/rollups/md5.js"></script>
-->

<!--若从github下载则引入以下文件即可代替上述所有(此为建议,可从bootcdn引入此文件)-->
<script src="crypto-js-3.1.9-1/crypto-js.js"></script>
或引用cdn的:
<script src="https://cdn.bootcss.com/crypto-js/3.1.9-1/crypto-js.js"></script>


<!--
### 引入RSA加密库!
关于jsencrypt.js库,可以自行下载或引入cdn,示例为bootcdn链接。
-->
<script src="https://cdn.bootcss.com/jsencrypt/3.0.0-rc.1/jsencrypt.min.js"></script>

API:

> 函数: AESEncrypt、AESDecrypt、RSAEncrypt
函数功能和用法与Python版对应
> 类: EncryptedCommunicationBrowser -> (browserEncrypt, browserDecrypt)
亦对应Python版中EncryptedCommunicationClient,browserEncrypt和browserDecrypt方法也分别对应clientEncrypt和clientDecrypt。 差异在于:browserEncrypt必须传入有效的signIndex字段,暂不支持false和全部提交数据的加签。

Demo:

<!DOCTYPE html>
<html>
<head>
    <title>SecureHTTP.js</title>
</head>
<body>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <!--引入AES加密库-->
    <script src="https://cdn.bootcss.com/crypto-js/3.1.9-1/crypto-js.js"></script>
    <!--引入RSA加密库-->
    <script src="https://cdn.bootcss.com/jsencrypt/3.0.0-rc.1/jsencrypt.min.js"></script>
    <!--引入加密通信封装库-->
    <script src="https://static.saintic.com/securehttp.js/v0.1.0/SecureHTTP.js"></script>
    <script type="text/javascript">
        var eb = new EncryptedCommunicationBrowser(pubkey);
        var post = {a:1, b:2, c:3};
        $.ajax({
            url: "SecureHTTP API URL",
            type: 'post',
            data: eb.browserEncrypt(post, "a,b,c"),
            dataType: 'json',
            success: function(res) {
                var resp = eb.browserDecrypt(res);
                console.info(resp);
            },
            error: function(xhr) {
                alert('出错了');
            }
        });
    </script>
</body>
</html>

API Documentation

PS:

接口中函数返回值,正常情况下,Python2.7返回unicode,Python3.x返回str。

函数参数要求字符串的,一般建议py2中使用unicode,py3中使用bytes,请注意编码问题。

另,RSADecrypt解密中,新增一个sentinel参数,一个挺重要的解密失败的标记,关于此参数的建议,请参考:Pycryptodomex接口文档,注意文档中 Warning 部分。

Python-SecureHTTP

关于通信过程加密算法的说明:

  1. AES加解密:

    模式:CBC
    密钥长度:128,192,256bit
    密钥key:16,24,32bytes(建议使用ASCII编码密钥),初始偏移向量iv固定为key前16个字节
    补码方式:PKCS7Padding(在AES中理论上同PKCS5Padding)
    加密结果编码方式:十六进制或base64编码
    
  2. RSA加解密:

    算法:RSA
    填充:RSA_PKCS5_PADDING
    密钥格式:符合PKCS#1规范,密钥对采用PEM形式,公钥要求pkcs1或pkcs8格式,私钥要求pkcs1格式
    
  3. 签名:

    可选对请求参数或数据添加公共参数后排序再使用摘要算法签名(MD5、SHA1等)
    

关于参数中字符串说明,若无指定,则:

  • py2: str, unicode
  • py3: str, bytes
copyright:
  1. 2019 by staugur.
license:

BSD 3-Clause, see LICENSE for more details.

SecureHTTP.RSAEncrypt(pubkey, plaintext, output='base64')

RSA公钥加密

参数:
  • pubkey – str,bytes: pkcs1或pkcs8格式公钥
  • plaintext – str,bytes: 准备加密的文本消息
  • output – str: Output format: base64 (default), hex (hexadecimal)
返回:

str,unicode: base64编码的字符串

SecureHTTP.RSADecrypt(privkey, ciphertext, passphrase=None, sentinel='ERROR', input='base64')

RSA私钥解密

参数:
  • privkey – str,bytes: pkcs1格式私钥
  • ciphertext – str,bytes: 已加密的消息
  • passphrase – str,bytes: 私钥保护的密码短语
  • sentinel – any type: 检测到错误时返回的标记,默认返回ERROR字符串
  • input – str: Input format: base64 (default) or hex (hexadecimal), refer to the output parameter of RSAEncrypt()
返回:

str,unicode: 消息原文

SecureHTTP.AESEncrypt(key, plaintext, output='base64', output_type=None)

AES Encryption Function.

参数:
  • key – basestring: 16 24 32 bytes with ASCII.
  • plaintext – basestring: Plaintext message to be encrypted
  • output – str: Output format: base64 (default), hex (hexadecimal)
  • output_type – basestring: The type of encrypted string to be output, refer to the dst_type parameter of required_string()
Raises:

AESError,ValueError

返回:

Encrypted ciphertext

SecureHTTP.AESDecrypt(key, ciphertext, input='base64', output_type=None)

AES Decryption Function.

参数:
  • key – basestring: Refer to the key parameter of AESEncrypt()
  • ciphertext – basestring: Ciphertext message to be decrypted
  • input – str: Input format: base64 (default) or hex (hexadecimal), refer to the output parameter of AESEncrypt()
  • output_type – basestring: The type of decrypted string to be output, refer to the dst_type parameter of required_string()
Raises:

AESError,binascii.Error,ValueError,TypeError

返回:

Decrypted plaintext

class SecureHTTP.EncryptedCommunicationClient(PublicKey)

基类:SecureHTTP.EncryptedCommunicationMix

客户端:主要是公钥加密

clientDecrypt(encryptedRespData)

客户端获取服务端返回的加密数据并解密 for NO.4

参数:encryptedRespData – dict: 服务端返回的加密数据,其格式应该是 {data: AES加密数据}
Raises:TypeError,SignError
返回:解密验签成功后,返回服务端的消息原文
clientEncrypt(post, **signargs)

客户端发起加密请求通信 for NO.1

参数:
  • post – dict: 请求的数据
  • signIndex – str: 参与排序加签的键名,False表示不签名,None时表示加签post中所有数据,非空时请用逗号分隔键名(字符串)
  • signMethod – str: 签名算法,可选md5、sha1、sha256
返回:

dict: {key=RSAKey, value=加密数据}

class SecureHTTP.EncryptedCommunicationServer(PrivateKey)

基类:SecureHTTP.EncryptedCommunicationMix

服务端:主要是私钥解密

serverDecrypt(encryptedPostData)

服务端获取请求数据并解密 for NO.2

参数:encryptedPostData – dict: 请求的加密数据
Raises:TypeError,SignError
返回:解密后的请求数据原文
serverEncrypt(resp, **signargs)

服务端返回加密数据 for NO.3

参数:
  • resp – dict: 服务端返回的数据,目前仅支持dict
  • signIndex – tuple,list: 参与排序加签的键名,False表示不签名,None时表示加签resp中所有数据,非空时请用逗号分隔键名(字符串)
  • signMethod – str: 签名算法,可选md5、sha1、sha256
Raises:

TypeError,ValueError

返回:

dict: 返回dict,格式是 {data: AES加密数据}

SecureHTTP.generate_rsa_keys(incall=False, length=2048, passphrase=None)

生成RSA所需的公钥和私钥,公钥格式pkcs8,私钥格式pkcs1。

参数:
  • incall – bool: 是否内部调用,默认False表示提供给脚本调用直接打印密钥,True不打印密钥改为return返回
  • length – int: 指定密钥长度,默认1024,需要更强加密可设置为2048
  • passphrase – str: 私钥保护的密码短语
返回:

tuple(public_key, private_key)

exception SecureHTTP.SignError

基类:SecureHTTP.SecureHTTPException

签名异常:加签异常、验签不匹配等

exception SecureHTTP.AESError

基类:SecureHTTP.SecureHTTPException

AES异常:加密、解密时参数错误

exception SecureHTTP.RSAError

基类:SecureHTTP.SecureHTTPException

RSA异常:密钥错误、加密解密错误

更新日志(CHANGELOG)

V0.5.0

Released in 2019-04-25

  • fix: 修复AES加密key长度问题 (#2)
  • feat: 新增 required_string() 转化不同py版本的字符串
  • feat: 新增AES加密参数,可以定义返回加密的字符串的类型
  • feat: 新增RSA加密输出/解密输入的编码参数(hex、base64)
  • feat: 更新 EncryptedCommunicationMix 中生成AESKey的函数,现在生成的key默认为16字节
  • chore: AES加密解密函数调整,填充方法改为pkcs7,理论上兼容pkcs5,但可能会遇到与旧版本不兼容
  • chore: 不再支持pycrypto和pycryptodome,一律使用pycryptodomex!
  • chore: Update README.md to README.rst
  • chore: 更新文档
  • todo: RSA加解密调整

V0.4.1

Released in 2019-04-21

  • fix: 修复python2.7下RSAEncrypt的plaintext参数编码
  • feat: 新增RSADecrypt参数sentinel
  • docs: 更新js和api说明

V0.4.0

Released in 2019-04-05

  • fix & feat: set aes output/input format, support hex and base64

V0.3.0

Released in 2019-01-22

  • 生成、导入RSA私钥时可以设置密码
  • 命令行参数化,可设置长度、密码并写入PEM文件
  • 自定义签名算法:md5、sha1、sha256
  • RSA加密、解密弃用rsa包,改用pycryptdome的PKCS1_v1_5
  • 注意:Pycrypto、Pycryptodome将弃用!

V0.2.4

Released in 2019-01-18

  • 修复部分编码问题
  • 增加sha1、sha256签名

V0.2.3

Released in 2019-01-16

  • 修复部分编码问题
  • 加密库从pycrypto改为pycryptodomex
  • 支持pypy

V0.2.2

Released in 2019-01-15

  • 支持python3

V0.2.1

Released in 2019-01-11

  • 修复签名排序bug

V0.2.0

Released in 2019-01-08

  • 修改签名bug
  • 增加签名可选参数实现关键字段签名、不签名等
  • 优化加密、解密类,简化用户操作,方便调用(不兼容0.1.0版本)

V0.1.0

Released in 2019-01-04

  • 首发版本
  • RSA密钥对生成
  • RSA加密、解密
  • AES加密、解密
  • 加密通信流程初步完成:客户端上传数据加密 ==> 服务端获取数据解密 ==> 服务端返回数据加密 ==> 客户端获取数据解密