简介
Feistel 加密算法,或者叫做 Feistel 网络,是一种块加密(block cipher)模型,很多常见的加密算法都具有 Feistel 结构,如 DES、blowfish 等。
Feistel 将明文分割成固定大小(block size)的块(如 32bit、64bit),然后对于每个块进行若干轮操作,每轮操作需要用到一个 key,因此总计需要循环轮数个 key。解密时需要用相同的 keys,因此这是一种对称加密算法。
加密步骤
- 将明文转换为 8-bit 二进制形式
- 根据块大小(block size)分割成若干个数据块,不足整数块时需要加 padding
- 生成轮数(rounds)个 key 的 keys 数组
- 对于每个数据块进行若干轮操作,具体如下:
- 在每轮操作中都将数据块分成相等的左右两部分(L1, R1)
- 将右半部分(R1)与keys[n]进行加密函数f运算,记为f1
- 然后将f1与左半部分进行异或(XOR)操作的结果作为下一轮输入的左半部分(L2)
- 将本轮左半部分(L1)作为下一轮输入的右半部分(R2)
- 下一轮的输入为L2 + R2, 再进行下一轮操作
- 在最后一轮结束后将左半部分(Ln)和右半部分(Rn)对调(这样可以使用同一个循环进行加密和解密)
解密步骤
- 将密文使用相同的 keys 进行与加密过程相同的循环,不过 key 的顺序与加密时相反(加密时使用 key[1], key[2], ..., key[n], 解密时使用 key[n], key[n-1], ..., key[1])
- 组合数据块并按照 8bit 进行分割
- 根据 ASCII 码转换为明文
Python 实现
首先定义一些变量
plain_text = 'Hello world!'
# 为了便于取整,选择8bit
block_size = 8
rounds = 16
keys = []
生成随机的 keys
def rand_key(n):
res = ''
for i in range(n):
bit = random.randint(0, 1)
res += str(bit)
return res
# length 是key的长度,可以根据需要调整
def generate_keys(length, rounds):
for i in range (rounds):
keys.append(rand_key(length))
return res
定义每一轮循环的操作
def exor(a, b):
n = len(a)
res = ''
for i in range(n):
if a[i] == b[i]: res += '0'
else: res += '1'
return res
def round(str, key):
n = len(str) // 2
L1 = str[0:n]
R1 = str[n:]
# 这里的f函数选择exor操作
# 可以替换为其他的加密函数
f1 = exor(R1, key)
R2 = exor(f1, L1)
L2 = R1
return L2 + R2
加密过程
def feistel_encode(s, rounds):
res = ''
# 将密文转为8bit二进制表示
str_ascii = [ord(x) for x in s]
str_bin = [format(x, '08b') for x in str_ascii]
str_bin = ''.join(str_bin)
# 选择block size的一半最为key length是因为将f函数定义为xor操作
# 保证长度和block size的一半相等,在执行时就不需要补padding
# 实际的key length会更长,一般不会少于128bit避免被快速爆破
key_length = block_size // 2
generate_keys(key_length, rounds)
res_bin = ''
for n in range(0, len(str_bin), block_size):
temp = str_bin[n:n + block_size]
for i in range(rounds):
temp = round(temp, keys[i])
# 最后一轮循环后交换左右部分
temp = temp[key_length::] + temp[0:key_length]
res_bin += temp
# 将密文转为ASCII字符表示
for i in range(0, len(res_bin), 8):
bin_data = res_bin[i: i + 8]
decimal = int(bin_data, 2)
res += chr(decimal)
return res
解密过程
def feistel_decode(s):
res = ''
str_ascii = [ord(x) for x in s]
str_bin = [format(x, '08b') for x in str_ascii]
str_bin = ''.join(str_bin)
res_bin = ''
for n in range(0, len(str_bin), block_size):
temp = str_bin[n:n + block_size]
for key in reversed(keys): # 确保使用反向密钥
temp = round(temp, key)
key_length = len(keys[0])
# 最后一轮循环后交换左右部分
temp = temp[key_length::] + temp[0:key_length]
res_bin += temp
# 转为ASCII字符
for i in range(0, len(res_bin), 8):
bin_data = res_bin[i: i + 8]
decimal = int(bin_data, 2)
res += chr(decimal)
return res
最后测试一下
def main():
print('plain_text: ', plain_text)
# plain_text: Hello world!
cipher_text = feistel_encode(plain_text, rounds)
print('cipher_text: ', cipher_text)
# cipher_text: yààÓlKÓàh}
decode_text = feistel_decode(cipher_text)
print('decode_text: ', decode_text)
# decode_text: Hello world!
if __name__ == "__main__":
main()
总结
- 本文中为了方便演示将block size和key size设置较小,实际一般会更长,如果不满整个block则需要补padding(用0填充)
- 加密和解密使用相同的循环,但是注意不要忘记需要在加/解密的最后一轮循环后交换左、右部分
- 动手实现时可以将round设为1或2,单步观察每一轮循环的结果
- 时间复杂度 O(n)
- 额外空间 O(n)
作者:justMonika
链接:https://juejin.cn/post/7395069382433456147