python 密码学 第一版 第二版_python 密码学编程 -- 2

接上一篇随笔

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

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

*        python-version : 2.7.11                      *

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

1.通过编程来检测英文

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

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

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

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

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

要运行下面这个程序,你需要下载这个字典并且和 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] =None10 dictionaryFile.close()11 returnenglishWords12

13 ENGLISH_WORDS =loadDictionary()14

15 def removeNonLetters(message): #只保留原字符串中的英文和空格部分

16 lettersOnly =[]17 for symbol inmessage:18 if symbol inLETTERS_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 =031 for word inpossibleWords:32 if word inENGLISH_WORDS:33 matches += 1

34 return float(matches) /len(possibleWords)35

36 def isEnglish(message , wordPercentage = 20, letterPercentage = 85):37 wordsMatch = getEnglishcount(message) * 100 >=wordPercentage38 numLetters =len(removeNonLetters(message))39 messageLettersPercentage = float(numLetters) / len(message) *100

40 lettersMatch = messageLettersPercentage >=letterPercentage41 return wordsMatch and lettersMatch

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

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

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

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

2.暴力破译换位加密法

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

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

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

transpositionHacker.py

1 #-*-coding:utf-8-*-

2 importdetectEnglish , transpositionDecrypt3

4 defmain():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 printhackedMessage13

14 defhackTransposition(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 ifdetectEnglish.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 returndecryptedText30 returnNone31

32 if __name__ == '__main__':33 main()

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

3.仿射加密法

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

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

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

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

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

这意味着,我们需要两个秘钥,假设为 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 , a6 returnb7

8 def findModInverse(a , m): #欧几里得 扩展算法求模逆

9 if gcd(a , m) != 1:10 returnNone11 u1 , u2 , u3 = 1, 0 , a12 v1 , v2 , v3 = 0, 1, m13 while v3 !=0:14 q = u3 /v315 v1 , v2 , v3 , u1 , u2 , u3 = (u1 - q * v1) , (u2 - q * v2) , (u3 - q *v3) , v1 , v2 , v316 return u1 % m

算法详见百度。

affineCipher.py

1 #-*-coding:utf-8-*-

2 importsys , cryptomath , random3 SYMBOLS = """!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""

4

5 defmain():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 printtranslated17 print ''

18 translated =decryptMessage(myKey , translated)19 print 'Decrypted test :'

20 printtranslated21

22 def getKeyParts(key): #由一个秘钥得到两部分秘钥

23 keyA = key/len(SYMBOLS)24 keyB = key%len(SYMBOLS)25 return(keyA , keyB)26

27 defcheckKeys(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 defencryptMessage(key , message):38 keyA , keyB =getKeyParts(key)39 checkKeys(keyA, keyB , 'encrypt')40 cipherText = ''

41 for symbol inmessage:42 if symbol inSYMBOLS:43 symIndex =SYMBOLS.find(symbol)44 cipherText += SYMBOLS[(symIndex * keyA + keyB) %len(SYMBOLS)]45 else:46 cipherText +=symbol47 returncipherText48

49 defdecryptMessage(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 inmessage:56 if symbol inSYMBOLS:57 symIndex =SYMBOLS.find(symbol)58 plainText += SYMBOLS[(symIndex - keyB) * modInverseOfKeyA %len(SYMBOLS)]59 else:60 plainText +=symbol61 returnplainText62

63 def getRandomKey():#可能不容易想到一个符合条件的秘钥。这时你可以选择用getRandomKey() 函数自动生成一个秘钥

64 whileTrue: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) +keyB69

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 importaffineCipher ,detectEnglish , cryptomath2 SILENT_MODE =False3

4 defmain():5 myMessage = """U&'<3dJ^Gjx'-3^MS'Sj0jxuj'G3'%j'

6 hackedMessage =hackAffine(myMessage)7

8 if hackedMessage !=None:9 printhackedMessage10 else:11 print 'Failed to hack encryption.'

12

13 defhackAffine(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 notSILENT_MODE:21 print 'Tried key %s ... (%s)'%(key , decryptedText)22

23 ifdetectEnglish.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 returndecryptedText33 returnNone34

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 importsys , random2

3 LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

4

5 defmain():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 printtranslated20 print ''

21 print 'the decrypted message is :'

22 printdecryptMessage(myKey , translated)23

24 defcheckValidKey(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 defencryptMessage(key , message):33 return translateMessage(key , message , 'encrypt')34 defdecryptMessage(key , message):35 return translateMessage(key , message , 'decrypt')36

37 deftranslateMessage(key , message , mode):38 translated = ''

39 charsA =LETTERS40 charsB =key41 if mode == 'encrypt':42 charsA , charsB =charsB , charsA43 for symbol inmessage:44 if symbol.upper() incharsA:45 symIndex =charsA.find(symbol.upper())46 ifsymbol.isupper():47 translated +=charsB[symIndex]48 else:49 translated +=charsB[symIndex].lower()50 else:51 translated +=symbol52 returntranslated53

54 defgetRandomKey():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 importpprint3 def getWordPattern(word): #返回单词模式

4 word =word.upper()5 nextNum =06 letterNums ={}7 wordPattern =[]8

9 for letter inword:10 if letter not inletterNums:11 letterNums[letter] =str(nextNum)12 nextNum += 1

13 wordPattern.append(letterNums[letter])14 return '.'.join(wordPattern)15

16 defmain():17 allPatterns ={}18 fo = open('dictionary.txt')19 wordList = fo.read().split('\n')20 fo.close()21

22 for word inwordList:23 pattern =getWordPattern(word)24

25 if pattern not inallPatterns: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 文件。里面以字典的形式保存了各种不同的单词模式所对应的单词。你可以用编辑器打开看一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值