乘数加密法
在讲仿射加密法之前,我们先说乘数加密法
凯撒加密法中,加密和解密符号涉及把他们转化成数字,加上或者减去密钥,再把新的数字换成符号
如果我们不使用加而用乘法,可以吗?当然,只需要用mod运算把一个大的整数唯一映射到一个字符
就好了
乘数加密法的优势是,可以使用一个很大的key, 而不局限于凯撒加密法的密钥空间 0~25
缺点是A总是映射到A,为了克服这种缺点,我们再使用一次凯撒加密法即可
缺点是并不是所有key都能使用
比如若选择key=8,则有
A | B | C | D | E | F | G | H | I | J | K | L | M |
A | I | Q | Y | G | O | W | E | M | U | C | K | S |
N | O | P | Q | R | S | T | U | V | W | X | Y | Z |
A | I | Q | Y | G | O | W | E | M | U | C | K | S |
这甚至不是一一映射!!没有用
仿射加密法
乘数加密法+凯撒加密法 = 仿射加密法
keyA应当满足一定条件:
密钥A数字和符号集的大小必须互质,也就是说gcd(密钥A, 符号集大小) = 1
模算术运算 加法and乘法
模b运算中,整数x的乘法逆元是y:使得(x × y)mod b = 1
模b运算中,整数x的加法逆元是y:使得(x + y)mod b = 0
一般来说,只有当一个整数与n互素的时候,他才会在Zn中存在一个乘法逆元
使用扩展欧几里得算法可以方便地找到一个数字的模逆,python实现:
def findModInverse(a, m): # Returns the modular inverse of a % m, which is # the number x such that a*x % m = 1 # 如果a 和 m 不互质,则不存在模逆 if gcd(a, m) != 1: return None # no mod inverse if a & m aren't relatively prime # Calculate using the Extended Euclidean Algorithm: u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: q = u3 // v3 # // is the integer division operator v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q * v3), v1, v2, v3 return u1 % m
输出:
>>> import cryptomath >>> cryptomath.findModInverse(7, 26) 15 >>> cryptomath.findModInverse(37, 41) 10 >>> cryptomath.findModInverse(8953851, 26) 17 >>>
前面是开胃菜,接下来要展示点真正下饭的东西了!
仿射加密法
我们要使用两个密钥,记住一个数是方便地。使用简单的数学技巧将一个数分解成两个key
如使用key = 2023 则:
keyA = key // len(密文符号集) 2023 // 95 = 21
keyB = key % len(密文符号集) 2023 % 95 = 28
显然 keyA * len(密文符号集) + keyB = key (21 * 95) + 28 = 2023
这两个密钥显然不能在程序中出差错,所以用元组保存是可取的。
元组是python中的一种数据类型。可以通过索引和分片来访问,但是很重要的一点,元组里面的值不能被更改!
对于生成的密钥,我们要确认其可行性:
- keyA不能为1
- keyB不能为0
- keyA、keyB必须大于0,keyB不大于ken(密文符号集) -1
- 最重要的一点,满足keyA必须与密文符号集的大小互质,才能使得这是一个一一映射。
仿射加密算法
# Affine Cipher # http://inventwithpython.com/hacking (BSD Licensed) ''' 1.The affine cipher can be thought of as the combination of which two other ciphers? 2.What is a tuple? 3.How is a tuple different from a list? 4.Why does having Key A be 1 make the affine cipher weak? 5.Why does having Key B be 0 make the affine cipher weak? answer 1.The shift and multiplicative ciphers. 2.A data type in Python similar to lists, except immutable. 3.Tuples are immutable, meaning they cannot have their items changed. 4.Because multiplying the symbol's number by 1 does not change it. 5.Because adding 0 to the symbol's number does not change it. ''' import sys, pyperclip, cryptomath, random SYMBOLS = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~""" # note the space at the front 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' # set to 'encrypt' or 'decrypt' 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) pyperclip.copy(translated) print('Full %sed text copied to clipboard.' % (myMode)) 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': sys.exit('The affine cipher becomes incredibly weak when key A is set to 1. Choose a different key.') if keyB == 0 and mode == 'encrypt': sys.exit('The affine cipher becomes incredibly weak when key B is set to 0. Choose a different key.') if keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) - 1: sys.exit('Key A must be greater than 0 and Key B must be between 0 and %s.' % (len(SYMBOLS) - 1)) if cryptomath.gcd(keyA, len(SYMBOLS)) != 1: sys.exit('Key A (%s) and the symbol set size (%s) are not relatively prime. Choose a different key.' % (keyA, len(SYMBOLS))) def encryptMessage(key, message): keyA, keyB = getKeyParts(key) checkKeys(keyA, keyB, 'encrypt') ciphertext = '' for symbol in message: if symbol in SYMBOLS: # encrypt this symbol symIndex = SYMBOLS.find(symbol) # 乘数加密 + 凯撒加密 = 仿射加密方法 ciphertext += SYMBOLS[(symIndex * keyA + keyB) % len(SYMBOLS)] else: #如果在符号集中没有这个特殊符号,不能舍弃,保留,保证密文和明文等长 ciphertext += symbol # just append this symbol unencrypted return ciphertext def decryptMessage(key, message): keyA, keyB = getKeyParts(key) checkKeys(keyA, keyB, 'decrypt') plaintext = '' #需要找到keyA的乘法模逆 modInverseOfKeyA = cryptomath.findModInverse(keyA, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: # decrypt this symbol symIndex = SYMBOLS.find(symbol) plaintext += SYMBOLS[(symIndex - keyB) * modInverseOfKeyA % len(SYMBOLS)] else: plaintext += symbol # just append this symbol undecrypted return plaintext def 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 affineCipher.py is run (instead of imported as a module) call # the main() function. if __name__ == '__main__': main()
输出:
Key: 2023 Encrypted text: fX<*h>}(rTH<Rh()?<?T]TH=T<rh<tT<*_))T?<ISrT))I~TSr<Ii<Ir<*h()?<?T*TI=T<_<4(>_S<ISrh<tT)IT=IS~<r4_r<Ir<R_]<4(>_SEf<0X)_S<k(HIS~ Full encrypted text copied to clipboard. PS D:PiaYiejczhang密码学py密码学编程教学代码>
暴力破译仿射密码
那么,暴力破译的话,就必须明白仿射加密法有多少个密钥?
keyB: “回调”受限:1 ~ len(密文符号集)
keyA: 与len(密文符号集)互质的数,是否也会出现“回调”的情况?
SYMBOLS = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~""" # note the space at the front
len(SYMBOLS) = 95
测试一下
# This program proves that the keyspace of the affine cipher is limited # to len(SYMBOLS) ^ 2. import affineCipher, cryptomath message = 'Make things as simple as possible, but not simpler.' for keyA in range(2, 100): key = keyA * len(affineCipher.SYMBOLS) + 1 if cryptomath.gcd(keyA, len(affineCipher.SYMBOLS)) == 1: print(keyA, affineCipher.encryptMessage(key, message))
输出:
n-2020.7.96456pythonFileslibpythondebugpylauncher' '49271' '--' 'd:PiaYiejczhangSJTU密码学py密码学编程教学代码affineKeyTest.py' 2 {DXL!jRT^Ph!Dh!hTZL!Dh!b`hhTFZL9!Flj!^`j!hTZLf= 3 I&D2!_;>M8!&!>JSG2!&!SP\>)G2E!)b_!MP_!>JSG2YK 4 vg0w!T$(< P!gP!P(8D4w!gP!D@PP(k4wQ!kXT!<@T!P(8D4wLY 6 q+gC!>U[yO8!+8!8[s&mC!+8!& 88[1mCi!1D>!y >!8[s&mC2u 7 ?lS)!3>Eh7,!l,!,EavZ)!l,!vo,,EsZ)u!s:3!ho3!,EavZ)%$ 8 lN?n!('/W~ !N ! /OgGn!N !g_ /VGn"!V0(!W_(! /OgGnw2 9 :0+T!|oxFfs!0s!sx=X4T!0s!XOssx94T.!9&|!FO|!sx=X4Tj@ 11 5Sb !fAL$6[!S[![Lx:m !S[!:/[[L^m F!^qf!$/f![Lx:m P 12 b5Ne![*6r}O!5O!O6f+Ze!5O!+~OO6AZeR!Ag[!r~[!O6f+ZeCj 13 0v:K!Pr aeC!vC!C T{GK!vC!{nCC $GK^!$]P!anP!C T{GK6x 14 ]X&1!E[iPM7!X7!7iBl41!X7!l^77if41j!fSE!P^E!7iBl41)' 16 X{]!/-=.|~!{~!~=}Nm!{~!N>~~=,m#!,?/!.>/!~=}Nm C 17 &]IB!$u'|dr!]r!r'k?ZB!]r!?.rr'nZB/!n5$!|.$!r'k?ZBaQ 18 S?5(!x^pkLf!?f!fpY0G(!?f!0}ffpQG(;!Q+x!k}x!fpY0G(T_ 21 {DX9!Wx.8cB!DB!B.#bm9!DB!bMBB.Ym9_!YlW!8MW!B.#bm9-* 22 I&D~!Law'K6!&6!6wpSZ~!&6!S=66w<Z~k!<bL!'=L!6wpSZ~ 8 23 vg0d!AJau3*!g*!*a^DGd!g*!D-**a~Gdw!~XA!u-A!*a^DGdrF 24 DI{J!63Kdz}!I}!}KL54J!I}!5|}}Ka4J$!aN6!d|6!}KL54JeT 26 ?lSu! d~BJe!le!e~(vmu!le!vee~'mu<!': !B !e~(vmuKp 27 lN?[!tMh12Y!NY!YhugZ[!NY!gLYYhiZ[H!i0t!1Lt!YhugZ[>~ 28 :0+A!i6R yM!0M!MRcXGA!0M!X<MMRLGAT!L&i! <i!MRcXGA1- 29 gqv'!^~<naA!qA!A<QI4'!qA!I,AA</4'`!/{^!n,^!A<QI4'$; 31 b5NR!HPoL1)!5)!)o-+mR!5)!+k))oTmRx!TgH!LkH!)o-+mRiW 32 0v:8!=9Y;x|!v|!|Yz{Z8!v|!{[||Y7Z8%!7]=!;[=!|Yz{Z8e 33 ]X&}!2"C*`p!Xp!pChlG}!Xp!lKppCyG}1!yS2!*K2!pChlG}Os 34 +:qc!'j-xHd!:d!d-V]4c!:d!];dd-4c=!I'!x;'!d-V]4cB" 36 &]I/!p<`VwL!]L!L`2?m/!]L!?zLL`"m/U!"5p!Vzp!L`2?m/(> 37 S?5t!e%JE_@!?@!@J 0Zt!?@!0j@@JdZta!d+e!Eje!@J 0ZtzL 39 Nbl@!OV}#/(!b(!(}[q4@!b(!qJ((}*4@y!*vO!#JO!(}[q4@`h 41 I&Dk!9(Q`^o!&o!oQ7Smk!&o!S*ooQOmk2!Ob9!`*9!oQ7SmkF% 42 vg0Q!.p;OFc!gc!c;%DZQ!gc!Dycc;2ZQ>!2X.!Oy.!c;%DZQ93 43 DI{7!#Y%>.W!IW!W%r5G7!IW!5iWW%tG7J!tN#!>i#!W%r5G7,A 44 q+g|!wBn-uK!+K!Kn`&4|!+K!&YKKnW4|V!WDw!-Yw!Kn`&4|~O 46 lN?H!asBjE3!N3!3B<gmH!N3!g933B|mHn!|0a!j9a!3B<gmHdk 47 :0+.!V\,Y-'!0'!',*XZ.!0'!X)'',_Z.z!_&V!Y)V!',*XZ.Wy 48 gqvs!KEuHtz!qz!zuwIGs!qz!IxzzuBGs'!B{K!HxK!zuwIGsJ( 49 5SbY!@._7 !Sn!n_e:4Y!Sn!:hnn_%4Y3!%q@!7h@!n_e:4Y=6 51 0v:%!*_3t,V!vV!V3A{m%!vV!{HVV3Jm%K!J]*!tH*!V3A{m%#R 52 ]X&j!~H|csJ!XJ!J|/lZj!XJ!l8JJ|-ZjW!-S~!c8~!J|/lZju` 53 +:qP!s1fR[>!:>!>f|]GP!:>!](>>foGPc!oIs!R(s!>f|]GPhn 54 X{]6!hyPAC2!{2!2PjN46!{2!Nw22PR46o!R?h!Awh!2PjN46[| 56 S?5a!RK$~ry!?y!y$F0ma!?y!0Wyy$wma(!w+R!~WR!y$F0maA9 58 Nbl-!<|WBa!ba!aW"qG-!ba!q7aaW=G-@!=v<!7<!aW"qG-'U 59 {DXr!1eAK*U!DU!UAob4r!DU!b'UUA 4rL! l1!K'1!UAob4ryc 61 vg0>!z7t)Y=!g=!=tKDm>!g=!Df==tEm>d!EXz!)fz!=tKDm>_ 62 DI{$!o ^wA1!I1!1^95Z$!I1!5V11^(Z$p!(No!wVo!1^95Z$R. 63 q+gi!dhHf)%!+%!%H'&Gi!+%!&F%%HjGi|!jDd!fFd!%H'&GiE< 64 ?lSO!YQ2Upx!lx!x2tv4O!lx!v6xx2M4O)!M:Y!U6Y!x2tv4O8J 66 :0+z!C#e3@`!0`!`ePXmz!0`!Xu``ermzA!r&C!3uC!`ePXmz}f 67 gqv`!8kO"(T!qT!TO>IZ`!qT!IeTTOUZ`M!U{8!"e8!TO>IZ`pt 68 5SbF!-T9poH!SH!H9,:GF!SH!:UHH98GFY!8q-!pU-!H9,:GFc# 69 b5N,!"=#_W<!5<!<#y+4,!5<!+E<<#z4,e!zg"!_E"!<#y+4,V1 71 ]X&W!knV='$!X$!$VUlmW!X$!l%$$V@mW}!@Sk!=%k!$VUlmW<M 72 +:q=!`W@,nw!:w!w@C]Z=!:w!]tww@#Z=*!#I`!,t`!w@C]Z=/[ 73 X{]#!U@*zVk!{k!k*1NG#!{k!Ndkk*eG#6!e?U!zdU!k*1NG#"i 74 &]Ih!J)si>_!]_!_s~?4h!]_!?T__sH4hB!H5J!iTJ!_s~?4htw 77 Nbly!)C16U;!b;!;1HqZy!b;!q$;;1PZyf!Pv)!6$)!;1HqZyMB 78 {DX_!},z%=/!D/!/z6bG_!D/!bs//z3G_r!3l}!%s}!/z6bG_@P 79 I&DE!rtds%#!&#!#d$S4E!&#!Sc##du4E~!ubr!scr!#d$S4E3^ 81 DI{p!F8QTj!Ij!j8_5mp!Ij!5Cjj8;mp7!;N!QC!j8_5mpxz 82 q+gV!Q/"@<^!+^!^"M&ZV!+^!&3^^"}ZVC!}DQ!@3Q!^"M&ZVk) 83 ?lS<!Fwk/$R!lR!Rk;vG<!lR!v#RRk`G<O!`:F!/#F!Rk;vG<^7 84 lN?"!;`U}kF!NF!FU)g4"!NF!grFFUC4"[!C0;!}r;!FU)g4"QE 86 gqvM!%2)[;.!q.!.)dImM!q.!IR..)hmMs!h{%![R%!.)dImM7a 87 5Sb3!yzrJ#"!S"!"rR:Z3!S"!:B""rKZ3 !Kqy!JBy!"rR:Z3*o 88 b5Nx!nc9ju!5u!u@+Gx!5u!+2uu.Gx,!.gn!92n!u@+Gx|} 89 0v:^!cLF(Ri!vi!iF.{4^!vi!{"iiFp4^8!p]c!("c!iF.{4^o, 91 +:q*!M}ye"Q!:Q!Qyi]m*!:Q!]aQQy6m*P!6IM!eaM!Qyi]m*UH 92 X{]o!BfcTiE!{E!EcWNZo!{E!NQEEcxZo!x?B!TQB!EcWNZoHV 93 &]IU!7OMCQ9!]9!9ME?GU!]9!?A99M[GUh![57!CA7!9ME?GU;d 94 S?5;!,8729-!?-!-7304;!?-!01--7>4;t!>+,!21,!-7304;.r 96 Nblf!uijoht!bt!tjnqmf!bt!qpttjcmf-!cvu!opu!tjnqmfs/ 97 {DXL!jRT^Ph!Dh!hTZL!Dh!b`hhTFZL9!Flj!^`j!hTZLf= 98 I&D2!_;>M8!&!>JSG2!&!SP\>)G2E!)b_!MP_!>JSG2YK 99 vg0w!T$(< P!gP!P(8D4w!gP!D@PP(k4wQ!kXT!<@T!P(8D4wLY
我们发现,使用2和使用97得到的密文是相同的,使用3和使用98得到的密文是相同的,使用4和使用99得到的密文是相同的,
显然仿射加密法的keyA和keyB都会出现“回调”现象,他们都受限于秘文符号集的大小!
95 * 95 = 9025种 keyA和keyB组合,但是还要减去keyA中不能和len(密文符号集)互质的数,剩下的这些就是暴力破译要考虑的密钥集。
破译仿射密码
# Affine Cipher Hacker # http://inventwithpython.com/hacking (BSD Licensed) import pyperclip, affineCipher, detectEnglish, cryptomath SILENT_MODE = False #SILENT_MODE = True def main(): # You might want to copy & paste this text from the source code at # http://invpy.com/affineHacker.py 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: # The plaintext is displayed on the screen. For the convenience of # the user, we copy the text of the code to the clipboard. print('Copying hacked message to clipboard:') print(hackedMessage) pyperclip.copy(hackedMessage) else: print('Failed to hack encryption.') def hackAffine(message): print('Hacking...') # Python programs can be stopped at any time by pressing Ctrl-C (on # Windows) or Ctrl-D (on Mac and Linux) print('(Press Ctrl-C or Ctrl-D to quit at any time.)') # brute-force by looping through every possible key # Python中的指数运算符号** # 暴力破译所有可能的密钥是 0 到affineCipher.SYMBOLS) ** 2 # getKeyParts(key)返的是一个元组 用下标.[0]访问ke'y for key in range(len(affineCipher.SYMBOLS) ** 2): keyA = affineCipher.getKeyParts(key)[0] if cryptomath.gcd(keyA, len(affineCipher.SYMBOLS)) != 1: # continue 跳过当前的循环中的操作继续下一轮操作,而不是跳出循环 continue decryptedText = affineCipher.decryptMessage(key, message) if not SILENT_MODE: print('Tried Key %s... (%s)' % (key, decryptedText[:40])) # 每个key都做了detectEnglish.isEnglish(decryptedText),但是不一定近到了if代码块, # 只有isEnglish为True才得出可能的密文 if detectEnglish.isEnglish(decryptedText): # Check with the user if the decrypted key has been found. 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:') response = input('> ') if response.strip().upper().startswith('D'): return decryptedText return None # If affineHacker.py is run (instead of imported as a module) call # the main() function. if __name__ == '__main__': main()