python 密码学编程 -- 2
接上一篇随笔
********************************************************************
* quote : "http://inventwithpython.com/" *
* python-version : 2.7.11 *
********************************************************************
1.通过编程来检测英文
计算机无法理解英文。但是和乱码相比,英文是由可以在字典里找到的单词组成的。
我们可以通过判断一个单词是不是存在于某个字典来判断这个单词是不是英文。
当然,可能我们的字典不够大,导致某些正确的英文单词被判断成乱码。或者乱码碰巧刚好是一个英文单词。
但是我们说。在字典足够大且单词多为常用词的情况下,发生这些事的概率是比较小的。
我们说,当一个字符串中的大多数参数都被包含在这个英文字典里,我们就说这是一个英文字符串。
这里提供一本英文字典:http://invpy.com/dictionary.txt
要运行下面这个程序,你需要下载这个字典并且和 py 文件放在同一个目录下
detectEnglish.py
#-*-coding:utf-8-*-
UPPERLETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + ' \t\n' def loadDictionary(): #返回一个dict。其中包含了dictionary.txt 中的所有单词
dictionaryFile = open("dictionary.txt")
englishWords = {}
for word in dictionaryFile.read().split('\n'):
englishWords[word] = None
dictionaryFile.close()
return englishWords ENGLISH_WORDS = loadDictionary() def removeNonLetters(message): #只保留原字符串中的英文和空格部分
lettersOnly = []
for symbol in message:
if symbol in LETTERS_AND_SPACE:
lettersOnly.append(symbol)
return ''.join(lettersOnly) def getEnglishcount(message): #返回一个占比值。即这个message中的英文单词占(百分)比多少
message = message.upper()
message = removeNonLetters(message)
possibleWords = message.split() #预处理。得到原message中所有单词组成的列表 if possibleWords == []:
return 0.0 matches = 0
for word in possibleWords:
if word in ENGLISH_WORDS:
matches += 1
return float(matches) / len(possibleWords) def isEnglish(message , wordPercentage = 20, letterPercentage = 85):
wordsMatch = getEnglishcount(message) * 100 >= wordPercentage
numLetters = len(removeNonLetters(message))
messageLettersPercentage = float(numLetters) / len(message) *100
lettersMatch = messageLettersPercentage >= letterPercentage
return wordsMatch and lettersMatch
具体实现。我们把一个字符串删除数字和特殊符号之后分成一个一个单词的列表。
当这些单词中至少有20%是被包含在字典中。
并且原字符串至少85%是英文字母或者空格时(非数字和特殊字符)时。
我们就判断这个字符串是英文消息。
2.暴力破译换位加密法
从理论上来说,当要加密的消息为 message 时,换位加密法可能的秘钥有 len(message)-2 个(从 2 到 len(message)-1)
对于每一个用可能的秘钥破译出来的明文,我们需要判断这个明文是不是英文消息,从而判断破译是否成功。
于是。我们可以和上一个程序联系起来。让电脑自动判断破译出来的消息是否为英文。
transpositionHacker.py
#-*-coding:utf-8-*-
import detectEnglish , transpositionDecrypt def main():
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"""
hackedMessage = hackTransposition(myMessage) if hackedMessage == None:
print 'Failed to hack encryption'
else:
print 'Copying hacked message to clipboard:'
print hackedMessage def hackTransposition(message):
print 'Hacking' for key in xrange(1 , len(message)):
print 'Trying key #%s ...'%(key)
decryptedText = transpositionDecrypt.decryptMessage(key , message)
if detectEnglish.isEnglish(decryptedText):
print ''
print 'Possible encryption hack : '
print 'Key %s : %s'%(key ,decryptedText[:100])
print ''
print 'If you think the message is Ok , Enter D for done , or just press Enter to continue hacking :'
resp = raw_input('>>>') if resp.strip().upper().startswith('D'):
return decryptedText
return None if __name__ == '__main__':
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
#cryptomath.py
#-*-coding:utf-8-*-
def gcd(a , b): #求最大公约数
while a != 0:
a ,b = b%a , a
return b def findModInverse(a , m): #欧几里得 扩展算法求模逆
if gcd(a , m) != 1:
return None
u1 , u2 , u3 = 1 , 0 , a
v1 , v2 , v3 = 0, 1, m
while v3 != 0:
q = u3 / v3
v1 , v2 , v3 , u1 , u2 , u3 = (u1 - q * v1) , (u2 - q * v2) , (u3 - q * v3) , v1 , v2 , v3
return u1 % m
算法详见百度。
affineCipher.py
#-*-coding:utf-8-*-
import sys , cryptomath , random
SYMBOLS = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" def main():
myMessage = """"A computer would deserve to be called intelligent if it could deceive a human into believing that it was human." -Alan Turing"""
myKey = 2023
myMode = 'encrypt' if myMode == 'encrypt':
translated = encryptMessage(myKey , myMessage)
elif myMode == 'decrypt':
translated = decryptMessage(myKey , myMessage)
print 'Key : %s'%(myKey)
print '%sed text :'%(myMode.title())
print translated
print ''
translated = decryptMessage(myKey , translated)
print 'Decrypted test :'
print translated def getKeyParts(key): #由一个秘钥得到两部分秘钥
keyA = key/len(SYMBOLS)
keyB = key%len(SYMBOLS)
return (keyA , keyB) def checkKeys(keyA , keyB , mode):
if keyA == 1 and mode == 'encrypt': #keyA 为 1 时,秘钥太弱
sys.exit("key A can't be 1.Choose a different key.")
elif keyB == 0 and mode == 'encrypt': #keyB 为 0 时,秘钥太弱
sys.exit("key B can't be 0.Choose a different key.")
elif keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) -1:#限定秘钥范围。其中keyA 大于0 , 0 < keyB < len(SYMBOLS)
sys.exit('Key A must be greater than 0 and key B must be between 0 and %s')%(len(SYMBOLS))
elif cryptomath.gcd(keyA , len(SYMBOLS)) != 1: #前面说秘钥keyA 必须与符号集的大小互质
sys.exit('key A and the symbol set size are not relatively prime.Choose a different key')
#加密解密过程均是找到对应关系
def encryptMessage(key , message):
keyA , keyB = getKeyParts(key)
checkKeys(keyA, keyB , 'encrypt')
cipherText = ''
for symbol in message:
if symbol in SYMBOLS:
symIndex = SYMBOLS.find(symbol)
cipherText += SYMBOLS[(symIndex * keyA + keyB) % len(SYMBOLS)]
else:
cipherText += symbol
return cipherText def decryptMessage(key , message):
keyA , keyB = getKeyParts(key)
checkKeys(keyA, keyB , 'decrypt')
plainText = ''
modInverseOfKeyA = cryptomath.findModInverse(keyA , len(SYMBOLS)) for symbol in message:
if symbol in SYMBOLS:
symIndex = SYMBOLS.find(symbol)
plainText += SYMBOLS[(symIndex - keyB) * modInverseOfKeyA % len(SYMBOLS)]
else:
plainText += symbol
return plainText def getRandomKey():#可能不容易想到一个符合条件的秘钥。这时你可以选择用getRandomKey() 函数自动生成一个秘钥
while True:
keyA = random.randint(2 , len(SYMBOLS))
keyB = random.randint(2 , len(SYMBOLS))
if cryptomath.gcd(keyA , len(SYMBOLS)) == 1:
return keyA * len(SYMBOLS) + keyB if __name__ == '__main__':
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
import affineCipher ,detectEnglish , cryptomath
SILENT_MODE = False def main():
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"""
hackedMessage = hackAffine(myMessage) if hackedMessage != None:
print hackedMessage
else:
print 'Failed to hack encryption.' def hackAffine(message):
print 'Hacking ...'
for key in xrange(len(affineCipher.SYMBOLS) ** 2):
keyA = affineCipher.getKeyParts(key)[0]
if cryptomath.gcd(keyA , len(affineCipher.SYMBOLS)) != 1:
continue
decryptedText = affineCipher.decryptMessage(key , message)
if not SILENT_MODE:
print 'Tried key %s ... (%s)'%(key , decryptedText) if detectEnglish.isEnglish(decryptedText):
print ''
print 'Possible encryption hack :'
print 'Key " %s'%(key)
print 'Decrypted message :'+decryptedText[:200]
print ''
print 'Enter D for done , or just press Enter to continue hacking'
resp = raw_input('>>>')
if resp.strip().upper().startswith('D'):
return decryptedText
return None if __name__ == '__main__':
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
import sys , random LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' def main():
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'
myKey = 'LFWOAYUISVKMNXPBDCRJTQEGHZ'
myMode = 'encrypt' checkValidKey(myKey) if myMode == 'encrypt':
translated = encryptMessage(myKey , myMessage)
elif myMode == 'decrypt':
translated = decryptMessage(myKey , myMessage)
print 'Using key %s'%(myKey)
print ''
print 'The %sed message is : '%(myMode)
print translated
print ''
print 'the decrypted message is :'
print decryptMessage(myKey , translated) def checkValidKey(key):
keyList = list(key)
lettersList = list(LETTERS)
keyList.sort()
lettersList.sort()
if keyList != lettersList:
sys.exit('There is an error in the key or symbol set.') def encryptMessage(key , message):
return translateMessage(key , message , 'encrypt')
def decryptMessage(key , message):
return translateMessage(key , message , 'decrypt') def translateMessage(key , message , mode):
translated = ''
charsA = LETTERS
charsB = key
if mode == 'encrypt':
charsA , charsB = charsB , charsA
for symbol in message:
if symbol.upper() in charsA:
symIndex = charsA.find(symbol.upper())
if symbol.isupper():
translated += charsB[symIndex]
else:
translated += charsB[symIndex].lower()
else:
translated += symbol
return translated def getRandomKey():
key = list(LETTERS)
random.shuffle(key)
return ''.join(key) if __name__ == '__main__':
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
#-*-coding:utf-8-*-
import pprint
def getWordPattern(word): #返回单词模式
word = word.upper()
nextNum = 0
letterNums = {}
wordPattern = [] for letter in word:
if letter not in letterNums:
letterNums[letter] = str(nextNum)
nextNum += 1
wordPattern.append(letterNums[letter])
return '.'.join(wordPattern) def main():
allPatterns = {}
fo = open('dictionary.txt')
wordList = fo.read().split('\n')
fo.close() for word in wordList:
pattern = getWordPattern(word) if pattern not in allPatterns:
allPatterns[pattern] = [word]
else:
allPatterns[pattern].append(word) fo = open('wordPatterns.py','w')
fo.write('allPatterns = ')
fo.write(pprint.pformat(allPatterns))
fo.close() if __name__ == '__main__':
main()
这个程序会在本地生成一个 wordPatterns.py 文件。里面以字典的形式保存了各种不同的单词模式所对应的单词。你可以用编辑器打开看一下。
python 密码学编程 -- 2的更多相关文章
- python 密码学编程
最近在看一本书.名字是 python密码学编程.在此做一些笔记,同时也为有需要的人提供一些参考. *************************************************** ...
- 【python密码学编程】5.反转加密法
#Reverse Cipher message = 'there can keep a secret,if two of them are dead.' translated = '' i = len ...
- 【python密码学编程】8.使用换位加密法加密
替代加密法:用其他字符替代原有字符 换位加密法:搞乱字符顺序 [换位加密法]需要一个密钥 仅允许非商业转载,转载请注明出处
- 【python密码学编程】7.暴力破解凯撒加密法
# _*_ coding:utf-8 _*_ #Caeser Ciper import pyperclip messgae = 'GUVF VF ZL FRPERG ZRFFTNR.' nums = ...
- 【python密码学编程】6.凯撒加密法
凯撒加密法的迷药是0~25的整数 # _*_ coding:utf-8 _*_ #Caeser Ciper import pyperclip messgae = 'this is my secret ...
- 《Python游戏编程快速上手》|百度网盘免费下载|Python基础编程
<Python游戏编程快速上手>|百度网盘免费下载| 提取码:luy6 Python是一种高级程序设计语言,因其简洁.易读及可扩展性日渐成为程序设计领域备受推崇的语言. 本书通过编写一个个 ...
- Python Socket 编程——聊天室示例程序
上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的 ...
- python多线程编程
Python多线程编程中常用方法: 1.join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,那么在调用线程的时就可以使用被调线程的join方法join( ...
- python 面向对象编程学习
1. 问题:将所有代码放入一个py文件:无法维护 方案:如果将代码才分放到多个py文件,好处: 1. 同一个名字的变量互相不影响 2.易于维护 3.引用模块: import module 2.包:解决 ...
随机推荐
- 通过bin-log对mysql进行数据恢复
mysqlbinlog --database=数据库名 --start-date="2017-06-01 5:00:00" --stop-date="2017-06-1 ...
- 使用mongodb作为Quartz.Net下的JobStore实现底层的持久化机制
我们都知道默认的Quartz底层采用的是RAMJobStore,所有的Job,Trigger,Calendar都是用Dictionary,SortSet等等这样的数据结构进行储存,相对来说性能肯 定快 ...
- python爬虫从入门到放弃前奏之学习方法
首谈方法 最近在整理爬虫系列的博客,但是当整理几篇之后,发现一个问题,不管学习任何内容,其实方法是最重要的,按照我之前写的博客内容,其实学起来还是很点枯燥不能解决传统学习过程中的几个问题: 这个是普通 ...
- 如何查询oracle中的关键字
如何查询oracle中的关键字,执行: select * from v$reserved_words
- 搭建开源java博客并通过域名访问
这个博客系统是王爵在GitHub上开源的,通过简单几步就可以部署成功. 前面几步可以参照如下几个链接: 1.https://www.qcloud.com/community/article/29008 ...
- .Net程序调试与追踪的一些方法
前言 作为一个.net开发工程师,不管是在写桌面程序.服务程序或web程序,在开发阶段,我们必须非常熟悉vs的动态调试技能,当然web程序可能还需要调试前端的脚本或样式,这不在本文的讨论范围.本文主要 ...
- servlet+jsp导入Excel到mysql数据库
package khservlet; import java.io.FileInputStream;import java.io.IOException;import java.io.InputStr ...
- 在Visual Studio 2017中使用Asp.Net Core构建Angular4应用程序
前言 Visual Studio 2017已经发布了很久了.做为集成了Asp.Net Core 1.1的地表最强IDE工具,越来越受.NET系的开发人员追捧. 随着Google Angular4的发布 ...
- ABP入门系列(20)——使用后台作业和工作者
ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1.引言 说到后台作业,你可能条件反射的想到BackgroundWorker,但后台作业并非是后台 ...
- Android系统--输入系统(十三)Dispatcher线程情景分析_Reader线程传递事件
Android系统--输入系统(十三)Dispatcher线程情景分析_Reader线程传递事件 1. 输入按键 我们知道Android系统的按键分为三类:(1)Global Key;(2)Syste ...