Накопилась целая библиотека программ шифрования/дешифрования на python'e. Это учебные примеры, требовалось показать суть алгоритмов, поэтому выбран python. Жалко будет если пропадут Шифры Шифр Цезаря. Шифрование, дешифрование, взлом Шифрование/расшифровка/взлом грубой силой и поиск дешифрованного варианта Взлом с использованием словаря с английскими словами Аффинный шифр Шифрование, дешифрование Взлом Шифр Виженэра Шифрование, дешифрование, взлом Индекс совпадения Фридмана Шифр Enigma Шифрование, дешифрование Взлом Шифр Хилла Шифрование, дешифрование Взлом шифра Хилла 2х2 Шифр 4 квадратов Шифрование, дешифрование Взлом Шифр bifid Шифрование, дешифрование Взлом Шифр trifid Шифрование, дешифрование Взлом Шифр Lorenz Шифрование, дешифрование Взлом Шифр простой замены Шифрование, дешифрование Взлом С использованием словарного шаблона Начало Продолжение Окончание С использованием статистики английских квадрограмм С использованием частотного анализа, словаря, логики и здравого смысла Перестановочные шифры Шифр вертикальной перестановки Заполнение ходом шахматного коня Шифр «Изгородь» Шифр RSA Шифрование, дешифрование Взлом ADFGX шифр Шифрование, дешифрование Взлом Шифр «Квадрат Полибия» Шифрование, дешифрование Взлом Шифр Гронсфельда Шифрование, дешифрование Взлом Шифр Бофора Шифрование, дешифрование Взлом Шифр апоЖ Шифр с автоключом Шифрование, дешифрование Взлом Шифр ЦезаряШифр подстановки. Основан на сдвиге букв алфавита на фиксированное число позиций, влево или вправо в закольцованном алфавите. Число позиций составляет ключ шифра. Взлом осуществляется путем перебора всех возможных вариантов ключа. Мораль: для усложнения взлома включите в алфавит прописные и строчные латинские буквы, прописные и строчные буквы кириллицы, пробел, скобки (круглые «()», квадратные «[]», фигурные «{}», угловые «<>»), знаки препинания, цифры. Чем больше мощность алфавита ― тем больше вариантов ключа. Еще одна защита ― изменить порядок букв в алфавите Шифрование/дешифрование/взлом Код (Python): import re SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' class Caesar(object): def __init__(self,key): self.key = key % len(SYMBOLS) def remove_punctuation(self,text,filter='[^A-Z\s]'): return re.sub(filter,'',text.upper()) def encipher(self,string): string = self.remove_punctuation(string) ret = '' for symbol in string.upper(): if symbol in SYMBOLS: symbolIndex = SYMBOLS.find(symbol) ret += SYMBOLS[(symbolIndex - self.key) % len(SYMBOLS)] return ret def decipher(self,string): self.key *= -1 return self.encipher(string) def main(): plaintext = 'defend the east wall of the castle' print("Исходный текст:\n" + plaintext) ciphertext = Caesar(key=3).encipher(plaintext) print("\nЗашифрованный текст:\n" + ciphertext) plaintext = Caesar(key=3).decipher(ciphertext) print("\nРасшифрованный текст:\n" + plaintext) print("\nВзлом шифра\n") for key in range(1, len(SYMBOLS)-1): text = Caesar(key).decipher(ciphertext) print('Key #%s: %s' % (key, text)) # Функция main() вызывается только в том случае, если файл Caesar.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Аффинный шифрМоноалфавитный шифр подстановки. При помощи модульной арифметики для каждой буквы [math]x[/math] исходного алфавита, вычисляется новая буква [math]y[/math], которая заменит [math]x[/math] в шифротексте. Функция шифрования [math]y=E(x)[/math] каждой букве [math]x[/math] соответствует [math]y=(a\cdot x+b)\bmod n[/math], [math]n[/math] — размер алфавита, пара [math]a[/math] и [math]b[/math] — ключ шифра. Значение [math]a[/math] должно быть выбрано таким, чтобы [math]a[/math] и [math]n[/math] были взаимно простыми числами. Функция расшифрования [math]x=D(y)[/math] каждой букве [math]y[/math] соответствует [math]x=(a^{−1}(y−b))\bmod n[/math], [math]a^{−1}[/math] — обратное к [math]a[/math] число по модулю [math]n[/math] удовлетворяет уравнению [math]1=(a\cdot a^{−1})\bmod n[/math]. Шифрование/дешифрование Код (Python): SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.' import random, sys def getKeyParts(key): keyA = key // len(SYMBOLS) keyB = key % len(SYMBOLS) return (keyA, keyB) def gcd(a, b): # Возвращает НОД чисел а и b, используя алгоритм Евклида while a != 0: a, b = b % a, a return b def findModInverse(a, m): # Возвращает модульное обращение а % m, т.е. число х, что а * х % m = 1 if gcd(a, m) != 1: return None # модульное обращение отсутствует, если а и m не являются взаимно простыми числами # Используем расширенный алгоритм Евклида u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: q = u3 // v3 # // - это оператор целочисленного деления v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q * v3), v1, v2, v3 return u1 % m def checkKeys(keyA, keyB, mode): if keyA == 1 and mode == 'encrypt': sys.exit('Cipher is weak if key A is 1. Choose a different key.') if keyB == 0 and mode == 'encrypt': sys.exit('Cipher is weak if key B is 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 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: # Зашифровать символ symbolIndex = SYMBOLS.find(symbol) ciphertext += SYMBOLS[(symbolIndex * keyA + keyB) % len(SYMBOLS)] else: ciphertext += symbol # присоединить символ без шифрования return ciphertext def decryptMessage(key, message): keyA, keyB = getKeyParts(key) checkKeys(keyA, keyB, 'decrypt') plaintext = '' modInverseOfKeyA = findModInverse(keyA, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: # Дешифровать символ symbolIndex = SYMBOLS.find(symbol) plaintext += SYMBOLS[(symbolIndex - keyB) * modInverseOfKeyA % len(SYMBOLS)] else: plaintext += symbol # присоединить символ без дешифрования return plaintext def main(): Key = 2894 fileobj=open("decrypted.txt",'r',encoding='utf-8') Message = fileobj.read() fileobj.close() encryptedMessage = encryptMessage(Key, Message) print(encryptedMessage) translated = decryptMessage(Key, encryptedMessage) print(translated) # Если файл affineCipher.py выполняется как программа, а не импортируется как модуль, вызвать функцию main() if __name__ == '__main__': main()
Взлом аффинного шифраАффинный шифр немного сложнее шифра Цезаря, но не обеспечивает большей безопасности. Количество возможных ключей если в сообщении используются только символы латинского алфавита (len(SYMBOLS)=26) равно 311. Для взлома сообщения зашифрованного аффинным шифром компьютер должен перебрать все возможные ключи и выбирать лучший. Чтобы найти лучший ключ, мы должны попытаться расшифровать зашифрованный текст с каждым ключом, а затем определить «пригодность» каждого фрагмента расшифрованного текста. Аффинный шифр имеет 2 ключа, [math]a[/math] и [math]b[/math]. [math]b[/math] может находиться в диапазоне от 1 до 25 (сдвиг на 0 и сдвиг на 26 соответствуют незашифрованному тексту), [math]a[/math] может иметь любое из значений 1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25, значение [math]a[/math] не должно быть кратно делителям 26 (2 и 13). Мы перебираем каждую из этих возможных комбинаций, которых насчитывается [math]311=12\cdot 26-1[/math], определяем пригодность каждой комбинации, а затем выбираем лучшую. affineCipher.py Код (Python): SYMBOLS = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЪЭЮЯ1234567890 !?.' import random, sys def getKeyParts(key): keyA = key // len(SYMBOLS) keyB = key % len(SYMBOLS) return (keyA, keyB) def gcd(a, b): # Возвращает НОД чисел а и b, используя алгоритм Евклида while a != 0: a, b = b % a, a return b def findModInverse(a, m): # Возвращает модульное обращение а % m, т.е. число х, что а * х % m = 1 if gcd(a, m) != 1: return None # модульное обращение отсутствует, если а и m не являются взаимно простыми числами # Используем расширенный алгоритм Евклида u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: q = u3 // v3 # // - это оператор целочисленного деления v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q * v3), v1, v2, v3 return u1 % m def checkKeys(keyA, keyB, mode): if keyA == 1 and mode == 'encrypt': sys.exit('Cipher is weak if key A is 1. Choose a different key.') if keyB == 0 and mode == 'encrypt': sys.exit('Cipher is weak if key B is 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 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.upper(): if symbol in SYMBOLS: # Зашифровать символ symbolIndex = SYMBOLS.find(symbol) ciphertext += SYMBOLS[(symbolIndex * keyA + keyB) % len(SYMBOLS)] else: ciphertext += symbol # присоединить символ без шифрования return ciphertext def decryptMessage(key, message): keyA, keyB = getKeyParts(key) checkKeys(keyA, keyB, 'decrypt') plaintext = '' modInverseOfKeyA = findModInverse(keyA, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: # Дешифровать символ symbolIndex = SYMBOLS.find(symbol) plaintext += SYMBOLS[(symbolIndex - keyB) * modInverseOfKeyA % len(SYMBOLS)] else: plaintext += symbol # присоединить символ без дешифрования return plaintext def main(): Key = 2894 fileobj=open("decrypted3.txt",'r',encoding='utf-8') Message = fileobj.read() fileobj.close() print("Исходный текст:\n" + Message) encryptedMessage = encryptMessage(Key, Message) print("\nЗашифрованный текст:\n" + encryptedMessage) fileobj=open("encrypted3.txt",'w',encoding='utf-8') fileobj.write(encryptedMessage) fileobj.close() translated = decryptMessage(Key, encryptedMessage) print("\nРасшифрованный текст:\n" + translated) # Если файл affineCipher.py выполняется как программа, а не импортируется как модуль, вызвать функцию main() if __name__ == '__main__': main() detectRussian.py Код (Python): # модуль распознавания русского языка, чтобы использовать данный модуль введите следующие строки: # import detectRussian # detectRussian.isRussian(someString) # возвращает True или False #(в каталоге программы должен быть файл "russian.txt" со всеми русскими #словами, содержащий по одному слову в строке. UPPERLETTERS = 'АББГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ' LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + ' \t\n' def loadDictionary(): dictionaryFile = open('russian.txt','r',encoding='utf-8') # получаем объект файла словаря russianWords = {} # создается переменная russianWords в виде пустого словаря for word in dictionaryFile.read().split('\n'): russianWords[word] = None dictionaryFile.close() return russianWords #Строковый метод split() разбивает переданную ему строку по переносам, #возвращая список из нескольких строк RUSSIAN_WORDS = loadDictionary() # вызывается функция loadDictionary() и возвращаемый ею словарь сохраняется # в переменной RUSSIAN_WORDS. def getRussianCount(message): message = message.lower() #символы строки преобразуются в верхний регистр message = removeNonLetters(message) #с помощью функции removeNonLetters() # из строки удаляются все небуквенные символы, такие как числа и знаки #препинания possibleWords = message.split() #метод split() разбивает строку на слова #и сохраняет их в переменной possibleWords if possibleWords == []: return 0.0 # слова отсутствуют, поэтому возвращаем 0.0. matches = 0 #для счетчика mаtсhеs устанавливается нулевое значение for word in possibleWords: #В цикле for мы проходим по всем словам в списке #possibleWords и проверяем существование каждого из них в словаре RUSSIAN_WORDS if word in RUSSIAN_WORDS: matches += 1 #Если слово содержится в словаре, значение счетчика #mаtсhеs инкрементируется return float(matches)/len(possibleWords) def removeNonLetters(message): lettersOnly = [] for symbol in message: if symbol in LETTERS_AND_SPACE: lettersOnly.append(symbol) return ''.join(lettersOnly) def isRussian(message, wordPercentage=20, letterPercentage=85): # По умолчанию 20% слов должны быть в файле словаря, # а 85% символов сообщения должны быть буквами или # пробелами (а не знаками препинания или числами) wordsMatch = getRussianCount(message) * 100 >= wordPercentage numLetters = len(removeNonLetters(message)) messageLettersPercentage = float(numLetters) / len(message) * 100 lettersMatch = messageLettersPercentage >= letterPercentage return wordsMatch and lettersMatch affineHacker.py Код (Python): # Программа для взлома аффинного шифра import affineCipher, detectRussian, time, sys SILENT_MODE = False def gcd(a, b): # Возвращает НОД чисел а и b, используя алгоритм Евклида while a != 0: a, b = b % a, a return b def main(): # Текст копируется из файла encrypted.txt fileObj = open('encrypted3.txt','r',encoding='utf-8') myMessage = fileObj.read() fileObj.close() hackedMessage = hackAffine(myMessage) if hackedMessage != None: # Вывод дешифрованного текста на экран; для удобства копируем его в буфер обмена print(hackedMessage) else: print('Не удалось взломать шифрование.') def hackAffine(message): print('Взламываю...') # Работу программы на Python можно в любой момент прервать, нажав <Ctrl+C> print('(Нажми Ctrl-C для выхода из программы.)') # Перебор всех возможных ключей методом грубой силы for key in range(len(affineCipher.SYMBOLS) ** 2): keyA = affineCipher.getKeyParts(key)[0] if gcd(keyA, len(affineCipher.SYMBOLS)) != 1: continue startTime = time.time() decryptedText = affineCipher.decryptMessage(key, message) russianPercentage = round(detectRussian.getRussianCount(decryptedText) * 100, 2) totalTime = round(time.time() - startTime, 3) if russianPercentage > 1: print('Процент русских слов: %5s%% Ключ: %4s' % (russianPercentage,key)) if detectRussian.isRussian(decryptedText): if russianPercentage > 40: # Подтвердить, что найден правильный ключ print() print('Возможный результат дешифровки:') print('Ключ: %s' % (key)) print('Расшифрованное сообщение: ' + decryptedText[:200]) print() print('Нажми на D чтобы завершить взлом, или любую другую клавишу чтобы продолжить:') response = input('> ') if response.strip().upper().startswith('D'): fileObj = open('decrypted2.txt','w',encoding='utf-8') fileObj.write(decryptedText) fileObj.close() return decryptedText return None #Если файл affineHacker.py выполняется как программа, а не импортируется как модуль, вызвать функцию main() if __name__ == '__main__': main()
Шифр ВиженэраПолиалфавитный шифр с использованием ключевого слова. Простая форма многоалфавитной замены. Шифр Виженэра состоит из последовательности нескольких шифров Цезаря с различными значениями сдвига. Для зашифровывания используется таблица алфавитов (tabula recta или квадрат Виженэра). На каждом этапе шифрования используются различные алфавиты, выбираемые в зависимости от символа ключевого слова. Открытый текст = ATTACK AT DAWN ключ = LEMON открытый текстATTACKATDOWNmi0191902100193142213ключLEMONLEMONLEki114121413114121413114ci=(mi+ki)mod 261123514152145171717шифротекстLXFOPVEFRNHRШифрование/дешифрование Код (Python): SYMBOLS = 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ 0123456789.,\n' def main(): with open('decrypted.txt','r',encoding='utf-8') as file: myMessage = file.read() print('Исходный текст:\n',myMessage) myKey = 'Пушкин' encryptedMessage = encryptVigenere(myKey, myMessage) with open('encrypted.txt','w',encoding='utf-8') as file: file.write(encryptedMessage) print('Зашифрованный текст:\n',encryptedMessage) Message = decryptVigenere(myKey, encryptedMessage) print("\nРасшифрованный текст:\n" + Message) def encryptVigenere(key, message): translated = [] # для создания зашифрованной строки keyIndex = 0 key = key.upper() for symbol in message.upper(): # Просмотр каждого символа в строке if symbol in SYMBOLS: num = SYMBOLS.find(symbol) num += SYMBOLS.find(key[keyIndex]) # складываем если шифруем num %= len(SYMBOLS) # для учета завертывания строки # Добавляем зашифрованный символ в конец translated: translated.append(SYMBOLS[num]) else: translated.append(symbol)# Добавить символ без шифрования keyIndex += 1 # Переходим к следующему символу ключа keyIndex %= len(key) return ''.join(translated) def decryptVigenere(key, message): translated = [] # для создания расшифрованной строки keyIndex = 0 key = key.upper() for symbol in message.upper(): # Просмотр каждого символа в строке if symbol in SYMBOLS: num = SYMBOLS.find(symbol) num -= SYMBOLS.find(key[keyIndex]) # вычитаем если расшифровываем num %= len(SYMBOLS) # для учета завертывания строки # Добавляем зашифрованный/расшифрованный символ в конец translated: translated.append(SYMBOLS[num]) else: translated.append(symbol)# Добавить символ без дешифрования. keyIndex += 1 # Переходим к следующему символу ключа keyIndex %= len(key) return ''.join(translated) #Функция main() вызывается только в том случае, если файл vigenereCipher.py был #запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
А в mode boolean уже не подходит? Надо обязательно строку? или хотя бы так Код (Python): def decryptCaesar(key,message): translated='' for symbol in message.upper(): if symbol in SYMBOLS: symbolIndex = SYMBOLS.find(symbol) translated += SYMBOLS[(symbolIndex + key) % len(SYMBOLS)] else: translated += symbol return translated def encryptCaesar(key,message): key *= -1 return decryptCaesar(key,message)
Шифр Enigmaшифрование/дешифрование Код (Python): # Программа шифрования и дешифрования на основе шифра Enigma LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ :-,.?'" REFLECTOR = "QYHOGNECVPUZTFDJAXWMKISRBL :-,.?'" DISKA = "JGDQOXUSCAMIFRVTPNEWKBLZYH :-,.?'" DISKB = "NTZPSFBOKMWRCJDIVLAEYUXHGQ :-,.?'" DISKC = "JVIUBHTCDYAKEQZPOSGXNRMWFL :-,.?'" class plugBoard: def __init__(self): self.connections = [] #Добавить провод к коммутационной панели def addWire(self, letterA, letterB): #конвертировать letterA в букву letterA = LETTERS.find(letterA.upper()) #конвертировать letterB в букву letterB = LETTERS.find(letterB.upper()) self.connections.append([letterA, letterB]) #Получите букву с другой стороны соединения def getLetter(self, charLetter): #Пройдитесь по всем соединениям, чтобы найти соответствующую букву for i in self.connections: if i[0] == charLetter: return i[1] elif i[1] == charLetter: return i[0] #Если буква не найдена, вернуть входящий символ return charLetter; class reflectorWheel: def __init__(self, settings): self.rotorWires = [] self.numOfSymbols = len(LETTERS) self.readSettings(settings) #Прочитать настройки def readSettings(self, settingsStr): pos = 0; for c in settingsStr: self.rotorWires.append(LETTERS.find(c.upper())) pos += 1 #Занять следующую позицию def getConnection(self, symbolNumber): return self.rotorWires[symbolNumber] class rotorWheel: def __init__(self, settings = "", turnOverSymbol = "a"): self.rotorWires = [] self.numOfSymbols = len(LETTERS) self.turnOverSymbol = turnOverSymbol if settings == "": self.scrambleConnections() else: self.readSettings(settings) #Прочитать настройки def readSettings(self, settingsStr): pos = 0; for c in settingsStr: self.rotorWires.append(LETTERS.find(c)) pos += 1 #Зашифрованное соединение def scrambleConnections(self): for i in range(self.numOfSymbols): foundUnique = 0 while not foundUnique: foundUnique = 1 randomMatch = random.randint(0, self.numOfSymbols) for wire in self.rotorWires: if wire == randomMatch: foundUnique = 0 self.rotorWires.append(randomMatch) #Повернуть список def rotate(self): self.rotorWires = [self.rotorWires[-1]] + self.rotorWires[:-1] #Получить позицию на другой стороне, 0 = налево, 1 = направо def getConnection(self, symbolNumber, direction): if direction == 1: return self.rotorWires[symbolNumber] else: pos = 0 for wire in self.rotorWires: if wire == symbolNumber: return pos pos += 1 class rotorColumn: def __init__(self): self.rotorWheels = [] self.enteredLetters = 0 #Поворотный механизм def turnOver(self): #Повернуть первый диск, rightMost self.rotorWheels[0].rotate() currentPos = 0 #Поверните первый диск и проверьте, достигает ли он turnOverSymbol for rotorWheel in self.rotorWheels: #Если бы диск достиг turnOverSymbol, повернуть диск вправо if rotorWheel.rotorWires[0] == rotorWheel.turnOverSymbol: #If rotorWheel exists on the left if currentPos < len(self.rotorWheels): self.rotorWheels[currentPos + 1].rotate() currentPos += 1 #добавить rotorWheel в колонку def addWheel(self, rotorWheel): self.rotorWheels.append(rotorWheel) def sendThroughSymbol(self, symbolValue, direction): currentValue = symbolValue if direction == 1: for rotorWheel in self.rotorWheels: currentValue = rotorWheel.getConnection(currentValue, direction) else: for rotorWheel in reversed(self.rotorWheels): currentValue = rotorWheel.getConnection(currentValue, 0) return currentValue class enigmaMachine: def __init__(self): self.rotorColumn = rotorColumn() self.plugBoard = plugBoard() self.reflector = None def encryptStr(self, message): encryptedMessage = "" for c in message: newSymbol = self.getSymbol(LETTERS.find(c.upper())) encryptedMessage += LETTERS[newSymbol] return encryptedMessage #передать букву def getSymbol(self, symbolVal): #Через коммутационная панель nextSymbol = self.plugBoard.getLetter(symbolVal) #Через rotorColumn nextSymbol = self.rotorColumn.sendThroughSymbol(symbolVal, 1) #Через рефлектор nextSymbol = self.reflector.getConnection(nextSymbol) #обратно через rotorColumn nextSymbol = self.rotorColumn.sendThroughSymbol(nextSymbol, 0) #обратно через коммутационную панель nextSymbol = self.plugBoard.getLetter(nextSymbol) #вращать диски self.rotorColumn.turnOver() return nextSymbol #добавить диск на ротор def addWheel(self, settings = "", turnOverSymbol = "A"): tempRotorWheel = rotorWheel(settings, turnOverSymbol) self.rotorColumn.addWheel(tempRotorWheel) #добавить reflectorWheel в Энигму def addReflector(self, settings = ""): self.reflector = reflectorWheel(settings) def main(): #создаем Энигму A enigmaMachineA = enigmaMachine() #настраиваем начальные позиции дисков enigmaMachineA.addWheel(DISKA, "Q") enigmaMachineA.addWheel(DISKB, "E") enigmaMachineA.addWheel(DISKC, "V") #добавляем рефлектор enigmaMachineA.addReflector(REFLECTOR) #создаем Энигму B enigmaMachineB = enigmaMachine() #настраиваем начальные позиции дисков enigmaMachineB.addWheel(DISKA, "Q") enigmaMachineB.addWheel(DISKB, "E") enigmaMachineB.addWheel(DISKC, "V") #добавляем рефлектор enigmaMachineB.addReflector(REFLECTOR) fileobj=open("decrypted.txt",'r',encoding='utf-8') plain_text=fileobj.read() fileobj.close() print("Исходный текст:\n" + plain_text) message = '' for letter in plain_text.upper(): if letter in LETTERS: message += letter elif letter == '\n': message += 'q' encryptedMessage = enigmaMachineA.encryptStr(message.lower()) print("\nЗашифрованный текст:\n" + encryptedMessage) decryptedMessage = enigmaMachineB.encryptStr(encryptedMessage) Message = '' for i in range(1,len(decryptedMessage)): if decryptedMessage[i-1] == 'Q' and decryptedMessage[i] != 'U': Message += '\n' else: Message += decryptedMessage[i-1] print("\nРасшифрованный текст:\n" + Message.lower()) # Функция main() вызывается только в том случае, если файл EnigmaMachine.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Шифр Хиллаполиграммный шифр подстановки, основан на линейной алгебре и модульной арифметике. Шифр, который позволил на практике одновременно оперировать более чем с тремя символами. Шифрование/дешифрование Код (Python): # Шифр Хилла import numpy as np LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ,!' def main(): fileobj=open("decrypted.txt",'r',encoding='utf-8') plain_text=fileobj.read() fileobj.close() print("Исходный текст:\n" + plain_text) message = '' for letter in plain_text.upper(): if letter in LETTERS: message += letter elif letter == '\n': message += 'q' key = np.array([[ 6,13,18, 9,20], [24,16, 2,24, 7], [ 1,10,23,11,22], [ 2,13,15,16, 3], [ 7, 1, 7,21,17]]) encrypted_text = hill_cipher_encrypt(key,message) print('зашифрованный текст:\n',encrypted_text) decryptedMessage = hill_cipher_decrypt(key,encrypted_text) Message = '' for i in range(1,len(decryptedMessage)): if decryptedMessage[i-1] == 'Q' and decryptedMessage[i] != 'U': Message += '\n' else: Message += decryptedMessage[i-1] print('расшифрованный текст:\n',Message) def matrixinvmod26(M): Minv = np.linalg.inv(M) Mdet = np.linalg.det(M) Mod26invTable = {} for m in range(len(LETTERS)): for n in range(len(LETTERS)): if (m*n)%len(LETTERS)== 1: Mod26invTable[m] = n Mdet26 = Mdet % len(LETTERS) if Mdet26 in Mod26invTable: Mdetinv26 = Mod26invTable[Mdet26] else: Mdetinv26 = None Madj = Mdet*Minv Madj26 = Madj % len(LETTERS) Minv26 = (Mdetinv26*Madj26) % len(LETTERS) Minv26 = np.matrix.round(Minv26, 0)% len(LETTERS) return Minv26 #Функция предназначена шифрования сообщений def hill_cipher_encrypt(M, plaintext): plaintext = plaintext.upper() while len(plaintext) % 5 != 0: plaintext += "Q" char_list = [LETTERS.find(c) for c in plaintext] cit=[] for i in range(0,len(char_list),5): sublist = char_list[i:i+5] mat_mul = np.matmul(M,sublist) % len(LETTERS) mult_res = [LETTERS[i] for i in mat_mul] cit=cit+mult_res c=''.join(cit) return c #Функция предназначена для дешифрования сообщений def hill_cipher_decrypt(M, ciphertext): ciphertext = ciphertext.upper() Minv = matrixinvmod26(M) Minv = Minv.astype(int) char_list = [LETTERS.find(c) for c in ciphertext] pla=[] for i in range(0,len(char_list),5): sublist = char_list[i:i+5] mat_mul = np.matmul(Minv,sublist) % len(LETTERS) mult_res = [LETTERS[i] for i in mat_mul] pla=pla+mult_res while pla[-1] == 'Q': pla.pop() p=''.join(pla) return p # Если файл HillCipher.py выполняется как программа # (а не импортируется как модуль), вызвать функцию main() if __name__ == '__main__': main() Взлом шифра Хилла, использующего матрицу 2 на 2Поскольку шифр Хилла является линейным, нам нужно найти только 2 соответствия биграмм для определения ключевой матрицы. Например, если бы мы знали, что «th» шифруется как «gk», а «er» шифруется как «bd», можно решить систему уравнений и найти матрицу ключей шифрования. Воспользуемся этим фактом, чтобы взломать шифр. Метод, который здесь представлен, зависит от знания некоторых слов в сообщении. Представим, что есть зашифрованный текст: fupcmtgzkyukbqfjhuktzkkixtta И мы знаем, что где-то в тексте встречается «of the». Это означает, что один из следующих вариантов является правильным (шифр Хилла 2х2 шифрует пары символов): fupcmtgzkyukbqfjhuktzkkixtta1ofthe2ofthe3ofthe4ofthe5ofthe6ofthe. . .18ofthe. . .и так далее. Если бы вторая строка была правильной, тогда мы бы получили следующее: PC → FT, т.е. биграмма PC расшифровывается как FT, а MT → HE. Создадим систему уравнений заменив A на 0, B на 1, O на 14 и т. д.: [math]D\begin{bmatrix} P \\ C \end{bmatrix}=\begin{bmatrix} F \\ T \end{bmatrix}\Rightarrow D\begin{bmatrix} 15 \\ 2 \end{bmatrix}=\begin{bmatrix} 5 \\ 19 \end{bmatrix} \bmod 26[/math] а также следующее уравнение: [math]D\begin{bmatrix} M \\ T \end{bmatrix}=\begin{bmatrix} H \\ E \end{bmatrix}\Rightarrow D\begin{bmatrix} 12 \\ 19 \end{bmatrix}=\begin{bmatrix} 7 \\ 4 \end{bmatrix} \bmod 26[/math] Мы хотим найти матрицу [math]D[/math], которая является ключом дешифрования. Сначала объединим два уравнения в одно: [math]D\begin{bmatrix} 15 & 12 \\ 2 & 19 \end{bmatrix}=\begin{bmatrix} 5 & 7 \\ 19 & 4 \end{bmatrix} \bmod 26[/math] Тогда [math]D[/math] равно: [math]D=\begin{bmatrix} 5 & 7 \\ 19 & 4 \end{bmatrix}\begin{pmatrix}\begin{bmatrix} 15 & 12 \\ 2 & 19 \end{bmatrix}\end{pmatrix}^{-1} \bmod 26[/math] Нам нужно получить остаток по модулю 26 от инвертированной матрицы. Важно знать об обратном по модулю значении, определителе матрицы и присоединённой (союзной, взаимной) матрице. [math]P=\begin{bmatrix} a & b \\ c & d \end{bmatrix}[/math] матрица, которую мы инвертируем. [math]d[/math] — определитель [math]P[/math]. Требуется найти [math]P^{-1}[/math] (обратную [math]P[/math]), такую, что [math](P\times P^{-1})\bmod 26= E[/math], где [math]E[/math] — квадратная матрица, у которой все элементы главной диагонали равны 1, а остальные элементы равны 0. [math]E=\begin{bmatrix}1 & \cdots & 0\\ \vdots & \ddots & \vdots\\ 0 & \cdots & 1 \end{bmatrix}[/math] Если известно [math]P[/math], тогда [math]P^{-1}=d^{-1}\times adj(P)=\frac{\displaystyle{1}}{\displaystyle{ad-bc}}\begin{bmatrix} d & -b \\ -c & a \end{bmatrix}[/math], где [math](d\times d^{-1})\bmod 26= 1[/math], [math]adj(P)[/math] — присоединённая матрица [math]P[/math]. Определитель матрицы, которую мы инвертируем, равен [math]d=(a\cdot d-b\cdot c)\bmod 26= (15\cdot 19 - 12\cdot 2)\bmod 26= 261\bmod 26=1[/math]. Нам также нужно найти обратный определитель [math]d^{-1}[/math], который, к счастью, в данном случае равен [math]1[/math], потому что [math]1^{-1}=1[/math] определитель [math]d[/math] является своим собственным обратным определителем. Присоединённая матрица выглядит следующим образом: [math]adj\begin{pmatrix}\begin{bmatrix} 15 & 12 \\ 2 & 19 \end{bmatrix}\end{pmatrix}=\begin{bmatrix} 19 & -12 \\ -2 & 15 \end{bmatrix}[/math] Рассчитаем обратную матрицу: [math]P^{-1}=1^{-1}\times adj\begin{pmatrix}\begin{bmatrix} 15 & 12 \\ 2 & 19 \end{bmatrix}\end{pmatrix}\bmod 26=1\times\begin{bmatrix} 19 & -12 \\ -2 & 15 \end{bmatrix}\bmod 26=\begin{bmatrix} 19 & 14 \\ 24 & 15 \end{bmatrix}[/math] Находим [math]D[/math]: [math]D=\begin{bmatrix} 5 & 7 \\ 19 & 4 \end{bmatrix}\begin{pmatrix}\begin{bmatrix} 15 & 12 \\ 2 & 19 \end{bmatrix}\end{pmatrix}^{-1}\bmod 26=\begin{bmatrix} 5 & 7 \\ 19 & 4 \end{bmatrix}\begin{bmatrix} 19 & 14 \\ 24 & 15 \end{bmatrix}\bmod 26=\begin{bmatrix} 263 & 175 \\ 457 & 326 \end{bmatrix}\bmod 26=\begin{bmatrix} 3 & 19 \\ 15 & 14 \end{bmatrix}[/math] Теперь у нас есть возможный ключ к расшифровке шифротекста. Если мы попытаемся расшифровать с его помощью наше предложение, то получим:frfthezyssqyvfetlvbafvaconfz Что не является правильным ответом. Что пошло не так? Это означает, что одно из наших первоначальных предположений было неверным, предположение заключалось в том, что фрагмент «of the» начинался со второй позиции. Чтобы определить фактический ключ, нужно попробовать протащить «of the» через каждую позицию, пока не получим английский текст на выходе. Если бы мы использовали смещение 18, тогда бы получили совпадения KT → FT и ZK → HE, повторив описанную выше процедуру, получим матрицу: [math]D=\begin{bmatrix} 17 & 5 \\ 18 & 23 \end{bmatrix}[/math] Теперь, пытаемся расшифровать зашифрованный текст, получим:defendtheeastwallofthecastle Чего мы и хотели. Техника, которую мы здесь использовали, называется «перетаскивание шаблона», которая может быть очень утомительной, если выполняется вручную. Проще написать программу, которая сделает это за нас. Обобщение только до зашифрованного текста Если у нас нет шаблонной строки — фрагмента открытого текста, нам придется внести изменения в алгоритм расшифрования. Можно по очереди перетаскивать набор вероятных квадрограмм вдоль зашифрованного текста. Самая распространенная квадрограмма в английском языке — «THAT». Делаем «THAT» нашим шаблоном и выполняем описанную выше процедуру для всех различных стартовых позиций. Если не получим в результате английский текст — пробуем вторую по распространенности квадрограмму 'THER'. Продолжаем пробовать, пока не получим расшифрованное предложение, похожее на предложение на английском языке. КриптостойкостьСтандартный шифр Хилла уязвим для атаки по выбранному открытому тексту, потому что в нём используются линейные операции. Криптоаналитик, который перехватит [math]n^2[/math] пар символ сообщения/символ шифротекста сможет составить систему линейных уравнений, которую обычно несложно решить. Если окажется, что система не решаема, то необходимо всего лишь добавить ещё несколько пар символ сообщения/символ шифротекста. Такого рода расчёты средствами обычных алгоритмов линейной алгебры требует совсем немного времени. В связи с этим для увеличения криптостойкости в него должны быть добавлены какие-либо нелинейные операции. Комбинирование линейных операций, как в шифре Хилла, и нелинейных шагов привело к созданию подстановочно-перестановочной сети (например, сеть Фейстеля). Поэтому с определённой точки зрения можно рассматривать современные блочные шифры как вид полиграммных шифров.Длина ключаДлина ключа — это двоичный логарифм от количества всех возможных ключей. Существует [math]26^{n^2}[/math] матриц размера [math]n\times n[/math]. Значит, [math]log _{2}(26^{n^2})\approx 4{,}7\cdot n^2[/math] — верхняя грань длины ключа для шифра Хилла, использующего матрицы [math]n\times n[/math]. Это только верхняя грань, поскольку не каждая матрица обратима, а только такие матрицы могут быть ключом.
Шифр 4 квадратовСимметрическое шифрование, модифицированный вариант шифра Плейфера. Шифрует пары букв, попадает в категорию полиграфических подстановочных шифров. Использование биграмм делает шифр 4 квадратов менее восприимчивым к частотному криптоанализу, так как он должен быть применен к 262=676 возможным парам букв латинского алфавита, а не только к 26 символам для монографического замещения. Частотный анализ для биграмм возможен, но гораздо сложнее, для того, чтобы этот анализ был полезен, требуется больше шифротекста. Шифрование/дешифрование Код (Python): # Программа шифрования и дешифрования на основе шифра 4 квадратов import re nonLettersAndSpacePattern = re.compile('[^A-Z\s]') class Foursquare(object): def __init__(self, key1, key2): self.key1 = [k.upper() for k in key1] self.key2 = [k.upper() for k in key2] self.alph = 'ABCDEFGHIKLMNOPQRSTUVWXYZ' # нет буквы J assert len(self.key1)==len(self.alph), 'длина key1 не равна 25' assert len(self.key2)==len(self.alph), 'длина key2 не равна 25' def encipher_pair(self,a,b): arow,acol = self.alph.index(a)//5, self.alph.index(a)%5 brow,bcol = self.alph.index(b)//5, self.alph.index(b)%5 return (self.key1[arow*5+bcol], self.key2[brow*5+acol]) def decipher_pair(self,a,b): arow,acol = self.key1.index(a)//5, self.key1.index(a)%5 brow,bcol = self.key2.index(b)//5, self.key2.index(b)%5 return (self.alph[arow*5+bcol], self.alph[brow*5+acol]) def encipher(self,string): if len(string)%2 == 1: string = string + 'X' ret = '' for c in range(0,len(string),2): a,b = self.encipher_pair(string[c],string[c+1]) ret += a + b return ret def decipher(self,string): ret = '' for c in range(0,len(string.upper()),2): a,b = self.decipher_pair(string[c],string[c+1]) ret += a + b return ret def main(): fileobj=open("decrypted.txt",'r',encoding='utf-8') text=fileobj.read() fileobj.close() print(text) text = nonLettersAndSpacePattern.sub('', text.upper()) text = text.replace('J','I') text = text.replace('\n',' ') plain_text = text.replace(' ','Q') fs = Foursquare(key1='zgptfoihmuwdrcnykeqaxvsbl',key2='mfnbdcrhsaxyogvituewlqzkp') cipher_text = fs.encipher(plain_text) print(cipher_text) text = fs.decipher(cipher_text) Message = '' for i in range(1,len(text)): if text[i-1] == 'Q' and text[i] != 'U': Message += ' ' else: Message += text[i-1] print(Message) # Функция main() вызывается только в том случае, если файл foursqure.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Шифр апоЖШифр подстановки, каждый символ алфавита заменяется на «отзеркаленный» ([math]A\leftrightarrow Z[/math], [math]B\leftrightarrow Y[/math], [math]...[/math]). Из-за простоты, шифр уязвим к частотному анализу. Шифрование/дешифрование Код (Python): SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' import re class Atbash(object): def __init__(self,key): self.key = [k.upper() for k in key] def remove_punctuation(self,text,filter='[^A-Z ]'): return re.sub(filter,'',text.upper()) def encipher(self,string): string = self.remove_punctuation(string) ret = '' for symbol in string.upper(): if symbol in SYMBOLS: symbolIndex = SYMBOLS.find(symbol) ret += self.key[symbolIndex] return ret def decipher(self,string): return self.encipher(string) def main(): atbash = Atbash(SYMBOLS[::-1]) plaintext = 'defend the east wall of the castle' ciphertext = atbash.encipher(plaintext) print(ciphertext) plaintext = atbash.decipher(ciphertext) print(plaintext) # Функция main() вызывается только в том случае, если файл apozh.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Шифр bifidШифр, сочетающий квадрат Полибия с транспозицией и использующий дробление для достижения диффузии. шифрование/дешифрование Код (Python): import re class PolybiusSquare(object): def __init__(self,key,size,chars=None): self.key = ''.join([k.upper() for k in key]) self.chars = chars or 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[:size] self.size = size assert len(self.key)==size*size, 'в init недопустимый ключ: длина ключа должна быть size*size, ключ имеет длину'+str(len(key)) assert len(self.chars)==size, 'недопустимые символы в init: must have length=size, has length '+str(len(chars)) def remove_punctuation(self,text,filter='[^A-Z]'): return re.sub(filter,'',text.upper()) def encipher_char(self,ch): row = self.key.index(ch) // self.size col = self.key.index(ch) % self.size return self.chars[row] + self.chars[col] def decipher_pair(self,pair): row = self.chars.index(pair[0]) col = self.chars.index(pair[1]) return self.key[row*self.size + col] def encipher(self,string): # Строка для шифрования string = self.remove_punctuation(string)#,filter='[^'+self.key+']') ret = '' for c in range(0,len(string)): ret += self.encipher_char(string[c]) return ret # Зашифрованная строка. Зашифрованный текст будет в два раза длиннее открытого текста. def decipher(self,string): # Строка для расшифровки string = self.remove_punctuation(string)#,filter='[^'+self.chars+']') ret = '' for i in range(0,len(string),2): ret += self.decipher_pair(string[i:i+2]) return ret # Расшифрованная строка. Открытый текст будет в два раза короче шифртекста class Bifid(object): def __init__(self,key='phqgmeaylnofdxkrcvszwbuti',period=5): self.key = [k.upper() for k in key] self.pb = PolybiusSquare(self.key,size=5) self.period = period assert len(key)==25, 'в init неверный ключ: длина должна быть 25, сейчас длина '+str(len(key)) assert period>=1, 'неверный период в init: период должен быть >= 1' def encipher(self,string): # Строка для шифрования. string = self.pb.remove_punctuation(string) step1 = self.pb.encipher(string) evens = step1[::2] odds = step1[1::2] step2 = [] for i in range(0,len(string),self.period): step2 += evens[i:int(i+self.period)] step2 += odds[i:int(i+self.period)] return self.pb.decipher(''.join(step2)) # Зашифрованная строка def decipher(self,string): # Строка для расшифровки ret = '' string = string.upper() rowseq,colseq = [],[] # берем блоки длиной period, преобразуем из них rowseq, colseq for i in range(0,len(string),self.period): tempseq = [] for j in range(0,self.period): if i+j >= len(string): continue tempseq.append(self.key.index(string[i + j]) // 5) tempseq.append(self.key.index(string[i + j]) % 5) rowseq.extend(tempseq[0:int(len(tempseq)/2)]) colseq.extend(tempseq[int(len(tempseq)/2):]) for i in range(len(rowseq)): ret += self.key[rowseq[i]*5 + colseq[i]] return ret # Расшифрованная строка def main(): bf = Bifid('phqgmeaylnofdxkrcvszwbuti',5) fileobj = open("decrypted.txt",'r',encoding='utf-8') plain_text = fileobj.read() fileobj.close() print(plain_text) plain_text = plain_text.replace(' ',' ') plain_text = plain_text.replace('j','i') plain_text = plain_text.replace('\n','qq') plain_text = plain_text.replace(' ','Q') cipher_text = bf.encipher(plain_text) print(cipher_text) plain_text = bf.decipher(cipher_text) Message = '' for i in range(1,len(plain_text)): if plain_text[i-1] == 'Q' and plain_text[i] == 'Q': Message += '\n' i += 1 elif plain_text[i-1] == 'Q' and plain_text[i] != 'U': Message += ' ' else: Message += plain_text[i-1] print(Message) # Функция main() вызывается только в том случае, если файл bifid.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Шифр trifidШифр сочетающий методы дробления и транспозиции для достижения определенного уровня путаницы и распространения. Буквы зашифрованного текста зависят от трех букв открытого текста и трех букв ключа. Процесс шифрования: Подготовка открытого текста. Из него удаляют пробелы, знаки препинания и любые неалфавитные символы. Определение алфавита шифра Trifid. Буквы присваивают 3-х мерным координатам на основе слоев, строк и столбцов. Группировка по размеру. Организованные координаты группируют по указанному размеру группы, сортируют по столбцам. Перестановка координат по группе. Внутри каждой группы координаты перекомбинируют по горизонтали, по 3 координаты на группу, в результате получаются новые координаты. Генерация зашифрованного текста. С помощью новых координат находят соответствующие буквы в 3-х мерной сетке и подставляют их для зашифрованного текста Код (Python): from itertools import product key='APCZ WRLFBDKOTYUQGENHXMIVS' chars = (1,2,3) IND2L = dict(zip(list(product(chars,repeat=3)),key)) L2IND = dict(zip(key,list(product(chars,repeat=3)))) def encipher(string): ctext = "" for c in string.upper(): ctext += ''.join([str(i) for i in L2IND[c]]) return ctext def decipher(string): ret = '' for i in range(0,len(string),3): ind = tuple([int(string[i+k]) for k in [0,1,2]]) ret += IND2L[ind] return ret def main(): plaintext = 'defend the east wall of the castle' ciphertext = encipher(plaintext) print(ciphertext) plaintext = decipher(ciphertext) print(plaintext) # Функция main() вызывается только в том случае, если файл trifid.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Шифр Бофораполиалфавитный подстановочный шифр Шифрование/дешифрование Код (Python): SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' import re class Beaufort(object): def __init__(self,key): self.key = [k.upper() for k in key] def remove_punctuation(self,text,filter='[^A-Z ]'): return re.sub(filter,'',text.upper()) def a2i(self,ch): ch = ch.upper() arr = dict([(SYMBOLS[i],i) for i in range(len(SYMBOLS))]) return arr[ch] def i2a(self,i): i = i % len(SYMBOLS) return SYMBOLS[i] def encipher(self,string): string = self.remove_punctuation(string) ret = '' for (i,c) in enumerate(string): i = i%len(self.key) ret += self.i2a(self.a2i(self.key[i])-self.a2i(c)) return ret def decipher(self,string): return self.encipher(string) def main(): beaufort = Beaufort('HELLO') plaintext = 'defend the east wall of the castle' ciphertext = beaufort.encipher(plaintext) print(ciphertext) plaintext = beaufort.decipher(ciphertext) print(plaintext) # Функция main() вызывается только в том случае, если файл beaufort.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Шифр Гронсфельдаполиалфавитный подстановочный шифр. Длина ключа (K) должна быть равной длине исходного текста. Для этого циклически записывают ключ до тех пор, пока его длина не будет соответствовать длине исходного текста Шифрация/дешифрация Код (Python): SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' import re class Gronsfeld(object): def __init__(self,key): self.key = key def remove_punctuation(self,text,filter='[^A-Z ]'): return re.sub(filter,'',text.upper()) def a2i(self,ch): ch = ch.upper() arr = dict([(SYMBOLS[i],i) for i in range(len(SYMBOLS))]) return arr[ch] def i2a(self,i): i = i % len(SYMBOLS) return SYMBOLS[i] def encipher(self,string): string = self.remove_punctuation(string) ret = '' for (i,c) in enumerate(string): i = i%len(self.key) ret += self.i2a(self.a2i(c) + self.key[i]) return ret def decipher(self,string): ret = '' for (i,c) in enumerate(string): i = i%len(self.key) ret += self.i2a(self.a2i(c) - self.key[i]) return ret def main(): plaintext = 'defend the east wall of the castle' gronsfeld = Gronsfeld([5, 4, 7, 9, 8, 5, 8, 2, 0, 9, 8, 4, 3]) ciphertext = gronsfeld.encipher(plaintext) print(ciphertext) plaintext = gronsfeld.decipher(ciphertext) print(plaintext) # Функция main() вызывается только в том случае, если файл gronsfeld.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Шифр «Квадрат Полибия»Относится к шифру простой замены. Шаг 1: Формирование таблицы шифрованияК каждому языку отдельно составляется таблица шифрования с одинаковым (не обязательно) количеством пронумерованных строк и столбцов, параметры которой зависят от его мощности (количества букв в алфавите). Берутся два целых числа, произведение которых ближе всего к количеству букв в языке — получаем нужное число строк и столбцов. Затем вписываем в таблицу все буквы алфавита подряд — по одной в каждую клетку. При нехватке клеток можно вписать в одну две буквы (редко употребляющиеся или схожие по употреблению). «Квадрат Полибия» представляет собой квадрат [math]5\times 5[/math], столбцы и строки которого нумеруются цифрами от [math]1[/math] до [math]5[/math]. В каждую клетку этого квадрата записывается одна буква. каждой букве соответствует пара чисел, и шифрованное сообщение превращается в последовательность пар чисел. Расшифровывается путем нахождения буквы, стоящей на пересечении строки и столбца. Буквы могут вписываться в таблицу в произвольном порядке — заполнение таблицы в этом случае и является ключомШаг 2: Принцип шифрованияСуществует несколько методов шифрования с помощью квадрата Полибия. Метод 1 123451ABCDE2FGHI/JK3LMNOP4QRSTU5VWXYZШифруем слово «sometext»: для шифрования в квадрате находим букву текста и вставляем в шифротекст нижнюю от неё в том же столбце. Столбец считаем закольцованным. Если буква находится в нижней строке, тогда берем верхнюю букву из того же столбца. Таблица координат Открытый текстSOMETEXTШифротекстXTRKYKCYМетод 2 Сообщение преобразуется в координаты по квадрату Полибия, координаты записываются вертикально. открытый текстSOMETEXTВертикальная координата34254534Горизонтальная координата43314154Затем координаты считывают по строкам 34 25 45 34 43 31 41 54, координаты преобразуются в буквы по этому же квадрату Вертикальная координата32434345Горизонтальная координата45543114шифротекстSWYSOCDUМетод 3 Усложнённый вариант: полученный первичный шифротекст (После второго метода шифрования, цифровой) шифруется вторично. При этом он выписывается без разбиения на пары: 34 25 45 34 43 31 41 54. Полученная последовательность сдвигается циклически влево на один шаг (нечётное количество шагов, т.е. цифра 3 перемещается в конец): 4 25 45 34 43 31 41 54 3 Последовательность вновь разбивается в группы по два 42 54 53 44 33 14 15 43 и по таблице заменяется на окончательный шифротекст: Вертикальная координата45543114Горизонтальная координата24343453шифротекстIUPTNQVOДля оценки шифра учитывают два фактора: Квадрат Полибия заполнен буквами произвольно При использование шифра квадраты заменяют периодически Тогда анализ предыдущих сообщений ничего не даёт, так как к моменту раскрытия шифра он может быть заменён. Буквы могут вписываться в таблицу в произвольном порядке — заполнение таблицы в этом случае и является ключом. Для латинского алфавита в первую клетку можно вписать одну из 25 букв, во вторую — одну из 24, в третью — одну из 23 и т. д. Получаем максимальное количество ключей для шифра на таблице латинского алфавита: [math]25!\approx 1.55\cdot 10^{25}[/math] Для дешифрования сообщения требуется знание алфавита и ключа, с помощью которого составлялась таблица шифрования. Так как произвольный порядок букв тяжело запомнить, поэтому пользователь шифра должен иметь при себе ключ — квадрат. Появляется опасность тайного ознакомления с ключом посторонних лиц. В качестве компромиссного решения был предложен ключ — пароль. Пароль выписывается без повторов букв в квадрат; в оставшиеся клетки в алфавитном порядке выписываются буквы алфавита, отсутствующие в пароле. Криптоанализ Один из методов атак на шифр — частотный анализ. Распределение букв в шифротексте сравнивается с распределением букв в алфавите исходного сообщения. Буквы с наибольшей частотой в шифротексте заменяются на букву с наибольшей частотой из алфавита, если он известен. Вероятность успешного вскрытия повышается с увеличением длины шифротекста, поскольку распределения статистические. Существуют множество различных таблиц о распределении букв в том или ином языке, но ни одна из них не содержит окончательной информации — даже порядок букв может отличаться в различных таблицах. Распределение сильно зависит от типа текста: проза, разговорный язык, технический язык и т. п. Квадрат Полибия является шифром замены, неустойчивым к частотной атаке.Шифрование/дешифрование Код (Python): import re SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' class PolybiusSquare(object): def __init__(self,key,size,chars=None): self.key = ''.join([k.upper() for k in key]) self.chars = chars or 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[:size] self.size = size assert len(self.key)==size*size, 'invalid key in init: must have length size*size, has length '+str(len(key)) assert len(self.chars)==size, 'invalid chars in init: must have length=size, has length '+str(len(chars)) def remove_punctuation(self,text,filter='[^A-Z]'): return re.sub(filter,'',text.upper()) def encipher_char(self,ch): row = self.key.index(ch) // self.size col = self.key.index(ch) % self.size return self.chars[row] + self.chars def decipher_pair(self,pair): row = self.chars.index(pair[0]) col = self.chars.index(pair[1]) return self.key[row*self.size + col] def encipher(self,string): string = self.remove_punctuation(string) ret = '' for c in range(0,len(string)): ret += self.encipher_char(string[c]) return ret def decipher(self,string): ret = '' for i in range(0,len(string),2): ret += self.decipher_pair(string[i:i+2]) return ret def main(): polybius = PolybiusSquare('APCZWRLFBDKOTYUQGENHXMIVS',5,'MKSBU') plaintext = 'defend the east wall of the castle' print(plaintext) plaintext = plaintext.replace(' ','Q') ciphertext = polybius.encipher(plaintext) print(ciphertext) plaintext = polybius.decipher(ciphertext) plaintext = plaintext.replace('Q',' ') print(plaintext) # Функция main() вызывается только в том случае, если файл polybius.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
ADFGX шифрШифр построен на соединении базовых операций замены и перестановки. Часть шифра, отвечающая замене, основывается на квадрате Полибия. Процесс шифрования начинается с заполнения квадрата 5×5, каждая ячейка квадрата заполняется буквами латиницы (I и J шифруются одинаково). Адрес строки и столбца квадрата задаются буквами: «A», «D», «F», «G» и «X». Заполнение ячеек происходит в случайном порядке, поэтому получатель должен знать точное расположение каждого элемента для дешифровки сообщений. ADFGXAFNHEQDRDZOCFI/JSAGUGBVKPWXXMYTLНа первом шаге каждая буква сообщения заменяется на пару букв, обозначающих строку и столбец соответствующей буквы в квадрате. HELLO WORLD → AF.AG.XX.XX.DG.GX.DG.DA.XX.DD На втором шаге для усложнения криптоанализа применяется перестановка. Перестановка осуществляется в зависимости от ключевого слова, которое должно быть известно получателю. Создается новая сетка, в верхней строке которой записываются буквы ключевого слова. Затем под этим словом построчно записывается, полученный на первом шаге зашифрованный текст. Далее буквы ключевого слова переставляются в алфавитном порядке вместе с соответствующими им столбцами сетки. После чего буквы каждого столбца выписываются поочередно сверху вниз. Из полученной последовательности букв при помощи квадрата создают шифротекст. BATTLE_ABELTTAFAGXXFAXXAGXXDGGXXXXGDGDGDAXXGDXXDADDDDHELLO WORLD → FXGDAXDDXXXXGXADDGGA → UVQDLLWNOB Шифрование/дешифрование Код (Python): import re nonLettersAndSpacePattern = re.compile('[^A-Z ]') class ADFGX(object): def __init__(self,key,keyword): self.key = [k.upper() for k in key] self.keyword = keyword assert len(key)==25, 'invalid key in init: must have length 25, has length '+str(len(key)) assert len(keyword)>0, 'invalid keyword in init: should have length >= 1' def encipher(self,string): step1 = PolybiusSquare(self.key,size=5,chars='ADFGX').encipher(string) step2 = ColTrans(self.keyword).encipher(step1) return step2 def decipher(self,string): step2 = ColTrans(self.keyword).decipher(string) step1 = PolybiusSquare(self.key,size=5,chars='ADFGX').decipher(step2) return step1 class ColTrans(object): def __init__(self,keyword): self.keyword = keyword.upper() assert len(keyword)>0, 'invalid keyword in init: should be >= 1' # return the sorted indices of a word e.g. 'german' = [2,1,5,3,0,4] ''' def sortind(self,word): t1 = [(word[i],i) for i in range(len(word))] t2 = [(k[1],i) for i,k in enumerate(sorted(t1))] return [q[1] for q in sorted(t2)] # return the unsorted indices of a word ''' def unsortind(self,word): t1 = [(word[i],i) for i in range(len(word))] return [q[1] for q in sorted(t1)] def encipher(self,string): ret = '' ind = self.sortind(self.keyword) for i in range(len(self.keyword)): ret += string[ind.index(i)::len(self.keyword)] return ret # deciphering is messy because the columns may be ragged ''' def decipher(self,string): ret = ['_']*len(string) L,M = len(string),len(self.keyword) ind = self.unsortind(self.keyword) upto = 0 for i in range(len(self.keyword)): thiscollen = (int)(L/M) if ind[i]< L%M: thiscollen += 1 ret[ind[i]::M] = string[upto:upto+thiscollen] upto += thiscollen return ''.join(ret) class PolybiusSquare(object): def __init__(self,key,size=5,chars=None): self.key = ''.join([k.upper() for k in key]) self.chars = chars or 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[:size] self.size = size assert len(self.key)==size*size, 'invalid key in init: must have length size*size, has length '+str(len(key)) assert len(self.chars)==size, 'invalid chars in init: must have length=size, has length '+str(len(chars)) def encipher_char(self,ch): row = self.key.index(ch) // self.size col = self.key.index(ch) % self.size return self.chars[row] + self.chars[col] def decipher_pair(self,pair): row = self.chars.index(pair[0]) col = self.chars.index(pair[1]) return self.key[row*self.size + col] def encipher(self,string): ret = '' for c in range(0,len(string)): ret += self.encipher_char(string[c]) return ret def decipher(self,string): ret = '' for i in range(0,len(string),2): ret += self.decipher_pair(string[i:i+2]) return ret def main(): fileobj=open("decrypted.txt",'r',encoding='utf-8') plaintext = fileobj.read() print("Исходный текст:\n" + plaintext) fileobj.close() plaintext = nonLettersAndSpacePattern.sub('', plaintext.upper()) plaintext = plaintext.replace('J','I') plaintext = plaintext.replace(' ',' ') plaintext = plaintext.replace('\n','QQ') plaintext = plaintext.replace(' ','Q') a = ADFGX('phqgmeaylnofdxkrcvszwbuti','HELLO') ciphertext = a.encipher(plaintext) print("\nЗашифрованный текст:\n" + ciphertext) plaintext = a.decipher(ciphertext) Message = '' for i in range(1,len(plaintext)): if plaintext[i-1] == 'Q' and plaintext[i] == 'Q': Message += '\n' i += 1 elif plaintext[i-1] == 'Q' and plaintext[i] != 'U' and plaintext[i] != 'Q': Message += ' ' else: Message += plaintext[i-1] print("\nРасшифрованный текст:\n" + Message) # Функция main() вызывается только в том случае, если файл adfgx.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Шифр с автоключомШифр, который включает открытый текст в ключ. Ключ генерируется из сообщения в автоматическом режиме путем выбора определенных букв из текста или путем добавления короткого первичного ключа в начало сообщения. Существуют две формы шифра c автоключом: ключевой и текстовый шифры с автоключом. Ключевой шифр с автоключом использует предыдущие элементы ключевого потока для определения следующего элемента в ключевом потоке. Текстовый шифр с автоключом использует предыдущий текст сообщения для определения следующего элемента в ключевом потоке. В криптографии самосинхронизирующие потоковые шифры — шифры с автоключом. Шифрование/дешифрование Код (Python): SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' import re class Autokey(object): def __init__(self,key): self.key = [k.upper() for k in key] def remove_punctuation(self,text,filter='[^A-Z ]'): return re.sub(filter,'',text.upper()) def a2i(self,ch): ch = ch.upper() arr = dict([(SYMBOLS[i],i) for i in range(len(SYMBOLS))]) return arr[ch] def i2a(self,i): i = i % len(SYMBOLS) return SYMBOLS[i] def encipher(self,string): string = self.remove_punctuation(string) ret = '' for (i,c) in enumerate(string): if i<len(self.key): offset = self.a2i(self.key[i]) else: offset = self.a2i(string[i-len(self.key)]) ret += self.i2a(self.a2i(c)+offset) return ret def decipher(self,string): string = self.remove_punctuation(string) ret = '' for (i,c) in enumerate(string): if i<len(self.key): offset = self.a2i(self.key[i]) else: offset = self.a2i(ret[i-len(self.key)]) ret += self.i2a(self.a2i(c)-offset) return ret def main(): autokey = Autokey('HELLO') plaintext = 'defend the east wall of the castle' ciphertext = autokey.encipher(plaintext) print(ciphertext) plaintext = autokey.decipher(ciphertext) print(plaintext) # Функция main() вызывается только в том случае, если файл autokey.py был # запущен как программа, а не импортируется в виде модуля другой программой if __name__ == '__main__': main()
Один из вариантов шифра подстановкиЗаполнение квадрата «шагом шахматного коня» Код (Python): #заполнение текстом ходом шахматного коня import re LETTERS = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФЦЧШЩЬЫЪЭЮЯ' SIDE = 16 MOVES = [(1,2),(-1,2),(2,1),(2,-1),(1,-2),(-1,-2),(-2,-1),(-2,1)] board = [[ 0 for x in range(SIDE) ] for y in range(SIDE) ] def test_move(row, col): return ( 0 <= row < SIDE ) and ( 0 <= col < SIDE ) and ( board[row][col] == 0 ) def remove_punctuation(text,filter='[^А-Я]'): return re.sub(filter,'',text.upper()) def count_next_moves(pos): goodMoves = 0 for m in MOVES: if test_move(pos[0] + m[0], pos[1] + m[1]): goodMoves += 1 return goodMoves def find_path(firstMove): movesCollection = [(firstMove,[(firstMove[0]+m[0],firstMove[1]+m[1],firstMove[2]+1) for m in MOVES if test_move(firstMove[0]+m[0],firstMove[1]+m[1])])] board[firstMove[0]][firstMove[1]] = firstMove[2] while len(movesCollection) > 0: topMoves = movesCollection[-1][1] if len(topMoves) == 0: lastMove = movesCollection[-1][0] board[lastMove[0]][lastMove[1]] = 0 movesCollection.pop() else: lastMove = topMoves.pop() board[lastMove[0]][lastMove[1]] = lastMove[2] if lastMove[2] == ( SIDE * SIDE ): return True nextMoves = [(lastMove[0]+m[0],lastMove[1]+m[1],lastMove[2]+1) for m in MOVES if test_move(lastMove[0]+m[0],lastMove[1]+m[1])] if len(nextMoves) == 0: board[lastMove[0]][lastMove[1]] = 0 else: nextMoves.sort(key=count_next_moves, reverse=True) movesCollection.append((lastMove, nextMoves)) return False def dump_board(SIDE): message = '''Кавалергарды, век недолог, И потому так сладок он. Поет труба, откинут полог, И где-то слышен сабель звон. Еще рокочет голос струнный, Но командир уже в седле… Не обещайте деве юной Любови вечной на земле! Течет шампанское рекою, И взгляд туманится слегка, И все как будто под рукою, И все как будто на века. Но как ни сладок мир подлунный — Лежит тревога на челе… Не обещайте деве юной Любови вечной на земле!''' message = remove_punctuation(message) print(' ', end=' ') for i in range(65,SIDE+65): print(chr(i), end=' ') print() print(' \u250C',end='\u2500') for i in range(SIDE): print('\u2500',end='\u2500') print('\u2510') for i in range(SIDE): m = str(i+1)+'\u2502' if i<9: m = ' ' + m print(m, end=' ') for j in range(SIDE): print(message[board[i][j]-1].upper(), end=' ') print('\u2502') print(' \u2514',end='\u2500') for i in range(SIDE): print('\u2500',end='\u2500') print('\u2518') def main(): SIDE = int(input("Введи размер стороны шахматной доски не больше 17\n>")) h = ord(input("Введи координаты первой клетки по горизонтали от A до "+chr(SIDE+64)+"\n>").upper()) - ord('A') v = input("Введи координаты первой клетки по вертикали от 1 до "+str(SIDE)+"\n>") v = int(v)-1 print('Шифрую:') firstMove = (v, h, 1) find_path(firstMove) dump_board(SIDE) # Если файл ChessKnight.py выполняется как программа, # а не импортируется как модуль, вызвать функцию main(). if __name__ == '__main__': main() ШифрованиеНомера строк перемешивают по известному алгоритму. Выписываем строка за строкой содержимое шахматной доски в одну длинную строку. Шифротекст готов. Дешифрование Длинный текст разбивают на известную длину строк [math]n[/math] (1-ая часть ключа). Если количество символов в тексте меньше чем [math]n^2[/math] оставшуюся часть дополняют «мусором» По известному алгоритму восстанавливают порядок строк (2-ая часть ключа) Считывают текст, начиная с известной клетки (3-ья часть ключа) Направление считывания ходом «шахматного коня» (существует несколько способов заполнения шахматной доски) ― это 4-ая часть ключа.
Аффинный шифрШифрование/расшифровка/взлом методом полного перебора Код (Python): import re from math import log10 SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' class ngram_score(object): def __init__(self,ngramfile,sep=' '):#load a file containing ngrams and counts, calculate log probabilities ''' self.ngrams = {} filebuffer = open(ngramfile,encoding="UTF-8") for line in filebuffer: key,count = line.split(sep) self.ngrams[key] = int(count) self.L = len(key) self.N = sum(self.ngrams.values()) #calculate log probabilities for key in self.ngrams.keys(): self.ngrams[key] = log10(float(self.ngrams[key])/self.N) self.floor = log10(0.01/self.N) def score(self,text):# compute the score of text score = 0 ngrams = self.ngrams.__getitem__ for i in range(len(text)-self.L+1): if text[i:i+self.L] in self.ngrams: score += ngrams(text[i:i+self.L]) else: score += self.floor return score def findModInverse(a, m):#Возвращает модульное обращение для (а % m), # число х при котором (а * х) % m = 1 # если gcd(a, m) != 1 значит модульное обращение отсутствует и # а и m невзаимнопростые числа # Используем расширенный алгоритм Евклида u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: q = u3 // v3 v1,v2,v3,u1,u2,u3=(u1 - q * v1),(u2 - q * v2),(u3 - q * v3),v1,v2,v3 return u1 % m def gcd(a, b): # Возвращает НОД чисел а и b, используя алгоритм Эвклида while a != 0: a, b = b % a, a return b def remove_punctuation(text,filter='[^A-Z ]'): return re.sub(filter,'',text.upper()) class Affine(object): def __init__(self,a,b): self.a = a self.b = b self.inva = -1 for i in range(1,len(SYMBOLS),2): if (self.a*i) % len(SYMBOLS) != 1: continue else: self.inva = i #assert 0 <= self.inva <= (len(SYMBOLS)-1), 'invalid key: a='+str(a)+', no inverse exists (mod len(SYMBOLS))' def decipher(self,string): ret = '' modInverseOfKeyA = findModInverse(self.a, len(SYMBOLS)) for symbol in string: symbolIndex = SYMBOLS.find(symbol) ret += SYMBOLS[(symbolIndex - self.b)*modInverseOfKeyA % len(SYMBOLS)] return ret def encipher(self,string): ret = '' for symbol in string.upper(): symbolIndex = SYMBOLS.find(symbol) ret += SYMBOLS[(symbolIndex * self.a + self.b) % len(SYMBOLS)] return ret def main(): plaintext = 'The cipher is less secure than a substitution cipher' aff = Affine(17,5) print('Исходный текст:\n',plaintext) plaintext = remove_punctuation(plaintext.upper()) ciphertext = aff.encipher(plaintext) print('\nЗашифрованный текст:\n',ciphertext) plaintext = aff.decipher(ciphertext) print('\nРасшифрованный текст:\n',plaintext) print('\nВзламываю...') fitness = ngram_score('english_quadgrams.txt') # load our quadgram statistics scores = [] print(' A \u2502 B \u2502'+' '*20+'plaintext'+' '*21+'\u2502fitness') print(('\u2500'*3+'\u253C')*2+'\u2500' * 50+'\u253C'+'\u2500'*8) x_old = -600.0 for i in range(len(SYMBOLS)): if gcd(i, len(SYMBOLS)) == 1: for j in range(0,len(SYMBOLS)-1): text = re.sub('[^A-Z]','',Affine(i,j).decipher(ciphertext)) x = fitness.score(text) scores.extend([(x,(i,j))]) if x > x_old: print('%2s \u2502%2s \u2502%s\u2502%.2f' % (i,j,Affine(i,j).decipher(ciphertext)[:50],x)) x_old = x print(('\u2500'*3+'\u2534')*2+'\u2500'*50+'\u2534'+'\u2500'*8) max_key = max(scores) print('лучшие кандидаты на ключи (a,b) = '+str(max_key[1])+':') print('Расшифрованный с помощью этих ключей текст:\n'+Affine(max_key[1][0],max_key[1][1]).decipher(ciphertext)) #Если файл affineHacker.py выполняется как программа, а не импортируется как модуль, вызвать функцию main() if __name__ == '__main__': main()
Аффинный шифрШифрование/расшифровка/взлом методом полного перебора Код (Python): import re SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ \n:.,"?' def gcd(a, b): # Возвращает НОД чисел а и b, используя алгоритм Эвклида while a != 0: a, b = b % a, a return b def findModInverse(a, m):#Возвращает модульное обращение для (а % m), # число х при котором (а * х) % m = 1 # если gcd(a, m) != 1 значит модульное обращение отсутствует и # а и m невзаимнопростые числа # Используем расширенный алгоритм Евклида u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: q = u3 // v3 v1,v2,v3,u1,u2,u3=(u1 - q * v1),(u2 - q * v2),(u3 - q * v3),v1,v2,v3 return u1 % m def encryptMessage(keyA, keyB, message):# Зашифровать символ text = '' for symbol in message.upper(): symbolIndex = SYMBOLS.find(symbol) text += SYMBOLS[(symbolIndex * keyA + keyB) % len(SYMBOLS)] return text def decryptMessage(keyA, keyB, message):# Дешифровать символ text = '' modInverseOfKeyA = findModInverse(keyA, len(SYMBOLS)) for symbol in message: symbolIndex = SYMBOLS.find(symbol) text += SYMBOLS[(symbolIndex - keyB)*modInverseOfKeyA % len(SYMBOLS)] return text def remove_punctuation(text,filter='[^A-Z \n:.,"?]'): return re.sub(filter,'',text.upper()) def loadDictionary(): dictionaryFile = open('english.txt') # получаем объект файла словаря englishWords = {} #создается переменная englishWords в виде пустого словаря for word in dictionaryFile.read().split('\n'): englishWords[word] = None dictionaryFile.close() return englishWords #Строковый метод split() разбивает переданную ему строку по переносам, #возвращая список из нескольких строк ENGLISH_WORDS = loadDictionary() # вызывается функция loadDictionary() и возвращаемый ею словарь сохраняется # в переменной ENGLISH_WORDS. def getEnglishCount(message): message = message.upper() #символы строки преобразуются в верхний регистр message = remove_punctuation(message) #с помощью функции removeNonLetters() # из строки удаляются все небуквенные символы, числа и знаки препинания possibleWords = message.split() #метод split() разбивает строку на слова #и сохраняет их в переменной possibleWords if possibleWords == []: return 0.0 # слова отсутствуют, поэтому возвращаем 0.0. matches = 0 #для счетчика mаtсhеs устанавливается нулевое значение for word in possibleWords: #В цикле for мы проходим по всем словам в списке #possibleWords и проверяем существование каждого из них в словаре ENGLISH_WORDS if word in ENGLISH_WORDS: matches += 1 #Если слово содержится в словаре, значение счетчика #mаtсhеs инкрементируется return float(matches)/len(possibleWords) def main(): keyA, keyB = 17, 5 with open('Carroll.txt', 'r', encoding='utf-8') as file: plaintext = file.read() print('Исходный текст:\n',plaintext) key = (keyA,keyB) print('\nКлючи (a,b) = ',key) plaintext = remove_punctuation(plaintext.upper()) ciphertext = encryptMessage(keyA, keyB, plaintext) print('Текст зашифрованный с этими коэффициентами:\n',ciphertext[:200]) plaintext = decryptMessage(keyA, keyB, ciphertext) print('\nРасшифрованный текст:\n',plaintext[:200]) print('\nВзламываю шифр методом грубой силы...') englishPercentageOld = 25 for keyA in range(1,len(SYMBOLS)): if gcd(keyA,len(SYMBOLS)) == 1: for keyB in range(len(SYMBOLS)): text = decryptMessage(keyA, keyB, ciphertext) englishPercentage = round(getEnglishCount(text) * 100, 2) if englishPercentage > englishPercentageOld: print('Процент английских слов: %s%% КлючA #%2d КлючB #%2d: \n%s' % (englishPercentage, keyA, keyB, text)) englishPercentageOld = englishPercentage print('\nВзлом завершен') if __name__ == '__main__': main()
Взлом шифра простой замены с использованием словарного шаблона (начало)Ключ шифра простой замены это перемешанный случайным образом алфавит. Допустим используется открытый текст на английском языке. При количестве букв в латинском алфавите = 26 количество возможных ключей для шифрования такого текста будет равно [math]26! \approx 40.329 \times 10^{25}[/math] Шифр простой замены невозможно взломать методом грубой силы из-за слишком большого количества возможных ключей. Для взлома такого шифра потребуется более сложный алгоритм, в котором для поиска возможных вариантов обратного преобразования букв зашифрованного текста в открытый текст используется словарь. Предварительный анализ шифротекста может значительно сократить количество потенциальных ключей, подлежащих тестированию, а иногда даже определить полный или частичный ключ. Предположим, что открытый текст состоит из английских слов, которые можно найти в англо-русском словаре. Шифротекст не будет содержать подобных слов, но в нем все равно будут встречаться группы букв, разделенных пробелами, подобно словам в предложениях. В отношении таких групп употребляют термин шифрослово. В шифре простой замены каждой букве алфавита соответствует ровно одна уникальная зашифрованная буква. Такие буквы называют шифробуквами. Каждая буква исходного текста шифруется одной конкретной шифробуквой, в исходном тексте и шифротексте будут встречаться одни и те же шаблоны слов. Открытому тексту "MISSISSIPPI SPILL" (Наводнение на Миссисипи) может соответствовать шифротекст "RJBBJBBJXXJ BXJHH". В открытом тексте и шифротексте первое и второе слово содержат одинаковое количество букв. И открытый текст, и шифротекст характеризуются одним и тем же шаблоном (закономерностью) расположения букв и пробелов. Повторяющиеся буквы сообщения встречаются в том же количестве и в тех же позициях шифротекста. Можно предположить, что каждому шифрослову соответствует слово, входящее в словарь английского языка, и их шаблоны должны совпадать. Если найдем в словаре слово, в которое дешифруется данное шифрослово, то это указало бы нам, как дешифровать каждую его шифробукву. Если с помощью данной методики удастся восстановить достаточное число шифробукв, тогда можно полностью дешифровать сообщение.Поиск ша6понов словВ качестве примера исследуем шаблон шифрослова "HGHHU". В нем видны определенные закономерности, которые должны быть свойственны и соответствующему слову в исходном тексте. Оба слова должны иметь следующие общие характеристики: длина слова — пять букв первая, третья, четвертая буквы слова — одинаковы слово содержит три разные буквы: первую, вторую и пятую Какие английские слова укладываются в этот шаблон? Одно из слов, удовлетворяющих описанному критерию, — слово рuрру, три буквы разные ('Р', 'U', 'Y'), их позиции соответствуют шаблону ('Р' — первая, третья и четвертая, 'U' — вторая и 'Y' — пятая). Слова mommy, bobby, lulls и nanny тоже подпадают под это описание. Все они, также как и любое другое слово из файла словаря, удовлетворяющее заданным критериям, являются возможными вариантами дешифрования слова "HGHHU". Чтобы представить шаблон слова в форме, которую можно обработать программой, преобразуем шаблон в набор чисел, разделенных точками. Эти числа указывают на повторяемость букв в шаблоне. Как составляется шаблон слова? Первой букве ставится в соответствие число, первому вхождению любой другой буквы — следующее число, на единицу превышающее предыдущее использованное. Шаблон "cat" — 0.1.2, шаблон "classification" — 0.1.2.3.3.4.5.4.0.2.6.4.7.8. В случае шифра простой замены не имеет значения, какой именно ключ используется для шифрования: слово исходного текста и соответствующее ему шифрослово всегда соответствуют одному и тому же шаблону. Если шаблон шифрослова "HGHHU" — 0.1.0.0.2, значит, исходное слово имеет такой же шаблон.Поиск возможных вариантов дешифровано 6уквЧтобы дешифровать шифрослово "HGHHU", в файле словаря нужно найти все слова, соответствующие шаблону 0.1.0.0.2. Такие слова называют кандидатами для данного шифрослова. Список кандидатов для шаблона "HGHHU" выглядит так: рuррy mommy bobby lulls nanny Используя шаблоны слов, можно выдвигать предположения относительно того, в какие буквы исходного текста могут дешифровываться шифробуквы. Такие буквы называют вариантами дешифрования для данной шифробуквы. Чтобы взломать сообщение, зашифрованное с помощью шифра простой замены, нужно найти варианты дешифрования для всех шифробукв, а затем определить фактические буквы исходного текста, действуя методом исключения. В таблице приведены варианты дешифрования шаблона "HGHHU". ШифробуквыHGHHUВарианты дешифрованияPUPPYMOMMYBOBBYLULLSNANNYВыводы; 'Н' имеет следующие варианты дешифрования: 'Р', 'М', 'В', 'L', 'N' 'G' имеет следующие варианты дешифрования: 'U', 'О', 'А' 'U' имеет следующие варианты дешифрования: 'Y', 'S' Для остальных шифробукв нет вариантов дешифрования В результате формируется дешифровальный словарь, в котором буквам алфавита ставятся в соответствие потенциальные варианты дешифрования. По мере накопления зашифрованных сообщений находят варианты дешифрования для каждой буквы алфавита, в данном примере наш шифротекст включает шифробуквы 'Н', 'G' и 'U', а варианты дешифрования для остальных шифробукв не определены. Буква 'U' имеет два варианта дешифрования ('Y' и 'S') из-за перекрывания слов-кандидатов, многие из которых заканчиваются буквой 'Y'. Чем больше перекрывание, тем меньше вариантов дешифрования, тем легче вычислить, в какую букву должна быть дешифрована шифробуква. Чтобы перевести таблицу на Python, представим дешифровальный словарь значениями словарного типа (пары "ключ: значение") Код (Python): {'А':[],'В':[],'С':[],'D':[],'Е':[],'F':[],'G':['U','O','А'], 'Н':['Р','М','В','L','N'], 'I':[], 'J':[],'К':[],'L':[], 'М':[],'N':[],'О':[],'Р':[],'Q':[],'R':[],'S':[],'Т':[], 'U':['Y','S'],'V':[],'W':[],'Х':[],'Y':[],'Z':[]) словарь включает 26 пар "ключ: значение": по одному ключу для каждой буквы алфавита и список вариантов дешифрования для каждой буквы. В словаре содержатся варианты дешифрования для букв 'Н', 'G' и 'U'. Всем остальным ключам соответствуют значения в виде пустых списков, [], так как варианты дешифрования для них пока не определены. Если удастся свести количество вариантов дешифрования для некоторой шифробуквы всего лишь к одной букве за счет перекрывания дешифровальных словарей, тогда можно расшифровать данную букву. Даже если не удастся добиться этого для всех 26 шифробукв, может оказаться так, что, взломав значительную часть шифробукв, будем в состоянии расшифровать большую часть шифротекста.Обзор процесса взлома Шифр простой замены можно взломать, используя шаблоны слов. Основные этапы процедуры взлома можно сформулировать следующим образом. Определить шаблоны для всех шифрослов в тексте Найти слова-кандидаты, в которые может быть расшифровано каждое шифрослово. Для каждого шифрослова создать словарь, представляющий варианты дешифрования для каждой шифробуквы Объединить словари в единую структуру, которую назовем пересечением словарей Удалить из пересечения буквы, для которых удалось установить, каким шифробуквам они соответствуют Дешифровать шифротекст с помощью установленных шифробукв. Чем больше слов в шифротексте, тем выше вероятность перекрывания словарей и тем меньше остается вариантов дешифрования для каждой шифробуквы. Чем длиннее сообщение, зашифрованное с помощью шифра простой замены, тем легче его взломать. Как можно упростить выполнение первых двух этапов процедуры взлома. Используем файл словаря. Потребуется вспомогательный модуль wordPatterns.py для получения упорядоченного списка шаблонов по каждому слову из словаря.Модуль WordPatternsДля получения шаблонов слов для каждого слова в словаре dictioпary. txt, загрузим файл makeWordPatterns.py. Убедитесь, что оба файла находятся в той же папке, что и программа simpleSubHacker.py. Программа makeWordPatterns.py содержит функцию getWordPattern(), которая получает строку ('puppy') и возвращает ее шаблон ('0.1.0.0.2'). После запуска программы, будет создан модуль Python wordPatterns.py, который включает единственную инструкцию присваивания, занимающую более 43 тысяч строк. Переменная allPatterns содержит словарь, в котором ключами являются строки шаблонов слов, а значениями — английские слова, соответствующие данному шаблону. Чтобы найти все cлoвa, соответствующие шаблону 0.1.2.1.3.4.5.4.6.7.8, введите в интерактивной оболочке следующие инструкции. В словаре allPatterns значением ключа '0.1.2.1.3.4.5.4.6.7.8' является список ['BENEFICIARY', 'HOMOGENEITY', 'MOTORCYCLES'], который содержит три слова, соответствующих данному шаблону. Код (Python): import pprint def getWordPattern(word): # Возвращает строку шаблона заданного слова. # например '0.1.2.3.4.1.2.3.5.6' для слова 'DUSTBUSTER' word = word.upper() nextNum = 0 letterNums = {} wordPattern = [] for letter in word: if letter not in letterNums: letterNums[letter] = str(nextNum) nextNum += 1 wordPattern.append(letterNums[letter]) return '.'.join(wordPattern) def main(): allPatterns = {} with open('dictionary.txt',encoding='utf-8') as file: wordList = file.read().split('\n') for word in wordList: # Получаем шаблон каждого слова в wordList: pattern = getWordPattern(word) if pattern not in allPatterns: allPatterns[pattern] = [word] else: allPatterns[pattern].append(word) # Это код, который пишет код. Файл wordPatterns.py содержит один очень, # очень большой оператор присваивания with open('wordPatterns.py', 'w',encoding='utf-8') as file: file.write('allPatterns = ') file.write(pprint.pformat(allPatterns)) print('Файл wordPatterns.py сформирован') if __name__ == '__main__': main() Продолжение здесь...