用python实现维吉尼亚密码的加密与唯密文解密

加密过程

加密原理

字母表中每一个字母都对应着一个数字,从A~ Z依次是0~25,给定一个明文与密文,假设明文是showmaker,密钥是bde,则密钥三个字母对应的三个数字分别是1,3,4,而密钥的长度为3,我们以每组3个字符的长度将明文分为 sho wma ker三组,其中每组的第一个字母s w k需要加上1,也就是向后取一个字母,得到t x l,而每组的第二个字母需要+3,每组的第三个字母+4,其中,若加密得到的字母超出z(即25),则从a开始重新计数,也就是取26的模,明文全部加密按顺序得到的字符串即为密文。

明文:showmaker

密钥:bde

密文:tksxpelhv

代码加密的思路

维吉尼亚密码的加密过程较为简单,只需要通过循环将明文字母的Unicode码加上相应的数字即可实现,要注意的是输入的明文中可能存在大小写与标点符号,需要将标点符号剔除,并将其统一转化为大写形式或者小写形式。

所以主要有以下步骤:

1、获取密钥长度与密钥中字母所对应的0~25数字。

2、剔除明文中的标点符号并统一大小写

3、通过循环将所有明文字母的Unicode码加上相应的数字(mod 26)

代码预览

str = input("请输入密钥:")
length = len(str)
str.lower()               #将输入字母全部转换为小写
list_cipher = []                #创建空列表,用于存放密钥对应的数字

#下面将将密钥对应的数字输入到列表list_cipher中
for i in str:
    no_cipher = ord(i) - ord('a')
    list_cipher.append(no_cipher)

plaintext_path = input("请输入明文所在txt文件的路径:")

#下面获取明文
f_1 = open(plaintext_path,'r')
plaintext_0 = f_1.read()
f_1.close()

plaintext = ''
#下面将一个个字符的Unicode码进行比对,来达到去除标点符号的目的
for i in plaintext_0:
    if (ord('a') <= ord(i) and ord('z') >= ord(i)) or (ord('A') <= ord(i) and ord('Z') >= ord(i)):
        plaintext += i
plaintext = plaintext.lower()

count = 0
cipher = ''
#下面是加密程序
while count <= (len(plaintext)-1):
    for i in range(length):
        if count + i <= (len(plaintext)-1):
            cipher_word = chr((ord(plaintext[count+i]) + list_cipher[i] - ord('a')) % 26 + ord('a'))
            cipher += cipher_word
    count += length

f_2 = open('C:\\Users\\辉夜大小姐\\PycharmProjects\\pythonProject\\cipher.txt','w')
for i in range(len(cipher)):
    f_2.write(cipher[i])
f_2.close()

代码解析与运行效果

str = input("请输入密钥:")
length = len(str)
str.lower()               #将输入字母全部转换为小写
list_cipher = []                #创建空列表,用于存放密钥对应的数字

#下面将将密钥对应的数字输入到列表list_cipher中
for i in str:
    no_cipher = ord(i) - ord('a')
    list_cipher.append(no_cipher)

先输入密钥,假设我输入的密钥是crypto。

length的值即为密钥长度,将密钥转化为小写,每个字母与字母a的Unicode码的差即为其所对应的数字,依次保存在列表list_cipher中。

plaintext_path = input("请输入明文所在txt文件的路径:")

#下面获取明文
f_1 = open(plaintext_path,'r')
plaintext_0 = f_1.read()
f_1.close()

plaintext = ''
#下面将一个个字符的Unicode码进行比对,来达到去除标点符号的目的
for i in plaintext_0:
    if (ord('a') <= ord(i) and ord('z') >= ord(i)) or (ord('A') <= ord(i) and ord('Z') >= ord(i)):
        plaintext += i
plaintext = plaintext.lower()

将明文写在一个txt文件中,如下图。

明文

输入该txt文件所在的路径,而后将内容读到plaintext_0中,将plaintext_0中的所有字符遍历一遍,留下Unicode码在a~z之间或者A到Z之间的字符并保存在plaintext中,达到剔除标点符号的目的,再通过lower函数将其全部小写,方便后续的加密过程。

count = 0
cipher = ''
#下面是加密程序
while count <= (len(plaintext)-1):
    for i in range(length):       
        if count + i <= (len(plaintext)-1):
            cipher_word = chr((ord(plaintext[count+i]) + list_cipher[i] - ord('a')) % 26 + ord('a'))
            cipher += cipher_word
    count += length

f_2 = open('C:\\Users\\辉夜大小姐\\PycharmProjects\\pythonProject\\cipher.txt','w')
for i in range(len(cipher)):
    f_2.write(cipher[i])
f_2.close()

以length(密钥长度)进行for循环,通过Unicode码的转换将明文转化为密文,并写入到另一个txt文件中。我们运行一遍代码看看cipher.txt。
密文

可以看到txt中已经有了写入的密文,如果该txt文件中原本有内容,那也会将原内容进行覆盖。

唯密文解密过程

解密原理

我们先看一个表

字 母概 率字 母概 率
A0.082N0.067
B0.015O0.075
C0.028P0.019
D0.043Q0.001
E0.127R0.060
F0.022S0.063
G0.020T0.091
H0.061U0.028
I0.070V0.010
J0.002W0.023
K0.008X0.001
L0.040Y0.020
M0.024Z0.001

这是从众多小说、杂志和报纸上搜集统计出的26个字母出现的相对概率。

本次代码中我使用重合指数法来实现维吉尼亚密码的解密。

f 0 , f 1 , … f 25 f_0,f_1,…f_{25} f0,f1,f25来表示A,B,C,……Z出现的频数,则在长度为n的字母串中随机取两个字母,取到相同字母的概率为 I c ( x ) = ∑ i = 0 25 f i ( f i − 1 ) n ( n − 1 ) I_c(x)={{\sum_{i=0}^{25}f_i(f_i-1)}\over{n(n-1)}} Ic(x)=n(n1)i=025fi(fi1),表1.1中的数据可得出 I c ( x ) ≈ 0.065 I_c(x)≈0.065 Ic(x)0.065

假设长度为n的密文串为 Y = y 1 y 2 … y n Y=y_1y_2…y_n Y=y1y2yn。将其分割为m组长度相等的子串,具体如下。

Y 1 = y 1 y m + 1 y 2 m + 1 … Y_1=y_1y_{m+1}y_{2m+1}… Y1=y1ym+1y2m+1

Y 2 = y 2 y m + 2 y 2 m + 2 … Y_2=y_2y_{m+2}y_{2m+2}… Y2=y2ym+2y2m+2

┆ ┆ ┆

Y m = y m y 2 m y 3 m … Y_m=y_my_{2m}y_{3m}… Ym=ymy2my3m

如果m是密钥字的长度,那么每一个 I c ( Y i ) I_c(Y_i) Ic(Yi)的值会在0.065附近。

所以我们可以通过循环来从m = 1开始尝试,一直到选定的一个值(比如m = 10),找出其中 I c ( Y ) I_c(Y) Ic(Y)最接近于0.065时m的值,此时m的值最有可能是密钥的长度。

得到密钥长度后,定义公式 M g = ∑ i = 0 25 p i f i + g n ′ M_g=\sum_{i=0}^{25}{p_if_{i+g}\over{n'}} Mg=i=025npifi+g(n’为每个子串的长度)( 0 ≤ g ≤ 25 0≤g≤25 0g25,代表着26个字母)

g = k i g=k_i g=ki,即 g g g对应密钥当前位的正确字母,则应该有 M g ≈ 0.065 M_g≈0.065 Mg0.065

通过遍历0~25,得到 M g M_g Mg最接近0.065时的 g g g的值,重复该过程m次,而后将得到的数字全部转化为字母顺序排列,即可得到密钥。

代码预览

cipher_path = input("请输入密文所在txt文件的路径:")
f = open(cipher_path)
cipher = f.read()
f.close()

cipher = cipher.lower()
cipher = ",".join(cipher)
list_cipher = cipher.split(",")         #将密文中的每个字母转化为列表中的元素

#下面这个函数用于记录字母在列表中出现的频数
def count_f(unicode,list):
    length = len(list)
    count = 0
    for i in range(length):
        if chr(unicode) == list[i]:
            count = count + 1
    return count

#该函数用于将密文列表分为m组,每一组n个
def list_M(m,n):
    list_m = []
    for i in range(m):
        for j in range(n):
            list_m.append(list_cipher[i + j * m])
    return list_m


#下面来求密钥字长度m
for m in range(1,10):
    n = int((len(list_cipher) - len(list_cipher) % m) / m)   #n为每一组的个数,若无法平分则舍弃最后的几个密文
    list_m = list_M(m, n)
      #得到的列表list_m每n个为一组,共m组

    #下面求每一组中各个字母的出现频数
    list_I = []            #用于保存每一组的重合指数
    for i in range(m):
        list_f = []  # 用于记录各个字母出现的频数
        for j in range(26):
            unicode_j = ord('a') + j
            list_f.append(count_f(unicode_j, list_m[i * n:(i * n + n)]))

        #下面求每一组的重合指数
        I = 0
        for j in range(26):
            I = I + (list_f[j]*(list_f[j]-1))/(n * (n - 1))
        list_I.append(I)

    #下面求平均重合指数以及它与0.065的差值
    sum = 0
    for i in range(m):
        sum = sum + list_I[i]
    avg_I = sum / m
    difference = abs(avg_I-0.065)

    if m == 1:
        t = difference
        k = 1
    elif m != 1:
        if difference <= t:                                                                                       #作比较,若这一次的差值小于上一次的差值,说明这一次的重合指数更接近0.065,循环结束后得到的k即为密钥字长度
            t = difference
            k = m

#得到了密钥的长度,下面来求密钥
n = int((len(list_cipher) - len(list_cipher) % k) / k)
list_m = list_M(k, n)

list_secret = []
list_usual = [0.082,0.015,0.028,0.043,0.127,0.022,0.020,0.061,0.070,0.002,0.008,0.040,0.024,0.067,0.075,0.019,0.001,0.060,0.063,0.091,0.028,0.010,0.023,0.001,0.020,0.001]
for i in range(k):
    list_f = []  # 用于记录各个字母出现的频数
    for j in range(26):
        unicode_j = ord('a') + j
        list_f.append(count_f(unicode_j, list_m[i * n:(i * n + n)]))
    for g in range(26):
        sum = 0
        for p in range(26):
            sum = sum + list_usual[p]*list_f[(p+g)%26]
        M = abs((sum / n) - 0.065)
        if g == 0:
            t = M
            secret = 0
        elif g != 0:
            if M <= t:
                t = M
                secret = g
    list_secret.append(secret)

list_str = []
for i in range(k):
    list_str.append(chr(ord('a') + list_secret[i]))
str = ''.join(list_str)
print(str)

    list_str.append(chr(ord('a') + list_secret[i]))
str = ''.join(list_str)
print(str)

代码分析与运行结果

cipher_path = input("请输入密文所在txt文件的路径:")
f = open(cipher_path)
cipher = f.read()
f.close()

cipher = cipher.lower()
cipher = ",".join(cipher)
list_cipher = cipher.split(",")         #将密文中的每个字母转化为列表中的元素

读取密文所在txt文件中的内容,并将其全部转化为小写后保存在列表中,每个字母均为一个单独的元素

#下面这个函数用于记录字母在列表中出现的频数
def count_f(unicode,list):
    length = len(list)
    count = 0
    for i in range(length):
        if chr(unicode) == list[i]:
            count = count + 1
    return count

#该函数用于将密文列表分为m组,每一组n个
def list_M(m,n):
    list_m = []
    for i in range(m):
        for j in range(n):
            list_m.append(list_cipher[i + j * m])
    return list_m

两个函数的编写,用于后续使用

count_f函数为输入一个字母的Unicode码与一个列表,而后返回该字母在此列表中出现的频数。

list_M函数用于将密文分组。

#下面来求密钥字长度m
for m in range(1,10):
    n = int((len(list_cipher) - len(list_cipher) % m) / m)   #n为每一组的个数,若无法平分则舍弃最后的几个密文
    list_m = list_M(m, n)
      #得到的列表list_m每n个为一组,共m组

    #下面求每一组中各个字母的出现频数
    list_I = []            #用于保存每一组的重合指数
    for i in range(m):
        list_f = []  # 用于记录各个字母出现的频数
        for j in range(26):
            unicode_j = ord('a') + j
            list_f.append(count_f(unicode_j, list_m[i * n:(i * n + n)]))

        #下面求每一组的重合指数
        I = 0
        for j in range(26):
            I = I + (list_f[j]*(list_f[j]-1))/(n * (n - 1))
        list_I.append(I)

    #下面求平均重合指数以及它与0.065的差值
    sum = 0
    for i in range(m):
        sum = sum + list_I[i]
    avg_I = sum / m
    difference = abs(avg_I-0.065)

    if m == 1:
        t = difference
        k = 1
    elif m != 1:
        if difference <= t:                                                                                       #作比较,若这一次的差值小于上一次的差值,说明这一次的重合指数更接近0.065,循环结束后得到的k即为密钥字长度
            t = difference
            k = m

这里m的循环我只写了(1,10),只进行到9,也就是说只能破解9位以下的密钥。

由于密文的长度n不一定每一次都能整除m,所以直接舍弃最后一部分,来保证可以将其分为长度相等的m组。

list_m列表的顺序为[ y 1 , y m + 1 , … y ( n − 1 ) ∗ m + 1 , y 2 … y m n y_1,y_{m+1},…y_{(n-1)*m+1},y_2…y_{mn} y1,ym+1,y(n1)m+1,y2ymn],即依次为 Y 1 , Y 2 … Y m Y_1,Y_2…Y_m Y1,Y2Ym

list_f列表的顺序与list_m相同,后者的元素是密文中的字母,前者的元素是其对应字母的在每一组中的出现频数(例如字母a在 Y 1 Y_1 Y1中的出现频数)

list_I中记录的依次是 Y 1 , Y 2 , … Y m Y_1,Y_2,…Y_m Y1,Y2,Ym的重合指数。

而后取出list_I中的元素求总的平均重合指数,并求与0.065的差值,每一次m与前一次m的差值进行比较,用k保留差值较小时的m,循环结束后得到的k即为密钥的长度。

#得到了密钥的长度,下面来求密钥
n = int((len(list_cipher) - len(list_cipher) % k) / k)
list_m = list_M(k, n)

list_secret = []      #用于保存密钥字母对应的数字
list_usual = [0.082,0.015,0.028,0.043,0.127,0.022,0.020,0.061,0.070,0.002,0.008,0.040,0.024,0.067,0.075,0.019,0.001,0.060,0.063,0.091,0.028,0.010,0.023,0.001,0.020,0.001]
for  in range(k):
    list_f = []  # 用于记录各个字母出现的频数
    for j in range(26):
        unicode_j = ord('a') + j
        list_f.append(count_f(unicode_j, list_m[i * n:(i * n + n)]))
    for g in range(26):
        sum = 0
        for p in range(26):
            sum = sum + list_usual[p]*list_f[(p+g)%26]
        M = abs((sum / n) - 0.065)
        if g == 0:
            t = M
            secret = 0
        elif g != 0:
            if M <= t:
                t = M
                secret = g
    list_secret.append(secret)

list_str = []
for i in range(k):
    list_str.append(chr(ord('a') + list_secret[i]))
str = ''.join(list_str)
print(str)

此时密钥长度k已经得到,利用list_M函数来进行分成k组。

list_usual中的元素依次为上面表中A~Z的出现概率,用其进行 M g M_g Mg的计算,并与0.065进行比较,将最靠近0.065时的 g g g值保存在list_secret列表中,最后利用Unicode码转化为字母并保存在字符串str中并打印出str,即密钥。

前面我们利用加密程序对明文加密得到了密文,现在我们将得到的密文在解密程序中运行来看看结果。

在这里插入图片描述

可以看到密钥完全正确。

  • 13
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值