Сообщество Engee

Шифр ADFGVX

Автор
avatar-maximsidorovmaximsidorov
Notebook

Шифр ADFGVX

В этом примере рассматривается реализация шифра ADFGVX — одного из самых сложных военных шифров Первой мировой войны, использовавшегося германской армией.

Введение

ADFGVX — это полиграфический шифр замены, использовавшийся Германией во время Первой мировой войны. Он сочетает в себе шифр замены (с использованием таблицы Полибия) и транспозиционный шифр. Алгоритм шифрования был рассчитан на высокий уровень безопасности — при этом использовался ограниченный набор букв для кодирования (A, D, F, G, V, X), что облегчало радиопередачу. Этот шифр стал известен тем, что долгое время считался неразгадываемым, и был успешно взломан только французским криптографом Жоржем Пенвеном.

# Устанавливаем необходимый пакет, если он еще не установлен
import Pkg; Pkg.add("Random")

# Подключаем пакет для генерации случайных чисел и перетасовки
using Random
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

Основная часть

Структура данных для шифра ADFGVX

Первым делом мы определим структуру данных для хранения всех необходимых компонентов шифра:

"""
    struct ADFGVX

Определяет структуру для хранения параметров шифра ADFGVX:

- `polybius`: таблица символов (шифруемый алфавит)
- `pdim`: размер таблицы Полибия (например, 6 для 6x6)
- `key`: ключ транспозиции
- `keylen`: длина ключа
- `alphabet`: алфавит, используемый для кодирования (обычно "ADFGVX")
- `encode`: словарь шифрования (символ -> пара букв)
- `decode`: словарь дешифрования (пара букв -> символ)
"""
struct ADFGVX
    polybius::Vector{Char}
    pdim::Int
    key::Vector{Char}
    keylen::Int
    alphabet::Vector{Char}
    encode::Dict{Char, Vector{Char}}
    decode::Dict{Vector{Char}, Char}
end
ADFGVX

Конструктор структуры ADFGVX

Дальше создаем конструктор, который формирует таблицу сопоставления и заполняет словари шифрования и дешифрования:

"""
    ADFGVX(s, k, alph = "ADFGVX")

Конструктор создает объект шифра ADFGVX по данным:
- `s`: строка символов, образующая таблицу Полибия
- `k`: ключ транспозиции
- `alph`: алфавит шифра, по умолчанию "ADFGVX"
"""
function ADFGVX(s, k, alph = "ADFGVX")
    # Преобразуем в вектор прописных букв
    poly = collect(uppercase(s))
    pdim = isqrt(length(poly))  # вычисляем размерность таблицы (для квадратной)
    
    al = collect(uppercase(alph)) # преобразуем алфавит
    
    # Создаем словарь шифрования: каждому символу сопоставляется пара букв
    enco::Dict = Dict([(poly[(i - 1) * pdim + j] => [al[i], al[j]])
        for i in 1:pdim, j in 1:pdim])
    
    # Создаем словарь для расшифровки (обратный)
    deco = Dict(last(p) => first(p) for p in enco)
    
    # Проверка корректности данных
    @assert pdim^2 == length(poly) && pdim == length(al)
    
    # Возвращаем инициализированный объект
    return ADFGVX(poly, pdim, collect(uppercase(k)), length(k), al, enco, deco)
end
ADFGVX

Функция шифрования

Функция шифрования преобразует введённый текст в последовательность символов из алфавита шифра, затем применяет транспозицию на основе ключа.

"""
    encrypt(s::String, k::ADFGVX)

Шифрует строку `s` с помощью объекта `k` типа `ADFGVX`.
"""
function encrypt(s::String, k::ADFGVX)
    # Символы, которые будут зашифрованы
    chars = reduce(vcat, [k.encode[c] for c in 
        filter(c -> c in k.polybius, collect(uppercase(s)))])

    # Распределяем символы по колонкам в соответствии с ключом
    colvecs = [lett => chars[i:k.keylen:length(chars)] for (i, lett) in enumerate(k.key)]
    
    # Упорядочиваем колонки по алфавиту ключа
    sort!(colvecs, lt = (x, y) -> first(x) < first(y))
    
    # Объединяем отсортированные столбцы в строку
    return String(mapreduce(p -> last(p), vcat, colvecs))
end
encrypt

Функция дешифрования

"""
    decrypt(s::String, k::ADFGVX)

Дешифрует строку `s`, зашифрованную с помощью шифра ADFGVX.
"""
function decrypt(s::String, k::ADFGVX)
    # Фильтруем символы, входящие в алфавит шифра
    chars = filter(c -> c in k.alphabet, collect(uppercase(s)))
    sortedkey = sort(collect(k.key)) # сортируем ключ
    
    # Порядок сортировки для столбцов
    order = [findfirst(c -> c == ch, k.key) for ch in sortedkey]
    originalorder = [findfirst(c -> c == ch, sortedkey) for ch in k.key]
    
    # Вычисляем длины столбцов (с учётом возможного неравномерного распределения)
    a, b = divrem(length(chars), k.keylen)
    strides = [a + (b >= i ? 1 : 0) for i in order]
    starts = accumulate(+, strides[begin:end-1], init=1)
    pushfirst!(starts, 1)
    ends = [starts[i] + strides[i] - 1 for i in 1:k.keylen]
    
    # Восстанавливаем столбцы в оригинальном порядке
    cols = [chars[starts[i]:ends[i]] for i in originalorder]
    
    # Восстанавливаем строки из столбцов
    pairs, nrows = Char[], (length(chars) - 1) ÷ k.keylen + 1
    for i in 1:nrows, j in 1:k.keylen
        (i - 1) * k.keylen + j > length(chars) && break
        push!(pairs, cols[j][i])
    end
    
    # Преобразуем пары символов обратно в символы исходного алфавита
    return String([k.decode[[pairs[i], pairs[i + 1]]] for i in 1:2:length(pairs)-1])
end
decrypt

Демонстрация работы

Генерируем случайную таблицу Полибия и случайный ключ

const POLYBIUS = String(shuffle(collect("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")))
"IJQBWNR9MFS17GD85L2UAC4TVK6OZEH3X0PY"

Загружаем английский словарь и выбираем случайный подходящий ключ длины 9

# Загружаем английский словарь и выбираем случайный подходящий ключ длины 9
const KEY = read("unixdict.txt", String) |>
    v -> split(v, r"\s+") |>
    v -> filter(w -> (n = length(w); n == 9 && n == length(unique(collect(w)))), v) |>
    shuffle |> first |> uppercase
"KINGSBURY"

Создаем объект шифра

const SECRETS = ADFGVX(POLYBIUS, KEY)
message = "ATTACKAT1200AM"
"ATTACKAT1200AM"
# Вывод данных на экран
println("Polybius: $POLYBIUS, Key: $KEY\n")
println("Message: $message\n")

# Шифрование сообщения
encoded = encrypt(message, SECRETS)
println("Encoded: $encoded\n")

# Дешифрование сообщения
decoded = decrypt(encoded, SECRETS)
println("Decoded: $decoded\n")
Polybius: IJQBWNR9MFS17GD85L2UAC4TVK6OZEH3X0PY, Key: KINGSBURY

Message: ATTACKAT1200AM

Encoded: XGGXGGFVAGGGFGDXFDFGFXGXGGXD

Decoded: ATTACKAT1200AM

Заключение

Мы рассмотрели и реализовали на языке Julia шифр ADFGVX — один из самых безопасных полевых шифров Первой мировой войны. Мы научились:

  • строить таблицу шифрования (таблицу Полибия);

  • формировать правила подстановки (шифрование по парам символов);

  • использовать транспозицию на основе ключа;

  • восстанавливать исходное сообщение на этапе дешифрования.

Данный проект демонстрирует важность изучения классических криптографических методов, которые лежат в основе современных систем защиты данных. Реализация на Julia позволяет легко экспериментировать с алгоритмами и понимать их работу «под капотом».

Пример разработан с использованием материалов Rosetta Code