├── .gitignore
├── README.md
└── mod_equations.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #模线性方程组在密码分析中的应用
2 |
3 | By [@55-AA](http://weibo.com/u/2259260797) 12:50 2016/7/19
4 |
5 | ##0 前言
6 | 在很多的加密算法中都用到了求模运算,结合实际运算中的一些已知条件,最终可将破解归结为模线性方程组的求解问题。本文重点探讨了在不考虑约束条件下,单模多元线性方程组的通用解法。这对研究算法的抗攻击性有一定的实际意义。
7 |
8 | ##1 概念和定义
9 | **“线性方程组”**即普通的线性方程组,其解法在线性代数中有详细的讨论。比较适合计算机求解的就是高斯消元法。本文的也是在高斯消元法的基础上进行扩展。“线性方程组”的一般形式为:
10 |
11 | A*X=B
12 |
13 | 其中:
14 | / a11, a12, ..., a1m \ / x1 \ / b1 \
15 | | a21, a22, ..., a2m | | x2 | | b2 |
16 | A = | a31, a32, ..., a3m | X = | x3 | B = | b3 |
17 | | ...... | | ...| | ...|
18 | \ an1, an2, ..., anm / \ xm / \ bn /
19 |
20 | A为系数矩阵,X为解向量,B为常数向量。
21 |
22 |
23 | **“模线性方程”**也叫一次同余方程,其解法在初等数论中有详细的讨论,关键是清楚几个定理。这里列举出来,主要是这几个概念中文很容易混淆。
24 |
25 | + *欧拉函数: Euler's totient function*
26 | + *欧拉定理: Euler's theorem*
27 | + *费马小定理: Fermat's little theorem*
28 | + *欧几里德算法: Euclidean algorithm*
29 | + *扩展欧几里德算法: Extended Euclidean algorithm*
30 |
31 | 本文用到几个表示方法:
32 |
33 | + 最大公约数: gcd(a,b)表示a和b的最大公约数
34 | + 模逆: inv(a,q)表示a关于q的逆元
35 |
36 |
37 | “模线性方程”的一般形式为:
38 |
39 | a*x ≡ b(mod q)
40 |
41 | **“模线性方程组”**的分类:
42 |
43 | + 按照未知数的数量区分:
44 |
45 | 一元模线性方程组,如下,只有一个未知数x:
46 |
47 | a1*x ≡ b1(mod q1)
48 | a2*x ≡ b2(mod q2)
49 | ...
50 | an*x ≡ bn(mod qn)
51 | 其中n为正整数。
52 |
53 | 多元模线性方程组,如下,未知数为x1、x2、...、xm:
54 |
55 | a11*x1 + a12*x2 + ... a0m*xm ≡ b1(mod q1)
56 | a12*x1 + a22*x2 + ... a1m*xm ≡ b2(mod q2)
57 | ...
58 | a1n*x1 + an2*x2 + ... anm*xm ≡ bn(mod qn)
59 | 其中m、n为正整数。
60 |
61 | + 按照模的数量区分:
62 |
63 | 单模线性方程组,如下,模数都为q:
64 |
65 | a11*x1 + a12*x2 + ... a0m*xm ≡ b1(mod q)
66 | a12*x1 + a22*x2 + ... a1m*xm ≡ b2(mod q)
67 | ...
68 | a1n*x1 + an2*x2 + ... anm*xm ≡ bn(mod q)
69 | 其中m、n为正整数。
70 |
71 | 多模线性方程组,如下,模数为q1、q2、...、qn:
72 |
73 | a1*x ≡ b1(mod q1)
74 | a2*x ≡ b2(mod q2)
75 | ...
76 | an*x ≡ bn(mod qn)
77 | 其中n为正整数。
78 |
79 | 综合考虑上述分类,模线性方程组可包括:
80 |
81 | 1. 一元单模线性方程组(A),无太多实际意义。
82 |
83 | 2. 一元多模线性方程组(B),当(q0、q1、...qn)两两互质时,方程组有唯一解。可通过中国剩余定理求解,这里不再详述。
84 |
85 | 3. 多元单模线性方程组(C),是本文要研究的对象,它有多个未知数,且模数为同一个。这里的模数不要求必须为质数,因而更具有通用性。以下讨论都针对于这种形式展开。
86 |
87 | 4. 多元多模线性方程组(D),在论文[1]中有详细论述,本文不做探讨。
88 |
89 | ##2 方程组解的求解过程
90 |
91 | 多元单模线性方程组(C)
92 |
93 | a11*x1 + a12*x2 + ... a0m*xm ≡ b1(mod q)
94 | a21*x1 + a22*x2 + ... a2m*xm ≡ b2(mod q)
95 | ...
96 | an1*x1 + an2*x2 + ... anm*xm ≡ bn(mod q)
97 | 其中m、n为正整数。
98 |
99 | 可表示为:
100 |
101 | A*X ≡ B (mod q)
102 |
103 | 其中:
104 | / a11, a12, ..., a1m \ / x1 \ / b1 \
105 | | a21, a22, ..., a2m | | x2 | | b2 |
106 | A = | a31, a32, ..., a3m | X = | x3 | B = | b3 |
107 | | ...... | | ...| | ...|
108 | \ an1, an2, ..., anm / \ xm / \ bn /
109 |
110 | A为系数矩阵,X为解向量,B为常数向量,q为模数。
111 |
112 | 在论文[1]中也探讨了该类方程组的解法。在该文中,当模为质数时采用高斯消元,当模为合数时采用LU分解。但其解法仍有一定的局限性,包括:
113 |
114 | 1. 当模为合数时,如果系数与模不互质则不能进行LU分解;
115 | 2. 只对系数矩阵的秩小于未知数个数时的多解情况做了探讨,而且仅给出了通解的表达式;
116 | 3. 文中只对系数行列式与模数互质的情况作了讨论,实际上不互质时方程组也是可解的。
117 |
118 | 本文则讨论模数和系数为任意整数,方程数和未知数为任意个数时的通用情况,并对有限域内的多解情况进行了延伸,把上述2和3导致的多解情况统一考虑。此外,本文在有限域内遍历多解,这对研究算法攻击是有现实意义的。
119 |
120 | ###2.1 通用求解过程
121 | 本文求解的基本思路还是高斯消元。通过全主消元,把系数的增广矩阵化简为如下的上三角形式,然后逐一回代求得所有解。
122 |
123 | / a11, a12, a33, ..., a1m, b1 \
124 | | 0, a22, a32, ..., a1m, b2 |
125 | | 0, 0, a33, ..., a3m, b3 |
126 | | ...... |
127 | | 0, 0, 0, ..., amm, bm |
128 | | 0, 0, 0, ..., 0, 0 |
129 | | ...... |
130 | \ 0, , 0, ..., 0, 0 /
131 |
132 | 在化简上三角的过程中所遵循的原则和代数方程组有所区别,不能完全套用代数方程组的消元法则。特别是单个方程不能和任意数做乘法/除法,乘法和除法只能在有条件下进行:
133 |
134 | 1. 当整数k与模数q互质时,k乘以方程和原方程同解;
135 | 2. 当整数k与模数q互质时,k的模逆乘以方程和原方程同解,相当于方程除以k和原方程同解;
136 |
137 | 除此之外,方程不能做乘法/除法,否则可能造成解扩散。能够保证同解的初等行列式变换只能是如下三种形式:
138 |
139 | 1. (H1)交换任意两行
140 | 2. (H2)交换任意两列(不包括常数项列)
141 | 3. (H3)某一行加另外一行(可加多次。在模q的有限域内,加减法是一致的,A-B ≡ A+kB(mod q))
142 |
143 | 化简上三角流程如下:
144 |
145 | 1. 寻找和模数q互质的系数,通过H1和H2变换,把该系数放置到对角线上;
146 | 2. 如果1找不到,则通过H3化简寻找一个系数k,使得gcd(k, q)为最小(在查找过程中如果gcd(k, q)=1,可提前结束查找);
147 | 3. 通过H1和H2变换,把系数k放置到对角线上;
148 | 4. 对每个方程做如上1、2、3的变换。
149 |
150 | 最后得到的情形可能为:
151 |
152 | + 形式1
153 |
154 | a x x α
155 | 0 b x β
156 | 0 0 c γ
157 | + 形式2
158 |
159 | a x x α
160 | 0 b x β
161 | + 形式3
162 |
163 | a x x α
164 | 0 b x β
165 | 0 0 0 γ
166 |
167 | 形式1是唯一解或多解,形式2是一定是多解,形式3无解。对于形式2可化为:
168 |
169 | + 形式4
170 |
171 | a x x α
172 | 0 b x β
173 | 0 0 0 0
174 |
175 | 然后对形式4和形式1做统一处理,进行回代求解。
176 |
177 | 回代过程:
178 | 从对角线的最右下角开始,求解单个未知数,其形式可简化为ax ≡ b (mod q)的求解。
179 | 由初等数论定理可知,x有三种情况:
180 |
181 | 1. 当gcd(a,q)=1时,有唯一解x=b*inv(a),其中inv(a)是a关于q的模逆;
182 | 2. 当gcd(a,q)=k>1,且gcd(k,b)=k时,有多解, x=(b/k)*inv(a/k)+n(q/k),其中n为整数且0 <= n < k;
183 | 3. 当gcd(a,q)=k>1,且gcd(k,b)[2]:
206 |
207 | 方程:
208 | a1*x1 + a2*x2 + ... an*xn ≡ b(mod q)
209 | 有解的充要条件是k=gcd(a1,a2, ..., an,q),k能整除b。解的个数为:
210 | p = q^(n-1)*k
211 |
212 | 证明过程略。
213 |
214 | ##3 Hill密码攻击
215 | Hill密码是一种分组对称加密算法。该算法在已知明文情况下,抗攻击性较差。例如以下加密过程:
216 |
217 | 明文P=[11,12,7,14,18,23,17,5,19]
218 | 加密矩阵K=[
219 | [23,15,19],
220 | [13,25,17],
221 | [24,18,11]
222 | ]
223 | K左乘P得密文C=[20,16,11,15,9,3,21,19,5]
224 |
225 | 现已知明文P和密文C,并且推测加密矩阵的阶数为3,则可通过求解如下线性方程组来计算加密矩阵M。
226 |
227 | 设加密矩阵K的行展开向量为:
228 | k'= {ki}, 1 <= i <= 9
229 | 已知明文向量为:
230 | P = {pi}, 1 <= i <= 9
231 | 已知密文向量为:
232 | C = {ci}, 1 <= i <= 9
233 |
234 | 则可得方程组:
235 |
236 | p1*k1 + p2*k2 + p3*k3 + 0 + 0 + 0 + 0 + 0 + 0 ≡ c1(mod 26)
237 | 0 + 0 + 0 + p1*k4 + p2*k5 + p3*k6 + 0 + 0 + 0 ≡ c2(mod 26)
238 | 0 + 0 + 0 + 0 + 0 + 0 + p1*k7 + p2*k8 + p3*k9 ≡ c3(mod 26)
239 |
240 | p4*k1 + p5*k2 + p6*k3 + 0 + 0 + 0 + 0 + 0 + 0 ≡ c4(mod 26)
241 | 0 + 0 + 0 + p4*k4 + p5*k5 + p6*k6 + 0 + 0 + 0 ≡ c5(mod 26)
242 | 0 + 0 + 0 + 0 + 0 + 0 + p4*k7 + p5*k8 + p6*k9 ≡ c6(mod 26)
243 |
244 | p7*k1 + p8*k2 + p9*k3 + 0 + 0 + 0 + 0 + 0 + 0 ≡ c7(mod 26)
245 | 0 + 0 + 0 + p7*k4 + p8*k5 + p9*k6 + 0 + 0 + 0 ≡ c8(mod 26)
246 | 0 + 0 + 0 + 0 + 0 + 0 + p7*k7 + p8*k8 + p9*k9 ≡ c9(mod 26)
247 |
248 | 代入已知,得到对应的增广矩阵为:
249 |
250 | [
251 | [ 11, 12, 7, 0, 0, 0, 0, 0, 0, 20],
252 | [ 0, 0, 0, 11, 12, 7, 0, 0, 0, 16],
253 | [ 0, 0, 0, 0, 0, 0, 11, 12, 7, 11],
254 | [ 14, 18, 23, 0, 0, 0, 0, 0, 0, 15],
255 | [ 0, 0, 0, 14, 18, 23, 0, 0, 0, 9],
256 | [ 0, 0, 0, 0, 0, 0, 14, 18, 23, 3],
257 | [ 17, 5, 19, 0, 0, 0, 0, 0, 0, 21],
258 | [ 0, 0, 0, 17, 5, 19, 0, 0, 0, 19],
259 | [ 0, 0, 0, 0, 0, 0, 17, 5, 19, 5],
260 | ]
261 |
262 | 解方程得唯一解:
263 |
264 | k' = [23, 15, 19, 13, 25, 17, 24, 18, 11]
265 |
266 | 显然k'对应加密矩阵K。
267 |
268 | 由此可见,只要获得足够多的明密文对,就一定能得到唯一的加密矩阵。但在少量明密文对的情况下,方程组系数矩阵的行列式值可能与模数26不互质,从而导致方程组出现多解。
269 |
270 |
271 | ##4 DSA共模攻击
272 | DSA(Digital Signature Algorithm)是一种签名算法。当两次签名使用相同的随机数时,可通过两次明、密文对推导出私钥,导致私钥泄漏。
273 |
274 | p:L bits长的素数。L是64的倍数,范围是512到1024;
275 | q:p - 1的160bits的素因子;
276 | g:g = h^((p-1)/q) mod p,h满足h < p - 1, h^((p-1)/q) mod p > 1;
277 | m:为原文
278 | 其中:
279 | x:x < q,x为私钥 ;
280 | y:y = g^x mod p ,( p, q, g, y )为公钥;
281 | 签名过程为:
282 | r = ( g^k mod p ) mod q
283 | s = ( inv(k) * (HASH(m) + xr)) mod q
284 | 签名结果是( m, r, s )
285 |
286 | 在上述签名过程中,若两次签名使用相同的随机数k,两次的签名分别是:
287 |
288 | 对原文m1的签名:(m1, r1, s1)
289 | 对原文m2的签名:(m2, r2, s2)
290 |
291 | 根据签名过程可得:
292 |
293 | 令: M=HASH(m)
294 | 由: s ≡ inv(k)*(M+x*r) (mod q)
295 | k*inv(k) ≡ 1 (mod q)
296 | 得: k*s ≡ K*inv(k)*(M+x*r) (mod q)
297 | 即: k*s ≡ M+x*r (mod q)
298 | 由两次签名的结果,可得方程组:
299 | k*s1 ≡ M1 + r1*x (mod q)
300 | k*s2 ≡ M2 + r2*x (mod q)
301 | 规范一下:
302 | s1*k - r1*x ≡ M1 (mod q)
303 | s2*k - r2*x ≡ M2 (mod q)
304 |
305 | 在上面得到的二元一次模线性方程组中,系数为s1、s2、r1、r2,未知数为k和x,常数项为M1、M2,模数为q。(m1, r1, s1)和(m2, r2, s2)都是已知,因此可通过求解方程得到私钥x。
306 |
307 | 参考资料[3]中的一个例子:
308 |
309 | p = 0x8c286991e30fd5341b7832ce9fe869c0a73cf79303c2959ab677d980237abf7ecf853015c9a086c4330252043525a4fa60c64397421caa290225d6bc6ec6b122cd1da4bba1b13f51daca8b210156a28a0c3dbf17a7826f738fdfa87b22d7df990908c13dbd0a1709bbbab5f816ddba6c8166ef5696414538f6780fdce987552b
310 | g = 0x49874582cd9af51d6f554c8fae68588c383272c357878d7f4079c6edcda3bcbf1f2cbada3f7d541a5b1ae7f046199f8f51d72db60a2601bd3375a3b48d7a3c9a0c0e4e8a0680f7fb98a8610f042e10340d2453d3c811088e48c5d6dd834eaa5509daeb430bcd9de8aabc239d698a655004e3f0a2ee456ffe9331c5f32c66f90d
311 | q = 0x843437e860962d85d17d6ee4dd2c43bc4aec07a5
312 |
313 | 已知,使用私钥x、固定值k,对m1进行的签名为:
314 | m1 = 0x3132333435363738
315 | r1 = 0x4d91a491d95e4eef4196a583cd282ca0e625f36d
316 | s1 = 0x3639b47678abf7545397fc9a1af108537fd1dfac
317 | 已知,使用私钥x、固定值k,对m2进行的签名为:
318 | m2 = 0x49276c6c206265206261636b2e
319 | r2 = 0x4d91a491d95e4eef4196a583cd282ca0e625f36d
320 | s2 = 0x314c044409a94f4961340212b42ade005fb27b0a
321 |
322 | 由两次签名结果,得到的二元模线性方程组,其增广矩阵为:
323 |
324 | M1 = int(hashlib.sha1('3132333435363738'.decode('hex')).hexdigest(), 16)
325 | M2 = int(hashlib.sha1('49276c6c206265206261636b2e'.decode('hex')).hexdigest(), 16)
326 |
327 | matrix = [
328 | [0x3639b47678abf7545397fc9a1af108537fd1dfac, -0x4d91a491d95e4eef4196a583cd282ca0e625f36d, M1],
329 | [0x314c044409a94f4961340212b42ade005fb27b0a, -0x4d91a491d95e4eef4196a583cd282ca0e625f36d, M2]
330 | ] (mod 0x843437e860962d85d17d6ee4dd2c43bc4aec07a5)
331 |
332 | 求解该方程得:
333 |
334 | k=0x8177f48fcb32d7a6d738210ea9ae63168326a5d6
335 | x=0x1b4f7f1b231596da9dd3ef03bfe8fb42324d581f
336 |
337 | 容易验证上面的x即为签名时使用的私钥,k为签名时使用的随机数。
338 |
339 |
340 | ##5 实现代码
341 | 本文的实现代码:
342 |
343 | https://github.com/55-AA/mod_equations
344 |
345 | ##6 参考资料
346 | 1. 周利敏,单/多模数线性同余方程组的数值解法及其在密码学中的应用
347 | 2. 朱萍,初等数论及其在信息科学中的应用,P42,清华大学出版社
348 | 3. DSA相关的趣味数学题,http://scz.617.cn/misc/201607011637.txt
--------------------------------------------------------------------------------
/mod_equations.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | '''
5 | Author @55-AA
6 | 19 July, 2016
7 | '''
8 |
9 | import copy
10 |
11 | def gcd(a, b):
12 | """
13 | Return the greatest common denominator of integers a and b.
14 | gmpy2.gcd(a, b)
15 | """
16 | while b:
17 | a, b = b, a % b
18 | return a
19 |
20 | def lcm(a, b):
21 | return a * b / (gcd(a, b))
22 |
23 | def egcd(a, b):
24 | """
25 | ax + by = 1
26 | ax ≡ 1 mod b
27 | Return a 3-element tuple (g, x, y), the g = gcd(a, b)
28 | gmpy2.gcdext(a, b)
29 | """
30 | if a == 0:
31 | return (b, 0, 1)
32 | else:
33 | g, y, x = egcd(b % a, a)
34 | return (g, x - (b // a) * y, y)
35 |
36 | def mod_inv(a, m):
37 | """
38 | ax ≡ 1 mod m
39 | gmpy2.invert(a, m)
40 | """
41 | g, x, y = egcd(a, m)
42 | assert g == 1
43 | return x % m
44 |
45 | def int2mem(x):
46 | """
47 | 0x12233 => '\x33\x22\x01'
48 | """
49 | pad_even = lambda x : ('', '0')[len(x)%2] + x
50 | x = list(pad_even(format(x, 'x')).decode('hex'))
51 | x.reverse()
52 | return ''.join(x)
53 |
54 | def mem2int(x):
55 | """
56 | '\x33\x22\x01' => 0x12233
57 | """
58 | x = list(x)
59 | x.reverse()
60 | return int(''.join(x).encode('hex'), 16)
61 |
62 | ###########################################################
63 | # class
64 | ###########################################################
65 | class GaussMatrix:
66 | """
67 | A*X ≡ B (mod p),p为大于0的整数。
68 |
69 | 高斯消元求解模线性方程组。先化简为上三角,然后回代求解。
70 | 当r(A) <= n时,一定有多解;
71 | 当r(A) == n时,有多解或唯一解;
72 | 当r(A) != r(A~)时,无解。
73 | r(A)为系数矩阵的秩,r(A)为增广矩阵的秩,n为未知数的个数。
74 |
75 | http://www.docin.com/p-1063811671.html讨论了gcd(|A|, m) = 1时的LU分解解法,
76 | 本文包括了gcd(|A|, m) > 1时的解法,
77 |
78 | 化简原则:
79 | 1、系数与模互质
80 | 2、系数加某一行n次后,对应的系数与模的GCD最小
81 | 3、将1或2得到的系数移到对角线上
82 |
83 | 初始化参数:
84 | matrix:方程组的增广矩阵(最后一列为常数项)。
85 | matrix = [
86 | [ 69, 75, 78, 36, 58],
87 | [ 46, 68, 51, 26, 42],
88 | [ 76, 40, 42, 49, 11],
89 | [ 11, 45, 2, 45, 1],
90 | [ 15, 67, 60, 14, 72],
91 | [ 76, 67, 73, 56, 58],
92 | [ 67, 15, 68, 54, 75],
93 | ]
94 | mod:模数
95 |
96 | 函数:
97 | gauss():求解方程
98 |
99 | 输出变量:
100 | error_str:出错的信息
101 | count:解的数量
102 | """
103 | def __init__(self, matrix, mod):
104 | self.matrix = copy.deepcopy(matrix)
105 | self.d = None
106 |
107 | self.r = len(matrix)
108 | self.c = len(matrix[0])
109 | self.N = len(matrix[0]) - 1
110 | self.mod = mod
111 | self.count = 1
112 | self.error_str = "unknown error"
113 |
114 | def verify_solution(self, solution):
115 | for d in self.matrix:
116 | result = 0
117 | for r in map(lambda x,y:0 if None == y else x*y, d, solution):
118 | result += r
119 | if (result % self.mod) != ((d[-1]) % self.mod):
120 | return 0
121 | return 1
122 |
123 | def swap_row(self, ra, rb):
124 | (self.d[ra], self.d[rb]) = (self.d[rb], self.d[ra])
125 |
126 | def swap_col(self, ca, cb):
127 | for j in range(self.r):
128 | (self.d[j][ca], self.d[j][cb]) = (self.d[j][cb], self.d[j][ca])
129 |
130 | def inv_result(self, r, n):
131 | """
132 | 求解第n个未知数,r已经获得的解。形如:[None,None, ..., n+1, ...]
133 |
134 | a*x ≡ b(mod m)
135 | x有解的条件:gcd(a,m) | b。也即a,m互质时一定有解,不互质时,b整除gcd(a,m)也有解,否则无解。
136 | 解的格式为:x0+k(m/gcd(a,m)),其中x0为最小整数特解,k为任意整数。
137 | 返回[x0, x1, ...xn],其中x0 < x1 < xn < m。
138 | """
139 | b = self.d[n][self.N]
140 | a = self.d[n][n]
141 | m = self.mod
142 | k = gcd(a, m)
143 | for j in xrange(n + 1, self.N):
144 | b = (b - (self.d[n][j] * r[j] % m)) % m
145 |
146 | if 1 == k:
147 | return [mod_inv(a, m) * b % m]
148 | else:
149 | if k == gcd(k, b):
150 | a /= k
151 | b /= k
152 | m /= k
153 |
154 | x0 = mod_inv(a, m) * b % m
155 | x = []
156 | for i in xrange(k):
157 | x.append(x0 + m*i)
158 | return x
159 | return None
160 |
161 | def find_min_gcd_row_col(self, i, j):
162 | # 查找直接互质的对角线系数
163 | for k in xrange(i, self.r):
164 | for l in xrange(j, self.c - 1):
165 | if(1 == gcd(self.d[k][l], self.mod)):
166 | return [k, l]
167 |
168 |
169 | def add_min_gcd(a, b, m):
170 | r = [m, 1]
171 | g = gcd(a, b)
172 | if g:
173 | i = a / g
174 | for j in xrange(i):
175 | g = gcd((a + j * b) % m, m)
176 | if g < r[0]:
177 | r[0] = g
178 | r[1] = j
179 | if g == 1:
180 | break
181 | return r
182 |
183 | # 查找加乘后GCD最小的对角线系数
184 | # [加乘后的最大公约数,加乘的倍数,要化简的行号,加乘的行号,要化简的列号]
185 | r = [self.mod, 1, i, i + 1, j]
186 | for k in xrange(i, self.r):
187 | for kk in xrange(k+1, self.r):
188 | for l in range(j, self.c - 1):
189 | rr = add_min_gcd(self.d[k][l], self.d[kk][l], self.mod)
190 | if rr[0] < r[0]:
191 | r[0] = rr[0]
192 | r[1] = rr[1]
193 | r[2] = k
194 | r[3] = kk
195 | r[4] = l
196 | pass
197 | if(1 == rr[0]):
198 | break
199 | g = r[0]
200 | n = r[1]
201 | k = r[2]
202 | kk = r[3]
203 | l = r[4]
204 |
205 | if n and g < self.mod:
206 | self.d[k] = map(lambda x, y : (x + n*y)%self.mod, self.d[k], self.d[kk])
207 | return [k, l]
208 |
209 | def mul_row(self, i, k, j):
210 | a = self.d[k][j]
211 | b = self.d[i][j]
212 |
213 | def get_mul(a, b, m):
214 | k = gcd(a, m)
215 | if 1 == k:
216 | return mod_inv(a, m) * b % m
217 | else:
218 | if k == gcd(k, b):
219 | return mod_inv(a/k, m/k) * (b/k) % (m/k)
220 | return None
221 |
222 | if b:
223 | mul = get_mul(a, b, self.mod)
224 | if None == mul:
225 | print_matrix(self.d)
226 | assert(mul != None)
227 | self.d[i] = map(lambda x, y : (y - x*mul) % self.mod, self.d[k], self.d[i])
228 |
229 |
230 | def gauss(self):
231 | """
232 | 返回解向量,唯一解、多解或无解(None)。
233 | 例如:[[61, 25, 116, 164], [61, 60, 116, 94], [61, 95, 116, 24], [61, 130, 116, 129], [61, 165, 116, 59]]
234 | """
235 |
236 | self.d = copy.deepcopy(self.matrix)
237 | for i in xrange(self.r):
238 | for j in xrange(self.c):
239 | self.d[i][j] = self.matrix[i][j] % self.mod #把负系数变成正系数
240 |
241 | if self.r < self.N:
242 | self.d.extend([[0]*self.c]*(self.N - self.r))
243 |
244 | # 化简上三角
245 | index = [x for x in xrange(self.N)]
246 | for i in range(self.N):
247 | tmp = self.find_min_gcd_row_col(i, i)
248 | if(tmp):
249 | self.swap_row(i, tmp[0])
250 | (index[i], index[tmp[1]]) = (index[tmp[1]], index[i])
251 | self.swap_col(i, tmp[1])
252 | else:
253 | self.error_str = "no min"
254 | return None
255 |
256 | for k in range(i + 1, self.r):
257 | self.mul_row(k, i, i)
258 |
259 | # print_matrix(self.d)
260 | if self.r > self.N:
261 | for i in xrange(self.N, self.r):
262 | for j in xrange(self.c):
263 | if self.d[i][j]:
264 | self.error_str = "r(A) != r(A~)"
265 | return None
266 |
267 | # 判断解的数量
268 | for i in xrange(self.N):
269 | self.count *= gcd(self.d[i][i], self.mod)
270 |
271 | if self.count > 100:
272 | self.error_str = "solution too more:%d" % (self.count)
273 | return None
274 |
275 | # 回代
276 | result = [[None]*self.N]
277 | for i in range(self.N - 1, -1, -1):
278 | new_result = []
279 | for r in result:
280 | ret = self.inv_result(r, i)
281 | if ret:
282 | for rr in ret:
283 | l = r[:]
284 | l[i] = rr
285 | new_result.append(l)
286 |
287 | else:
288 | self.error_str = "no inv:i=%d" % (i)
289 | return None
290 |
291 | result = new_result
292 |
293 | # 调整列变换导致的未知数顺序变化
294 | for i in xrange(len(result)) :
295 | def xchg(a, b):
296 | result[i][b] = a
297 | map(xchg, result[i][:], index)
298 |
299 | return result
300 |
301 | ###########################################################
302 | # test
303 | ###########################################################
304 | def print_array(x):
305 | prn = "\t["
306 | for j in x:
307 | if j:
308 | prn += "%3d, " % j
309 | else:
310 | prn += " 0, "
311 |
312 | print prn[:-2]+"],"
313 |
314 | def print_matrix(x):
315 | print "["
316 | for i in x:
317 | print_array(i)
318 | print "]"
319 |
320 | def random_test(times):
321 | import random
322 | for i in xrange(times):
323 | print "\n============== random test %d ==============\n" % i
324 | mod = random.randint(5, 999)
325 | col = random.randint(2, 30)
326 | row = random.randint(2, 30)
327 |
328 | solution = map(lambda x : random.randint(0, mod - 1), [xc for xc in xrange(col)])
329 |
330 | matrix = []
331 | for y in xrange(row):
332 | array = map(lambda x : random.randint(0, mod), [xc for xc in xrange(col)])
333 |
334 | t = 0
335 | for j in map(lambda x,y:0 if None == y else x*y, array, solution):
336 | t += j
337 | array.append(t % mod)
338 |
339 | matrix.append(array)
340 |
341 | run_test(mod, solution, matrix)
342 |
343 | def static_test_ex():
344 | mod = 37
345 | solution = [6, 10, 5, 11, 32, 39, 6, 42, 7, 18, 21, 8, 8, 27]
346 | matrix = [
347 | [ 32, 43, 11, 27, 14, 41, 27, 20, 0, 37, 7, 12, 9, 16, 12],
348 | [ 23, 35, 31, 25, 46, 27, 48, 0, 4, 19, 43, 11, 31, 24, 36],
349 | [ 48, 10, 47, 1, 42, 26, 0, 21, 10, 23, 7, 5, 13, 32, 41],
350 | [ 15, 0, 6, 24, 6, 36, 4, 36, 18, 46, 33, 20, 4, 20, 39],
351 | [ 4, 37, 3, 39, 26, 33, 13, 32, 23, 11, 45, 45, 29, 32, 35],
352 | [ 38, 8, 38, 47, 1, 34, 36, 46, 47, 0, 22, 23, 21, 31, 21],
353 | [ 21, 3, 17, 15, 46, 42, 7, 17, 12, 37, 30, 3, 14, 12, 16],
354 | [ 7, 22, 14, 31, 31, 19, 34, 46, 9, 33, 12, 18, 4, 15, 32],
355 | [ 13, 41, 35, 25, 19, 9, 44, 8, 0, 42, 15, 20, 3, 47, 29],
356 | [ 36, 21, 36, 13, 37, 40, 21, 39, 2, 16, 26, 4, 15, 2, 23],
357 | [ 41, 19, 28, 2, 42, 24, 27, 21, 21, 35, 3, 18, 7, 22, 36],
358 | [ 42, 34, 17, 40, 26, 7, 14, 0, 7, 46, 30, 14, 34, 22, 39],
359 | [ 18, 1, 40, 38, 17, 45, 24, 34, 34, 9, 32, 24, 9, 2, 45],
360 | [ 43, 2, 1, 29, 47, 48, 28, 37, 10, 23, 35, 34, 37, 44, 35],
361 | ]
362 |
363 | run_test(mod, solution, matrix)
364 |
365 | def static_test():
366 | mod = 26
367 | solution = [23,15,19,13,25,17,24,18,11]
368 | matrix = [
369 | [11,12,7,0,0,0,0,0,0],
370 | [0,0,0,11,12,7,0,0,0],
371 | [0,0,0,0,0,0,11,12,7],
372 | [14,18,23,0,0,0,0,0,0],
373 | [0,0,0,14,18,23,0,0,0],
374 | [0,0,0,0,0,0,14,18,23],
375 | [17,5,19,0,0,0,0,0,0],
376 | [0,0,0,17,5,19,0,0,0],
377 | [0,0,0,0,0,0,17,5,19],
378 | ]
379 |
380 | for i in xrange(len(matrix)):
381 | t = 0
382 | for j in map(lambda x,y:0 if None == y else x*y, matrix[i], solution):
383 | t += j
384 | matrix[i].append(t % mod)
385 |
386 | run_test(mod, solution, matrix)
387 |
388 | def run_test(mod, solution, matrix):
389 | print "row = %d, col = %d" % (len(matrix), len(matrix[0])-1)
390 | print "mod = %d" % (mod)
391 | print "solution =", solution
392 |
393 | print "matrix ="
394 | print_matrix(matrix)
395 |
396 | g = GaussMatrix(matrix, mod)
397 |
398 | ret = g.gauss()
399 | if not ret:
400 | print "error:"
401 | print_matrix(g.d)
402 | print "error_str:", g.error_str
403 | else:
404 | print "times:", g.count
405 | print "result:"
406 | print_matrix(ret)
407 |
408 |
409 | def DSA_comK():
410 | """
411 | # DSA两次签名使用相同的随机数k可导致私钥x泄漏
412 | # p:L bits长的素数。L是64的倍数,范围是512到1024;
413 | # q:p - 1的160bits的素因子;
414 | # g:g = h^((p-1)/q) mod p,h满足h < p - 1, h^((p-1)/q) mod p > 1;
415 | # x:x < q,x为私钥 ;
416 | # y:y = g^x mod p ,( p, q, g, y )为公钥;
417 |
418 | # r = ( g^k mod p ) mod q
419 | # s = ( k^(-1) (HASH(m) + xr)) mod q
420 | # 签名结果是( m, r, s )
421 | """
422 | import hashlib
423 | p = 0x8c286991e30fd5341b7832ce9fe869c0a73cf79303c2959ab677d980237abf7ecf853015c9a086c4330252043525a4fa60c64397421caa290225d6bc6ec6b122cd1da4bba1b13f51daca8b210156a28a0c3dbf17a7826f738fdfa87b22d7df990908c13dbd0a1709bbbab5f816ddba6c8166ef5696414538f6780fdce987552b
424 | g = 0x49874582cd9af51d6f554c8fae68588c383272c357878d7f4079c6edcda3bcbf1f2cbada3f7d541a5b1ae7f046199f8f51d72db60a2601bd3375a3b48d7a3c9a0c0e4e8a0680f7fb98a8610f042e10340d2453d3c811088e48c5d6dd834eaa5509daeb430bcd9de8aabc239d698a655004e3f0a2ee456ffe9331c5f32c66f90d
425 |
426 | q = 0x843437e860962d85d17d6ee4dd2c43bc4aec07a5
427 | m1 = 0x3132333435363738
428 | r1 = 0x4d91a491d95e4eef4196a583cd282ca0e625f36d
429 | s1 = 0x3639b47678abf7545397fc9a1af108537fd1dfac
430 |
431 | m2 = 0x49276c6c206265206261636b2e
432 | r2 = 0x4d91a491d95e4eef4196a583cd282ca0e625f36d
433 | s2 = 0x314c044409a94f4961340212b42ade005fb27b0a
434 |
435 | # M1 = mem2int(hashlib.sha1(int2mem(m1)).digest())
436 | M1 = int(hashlib.sha1('3132333435363738'.decode('hex')).hexdigest(), 16)
437 | # M2 = mem2int(hashlib.sha1(int2mem(m2)).digest())
438 | M2 = int(hashlib.sha1('49276c6c206265206261636b2e'.decode("hex")).hexdigest(), 16)
439 |
440 | matrix_c = [
441 | [0x3639b47678abf7545397fc9a1af108537fd1dfac, -0x4d91a491d95e4eef4196a583cd282ca0e625f36d, M1],
442 | [0x314c044409a94f4961340212b42ade005fb27b0a, -0x4d91a491d95e4eef4196a583cd282ca0e625f36d, M2]
443 | ]
444 |
445 | print "mod = %d" % (q)
446 | print "matrix ="
447 | print_matrix(matrix_c)
448 |
449 | Gauss = GaussMatrix(matrix_c, q)
450 |
451 | ret = Gauss.gauss()
452 | if not ret:
453 | print "error:"
454 | print_matrix(Gauss.d)
455 | print "error_str:", Gauss.error_str
456 | else:
457 | k = ret[0][0]
458 | x = ret[0][1]
459 | print "k: %x" % (k)
460 | print "x: %x" % (x)
461 | print Gauss.verify_solution(ret[0])
462 |
463 |
464 | if __name__ == "__main__":
465 | # DSA_comK()
466 | # static_test()
467 | # static_test_ex()
468 | random_test(1)
469 | exit(0)
470 |
--------------------------------------------------------------------------------