python维吉尼亚算法

庆的密码学课程实验罢了:(

要求:

(1)对于维吉尼亚密码,练习使用密钥对明文进行加密生成密文;

     明文:ATTACKATDAWN,密钥:LEMONLEMONLE,密文:LXFOPVEFRNHR。

(2)对于维吉尼亚密码,练习使用密钥对密文进行解密得到明文的操作;

例1:课后习题,对于下面是一段经过维吉尼亚加密的密文,请你找出它的密钥并解密出原文。

密文如下:

CHREEVOAHMAERATBIAXXWTNXBEEOPHBSBQMQEQERBWRVXUOAKXAOSXXWEAHBWGJMMQMNKGRFVGXWTRZXWIAKLXFPSKAUTEMNDCMGTSXMXBTUIADNGMGPSRELXNJELXVRVPRTULHDNQWTWDTYGBPHXTFALJHASVBFXNGLLCHRZBWELEKMSJIKNBHWRJGNMGJSGLXFEYPHAGNRBIEQJTAMRVLCRREMNDGLXRRIMGNSNRWCHRQHAEYEVTAQEBBIPEEWEVKAKOEWADREMXMTBHHCHRTKDNVRZCHRCLQOHPWQAIIWXNRMGWOIIFKEE

例2:选做:课本例题3.12(P59页)

    思路:测试重复出现的二个字母的组合:TN,PM,QF,XG,PJ,UCW,JA,JH,WV,

密文如下:

BZGTNPMMCGZFPUWJCUIGRWXPFNLHZCKOAPGLKYJNRAQFIUYRAVGNPANU

MDQOAHMWTGJDXGOMPJPTKAAVZIUIWKVTUCWBWNFWDFUMPJWPMQGPTN

WXTSDPLPMWJAXUHHXWPFXXGVAPFNTXVFKOYIRBOQJHCBVWVFYCGQFGU

SUBDWVIYATJGTBNDKGHCTMTWIUEFJITVUGJHHIMUVJICUWYQWYGGUWPU

UCWIFGWUANILKPHDKOSPJTTWJQOJHXLBJAPZHVQWPDYPGLLGDBCHTGIZCC

MEGVIIJLIFFBHSMEGUJHRXBOG)UBDNASPEUCWNGWSNWXTSDPLPMWJAIUHU

MWPSYCTUWFBMIAMKVBNTDMMIQNBVDKILQSSDYVWVXIGDQFIBHSLEAVDBXG

 OLGDBCHTGIZVNFQFKTNGRWX UDCTGKWCOXIXKZPPFDZG

(3)练习针对维吉尼亚密码,使用Kasiski测试确定密钥长度;

(4)Kasiski和重合指数法来确定秘钥长度;

(5)练习使用拟重合指数法确定密钥内容。

实现:

str1 = "CHREEVOAHMAERATBIAXXWTNXBEEOPHBSBQMQEQERBWRVXUOAKXAOSXXWEAHBWGJMMQMNKGRFVGXWTRZXWIAKLXFPSKAUTEMNDCMGTSXMXBTUIA" \
       "DNGMGPSRELXNJELXVRVPRTULHDNQWTWDTYGBPHXTFALJHASVBFXNGLLCHRZBWELEKMSJIKNBHWRJGNMGJSGLXFEYPHAGNRBIEQJTAMRVLCRREM" \
       "NDGLXRRIMGNSNRWCHRQHAEYEVTAQEBBIPEEWEVKAKOEWADREMXMTBHHCHRTKDNVRZCHRCLQOHPWQAIIWXNRMGWOIIFKEE"  # 实验的例一
str2 = "BZGTNPMMCGZFPUWJCUIGRWXPFNLHZCKOAPGLKYJNRAQFIUYRAVGNPANUMDQOAHMWTGJDXGOMPJPTKAAVZIUIWKVTUCWBWNFWDFUMPJWPMQGPTN" \
       "WXTSDPLPMWJAXUHHXWPFXXGVAPFNTXVFKOYIRBOQJHCBVWVFYCGQFGUSUBDWVIYATJGTBNDKGHCTMTWIUEFJITVUGJHHIMUVJICUWYQWYGGUWP" \
       "UUCWIFGWUANILKPHDKOSPJTTWJQOJHXLBJAPZHVQWPDYPGLLGDBCHTGIZCCMEGVIIJLIFFBHSMEGUJHRXBOGUBDNASPEUCWNGWSNWXTSDPLPMW" \
       "JAIUHUMWPSYCTUWFBMIAMKVBNTDMMIQNBVDKILQSSDYVWVXIGDQFIBHSLEAVDBXGOLGDBCHTGIZVNFQFKTNGRWXUDCTGKWCOXIXKZPPFDZG"  # 实验的例二
dict_char = {}  # 以char为key,num为value
for i in range(65, 91):
    dict_char[chr(i)] = i - 65
dict_num = {v: k for k, v in dict_char.items()}  # 调转dict_char的键与值


def virginia(k, p='', c=''):
    """
    维吉尼亚加解密函数,密钥必须传入,传入明文则通过密钥解码(优先),传入密文则通过密钥加密,需要手动写出具体参数
    :param k: 密钥 str
    :param p: 明文 str
    :param c: 密文 str
    :return: 解码结果 str
    """
    dict_char = {}  # 以char为key,num为value
    for i in range(65, 91):
        dict_char[chr(i)] = i - 65
    dict_num = {v: k for k, v in dict_char.items()}  # 调转dict_char的键与值
    # 原理:(明文+密钥)%26=密文 <-> 明文=(密文-密钥)%26
    if c == '':
        for i in range(len(p)):
            c += dict_num[(dict_char[p[i]] + dict_char[k[i % len(k)]]) % 26]
        return c
    elif p == '':
        for i in range(len(c)):
            p += dict_num[(dict_char[c[i]] - dict_char[k[i % len(k)]]) % 26]
        return p


def kasisiki(c):
    """
    kasisiki测试法,统计的出现的次数最高的双字符串与它们的出现位置,需要靠你自己来判断gcd
    :param c:密文, 二维str列表 [str1,str2,...]
    :return: 二维列表,str&int [[x1,x2...],[y1,y2...],...[str_x,str_y...]]
    """
    list_returning = []  # 返回值
    list_num = []  # 统计每个双字符串出现的频率,简单来说,有许多如CH,TH的字符串它们出现的次数可能是4次,5次,我们只关心出现次数最高的
    for i in range(len(c) - 1):
        list_num.append(c.count(c[i] + c[i + 1]))

    list_char = []  # 假如出现次数最高是5次,那么这里将存放所有出现了5次的双字符
    for i in range(len(c) - 1):
        if c.count(c[i] + c[i + 1]) == max(list_num) and c[i] + c[i + 1] not in list_char:
            list_char.append(c[i] + c[i + 1])

    for char in list_char:
        list_index = []
        for i in range(len(c) - 1):
            if c[i] + c[i + 1] == char:  # 检测那些双字符出现的位置
                list_index.append(i)
        list_returning.append(list_index)
    return list_returning + [list_char]  # 拼接两个列表后返回


def comprehensive_index_method(c):
    """
    使用重合指数法求密钥长度
    重合指数法:在密文c按照某个长度切割后,对于每一组切割后的密文来说,在其中随机选取两个字符,其相同的可能性接近0.065(每一组都接近0.065)
    :param c: 密文 str
    :return: 密钥长度 int
    """
    list_match = []
    for C_len in range(1, len(c)):  # 密钥长度最长为密文长度,所以for到len(c)即可
        list_str1 = [''] * C_len
        for i in range(len(c)):  # 按列切割密文
            list_str1[i % C_len] += c[i]
        list_ans = []
        for x in list_str1:
            fenmu = len(x) * (len(x) - 1) / 2  # 分母为Cn2,代表所有的可能性
            fenzi = 0  # 分子就是在分母的那些情况中,在选取出来的两个字符中有多少是相同的
            for i in range(len(x)):
                for j in range(i + 1, len(x)):  # 双重循环计算所有选取的可能性,i+1是为了去重,避免选择的范围达到n^2的情况
                    if x[i] == x[j]:  # 两个字符相同
                        fenzi += 1
            try:  # 这个try解决一个小bug
                list_ans.append("%.4f" % (fenzi / fenmu))  # 取4位小数
            except ZeroDivisionError:  # 当密钥长度超过密文长度的一半是就会出现空列,这时我们直接跳过那一列就行了
                pass
            # print(f"密钥长度为{C_len}的时候重合指数列表为", list_ans)
        D_value = 0  # 这个变量与下面的for用来计算在列表中与0.065有着最小差值的数
        for i in list_ans:
            D_value += abs(float(i) - 0.065)
        # print("与0.065的差值的平均值为:", D_value / len(list_ans))
        list_match.append(D_value / len(list_ans))
    return list_match.index(min(list_match)) + 1  # 因为是索引值,所以+1


def rate_method(c):
    """
    使用频率法(基于单表代换)猜测密钥内容,结果需要你自己判断(感觉没jb用就是了,因为这个方法需要极其大量的密文进行判断,这里一般没用)
    :param c: 密文
    :return: 各个字母出现的频率 dict {"A":float_A,"B":float_B,...,"Z":float_Z}
    """
    # print("\n使用频率法(基于单表代换)猜测密钥内容,在密文中:")
    dict_char = {}  # 以char为key,num为value
    for i in range(65, 91):
        dict_char[chr(i)] = i - 65
    dict_num = {v: k for k, v in dict_char.items()}  # 调转dict_char的键与值
    list_temp = []
    for i in range(26):
        # print(dict_num[i], "出现的次数为:", str1.count(dict_num[i]), "\t频率为:", str1.count(dict_num[i]) / len(str1))
        list_temp.append(c.count(dict_num[i]) / len(c))
    # print("似乎与英语中字母的出现的频率没有显然的相似性,所以这个方法行不通\n")
    for i in dict_num.keys():
        dict_char[dict_num[i]] = list_temp[i]
    return dict_char


def coincidence_mutual_index_method(c, c_len):
    """
    使用重合互指数的方法来求密钥内容
    :param c_len: 密钥长度
    :param c: 密文 str
    :return: 密文内容 str
    """
    # 下面的三个字典都是常数
    dict_char = {}  # 以char为key,num为value
    for i in range(65, 91):
        dict_char[chr(i)] = i - 65
    dict_num = {v: k for k, v in dict_char.items()}  # 调转dict_char的键与值
    dict_rate_org = {'E': 0.12702, 'T': 0.09056, 'A': 0.08167, 'O': 0.07507, 'I': 0.06966, 'N': 0.06749, 'S': 0.06327,
                     'H': 0.06094, 'R': 0.05987, 'D': 0.04253, 'L': 0.04025, 'C': 0.02782, 'U': 0.02758, 'M': 0.02406,
                     'W': 0.02360, 'F': 0.02228, 'G': 0.02015, 'Y': 0.01974, 'P': 0.01929, 'B': 0.01492, 'V': 0.00978,
                     'K': 0.00772, 'J': 0.00153, 'X': 0.0015, 'Q': 0.00095, 'Z': 0.00074}  # 根据统计学中得到现实中的英文字母的频率:

    list_str1 = [''] * c_len  # 按密钥长度将密文按列切割
    for i in range(len(c)):
        list_str1[i % c_len] += c[i]
    C = ''  # 密钥
    # print("每一位密钥的重合指数:")
    for char in range(len(list_str1)):  # char是每一列的密文,所以每次for求出一位密钥
        list_rate = []
        for num in dict_num.keys():
            # new_char是偏移1,2,3...位后的这列密文,如ABD偏移一位变成BCE
            new_char = ''
            for j in list_str1[char]:
                # (明文+密钥)%26=密文 -> 明文=(密文-密钥)%26
                new_char += dict_num[(dict_char[j] - num) % 26]
            rate_i = 0
            for i in dict_char.keys():
                # 重要概念!重合指数R = ∑ pi * qi (‘a’ <= i <= ‘z’) p代表现实中自然语言的概率,q表示在位移后的密文中出现的几率
                new_rate = new_char.count(i) / len(new_char)  # q
                org_rate = dict_rate_org[i]  # p
                rate_i += org_rate * new_rate  # 这里就代表p * q
            list_rate.append("%.4f" % rate_i)  # 统计字母在该列密文中的出现频率
        # print(f"第{char}位密钥的重合指数序列:{list_rate}")
        list_match = []
        for i in list_rate:  # 这个for与for后的min用来计算列表中与0.065有着最小的差值的元素,其索引+1就是密钥在dict_num中的key
            list_match.append(abs(float(i) - 0.065))
        C += dict_num[list_match.index(min(list_match))]  # 最外层的for没次只能求出一位密钥,所以要进行拼接
        # print(f"上面列表中与0.065的最小差值为:{'%.5f' % min(list_match)},\t是第{list_match.index(min(list_match)) + 1}位,\t是字母:{dict_num[list_match.index(min(list_match))]}")
    return C


if __name__ == "__main__":
    print("对ATTACKATDAWN加密结果为:", virginia("LEMON", p="ATTACKATDAWN"))  # 加密
    print("对LXFOPVEFRNHR解密结果为:", virginia("LEMON", c="LXFOPVEFRNHR"))  # 解码
    print("kasisiki测试得到的结果:", kasisiki(str1))
    key_len = comprehensive_index_method(str1)  # 密钥长度
    print("使用重合指数法求密钥长度为:", key_len)
    print(
        f"使用频率法(基于单表代换)猜测密钥内容(这个方法需要极其大量的密文进行判断,这里一般没用),在密文中各个字母出现的频率为:{rate_method(str1)}")
    key = coincidence_mutual_index_method(str1, key_len)  # 密钥
    print("使用重合互指数的方法来求密钥内容,结果为:", key)
    print("使用密钥解密密文的结果:", virginia(key, c=str1))

运行效果图:

有任何问题可向作者提出,庆的同学除外。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值