仿射密码为单表加密的一种,字母系统中所有字母都藉一简单数学方程加密,对应至数值,或转回字母。 ——百度百科
辗转相除法 与 寻找模逆:
1 # Cryptomath Module 2 # http://inventwithpython.com/hacking (BSD Licensed) 3 4 def gcd(a, b): 5 # Return the GCD of a and b using Euclid's Algorithm 6 while a != 0: 7 a, b = b % a, a 8 return b 9 10 11 def findModInverse(a, m): 12 # Returns the modular inverse of a % m, which is 13 # the number x such that a*x % m = 1 14 15 if gcd(a, m) != 1: 16 return None # no mod inverse if a & m aren't relatively prime 17 18 # Calculate using the Extended Euclidean Algorithm: 19 u1, u2, u3 = 1, 0, a 20 v1, v2, v3 = 0, 1, m 21 while v3 != 0: 22 q = u3 // v3 # // is the integer division operator 23 v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q * v3), v1, v2, v3 24 return u1 % m
仿射密码是一种替换密码。它是一个字母对一个字母的。它的加密函数是 ,其中a和m互质,m是字母的数目。
乘法加密的缺点是字母a总是加密到字母a,应为0乘以任何数都是0。可以再对其使用凯撒密码进行二次加密。
注意: 在仿射加密里,密钥a数字与字符集大小必须互质。即 gcd(密钥, 符号集的大小)== 1.
加解密步骤:
- 输入密钥并进行判断
- 得到字符在字符集里的位置,并对其计算得到密文
1 # Affine Cipher 2 # http://inventwithpython.com/hacking (BSD Licensed) 3 4 import sys, pyperclip, cryptomath, random 5 SYMBOLS = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~""" # note the space at the front 6 7 8 def main(): 9 myMessage = """"A computer would deserve to be called intelligent if it could deceive a human into believing that it was human." -Alan Turing""" 10 myKey = 2023 11 myMode = 'encrypt' # set to 'encrypt' or 'decrypt' 12 13 if myMode == 'encrypt': 14 translated = encryptMessage(myKey, myMessage) 15 elif myMode == 'decrypt': 16 translated = decryptMessage(myKey, myMessage) 17 print('Key: %s' % (myKey)) 18 print('%sed text:' % (myMode.title())) 19 print(translated) 20 pyperclip.copy(translated) 21 print('Full %sed text copied to clipboard.' % (myMode)) 22 23 24 def getKeyParts(key): 25 keyA = key // len(SYMBOLS) 26 keyB = key % len(SYMBOLS) 27 return (keyA, keyB) 28 29 30 def checkKeys(keyA, keyB, mode): 31 if keyA == 1 and mode == 'encrypt': 32 sys.exit('The affine cipher becomes incredibly weak when key A is set to 1. Choose a different key.') 33 if keyB == 0 and mode == 'encrypt': 34 sys.exit('The affine cipher becomes incredibly weak when key B is set to 0. Choose a different key.') 35 if keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) - 1: 36 sys.exit('Key A must be greater than 0 and Key B must be between 0 and %s.' % (len(SYMBOLS) - 1)) 37 if cryptomath.gcd(keyA, len(SYMBOLS)) != 1: 38 sys.exit('Key A (%s) and the symbol set size (%s) are not relatively prime. Choose a different key.' % (keyA, len(SYMBOLS))) 39 40 41 def encryptMessage(key, message): 42 keyA, keyB = getKeyParts(key) 43 checkKeys(keyA, keyB, 'encrypt') 44 ciphertext = '' 45 for symbol in message: 46 if symbol in SYMBOLS: 47 # encrypt this symbol 48 symIndex = SYMBOLS.find(symbol) 49 ciphertext += SYMBOLS[(symIndex * keyA + keyB) % len(SYMBOLS)] 50 else: 51 ciphertext += symbol # just append this symbol unencrypted 52 return ciphertext 53 54 55 def decryptMessage(key, message): 56 keyA, keyB = getKeyParts(key) 57 checkKeys(keyA, keyB, 'decrypt') 58 plaintext = '' 59 modInverseOfKeyA = cryptomath.findModInverse(keyA, len(SYMBOLS)) 60 61 for symbol in message: 62 if symbol in SYMBOLS: 63 # decrypt this symbol 64 symIndex = SYMBOLS.find(symbol) 65 plaintext += SYMBOLS[(symIndex - keyB) * modInverseOfKeyA % len(SYMBOLS)] 66 else: 67 plaintext += symbol # just append this symbol undecrypted 68 return plaintext 69 70 71 def getRandomKey(): 72 while True: 73 keyA = random.randint(2, len(SYMBOLS)) 74 keyB = random.randint(2, len(SYMBOLS)) 75 if cryptomath.gcd(keyA, len(SYMBOLS)) == 1: 76 return keyA * len(SYMBOLS) + keyB 77 78 79 # If affineCipher.py is run (instead of imported as a module) call 80 # the main() function. 81 if __name__ == '__main__': 82 main()
测试其有多少密钥:
1 # This program proves that the keyspace of the affine cipher is limited 2 # to len(SYMBOLS) ^ 2. 3 4 import affineCipher, cryptomath 5 6 message = 'Make things as simple as possible, but not simpler.' 7 for keyA in range(2, 100): 8 key = keyA * len(affineCipher.SYMBOLS) + 1 9 10 if cryptomath.gcd(keyA, len(affineCipher.SYMBOLS)) == 1: 11 print(keyA, affineCipher.encryptMessage(key, message))
其大约有几千个密码,可暴力:
1 # Affine Cipher Hacker 2 # http://inventwithpython.com/hacking (BSD Licensed) 3 4 import pyperclip, affineCipher, detectEnglish, cryptomath 5 6 SILENT_MODE = False 7 8 def main(): 9 # You might want to copy & paste this text from the source code at 10 # http://invpy.com/affineHacker.py 11 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""" 12 13 hackedMessage = hackAffine(myMessage) 14 15 if hackedMessage != None: 16 # The plaintext is displayed on the screen. For the convenience of 17 # the user, we copy the text of the code to the clipboard. 18 print('Copying hacked message to clipboard:') 19 print(hackedMessage) 20 pyperclip.copy(hackedMessage) 21 else: 22 print('Failed to hack encryption.') 23 24 25 def hackAffine(message): 26 print('Hacking...') 27 28 # Python programs can be stopped at any time by pressing Ctrl-C (on 29 # Windows) or Ctrl-D (on Mac and Linux) 30 print('(Press Ctrl-C or Ctrl-D to quit at any time.)') 31 32 # brute-force by looping through every possible key 33 for key in range(len(affineCipher.SYMBOLS) ** 2): 34 keyA = affineCipher.getKeyParts(key)[0] 35 if cryptomath.gcd(keyA, len(affineCipher.SYMBOLS)) != 1: 36 continue 37 38 decryptedText = affineCipher.decryptMessage(key, message) 39 if not SILENT_MODE: 40 print('Tried Key %s... (%s)' % (key, decryptedText[:40])) 41 42 if detectEnglish.isEnglish(decryptedText): 43 # Check with the user if the decrypted key has been found. 44 print() 45 print('Possible encryption hack:') 46 print('Key: %s' % (key)) 47 print('Decrypted message: ' + decryptedText[:200]) 48 print() 49 print('Enter D for done, or just press Enter to continue hacking:') 50 response = input('> ') 51 52 if response.strip().upper().startswith('D'): 53 return decryptedText 54 return None 55 56 57 # If affineHacker.py is run (instead of imported as a module) call 58 # the main() function. 59 if __name__ == '__main__': 60 main()