添加 quip.py

main
amuliang 2023-12-06 08:18:18 +00:00
parent 4ad6e02d2d
commit 0061982e38
1 changed files with 245 additions and 0 deletions

245
quip.py 100644
View File

@ -0,0 +1,245 @@
import random
import string
import math
import sys
# 思路
# 第一步:从长度最小的单词开始匹配,初步得到大致的映射结果
# 第二步:找到只剩一个字母没有映射关系的单词,用未映射的字母进行替换再与词库比较,且优先处理长单词,如果能与词库匹配,则可以反向修改第一步得到的映射关系
# 第三步:从匹配好的单词中随机取出一部分,作为已知初始值,再次执行前两步。直到达到最优解
# 词库目前最大单词长度只能到6并且越长的单词在词库中其实越不全因为单词太多了这里为了效率只包含了常用单词
words = [
[],
['a', 'i'],
['is', 'if', 'an', 'am', 'to', 'us', 'go', 'be', 'he', 'up', 'hi', 'as', 'at', 'by', 'do', 'in', 'it', 'me', 'my', 'no', 'of', 'on', 'or', 'so', 'to', 'up', 'us', 'we'],
['act','add','age','ago','aid','aim','air','all','and','ant','any','apt','are','arm','art','ash','ask','awe','bad','bag','ban','bar','bat','bay','bed','bee','beg','bet','big','bin','bit','bow','box','boy','bud','bug','bus','but','buy','can','cap','car','cat','cop','cow','cry','cue','cup','cut','dam','day','die','eye','ebb','dog','egg','dry','end','dip','dim','dew','dye','car','eat','duc','ego','dig','dot','ear','eve','fan','far','fat','fax','fee','few','fit','fix','flu','fly','fog','for','fox','fry','fun','fur','gay','get','god','got','gum','gun','gut','guy','ham','hat','hay','hen','her','him','hip','his','hit','hop','hot','how','hug','hum','hut','icy','ill','ink','ion','its','jaw','jet','job','jog','joy','jug','key','kid','kin','kit','lab','lad','lag','lap','law','lay','leg','let','lid','lie','log','lot','low','mad','man','map','mat','may','mix','mob','mud','mug','nap','net','ncw','nod','nor','not','now','nut','oak','oar','odd','off','oil','old','one','opt','orc','our','out','owe','owl','own','pad','pan','pat','paw','pay','pea','pen','per','pet','pie','pig','pin','pit','pop','pot','pub','put','rag','rap','rat','raw','ray','red','rib','rid','rob','rod','rot','row','rub','run','sad','saw','say','sea','set','sew','sex','she','sit','six','ski','sky','son','sow','spy','sue','sum','sun','tag','tap','tar','tax','tea','ten','the','tie','tim','tin','tip','toe','ton','too','top','tow','toy','try','tub','tug','two','use','van','via','war','wax','way','web','wet','who','why','win','won','yes','yet','you','zip','zoo'],
['able','ache','acid','acre','ally','also','amid','arch','area','army','atom','aunt','auto','away','axis','baby','back','bait','bake','bald','ball','band','bang','bank','bare','bark','barn','base','bath','beam','bean','bear','beat','beef','beer','bell','belt','bend','best','bias','bike','bill','bind','bird','bite','blow','blue','blur','boat','body','boil','bold','bolt','bomb','bond','bone','book','boom','boot','bore','born','boss','both','bowl','brim','brow','bulb','bulk','bull','burn','bury','bush','busy','cafe','cage','cake','call','calm','camp','cape','card','care','cart','case','cash','cast','cave','chap','chat','chef','chew','chin','chip','chop','cite','city','clap','claw','clay','clip','club','clue','coal','coat','cock','code','coil','coin','coke','cold','comb','come','cook','cool','cope','copy','cord','corn','cost','cosy','crew','crop','crow','cube','curb','cure','curl','dare','data','date','dead','desk','dock','door','down','else','exam','damp','dear','damn','dull','dash','dove','duck','dome','dorm','deny','dean','disc','edge','defy','dump','draw','doom','drum','deem','emit','drip','drop','diet','dine','dish','ease','dive','dark','dawn','dusk','dirt','dust','drag','envy','doze','dose','drug','doll','duty','deal','debt','deck','deep','dial','earn','east','easy','echo','edit','even','ever','exit','facc','fact','fade','fail','fair','fake','fall','fare','farm','fast','fate','fear','feat','feed','feel','fell','file','fine','fire','firm','fish','fist','five','flag','flap','flat','flaw','flee','flow','foam','fold','food','fool','foot','fore','form','foul','four','from','fuel','full','fume','gage','gain','game','gang','gaol','gasp','gate','gaze','gear','give','glad','gold','golf','good','gown','grab','gray','grip','grow','gulf','hail','hair','half','hall','halt','hand','hang','hard','harm','hate','haul','have','hawk','head','heal','heap','hear','heel','heir','hell','help','herb','here','hero','hers','hide','high','hike','hill','hint','hire','hold','hole','holy','home','hope','horn','hose','host','hour','howl','huge','hunt','hurl','idea','idle','inch','into','iron','isle','item','jail','jazz','join','joke','july','jump','june','junk','kill','kilo','kind','king','kite','knot','lace','lack','lady','lake','lamb','lame'],
['based','hello','world','marks','words','works','essay','first','going','start','often','found','helps','money','child','large','early','grain','still','woman','given','place','leave','right','third','later','short','exact','least','might','rules','small','water','night','watch','kodak','sales','women','among','bring','cause','check','looks','meals','since','tests','today','young','begin','clock','every','limit','older','sense','break','force','hours','means','needs','plans','point','visit','apple','clear','close','comet','feels','focus','local','never','seems','shows','using','asked','gives','known','nerds','plays','prove','shift','argue','basic','ideas','knows','lower','rates','books','built','count','daily','field','forms','greek','homes','level','match','moral','raise','share','taken','terms','treat','types','wants','wheat','fresh','goals','parts','pause','reach','serve','tends','turns','weeks','began','boost','brain','carry','comes','drugs','earth','firms','guide','kinds','light','minor','moved','movie','north','notes','phone','piece','range','ready','space','speed','staff','tasks','trips','views','worth','wrong','agree','along','aside','carol','cases','china','delay','dream','games','grant','hopes','hurry','imply','india','lines','loans','lunch','minds','model','music','occur','ocean','peace','power','smart','smith','stand','taste','total','trade','waste','admit','agent','alarm','angry','areas','aware','blind','board','chief','climb','crops','deals','death','dirty','doubt','drawn','eight','ended','event','fault','fired'],
['aboard','abound','abroad','accept','access','accord','across','action','active','actual','adjust','advice','advise','affect','afraid','almost','always','amount','anchor','animal','answer','anyhow','anyone','anyway','appear','around','arrive','asleep','aspect','assert','assess','assign','assist','attach','attack','august','author','ballot','banana','banner','barber','basket','battle','beauty','become','before','behave','behind','belief','belong','better','beyond','border','borrow','chance','change','charge','circle','choice','choose','commit','common','custom','danger','delete','eighty','either','excuse','degree','extent','effect','enough','double','during','etitor','engine','except','factor','export','format','future','handle','happen','ignore','insert','lately','letter','letter','member','memory','method','middle','moment','modify','myself','native','nature','nearby','nearly','normal','office','origin','people','permit','person','please','pretty','prefer','public','rabbit','random','rather','reader','recent','record','reduce','remote','remove','render','repair','report','result','return','review','reward','rotate','sample','saving','school','scream','screen','script','search','season','second','select','serial','shadow','simple','single','social','source','speech','strict','strong','summit','sunday','switch','symbol','system','target','theirs','thirst','though','thread','tunnel','unable','unique','unless','update','verify','window','wonder','worker','worthy','writer','yearly']
]
# 修补单词,单词中只有一个*号,使用没被匹配过的字母替换,看是否能跟词库匹配
def fixword(test_items, test_words, indexs, mapped1, mapped2):
for i in indexs:
item = test_words[i]
star_index = str(item).index('*')
cipher_char = test_items[i][star_index]
item_len = len(item)
b = False
for c in range(ord('a'), ord('z')+1):
if mapped2[c] == '*':
word = str(item).replace('*', chr(c))
if word in words[item_len]:
# 这里不判断是否已经映射过了所以一定会产生覆盖原有映射的情况。因为很大概率只剩一个字母的是对的这句话应该是改善性能的核心代码。if mapped1[ord(cipher_char)] == '*':
mapped1[ord(cipher_char)] = chr(c)
mapped2[c] = cipher_char
test_words[i] = word
b = True
if not b:
test_words[i] = str(item).replace('*', '?')
# 将密文与单词进行匹配
def matchword(ii, test_items, item, word, mapped1, mapped2, matches, info):
item_len = len(item)
for i in range(item_len):
item_int = ord(item[i])
word_int = ord(word[i])
if mapped1[item_int] == '*':
if mapped2[word_int] != '*': # 如果密文没有映射过,但是该密文对应的明文却已经被映射了,说明存在问题
return
else:
# 添加新的映射关系
mapped1[item_int] = word[i]
mapped2[word_int] = item[i]
elif mapped1[item_int] == word[i]: # 已经有映射的值了,就跳过
continue
else: # 如果与之前匹配的不相符,可以尝试更新值,会影响之前匹配好的词,暂时先这么运行试试
# if item_len > 2:
# mapped11 = mapped1[:]
# mapped22 = mapped2[:]
# mapped11[item_int] = word[i]
# mapped22[item_int] = item[i]
# matchitem(ii+1, test_items, mapped11, mapped22, matches, info)
return
matchitem(ii+1, test_items, mapped1, mapped2, matches + [word], info)
# 通过映射好的关系,渲染密文
def rendercontent(content, mapped1):
result = ''
for c in content.lower():
if c in string.ascii_lowercase:
result += mapped1[ord(c)]
else:
result += c
return result
def renderlist(items, mapped1):
content = ' '.join(items)
result = rendercontent(content, mapped1)
splited = result.split(' ')
return splited
# 过滤出来只含有一个*号单词的的序号,且保证长单词在前
def get_one_star_word_index(test_words):
indexs = []
for i in range(len(test_words)):
item = test_words[i]
if str(item).count('*') == 1:
indexs += [i]
return indexs[::-1] # 逆序,优先匹配长度长的,因为长度长的能匹配到的话,那么是正确单词的概率会比较到
# 匹配密文项
def matchitem(ii, test_items, mapped1, mapped2, matches, info):
# 第一步处理正向与词库进行匹配。对密文单词逐个进行匹配这里不是for循环而是通过ii参数代表index进行的递归操作
if ii < len(test_items):
item = test_items[ii]
# 使用词库中的每个单词来匹配密文
for word in words[len(item)]:
if word in matches: # 如果词库中的单词被匹配过则跳过
continue
# 对密文和单词之间进行精细匹配操作
matchword(ii, test_items, item, word, mapped1[:], mapped2[:], matches, info)
else:
# 第二步处理:反向修补单词,可修改映射关系。找到哪些词语就差一个字母,连续遍历匹配,匹配就建立新的映射关系,并且可以覆盖上一步递归得到的映射关系
temp_indexs = []
while True:
test_words = renderlist(info['sorted_items'], mapped1)
indexs = get_one_star_word_index(test_words)
if len(indexs) > 0 and str(indexs) != str(temp_indexs):
fixword(info['sorted_items'], test_words, indexs, mapped1, mapped2)
temp_indexs = indexs
else: # 至此说明已经没有可以通过词库匹配的单词了
break
# 计算匹配数
parsed = renderlist(info['sorted_items'], mapped1) # 对指定的单词进行渲染
content_parsed = rendercontent(info['content'], mapped1) # 对全文进行渲染
num = 0 # 匹配到的单词数量
mw = '' # 匹配到的单词
for item in parsed:
if item in words[len(item)]:
num += 1
mw += item + ' '
# 记录最优解
if num > info['best_num']:
info['best_num'] = num
info['best_mapped'] = mapped1
info['best_parsed'] = parsed
info['best_content_parsed'] = content_parsed
info['best_matched_words'] = mw
# 限定只有在于词库匹配数量达到最优解的时候才打印输出
if num == info['best_num'] and mw != info['best_matched_words']:
print('=================================================================')
print('映射关系为:')
printmapped(mapped1)
print('匹配单词量:' + str(num))
print('匹配单词为:' + mw)
print('解密结果为:' + content_parsed)
# 打印映射关系
def printmapped(mapped1):
temp = ' 密文 -> '
for c in range(ord('a'), ord('z')+1):
temp += chr(c) + ' '
print(temp)
temp = ' 明文 -> '
for c in range(ord('a'), ord('z')+1):
temp += mapped1[c] + ' '
print(temp)
def modifymapped(c, m, mapped1, mapped2):
for i in range(len(c)):
mapped1[ord(c[i])] = m[i]
mapped2[ord(m[i])] = c[i]
def tokeyvalue(data, c, m):
for i in range(len(c)):
data[c[i]] = m[i]
# 开始匹配
def domatch(content, separator, known_item, matched_num):
# 对内容预处理单词分割去重按长度从小到大排序取出单词长度小于7的单词与词库进行匹配
items = content.lower().split(separator) # 分割
sorted_items = list(set(items)) # 去重
sorted_items = [i for i in sorted_items if len(i) < 7] # 取出单词长度小于7的
sorted_items = list(sorted(sorted_items, key=lambda s: len(s))) # 排序
print('=================================================================')
print('密文统计:')
print(sorted_items)
# 为了保证处理速度限定最大处理单词量为20。如果单词数小于20则所有单词都处理sorted_items是所有可与词库匹配的单词长度小于6test_items是实际破解过程中使用的单词不超过20个
test_items = sorted_items
items_len = len(test_items)
max_len = 20
if items_len > max_len:
items_len = max_len
test_items = test_items[0:max_len]
mapped1 = ['*']*128 # 构建两个映射数组,可以双向寻找映射关系
mapped2 = ['*']*128
# 已知匹配
for key in known_item:
mapped1[ord(key)] = known_item[key]
mapped2[ord(known_item[key])] = key
# 尝试匹配
info = {
'content': content, # 密文
'separator': separator, # 分隔符
'sorted_items': sorted_items, # 所有长度小于7的单词
'best_mapped': None, # 最优解的映射关系
'best_num': 0, # 最优解匹配的单词数量
'best_matched_words': None, # 最优解匹配到的单词
'best_parsed': None, # 数组最优解解析到的结果与sorted_items对应
'best_content_parsed': '' # 字符串最优解对密文解析后的结果与content对应
}
matchitem(0, test_items, mapped1, mapped2, [], info)
# 再加一步,线性回归优化。对上一步的处理结果,随机取出匹配到的单词作为初始已知条件,然后再进行密文匹配,直到达到最优解
best_num = info['best_num']
if best_num > matched_num:
print('=============正在尝试优化=====================================')
steps = math.ceil(math.sqrt(best_num))
for i in range(steps):
matched_words = str(info['best_matched_words']).strip().split(separator)
random_indexs = random.choices(range(len(matched_words)), k=6)
new_known_item = {}
for index in random_indexs:
item = sorted_items[index]
word = matched_words[index]
for i in range(len(item)):
new_known_item[item[i]] = word[i]
for key in known_item:
new_known_item[key] = known_item[key]
domatch(content, separator, known_item, best_num)
# 最终结果打印
print('=================================================================')
print('映射关系为:')
printmapped(info['best_mapped'])
print('匹配单词量:' + str(info['best_num']))
print('匹配单词为:' + info['best_matched_words'])
print('解密结果为:' + info['best_content_parsed'])
print('=================================================================')
# =================================================================================================================
# 密文内容
content = 'VIZZB IFIUOJBWO NVXAP OBC XZZ UKHVN IFIUOJBWO HB XVIXW XAW VXFI X QIXN VBD KQ IFIUOJBWO WBKAH NBWXO VBD XJBCN NKG QLKEIU DI XUI VIUI DKNV QNCWIANQ XN DXPIMKIZW VKHV QEVBBZ KA XUZKAHNBA FKUHKAKX XAW DI VXFI HBN QNCWIANQ NCAKAH KA MUBG XZZ XEUBQQ XGIUKEX MUBG PKAWIUHXUNIA NVUBCHV 12NV HUXWI XAW DI XUI SCQN QB HZXW NVXN XZZ EBCZW SBKA CQ NBWXO XAW DI DXAN NB NVXAP DXPIMKIZW MBU JIKAH QCEV XA BCNQNXAWKAH VBQN HKFI OBCUQIZFIQ X JKH UBCAW BM XLLZXCQI XAW NVI PIO KQ 640I11012805M211J0XJ24MM02X1IW09'
# 单词分隔符
separator = ' '
# 已知映射关系,键代表密文,值代表明文
known_item = {
# 'x': 'a'
}
# 执行破解
if sys.argv > 1:
content = sys.argv[1]
domatch(content, separator, known_item, 0)