在 Windows 10 上安装 PyCtypto

PyCrypto 是一个 Python 加密库,核心使用 C 实现,因此在安装的过程中需要编译。

最简单的按照方法莫过于寻找编译好的 exe 版本进行安装。但由于这个库已经 3 年多没有维护了,目前能找到的编译好的版本基本上都针对较老的 Python 版本,例如 Python 3.3/3.5 等等,这些 exe 版本都无法在我需要的环境中安装成功。

我的环境:

  • Windows 10 x64
  • Python 3.6.2

要成功安装,首先必须安装 Microsoft 的编译工具。如果已经安装了 Visual Studio ,则可以跳过这一步。若还没有,而且后续也没有使用 VS 的需求,可以下载独立的编译工具 Visual C++ 2015 Build Tools

使用 pip 安装:

1
pip install pycrypto

在安装过程中会出现编译失败。这是由于新的 python 源码 include\pyport.h 不再包含 #include < stdint.h > ,导致 intmax_t 未定义。

我们需要在编译环境中设置 CL 参数才能成功编译。

执行下面命令的时候需要注意两点:

  1. 如果使用的是独立版本的 Visual C++ Build Tool,文件夹可能会不同,请自行修改。
  2. 需要在同一个 Shell 会话中执行下面所有命令。这意味着如果你使用了 Python 虚拟环境,需要先进入虚拟环境,再执行 vcvarsall 以及 set CL...
1
2
3
4
5
6
7
8
9
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC>vcvarsall
set CL=-FI"%VCINSTALLDIR%\INCLUDE\stdint.h"
pip install pycrypto
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC>pip install pycrypto
Collecting pycrypto
Using cached pycrypto-2.6.1.tar.gz
Installing collected packages: pycrypto
Running setup.py install for pycrypto ... done
Successfully installed pycrypto-2.6.1

迁移到 PyCryptodome

上面已经提到, PyCrypto 已经三年多没更新了 。因此有人在 PyCrypto 的 Issues 列表中 号召 PyCrypto 的使用者 迁移到 PyCryptodome 。我已经完成了迁移,下面记录一下迁移过程。

下面这段代码是使用 PyCrypto 库进行 AES 对称加密的封装,其中 key 代表密钥, plain_textcipher_text 分别代表明文和密文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import binascii
from Crypto.Cipher import AES
def _encrypt(key, plain_text):
mod = len(plain_text) % 16
if mod > 0:
# 补齐16的倍数
zero = '\0' * (16 - mod)
plain_text += zero
IV = 16 * ''
aes = AES.new(key, AES.MODE_ECB, IV)
cipher_text = binascii.hexlify(aes.encrypt(plain_text)).decode()
return cipher_text
def _decrypt(key, cipher_text):
IV = 16 * ''
aes = AES.new(key, AES.MODE_ECB, IV)
plain_text = aes.decrypt(binascii.unhexlify(cipher_text)).decode().rstrip('\0')
return plain_text

PyCryptodome 使用相同的包名和模块名,因此需要先删除 PyCrypto,然后安装 PyCryptodome :

1
2
pip uninstall pycrypto
pip install pycryptodome

原来的代码如果一字不改,在调用 _encrypt 进行加密的时候会报错:

1
TypeError: IV is not meaningful for the ECB mode

这是因为 IV 仅应用于 MODE_CBC , MODE_CFB , MODE_OFBMODE_OPENPGPAES 文档 中有说明。

PyCryptodome 的作者在 Compatibility with PyCrypto 中也提到:

Symmetric ciphers do not have ECB as default mode anymore. ECB is not semantically secure and it exposes correlation across blocks. An expression like AES.new(key) will now fail. If ECB is the desired mode, one has to explicitly use AES.new(key, AES.MODE_ECB).

关于 ECB 模式不是语义安全的说法,这里有更详细介绍: Why shouldn’t I use ECB encryption?

不过我们就不深究安全性的问题了。去掉 IV:

1
2
3
4
def _encrypt(key, plain_text):
aes = AES.new(key, AES.MODE_ECB)
cipher_text = binascii.hexlify(aes.encrypt(plain_text)).decode()
return cipher_text

出现新的错误:

TypeError: Only byte strings can be passed to C code

这是因为加密时提供的字符串必须使用 byte 格式而不能使用 str 格式。是所有 str.encode() 进行转换即可。

1
2
3
4
def _encrypt(key, plain_text):
aes = AES.new(key.encode(), AES.MODE_ECB)
cipher_text = binascii.hexlify(aes.encrypt(plain_text.encode())).decode()
return cipher_text

迁移成功的完整代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def _encrypt(key, plain_text):
mod = len(plain_text) % 16
if mod > 0:
# 补齐16的倍数
zero = '\0' * (16 - mod)
plain_text += zero
aes = AES.new(key.encode(), AES.MODE_ECB)
cipher_text = binascii.hexlify(aes.encrypt(plain_text.encode())).decode()
return cipher_text
def _decrypt(key, cipher_text):
aes = AES.new(key.encode(), AES.MODE_ECB)
plain_text = aes.decrypt(binascii.unhexlify(cipher_text)).decode().rstrip('\0')
return plain_text

去掉解密后字符串结尾的控制字符

有时候(尤其是解密使用其它语言加密的密文时),解密后的字符串末尾为了补全长度,带有用于填充的控制字符。这些控制字符一般是 \0 ,使用 rstrip('\0') 就可以去掉。

但当我解密使用 Node.js 加密的密文时,结果是这样的:

1
HelloWorld!你好世界!\x06\x06\x06\x06\x06\x06

因为字符串的长度必须是 16 的整数倍,Node.js 在字符串最后填充了具体的位数。如果是需要补 6 字符,它会填充 6 个 \0x06 。如果需要补 3 个字符,则填充 3 个 \0x03 。由于缺少的字符数不能确定,使用 rstrip('\0') 就不行了。

可以使用正则来处理:

1
2
_zero_re = re.compile(r'[\x00-\x1F]+$')
plain_text = _zero_re.sub('', plain_text)

其它的 Python 密码学库

Cryptograph

cryptography includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and key derivation functions.

PyNaCl: Python binding to the libsodium library

PyNaCl is a Python binding to libsodium, which is a fork of the Networking and Cryptography library. These libraries have a stated goal of improving usability, security and speed. It supports Python 2.7 and 3.3+ as well as PyPy 2.6+.

参考文档

全文完

留言