信安读书笔记03-经典密码学_单表多表代换_Playfair_Vigenere代码

1,087次阅读
没有评论

前言时刻:

总结一波经典密码学中的几个经典加密算法。这里面有一个小插曲,好玩的是,我之前做项目时需要对一个链接进行加密和解密,我首先想到的是用 base64 进行加密,因为这个算法很成熟,但是这种算法是防君子不能防小人,网上随便搜 base64 解密,会出现一堆解密网站。所以我就想着对使用 base64 加密后的数据的最后10位进行替换,如果是字母就往后偏移4位,是数字默认跳过。效果很好,也解决了问题,不过最终还是采用 AES-128加密算法。

当我看到经典密码学中的代换密码中的凯撒密码时,我傻了,这不就是一样的思路吗😂。突然间发现自己的思想落后了一个世纪呢,哈哈。

经典密码学一般采用代换技术和置换技术对数据混淆加密,虽然看起来简单,但是这个思想很重要,以至于后来的标准加密算法 DES 和 AES 里面都有使用代换和置换技术。

1、代换密码

代换密码包括:单表代换密码、多表代换密码、一次一密等

  1. 凯撒密码,核心思想是对每个字母用其向后偏移 k 位的字母替换。解密时将字母向前偏移三位。

    缺点:容易被破解,统计经常使用的单词字母,然后与密文进行对比,就可轻易的得到加密规则。

    # 加密:C = (p+k) mod 26
    # 解密:P = (C-k) mod 26
    
    # 1、凯撒密码
    
    # 设k为3
    k = 3
    
    plaintext =  "cipherxyz"
    ciphertext = " ".join([chr((ord(i)- ord('A') + k)%26 + ord('A')) for i in plaintext.upper()])
    print(f"明文:{plaintext}\n密文:{ciphertext}")
    
    # 明文:cipherxyz
    # 密文:F L S K H U A B C
    
  2. 单表代换密码,核心思想是明文中的每个字母都由一个特定的字母替换。相对于凯撒密码做了秘钥复杂度升级,每次加密时,第一个字母用26个字母中的一个随机字母替代,第二个用剩下的 25 中的随机一个……,这样秘钥复杂度就有 26! 。缺点:仅仅增强了秘钥复杂度,但是还是没有改进攻击者对密文进字母频率统计的问题,容易被破解。

  3. 多表代换密码,核心思想是,明文中的每一个字母都由多个可能的字母替换。这个厉害了,简直就是单表代换的升级版,避免了字母频率统计问题。著名的多代换密码算法有Playfair密码、Vibenere密码等

1.1 Playfair密码

这个思想很厉害也很巧妙,实现了一对多的可能性,可有效避免攻击者对字母频率进行分析破击秘钥。

生成秘钥规则

  1. 用户自行给出一个秘钥单词如:goodluck,

  2. 将其放入 5 X 5 的矩阵中,按照从左到右,从上到下的原则填充。

  3. 应去除被当做秘钥的单词中的重复字母 goodluck -> godluck ,不足25个,剩下的按照字母表的顺序,去除和秘钥中的字母重复的,按顺序依次填充。

    G O D L U
    C K A B E
    F H I/J M N
    P Q R S T
    V W X Y Z

生成密文规则:

  1. 用户给出明文如:balloon

  2. 将其按照2位一组划分,不足2位最后补一个字母(可自行设置)如:k,或者2位字母一样的中间插入一个字母设:k。例如:balloon -> ba lk lo on

  3. 落在矩阵中的同一行的两个字母,用其对中的字母右边的字母代换。若是最右边的字母则用该行最左边的字母代换。例如:ba变成EB,wy变成XZ。

  4. 落在矩阵中的同一列的两个字母,用其对中的字母下面的字母代换。若是最下面的字母则用该行最上面的字母代换。例如:cf变成FP,bs变成MY。

  5. 其他的字母按照,取两个字母所在行和列的各自对角线上的值。例如:cq变成kp,ks变成B

  6. 手写代码实现Playfair算法:

    很费时间,不过还是完成了。

初始化:

# 手写 哈哈 勇气
import numpy as np

# 1、生成秘钥
def set_key():
    """生成秘钥"""

    # 1.1 秘钥去重
    main_key_list = list(main_key.upper())
    main_key_set = list(set(main_key_list))  # 去重  
    main_key_set.sort(key=main_key_list.index)  # 按原秘钥的顺序排序

    # 1.2 整合作为秘钥的字母
    alphabet_list = list("ABCDEFGHIKLMNOPQRSTUVWXYZ")
    for i in main_key_set:
        alphabet_list.remove(i)

    key = main_key_set + alphabet_list      # 拼接秘钥

    # 1.3 生成5 X 5的矩阵
    key_matrix = np.array(key).reshape(5, 5)  # 变成5 X 5的矩阵
    print(key_matrix)
    return key_matrix

# 2、处理明文
def deal_plaintext(pad='m'):
    """对明文进行 分组 填充"""
    plain_list, i= [], 0             # i:用于切片明文
    pad_num = 0                      # 记录填充的个数,用于后面计算,是否剩余最后一位没分组
    while i<len(plaintext)-1:        # 用一下冒泡排序的思想,从左向右扫描
        if plaintext[i] == plaintext[i+1]:
            plain_list.append(plaintext[i]+pad) 
            pad_num += 1
            i+=1
        else:
            plain_list.append(plaintext[i:i+2])
            i+=2

    # 如果:剩余的最后一位时,也和填充pad组成一组
    if len(plain_list)*2 < pad_num + len(plaintext):
        plain_list.append(plaintext[-1] + pad)

    return plain_list

加密算法:

def encrypt(text_list, key):
    """加密函数"""

    encrypt_text = ''
    h, w = key.shape         # key矩阵的行数和宽数,其实就是5X5
    for each_t in text_list:
        m, n = tuple(each_t.upper())

        x1, y1 = np.where(key==m)   # numpy中查询元素索引序号,用于判断两元素行列
        x2, y2 = np.where(key==n)

        # 1. 同一行情况下
        if x1 == x2:
            encrypt_text += key[x1, min(y1, y2)][0]   # 先算最左边的,x右加1一定不会超界
            encrypt_text += key[x1, max(y1, y2)][0] if max(y1, y2) != w else key[x1, 0][0]

        # 2. 同一列情况下
        elif y1 == y2:
            encrypt_text += key[min(x1, x2), y1][0]   # 先算最上边的,y下加1一定不会超界
            encrypt_text += key[max(x1, x2), y1][0] if max(x1, x2) != h else key[0, y1][0]

        # 3. 对角线
        else:
            encrypt_text += key[x1, y2][0]
            encrypt_text += key[x2, y1][0]

    return encrypt_text

解密算法:

def decipher(text, key, pad='m'):
    """解密函数"""

    decipher_text, index = '', 0
    h, w = key.shape         # key矩阵的行数和宽数,其实就是5X5
    while index<len(text):

        m, n = tuple(text[index:index+2])

        x1, y1 = np.where(key==m)
        x2, y2 = np.where(key==n)

        # 1. 同一行情况下
        if x1 == x2:
            # 先算最右边的,x右加1一定不会超界  和加密是相对的
            decipher_text += key[x1, max(y1, y2)-1][0]
            decipher_text += key[x1, min(y1, y2)-1][0] if min(y1, y2) != 0 else key[x1, w][0]

        # 2. 同一列情况下
        elif y1 == y2:
            decipher_text += key[max(x1, x2)-1, y1][0]
            decipher_text += key[min(x1, x2)-1, y1][0] if min(x1, x2) != 0 else key[h, y1][0]

        # 3. 对角线
        else:
            decipher_text += key[x1, y2][0]
            decipher_text += key[x2, y1][0]

        index += 2

    # 2、处理解密后的明文中的含有填充的字母pad
    nums_list = []       # 存放是pad='m'的索引序号,等会删除
    decipher_t_list = list(decipher_text)
    for i in range(len(decipher_t_list)-2):
        if i%2==0 and decipher_t_list[i] == decipher_t_list[i+2] and decipher_t_list[i+1] == pad.upper():
            nums_list.append(i+1)

    # 判断最后一位,若等于pad则删除
    if decipher_t_list[-1] == pad.upper():
        nums_list.append(-1)

    # 注意:这里对要删除的索引进行倒序迭代。防止正序迭代删除索引,列表会进行自动向前补充移位,导致索引不对应。
    for i in nums_list[::-1]:      
        decipher_t_list.pop(i)

    return ''.join(decipher_t_list).lower()
# 初始化的明文和秘钥
plaintext =  "cipheerxyz"   # 明文
main_key =  'goodluck'     # 用户设置的秘钥
key = ''                   # 真正参与加密的的秘钥
pad = 'm'

key = set_key()
plaintext_group = deal_plaintext()
print(plaintext_group)              # ['ci', 'ph', 'em', 'er', 'xy', 'zm']

encrypt_text = encrypt(plaintext_group, key)
print(encrypt_text)                 # AFQFBNATXYYN

deciper_text = decipher(encrypt_text, key)
print(deciper_text)                  # cipheerxwz

Playfair总结:

解密函数我感觉写的有点问题,但是搞不懂。就是遇到如果明文中有两个重复字母中间夹了一个 pad 字母,那岂不是在解密函数中就被误删除了?等有时间在研究一下。

1.2 Vigenere密码

这个算法我不写代码了,有点费时间,书后面还有很多内容呢😂,不行写代码有助于理解算法,得写。

Vigenere密码比较简单,很容易理解。

生成秘钥规则

  1. 用户自行给出一个秘钥单词如:goodluck,

  2. 重复循环拼接,补齐26位,如:goodluckgoodluckgoodluckgo。

生成密文规则:

  1. 用户给出明文如:i like python
  2. 去除里面的空格如:ilikepython
  3. 加密规则,设26个字母中 a 代表0,依次递增1。从第一个明文字母开始 i ,对应第一个秘钥字母 g ,密文是就是 i + g mod 26=i + 7mod26=p
    1. 秘钥:goodluckgoodluckgoodluckgo
    2. 明文:ilikepython
    3. 密文:ozwnpjadncb
  4. 手写代码:
# Vigenere代换密码  

# 初始化的明文和秘钥
plaintext =  "i like python"   # 明文
main_key =  'goodluck'         # 用户设置的秘钥
key = ''                       # 真正参与加密的的秘钥

def set_key():

    global key
    key = (main_key*(int(26/len(main_key))+1))[0:26]
    print(f"key:{key}")
    return key

def encrypt(plaintext, key):

    encrypt_text, index = '', 0

    while index<len(plaintext):
        text_ord = ord(plaintext[index]) - ord('a')
        key_iv = ord(key[index]) - ord('a')      # 秘钥字母相对于 'a' 的偏移量
        encrypt_text = encrypt_text + chr(( text_ord+ key_iv)%26 + ord('a'))

        index+=1

    print(f"encrypt_text:{encrypt_text}")
    return encrypt_text

print(f"plaintext:{plaintext.replace(' ', '')}")
set_key()
encrypt(plaintext.replace(' ', ''), key)

"""
plaintext:ilikepython
key:goodluckgoodluckgoodluckgo
encrypt_text:ozwnpjadncb
"""

总结:

看似枯燥的理论加密算法,其实仔细读下来,也不难,如果动手写下加密算法,记得就更牢固了。

参考链接:

https://baike.baidu.com/item/playfair%E5%AF%86%E7%A0%81/8999814?fr=aladdin

https://blog.csdn.net/BabyFish13/article/details/79290434

1
西园公子
版权声明:本站原创文章,由西园公子2021-04-30发表,共计5661字。
转载提示:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
载入中...