https://pypi.org/project/pycryptodomex/ 安装目录为:
Cryptodome
https://pypi.org/project/pycryptodome/ 安装目录为:Crypto
,同pycrypto
模块一样,因此建议安装pycryptodomex
**_python3 -m pip install pycryptodomex_**
一 传统模式
1 ECB MODE
https://www.pycryptodome.org/en/latest/src/cipher/classic.html#ecb-mode-1
- Electronic Codebook Book, 电码本模式
- 数据加密前需要pad, 默认为
pkcs7
, 将数据补全为block_size
的整数倍
Python
import json
import base64
from Cryptodome.Cipher import AES
from Cryptodome.Util import Padding
# key = AES.get_random_bytes(16)
key = ('A' * 16).encode() # 16 bytes, 即为AES-128 (16 * 8 bits)
data = json.dumps(dict(name='zoro', age=18)).encode()
pad_data = Padding.pad(data, AES.block_size)
mode = AES.MODE_ECB # AES-128-ECB
cipher = AES.new(key, mode)
cy_bytes = cipher.encrypt(pad_data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('ECB base64:', ct)
cipher = AES.new(key, mode)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(Padding.unpad(dec_message, AES.block_size).decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAA
ECB base64: BkA+I8H+dwwcUCdis5EGYcGnm9eHpqz6zEc3Qa71U5Y=
Parsed: {'name': 'zoro', 'age': 18}
'''
JavaScript
var key = 'AAAAAAAAAAAAAAAA' // 不能直接使用原始的KEY,需要用Utf8解码
var key = CryptoJS.enc.Utf8.parse('AAAAAAAAAAAAAAAA');
var encrypted = 'BkA+I8H+dwwcUCdis5EGYcGnm9eHpqz6zEc3Qa71U5Y=';
var pt = CryptoJS.AES.decrypt(encrypted, key, {
mode: CryptoJS.mode.ECB,
}).toString(CryptoJS.enc.Utf8);
console.log(JSON.parse(pt));
// {name: 'zoro', age: 18}
2 CBC MODE
https://www.pycryptodome.org/en/latest/src/cipher/classic.html#cbc-mode-1
- Cipher Block Chaining, 密码分组链接模式
- 数据加密前需要pad
- 数据加密时,可指定IV,默认会自动生成随机IV
- 解密时需要KEY + IV
Python
import json
import base64
from Cryptodome.Cipher import AES
from Cryptodome.Util import Padding
key = ('A' * 16).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
pad_data = Padding.pad(data, AES.block_size)
mode = AES.MODE_CBC
cipher = AES.new(key, mode)
iv = cipher.iv
cy_bytes = cipher.encrypt(pad_data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('CBC base64:', ct)
cipher = AES.new(key, mode, iv=iv)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(Padding.unpad(dec_message, AES.block_size).decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAA
IV: diU6MY0+Y7+LH6asCIvrhQ==
CBC base64: avCVt0G9tp4RC2bSAUrofV2DQU4Jggbgd7nMLG3im0E=
Parsed: {'name': 'zoro', 'age': 18}
'''
# 固定IV
iv = ('B' * 16).encode() # 16 bytes
cipher = AES.new(key, mode, iv=iv)
cy_bytes = cipher.encrypt(pad_data)
ct = base64.b64encode(cy_bytes).decode()
cipher = AES.new(key, mode, iv=iv)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(Padding.unpad(dec_message, AES.block_size).decode())
'''输出
KEY: AAAAAAAAAAAAAAAA
IV: QkJCQkJCQkJCQkJCQkJCQg==
CBC base64: QrG4mKyOKq3fBtDNWUE9CwCNjgDmz7YtDULyWnrW4cs=
Parsed: {'name': 'zoro', 'age': 18}
'''
JavaScript
var key = CryptoJS.enc.Utf8.parse('AAAAAAAAAAAAAAAA');
var iv = CryptoJS.enc.Base64.parse('QkJCQkJCQkJCQkJCQkJCQg==');
// or: var iv = CryptoJS.enc.Utf8.parse('BBBBBBBBBBBBBBBB');
var encrypted = 'QrG4mKyOKq3fBtDNWUE9CwCNjgDmz7YtDULyWnrW4cs=';
CryptoJS.AES.decrypt(encrypted, key, {
mode: CryptoJS.mode.CBC,
iv: iv,
}).toString(CryptoJS.enc.Utf8);
// '{"name": "zoro", "age": 18}'
3 CTR MODE
https://www.pycryptodome.org/en/latest/src/cipher/classic.html#ctr-mode-1
- Counter, 计算器模式
- 数据加密前无需pad
- 数据加密时,可指定nonce,默认会自动生成随机nonce,最大长度为15 (block_size-1 ?)
- 解密时需要KEY + nonce
import json
import base64
from Cryptodome.Cipher import AES
key = ('A' * 24).encode()
nonce = ('B' * 15).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
mode = AES.MODE_CTR
cipher = AES.new(key, mode, nonce=nonce)
cy_bytes = cipher.encrypt(data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('nonce:', nonce.decode())
print('CTR base64:', ct)
cipher = AES.new(key, mode, nonce=nonce)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(dec_message.decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAAAAAAAAAA
nonce: BBBBBBBBBBBBBBB
CTR base64: 2sC9xBr1Uk/qo7rkurfP9GfSZMkx6UVOZaC6
Parsed: {'name': 'zoro', 'age': 18}
'''
4 CFB MODE
https://www.pycryptodome.org/en/latest/src/cipher/classic.html#cfb-mode-1
- Cipher FeedBack, 密码反馈模式
- 数据加密前无需pad
- 数据加密时,可指定IV,默认会自动生成随机IV
- 解密时需要KEY + IV
Python
import json
import base64
from Cryptodome.Cipher import AES
key = ('A' * 16).encode()
iv = ('B' * 16).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
mode = AES.MODE_CFB
cipher = AES.new(key, mode, iv=iv)
cy_bytes = cipher.encrypt(data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('IV:', iv.decode())
print('CFB base64:', ct)
cipher = AES.new(key, mode, iv=iv)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(dec_message.decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAA
IV: BBBBBBBBBBBBBBBB
CFB base64: ShJ+eeRSprU2s/ekM2ht4k9EBuvuUn1usyCH
Parsed: {'name': 'zoro', 'age': 18}
'''
JavaScript
var key = CryptoJS.enc.Utf8.parse('AAAAAAAAAAAAAAAA');
var iv = CryptoJS.enc.Utf8.parse('BBBBBBBBBBBBBBBB');
var encrypted = 'ShJ+eeRSprU2s/ekM2ht4k9EBuvuUn1usyCH';
CryptoJS.AES.decrypt(encrypted, key, {
mode: CryptoJS.mode.CFB,
iv: iv,
}).toString(CryptoJS.enc.Utf8);
// '{"name": "zoro", "age": 18}'
5 OFB MODE
https://www.pycryptodome.org/en/latest/src/cipher/classic.html#ofb-mode-1
- Output FeedBack, 输出反馈模式
- 数据加密前无需pad
- 数据加密时,可指定IV,默认会自动生成随机IV
- 解密时需要KEY + IV
import json
import base64
from Cryptodome.Cipher import AES
from Cryptodome.Util import Padding
key = ('A' * 16).encode()
iv = ('B' * 16).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
mode = AES.MODE_OFB
cipher = AES.new(key, mode, iv=iv)
cy_bytes = cipher.encrypt(data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('IV:', iv.decode())
print('OFB base64:', ct)
cipher = AES.new(key, mode, iv=iv)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(dec_message.decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAA
IV: BBBBBBBBBBBBBBBB
OFB base64: SsFUDz81sqBec/aIH0O9VeN6PUzGnzBnkTjX
Parsed: {'name': 'zoro', 'age': 18}
'''
二 现代化模式
1 CCM MODE
https://www.pycryptodome.org/en/latest/src/cipher/modern.html#ccm-mode-1
- Counter with CBC-MAC,
- 数据加密前无需pad
- 数据加密时,可指定nonce,默认会自动生成随机nonce, nonce长度任意,推荐12
- 解密时需要 key + nonce
import json
import base64
from Cryptodome.Cipher import AES
key = ('A' * 16).encode()
nonce = ('B' * 12).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
mode = AES.MODE_CCM
cipher = AES.new(key, mode, nonce=nonce)
cy_bytes = cipher.encrypt(data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('nonce:', nonce.decode())
print('CCM base64:', ct)
cipher = AES.new(key, mode, nonce=nonce)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(dec_message.decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAA
nonce: BBBBBBBBBBBB
CCM base64: QLkTB74YqRW4cjEqYKq1oUnyUnOR65LNqc4r
Parsed: {'name': 'zoro', 'age': 18}
'''
2 EAX MODE
https://www.pycryptodome.org/en/latest/src/cipher/modern.html#eax-mode-1
- key + nonce
import json
import base64
from Cryptodome.Cipher import AES
key = ('A' * 16).encode()
nonce = ('B' * 12).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
mode = AES.MODE_EAX
cipher = AES.new(key, mode, nonce=nonce)
cy_bytes = cipher.encrypt(data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('nonce:', nonce.decode())
print('EAX base64:', ct)
cipher = AES.new(key, mode, nonce=nonce)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(dec_message.decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAA
nonce: BBBBBBBBBBBB
EAX base64: 6exKCce+QEjnZZ4uD1GJoRv24cKNSZX/Bd/c
Parsed: {'name': 'zoro', 'age': 18}
'''
3 GCM MODE
https://www.pycryptodome.org/en/latest/src/cipher/modern.html?highlight=GCM#ccm-mode-1
- Galois/Counter Mode, 伽罗瓦/计数器模式
- key + nonce
import json
import base64
from Cryptodome.Cipher import AES
key = ('A' * 16).encode()
nonce = ('B' * 16).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
mode = AES.MODE_GCM
cipher = AES.new(key, mode, nonce=nonce)
cy_bytes = cipher.encrypt(data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('nonce:', nonce.decode())
print('GCM base64:', ct)
cipher = AES.new(key, mode, nonce=nonce)
dec_message = cipher.decrypt(base64.b64decode(ct))
pt = json.loads(dec_message.decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAA
nonce: BBBBBBBBBBBBBBBB
GCM base64: 6L6INq3hBjFmuazE/AxDygdKoCf0/Xs1MCr6
Parsed: {'name': 'zoro', 'age': 18}
'''
4 SIV MODE
https://www.pycryptodome.org/en/latest/src/cipher/modern.html#siv-mode-1
- Synthetic Initialization Vector
- key + nonce + tag
- key: 32 bytes
import json
import base64
from Cryptodome.Cipher import AES
key = ('A' * 16 * 2).encode()
nonce = ('B' * 16).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
mode = AES.MODE_SIV
cipher = AES.new(key, mode, nonce=nonce)
cy_bytes, tag = cipher.encrypt_and_digest(data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('nonce:', nonce.decode())
print('tag:', tag)
print('SIV base64:', ct)
cipher = AES.new(key, mode, nonce=nonce)
dec_message = cipher.decrypt_and_verify(base64.b64decode(ct), tag)
pt = json.loads(dec_message.decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
nonce: BBBBBBBBBBBBBBBB
tag: b'\xd8>\xd6\x81*\xce\xa0k\x19\x05\xf7\x82\r{\xa1\x11'
SIV base64: jkdH0qf9Fy5KlymtehsLHGiQmX4rettnk16v
Parsed: {'name': 'zoro', 'age': 18}
'''
5 OCB MODE
https://www.pycryptodome.org/en/latest/src/cipher/modern.html#ocb-mode-1
- Offset CodeBook
- key + nonce + tag
- tag: <=15 bytes
import json
import base64
from Cryptodome.Cipher import AES
key = ('A' * 16).encode()
nonce = ('B' * 15).encode()
data = json.dumps(dict(name='zoro', age=18)).encode()
mode = AES.MODE_OCB
cipher = AES.new(key, mode, nonce=nonce)
cy_bytes, tag = cipher.encrypt_and_digest(data)
ct = base64.b64encode(cy_bytes).decode()
print('KEY:', key.decode())
print('nonce:', nonce.decode())
print('tag:', tag)
print('OCB base64:', ct)
cipher = AES.new(key, mode, nonce=nonce)
dec_message = cipher.decrypt_and_verify(base64.b64decode(ct), tag)
pt = json.loads(dec_message.decode())
print('Parsed:', pt)
'''输出
KEY: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
nonce: BBBBBBBBBBBBBBB
tag: b'\x13\x00g\xcc\xbb\x8aX{\xed\xe8\xf8\xd4h\xfa\x0f\xa1'
OCB base64: znbNBeF5YVgihsx1S+IRP4d4cZiJK1PunOcf
Parsed: {'name': 'zoro', 'age': 18}
'''
总结
模式 | 加密 | 解密 |
---|---|---|
ECB | KEY + PAD | UNPAD + KEY |
CBC | KEY + PAD [ + IV ] | UNPAD + KEY + IV |
CTR | KEY [ + nonce ] | KEY + nonce |
CFB | KEY [ + IV ] | KEY + IV |
OFB | KEY [ + IV ] | KEY + IV |
CCM | KEY [ + nonce ] | KEY + nonce |
EAX | KEY [ + nonce ] | KEY + nonce |
GCM | KEY [ + nonce ] | KEY + nonce |
SIV | KEY [ + nonce ] | KEY + nonce + tag |
OCB | KEY [ + nonce ] | KEY + nonce + tag |