使用Z3破解简单的XOR加密

翻译:无名侠

原文地址: https://yurichev.com/blog/XOR_Z3/

如果我们有一段用简单XOR加密过的文本,怎么寻找密钥呢?密钥的长度可能很长,所以暴力破解不是明智的选择。

明文、密文、密钥三者的关系可以用一些简单的方程组来描述,明文可能含有尽可能多的小写字母(a....z)。

import sys, hexdump
from z3 import * def xor_strings(s,t):
# https://en.wikipedia.org/wiki/XOR_cipher#Example_implementation
"""将两个字符串进行xor操作"""
return "".join(chr(ord(a)^ord(b)) for a,b in zip(s,t)) def read_file(fname):
file=open(fname, mode='rb')
content=file.read()
file.close()
return content def chunks(l, n):
"""将输入的数据分成n组"""
n = max(1, n)
return [l[i:i + n] for i in range(0, len(l), n)] def print_model(m, KEY_LEN, key):
# 从model中解析key(model()函数返回的是解
test_key="".join(chr(int(obj_to_string(m[key[i]]))) for i in range(KEY_LEN))
print "key="
hexdump.hexdump(test_key) # 使用解出来的key解密数据:
tmp=chunks(cipher_file, KEY_LEN)
plain_attempt="".join(map(lambda x: xor_strings(x, test_key), tmp))
print "plain="
hexdump.hexdump(plain_attempt) def try_len(KEY_LEN, cipher_file):
cipher_len=len(cipher_file)
print "len=", KEY_LEN
s=Optimize() # key的每一个字节对应一个未知数变量:
key=[BitVec('key_%d' % i, 8) for i in range (KEY_LEN)]
# 输入密文的每一个字节对应一个未知数变量:
cipher=[BitVec('cipher_%d' % i, 8) for i in range (cipher_len)]
# 每一个明文字节对应的未知数变量:
plain=[BitVec('plain_%d' % i, 8) for i in range (cipher_len)]
# 纯文本的每个字节的变量:1如果字节在'a'…“z”范围:
az_in_plain=[Int('az_in_plain_%d' % i) for i in range (cipher_len)] for i in range(cipher_len):
# 根据输入的密文数据给每一个密文字节变量赋值
s.add(cipher[i]==ord(cipher_file[i]))
# plain text is cipher text XOR-ed with key:
# 明文等于密文xor key
s.add(plain[i]==cipher[i]^key[i % KEY_LEN])
# 明文的每一个字节的应该是可以打印的字符,或CR 或 LF
s.add(Or(And(plain[i]>=0x20, plain[i]<=0x7E),plain[i]==0xA,plain[i]==0xD))
# 如果是小写字母则为1,否则为0.
s.add(az_in_plain[i]==If(And(plain[i]>=ord('a'),plain[i]<=ord('z')), 1, 0)) # 寻找小写字母个数最多的解。
s.maximize(Sum(*az_in_plain)) if s.check()==unsat:
return
m=s.model()
print_model(m, KEY_LEN, key) cipher_file=read_file (sys.argv[1]) for i in range(1,20):
try_len(i, cipher_file) #try_len(17, cipher_file)

( https://github.com/DennisYurichev/yurichev.com/blob/master/blog/XOR_Z3/1.py )

这里有一段需要被加密过的文件(350 字节):https://github.com/DennisYurichev/yurichev.com/blob/master/blog/XOR_Z3/cipher1.txt

运行尝试。

% python 1.py cipher1.txt
len= 1
len= 2
len= 3
len= 4
len= 5 ... len= 16
len= 17
key=
00000000: 90 A0 21 52 48 84 FB FF 86 83 CF 50 46 12 7A F9 ..!RH......PF.z.
00000010: 36 6
plain=
00000000: 4D 72 2E 22 54 63 65 72 6F 6F 63 6D 27 48 6F 6C Mr."Tceroocm'Hol
00000010: 6A 65 73 2C 22 70 63 6F 20 74 61 73 26 72 73 75 jes,"pco tas&rsu
00000020: 61 6B 6C 79 20 74 62 79 79 20 6F 61 74 63 27 69 akly tbyy oatc'i
00000030: 6E 20 73 68 65 20 6F 68 79 6E 69 6D 67 73 2A 27 n she ohynimgs*'
00000040: 73 61 76 62 0D 0A 75 72 68 65 20 74 6B 6F 73 63 savb..urhe tkosc
00000050: 27 6E 6F 74 27 69 6E 66 70 62 7A 75 65 6D 74 20 'not'infpbzuemt
00000060: 69 64 63 61 73 6E 6F 6E 73 22 70 63 65 6E 23 68 idcasnons"pcen#h
00000070: 65 26 70 61 73 20 72 70 20 61 6E 6B 2B 6E 69 64 e&pas rp ank+nid
00000080: 68 74 2A 27 77 61 73 27 73 65 61 76 62 6F 0D 0A ht*'was'seavbo..
00000090: 62 74 20 72 6F 65 20 62 75 65 61 6B 64 66 78 74 bt roe bueakdfxt
000000A0: 20 77 61 62 6A 62 2E 20 49 27 73 74 6F 6D 63 2B wabjb. I'stomc+
000000B0: 75 70 6C 6E 20 72 6F 65 20 68 62 61 72 74 6A 2A upln roe hbartj*
000000C0: 79 75 67 23 61 6E 62 27 70 69 63 6C 65 64 20 77 yug#anb'picled w
000000D0: 77 2B 74 68 66 0D 0A 75 73 69 63 6B 27 77 68 69 w+thf..usick'whi
000000E0: 61 6F 2B 6F 75 71 20 76 6F 74 69 74 6F 75 20 68 ao+ouq votitou h
000000F0: 61 66 27 67 65 66 77 20 62 63 6F 69 6E 64 27 68 af'gefw bcoind'h
00000100: 69 6D 22 73 63 65 20 6D 69 67 6E 73 20 62 65 61 im"sce migns bea
00000110: 6F 72 65 2C 27 42 74 20 74 61 73 26 66 0D 0A 66 ore,'Bt tas&f..f
00000120: 6E 6E 65 2C 22 73 63 69 63 68 20 70 6F 62 63 65 nne,"scich pobce
00000130: 20 68 66 20 77 6D 68 6F 2C 20 61 75 6C 64 68 75 hf wmho, auldhu
00000140: 73 2D 6F 65 61 64 67 63 27 20 6F 65 20 74 6E 62 s-oeadgc' oe tnb
00000150: 20 73 6F 75 74 20 77 6A 6E 68 68 20 6A 73 sout wjnhh js
len= 18
len= 19

这段文本并不能流畅地阅读,有趣的是,只存在17字节的key。

我们对英语文本了解多少? 数字在文本中很稀少,有一种叫做“二合字母”的东西,比如:“th”、“he”、“in”、“er” 等等,我们可以通过计算它们在文本中出现的频率,然后寻找出现次数最多的那一组解。

...
# 定义变量
# 对于每一个明文的字节变量: 1 如果这个字节是数字符号:
digits_in_plain=[Int('digits_in_plain_%d' % i) for i in range (cipher_len)]
# 对于每一个明文的字节变量: 1 如果当前字节 + 下一字节恰好等于‘th’:
th_in_plain=[Int('th_in_plain_%d' % i) for i in range (cipher_len-1)]
# ... 同理 ‘he’:
he_in_plain=[Int('he_in_plain_%d' % i) for i in range (cipher_len-1)]
in_in_plain=[Int('in_in_plain_%d' % i) for i in range (cipher_len-1)]
er_in_plain=[Int('er_in_plain_%d' % i) for i in range (cipher_len-1)] ... for i in range(cipher_len-1):
# 约束条件
# ... 对于每一个明文的字节变量: 1 如果当前字节 + 下一字节恰好等于‘th’:
s.add(th_in_plain[i]==(If(And(plain[i]==ord('t'),plain[i+1]==ord('h')), 1, 0)))
# ... etc:
s.add(he_in_plain[i]==(If(And(plain[i]==ord('h'),plain[i+1]==ord('e')), 1, 0)))
s.add(in_in_plain[i]==(If(And(plain[i]==ord('i'),plain[i+1]==ord('n')), 1, 0)))
s.add(er_in_plain[i]==(If(And(plain[i]==ord('e'),plain[i+1]==ord('r')), 1, 0))) # 寻找小写字母个数最多的解。
s.maximize(Sum(*az_in_plain))
# ... 且数字出现次数最少的解
s.minimize(Sum(*digits_in_plain)) # "th", "he", "in" , "er" 出现次数最多的解
s.maximize(Sum(*th_in_plain))
s.maximize(Sum(*he_in_plain))
s.maximize(Sum(*in_in_plain))
s.maximize(Sum(*er_in_plain)) ...

( https://github.com/DennisYurichev/yurichev.com/blob/master/blog/XOR_Z3/2.py )

现在我们再从次执行脚本,好像更接近原文了

len= 17
key=
00000000: 90 A0 22 50 4F 8F FB FF 85 83 CF 56 41 12 7A FE .."PO......VA.z.
00000010: 31 1
plain=
00000000: 4D 72 2D 20 53 68 65 72 6C 6F 63 6B 20 48 6F 6B Mr- Sherlock Hok
00000010: 6D 65 73 2F 20 77 68 6F 20 77 61 73 20 75 73 75 mes/ who was usu
00000020: 66 6C 6C 79 23 76 65 72 79 20 6C 61 74 65 20 69 flly#very late i
00000030: 6E 27 74 68 65 23 6D 6F 72 6E 69 6E 67 73 2C 20 n'the#mornings,
00000040: 73 61 71 65 0D 0A 76 70 6F 6E 20 74 68 6F 73 65 saqe..vpon those
00000050: 20 6E 6F 73 20 69 6E 65 72 65 71 75 65 6E 74 20 nos inerequent
00000060: 6F 63 63 61 74 69 6F 6E 70 20 77 68 65 6E 20 68 occationp when h
00000070: 65 20 77 61 73 27 75 70 20 62 6C 6C 20 6E 69 67 e was'up bll nig
00000080: 68 74 2C 20 77 61 74 20 73 65 62 74 65 64 0D 0A ht, wat sebted..
00000090: 61 74 20 74 68 65 20 65 72 65 61 68 66 61 73 74 at the ereahfast
000000A0: 20 74 61 62 6C 65 2E 20 4E 20 73 74 6C 6F 64 20 table. N stlod
000000B0: 75 70 6F 6E 20 74 68 65 20 6F 65 61 72 77 68 2D upon the oearwh-
000000C0: 72 75 67 20 61 6E 64 20 70 69 64 6B 65 64 23 75 rug and pidked#u
000000D0: 70 20 74 68 65 0D 0A 73 74 69 63 6C 20 77 68 6A p the..sticl whj
000000E0: 63 68 20 6F 75 72 20 76 69 73 69 74 68 72 20 68 ch our visithr h
000000F0: 62 64 20 6C 65 66 74 20 62 65 68 69 6E 63 20 68 bd left behinc h
00000100: 69 6E 20 74 68 65 20 6E 69 67 68 74 20 62 62 66 in the night bbf
00000110: 6F 72 66 2E 20 49 74 20 77 61 73 20 61 0D 0A 61 orf. It was a..a
00000120: 69 6E 65 2F 20 74 68 69 63 6B 20 70 69 65 63 65 ine/ thick piece
00000130: 27 6F 66 20 74 6F 6F 64 2C 20 62 75 6C 62 6F 75 'of tood, bulbou
00000140: 73 2A 68 65 61 67 65 64 2C 20 6F 66 20 74 68 65 s*heaged, of the

有几个字符还是有错误的,但是我们可以手动修复它们,因此添加限制条件。(不直接改原文的原因是我们的目标是寻找key,所以通过增加确定性限制条件重新求解key)

...
# 3 known characters of plain text:
s.add(plain[0xf]==ord('l'))
s.add(plain[0x20]==ord('a'))
s.add(plain[0x57]==ord('f'))
...

( https://github.com/DennisYurichev/yurichev.com/blob/master/blog/XOR_Z3/3.py )

这一次key正确了:

len= 17
key=
00000000: 90 A0 21 50 4F 8F FB FF 85 83 CF 56 41 12 7A F9 ..!PO......VA.z.
00000010: 31 1
plain=
00000000: 4D 72 2E 20 53 68 65 72 6C 6F 63 6B 20 48 6F 6C Mr. Sherlock Hol
00000010: 6D 65 73 2C 20 77 68 6F 20 77 61 73 20 75 73 75 mes, who was usu
00000020: 61 6C 6C 79 20 76 65 72 79 20 6C 61 74 65 20 69 ally very late i
00000030: 6E 20 74 68 65 20 6D 6F 72 6E 69 6E 67 73 2C 20 n the mornings,
00000040: 73 61 76 65 0D 0A 75 70 6F 6E 20 74 68 6F 73 65 save..upon those
00000050: 20 6E 6F 74 20 69 6E 66 72 65 71 75 65 6E 74 20 not infrequent
00000060: 6F 63 63 61 73 69 6F 6E 73 20 77 68 65 6E 20 68 occasions when h
00000070: 65 20 77 61 73 20 75 70 20 61 6C 6C 20 6E 69 67 e was up all nig
00000080: 68 74 2C 20 77 61 73 20 73 65 61 74 65 64 0D 0A ht, was seated..
00000090: 61 74 20 74 68 65 20 62 72 65 61 6B 66 61 73 74 at the breakfast
000000A0: 20 74 61 62 6C 65 2E 20 49 20 73 74 6F 6F 64 20 table. I stood
000000B0: 75 70 6F 6E 20 74 68 65 20 68 65 61 72 74 68 2D upon the hearth-
000000C0: 72 75 67 20 61 6E 64 20 70 69 63 6B 65 64 20 75 rug and picked u
000000D0: 70 20 74 68 65 0D 0A 73 74 69 63 6B 20 77 68 69 p the..stick whi
000000E0: 63 68 20 6F 75 72 20 76 69 73 69 74 6F 72 20 68 ch our visitor h
000000F0: 61 64 20 6C 65 66 74 20 62 65 68 69 6E 64 20 68 ad left behind h
00000100: 69 6D 20 74 68 65 20 6E 69 67 68 74 20 62 65 66 im the night bef
00000110: 6F 72 65 2E 20 49 74 20 77 61 73 20 61 0D 0A 66 ore. It was a..f
00000120: 69 6E 65 2C 20 74 68 69 63 6B 20 70 69 65 63 65 ine, thick piece
00000130: 20 6F 66 20 77 6F 6F 64 2C 20 62 75 6C 62 6F 75 of wood, bulbou
00000140: 73 2D 68 65 61 64 65 64 2C 20 6F 66 20 74 68 65 s-headed, of the
00000150: 20 73 6F 72 74 20 77 68 69 63 68 20 69 73 sort which is

因此,这就是正确的key。

不用说,输入的数据越大越好。这个350字节的文件实际上是另一个更大加密文件的前戏(cipher2.txt, 12903 bytes) ,这个大文件的key可以不用添加那些些启发式条件也能求得。

SMT Solver 可以秒杀这些东西,我曾经很天真的解决了这些问题,这里有一个更快的版本: https://yurichev.com/blog/XOR_mask_2/尽管如此,这仍然是另一个优化问题的演示。

使用Z3破解简单的XOR加密的更多相关文章

  1. XOR 加密简介

    本文介绍一种简单高效.非常安全的加密方法:XOR 加密. 一. XOR 运算 逻辑运算之中,除了 AND 和 OR,还有一种 XOR 运算,中文称为"异或运算". 它的定义是:两个 ...

  2. 手游Apk破解疯狂,爱加密apk加固保护开发人员

    2013年手游行业的规模与收入均实现了大幅增长,发展势头强劲.权威数据显示, 我国移动游戏市场实际销售收入从2012年的32.4亿猛增到2013年的112.4亿元,同比增长了246.9%,手游用户从2 ...

  3. XOR 加密

    XOR 是一个神奇的运算符, 观察它的真值表, 很容易得到以下结论: 假设现有 a , b 变量, 则 a ^ 0 == a a ^ 0xff == ~a (取反加1等于作为补码的a的真值的相反数的补 ...

  4. 完全教程 Aircrack-ng破解WEP、WPA-PSK加密利器

    其 实关于无线基础知识的内容还是挺多的,但是由于本书侧重于BT4自身工具使用的讲解,若是再仔细讲述这些外围的知识,这就好比讲述DNS工具时还要把 DNS服务器的类型.工作原理及配置讲述一遍一样,哈哈, ...

  5. 用imagemagick和tesseract-ocr破解简单验证码

    用imagemagick和tesseract-ocr破解简单验证码 Tesseract-ocr据说辨识程度是世界排名第三,可谓神器啊. 准备工作: 1.安装tesseract-ocr sudo apt ...

  6. C#,ASP.NET简单的MD5加密,解密

    简单的MD5加密 首先要有一个加解密的规则  就是key 代码如下 // 创建Key public string GenerateKey() { DESCryptoServiceProvider de ...

  7. python+selenium十三:破解简单的图形验证码

    此方法可破解简单的验证码,如: 注:中文识别正在寻找办法 安装: 1.python3 2.Pillow 3.pytesseract 4.tesseract-ocr    下载地址:https://pa ...

  8. javascript 异或运算符实现简单的密码加密功能

    写在前面的 当我们需要在数据库中存储用户的密码时,当然是不能明文存储的. 我们就是介绍一下用^运算符来实现简单的密码加密以及解密功能 上代码 首先,回顾一下基础知识. String.fromCharc ...

  9. 【Python3爬虫】反反爬之破解同程旅游加密参数 antitoken

    一.前言简介 在现在各个网站使用的反爬措施中,使用 JavaScript 加密算是很常用的了,通常会使用 JavaScript 加密某个参数,例如 token 或者 sign.在这次的例子中,就采取了 ...

随机推荐

  1. Elasticsearch介绍,一些概念的笔记

    Elasticsearch,分布式,高性能,高可用,可伸缩的搜索和分析系统 什么是搜索? 如果用数据库做搜索会怎么样? 什么是全文检索和Lucene? 什么是Elasticsearch? Elasti ...

  2. mysql常用的提权方法

    一,利用MOF提权 Windows 管理规范 (WMI) 提供了以下三种方法编译到 WMI 存储库的托管对象格式 (MOF) 文件: 方法 1: 运行 MOF 文件指定为命令行参数将 Mofcomp. ...

  3. centos6+cdh5.4.0 离线搭建cdh搭建

    p.MsoNormal { margin: 0pt; margin-bottom: .0001pt; text-align: justify; font-family: Calibri; font-s ...

  4. Spring注解依赖注入的三种方式的优缺点以及优先选择

    当我们在使用依赖注入的时候,通常有三种方式: 1.通过构造器来注入: 2.通过setter方法来注入: 3.通过filed变量来注入: 那么他们有什么区别吗?应该选择哪种方式更好? 三种方式的区别小结 ...

  5. 【centos6.5 hadoop2.7 _64位一键安装脚本】有问题加我Q直接问

    #!/bin/bash#@author:feiyuanxing [既然笨到家,就要努力到家]#@date:2017-01-05#@E-Mail:feiyuanxing@gmail.com#@TARGE ...

  6. dedecms_分页技术

    <ul>{dede:list pagesize='30'} <li><a href="[field:arcurl/]">[field:title ...

  7. 版本控制——TortoiseSVN (3)多版本发布

    =================================版权声明================================= 版权声明:原创文章 禁止转载  请通过右侧公告中的“联系邮 ...

  8. iOS开发中一些有用的小代码

    1.判断邮箱格式是否正确的代码: //利用正则表达式验证 -(BOOL)isValidateEmail:(NSString *)email {   NSString *emailRegex = @&q ...

  9. jquery mobile-按钮控件

    jQuery Mobile 中的按钮会自动获得样式,这增强了他们在移动设备上的交互性和可用性.我们推荐您使用 data-role="button" 的 <a> 元素来创 ...

  10. javascript函数之arguments

    function foo(x,y,z){ console.info (arguments.length); //2 实际的参数个数 console.info(arguments[0]); //传入的第 ...