首页 > 代码库 > python 密码学编程 -- 2

python 密码学编程 -- 2

接上一篇随笔

********************************************************************

*        quote : "http://inventwithpython.com/"              *

*        python-version : 2.7.11                      *

********************************************************************

1.通过编程来检测英文

计算机无法理解英文。但是和乱码相比,英文是由可以在字典里找到的单词组成的。

我们可以通过判断一个单词是不是存在于某个字典来判断这个单词是不是英文。

当然,可能我们的字典不够大,导致某些正确的英文单词被判断成乱码。或者乱码碰巧刚好是一个英文单词。

但是我们说。在字典足够大且单词多为常用词的情况下,发生这些事的概率是比较小的。

我们说,当一个字符串中的大多数参数都被包含在这个英文字典里,我们就说这是一个英文字符串。

这里提供一本英文字典:http://invpy.com/dictionary.txt

要运行下面这个程序,你需要下载这个字典并且和 py 文件放在同一个目录下  

detectEnglish.py

 1 #-*-coding:utf-8-*-
 2 UPPERLETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 3 LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() +  \t\n
 4 
 5 def loadDictionary():    #返回一个dict。其中包含了dictionary.txt 中的所有单词
 6     dictionaryFile = open("dictionary.txt")
 7     englishWords = {}
 8     for word in dictionaryFile.read().split(\n):
 9         englishWords[word] = None
10     dictionaryFile.close()
11     return englishWords
12     
13 ENGLISH_WORDS = loadDictionary()
14 
15 def removeNonLetters(message):    #只保留原字符串中的英文和空格部分
16     lettersOnly = []
17     for symbol in message:
18         if symbol in LETTERS_AND_SPACE:
19             lettersOnly.append(symbol)
20     return ‘‘.join(lettersOnly)
21 
22 def getEnglishcount(message):    #返回一个占比值。即这个message中的英文单词占(百分)比多少
23     message = message.upper()
24     message = removeNonLetters(message)
25     possibleWords = message.split()    #预处理。得到原message中所有单词组成的列表
26     
27     if possibleWords == []:
28         return 0.0
29     
30     matches = 0
31     for word in possibleWords:
32         if word in ENGLISH_WORDS:
33             matches += 1
34     return float(matches) / len(possibleWords)
35     
36 def isEnglish(message , wordPercentage = 20, letterPercentage = 85):
37     wordsMatch = getEnglishcount(message) * 100 >= wordPercentage
38     numLetters = len(removeNonLetters(message))
39     messageLettersPercentage = float(numLetters)  / len(message) *100
40     lettersMatch = messageLettersPercentage >= letterPercentage
41     return wordsMatch and lettersMatch

具体实现。我们把一个字符串删除数字和特殊符号之后分成一个一个单词的列表。

当这些单词中至少有20%是被包含在字典中。

并且原字符串至少85%是英文字母或者空格时(非数字和特殊字符)时。

我们就判断这个字符串是英文消息。

 

2.暴力破译换位加密法

 从理论上来说,当要加密的消息为 message 时,换位加密法可能的秘钥有 len(message)-2  个(从 2 到 len(message)-1)

 对于每一个用可能的秘钥破译出来的明文,我们需要判断这个明文是不是英文消息,从而判断破译是否成功。

 于是。我们可以和上一个程序联系起来。让电脑自动判断破译出来的消息是否为英文。

transpositionHacker.py

 1 #-*-coding:utf-8-*-
 2 import detectEnglish , transpositionDecrypt
 3 
 4 def main():
 5     myMessage = """Cb b rssti aieih rooaopbrtnsceee er es no npfgcwu  plri ch nitaalr eiuengiteehb(e1  hilincegeoamn fubehgtarndcstudmd nM eu eacBoltaeteeoinebcdkyremdteghn.aa2r81a condari fmps" tad   l t oisn sit u1rnd stara nvhn fsedbh ee,n  e necrg6  8nmisv l nc muiftegiitm tutmg cm shSs9fcie ebintcaets h  aihda cctrhe ele 1O7 aaoem waoaatdahretnhechaopnooeapece9etfncdbgsoeb uuteitgna.rteoh add e,D7c1Etnpneehtn beete" evecoal lsfmcrl iu1cifgo ai. sl1rchdnheev sh meBd ies e9t)nh,htcnoecplrrh ,ide hmtlme. pheaLem,toeinfgn t e9yce da‘ eN eMp a ffn Fc1o ge eohg dere.eec s nfap yox hla yon. lnrnsreaBoa t,e eitsw il ulpbdofgBRe bwlmprraio po  droB wtinue r Pieno nc ayieeto‘lulcih sfnc  ownaSserbereiaSm-eaiah, nnrttgcC  maciiritvledastinideI  nn rms iehn tsigaBmuoetcetias rn"""
 6     hackedMessage = hackTransposition(myMessage)
 7     
 8     if hackedMessage == None:
 9         print Failed to hack encryption
10     else:
11         print Copying hacked message to clipboard:
12         print hackedMessage
13         
14 def hackTransposition(message):
15     print Hacking
16     
17     for key in xrange(1 , len(message)):
18         print Trying key #%s ...%(key)
19         decryptedText = transpositionDecrypt.decryptMessage(key , message)
20         if detectEnglish.isEnglish(decryptedText):
21             print ‘‘
22             print Possible encryption hack : 
23             print Key %s : %s%(key ,decryptedText[:100])
24             print ‘‘
25             print If you think the message is Ok , Enter D for done , or just press Enter to continue hacking :
26             resp = raw_input(>>>)
27             
28             if resp.strip().upper().startswith(D):
29                 return decryptedText
30     return None
31     
32 if __name__ == __main__:
33     main()

这段代码的主要函数都在另外两个py文件里。具体的代码和功能我们在之前有叙述。这里不重复。

 

3.仿射加密法

我们先来说一下乘数加密法。

类似于凯撒加密法。凯撒加密时我们是加上了一个秘钥得到了一个对应关系。那现在如果我们乘上一个秘钥呢?

和凯撒加密法相比,乘数加密法的优势是可以使用很大的秘钥。它的对应关系的周期更大。

但是乘数加密法一个缺点是字母 A 对应的数字是 0 。它在乘上秘钥之后仍然是 0 。那么对任意秘钥,字母 A 就永远对应字母 A。

现在我们在进行乘数加密法之后,再进行凯撒加密法,就可以解决这个问题。

这意味着,我们需要两个秘钥,假设为 A 和 B

  明文-->乘上秘钥 A -->加上秘钥 B -->根据符号集的大小取模-->密文

  明文<--根据符号集的大小进行取模运算<--乘上秘钥 A 的模逆<--减去秘钥 B <--密文

这就是仿射加密法。

 

但是。乘数加密法的秘钥 A 存在一个问题。

例如当你选择秘钥 A = 8 时。字母 C 和 P 都被加密成了 Q。这样在解密的时候就无法判断原明文。

实际上。秘钥A 和符号集的大小必须互质。 也就是说,两者的最大公约数必须为1。

另外。如果你足够细心的话。会发现解密的时候我们并没有 乘以 A 的倒数,而是乘上 A 的模逆。

原因在此不详细谈论。

我们先写两个函数来帮助我们求最大公约数和模逆。

cryptomath.py

 1 #cryptomath.py
 2 #-*-coding:utf-8-*-
 3 def gcd(a , b):    #求最大公约数
 4     while a != 0:
 5         a ,b = b%a , a
 6     return b
 7     
 8 def findModInverse(a , m): #欧几里得 扩展算法求模逆
 9     if gcd(a , m) != 1:
10         return None
11     u1 , u2 , u3 = 1 , 0 , a
12     v1 , v2 , v3 = 0, 1, m
13     while v3 != 0:
14         q = u3 / v3
15         v1 , v2 , v3 , u1 , u2 , u3 = (u1 - q * v1)  , (u2 - q * v2) , (u3 - q * v3) , v1 , v2 , v3
16     return u1 % m

算法详见百度。

 affineCipher.py

 1 #-*-coding:utf-8-*-
 2 import sys , cryptomath , random
 3 SYMBOLS = """ !"#$%&‘()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""
 4 
 5 def main():
 6     myMessage = """"A computer would deserve to be called intelligent if it could deceive a human into believing that it was human." -Alan Turing"""
 7     myKey = 2023
 8     myMode = encrypt
 9     
10     if myMode == encrypt:
11         translated = encryptMessage(myKey , myMessage)
12     elif myMode == decrypt:
13         translated = decryptMessage(myKey  , myMessage)
14     print Key : %s%(myKey)
15     print %sed text :%(myMode.title())
16     print translated
17     print ‘‘
18     translated = decryptMessage(myKey , translated)
19     print Decrypted test :
20     print translated
21 
22 def getKeyParts(key):    #由一个秘钥得到两部分秘钥  
23     keyA = key/len(SYMBOLS)   
24     keyB = key%len(SYMBOLS)
25     return (keyA , keyB)
26     
27 def checkKeys(keyA , keyB , mode):
28     if keyA == 1 and mode == encrypt:        #keyA 为 1 时,秘钥太弱
29         sys.exit("key A can‘t be 1.Choose a different key.")
30     elif keyB == 0 and mode == encrypt:    #keyB 为 0 时,秘钥太弱
31         sys.exit("key B can‘t be 0.Choose a different key.")
32     elif keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) -1:#限定秘钥范围。其中keyA 大于0 , 0 < keyB < len(SYMBOLS) 
33         sys.exit(Key A must be greater than 0 and key B must be between 0 and %s)%(len(SYMBOLS))
34     elif cryptomath.gcd(keyA  , len(SYMBOLS)) != 1:        #前面说秘钥keyA 必须与符号集的大小互质
35         sys.exit(key A and the symbol set size are not relatively prime.Choose a different key)
36 #加密解密过程均是找到对应关系
37 def encryptMessage(key , message):
38     keyA , keyB = getKeyParts(key)
39     checkKeys(keyA, keyB , encrypt)
40     cipherText = ‘‘
41     for symbol in message:
42         if symbol in SYMBOLS:
43             symIndex = SYMBOLS.find(symbol)
44             cipherText += SYMBOLS[(symIndex * keyA + keyB) % len(SYMBOLS)]
45         else:
46             cipherText += symbol
47     return cipherText
48     
49 def decryptMessage(key , message):
50     keyA , keyB = getKeyParts(key)
51     checkKeys(keyA, keyB , decrypt)
52     plainText = ‘‘
53     modInverseOfKeyA = cryptomath.findModInverse(keyA , len(SYMBOLS))
54     
55     for symbol in message:
56         if symbol in SYMBOLS:
57             symIndex = SYMBOLS.find(symbol)
58             plainText += SYMBOLS[(symIndex - keyB) * modInverseOfKeyA % len(SYMBOLS)]
59         else:
60             plainText += symbol
61     return plainText
62     
63 def getRandomKey():#可能不容易想到一个符合条件的秘钥。这时你可以选择用getRandomKey() 函数自动生成一个秘钥
64     while True:
65         keyA = random.randint(2 , len(SYMBOLS))
66         keyB = random.randint(2 , len(SYMBOLS))
67         if cryptomath.gcd(keyA , len(SYMBOLS)) == 1:
68             return keyA * len(SYMBOLS) + keyB
69             
70 if __name__ == __main__:
71     main()

 

4.仿射加密法的破译

仿射加密法的秘钥可能数是有限的
SYMBOLS = """ !"#$%&‘()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""

对于上述这个字符集。(len(SYMBOLS) = 95)。

因为秘钥 keyB 是对这个长度取模得到的。所以 keyB 的值在 0 和这个 长度减一 之间。 keyB的可能取值是95个

对于秘钥keyA 。我们假设一个字母原来对应的数字为 X , 加密后对应的数字是 Y。

即。 Y = (X * keyA + keyB)%len(SYMBOLS)

那么。我们同样有 (X * (keyA+len(SYMBOLS))  +keyB ) % len(SYMBOLS) = (X * keyA + keyB)%len(SYMBOLS) + x*len(SYMBOLS) % len(SYMBOLS) = Y

这也就是说。对于keyA ,我们取 keyA + len(SYMBOLS) 作为新的秘钥 keyA 。我们得到的加密消息将会是一样的。

这意味着。秘钥A 和 B都受限于符号集的大小。

affineHacker.py

 1 import affineCipher ,detectEnglish , cryptomath
 2 SILENT_MODE = False
 3 
 4 def main():
 5     myMessage = """U&‘<3dJ^Gjx‘-3^MS‘Sj0jxuj‘G3‘%j‘<mMMjS‘g{GjMMg9j{G‘g"‘gG‘<3^MS‘Sj<jguj‘m‘P^dm{‘g{G3‘%jMgjug{9‘GPmG‘gG‘-m0‘P^dm{LU‘5&Mm{‘_^xg{9"""
 6     hackedMessage = hackAffine(myMessage)
 7     
 8     if hackedMessage != None:
 9         print hackedMessage
10     else:
11         print Failed to hack encryption.
12 
13 def hackAffine(message):
14     print Hacking ...
15     for key in xrange(len(affineCipher.SYMBOLS) ** 2):
16         keyA = affineCipher.getKeyParts(key)[0]
17         if cryptomath.gcd(keyA , len(affineCipher.SYMBOLS)) != 1:
18             continue
19         decryptedText = affineCipher.decryptMessage(key , message)
20         if not SILENT_MODE:
21             print Tried key %s ... (%s)%(key , decryptedText)
22             
23         if detectEnglish.isEnglish(decryptedText):
24             print ‘‘
25             print Possible encryption hack :
26             print Key " %s%(key)
27             print Decrypted message :+decryptedText[:200]
28             print ‘‘
29             print Enter D for done , or just press Enter to continue hacking
30             resp = raw_input(>>>)
31             if resp.strip().upper().startswith(D):
32                 return decryptedText
33     return None
34 
35 if __name__ == __main__:
36     main()

由前面的分析。我们得到。keyA 和 keyB 的值均在 1 到 len(SYMBOLS)之间。

那么,我们对应的秘钥就可能有 len(SYMBOLS) * len(SYMBOLS) 个。

我们遍历这些可能的秘钥。通过上一个py文件得到 keyA keyB 的值然后尝试解密。得到英文消息时自动暂停。

 

5.简单替代加密法

要实现简单替代加密法,随机选择字母来加密字母表的每个字母。每个字母只用一次。这样的秘钥可以有 403 291 461 126 605 635 584 000 000 个。

类似于前面的凯撒加密法和仿射加密法。 只不过,这一次,我们并不用简单的加或乘的关系来找对应的秘钥。

simpleSubCipher.py

 1 import sys , random
 2 
 3 LETTERS = ABCDEFGHIJKLMNOPQRSTUVWXYZ
 4 
 5 def main():
 6     myMessage = If a man is offered a fact which goes against his instincts, he will scrutinize it closely, and unless the evidence is overwhelming, he will refuse to believe it. If, on the other hand, he is offered something which affords a reason for acting in accordance to his instincts, he will accept it even on the slightest evidence. The origin of myths is explained in this way. -Bertrand Russell
 7     myKey = LFWOAYUISVKMNXPBDCRJTQEGHZ
 8     myMode = encrypt
 9     
10     checkValidKey(myKey)
11     
12     if myMode == encrypt:
13         translated = encryptMessage(myKey , myMessage)
14     elif myMode == decrypt:
15         translated = decryptMessage(myKey , myMessage)
16     print Using key %s%(myKey)
17     print ‘‘
18     print The %sed message is : %(myMode)
19     print translated
20     print ‘‘
21     print the decrypted message is :
22     print decryptMessage(myKey , translated)
23         
24 def checkValidKey(key):
25     keyList = list(key)
26     lettersList = list(LETTERS)
27     keyList.sort()
28     lettersList.sort()
29     if keyList != lettersList:
30         sys.exit(There is an error in the key or symbol set.)
31 
32 def encryptMessage(key , message):
33     return translateMessage(key , message , encrypt)
34 def decryptMessage(key , message):
35     return translateMessage(key , message , decrypt)
36     
37 def translateMessage(key , message , mode):
38     translated = ‘‘
39     charsA = LETTERS
40     charsB = key
41     if mode == encrypt:
42         charsA , charsB = charsB , charsA
43     for symbol in message:
44         if symbol.upper() in charsA:
45             symIndex = charsA.find(symbol.upper())
46             if symbol.isupper():
47                 translated += charsB[symIndex]
48             else:
49                 translated += charsB[symIndex].lower()
50         else:
51             translated += symbol
52     return translated
53     
54 def getRandomKey():
55     key = list(LETTERS)
56     random.shuffle(key)
57     return ‘‘.join(key)
58     
59 if __name__ == __main__:
60     main()

我们设置一个秘钥。这个秘钥应当是26个英文字母的某种排列方式。我们将这种设定的排列方式和原顺序(ABCDEFG。。。)对应起来进行替换。

怎么破译简单替代加密法。?

前面我们说到。简单替代加密法可能的秘钥有 403 291 461 126 605 635 584 000 000 个。秘钥实在太多了,无法让计算机来暴力破解。

我们来看一下一个可能的密文单词 HGHHU。

虽然我们不知道它对应的明文是什么。但是我们可以知道一点关于明文的信息:

  1. 明文单词一定是 5 个字母

  2. 明文第一,第三,第四个字母是一样的

  3. 这个单词有 3 个不同的字母,第一,第二,第五个字母彼此不同

那我们可以从字典中找到同样符合这三个特性的单词。例如 Puppy   Mommy  Bobby  Lilly

现在。我们需要给密词创建单词模式。第一个字母获得数字0 , 后面每个不同的字母第一次出现时都会获得下一个数字。比如:

  cat 的单词模式为 0,1,2

  catty 的单词模式为 0,1,2,2,3

  roofer 的单词模式为 0,1,1,2,3,0

那么。我们说,明文单词和密文单词总是有相同的单词模式。不管你使用的是什么秘钥。

我们先写一个小程序来计算字典文件中每个单词的单词模式,并且保存在本地

wordPatterns.py

 1 #-*-coding:utf-8-*-
 2 import pprint
 3 def getWordPattern(word):    #返回单词模式
 4     word = word.upper()
 5     nextNum = 0
 6     letterNums = {}
 7     wordPattern = []
 8     
 9     for letter in word:
10         if letter not in letterNums:
11             letterNums[letter] = str(nextNum)
12             nextNum += 1
13         wordPattern.append(letterNums[letter])
14     return ..join(wordPattern)
15 
16 def main():
17     allPatterns = {}
18     fo = open(dictionary.txt)
19     wordList = fo.read().split(\n)
20     fo.close()
21     
22     for word in wordList:
23         pattern = getWordPattern(word)
24         
25         if pattern not in allPatterns:
26             allPatterns[pattern] = [word]
27         else:
28             allPatterns[pattern].append(word)
29         
30     fo = open(wordPatterns.py,w)
31     fo.write(allPatterns = )
32     fo.write(pprint.pformat(allPatterns))
33     fo.close()
34     
35 if __name__ == __main__:
36     main()

这个程序会在本地生成一个 wordPatterns.py 文件。里面以字典的形式保存了各种不同的单词模式所对应的单词。你可以用编辑器打开看一下。

 

python 密码学编程 -- 2