维吉尼亚密码——笔记1

本文深入探讨了维吉尼亚密码的原理,包括多表代换的加密与解密过程,以及无密钥情况下的破译分析。介绍了Kasiski测试法和重合指数法用于确定密钥长度,并详细讲解了如何通过分析密文的统计特性来推测密钥内容。

维吉尼亚密码——笔记1

原理

  • 是一种多表代换密码
  • 若密钥 K = ( k 0 , k 1 , k 2 , . . . , k m − 1 ) K=(k_0,k_1,k_2,...,k_{m-1}) K=(k0,k1,k2,...,km1),即密钥长度为m;明文为 ( x 0 , x 1 , x 2 , . . . ) (x_0,x_1,x_2,...) (x0,x1,x2,...),对应密文为 ( y 0 , y 1 , y 2 , . . . ) (y_0,y_1,y_2,...) (y0,y1,y2,...)
  • 则对于明文中的每一个字符 x i x_i xi,有 { 加 密 函 数   e ( x i ) = ( x i + k i   m o d   m ) 解 密 函 数   d ( y i ) = ( y i − k i   m o d   m ) \begin{cases} 加密函数\,e(x_i)=(x_i+k_{i\,mod\, m})\\ 解密函数\,d(y_i)=(y_i-k_{i\,mod\, m}) \end{cases} {e(xi)=(xi+kimodm)d(yi)=(yikimodm)

无密钥破译分析

  • 首先需要找到密钥的长度m
    • Kasiski测试法
      • 由Friedrich Kasiski在1863年给出描述,由Charles Babbage于1854年首先发现

      基于这样一个事实:两个相同的明文段将加密成相同的密文段,它们的位置间距假设为 δ \delta δ,则 δ = 0 ( m o d   m ) \delta=0(mod\,m) δ=0(modm)。反过来,如果在密文中观察到两个相同的长度至少为3的密文段,那么将给破译者带来很大方便,因为它们实际上对应了相同的明文串

      • 工作流程:
        • 搜索长度至少为3的相同的密文段,记下其离起始点的那个密文段的距离
        • 假设得到距离为: δ 1 , δ 2 , . . . \delta_{1},\delta_{2},... δ1,δ2,...
        • 那么可以猜测 m m m为这些 δ i \delta_i δi的最大公因子的因子
    • 重合指数法
      • 由William Friedman提出

      x = x 1 x 2 . . . x n \textbf{x}=x_1x_2...x_n x=x1x2...xn是一条n个字母的串, x \textbf{x} x的重合指数记为 I c ( x ) I_c(x) Ic(x),定义为 x \textbf{x} x中两个随机元素相同的概率

      • f 0 , f 1 , . . . , f 25 f_0,f_1,...,f_{25} f0,f1,...,f25分别表示A,B,…,Z在x中出现的频数
      • I c ( x ) = ∑ i = 0 25 ( f i 2 ) ( n 2 ) = ∑ i = 0 25 f i ( f i − 1 ) n ( n − 1 ) I_c(x) =\frac{\sum_{i=0}^{25}\binom{f_i}{2}}{\binom{n}{2}}=\frac{\sum_{i=0}^{25}f_i(f_i-1)}{n(n-1)} Ic(x)=(2n)i=025(2fi)=n(n1)i=025fi(fi1)
      • 如果x是英文文本串,有 I c ( x ) ≈ 0.065 I_c(x)\approx0.065 Ic(x)0.065
      • 如果x是一个完全的随机串,有 I c ( x ) = 1 26 ≈ 0.038 I_c(x)=\frac{1}{26}\approx0.038 Ic(x)=2610.038
      • 也就是说对于一个英文文本串,或是其经过移位密码加密后的密文,其重合指数 I c ( x ) I_c(x) Ic(x)应该接近0.065
        • 但Vigenere加密中对每一明文字符移位距离不一定相同
        • 对这种问题,我们将用密钥字中相同字符加密后的密文提取出
          • y i = y i y m i y 2 m + i . . . \textbf{y}_i=y_iy_{m_i}y_{2m+i}... yi=yiymiy2m+i...这样提取出的字符串的重合指数应该接近0.065
      • 由上一步分析结果,可对m进行测试,对于每一个测试的m,可将密文 y y y分组 y 0 = y 0 y m y 2 m . . . y 1 = y 1 y m + 1 y 2 m + 1 . . . ⋮ y m − 1 = y m − 1 y 2 m − 1 y 3 m − 1 . . . \begin{aligned} \textbf{y}_0&=y_0y_my_{2m}...\\ \textbf{y}_1&=y_1y_{m+1}y_{2m+1}...\\ \vdots\\ \textbf{y}_{m-1}&=y_{m-1}y_{2m-1}y_{3m-1}... \end{aligned} y0y1ym1=y0ymy2m...=y1ym+1y2m+1...=ym1y2m1y3m1...
      • 求出这些字串的重合指数平均数,按平均数与0.065的接近程度排序,得到一系列m的可能值
      • 可依次对每个m的可能值进行下步内容(确定密钥内容+译码)
  • 得到密钥长度 m m m后,确定密钥内容
    • 由前步分析知,对于分割的每一个子串,子串中每个密文字符都是用同一密钥字符加密的,相当于一个移位加密,其中子串 y i \textbf{y}_i yi对应移位为 k i k_i ki
    • n n n表示字串 y i \textbf{y}_i yi长度, f 0 , f 1 , . . . , f 25 f_0,f_1,...,f_{25} f0,f1,...,f25表示此子串中字母A,B,…,Z出现的频数,则有26个字母在子串中的概率分布估计为:
      f 0 n , f 1 n , . . . , f 25 n \frac{f_0}{n},\frac{f_1}{n},...,\frac{f_{25}}{n} nf0,nf1,...,nf25
    • 又因为移位为 k i k_i ki,所以明文中频率 f j n \frac{f_j}{n} nfj等于密文中频率 f j + k i n \frac{f_{j+k_i}}{n} nfj+ki
    • 于是有数值 M = ∑ i = 0 25 p i f i + k i n ≈ ∑ i = 0 25 p i 2 = 0.065 M=\sum_{i=0}^{25}\frac{p_if_{i+k_i}}{n}\approx\sum_{i=0}^{25}p_i^2=0.065 M=i=025npifi+kii=025pi2=0.065
    • 到现在我们已经有了如下问题转换 确 定 密 钥 内 容 ⇒ 确 定 每 个 子 串 中 对 应 k i ⇒ 通 过 改 变 k i 值 对 M 进 行 测 试 确定密钥内容\Rightarrow确定每个子串中对应k_i\Rightarrow通过改变k_i值对M进行测试 kikiM
    • 如上步所述,对 k i k_i ki测试,记测试值为 g , ( 0 ≤ g ≤ 25 ) g,(0\leq g\leq 25) g,(0g25),有数值 M g = ∑ i = 0 25 p i f i + g n M_g=\sum_{i=0}^{25}\frac{p_if_{i+g}}{n} Mg=i=025npifi+g
    • 取使得 M g M_g Mg最接近0.065的g作为 k i k_i ki
      • 这样的取法可能导致最终密钥并不一定是正确的,但大部分正确的可能性比较大,最后解密结果可能与真实结果有偏差,但偏差一般不会很大,一般可以人为看出来,如原文为Helloworld,可能错破译为Hjllowhrld
    • 集合所得到的所有 k i k_i ki,合成最终密钥字
  • 解密

代码

class VigenereCipher:
    def __init__(self,s):
        self.msg=s.lower()
    # 更改字串
    def update(self,s):
        self.msg=s.lower()
    # 重合指数计算
    def Ic(self,x):
        assert type(x)==str
        x=x.lower()
        n=len(x)
        ans=0
        for i in range(26):
            f=x.count(chr(i+ord('a')))
            ans+=f*(f-1)
        ans=float(ans)/(n*(n-1))
        return ans
    # 得到密钥字长度的可能值(依各子串重合指数平均数与0.065的接近排序,接近的在前)
    def findKeyLen(self,maxkeylen=30):
        assert type(self.msg)==str
        mp={}
        for m in range(1,maxkeylen+1):
            ans=sum([self.Ic(self.msg[i::m]) for i in range(m)])/float(m)
            mp[m]=ans
        mp=dict(sorted(mp.items(),key=lambda item:abs(item[1]-0.065)))
        return list(mp.keys())
    # 确定密钥内容
    # 并不一定是真正的密钥内容,只是可能性大,即便密钥长度判断是正确的
    def findKeyContent(self,keylen):
        key=""
        p=[float(i)/1000 for i in [82,15,28,43,127,22,20,61,70,2,8,40,24,67,75,19,1,60,63,91,28,10,23,1,20,1]]
        for i in range(keylen):
            yi=self.msg[i::keylen]
            n=len(yi)
            #频率f/n
            fn=[float(fi)/n for fi in [yi.count(chr(i+ord('a'))) for i in range(26)]]
            M=lambda g: sum(p[i]*fn[(i+g)%26] for i in range(26))
            # 直接令每一块使得Mg最接近0.065的为ki
            ki=min(range(26),key=lambda g:abs(M(g)-0.065))
            key+=chr(ord('a')+ki)            
        return key
    # 根据密钥对msg解密
    def decode(self,key):
        assert type(self.msg)==str
        key=key.lower()
        m=len(key)
        digit=lambda a: ord(a)-ord('a')
        alpha=lambda a: chr(a+ord('a'))
        sub=lambda a,b: alpha((digit(a)-digit(b))%26)
        ans=""
        for i in range(len(self.msg)):
            ans+=sub(self.msg[i],key[i%m])
        return ans
    # 根据密钥对msg加密
    def encode(self,key):
        assert type(self.msg)==str
        key=key.lower()
        m=len(key)
        digit=lambda a: ord(a)-ord('a')
        alpha=lambda a: chr(a+ord('a'))
        add=lambda a,b: alpha((digit(a)+digit(b))%26)
        ans=""
        for i in range(len(self.msg)):
            ans+=add(self.msg[i],key[i%m])
        return ans
    # 破解
    def violentCrack(self,maxkeylen=30):
        cnt=0
        for m in self.findKeyLen(maxkeylen):
            key=self.findKeyContent(m)
            print("key="+key+":\n"+self.decode(key),end='\n\n')
            cnt+=1
            if cnt%10 == 0:
                q=input('输入q退出。。。')
                try:
                    if ord(q[0]) in [ord('q'),ord('Q')]:
                        return 
                except:
                    pass


if __name__=="__main__":
    s="CHREEVOAHMAERATBIAXXWTNXBEEOPHBSBQMQEQERBWRVXUOAKXAOSXXWEAHBWGJMMQMNKGRFVGXWTRZXWIAKLXFPSKAUTEMNDCMGTSXMXBTUIADNGMGPSRELXNJELXVRVPRTULHDNQWTWDTYGBPHXTFALJHASVBFXNGLLCHRZBWELEKMSJIKNBHWRJGNMGJSGLXFEYPHAGNRBIEQJTAMRVLCRREMNDGLXRRIMGNSNRWCHRQHAEYEVTAQEBBIPEEWEVKAKOEWADREMXMTBHHCHRTKDNVRZCHRCLQOHPWQAIIWXNRMGWOIIFKEE"
    
    ss="KCCPKBGUFDPHQTYAVINRRTMVGRKDNBVFDETDGILTXRGUDDKOTFMBPVGEGLTGCKQRACQCWDNAWCRXIZAKFTLEWRPTYCQKYVXCHKFTPONCQQRHJVAJUWETMCMSPKQDYHJVDAHCTRLSVSKCGCZQQDZXGSFRLSWCWSJTBHAFSIASPRJAHKJRJUMVGKMITZHFPDISPZLVLGWTFPLKKEBDPGCEBSHCTJRWXBAFSPEZQNRWXCVYCGAONWDDKACKAWBBIKFTIOVKCGGHJVLNHIFFSQESVYCLACNVRWBBIREPBBVFEXOSCDYGZWPFDTKFQIYCWHJVLNHIQIBTKHJVNPIST"
    tmp=VigenereCipher(ss)
    tmp.violentCrack()
Python维吉尼亚密码是一种简单的替换加密算法,也被称为凯撒密码的一种变体,不过它涉及到两个字母表。解密维吉尼亚密码通常需要一个密钥,该密钥是一段与原文长度相同的文本,通过这个密钥将明文按照字母表顺序与密钥对应位置的字符替换。 以下是基本的解密步骤: 1. **获取密钥**:首先,你需要知道用于加密的密钥。如果只知道加密后的结果,需要找到对应的密钥才能解密,这可能比较困难。 2. **匹配**:对于每一个加密过的字符,从原文的密钥中找到相应位置的字符。如果是大写字母,直接按字母顺序查找;如果是小写字母,先转换成大写再找。 3. **替换**:找到了对应位置的密钥字符,替换原加密字符,得到解密后的字母。 4. **还原**:完成所有字符的替换后,组合起来就得到了原始的消息。 如果你有一个具体的加密字符串和已知的密钥,我可以帮你演示如何在Python中进行解密,但是由于无法直接在这里编写代码,你可以参考以下伪代码: ```python def decrypt_vigenere(ciphertext, key): plaintext = "" key_index = 0 for char in ciphertext: if char.isalpha(): shift = ord(key[key_index]) - ord('A') # 如果是英文,shift是基于'A' new_char = chr((ord(char) - ord('A') + shift) % 26 + ord('A')) if char.islower(): new_char = new_char.lower() plaintext += new_char key_index = (key_index + 1) % len(key) else: plaintext += char return plaintext # 使用示例 ciphertext = "Khoor Zruog" key = "ExampleKey" # 密钥应与原文长度一致 decrypted_text = decrypt_vigenere(ciphertext, key) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值