目录
加密过程
加密原理
字母表中每一个字母都对应着一个数字,从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=025fi(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=y1y2…yn。将其分割为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=025n′pifi+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,即密钥。
前面我们利用加密程序对明文加密得到了密文,现在我们将得到的密文在解密程序中运行来看看结果。
可以看到密钥完全正确。
版权归原作者 Ultramarine! 所有, 如有侵权,请联系我们删除。