0


用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.067B0.015O0.075C0.028P0.019D0.043Q0.001E0.127R0.060F0.022S0.063G0.020T0.091H0.061U0.028I0.070V0.010J0.002W0.023K0.008X0.001L0.040Y0.020M0.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(n−1)∑i=025​fi​(fi​−1)​,表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=y1​y2​…yn​。将其分割为m组长度相等的子串,具体如下。


 
  
   
    
     Y
    
    
     1
    
   
   
    =
   
   
    
     y
    
    
     1
    
   
   
    
     y
    
    
     
      m
     
     
      +
     
     
      1
     
    
   
   
    
     y
    
    
     
      2
     
     
      m
     
     
      +
     
     
      1
     
    
   
   
    …
   
  
  
   Y_1=y_1y_{m+1}y_{2m+1}…
  
 
Y1​=y1​ym+1​y2m+1​…


 
  
   
    
     Y
    
    
     2
    
   
   
    =
   
   
    
     y
    
    
     2
    
   
   
    
     y
    
    
     
      m
     
     
      +
     
     
      2
     
    
   
   
    
     y
    
    
     
      2
     
     
      m
     
     
      +
     
     
      2
     
    
   
   
    …
   
  
  
   Y_2=y_2y_{m+2}y_{2m+2}…
  
 
Y2​=y2​ym+2​y2m+2​…

┆ ┆ ┆

     Y
    
    
     m
    
   
   
    =
   
   
    
     y
    
    
     m
    
   
   
    
     y
    
    
     
      2
     
     
      m
     
    
   
   
    
     y
    
    
     
      3
     
     
      m
     
    
   
   
    …
   
  
  
   Y_m=y_my_{2m}y_{3m}…
  
 
Ym​=ym​y2m​y3m​…

如果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=025​n′pi​fi+g​​(n’为每个子串的长度)(

 
  
   
    0
   
   
    ≤
   
   
    g
   
   
    ≤
   
   
    25
   
  
  
   0≤g≤25
  
 
0≤g≤25,代表着26个字母)

    g
   
   
    =
   
   
    
     k
    
    
     i
    
   
  
  
   g=k_i
  
 
g=ki​,即

 
  
   
    g
   
  
  
   g
  
 
g对应密钥当前位的正确字母,则应该有

 
  
   
    
     M
    
    
     g
    
   
   
    ≈
   
   
    0.065
   
  
  
   M_g≈0.065
  
 
Mg​≈0.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(n−1)∗m+1​,y2​…ymn​],即依次为

 
  
   
    
     Y
    
    
     1
    
   
   
    ,
   
   
    
     Y
    
    
     2
    
   
   
    …
   
   
    
     Y
    
    
     m
    
   
  
  
   Y_1,Y_2…Y_m
  
 
Y1​,Y2​…Ym​。

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,即密钥。

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

在这里插入图片描述

可以看到密钥完全正确。

标签: python 网络

本文转载自: https://blog.csdn.net/Huiye_Daxiaojie/article/details/127189628
版权归原作者 Ultramarine! 所有, 如有侵权,请联系我们删除。

“用python实现维吉尼亚密码的加密与唯密文解密”的评论:

还没有评论