这是百度百科的定义:
PBE算法在加密过程中并不是直接使用口令来加密,而是加密的密钥由口令生成,这个功能由PBE算法中的KDF函数完成。KDF函数的实现过程为:将用户输入的口令首先通过“盐”(salt)的扰乱产生准密钥,再将准密钥经过散列函数多次迭代后生成最终加密密钥,密钥生成后,PBE算法再选用对称加密算法(如AES)对数据进行加密。
salt的作用是预防字典攻击。试想,如果没有salt,攻击者可以事先构造一个口令到key的字典,然后遍历字典里的候选key,这样就能大幅缩短尝试时间。但如果加了salt,准备候选key就变得非常困难,因为攻击者事先并不知道salt是啥,那他要准备的候选key的数量就会变得非常巨大。所以,salt应该要存放在一个安全的地方。
将准密钥经过散列函数多次迭代后生成最终加密密钥,这个过程称之为stretching。多次迭代的目的是为了加大攻击者破解的成本,对用户来说,准密钥就那一个,多次执行散列算法开销并不大。但对于攻击者而言,他是要用大量的候选key进行尝试的,再叠加多次散列算法执行,那就是一个很大的负担。当然,最终加密密钥也要放到一个安全的地方。
一个用python3写的PBE算法的例子:
from Crypto.Cipher import AES
from Crypto.Random.random import StrongRandom
from Crypto.Util.number import long_to_bytes
from Crypto.Hash import SHA256
def get_secure_random(byte_num) -> bytes:
sr = StrongRandom()
v = long_to_bytes(sr.getrandbits(byte_num * 8))
l = len(v)
if l == byte_num:
return v
assert (l < byte_num)
res = v + b'0' * (byte_num - l)
return res
# password based encryption
class PBE(object):
"""docstring for PBE"""
_SALT_LEN = 32
def __init__(self, password: str):
super(PBE, self).__init__()
self.password = password
self.store_file = 'secret.bin'
def encrypt(self, msg: str) -> bytes:
salt = get_secure_random(PBE._SALT_LEN)
kek = self.gen_kek(salt, self.password)
msg_key = get_secure_random(32)
aes4key = AESHelper(kek)
enc_msg_key = aes4key.encrypt(msg_key)
# we should store it in binary format, not text format
with open(self.store_file, 'wb') as fp:
fp.write(salt)
fp.write(enc_msg_key)
aes4msg = AESHelper(msg_key)
return aes4msg.encrypt(msg)
def decrypt(self, cipher_msg: bytes) -> bytes:
# we should read it in binary format, not text format
with open(self.store_file, 'rb') as fp:
bs = fp.read()
salt = bs[0:PBE._SALT_LEN]
kek = self.gen_kek(salt, self.password)
enc_msg_key = bs[PBE._SALT_LEN:]
aes4key = AESHelper(kek)
msg_key = aes4key.decrypt(enc_msg_key)
aes4msg = AESHelper(msg_key)
return aes4msg.decrypt(cipher_msg)
def decrypt2str(self, cipher_msg: bytes) -> str:
return byte2str(self.decrypt(cipher_msg))
@staticmethod
def gen_kek(salt, password):
# salt is used to prevent dictionary-attack
# we can call SHA multi times to stretch
h = SHA256.new()
h.update(salt + str2byte(password))
kek = h.digest() # get byte digest
assert (len(kek) == 32) # SHA256 gen 32bytes hash
return kek
上述代码里的salt + str2byte(password)就是准密钥,经过SHA256安全散列算法生成kek,这里我们为简单起见,只做了一次散列,实际是可以做多次的。msg_key就是最终密钥,而kek(key encrypted key)是用来加密最终密钥msg_key的密钥。kek我们不用存,因为它可以通过salt+口令计算生成,但msg_key我们要加密后存储,这里我们将salt和msg_key放到了一个单独的secret.bin文件里,这个secret.bin文件也要保护起来(通过权限或口令)。