├── .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 | --------------------------------------------------------------------------------