适合这样的读者: 需要在 Python 中处理加密、编码、哈希操作的开发者。本文按用途分类整理------先分清"编码/哈希/加密"的区别,再给出各场景的实操代码。
前置知识: 了解字节与字符串的区别即可。
密码学操作可以按三个维度分类:
| 类别 | 是否可逆 | 是否需要密钥 | 常见用途 |
|---|---|---|---|
| 编码 | 可逆 | 否 | URL 传输、二进制文本化 |
| 哈希摘要 | 不可逆 | 否 | 密码存储、数据完整性校验 |
| 加密 | 可逆 | 是 | 数据保密传输 |
编码
不依赖密钥,仅改变数据表示形式,任何人都可以解码
URL 编码
将非 ASCII 字符或特殊字符转为 %XX 格式,使其能在 URL 中安全传输
py
from urllib.parse import quote, urlencode, unquote
# 1. 编码字符串
print(quote('米饭怎么吃')) # %E7%B1%B3%E9%A5%AD%E6%80%8E%E4%B9%88%E5%90%83
print(quote('米饭怎么吃', encoding="gbk")) # %C3%D7%B7%B9%D4%F5%C3%B4%B3%D4
# 2. 编码字典
dic = {"wq": "米饭怎么吃", "new_wq": "想怎么吃就怎么吃"}
print(urlencode(dic))
# 3. 解码
print(unquote("http://www.baidu.com/s?wd=%E5%A4%A7%E7%8E%8B"))
# http://www.baidu.com/s?wd=大王
Base64
将二进制数据转为 64 个可打印字符表示,常用于图片/附件传输
Base64 字符串长度是 4 的倍数,不足时需要填充
=。如果解码时遇到Incorrect padding错误,说明缺少填充字符。
py
import base64
# 编码:字节 → Base64 字符串
print(base64.b64encode("我要吃饭".encode("utf-8")).decode())
# 解码:Base64 字符串 → 字节
print(base64.b64decode("5oiR6KaB5ZCD6aWt").decode("utf-8"))
手动处理填充不足:
py
s = "ztKwrsTj0b0"
s += ("=" * (4 - len(s) % 4))
print("填充后", s)
bb = base64.b64decode(s).decode("gbk")
print(bb)
哈希摘要
将任意长度的数据映射为固定长度的哈希值,不可逆。同样的输入总是同样的输出。
哈希不可逆是重要特性:无法从哈希值反推出原文。常用于密码存储和文件完整性校验。
MD5
输出 128 位(32 位十六进制),速度快,但已不再安全,不应用于密码存储
py
from hashlib import md5
# 不加盐
obj = md5()
obj.update("明文".encode("utf-8"))
print(obj.hexdigest())
# 加盐(防止彩虹表攻击)
obj = md5("固定盐值".encode("utf-8"))
obj.update("明文".encode("utf-8"))
print(obj.hexdigest())
SHA-1 / SHA-256
输出更长,安全性更高。SHA-256 是目前推荐的哈希算法
py
from hashlib import sha1, sha256
sha = sha256()
sha.update('明文'.encode("utf-8"))
print(sha.hexdigest())
# 加盐
sha = sha256('固定盐值'.encode("utf-8"))
sha.update('明文'.encode("utf-8"))
print(sha.hexdigest())
HMAC(基于哈希的消息认证码)
用密钥对消息签名,同时验证消息完整性和来源身份。API 接口签名最常用的方式
HMAC = 哈希函数 + 密钥。和哈希的区别:哈希任何人都能计算,HMAC 需要密钥才能验证。这在数据采集场景中很常见------很多 API 要求对请求参数做 HMAC 签名。
py
import hmac
import hashlib
key = b"api_secret_key"
msg = b"param1=value1¶m2=value2"
# 生成签名
sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
print(sig) # 输出 64 位十六进制字符串
# 验证签名(服务端用相同 key 和 msg 重新计算,比对是否一致)
expected = hmac.new(key, msg, hashlib.sha256).hexdigest()
print(hmac.compare_digest(sig, expected)) # True
实际场景: 爬虫请求需要签名的 API 时,找到签名算法(通常是 HMAC-SHA256),用抓到的 key 对请求参数排序后计算签名,拼到请求头中发送。
对称加密
加密和解密使用同一个密钥。速度快,适合大量数据的加密传输。
关键参数:
- 密钥长度:越长越安全(AES-128 / AES-192 / AES-256)
- 加密模式 :
- ECB:每个分组独立加密,不推荐(相同明文产生相同密文)
- CBC:前一分组的密文参与下一分组加密,推荐
- CFB / OFB:将块密码转为流密码模式
对称加密的安全前提:密钥只能由通信双方知晓,不能通过网络明文传输。
AES
当前最广泛使用的对称加密标准
python
from Crypto.Cipher import AES
key = b"0123456789abcdef" # 16 字节 = AES-128
IV = b"0102030405060708" # 初始向量,CBC 模式必需
# 加密
aes = AES.new(key, mode=AES.MODE_CBC, IV=IV)
data = "明文"
data_bs = data.encode("utf-8")
# 数据长度必须是 16 的倍数,不足补位
pad_len = 16 - len(data_bs) % 16
data_bs += (pad_len * chr(pad_len)).encode("utf-8")
bs = aes.encrypt(data_bs)
print(bs)
# 解密
aes = AES.new(key, mode=AES.MODE_CBC, IV=IV)
result = aes.decrypt(bs)
print(result.decode("utf-8"))
DES
密钥仅 56 位(8 字节),已可被暴力破解。不建议在新项目中使用,了解即可
python
from Crypto.Cipher import DES
key = b"01234567" # 8 字节
IV = b"01020304"
# 加密
des = DES.new(key, mode=DES.MODE_CBC, IV=IV)
data = "我要吃饭".encode("utf-8")
pad_len = 8 - len(data) % 8
data += (pad_len * chr(pad_len)).encode("utf-8")
bs = des.encrypt(data)
print(bs)
# 解密
des = DES.new(key, mode=DES.MODE_CBC, IV=IV)
result = des.decrypt(bs)
print(result.decode("utf-8"))
非对称加密
使用公钥加密、私钥解密。公钥可公开,私钥必须保密。
对称加密的问题在于"密钥如何安全传输"------非对称加密解决了这个问题:发送方用接收方的公钥加密数据,只有接收方的私钥能解密。
RSA
最常用的非对称加密算法,安全性基于大整数分解的难度
py
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto import Random
import base64
gen_random = Random.new
# 1. 生成密钥对
rsakey = RSA.generate(1024)
with open("rsa_public.pem", mode="wb") as f:
f.write(rsakey.publickey().exportKey())
with open("rsa_private.pem", mode="wb") as f:
f.write(rsakey.exportKey())
# 2. 加密(使用公钥)
data = "需要加密的敏感数据"
with open("rsa_public.pem", mode="r") as f:
pk = f.read()
rsa_pk = RSA.importKey(pk)
rsa = PKCS1_v1_5.new(rsa_pk)
result = rsa.encrypt(data.encode("utf-8"))
b64_result = base64.b64encode(result).decode("utf-8")
print(b64_result)
# 3. 解密(使用私钥)
with open("rsa_private.pem", mode="r") as f:
prikey = f.read()
rsa_pk = RSA.importKey(prikey)
rsa = PKCS1_v1_5.new(rsa_pk)
result = rsa.decrypt(base64.b64decode(b64_result), gen_random)
print(result.decode("utf-8"))
如何选择
| 需求 | 推荐方案 |
|---|---|
| URL 传输非 ASCII 字符 | URL 编码 |
| 二进制数据文本化(如图片传输) | Base64 |
| 密码存储 | SHA-256 + 盐值(或 bcrypt / argon2) |
| 文件完整性校验 | SHA-256 |
| 大量数据传输加密 | AES-256-CBC |
| 密钥分发与数字签名 | RSA |
| 端到端加密(性能要求高) | 非对称加密交换密钥 → 对称加密传输数据 |
安全提示
- 不要自己造加密算法------使用经过验证的标准库
- 不要用 ECB 模式------相同明文产生相同密文,信息泄漏风险
- 哈希密码必须加盐------防止彩虹表攻击
- RSA 密钥长度不低于 2048 位------1024 位已被认为不安全