Простые алгоритмы шифрования

Тема в разделе "WASM.CRYPTO", создана пользователем Mikl___, 18 май 2025.

  1. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Индекс совпадений Фридмана

    Индекс совпадений — один из методов криптоанализа шифра Виженэра. Описание было опубликовано Уильямом Фридманом в 1920.
    Метод основывается на вычислении вероятности того, что два случайных элемента текста совпадут. Эту вероятность называют индексом совпадений. Уильям Фридман показал, что значения индекса совпадений существенно отличаются для текстов различной природы. Это позволяет сначала определить длину ключа шифра, а затем найти и сам ключ.
    Появление метода индекса совпадений открыло новые возможности в криптоанализе шифра Виженэра. По сравнению с методом Касиски, метод индекса совпадений менее трудоемок, требует меньшей длины текста, более пригоден для автоматизации и менее подвержен ошибкам. Индекс совпадений более эффективен и допускает анализ шифров с длинными ключами.

    Формула для вычисления индекса совпадений

    Допустим у нас есть текст, написанный на неизвестном языке. Алфавит данного языка состоит из [math]m[/math] символов. В нашем распоряжении есть строка [math]\vec {x}[/math] из [math]n[/math] символов. Индекс совпадения это вероятность совпадения двух произвольных символов в строке. Если [math]f_i[/math] — это количество [math]i[/math]-го символа алфавита в строке [math]\vec {x}[/math], тогда индекс совпадения вычисляется по формуле:[math] I({\vec {x}})=\sum \limits _{i=1}^{m}\displaystyle{\frac {f_i(f_{i}-1)}{n(n-1)}}[/math]

    Открытый текст

    Допустим, строка [math]\vec {x}[/math] открытый текст или результат любого моноалфавитного шифрования. Индекс совпадений выражают через [math]p_i[/math] — вероятности появления [math]i[/math]-го символа. Получаем формулу: [math]I({\vec {x})=\sum \limits _{i=1}^{m}p_{i}^{2}} [/math] Величины [math]p_i[/math] имеют определенные значения, для открытого текста индекс совпадений не зависит от его содержания, а зависит от языка, на котором написан текст. Индекс совпадения также зависит от типа текста (художественная литература, математические или химические тексты), от времени написания (в текстах на русском языке написанных до 1918 будет обилие ъ и ѣ), а также зависит от автора текста. Значения [math]p_i[/math] исследованы и известны, это позволяет рассчитать значения индекса совпадений открытого текста для различных языков.
    языкиндекс совпаденийисточник
    русский0.0553Пилиди В. С. Криптография. Вводные главы
    английский0.0644William Frederick Friedman. Military Cryptanalysis. Part II. Simpler vrieties of polyalphabetic substitution systems (p. 117)
    немецкий0.0762
    французский0.0778
    испанский0.0775
    португальский0.0746
    итальянский0.0738
    Код (Python):
    1. SYMBOLSEN = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    2. SYMBOLSRU = 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'
    3. import re
    4. def friedman(text,SYMBOLS):
    5.     n = 0
    6.     freq = [0 for i in range(len(SYMBOLS))]
    7.     for c in text:
    8.         freq[SYMBOLS.find(c.upper())] += 1
    9.         n += 1
    10.     ic = 0
    11.     for f in freq:
    12.         ic += f * (f - 1)
    13.     ic = float(ic) / float((n * (n - 1)))
    14.     return ic
    15.  
    16. def main():
    17.     autorEn = ['Льюис Кэрролл','Эрл Стенли Гарднер','Джоан Роулинг']
    18.     autorRu = ['А.С.Пушкин','Л.Н.Толстой','А.А.Бушков']
    19.     plaintext_icEn = []
    20.     plaintext_icRu = []
    21.     yearEn = ['1864','1934','1997']
    22.     yearRu = ['1833','1863','1996']
    23.     filenameEn = ['Carroll.txt','Gardner.txt','Rowling.txt']
    24.     filenameRu = ['Pushkin.txt','Tolstoy.txt','Bushkov.txt']
    25.     print('\u2500' * 19+'\u252C'+'\u2500'*26+'\u252C'+'\u2500'*6)
    26.     print(' '*7+'Автор'+' '*7+'\u2502Индекс совпадений Фридмана\u2502 год')
    27.     print('\u2500' * 19+'\u2534'+'\u2500' * 26+'\u2534'+'\u2500' * 6)
    28.     print(' ' * 22+'для английского языка')
    29.     print('\u2500' * 19+'\u252C'+'\u2500'*26+'\u252C'+'\u2500'*6)
    30.     for i in range(len(autorEn)):
    31.         with open(filenameEn[i], 'r', encoding='utf-8') as file:
    32.             plaintext = file.read()
    33.         plaintext = re.sub('[^A-Z]','',plaintext.upper())
    34.         plaintext_icEn.append(friedman(plaintext,SYMBOLSEN))
    35.         print(autorEn[i]+' '*(19-len(autorEn[i]))+'\u2502  %.019f'%plaintext_icEn[i],'  \u2502',yearEn[i])
    36.     print('\u2500' * 19+'\u2534'+'\u2500' * 26+'\u2534'+'\u2500' * 6)
    37.     print(' ' * 22+'для русского языка')
    38.     print('\u2500' * 19+'\u252C'+'\u2500'*26+'\u252C'+'\u2500'*6)
    39.     for i in range(len(autorRu)):
    40.         with open(filenameRu[i], 'r', encoding='utf-8') as file:
    41.             plaintext = file.read()
    42.             plaintext = re.sub('[^А-Я]','',plaintext.upper())
    43.             plaintext_icRu.append(friedman(plaintext,SYMBOLSRU))
    44.     for i in range(len(autorRu)):
    45.         print(autorRu[i]+' '*(19-len(autorRu[i]))+'\u2502  %.019f'%plaintext_icRu[i],'  \u2502',yearRu[i])
    46.     print('\u2500' * 19+'\u2534'+'\u2500' * 26+'\u2534'+'\u2500' * 6)
    47.     print('\nИндексы совпадений для других языков')
    48.     print('\u2500' * 14+'\u252C'+'\u2500' * 7)
    49.     langs = ['английский','русский','итальянский','испанский','немецкий','французский','португальский']
    50.     IC = [0.0662,0.0553,0.0738,0.0775,0.0762,0.0778,0.0746]
    51.     for i in range(len(langs)):
    52.         print(langs[i]+' '*(14-len(langs[i]))+'\u2502'+str(IC[i]))
    53.     print('\u2500' * 14+'\u2534'+'\u2500' * 7)
    54. if __name__ == '__main__':
    55.     main()
    00.png
    Использовались тексты: Бушков "Охота на Пиранью", Пушкин "Капитанская дочка", Толстой "Война и мир", Кэрролл "Алиса в стране чудес", Гарднер "Дело о воющей собаке", Роулинг "Гари Потер и философский камень"
     

    Вложения:

    • Bushkov.txt
      Размер файла:
      1,7 МБ
      Просмотров:
      181
    • Carroll.txt
      Размер файла:
      2,4 КБ
      Просмотров:
      199
    • Gardner.txt
      Размер файла:
      359,3 КБ
      Просмотров:
      230
    • Pushkin.txt
      Размер файла:
      6,3 КБ
      Просмотров:
      193
    • Rowling.txt
      Размер файла:
      455,7 КБ
      Просмотров:
      235
    • Tolstoy.txt
      Размер файла:
      30,9 КБ
      Просмотров:
      183
    • mil_crypt_II.pdf
      Размер файла:
      5,7 МБ
      Просмотров:
      245
    Последнее редактирование: 24 июл 2025
    MaKsIm и q2e74 нравится это.
  2. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167
    Взлом шифра простой замены
    с использованием словарного шаблона

    (Продолжение)​
    Функция hackSimpleSub() возвращает пересечение словарей шифробукв, из которого удалены дешифрованные буквы. Затем это пересечение передается функции decryptWithCipherletterMapping() для дешифрования шифротекста, сохраненного в переменной message.
    Пересечение, сохраненное в переменной letterMapping, - это словарь, ключами которого являются 26 однобуквенных строк в верхнем регистре, представляющих шифробуквы. Значениями словаря будут списки вариантов дешифрования для каждой шифробуквы. Если каждой шифробукве сопоставлен только один вариант дешифрования, тогда имеем полностью решенный словарь и можем расшифровать любой шифротекст, в котором используются те же шифр и ключ.
    Каждый сгенерированный словарь шифробукв зависит от используемого шифротекста. Иногда мы получим лишь частично решенный словарь, в котором для некоторых шифробукв отсутствуют варианты дешифрования или остается несколько вариантов. Короткие шифротексты, содержащие не все буквы алфавита, будут с большей вероятностью приводить к неполным словарям.
    В программе вызывается функция print() для вывода на экран переменной letterMapping, исходного шифротекста и дешифрованного сообщения.
    Дешифрованное сообщение сохраняется в переменной hackedМessage, которая выводится на экран, чтобы пользователь мог проверить текст. Для получения дешифрованного сообщения использется функцию decryptWithCipherletterMapping().

    Создание словарей шифро6укв

    Программа строит дешифровальный словарь для каждого слова шифротекста. Для этого потребуется несколько вспомогательных функций. Одна из них создает пустой словарь шифробукв, и ее вызывают для каждого шифрослова. Еще одна функция получает шифрослово, текущий словарь шифробукв и слово-кандидат. Эту функцию вызывают для каждого шифрослова и каждого слова-кандидата. Она добавляет все варианты дешифрования, определенные на основе данного слова-кандидата, в дешифровальный словарь шифрослова и возвращать этот словарь.
    Получив словари шифробукв для нескольких слов шифротекста, используем функцию для их объединения. И еще одна вспомогательная функция будет находить максимально возможное число решений, сопоставляя дешифрованные буквы с каждой шифробуквой.

    Создание пустого словаря шифро6укв

    Сначала необходимо создать пустой словарь шифробукв.
    Код (Python):
    1. def getBlankCipherletterMapping():
    2.     # возвращает словарь, ключами которого являются односимвольные строки,
    3.     # соответствующие 26 буквам алфавита.
    4.     return {'A':[],'B':[],'C':[],'D':[],'E':[],'F':[],'G':[],'H':[],'I':[],
    5.             'J':[],'K':[],'L':[],'M':[],'N':[],'O':[],'P':[],'Q':[],'R':[],
    6.             'S':[],'T':[],'U':[],'V':[],'W':[],'X':[],'Y':[],'Z':[]}
    Функция getBlankCipherletterMapping() возвращает словарь, ключами которого являются односимвольные строки, соответствующие 26 буквам алфавита.

    Добавление букв в дешифровальный словарь

    Создается функция addLettersToMapping(), предназначенная для добавления букв в словарь.
    Код (Python):
    1. def addLettersToMapping(letterMapping, cipherword, candidate):
    У функции три параметра: словарь шифробукв (letterMapping), шифрослово (cipherword) и слово - кандидат, в которое может быть дешифровано данное шифрослово (candidate). Функция сопоставляет каждую букву слова-кандидата с шифробуквой, занимающей соответствующую позицию в шифрослове, и добавляет эту букву в словарь letterMapping, если ее там еще нет.
    Если 'PUPPY' - слово-кандидат для шифрослова 'HGHHU', тогда функция addLettersToMapping() добавит в словарь letterMapping значение 'Р' для ключа 'Н'. Затем функция перейдет к следующей букве и присоединит 'U' к списку, связанному с ключом 'G'. Если буква уже находится в списке вариантов дешифрования, то функция addLettersToMapping() не добавит ее повторно в список. В слове 'PUPPY' функция не станет добавлять букву 'Р' в ключ 'Н' для следующих двух вхождений буквы 'Р', так как она там уже имеется. Функция изменит значение ключа 'U', добавив в его список вариантов дешифрования букву 'Y'.
    В функции addLettersToMapping() предполагается, что вызовы len(cipherword) и len(candidate) возвращают одно и то же значение, поскольку передаются только пары "шифрослово - кандидат" с совпадающими шаблонами. Функция проходит по каждому индексу строки cipherword, чтобы проверить, не добавлена ли соответствующая буква в список вариантов дешифрования.
    Код (Python):
    1. for j in range(len(cipherword)):
    2.     if candidate[j] not in letterMapping [cipherword[j]]:
    3.          letterMapping[cipherword[j]].append(candidate[j])
    В процессе итерирования по буквам шифрослова в качестве индекса в слове-кандидате тоже используется переменная j. Мы можем так поступать, поскольку вариантом дешифрования для шифробуквы cipherword[j], подлежащим добавлению, является буква candidate[j]. Например, для доступа к первым буквам каждой из строк будут использоваться выражения cipherword[0] и candidate[0]. Далее выполнение переходит к инструкции if.
    Эта инструкция проверяет отсутствие варианта candidate[j] в списке вариантов для данной шифробуквы, пропуская варианты, уже имеющиеся в списке. Соответствующая буква в словаре ищется с помощью выражения letterMapping[cipherword[j]], поскольку cipherword[j] - это ключ в словаре letterMapping, к которому необходимо осуществить доступ. Такая проверка нужна для того, чтобы предотвратить дублирование букв в списке вариантов дешифрования.
    Первая буква 'Р' в слове 'PUPPY' может быть добавлена в словарь letterMapping на первой итерации цикла, но когда j станет равно 2 на третьей итерации, буква 'Р' в позиции candidte[2] не будет добавлена в словарь, поскольку она уже была добавлена в него на первой итерации.
    Если вариант дешифрования отсутствует в словаре, то новая буква, candidate[j], добавляется в список вариантов дешифрования по ключу letterMapping[cipherword[j]].
    В Python в качестве параметра передается ссылка на словарь, а не сам словарь, поэтому любые изменения, внесенные в словарь letterMapping в функции addLettersToMapping(), будут сохранены и после ее завершения. Соответственно, изменится также словарь candidateMap, передаваемый в функцию addLettersToMapping().
    Пройдя по всем индексам в строке cipherword, функция завершит добавление букв в словарь letterMapping. Теперь необходимо выяснить, как программа сравнивает полученный словарь со словарями других шифрослов для выявления пересечений.

    Пересечение двух словарей

    Функция hackSimpleSub() использует функцию intersectMappings(), которая получает два словаря шифробукв, передаваемых ей в качестве параметров mapA и mapB, и возвращает объединенный словарь.
    Предварительно создается пустой словарь, а затем в него добавляются варианты дешифрования букв, но только в том случае, если они имеются в обоих словарях, что позволяет избежать появления дубликатов.
    Код (Python):
    1. def intersectMappings(mapA, mapB):
    2.     # Чтобы построить пересечение словарей, создаем пустой словарь и
    3.     # добавляем в него только варианты, существующие в ОБОИХ словарях
    4.     intersectedMapping = getBlankCipherletterMapping()
    С помощью функции getBlankCipherletterMapping() создается пустой словарь шифробукв, который сохраняется в переменной intersectedMapping.
    В цикле for перебираются все буквы, хранящиеся в константе LETTERS. Переменная letter используется в качестве ключа в словарях mapA и mapB.
    Код (Python):
    1. for letter in LETTERS:
    2.    # Пустой список означает "возможна любая буква";
    3.    # в этом случае копируем список целиком
    4.    if mapA[letter] == []:
    5.        intersectedMapping[letter]
    6.    elif mapB[letter] == []:
    7.        intersectedМapping[letter]
    8.        copy.deepcopy (mapB[letter])
    9.        copy.deepcopy(mapA[letter])
    Проверяется, не является ли список вариантов дешифрования буквы в словаре mapA пустым. Пустой список означает, что данная шифробуква может быть дешифрована в любую букву. В таком случае в объединенный словарь копируется весь список вариантов дешифрования из словаря mapB. Аналогичная проверка делается для словаря mapB. Если оба списка пустые, то условие вернет Тrue, и тогда в объединенный словарь будет скопирован пустой список из mapB.
    Блок else обрабатывает случай, когда ни список mapA, ни список mapB для данной буквы не являются пустыми.
    Код (Python):
    1. else:
    2.    # Если буква из списка mapA[letter] существует в списке
    3.    # mapB[letter], добавляем ее в intersectedМapping[letter]
    4.    for mappedLetter in mapA[letter]:
    5.        if mappe dLetter in mapB[letter]:
    6.            intersectedMapping[letter].append(mappedLetter)
    7. return intersectedMapping
    Если списки не пустые, то организуется цикл по буквам, хранящимся в списке mapA[letter]. Проверяется, встречается ли буква в списке mapB[letter]. Если это так, то совпадающая буква добавляется в список intersectedMapping[letter] объединенного словаря.
    По окончании цикла словарь intersectedMapping должен содержать лишь варианты дешифрования букв, которые существуют как в словаре mapA, так и в словаре mapB. Этот объединенный словарь возвращается. Рассмотрим примеры подобных объединений.

    Как paбoтaют вспомогательные функции

    Теперь, когда у нас имеются определения вспомогательных функций, попробуем поработать с ними в интерактивной оболочке, чтобы лучше понять их специфику. Создадим пересечение словарей для шифротекста 'OLQIHXIRCKGNZ PLQRZKBZB MPBKSSIPLC' содержащего три шифрослова. Для этого потребуется создать по одному словарю для каждого слова, а затем объединить словари. Импортируем модуль simpleSubHacker.py в интерактивной оболочке, затем вызовем функцию getBlankCipherletterMapping() для создания пустого словаря шифробукв и сохраним его в переменной letterMappingl .
    00.png
    Приступим к взлому первого шифрослова, 'OLQIHXIRCKGNZ'. Прежде всего, нужно получить для него шаблон, вызвав функцию getWordPattern().
    01.png
    Чтобы определить, какие слова, содержащиеся в файле английского словаря, соответствуют шаблону 0.1.2.3.4.5.3.6.7.8.9.10.11 (т.е. являются словами-кандидатами шифрослова 'OLQIHXIRCKGNZ'), импортируем модуль wordPatterns и выполним поиск этого шаблона.
    00.png
    Шаблону шифрослова 'OLQIHXIRCKGNZ' соответствуют только два английских слова: 'UNCOMFORTABLE' и 'UNCOMFORTABLY'. Они являются нашими кандидатами, поэтому сохраним их в переменной candidates в виде списка.
    Далее следует сопоставить буквы этих слов с буквами шифрослова, используя функцию addLettersToMapping(). Сначала сопоставим слово 'UNCOMFORTABLE', обратившись к первому элементу списка кандидатов.
    >>> simpleSubHacker.addLettersToМapping(letterМappingl, ' OLQIВXIRCКGNZ', candidates[0])
    >>> letterМappingl
    { 'А':[], 'В':[], 'С':['Т'], 'D':[], 'Е':[], 'F':[], 'G':['В'], 'Н':['М'], 'I':['О'], 'J':[],
    'К':['А'], 'L':['N'], 'М':[], 'N':['L'], 'О':['U'], 'Р':[], 'Q':['С'], 'R':['R'], 'S':[], 'Т':[],
    'U':[], 'V':[], 'W':[], 'Х':['F'], 'Y':[], 'Z':['Е'] }

    Как показывает анализ словаря letterMappingl, буквы шифрослова 'OLQIHXIRCKGNZ' транслируются в буквы слова 'UNCOMFORTABLE' следующим образом: 'О' - ['U'], 'L' - ['N'], 'Q' - ['С'] и т.д.
    Шифрослово 'OLQIHXIRCKGNZ' может быть также дешифровано в слово 'UNCOMFORTABLY', необходимо и его добавить в словарь.
    Введите в интерактивной оболочке следующие инструкции.
    >>> simpleSubHacker.addLettersToМapping (letterМappinql, ' OLQIВXIRCКGNZ', candidates[1])
    > > > letterМappingl
    { 'А':[], 'В':[], 'С':['Т'], 'D':[], 'Е':[], 'F':[], 'G':['В'], 'Н':['М'], 'I':['О'], 'J':[], 'К':['А'],
    'L':['N'], 'М':[], 'N':['L'], 'О':['U'], 'Р':[], 'Q':['С'], 'R':['R'], 'S':[], 'Т':[], 'U':[], 'V':[],
    'W':[], 'Х':['F'], 'Y':[], 'Z':['Е', 'Y'] }

    Обратите внимание на то, что содержимое словаря letterMappingl почти не изменилось, за исключением того, что теперь шифробукве 'Z' соответствует не только буква 'Е', но и 'Y'. Это объясняется тем, что функция addLettersToMapping() добавляет букву в список только в том случае, если до этого она в нем отсутствовала. Итак, мы получили словарь шифробукв для первого из трех шифрослов.
    Теперь необходимо построить словарь для второго шифрослова, 'PLQRZKBZB', повторив весь процесс.
    >>> letterМappinq2 = simpleSubHacker.getblankCipherletterМapping()
    >>> wordPat = makeWordPatterns.getWordPattern('PLQRZКВZB')
    > > > candidates = wordPatterns.allPatterns [wordPat]
    >>> candidates
    ['CONVERSES', 'INCREASES', 'PORTENDED', 'UNIVERSES']
    > > > for candidate in candidates:
    simpleSubHacker.addLettersToМapping(letterМapping2, 'PLQRZКВZB', candidate)
    > > > letterМapping2
    { 'А':[], 'В':['S', 'D'], 'С':[], 'D':[], 'Е':[], 'F':[], 'G':[], 'Н':[], 'I':[], 'J':[], 'К':['R', 'А', 'N'], 'L':['О', 'N'],
    'М':[], 'N':[], 'О':[], 'Р':['С', 'I', 'Р', 'U'], 'Q':['N', 'С', 'R', 'I'], 'R':['V', 'R', 'Т'], 'S':[], 'Т':[], 'U':[],
    'V':[], 'W':[], 'Х':[], 'Y':[], 'Z':['Е'] }

    Вместо того чтобы четырежды вводить функцию addLettersToMapping() для каждого из четырех слов-кандидатов, мы организуем цикл for по словам-кандидатам в списке. Таким образом, мы получили словарь шифробукв для второго шифрослова.
    Следующий шаг - создание пересечения словарей, сохраненных в переменных letterMappingl и letterMapping2. Для этого мы передаем словари в функцию intersectMappings(). Введите в интерактивной оболочке следующие инструкции.
    >>> intersectedМapping = simpleSubHacker.intersectмappings( letterМappingl, letterмapping2)
    >>> intersectec:Mapping
    { 'А':[], 'В':['S', 'D'], 'С':['Т'], 'D':[], 'Е':[], 'F':[], 'G':['В'], 'Н':['М'],
    'I':['О'], 'J':[], 'К':['А'], 'L':['N'], 'М':[], 'N':['L'], 'О':['U'], 'Р':['С', 'I', 'Р', 'U'], 'Q':['С'],
    'R':['R'], 'S':[], 'Т':[], 'U':[], 'V':[], 'W':[], 'Х':['F'], 'Y':[], 'Z':['Е'] }

    Теперь список вариантов дешифрования для любой шифробуквы, входящей в объединенный словарь, должен содержать лишь варианты, общие для словарей letterMappingl и letterMapping2. Например, список для ключа 'Z' включает лишь одну букву ['Е'], поскольку словарь letterMappingl содержал список ['Е', 'Y'], тогда как словарь letterMapping2 - только список ['Е']. Далее повторяем все то же самое для третьего шифрослова, 'MPBKSSIPLC'.
    >>> letterМappingЗ = simpleSubHacker.getblankCipherletterмapping()
    >>> wordPat = makeWordPatterns.getWordPattern('МPBКSSIPLC')
    >>> candidates = wordPatterns.allPatterns[wordPat]
    >>> for i in range(len(candidates)):
    simpleSubHacker.addLettersToМapping(letterNapping3, 'МPВKSSIPLC', candidates[I])
    >>> letterМappingЗ
    ( 'А':[], 'В':['М','S'], 'С':['Y', 'Т'], 'D':[], 'Е':[], 'F':[], 'G':[], 'Н':[], 'I':['Е', 'О'],
    'J':[], 'К':['I', 'А'],'L':['L', 'N'], 'М':['А', 'D'], 'N':[], 'О':[], 'Р':['D', 'I'], 'Q':[],
    'R':[], 'S':['Т', 'Р'], 'Т':[], 'U':[], 'V':[], 'W':[], 'Х':[], 'Y':[], 'Z':[] }


    ведите в интерактивной оболочке следующие инструкции, чтобы создать пересечение словарей letterMapping3 и intersectedMapping (последний представляет собой пересечение словарей letterMappingl и letterMapping2).
    >>> intersecteclМapping = simpleSubHacker.intersectmappings( letterМapping3, intersecteclМapping)
    >>> intersecteclМapping
    { 'А':[], 'В':['S'], 'С':['Т'], 'D':[], 'Е':[], 'F':[], 'G':['В'], 'Н':['М'], 'I':['О'], 'J':[], 'К':['А'],
    'L':['N'], 'М':['А', 'D'], 'N':['L'], 'О':['U'], 'Р':['I'], 'Q':['С'], 'R':['R'], 'S':['Т', 'Р'], 'Т':[],
    'U':[ ], 'V':[], 'W':[], 'Х':['F'], 'Y':[], 'Z':['Е'] }

    В этом примере можно найти решения лишь для ключей, в списке которых содержится единственное значение. Шифробуква 'К' дешифруется в 'А'. Обратите внимание ключ 'М' может быть дешифрован либо в 'А', либо в 'D'. Известно, что 'К' дешифруется в 'А', можно сделать вывод, что ключ 'М' должен дешифроваться в 'D', а не в 'А'. Если достоверно известно, что определенная буква уже используется для какой-то шифробуквы, то она не может быть использована для другой шифробуквы, поскольку такова особенность шифра простой замены.
    Рассмотрим, как функция removeSolvedLettersFromMapping() находит такие буквы и удаляет их из списков вариантов дешифрования.
    Нам понадобится только что созданный объединенный словарь intersectedMapping.

    Вычисление достоверно установленных букв в словарях

    Функция removeSolvedLettersFromМapping() ищет в параметре letterMapping шифробуквы, имеющие лишь один вариант дешифрования. Такие шифробуквы считаются достоверно установленными, а это значит, никакие другие шифробуквы не могут содержать в своих списках данные варианты дешифрования.
    Это может вызвать цепную реакцию, ведь если буква исключается из списка вариантов дешифрования, содержащего всего два элемента, то мы получаем новую достоверно дешифрованную шифробукву. Функция обрабатывает подобные ситуации в цикле, удаляя из словаря достоверно установленные буквы.
    Код (Python):
    1. def removeSolvedLettersFrornМapping(letterMapping):
    2.  . . .
    3.   loopAgain = True
    4.   while loopAgain:
    5.      # Сначала предполагаем, что цикл не будет выполнен повторно
    6.       loopAgain = False
    Не забывайте, что в параметре letterMapping передается ссылка на словарь, поэтому все изменения, внесенные в словарь внутри функции, сохранятся и после ее завершения. Создается переменная loopAgain, хранящая булево значение, которое определяет, должна ли функция пройти цикл повторно, если будет найдена еще одна достоверно установленная буква.
    Функция входит в цикл while. В начале цикла переменная loopAgain устанавливается равной False. Тем самым предполагается, что данная итерация станет последней. Переменная loopAgain будет установлена в True только в том случае, если в ходе итерации программа найдет новую шифробукву с достоверно установленным вариантом дешифрования.
    Далее определяются шифробуквы, для которых имеется ровно один вариант дешифрования. Такие варианты дешифрования будут удаляться из словаря.
    Код (Python):
    1. # solvedLetters - список букв в верхнем регистре, имеющих
    2. # единственное соответствие в словаре letterMapping
    3. solvedLetters = [ ]
    4. for cipherletter in LETTERS:
    5.    if len(letterMapping[cipherletter]) == 1 :
    6.          solvedLetters.append(letterMapping[cipherletter][0] )
    Организуется цикл for по всем возможным 26 шифробуквам. Проверяются списки вариантов дешифрования для каждой шифробуквы (т.е. списки letterMapping[cipherletter]). Проверяется, не равна ли длина такого списка 1. Если это так, тогда для данной шифробуквы существует всего один вариант дешифрования, а значит, шифробуква дешифрована. Соответствующий вариант дешифрования добавляется в список solvedLetters. Этой букве всегда соответствует элемент letterMapping[cipherletter][0], так как в списке letterMapping[cipherletter] всего один элемент. По завершении цикла for переменная solvedLetters будет содержать список всех достоверно установленных вариантов дешифрования шифробукв. Далее функция проверяет, не числятся ли они в списках вариантов дешифрования других шифробукв, и удаляет их из этих списков. С этой целью организуется цикл for по всем 26 возможным шифробуквам для просмотра их списков.
    Окончание здесь
     

    Вложения:

    • 00.png
      00.png
      Размер файла:
      16,2 КБ
      Просмотров:
      496
    Последнее редактирование: 21 июл 2025
    Research и MaKsIm нравится это.
  3. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Шифр Цезаря

    Шифрование/расшифровка/взлом грубой силой и поиск дешифрованного варианта
    Код (Python):
    1. # шифрование/дешифрование шифра Цезаря. Взлом грубой силой и поиск дешифрованного варианта
    2. import re
    3. from math import log10
    4. SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '
    5. class Caesar(object):
    6.     def __init__(self,key):
    7.         self.key = key % len(SYMBOLS)  
    8.     def remove_punctuation(self,text,filter='[^A-Z ]'):
    9.         return re.sub(filter,'',text.upper())    
    10.     def encipher(self,string):          
    11.         ret = ''
    12.         for symbol in string:
    13.             ret += SYMBOLS[(SYMBOLS.find(symbol) - self.key) % len(SYMBOLS)]
    14.         return ret
    15.     def decipher(self,string):  
    16.         ret = ''
    17.         for symbol in string:
    18.             ret += SYMBOLS[(SYMBOLS.find(symbol) + self.key) % len(SYMBOLS)]  
    19.         return ret  
    20. class ngram_score(object):
    21.     def __init__(self,ngramfile,sep=' '): #load a file containing ngrams and counts, calculate log probabilities '''
    22.         self.ngrams = {}
    23.         file = open(ngramfile,encoding="UTF-8")
    24.         for line in file:
    25.             key,count = line.split(sep)      
    26.             self.ngrams[key] = int(count)
    27.         self.L = len(key)
    28.         self.N = sum(self.ngrams.values())
    29.         #calculate log probabilities
    30.         for key in self.ngrams.keys():
    31.             self.ngrams[key] = log10(float(self.ngrams[key])/self.N)
    32.         self.floor = log10(0.01/self.N)
    33.     def score(self,text):# compute the score of text
    34.         score = 0
    35.         ngrams = self.ngrams.__getitem__
    36.         for i in range(len(text)-self.L+1):
    37.             if text[i:i+self.L] in self.ngrams:
    38.                 score += ngrams(text[i:i+self.L])
    39.             else:
    40.                 score += self.floor    
    41.         return score
    42. def main():
    43.     caes = Caesar(key=22)
    44.     plaintext = 'THE CAESAR CIPHER IS ONE OF THE EARLIEST KNOWN AND SIMPLEST CIPHERS'
    45.     plaintext = caes.remove_punctuation(plaintext)
    46.     print("Исходный текст:\n" + plaintext)
    47.     ciphertext = caes.encipher(plaintext)
    48.     print("\nЗашифрованный текст:\n" + ciphertext)
    49.     plaintext = caes.decipher(ciphertext)
    50.     print("\nРасшифрованный текст:\n" + plaintext)
    51.     print("\nВзлом шифра\n")          
    52.     fitness = ngram_score('english_quadgrams.txt') # загружаю файл со статистикой квадрограмм
    53.     scores = [] #пустой список
    54.     print('key',' '*25,'plaintext',' '*30,'fitness')
    55.     print('-' * 78)
    56.     for i in range(1,len(SYMBOLS)):
    57.             text = re.sub('[^A-Z]','',Caesar(i).decipher(ciphertext))
    58.             x = fitness.score(text)
    59.             scores.append((x,i))
    60.             print('%2s %s %.2f' % (i,Caesar(i).decipher(ciphertext),x))
    61.     res = sorted(scores, reverse=True, key=lambda x: x[0])
    62.     max_key = max(scores)
    63.     print("\n",max_key[1])
    64.     print(Caesar(max_key[1]).decipher(ciphertext))
    65. # Функция main() вызывается только в том случае, если файл Caesar.py был
    66. # запущен как программа, а не импортируется в виде модуля другой программой          
    67. if __name__ == '__main__':
    68.     main()
    Для дешифровки использовалась статистика по английским квадрограммам
    00.png
     

    Вложения:

    Research и MaKsIm нравится это.
  4. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Шифр Цезаря
    взлом с использованием словаря с английскими словами

    Шифрование/расшифровка/взлом зашифрованного файла. Для взлома использовался словарь с английскими словами, на экран выводились фрагменты с наилучшей статистикой
    Код (Python):
    1. import re
    2. SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ \n:.,"?-'
    3. class Caesar(object):
    4.     def __init__(self,key):
    5.         self.key = key % len(SYMBOLS)              
    6.  
    7.     def encipher(self,string):          
    8.         ret = ''
    9.         for symbol in string:
    10.             ret += SYMBOLS[(SYMBOLS.find(symbol) - self.key) % len(SYMBOLS)]
    11.         return ret
    12.  
    13.     def decipher(self,string):    
    14.         ret = ''
    15.         for symbol in string:
    16.             ret += SYMBOLS[(SYMBOLS.find(symbol) + self.key) % len(SYMBOLS)]    
    17.         return ret    
    18.  
    19. def remove_punctuation(text,filter='[^A-Z \n:.,"?-]'):
    20.         return re.sub(filter,'',text.upper())
    21.  
    22. def loadDictionary():
    23.     dictionaryFile = open('english.txt') # получаем объект файла словаря
    24.     englishWords = {} #создаем переменную - пустой словарь
    25.     for word in dictionaryFile.read().split('\n'):
    26.         englishWords[word] = None
    27.     dictionaryFile.close()
    28.     return englishWords
    29. # разбиваем с помощью split() переданную строку по переносам, возвращаем список
    30. ENGLISH_WORDS = loadDictionary()
    31. # функция loadDictionary() и возвращаемый ею словарь сохраняется в переменной
    32.  
    33. def getEnglishCount(message):
    34.     message = message.upper()
    35.     message = remove_punctuation(message) #из строки удаляем неучтенные символы
    36.     possibleWords = message.split() #разбиваем строку на слова и сохраняем в переменной
    37.     if possibleWords == []:
    38.         return 0.0 # слова отсутствуют, поэтому возвращаем 0.0.
    39.     matches = 0 #для счетчика mаtсhеs устанавливается нулевое значение
    40.     for word in possibleWords: #В цикле for мы проходим по всем словам в списке
    41.     #possibleWords и проверяем существование каждого из них в словаре ENGLISH_WORDS
    42.         if word in ENGLISH_WORDS:
    43.             matches += 1 #Если слово содержится в словаре, значение счетчика
    44.             #mаtсhеs инкрементируется
    45.     return float(matches)/len(possibleWords)
    46.  
    47. def main():
    48.     caes = Caesar(key=22)
    49.     with open('Carroll.txt', 'r', encoding='utf-8') as file:
    50.         plaintext = file.read()    
    51.     print("Исходный текст:\n",plaintext[:400])
    52.     plaintext = remove_punctuation(plaintext)
    53.     ciphertext = caes.encipher(plaintext)
    54.     print("\nЗашифрованный текст:\n",ciphertext[:400])
    55.     plaintext = caes.decipher(ciphertext)
    56.     print("\nРасшифрованный текст:\n",plaintext[:400])
    57.     print("\nВзламываю шифр методом грубой силы:\n")
    58.     englishPercentageOld = 25
    59.     for i in range(1,len(SYMBOLS)):
    60.           text = Caesar(i).decipher(ciphertext)
    61.           englishPercentage = round(getEnglishCount(text) * 100, 2)
    62.           if englishPercentage > englishPercentageOld:
    63.               print('Процент английских слов: %s%% Ключ #%2d: \n%s' % (englishPercentage, i, text))
    64.               englishPercentageOld = englishPercentage
    65.     print("\nВзлом закончен")
    66. # Функция main() вызывается только в том случае, если файл Caesar.py был
    67. # запущен как программа, а не импортируется в виде модуля другой программой            
    68. if __name__ == '__main__':
    69.     main()
    00.png
     

    Вложения:

    • Carroll.txt
      Размер файла:
      2,4 КБ
      Просмотров:
      182
    • english.txt
      Размер файла:
      443,4 КБ
      Просмотров:
      242
    Последнее редактирование: 16 июл 2025
    MaKsIm нравится это.
  5. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167
    Взлом шифра простой замены
    с использованием словарного шаблона

    (Окончание)​
    Код (Python):
    1. for cipherletter in LETTERS:
    2.     for s in solvedLetters:
    3.         if len(letterMapping[cipherletter]) != 1 and s in letterMapping[cipherletter]:
    4.            letterMapping[cipherletter].remove(s)
    5.         if len(letterMapping[cipherletter]) == 1 :
    6.         # Решена новая буква, продолжаем цикл
    7.            loopAgain = True
    8.         return letterMapping
    Для каждой шифробуквы организуется циклический просмотр всех букв в списке solvedLetters с целью проверки того, не встречаются ли они в списке вариантов дешифрования letterMapping[cipherletter].
    Проверяется как выполнение условия len(letterMapping[cipherletter]) != 1 (т.е. шифробуква еще не дешифрована), так и наличие достоверно установленной буквы в списке вариантов дешифрования данной шифробуквы. В случае выполнения обоих критериев достоверно установленная буква s удаляется из списка вариантов дешифрования.
    Если в результате такого удаления в списке вариантов дешифрования остается только одна буква, то значение переменной loopAgain устанавливается равным True, и тем самым функция сможет удалить эту букву из других списков на следующей итерации цикла. Как только на очередной итерации для переменной loopAgain не будет установлено значение True, цикл while, завершится, и функция вернет словарь letterMapping. Теперь переменная letterMapping будет содержать частично или полностью решенный словарь шифробукв.
    Код (Python):
    1. # Программа взлома шифра простой замены с использованием словарного шаблона
    2. import re, copy, wordPatterns
    3. LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    4. Key = 'TBMKLQOVECINHWRDAUPSGFJYXZ'
    5. def main():
    6.     with open('Carroll.txt','r',encoding='utf-8') as file:
    7.         plaintext = file.read().upper()
    8.     message = encryptMessage(Key, plaintext)
    9.     # Определяем варианты дешифрования шифротекста
    10.     print('Взламываю...')
    11.     letterMapping = hackSimpleSub(message.upper())
    12.     # Выводим результаты
    13.     print('Взломанное сообщение:')
    14.     hackedMessage = decryptWithCipherletterMapping(message, letterMapping)
    15.     print(hackedMessage)
    16. def getWordPattern(word):
    17.     # Возвращает шаблон заданного слова, например '0.1.2.3.4.1.2.3.5.6' для слова 'DUSTBUSTER'
    18.     word = word.upper()
    19.     nextNum = 0
    20.     letterNums = {}
    21.     wordPattern = []
    22.     for letter in word:
    23.         if letter not in letterNums:
    24.             letterNums[letter] = str(nextNum)
    25.             nextNum += 1
    26.         wordPattern.append(letterNums[letter])
    27.     return '.'.join(wordPattern)
    28. def encryptMessage(key, message):#Функция предназначена как для шифрования сообщений
    29.     translated = ''
    30.     for symbol in message.upper():# Цикл по всем символам сообщения
    31.         # На каждой итерации цикла for переменной symbol присваивается
    32.         # очередной символ строки сообщения
    33.         if symbol in LETTERS: # Шифрование символа
    34.             symIndex = LETTERS.find(symbol)
    35.             translated += key[symIndex]  
    36.         else:# Символ отсутствует в наборе LETTERS; просто добавляем его
    37.             translated += symbol
    38.     return translated
    39. def decryptMessage(key, message):#Функция для дешифрования сообщений
    40.     translated = ''
    41.     for symbol in message.upper(): # Цикл по всем символам сообщения
    42.         # На каждой итерации цикла for переменной symbol присваивается
    43.         # очередной символ строки сообщения
    44.         if symbol in key: # Дешифрование символа
    45.             symIndex = key.find(symbol)
    46.             translated += LETTERS[symIndex]  
    47.         else: # Символ отсутствует в LETTERS; просто добавляем его
    48.             translated += symbol
    49.     return translated
    50. def getBlankCipherletterMapping():
    51.     # возвращает словарь, ключами которого являются односимвольные строки,
    52.     # соответствующие 26 буквам алфавита.
    53.     return {'A':[],'B':[],'C':[],'D':[],'E':[],'F':[],'G':[],'H':[],'I':[], 'J':[],'K':[],'L':[],'M':[],
    54.                   'N':[],'O':[],'P':[],'Q':[],'R':[],'S':[],'T':[],'U':[],'V':[],'W':[],'X':[],'Y':[],'Z':[]}
    55. # функция addLettersToMapping, предназначенная для добавления букв в словарь
    56. def addLettersToMapping(letterMapping, cipherword, candidate):
    57.     # Параметр letterMapping - это дешифровальный словарь, обрабатываемый
    58.     # данной функцией. Параметр cipherword - это строковое значение шифрослова.
    59.     # Параметр candidate - это английское слово-кандидат, в которое может быть
    60.     # дешифровано данное шифрослово. Функция добавляет в дешифровальный словарь
    61.     # буквы слова - кандидата в качестве вариантов дешифрования шифробукв
    62.     for i in range(len(cipherword)):
    63.         if candidate[i] not in letterMapping[cipherword[i]]:
    64.             letterMapping[cipherword[i]].append(candidate[i])
    65. def intersectMappings(mapA, mapB):
    66.     # Чтобы построить пересечение словарей, создаем пустой словарь и
    67.     # добавляем в него только варианты, существующие в ОБОИХ словарях
    68.     intersectedMapping = getBlankCipherletterMapping()
    69.     for letter in LETTERS: # Пустой список означает "возможна любая буква";
    70.         # в этом случае копируем список целиком
    71.         if mapA[letter] == []:
    72.             intersectedMapping[letter] = copy.deepcopy(mapB[letter])
    73.         elif mapB[letter] == []:
    74.             intersectedMapping[letter] = copy.deepcopy(mapA[letter])
    75.         else: # Если буква из словаря mapA[letter] существует в словаре
    76.             # mapB[letter], добавляем ее в intersectedМapping[letter]
    77.             for mappedLetter in mapA[letter]:
    78.                 if mappedLetter in mapB[letter]:
    79.                     intersectedMapping[letter].append(mappedLetter)
    80.     return intersectedMapping
    81. def removeSolvedLettersFromMapping(letterMapping):# Шифробуквы, транслируемые
    82.     # только в одну букву, считаются дешифрованными, и соответствующие буквы могут
    83.     # быть удалены из остальных списков. Например, если 'А' транслируется в ['М', 'N'], а
    84.     # 'В' - в ['N'], мы знаем, что 'В' соответствует 'N', поэтому 'N' можно удалить из списка
    85.     # для 'А'. Это также означает, что 'А' транслируется в ['М']. А поскольку 'А' теперь
    86.     # соответствует единственной букве, можно удалить 'М' из остальных списков (вот
    87.     # почему словарь сокращается в цикле)
    88.     loopAgain = True
    89.     while loopAgain: # Сначала предполагаем, что цикл не будет выполнен повторно
    90.         loopAgain = False
    91.         # solvedLetters - список букв в верхнем регистре, имеющих
    92.         # единственное соответствие в словаре letterMapping
    93.         solvedLetters = []
    94.         for cipherletter in LETTERS:
    95.             if len(letterMapping[cipherletter]) == 1:
    96.                 solvedLetters.append(letterMapping[cipherletter][0])
    97.         # Если буква установлена, то она не может быть вариантом дешифрования
    98.         # для другой шифробуквы, поэтому она должна быть удалена из других списков
    99.         for cipherletter in LETTERS:
    100.             for s in solvedLetters:
    101.                 if len(letterMapping[cipherletter]) != 1 and s in letterMapping[cipherletter]:
    102.                     letterMapping[cipherletter].remove(s)
    103.                     if len(letterMapping[cipherletter]) == 1:
    104.                         # Дешифрована новая буква, продолжаем цикл
    105.                         loopAgain = True
    106.     return letterMapping
    107. def hackSimpleSub(message):
    108.     intersectedMap = getBlankCipherletterMapping()
    109.     cipherwordList = re.compile('[^A-Z ]').sub('', message.upper()).split()
    110.     for cipherword in cipherwordList: # Получить новый дешифровальный словарь для
    111.        # каждого шифрослова создается новый пустой словарь шифробукв, который
    112.        # сохраняется в переменной candidateMap
    113.         candidateMap = getBlankCipherletterMapping()
    114.         # Чтобы найти слова-кандидаты для текущего шифрослова, вызываем функцию
    115.         # getWordPattern()
    116.         wordPattern = getWordPattern(cipherword)
    117.         if wordPattern not in wordPatterns.allPatterns:
    118.             continue # этого слова нет в словаре, продолжаем
    119.         # Добавить буквы каждого слова-кандидата в словарь
    120.         for candidate in wordPatterns.allPatterns[wordPattern]:
    121.             addLettersToMapping(candidateMap, cipherword, candidate)
    122.         # Найти пересечение нового словаря с существующим пересечением
    123.         intersectedMap = intersectMappings(intersectedMap, candidateMap)
    124.     # Удалить решенные буквы из других списков
    125.     return removeSolvedLettersFromMapping(intersectedMap)
    126.     # Обработанный словарь шифробукв, полученный в результате выполнения
    127.     # функции removeSolvedLettersFromМapping(), возвращается функцией
    128.     # hackSimpleSub()
    129. def decryptWithCipherletterMapping(ciphertext, letterMapping):
    130.     # Возвращает строку шифротекста, дешифрованную с помощью словаря
    131.     # шифробукв, в которой неоднозначности заменены подчеркиваниями
    132.     # Сначала создаем ключ на основе словаря letterMapping
    133.     key = ['x'] * len(LETTERS)
    134.     for cipherletter in LETTERS:
    135.         if len(letterMapping[cipherletter]) == 1:
    136.             # Если имеется только одна буква, добавляем ее в ключ
    137.             keyIndex = LETTERS.find(letterMapping[cipherletter][0])
    138.             key[keyIndex] = cipherletter
    139.         else: ciphertext = ciphertext.replace(cipherletter, '*') # вместо нерасшифрованных букв стоит '*'
    140.     key = ''.join(key)
    141.     # Дешифруем шифротекст с помощью созданного ключа
    142.     return decryptMessage(key, ciphertext)
    143. # Функция main() вызывается только в том случае, если файл simpleSubHacker.py
    144. # был запущен как программа, а не импортируется в виде модуля другой программой
    145. if __name__ == '__main__':
    146.     main()
     

    Вложения:

    • 00.png
      00.png
      Размер файла:
      23,6 КБ
      Просмотров:
      486
    Последнее редактирование: 16 июл 2025
    MaKsIm нравится это.
  6. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167
    Шифр простой замены
    Шифр простой замены, простой подстановочный шифр, моноалфавитный шифр — класс методов шифрования, которые сводятся к созданию по определённому алгоритму таблицы шифрования, в которой для каждой буквы открытого текста существует единственная сопоставленная ей буква шифр-текста. Само шифрование заключается в замене букв согласно таблице. Для расшифровки достаточно иметь ту же таблицу, либо знать алгоритм, по которому она генерируется.

    Шифрование/расшифровка

    Код (Python):
    1. # Шифр простой замены
    2. LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '
    3. def main():
    4.     with open ('Carroll.txt',encoding="UTF-8") as file:
    5.                plaintext = file.read()
    6.     myKey = 'TBMK LQOVECINHWRDAUPSGFJYXZ'
    7.     print('Исходный текст:')
    8.     print(plaintext[:200])
    9.     plaintext = plaintext.replace('\n','Z')
    10.     print('Шифрую файл с ключом '+myKey)
    11.     translated = encryptMessage(myKey, plaintext)
    12.     print('Зашифрованный текст:\n'+translated[:200])
    13.     plaintext = decryptMessage(myKey, translated)
    14.     plaintext = plaintext.replace('Z','\n')
    15.     print('Расшифрованный текст:\n' + plaintext)
    16. def encryptMessage(key, message):
    17.     translated = '' # Цикл по всем символам сообщения
    18.     for symbol in message.upper():
    19.         # На каждой итерации цикла for переменной symbol присваивается
    20.         # очередной символ строки сообщения
    21.         if symbol in LETTERS:
    22.             # Шифрование символа
    23.             symIndex = LETTERS.find(symbol)
    24.             translated += key[symIndex]
    25.     return translated    
    26. def decryptMessage(key, message):
    27.     translated = ''
    28.     for symbol in message.upper():
    29.         if symbol in key:
    30.             # Дешифрование символа
    31.             symIndex = key.find(symbol)
    32.             translated += LETTERS[symIndex]    
    33.     return translated
    34. #Функция main() вызывается только в том случае, если файл simplSubCipher.py был
    35. #запущен как программа, а не импортируется в виде модуля другой программой
    36. if __name__ == '__main__':
    37.     main()
     

    Вложения:

    • 00.png
      00.png
      Размер файла:
      108,8 КБ
      Просмотров:
      495
    Последнее редактирование: 30 авг 2025
    MaKsIm нравится это.
  7. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Шифр Виженэра

    Шифрование/расшифровка/взлом
    Код (Python):
    1. SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ \n,.:()-'
    2. a = []
    3. import re
    4.  
    5. def lenKey(txt):
    6.     #возвращает длину ключа, иначе выводим на экран [index_list]
    7.     index_list = [] # массив индекса совпадений
    8.     lenMsg = len(txt)  
    9.     shift_txt = txt[2:] + txt[:2]# 2 - минимальный размер ключа
    10.     for shift in range(2, len(txt)//10):
    11.         count = 0 # число совпавших символов(с одинаковым индексом) изначальной строки со сдвинутой на shift символов
    12.         for i in range(lenMsg):
    13.             if txt[i] == shift_txt[i]:
    14.                     count += 1
    15.         index_list.append(round(count * 100 / lenMsg, 3)) # добавляем индекс сопадений в масив
    16.         shift_txt = shift_txt[1:] + shift_txt[:1] # снова двигаем строку на 1 символ
    17.     inx = [] # индексы больших "индексов совпадений" в [index_list]
    18.  
    19.     for i in range(1, len(index_list)-1):          
    20.             if index_list[i-1] < index_list[i] > index_list[i+1] and index_list[i] > 4.4: # определяем скачек 4,4?
    21.                 inx.append(index_list.index(index_list[i]) + 2) # записываем на каких по счету сдвигах был скачек      
    22.     if inx[1] - inx[0] == inx[2] - inx[1]: # этого условия достаточно в большинстве случаев
    23.             return inx[1] - inx[0] # вернет число кратное длине ключа  
    24. def textFormat(txt, lenKey):#Принимает исходный текст и длину ключа. Разбивает текст на lenKey элементов  
    25.     l = [''] * lenKey
    26.     for i in range(0, len(txt), lenKey):
    27.         split_txt = txt[i:i + lenKey]
    28.         try:
    29.             for q in range(lenKey):
    30.                 l[q] += split_txt[q]
    31.         except IndexError:
    32.             None
    33.     return l#Возвращает список из lenKey строк
    34. def letterFrequency(line):#Принимает строку. Возвращает массив с числом(не путать с частотой)
    35.         #символов для каждого символа алфавита
    36.         #[8,13,7,7,0,2,0,4,1,0,0,0,2,2,0,2,29,13,4,9,9,7,16,3,2,17,1,8,10,10,14,22,4]
    37.         letterFrequencyList = [0] * len(SYMBOLS)
    38.         for letterIndex, letter in enumerate(SYMBOLS):
    39.                 letterFrequencyList[letterIndex] = line.count(letter)
    40.         return letterFrequencyList
    41. def caesar(txt, _step):
    42.     ret = ''
    43.     for symbol in txt:
    44.         symbolIndex = SYMBOLS.find(symbol)
    45.         ret += SYMBOLS[(symbolIndex + _step) % len(SYMBOLS)]
    46.     return ret
    47. def matchIndex(letter_count1, letter_count2, num_line, step=0):
    48.     #На вход подается массивы частот символов 0-го и n-го столбца.
    49.     #На выходе получаем сдвиг n-го столбца относительно 0-го
    50.     res = 0
    51.     for i in range(len(SYMBOLS)):
    52.         res += letter_count1[i] * letter_count2[i]
    53.     if 0.064 <= round(res/(len(lines[0])*len(lines[num_line])), 3) <= 0.1:#индекс совпадения для английского языка 0,0644 0,0667      
    54.         return step # вернет нужный сдвиг  
    55.     new_letter_count2 = letterFrequency(caesar(lines[num_line], step+1))  
    56.     return matchIndex(letter_count1, new_letter_count2, num_line, step+1)  
    57. def findKey(lines):
    58.     letter_counts = []
    59.     for line in lines:
    60.         letter_counts.append(letterFrequency(line))  
    61.     keys = [0] * (len(lines) - 1)
    62.     for i in range(0, len(lines)-1):
    63.         keys[i] = matchIndex(letter_counts[0], letter_counts[i + 1], i + 1)      
    64.     for indexLetter in range(len(SYMBOLS)):
    65.         word = SYMBOLS[indexLetter]      
    66.         for key in keys:
    67.             word += SYMBOLS[indexLetter - key]
    68.         a.append(word)  
    69. def a2i(ch):  
    70.     arr = dict([(SYMBOLS[i],i) for i in range(len(SYMBOLS))])
    71.     return arr[ch]
    72. def i2a(i):
    73.     return SYMBOLS[i % len(SYMBOLS)]
    74.  
    75. def decipher(string,key):
    76.     ret = ""
    77.     for (i, c) in enumerate(string):
    78.         i = i%len(key)#ключ растянут по длине зашифрованного текста
    79.         ret += i2a(a2i(c) - a2i(key[i]))
    80.     return ret
    81. def encipher(string,key):
    82.     ret = ''
    83.     for (i,c) in enumerate(string):
    84.         i = i%len(key)
    85.         ret += i2a(a2i(c) + a2i(key[i]))
    86.     return ret
    87. def remove_punctuation(text,filter='[^A-Z \n,.:()-]'):
    88.         return re.sub(filter,'',text.upper())
    89. def loadDictionary():
    90.     dictionaryFile = open('english.txt')
    91.     englishWords = {}
    92.     for word in dictionaryFile.read().split('\n'):
    93.         englishWords[word] = None
    94.     dictionaryFile.close()
    95.     return englishWords
    96. ENGLISH_WORDS = loadDictionary()
    97. def getEnglishCount(message):
    98.     message = message.upper()
    99.     message = remove_punctuation(message)
    100.     possibleWords = message.split()
    101.     if possibleWords == []:
    102.         return 0.0 # No words at all, so return 0.0.
    103.     matches = 0
    104.     for word in possibleWords:
    105.         if word in ENGLISH_WORDS:
    106.             matches += 1
    107.     return float(matches) / len(possibleWords)
    108. if __name__ == '__main__':
    109.     key = 'HAPPY'  
    110.     with open('Carroll.txt', 'r', encoding='utf-8') as file:
    111.         plaintext = file.read()
    112.     plaintext = remove_punctuation(plaintext)
    113.     print("Исходный текст:\n" + plaintext[:100])
    114.     ciphertext = encipher(plaintext,key)  
    115.     print("\nКлюч:"+key+"\nЗашифрованный текст:\n" + ciphertext[:100])  
    116.     plaintext = decipher(ciphertext,key)
    117.     print("\nРасшифрованный текст:\n" + plaintext[:100])
    118.     print('Взламываю...')
    119.     # Работу программы на Python можно в любой момент прервать, нажав <Ctrl+C>
    120.     print('(Нажми Ctrl-C для выхода из программы.)')
    121.     print('Вычисляю длину ключа')  
    122.     lnKey = lenKey(ciphertext)   # вычисляем предполагаемую длину ключа
    123.     print("Предполагаемая длина ключа: ", lnKey)
    124.  
    125.     lines = textFormat(ciphertext, lnKey)  # разбиваем text на lenKey частей и делаем из них массив lines
    126.     findKey(lines) # находим сдвиги всех столбцов относительно первого
    127.     englishPercentageOld = 0
    128.     for i in range(len(SYMBOLS)):          
    129.             decryptedText =  decipher(ciphertext, a[i])
    130.             englishPercentage = round(getEnglishCount(decryptedText) * 100, 2)
    131.             if englishPercentage > englishPercentageOld:
    132.                 print('Процент английских слов: %5s%% Ключ:%s' % (englishPercentage,a[i]))
    133.                 englishPercentageOld = englishPercentage
    134.                 if englishPercentage > 40:
    135.                     print('Возможный результат дешифровки:\n' + decryptedText)                  
    136.     print('\nВзлом закончен ')
    00.png
     
    MaKsIm нравится это.
  8. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Шифр простой замены
    Взлом с использованием статистики английских квадрограмм

    Шифрование/расшифровка/для взлома используем статистику английских квадрограмм и частотный анализ латинских букв в английском языке
    Код (Python):
    1. import random, re
    2. from math import log10
    3. LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '
    4. FREQ_LETTERS = ' ETAOINSHRDLCUMWFGYPBVKXJQZ'
    5. etalon_dict = {' ':14.10,'E':12.10,'T':8.94,'A':8.55,'O':7.47,'I':7.33,'N':7.17,
    6.                'S':6.73,'H':4.96,'R':6.33,'D':3.87,'L':4.21,'C':3.16,'U':2.68,
    7.                'M':2.53,'W':1.83,'F':2.18,'G':2.09,'Y':1.72,'P':2.07,'B':1.60,
    8.                'V':1.06,'K':0.81,'X':0.19,'J':0.22,'Q':0.11,'Z':0.09}
    9. def remove_punctuation(text,filter='[^A-Z ]'):
    10.         return re.sub(filter,'',text.upper())
    11. def main():
    12.     fitness = ngram_score('english_quadgrams.txt') # загружаю статистику по квадраграммам
    13.     with open('Carroll.txt',encoding="UTF-8") as file:
    14.         plaintext = file.read()
    15.     print("Исходный текст:\n" + plaintext[:200])
    16.     plaintext = plaintext.replace('\n','z')
    17.     plaintext = remove_punctuation(plaintext)
    18.     myKey = 'TBMK LQOVECINHWRDAUPSGFJYXZ'
    19.     print('Ключ шифрования:"'+myKey+'"')
    20.     print('Алфавит:\t'+'"'+LETTERS+'"')
    21.     ss = SimpleSubstitution(myKey)
    22.     ciphertext = ss.encipher(plaintext)
    23.     print("Зашифрованный текст:\n" + ciphertext[:200])
    24.     plaintext = ss.decipher(ciphertext)
    25.     plaintext = plaintext.replace('Z','\n')
    26.     print("Расшифрованный текст:\n" + plaintext[:340])
    27.     print('Взламываю...')
    28.     # Работу программы на Python можно в любой момент прервать, нажав <Ctrl+C>
    29.     print('(Нажми Ctrl-C для выхода из программы.)')
    30.     code = list(ciphertext.upper())
    31.     alphabet = {}
    32.     for i in LETTERS:
    33.         alphabet[i]=0
    34.     total = 0
    35.     for i in range(len(code)):
    36.         if code[i] in alphabet:
    37.             alphabet[code[i]] += 1
    38.             total += 1
    39.     sorted_alphabet = dict(sorted(alphabet.items(), key=lambda item: item[1], reverse=True))
    40.     for k, v in sorted_alphabet.copy().items():
    41.         sorted_alphabet[k] = round(v*100/total,3)
    42.     sorted_alphabet_keys = list(sorted_alphabet.keys())
    43.     sorted_alphabet_values = list(sorted_alphabet.values())
    44.     etalon_dict_keys = list(etalon_dict.keys())
    45.     etalon_dict_values = list(etalon_dict.values())
    46.     print('Начинаем расшифровку\n   Эталон   \u2502Частотность в тексте')
    47.     print('\u2500'*2+'\u253C'+'\u2500'*9+'\u253C'+'\u2500'*9)
    48.     for i in range(len(LETTERS)):
    49.         print("%2d\u2502%c: %5.2f \u2502 %c: %5.2f" % (i,etalon_dict_keys[i],etalon_dict_values[i],sorted_alphabet_keys[i],sorted_alphabet_values[i]))
    50.     ETALON = 'x' * len(LETTERS)
    51.     for i in range(len(LETTERS)):
    52.         index = LETTERS.find(etalon_dict_keys[i])
    53.         ETALON = ETALON[:index] + sorted_alphabet_keys[i].upper()+ ETALON[index+1:]
    54.     print('алфавит:\t',LETTERS)
    55.     print('Возможный ключ:\t',ETALON)
    56.     maxkey = list(ETALON)
    57.     maxscore = -99e9
    58.     parentscore,parentkey = maxscore,maxkey[:]
    59.     print('Взламываем шифр простой замены, возможно, придется подождать')
    60.     print('несколько итераций для правильного результата.')
    61.     print('Если хотите завершить расчет \u2500 тогда нажмите CTRL+C')
    62. # продолжаем, пока процесс не прибьет пользователь
    63.     i = 0
    64.     while 1:
    65.         i = i+1
    66.         deciphered = SimpleSubstitution(parentkey).decipher(ciphertext)
    67.         parentscore = fitness.score(deciphered)
    68.         count = 0
    69.         while count < 1000:
    70.             a = random.randint(0,len(LETTERS)-1)
    71.             b =  random.randint(0,len(LETTERS)-1)
    72.             child = parentkey[:]
    73.         # поменять местами два символа в child
    74.             child[a],child[b] = child[b],child[a]
    75.             deciphered = SimpleSubstitution(child).decipher(ciphertext)
    76.             score = fitness.score(re.sub('[^A-Z]','',deciphered))
    77.         # если child был лучше, замените им parent
    78.             if score > parentscore:
    79.                 parentscore = score
    80.                 parentkey = child[:]
    81.                 count = 0
    82.             count = count+1
    83.     # keep track of best score seen so far
    84.         if parentscore > maxscore:
    85.             maxscore,maxkey = parentscore,parentkey[:]
    86.             print('лучший результат на данный момент:',maxscore,'на итерации',i)
    87.             ss = SimpleSubstitution(maxkey)
    88.             print('алфавит:\t',LETTERS)
    89.             print('лучший ключ:\t',''.join(maxkey))
    90.             plaintext = ss.decipher(ciphertext)
    91.             plaintext = plaintext.replace('Z','\n')
    92.             print('открытый текст:\n' + plaintext[:340])
    93.             break
    94.     print('Взлом завершен')
    95. class ngram_score(object):
    96.     def __init__(self,ngramfile,sep=' '):
    97.         ''' load a file containing ngrams and counts, calculate log probabilities '''
    98.         self.ngrams = {}
    99.         fileobj = open('english_quadgrams.txt',encoding="UTF-8")
    100.         for line in fileobj:
    101.             key,count = line.split(sep)
    102.             self.ngrams[key] = int(count)
    103.         self.L = len(key)
    104.         self.N = sum(self.ngrams.values())
    105.         #calculate log probabilities
    106.         for key in self.ngrams.keys():
    107.             self.ngrams[key] = log10(float(self.ngrams[key])/self.N)
    108.         self.floor = log10(0.01/self.N)
    109.         fileobj.close()
    110.     def score(self,text):
    111.         ''' compute the score of text '''
    112.         score = 0
    113.         ngrams = self.ngrams.__getitem__
    114.         for i in range(len(text)-self.L+1):
    115.             if text[i:i+self.L] in self.ngrams: score += ngrams(text[i:i+self.L])
    116.             else: score += self.floor
    117.         return score
    118.  
    119. class SimpleSubstitution(object):
    120.     def __init__(self,key):
    121.         assert len(key) == len(LETTERS)
    122.         self.key = [k.upper() for k in key]
    123.  
    124.     def encipher(self,string):
    125.         ret = ''
    126.         for symbol in string.upper():# Цикл по всем символам сообщения
    127.             if symbol in LETTERS:
    128.                 # Шифрование символа
    129.                 symIndex = LETTERS.find(symbol)
    130.                 ret += self.key[symIndex]
    131.         return ret
    132.     def decipher(self,string):
    133.         key = ''.join(self.key)
    134.         ret = ''
    135.         for symbol in string.upper():
    136.             if symbol in key:
    137.                 # Дешифрование символа
    138.                 symIndex = key.find(symbol)
    139.                 ret += LETTERS[symIndex]
    140.         return ret
    141. #Функция main() вызывается только в том случае, если файл simplSubCipher.py был
    142. #запущен как программа, а не импортируется в виде модуля другой программой
    143. if __name__ == '__main__':
    144.     main()
    00.png
     
    Последнее редактирование: 16 июл 2025
    mantissa и MaKsIm нравится это.
  9. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Шифр простой замены
    для взлома используем частотный анализ,
    словарь, логику и здравый смысл

    Код (Python):
    1. import random, re
    2. LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '
    3. etalon_dict = {' ':14.10, 'E':12.10, 'T':8.94, 'A':8.55, 'O':7.47, 'I':7.33, 'N':7.17, 'S':6.73, 'H':4.96,
    4.                 'R':6.33,'D':3.87, 'L':4.21,'C':3.16,'U':2.68,'M':2.53,'W':1.83,'F':2.18,'G':2.09,'Y':1.72,
    5.                 'P':2.07,'B':1.60,'V':1.06,'K':0.81,'X':0.19,'J':0.22,'Q':0.11,'Z':0.09}
    6. def remove_punctuation(text,filter='[^A-Z \n]'):
    7.         return re.sub(filter,'',text.upper())
    8. def loadDictionary():
    9.     with open('english.txt') as file:
    10.             englishWords = file.read().split('\n')
    11.     return englishWords
    12. class SimpleSubstitution(object):
    13.     def __init__(self,key):
    14.         assert len(key) == len(LETTERS)
    15.         self.key = [k.upper() for k in key]
    16.     def encipher(self,string):
    17.         ret = ''
    18.         for symbol in string.upper():# Цикл по всем символам сообщения
    19.             if symbol in LETTERS:
    20.                 # Шифрование символа
    21.                 symIndex = LETTERS.find(symbol)
    22.                 ret += self.key[symIndex]
    23.             else:
    24.                 ret += symbol
    25.         return ret
    26.     def decipher(self,string):
    27.         key = ''.join(self.key)
    28.         ret = ''
    29.         for symbol in string.upper():
    30.             if symbol in key:
    31.                 # Дешифрование символа
    32.                 symIndex = key.find(symbol)
    33.                 ret += LETTERS[symIndex]
    34.             else:
    35.                 ret += symbol
    36.         return ret
    37. def getRandomKey():#функция возвращает подходящий ключ. Осуществляется случайное
    38.     #перемешивание символов в константе LETTERS
    39.     key = list(LETTERS)
    40.     random.shuffle(key)
    41.     return ''.join(key)
    42. def main():
    43.     with open('Carroll.txt',encoding="UTF-8") as file:
    44.         plaintext = file.read()
    45.     #print("Исходный текст:\n" + plaintext[:200])
    46.     plaintext = remove_punctuation(plaintext.lower())
    47.     ENGLISH_WORDS = loadDictionary()# вызывается функция loadDictionary() и
    48.     # возвращаемый ею словарь сохраняется в переменной ENGLISH_WORDS.
    49.     myKey = 'QBLSXICDOPNETZ YRHAWGUKMVFJ'#getRandomKey()
    50.     print('Ключ шифрования: '+myKey)
    51.     ss = SimpleSubstitution(myKey)
    52.     ciphertext = ss.encipher(plaintext)
    53.     ciphertext = ciphertext.lower()
    54.     print('Взламываю...')
    55.     code = list(ciphertext.upper())
    56.     alphabet = {}
    57.     for i in LETTERS:
    58.         alphabet[i]=0
    59.     total = 0
    60.     for i in range(len(code)):
    61.         if code[i] in alphabet:
    62.             alphabet[code[i]] += 1
    63.             total += 1
    64.     sorted_alphabet = dict(sorted(alphabet.items(), key=lambda item: item[1], reverse=True))
    65.     for k, v in sorted_alphabet.copy().items():
    66.         sorted_alphabet[k] = round(v*100/total,3)
    67.     sorted_alphabet_keys = list(sorted_alphabet.keys())
    68.     sorted_alphabet_values = list(sorted_alphabet.values())
    69.     etalon_dict_keys = list(etalon_dict.keys())
    70.     etalon_dict_values = list(etalon_dict.values())
    71.     print('Начинаем расшифровку\n   Эталон   \u2502Частотность в тексте')
    72.     print('\u2500'*2+'\u253C'+'\u2500'*9+'\u253C'+'\u2500'*9)
    73.     for i in range(len(LETTERS)):
    74.         print("%2d\u2502%c: %5.2f \u2502 %c: %5.2f" % (i,etalon_dict_keys[i],etalon_dict_values[i],sorted_alphabet_keys[i],sorted_alphabet_values[i]))
    75. #Функция main() вызывается только в том случае, если файл simplSubCipher.py был
    76. #запущен как программа, а не импортируется в виде модуля другой программой
    77. if __name__ == '__main__':
    78.     main()
    Из индекса Фридмана [math]IC\approx 0.0644[/math] можно сделать вывод о том, что шифротекст на английском языке. Результат работы программы.
    00.png
    В глаза должно бросится следующее — средняя длина английского слова 6-7 букв, шифрослова намного длиннее, отсюда вывод
    Пробел.jpeg
    пробелу будет соответствовать самый часто встречающийся символ j=19.97
    второй символ по частоте соответствует букве E в английских текстах x=10.05
    Код (Python):
    1. ciphertext = ciphertext.replace(' ','f')# буква F в шифротексте не задействована, заменяем "лжепробел" на F
    2. ciphertext = ciphertext.replace('j',' ')# 1 восстанавливаем пробел
    3. ciphertext = ciphertext.replace('x','E')# 2 самая часто встречающаяся буква английского языка E
    4. message_list = ciphertext.split()# получаю список шифрослов разделенных реальными пробелами
    5.     sorted_message = sorted(message_list, key=len)
    6.     message_list= list(set(sorted_message)) # удаляю повторы'''
    7.     print('однобуквенные слова в шифротексте')
    8.     for word in message_list:
    9.             if len(word)==1:
    10.                     print(word)
    11.     print('однобуквенные слова в английском словаре')    
    12.     for word in ENGLISH_WORDS:
    13.             if len(word)==1:
    14.                     print(word)
    Частота шифробуквы "o"=5.07, скорее всего это 'I'=7.33
    Частота шифробуквы "q"=6.47, скорее всего это 'A'=8.55
    Код (Python):
    1. ciphertext = ciphertext.replace('o','I')# 3 I
    2. ciphertext = ciphertext.replace('q','A')# 4 A
    Обратите внимание на шифрослово "bEcIzzIzc"
    00.png
    Код (Python):
    1. for word in ENGLISH_WORDS:
    2.             if len(word)==9 and word[1]=='E' and word[3]==word[6]=='I' and word[2]==word[8]
    3.             and word[4]==word[5]==word[7]:
    4.                     print(word)
    шифрослову "bEcIzzIzc" соответствует английское слово "BEGINNING", так мы нашли еще 3 буквы "B", "G" и "N"
    Код (Python):
    1. ciphertext = ciphertext.replace('b','B')# 5 BEGINNING
    2. ciphertext = ciphertext.replace('c','G')# 6
    3. ciphertext = ciphertext.replace('z','N')# 7
    А дальше понеслось, заменяя одну шифробукву за другой, просматриваем шифротекст снова и снова, иногда делаем поиск в английском словаре. В конце концов находим полное соответствие 26 буквам латинского алфавита. Последние 3 буквы найдены при ответе на вопрос "А какие еще буквы не найдены в алфавите?"
    Код (Python):
    1. ciphertext = ciphertext.replace('s','D')# 8 AND
    2. ciphertext = ciphertext.replace('v','Y')# 9 BY
    3. ciphertext = ciphertext.replace('w','T')#10 GET
    4. ciphertext = ciphertext.replace('f','O')#11 INTO
    5. ciphertext = ciphertext.replace('d','H')#12 THE
    6. ciphertext = ciphertext.replace('u','V')#13 HAVING
    7. ciphertext = ciphertext.replace('h','R')#14 VERY
    8. ciphertext = ciphertext.replace('a','S')#15 SITTING SISTER
    9. ciphertext = ciphertext.replace('n','K')#16 BOOK
    10. ciphertext = ciphertext.replace('g','U')#17 OUT
    11. ciphertext = ciphertext.replace('e','L')#18 TUNNEL
    12. ciphertext = ciphertext.replace('l','C')#19 CONVERSATIONS
    13. ciphertext = ciphertext.replace('k','W')#20 TWICE WAS
    14. ciphertext = ciphertext.replace('y','P')#21 UP PICTURES
    15. ciphertext = ciphertext.replace('i','F')#22 OF AFTER FOR
    16. ciphertext = ciphertext.replace('t','M')#23 SOMEBODY MOMENT
    17. ciphertext = ciphertext.replace('p','J')#24 JAR JUST
    00.png Общее время взлома около 20 минут
     

    Вложения:

    • english.txt
      Размер файла:
      443,6 КБ
      Просмотров:
      186
    mantissa нравится это.
  10. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Шифр «Изгородь» (Rail-fence Cipher)

    Разновидность перестановочного шифра. Получил название из-за способа шифрования, напоминающего забор из горизонтальных (rail) и диагональных (fence) штакетин. Открытый текст записывается на пересечении диагональных и вертикальных штакетин сперва сверху-вниз на диагональных «штакетниках» воображаемого ограждения, затем, когда достигается нижний штакетник, текст пишется снизу-вверх, пока не достигается верхний штакетник, затем снова сверху-вниз, и так далее, пока не будет записан весь открытый текст. Затем зашифрованный текст считывается построчно. Шифруем текст "HELLO, WORLD!"
    ключ
    N
    шифротекстдиагоналейпериод
    2(N-1)​
    2H.L.O.O.L.
    .E.L.W.R.D
    HLOOLELWRD92
    3H...O...L.
    .E.L.W.R.D
    ..L...O...
    HOLELWRDLO54
    4H.....O...
    .E...W.R..
    ..L.O...L.
    ...L.....D
    HOEWRLOLLD36
    5H.......L.
    .E.....R.D
    ..L...O...
    ...L.W....
    ....O.....
    HLERDLOLWO38
    6H.........
    .E.......D
    ..L.....L.
    ...L...R..
    ....O.O...
    .....W....
    HEDLLLROOW2
    7H.........
    .E........
    ..L.......
    ...L.....D
    ....O...L.
    .....W.R..
    ......O...
    HELLDOLWRO2
    8H.........
    .E........
    ..L.......
    ...L......
    ....O.....
    .....W...D
    ......O.L.
    .......R..
    HELLOWDOLR2
    9H.........
    .E........
    ..L.......
    ...L......
    ....O.....
    .....W....
    ......O...
    .......R.D
    ........L.
    HELLOWORDL2
    При длине открытого текста в 10 символов, ключ может быть от 2 до 9.

    Расшифровка

    Пусть [math]N[/math] — количество строк, используемых при шифровании. При шифровании последовательность расположения каждой буквы меняется в зависимости от цикла. Если [math]N=2,3,4,5[/math], вертикальное расположение повторяется с периодом [math]2,4,6,8[/math]. Период «пилы» от верхнего пика до следующего верхнего пика составляет [math]2(N−1)[/math] символов. [math]L[/math] — длина строки, которую нужно расшифровать.
    строкаИндекс символа
    001020
    119111921
    228121822
    337131723
    4461416
    5515
    00.png
    Текст разбивается на строки так, чтобы количество символов в первой и последней строке равно [math]\displaystyle{\frac{N}{2}}[/math], количество символов в каждой промежуточной строке [math]N-1[/math]. Шифруем текст "WE ARE DISCOVERED. RUN AT ONCE" [math]L=24[/math] при [math]N=6[/math], получим шифротекст:
    6W.........V.........O.....
    .E.......O.E.......T.N....
    ..A.....C...R.....A...C...
    ...R...S.....E...N.....E..
    ....E.I.......D.U.......*.
    .....D.........R.........*
    WVOEOETNACRACRSENEEIDU*DR*
    Для упрощения зашифрования дополним исходный текст символами (*), которые будут удалены при расшифровке. Добавим переменные, [math]x+1 =[/math] количество диагоналей в зашифрованном тексте и [math]y[/math] количество пустых символов в последней диагонали. [math]\displaystyle 1={\frac {L+y}{N+(N-1)x}}[/math] Вычислим [math]x[/math] и [math]y[/math]. Увеличиваем [math]x[/math] на 1, пока знаменатель не станет больше, чем [math]L[/math], а затем найдем число [math]y[/math]. Пусть [math]L=24[/math] и [math]N=6[/math], решим уравнение [math]1=\displaystyle{\frac {24+y}{6+5x}}[/math] Упрощаем дробь [math]24+y=6+5x,\ [/math] [math]18+y=5x,\ [/math] [math]y\geqslant 0[/math]
    [math]18+y=5x[/math][math]x[/math]
    [math]18+y>5[/math]1
    [math]18+y>10[/math]2
    [math]18+y>15[/math]3
    [math]18+y=20[/math]4
    При [math]N=6[/math] строк, [math]x+1=5 [/math] диагоналей, [math]y=2[/math] пустых символа в конце открытого текста.

    Криптоанализ

    Ключом шифра является [math]N[/math], количество строк. Если [math]N[/math] известно, зашифрованный текст можно расшифровать с помощью приведённого выше алгоритма. Значения [math]N[/math] равные или превышающие [math]L[/math], длину зашифрованного текста, не подходят, так как в этом случае зашифрованный текст совпадает с исходным. Количество подходящих ключей невелико, это позволяет использовать метод перебора всех возможных ключей. В результате шифр «изгороди» считается слабым.
    Код (Python):
    1. import re
    2. SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ \n,.()?":-'
    3. class Railfence(object):
    4.     def __init__(self, key):
    5.         self.key = key
    6.         assert 1 < self.key, 'неправильный ключ: key='+str(key)+', должен быть больше единицы'
    7.     def encipher(self,string):
    8.         return ''.join(self.buildfence(string, self.key))
    9.     def decipher(self,string):
    10.         ind = range(len(string))
    11.         pos = self.buildfence(ind, self.key)
    12.         return ''.join(string[pos.index(i)] for i in ind)
    13.     def buildfence(self,chars, numrails):
    14.         fence = [[None] * len(chars) for n in range(numrails)]
    15.         rails = list(range(numrails - 1)) + list(range(numrails - 1, 0, -1))
    16.         for n, x in enumerate(chars):
    17.             fence[rails[n % len(rails)]][n] = x
    18.         return [c for rail in fence for c in rail if c is not None]
    19. def remove_punctuation(text,filter='[^A-Z \n,.()?":-]'):
    20.         return re.sub(filter,'',text.upper())
    21. def loadDictionary():
    22.     dictionaryFile = open('english.txt') # получаем объект файла словаря
    23.     englishWords = {} #создается переменная englishWords в виде пустого словаря
    24.     for word in dictionaryFile.read().split('\n'):
    25.         englishWords[word] = None
    26.     dictionaryFile.close()
    27.     return englishWords
    28. #Строковый метод split() разбивает переданную ему строку по переносам,
    29. #возвращая список из нескольких строк
    30. ENGLISH_WORDS = loadDictionary()
    31. # вызывается функция loadDictionary() и возвращаемый ею словарь сохраняется
    32. # в переменной ENGLISH_WORDS.
    33. def getEnglishCount(message):
    34.     message = message.upper() #символы строки преобразуются в верхний регистр
    35.     message = remove_punctuation(message) #с помощью функции removeNonLetters()
    36.     # из строки удаляются все небуквенные символы, числа и знаки препинания
    37.     possibleWords = message.split() #метод split() разбивает строку на слова
    38.     #и сохраняет их в переменной possibleWords
    39.     if possibleWords == []:
    40.         return 0.0 # слова отсутствуют, поэтому возвращаем 0.0.
    41.     matches = 0 #для счетчика mаtсhеs устанавливается нулевое значение
    42.     for word in possibleWords: #В цикле for мы проходим по всем словам в списке
    43.     #possibleWords и проверяем существование каждого из них в словаре ENGLISH_WORDS
    44.         if word in ENGLISH_WORDS:
    45.             matches += 1 #Если слово содержится в словаре, значение счетчика
    46.             #mаtсhеs инкрементируется
    47.     return float(matches)/len(possibleWords)
    48. def main():
    49.     key = 22
    50.     rf = Railfence(key)
    51.     with open('decrypted.txt','r',encoding = 'utf-8') as file:
    52.         plaintext = file.read()
    53.     plain_text = remove_punctuation(plaintext)
    54.     print("Исходный текст:\n" + plaintext[:200])
    55.     ciphertext = rf.encipher(plaintext.upper())
    56.     print("\nЗашифрованный текст с ключом = "+str(key)+"\n" + ciphertext[:200])
    57.     plaintext = rf.decipher(ciphertext)
    58.     print("\nРасшифрованный текст:\n" + plaintext[:200])
    59.     print("\nВзлом шифра...\nНажмите Ctrl+C для завершения программы")
    60.     englishPercentageOld = 0
    61.     for key in range(2,len(ciphertext)+1):
    62.         text = Railfence(key).decipher(ciphertext)
    63.         englishPercentage = round(getEnglishCount(text) * 100, 2)
    64.         if englishPercentage > englishPercentageOld:
    65.             print('Процент английских слов: %s%% Ключ #%2d: \n%s' % (englishPercentage, key, text[:165]))
    66.             englishPercentageOld = englishPercentage
    67.     print('\nВзлом закончен')
    68. # Функция main() вызывается только в том случае, если файл railfence.py был
    69. # запущен как программа, а не импортируется в виде модуля другой программой
    70. if __name__ == '__main__':
    71.     main()
    00.png
     
    Последнее редактирование: 27 авг 2025
    Asyroino нравится это.
  11. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Перестановочный шифр — вертикальная перестановка

    Метод симметричного шифрования, в котором элементы исходного открытого текста меняют местами. Элементами текста могут быть отдельные символы, пары букв, тройки букв, комбинирование этих случаев. Шифры перестановки делят на два класса:
    • Шифры одинарной (простой) перестановки — при шифровании символы открытого текста перемещаются с исходных позиций в новые один раз.
    • Шифры множественной (сложной) перестановки — при шифровании символы открытого текста перемещаются с исходных позиций в новые несколько раз.
    Альтернатива шифрам перестановки — подстановочные шифры. В них элементы текста не меняют свою последовательность, а изменяются сами.
    Разновидность шифра перестановки — вертикальная перестановка. Используется прямоугольная таблица, в которую в верхнюю строку записывается ключ, а начиная со второй строки, открытое сообщение, которое записывается по строкам слева направо. Ключ = 'GERMAN', открытый текст = 'WE ARE DISCOVERED. RUN AT ONCE'
    GERMAN
    WEXARE
    XDISCO
    VEREDX
    RUNXAT
    XONCE_
    Выписывается шифрограмма по вертикалям, при этом столбцы выбираются в порядке, определяемом ключом.
    AEGMNR
    REWAEX
    CDXSOI
    DEVEXR
    AURXTN
    EOXC_N
    шифротекст = RCDAEEDEUOWXVRXASEXCEOXT_XIRNN

    Шифрация/дешифрация

    Код (Python):
    1. SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    2. import re
    3. def remove_punctuation(text,filter='[^A-Z]'):
    4.         return re.sub(filter,'',text.upper())
    5. class ColTrans(object):
    6.     def __init__(self,keyword):
    7.         self.keyword = keyword
    8.         assert len(keyword)>0, 'недопустимое ключевое слово в init: должно быть >= 1'
    9.     # возвращает отсортированные индексы ключевого слова,'GERMAN' = [2,1,5,3,0,4]
    10.     def sortind(self,word):
    11.         t1 = [(word[i],i) for i in range(len(word))]
    12.         t2 = [(k[1],i) for i,k in enumerate(sorted(t1))]
    13.         return [q[1] for q in sorted(t2)]
    14.     # возвращает неотсортированные индексы ключевого слова,'GERMAN' = [0,1,2,3,4,5]
    15.     def unsortind(self,word):
    16.         t1 = [(word[i],i) for i in range(len(word))]
    17.         return [q[1] for q in sorted(t1)]
    18.     def encipher(self,string):
    19.         string = string.replace('X','KS')
    20.         if (len(string) % len(self.keyword)) != 0:
    21.             for i in range(len(self.keyword)-(len(string) % len(self.keyword))):
    22.                 string += ' '
    23.         string = string.replace(' ','X')
    24.         string = remove_punctuation(string)
    25.         ret = ''
    26.         ind = self.sortind(self.keyword)
    27.         for i in range(len(self.keyword)):
    28.             ret += string[ind.index(i)::len(self.keyword)]
    29.         return ret
    30.     # расшифровка может быть затруднена, так как столбцы могут быть невыровнены
    31.     def decipher(self,string):
    32.         L,M = len(string),len(self.keyword)
    33.         ret = ['_']*L
    34.         ind = self.unsortind(self.keyword)
    35.         upto = 0
    36.         for i in range(len(self.keyword)):
    37.             thiscollen = (int)(L/M)
    38.             if ind[i]< L%M: thiscollen += 1
    39.             ret[ind[i]::M] = string[upto:upto+thiscollen]
    40.             upto += thiscollen
    41.         s = ''.join(ret)
    42.         s = s.replace('X',' ')
    43.         s = s.replace('KS','X')
    44.         return s
    45. def main():
    46.     plaintext = 'WE ARE DISCOVERED. RUN AT ONCE'
    47.     print("Исходный текст:\n" + plaintext)
    48.     keyword = 'german'
    49.     cl = ColTrans(keyword)
    50.     ciphertext = cl.encipher(plaintext)
    51.     print("\nЗашифрованный текст с ключом = ",keyword,"\n",ciphertext)
    52.     plaintext = cl.decipher(ciphertext)
    53.     print("\nРасшифрованный текст:\n",plaintext)
    54. # Функция main() вызывается только в том случае, если файл ColTrans.py был
    55. # запущен как программа, а не импортируется в виде модуля другой программой
    56. if __name__ == '__main__':
    57.     main()
    00.png

    Взлом перестановочного шифра

    Предполагаем, что длина ключа от 2 до 10 символов
    Код (Python):
    1. SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ \n,.()?":-'
    2. import re
    3. from itertools import permutations
    4. def remove_punctuation(text,filter='[^A-Z \n,.()?":-]'):
    5.         return re.sub(filter,'',text.upper())
    6. class ColTrans(object):
    7.     def __init__(self,keyword):
    8.         self.keyword = keyword
    9.         assert len(keyword)>0, 'недопустимое ключевое слово в init: должно быть >= 1'
    10.     # возвращает отсортированные индексы ключевого слова,'GERMAN' = [2,1,5,3,0,4]
    11.     def sortind(self,word):
    12.         t1 = [(word[i],i) for i in range(len(word))]
    13.         t2 = [(k[1],i) for i,k in enumerate(sorted(t1))]
    14.         return [q[1] for q in sorted(t2)]
    15.     # возвращает неотсортированные индексы ключевого слова,'GERMAN' = [0,1,2,3,4,5]
    16.     def unsortind(self,word):
    17.         t1 = [(word[i],i) for i in range(len(word))]
    18.         return [q[1] for q in sorted(t1)]      
    19.     def encipher(self,string):
    20.         if (len(string) % len(self.keyword)) != 0:
    21.             for i in range(len(self.keyword)-(len(string) % len(self.keyword))):
    22.                 string += ' '
    23.         string = remove_punctuation(string)
    24.         ret = ''
    25.         ind = self.sortind(self.keyword)
    26.         for i in range(len(self.keyword)):
    27.             ret += string[ind.index(i)::len(self.keyword)]
    28.         return ret
    29.     # расшифровка может быть затруднена, так как столбцы могут быть невыровнены
    30.     def decipher(self,string):
    31.         L,M = len(string),len(self.keyword)
    32.         ret = ['_']*L
    33.         ind = self.unsortind(self.keyword)
    34.         upto = 0
    35.         for i in range(len(self.keyword)):
    36.             thiscollen = (int)(L/M)
    37.             if ind[i]< L%M: thiscollen += 1
    38.             ret[ind[i]::M] = string[upto:upto+thiscollen]
    39.             upto += thiscollen
    40.         s = ''.join(ret)
    41.         return s
    42. def isEnglish(message, wordPercentage=80, letterPercentage=85):
    43.     # По умолчанию 20% слов должны быть в файле словаря,
    44.     # а 85% символов сообщения должны быть буквами или
    45.     # пробелами (а не знаками препинания или числами)
    46.     wordsMatch = getEnglishCount(message) * 100 >= wordPercentage
    47.     numLetters = len(remove_punctuation(message))
    48.     messageLettersPercentage = float(numLetters) / len(message) * 100
    49.     lettersMatch = messageLettersPercentage >= letterPercentage
    50.     return wordsMatch and lettersMatch
    51. def loadDictionary():
    52.     dictionaryFile = open('english.txt') # получаем объект файла словаря
    53.     englishWords = {} # создается переменная englishWords в виде пустого словаря
    54.     for word in dictionaryFile.read().split('\n'):
    55.         englishWords[word] = None
    56.     dictionaryFile.close()
    57.     return englishWords
    58. #Строковый метод split() разбивает переданную ему строку по переносам,
    59. #возвращая список из нескольких строк
    60. ENGLISH_WORDS = loadDictionary()
    61. # вызывается функция loadDictionary() и возвращаемый ею словарь сохраняется
    62. # в переменной ENGLISH_WORDS.
    63. def getEnglishCount(message):
    64.     message = message.upper() #символы строки преобразуются в верхний регистр
    65.     message = remove_punctuation(message) #с помощью функции removeNonLetters()
    66.     # из строки удаляются все небуквенные символы, такие как числа и знаки
    67.     #препинания
    68.     possibleWords = message.split() #метод split() разбивает строку на слова
    69.     #и сохраняет их в переменной possibleWords
    70.     if possibleWords == []:
    71.         return 0.0 # слова отсутствуют, поэтому возвращаем 0.0.
    72.     matches = 0 #для счетчика mаtсhеs устанавливается нулевое значение
    73.     for word in possibleWords: #В цикле for мы проходим по всем словам в списке
    74.     #possibleWords и проверяем существование каждого из них в словаре ENGLISH_WORDS
    75.         if word in ENGLISH_WORDS:
    76.             matches += 1 #Если слово содержится в словаре, значение счетчика
    77.             #mаtсhеs инкрементируется
    78.     return float(matches)/len(possibleWords)
    79. def main():
    80.     with open('Carroll.txt','r',encoding = 'utf-8') as file:
    81.             plaintext = file.read()
    82.     keyword = 'german'
    83.     cl = ColTrans(keyword)      
    84.     ciphertext = cl.encipher(plaintext)
    85.     print("\nВзлом шифра...\nНажмите Ctrl+C для завершения программы")
    86.     englishPercentageOld = 0
    87.     for j in range(2,10):
    88.             key1 = [i for i in range(j)]
    89.             for p in permutations(key1):
    90.                     plaintext = ColTrans(p).decipher(ciphertext)
    91.                     englishPercentage = round(getEnglishCount(plaintext) * 100, 2)
    92.                     if englishPercentage > englishPercentageOld:
    93.                             print('Процент английских слов: %5s%%' % englishPercentage)
    94.                             print('Возможная длина ключа:',len(p))
    95.                             print('Возможный ключ:',p)
    96.                             print('Возможный результат дешифровки:\n' + plaintext[:100])
    97.                             englishPercentageOld = englishPercentage          
    98.     print('\nВзлом закончен')
    99. # Функция main() вызывается только в том случае, если файл ColTrans.py был
    100. # запущен как программа, а не импортируется в виде модуля другой программой
    101. if __name__ == '__main__':
    102.     main()
    01.png
     

    Вложения:

    • 08.gif
      08.gif
      Размер файла:
      1,9 МБ
      Просмотров:
      448
    mantissa нравится это.
  12. asmlamo

    asmlamo Well-Known Member

    Публикаций:
    0
    Регистрация:
    18 май 2004
    Сообщения:
    1.750
  13. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167

    Взлом шифра «Квадрат Полибия»

    Шифр имеет большое пространство ключей. При использовании квадрата размером [math]5\times 5[/math] количество ключей равно [math]25!=15511210043330985984\cdot 10^{6}=2^{83}[/math]. Единственное отличие шифра «Квадрат Полибия» от шифра простой замены заключается в том, что буква исходного текста замещается двумя символами.
    Для атаки используют методику, применяемую при взломе шифра простой замены — поиск восхождением к вершине.
    В качестве основного ключа выбирается случайный квадрат размером [math]5\times 5[/math]. В ходе каждой итерации ключ подвергается незначительным изменениям и проверяется насколько распределение триграмм или квадрограмм в тексте, полученном в результате расшифровки, соответствует распределению в естественном языке.
    Описание поиска восхождением к вершине на wikipedia
     
    Последнее редактирование: 30 авг 2025
  14. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    610
    оно? https://habr.com/ru/articles/345876/
     
  15. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167
    who_know777, написано по мотивам python-hillcipher/break.py at master · anag004/python-hillcipher · GitHub
    Код (Python):
    1. import itertools
    2. from math import log10
    3. from copy import deepcopy
    4. LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    5. common_ngraphs = []
    6. key = [[22, 9],[3, 6]]
    7. key_length = len(key)
    8. freqs = []
    9. class ngram_score(object):
    10.     def __init__(self,ngramfile):
    11.         #загрузить файл содержащий n-граммы и счетчики,
    12.         #вычислить логарифмические вероятности
    13.         self.ngrams = {}
    14.         filebuffer = open(ngramfile,encoding="UTF-8")
    15.         for line in filebuffer:
    16.             #n-граммы и счетчики разделены пробелом на одной строке
    17.             key,count = line.split(' ')
    18.             self.ngrams[key] = int(count)
    19.         self.L = len(key)
    20.         self.N = sum(self.ngrams.values())
    21.         #вычислить логарифмические вероятности
    22.         for key in self.ngrams.keys():
    23.             self.ngrams[key] = log10(float(self.ngrams[key])/self.N)
    24.         self.floor = log10(0.01/self.N)
    25.     def score(self,text):# вычислить оценку текста
    26.         score = 0
    27.         ngrams = self.ngrams.__getitem__
    28.         for i in range(len(text)-self.L+1):
    29.             if text[i:i+self.L] in self.ngrams: score += ngrams(text[i:i+self.L])
    30.             else: score += self.floor
    31.         return score
    32. class IncorrectLength(Exception):
    33.     pass
    34. class InverseError(Exception):
    35.     pass
    36. # Преобразовать символ в число mod len(LETTERS)
    37. def char2num(c):
    38.     return LETTERS.find(c.upper())
    39. # Преобразовать число mod len(LETTERS) в символ
    40. def num2char(n):
    41.     return LETTERS[n].lower()
    42. #вычислить коэффициент Фридмана
    43. def friedman(text):
    44.     n = 0
    45.     freq = [0 for i in range(len(LETTERS))]
    46.     for c in text:
    47.         freq[char2num(c)] += 1
    48.         n += 1
    49.     ic = 0
    50.     for f in freq:
    51.         ic += f * (f - 1)
    52.     ic = float(ic) / float((n * (n - 1)))
    53.     return ic
    54. def get_mapping(c1, c2):
    55.     s = ""
    56.     for i in range(len(c1)):
    57.         s += common_ngraphs[key_length - 2][c1[i]] + "->" + freqs[c2[i]][1] + " "
    58.     return s
    59. def matrix_mult(mat, vec, m):
    60.     n = len(mat)
    61.     assert(len(vec) == n)
    62.     # Create the result vector
    63.     res = [0 for i in range(n)]
    64.     for i in range(n):
    65.         # Create a row sum variable
    66.         row_sum = 0
    67.         for j in range(n):
    68.             row_sum += mat[i][j] * vec[j]
    69.             row_sum %= m
    70.         res[i] = row_sum
    71.     return res
    72. def get_key(d, e):
    73.     n = len(d)
    74.     mat1 = construct_matrix(d)
    75.     mat2 = construct_matrix(e)
    76.     mat1_inv = []
    77.     try:
    78.         mat1_inv = matrix_inverse(mat1, len(LETTERS))
    79.     except InverseError:
    80.         return (False, False, "diagraph non-invertible")
    81.     key = matrix_mult2(mat2, mat1_inv, len(LETTERS))
    82.     key_inv = []
    83.     try:
    84.         key_inv = matrix_inverse(deepcopy(key), len(LETTERS))
    85.     except InverseError:
    86.         return (False, False, "key non-invertible")
    87.     return (key, key_inv, "invertible")
    88. def matrix_mult2(mat1, mat2, m):
    89.     assert(len(mat1[0]) == len(mat2))
    90.     rows = len(mat1)
    91.     cols = len(mat2[0])
    92.     mid = len(mat1[0])
    93.     result = [[0 for i in range(cols)] for j in range(rows)]
    94.     for i in range(rows):
    95.         for j in range(cols):
    96.             tmp = 0
    97.             for k in range(mid):
    98.                 tmp += mat1[i][k] * mat2[k][j]
    99.                 tmp %= m
    100.             result[i][j] = tmp
    101.     return result
    102. # Построить матрицу N на N из n-грамм
    103. def construct_matrix(d):
    104.     n = len(d)
    105.     ans = [[0 for i in range(n)] for j in range(n)]
    106.     for i in range(n):
    107.         for j in range(n):
    108.             ans[j][i] = char2num(d[i][j])
    109.     return ans
    110. def get_diff(c1, c2):
    111.     ans = 0
    112.     for i in range(len(c1)):
    113.         ans += abs(c1[i] - c2[i])
    114.     return ans
    115. def gcd(a, b):
    116.     if (a == 0 and b == 0):
    117.         raise Exception("Cannot find gcd of zero and zero")
    118.     if (b == 0):
    119.         return [a, 1, 0]
    120.     else:
    121.         arr = gcd(b, a % b)
    122.         q = a//b #int(a / b)
    123.         return [arr[0], arr[2], arr[1] - q * arr[2]]
    124. def nCr(n, r):
    125.     ans = 1
    126.     for i in range(r):
    127.         ans *= (n - i)
    128.     return ans
    129. # Поменять местами две строки матрицы
    130. def swap_row(mat, i, j):
    131.     for k in range(len(mat[i])):
    132.         mat[i][k], mat[j][k] = mat[j][k], mat[i][k]
    133. # Умножить строки матрицы на некоторый коэффициент
    134. def mult_row(mat, row_idx, mult_factor, m):
    135.     for i in range(len(mat)):
    136.         mat[row_idx][i] *= mult_factor
    137.         mat[row_idx][i] = get_mod(mat[row_idx][i], m)
    138. # Сложить factor * (row j) матрицы до строки i
    139. def add_row(mat, i, j, factor, m):
    140.     for k in range(len(mat)):
    141.         mat[i][k] += (factor * mat[j][k])
    142.         mat[i][k] = get_mod(mat[i][k], m)
    143. def get_mod(a, m):
    144.     if (a >= 0):
    145.         return a % m
    146.     else:
    147.         return m - ((-a) % m)
    148.  
    149. def mod_inverse(a, m):
    150.     arr = gcd(a, m)
    151.     if (arr[0]) != 1:
    152.         raise InverseError
    153.     else:
    154.         return get_mod(arr[1], m)
    155.  
    156. def matrix_inverse(mat, m):
    157.     n = len(mat)
    158.     # Create a n x n identity matrix
    159.     res = [[0 for i in range(n)] for i in range(n)]
    160.     for i in range(n):
    161.         res[i][i] = 1
    162.     # Iterate over the rows to find nz_idx
    163.     for row_idx in range(n):
    164.         # Find a non-zero row
    165.         nz_idx = -1
    166.         for i in range(row_idx, n):
    167.             if (gcd(mat[i][row_idx], m)[0] == 1):
    168.                 nz_idx = i
    169.                 break
    170.         if (nz_idx == -1):
    171.             raise InverseError
    172.  
    173.         # Поменяйте местами эту строку с другой.
    174.         swap_row(mat, row_idx, nz_idx)
    175.         swap_row(res, row_idx, nz_idx)
    176.         # Умножьте эту строку на элемент, чтобы сделать элемент head равным единице.
    177.         mult_factor = mod_inverse(mat[row_idx][row_idx], m)
    178.         mult_row(mat, row_idx, mult_factor, m)
    179.         mult_row(res, row_idx, mult_factor, m)
    180.         # Очистите все, что сверху и снизу.
    181.         for i in range(n):
    182.             if (i != row_idx):
    183.                 # Очистить эту строку
    184.                 mult_factor = mat[i][row_idx]
    185.                 add_row(mat, i, row_idx, -mult_factor, m)
    186.                 add_row(res, i, row_idx, -mult_factor, m)
    187.     return res
    188. # Применяет матрицу к тексту и возвращает новый текст.
    189. def apply(mat, text):
    190.     mat_length = len(mat)
    191.         # Создать пустой массив символов out_text
    192.     out_text = []
    193.     # Повторить текст
    194.     curr_idx = 0
    195.     while curr_idx < len(text):
    196.         # Создайте строку для обработки с помощью крестиков
    197.         curr_text = [char2num('z') for i in range(mat_length)]
    198.         # Отслеживайте позиции, в которые следует вставлять символы в out_text.
    199.         insert_positions = []
    200.         # Подсчитайте количество прочитанных символов из текста.
    201.         read_ctr = 0
    202.         # Наращивать curr_text
    203.         while(read_ctr < mat_length and curr_idx < len(text)):
    204.             if (text[curr_idx].isalpha()):
    205.                 # Это символ
    206.                 curr_text[read_ctr] = char2num(text[curr_idx])
    207.                 # Вставьте фиктивный символ в out_text
    208.                 out_text.append('z')
    209.                 insert_positions.append(curr_idx)
    210.                 # Increment both counters
    211.                 read_ctr += 1
    212.                 curr_idx += 1
    213.             else:
    214.                 # Скопировать текстовый символ в out_text
    215.                 out_text.append(text[curr_idx])
    216.                 curr_idx += 1
    217.         # Выполнить умножение матриц mod len(LETTERS) и получить зашифрованный текст
    218.         mat_text = matrix_mult(mat, curr_text, len(LETTERS))
    219.         # Поместите текст в out_text
    220.         for i in range(read_ctr):
    221.             out_text[insert_positions[i]] = num2char(mat_text[i])
    222.         # Добавить остаточный out_text
    223.         for i in range(read_ctr, mat_length):
    224.             out_text.append(num2char(mat_text[i]))
    225.     # Преобразовать out_text в строку
    226.     out_text = ''.join(out_text)
    227.     return out_text
    228. def main():
    229.     plaintext = "hello world revolution"
    230.     print('Исходный текст:\n'+plaintext)
    231.     plaintext = plaintext.replace(' ','z')
    232.     # Прочитать ключ как матрицу
    233.     key = [[22, 9],[3, 6]]
    234.     ciphertext = apply(key, plaintext)
    235.     print('Зашифрованный текст:\n'+ciphertext)
    236.     fitness = ngram_score('english_quadgrams.txt') # load our quadgram statistics
    237.     # Получить обратную часть ключа
    238.     key_inverse = matrix_inverse(key, len(LETTERS))
    239.     # Создать пустой массив символов открытого текста
    240.     #plaintext = []
    241.     plaintext = apply(key_inverse, ciphertext)
    242.     plaintext = plaintext.replace('z',' ')
    243.     print('Расшифрованный текст:\n'+plaintext)
    244.     print('Взламываю...')
    245.     temp = int((len(ciphertext) - len('plaintext'))/2)+1
    246.     print('  c1     c2   \u2502'+' '*9+'KEY'+' '*8+'\u2502'+' '*temp+'plaintext'+' '*(temp+1)+'\u2502fitness')
    247.     # Create a ngraph frequency table
    248.     idx = {}
    249.     num_ngraphs = 0
    250.     filebuffer = open('english_bigrams.txt',encoding="UTF-8")
    251.     d = []
    252.     for line in filebuffer:
    253.         key,count = line.split(' ')
    254.         d.append(key.lower())
    255.     common_ngraphs.append(d)
    256.     trial_length = len(common_ngraphs[0])
    257.  
    258.     scores = []
    259.     tol = 0.01
    260.     ideal_ic = 0.0686 # индекс совпадений Фридмана для английского 0.0686, для русского 0.0553, для немецкого 0.0762
    261.     diff = 100
    262.     # Create a diagraph array
    263.     read_ctr = 0
    264.     while(read_ctr < len(ciphertext)):
    265.         ngraph = []
    266.         while(len(ngraph) < key_length):
    267.             if (read_ctr >= len(ciphertext)):
    268.                 raise IncorrectLength("Length of ciphertext is not multiple of key length")
    269.             if (ciphertext[read_ctr].isalpha()):
    270.                 ngraph.append(ciphertext[read_ctr])
    271.             read_ctr += 1
    272.         # Process the diagraph
    273.         ngraph = ''.join(ngraph)
    274.         if (ngraph in idx):
    275.             freqs[idx[ngraph]][0] += 1
    276.         else:
    277.             idx[ngraph] = num_ngraphs
    278.             freqs.append([1, ngraph])
    279.             num_ngraphs += 1
    280.     freqs.sort(reverse=True)
    281.     num_possibles = 0
    282.     found_keys = []
    283.     final_length = min(trial_length, len(freqs))
    284.     ctr = 0
    285.     total_tries = nCr(final_length, key_length) ** 2
    286.     x_old = -999.99
    287.     # Try various combinations of diagraphs
    288.     for c1 in itertools.combinations(range(final_length), key_length):
    289.         for c2 in itertools.combinations(range(final_length), key_length):
    290.             # Print the progress
    291.             progress = (100 * ctr) / total_tries
    292.             ctr += 1
    293.             if (get_diff(c1, c2) > diff):
    294.                 continue
    295.             (key, key_inv, verdict) = get_key([common_ngraphs[key_length - 2][c1[i]] for i in range(key_length)], [freqs[c2[i]][1] for i in range(key_length)])
    296.  
    297.             if (key != False):
    298.                 plaintext = apply(key_inv, ciphertext)
    299.                 x = fitness.score(plaintext.upper())
    300.                 plaintext_ic = friedman(plaintext)
    301.                 if (abs(plaintext_ic - ideal_ic) <= tol and (key not in found_keys)):
    302.                     # This can be a correct value
    303.                     if x_old <= x:
    304.                         scores.extend([(x,key,key_inv)])
    305.                         print(get_mapping(c1, c2)+'\u2502%20s\u2502 %s \u2502%.2f'%(key,plaintext,x))
    306.                         x_old = x
    307.     max_key = max(scores)
    308.     print('fitness = '+str(max_key[0])+':')
    309.     print('лучший кандит на ключ = '+str(max_key[1])+':')
    310.     print('Расшифрованный с помощью этого ключа текст:')
    311.     plaintext = apply(max_key[2], ciphertext)
    312.     print(plaintext.replace('z',' '))
    313.  
    314. # Если файл HillCipher.py выполняется как программа,
    315. # а не импортируется как модуль, вызвать функцию main()
    316. if __name__ == '__main__':
    317.     main()
    Но срабатывает не всегда. Правильное срабатывание, в основном, на длинных текстах.
    01.jpg
    А вот "Hello world" не распознает.
    02.jpg
    Плюс скоро начало учебного года, а я не успеваю, поэтому и спрашиваю...
     

    Вложения:

  16. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167
    Шифр Хилла с матрицей 2х2 вскрывается довольно быстро бутфорсом, для матрицы 2х2 количество ключей 294=707281. Но возникает проблема, как найти осмысленный текст? Если брать английский текст, то сообщений с индексом совпадений Фридмана от 0.0644 до 0.0645 получается довольно много. Если сообщение короткое, то коэффициент Фридмана 0,05 - 0,07. По биграммам, триграммам, квадрограммам тоже сложно найти осмысленный текст.
    При дешифровке используется невырожденная матрица. Невырожденная матрица не может иметь детерминант равный нулю. Для матрицы 2х2 [math]P=\begin{bmatrix} a & b \\ c & d \end{bmatrix}[/math] определитель равен [math]det(P)=a\cdot d-b\cdot c[/math], должно выполняться условие [math]a\cdot d\ne b\cdot c[/math]
    Может быть есть еще идеи?
     
  17. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.167
    Взлом шифра Хилла 2х2

    написано на базе программы, предоставленной who_know777

    1. Отбрасываем вырожденные матрицы
    2. Фильтрация невырожденных матриц ― пропускаем ключи с детерминантом, не взаимно простым с длиной алфавита.
    3. Проверка правдоподобия первой биграммы ― используем список наиболее часто употребимых биграмм английского языка.
    4. Вычисление индекса совпадений ― для фильтрации текстов, похожих на английский.
    5. Сортировка результатов ― по близости IC к значению 0.065.
    6. Используем словарь английских слов и выбираем сообщения, в которых более 40% английских слов
    Очень хорошо работает как с длинными, так и с короткими сообщениями от 8 символов. Огромное спасибо who_know777 :yes3:
    Код (Python):
    1. import numpy as np
    2. import math, detectEnglish
    3. LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    4. # Преобразование текста в числовой формат
    5. def char2num(text):
    6.     return [LETTERS.find(char) for char in text]
    7. # Преобразование числового представления в текст
    8. def num2char(numbers):
    9.     return ''.join(LETTERS[num] for num in numbers)
    10. # Вычисление обратного элемента modulo m
    11. def mod_inverse(a, m):
    12.     for x in range(1, m):
    13.         if (a * x) % m == 1:
    14.             return x
    15.     return None
    16. # Вычисление обратной матрицы 2x2 по модулю len(LETTERS)
    17. def matrix_inverse(matrix):
    18.     det = (matrix[0,0]*matrix[1,1] - matrix[0,1]*matrix[1,0]) % len(LETTERS)
    19.     # Проверка обратимости матрицы
    20.     if math.gcd(det, len(LETTERS)) != 1:
    21.         return None
    22.     det_inv = mod_inverse(det, len(LETTERS))
    23.     if det_inv is None:
    24.         return None
    25.     # Вычисление обратной матрицы
    26.     inv_matrix = np.array([[matrix[1, 1], -matrix[0, 1]], [-matrix[1, 0], matrix[0, 0]]], dtype=int) * det_inv
    27.     return inv_matrix % len(LETTERS)
    28. # Расшифровка текста с помощью ключевой матрицы
    29. def decrypt_hill(ciphertext, key_matrix):
    30.     n = len(ciphertext)
    31.     plaintext = []
    32.     # Получение обратной матрицы
    33.     inv_key = matrix_inverse(key_matrix)
    34.     if inv_key is None:
    35.         return None
    36.     # Расшифровка по биграммам
    37.     for i in range(0, n, 2):
    38.         if i + 1 < n:
    39.             block = np.array([ciphertext[i], ciphertext[i+1]])
    40.             decrypted_block = np.dot(inv_key, block) % len(LETTERS)
    41.             plaintext.extend(decrypted_block)
    42.     return plaintext
    43. # Вычисление индекса совпадений (IC)
    44. def index_of_coincidence(text):
    45.     n = len(text)
    46.     if n < 2:
    47.         return 0
    48.     freq = [0] * len(LETTERS)
    49.     for c in text:
    50.         freq[LETTERS.find(c)] += 1
    51.     ic = 0
    52.     for i in range(len(LETTERS)):
    53.         ic += freq[i]*(freq[i]-1)/(n*(n - 1))
    54.     return ic
    55. # Проверка, является ли биграмма правдоподобной
    56. def is_plausible_bigram(bigram,common_bigrams,rare_bigrams):
    57.     bigram_str = num2char(bigram)
    58.     return bigram_str in common_bigrams or bigram_str not in rare_bigrams
    59. # Основная функция взлома
    60. def break_hill_2x2(ciphertext, min_ic=0.053, max_ic=0.078):
    61.     ciphertext_num = char2num(ciphertext)
    62.     n = len(ciphertext_num)
    63.     if n < 4:
    64.         print("Слишком короткий шифртекст для анализа")
    65.         return
    66.     # Наиболее употребимые биграммы в английском языке
    67.     filebuffer = open('english_bigrams.txt',encoding="UTF-8")
    68.     common_bigrams = []
    69.     for line in filebuffer:
    70.         key,count = line.split(' ')
    71.         common_bigrams.append(key)
    72.     # Маловероятные биграммы
    73.     rare_bigrams = ['JQ', 'QJ', 'QX', 'XQ', 'ZQ', 'QZ', 'JX', 'XJ', 'JZ', 'ZJ', 'ZX', 'XZ']
    74.     first_bigram = ciphertext_num[:2]
    75.  
    76.     candidates = []
    77.     # Перебор всех возможных ключей 2x2
    78.     for a in range(len(LETTERS)):
    79.         for b in range(len(LETTERS)):
    80.             for c in range(len(LETTERS)):
    81.                 for d in range(len(LETTERS)):
    82.                     key_matrix = np.array([[a, b], [c, d]])  
    83.                     # Пропускаем вырожденные матрицы
    84.                     if math.gcd((a*d - b*c) % len(LETTERS), len(LETTERS)) != 1:
    85.                         continue  
    86.                     # Расшифровываем первую биграмму
    87.                     decrypted_bigram = decrypt_hill(first_bigram, key_matrix)    
    88.                     if decrypted_bigram is None or not is_plausible_bigram(decrypted_bigram,common_bigrams,rare_bigrams):
    89.                         continue  
    90.                     # Расшифровываем весь текст
    91.                     full_decryption = decrypt_hill(ciphertext_num, key_matrix)
    92.                     if full_decryption is None:
    93.                         continue  
    94.                     # Вычисляем IC
    95.                     decrypted_text = num2char(full_decryption)
    96.                     ic = index_of_coincidence(decrypted_text)
    97.                     decrypted_text = decrypted_text.replace('Z',' ')    
    98.                     # Проверяем, попадает ли IC в ожидаемый диапазон
    99.                     if min_ic <= ic <= max_ic:
    100.                         englishPercentage = detectEnglish.getEnglishCount(decrypted_text) * 100
    101.                         if englishPercentage > 40:
    102.                             candidates.append({
    103.                                 'key': key_matrix,
    104.                                 'text': decrypted_text,
    105.                                 'ic': ic,
    106.                                 'words': englishPercentage })
    107.     # Сортируем кандидатов по близости IC к 0.065
    108.     candidates.sort(key=lambda x: abs(x['ic'] - 0.065))
    109.     # Выводим результаты
    110.     print(f"Найдено кандидатов: {len(candidates)}")
    111.     for i, candidate in enumerate(candidates):  # Показываем результаты
    112.         print(f"#{i+1:0>3}: {candidate['words']:.2f} {candidate['text']}")
    113.     return candidates
    114.  
    115. def main():
    116.     ciphertext = "VOMKBGQOXWGBWONNHYTMYLGHZW"
    117.     print('Зашифрованный текст:\n'+ciphertext)
    118.     print("Взлом шифра Хилла 2x2...")
    119.     print("Придется подождать пару минут...")
    120.     candidates = break_hill_2x2(ciphertext)
    121.  
    122. if __name__ == "__main__":
    123.     main()
    02.jpg
     

    Вложения:

    Последнее редактирование: 1 сен 2025
    who_know777 нравится это.