Валерий Борисок - Delphi. Трюки и эффекты
RecalcAlphabet(1);
mmDecryptMessage.Lines.BeginUpdate;
mmDecryptMessage.Clear;
//дешифруем шифрованный текст построчно
for i := 0 to mmEncryptMessage.Lines.Count – 1 do
mmDecryptMessage.Lines.Add(EncryptDecryptString
(mmEncryptMessage.Lines[i]));
mmDecryptMessage.Lines.EndUpdate;
end
else
MessageDlg('Ошибка: символы подстановки заданы неверно',
mtError, [mbOk], 0);
end;
В итоге мы получили вполне рабочий вариант приложения, способного без особого труда шифровать и дешифровать сообщения. На рис. 12.2 представлен результат работы данного приложения.
Рис. 12.2. Результат работы приложения «Шифр простой подстановки»
12.3. Транспозиция
Следующий шифр, который мы будем рассматривать, называется транспозицией с фиксированным периодом d. В этом случае сообщение делится на группы символов длины d и к каждой группе применяется одна и та же перестановка. Эта перестановка является ключом и может быть задана некоторой перестановкой первых d целых чисел.
Таким образом, для d = 5 в качестве перестановки можно взять 23154. Это будет означать, что т1т2 тЗт4т5т6т7т8т9 тЮ… переходит в т2 тЗт1т5т4т7т8т6т10т9… Последовательное применение двух или более транспозиций будет называться составной транспозицией. Если периоды этих транспозиций d1…., ds, то, очевидно, в результате получится транспозиция периода d, где d – наименьшее общее кратное d1…., ds.
Теперь, зная определение данного шифра, можно перейти к примеру одной из возможных его реализаций. Для этого, как и в предыдущем случае, создадим новое приложение, а на форму поместим те же самые компоненты, за исключением редактора значений и кнопки для генерации перестановки. Вместо них используем следующие компоненты: текстовое поле класса TEdit и еще один компонент класса TLabel с соответствующими HMeHaMHedRearrangement и IbRearrangement. Когда вы закончите, то в результате должно получиться нечто подобное изображенному на рис. 12.3.
Рис. 12.3. Интерфейс программы «Транспозиция с фиксированным периодом»
Текстовое поле edRearrangement предназначено для задания перестановки, которая будет использоваться при шифровании. Перестановка будет задаваться числами, разделенными пробелом, а их количество задаст период транспозиции. По остальному интерфейсу наше приложение аналогично предыдущему.
Стоит отметить одну неприятную особенность данного шифра. Поскольку период фиксирован, то на текст накладывается определенное ограничение. Оно заключается в том, что длина текста должна быть кратна периоду. Существует несколько вариантов решения данной проблемы. Можно дополнять открытый текст какими-либо символами. И тогда зашифровать сообщение не составит труда. Если эти символы заранее определены, то это облегчит задачу противника по вскрытию шифра. Другой вариант – переписать сообщение, используя, например, синонимы, либо удалив часть сообщения, которую легко восстановить из контекста, таким образом, чтобы длина текста стала кратной периоду.
Теперь перейдем к рассмотрению исходного кода нашего приложения. Как и в прошлый раз, начнем с объявления класса необходимых нам типов, а также класса формы. Соответствующий программный код показан в листинге 12.9. Здесь мы ввели целочисленную константу, ограничивающую длину задаваемого периода. В данном случае она равна 100. Нам понадобится помнить саму перестановку, при помощи которой будет осуществляться транспозиция сообщения, поэтому вводится соответствующий тип....Листинг 12.9.
Объявление типов и класса нашей формы
const
MaxTerm = 100;
type
TRearrangement = array [0..MaxTerm] of Integer;
TfmTransposition = class(TForm)
mmDecryptMessage: TMemo;
mmEncryptMessage: TMemo;
lbDecryptMessage: TLabel;
lbEncryptMessage: TLabel;
lbRearrangement: TLabel;
edRearrangement: TEdit;
btnEncryptMessage: TButton;
btnDecpyptMessage: TButton;
procedure btnEncryptMessageClick(Sender: TObject);
procedure btnDecpyptMessageClick(Sender: TObject);
private
{ Private declarations }
Rear: TRearrangement;
function RecalcRearrangement(nKey: Integer): Boolean;
function GetLine(Lines: TStrings;
nRow, nInd: Integer): String;
procedure EncryptDecrypt(SrcLines, DstLines: TStrings;
nKey: Integer);
public
{ Public declarations }
end;
Теперь перейдем к рассмотрению исходного кода решаемых в данном случае подзадач. Первой функцией, с которой мы начнем, будет функция разбора введенной строки, выделяющая перестановку из нее и проверяющая, является ли она допустимой.
Функция RecalcRearrangement подготавливает перестановку требуемым образом для шифрования либо дешифрования в зависимости от параметра пКеу, который принимает два значения: 0 и 1. Значение 0 указывает на то, что будет производиться шифрование сообщения и дополнительных действий по подготовке перестановки не требуется, за исключением проверки ее корректности. Значение 1, напротив, указывает на то, что будет производиться дешифрование сообщения и требуется еще дополнительно преобразовать перестановку так, чтобы она была симметрична исходной, в этом случае процесс дешифрования ничем не будет отличаться от процесса шифрования.
Чтобы введенная перестановка считалась корректной, необходимо и достаточно выполнить три следующих требования:
• введены только числа через пробел;
• все числа не повторяются;
• числа находятся в диапазоне от 1 до их общего количества.
Проверка первого условия осуществляется следующим образом. Изначально считается, что в строке идут пробелы. Как только пробелы заканчиваются, предполагается, что началось число, и до тех пор, пока мы опять не встретим пробел, выделяем это число. Как только встретили пробел, пытаемся преобразовать выделенную часть из строкового представления в численное. После этого добавляем полученное число к итоговой перестановке. Когда фрагмент кода, в котором находится первый цикл с условием после него, отработает, в массиве Rear будет храниться введенная перестановка (в Rear [0] хранится количество чисел в полученной перестановке). Сразу за первой проверкой осуществляется совместно вторая и третья, то есть проверяется допустимость самих введенных чисел, а также их уникальность. После всех проверок при необходимости осуществляется преобразование исходной перестановки к симметричной.
Для получения симметричной перестановки стоит выполнить нехитрое действие по обмену местами индексов чисел и сами х чисел, то есть если имеется перестановка 3 1 2, то она преобразуется в 2 3 1, так как 1 стоит на втором месте, 2 – на 3,3 – на 1.
Исходный код данной функции приведен в листинге 12.10.
...Листинг 12.10.
Функция разбора строки и проверки допустимости перестановки
function TfmTransposition.RecalcRearrangement(nKey: Integer):
Boolean;
var
i: Integer;
s: String;
Space: Boolean;
Used: array [1..MaxTerm] of Boolean;
ExRear: TRearrangement;
begin
Result := False;
Rear[0] := 0;
Space := True;
//выделяем каждое слово, разделенное пробелом,
//и преобразуем его к числу
for i := 1 to Length(edRearrangement.Text) do
if (edRearrangement.Text[i] = ' ') and (not Space) then
begin
Inc(Rear[0]);
Rear[Rear[0]] := StrToInt(s);
Space := True;
end
else
if (edRearrangement.Text[i] <> ' ') then
begin
if Space then
begin
Space := False;
s := '
end;
s := s + edRearrangement.Text[i];
end;
if not Space then
begin
Inc(Rear[0]);
Rear[Rear[0]] := StrToInt(s);
end;
//проверяем допустимость полученных чисел
FillChar(Used, SizeOf(Used), False);
for i := 1 to Rear[0] do
if (0 < Rear[i]) and (Rear[i] <= Rear[0])
and not Used[Rear[i]] then
Used[Rear[i]] := True
else
Exit;
//преобразуем перестановку к шифровке, обратной
//для симметричности процесса дешифровки
if nKey = 1 then
begin
ExRear[0] := Rear[0];
for i := 1 to Rear[0] do
ExRear[Rear[i]] := i;
Rear := ExRear;
end;
Result := Rear[0] > 1;
end;
Еще для упрощения алгоритма шифрования необходимо уметь получать часть текста заданной длины, начиная с указанной позиции, в виде одной строки, пропуская все переводы строк. Это действие выполняет следующая описываемая функция. Алгоритм ее работы довольно прост. Изначально в результирующей строке нет ни единого символа. Далее осуществляется двойной вложенный цикл. Цикл верхнего уровня осуществляет изменение значения переменной, начиная с указанной строки до самой последней. Вложенный цикл, в свою очередь, изменяет значение переменной, первый раз начиная с указанной позиции в строке, а в остальных случаях всегда с 1, до длины текущей обрабатываемой строки. Каждый очередной символ добавляется к результирующей строке до тех пор, пока не будет достигнута заданная длина строки, равная периоду транспозиции. Соответствующий код приведен в листинге 12.11.
...Листинг 12.11. Функция получения части текста заданной длины, начиная с указанной позиции, в виде одной строки
function TfmTransposition.GetLine(Lines: TStrings;
nRow, nInd: Integer): String;
var
i, j, k: Integer;
s: String;
begin
Result := '
s := '
k := nInd;
for i := nRow to Lines.Count – 1 do
begin
for j := k to Length(Lines[i]) do
begin
s := s + Lines[i][j];
if Length(s) = Rear[0] then
begin
Result := s;
Exit;
end;
end;
k := 1;
end;
end;
Подготовительный этап мы рассмотрели, теперь остается рассмотреть основной код программы. Обработчики кнопок Onclick вызывают один и тот же метод и указывают необходимые параметры, чтобы зашифровать либо дешифровать текст сообщения. Процедура EncryptDecrypt в качестве параметров принимает источник текста сообщения, с которым нужно проделать необходимые действия, приемник преобразованного текста сообщения и тип преобразования. Последний параметр принимает одно из двух значений: 0 или 1. Значение О указывает на то, что будет производиться шифрование сообщения. Значение 1 указывает на то, что будет производиться дешифрование сообщения. Процедура EncryptDecrypt выполняет следующие действия. Сначала она пытается подготовить необходимую перестановку и, только если все прошло успешно, переходит к попытке преобразования текста сообщения, но предварительно делает еще одну проверку. Эта проверка заключается в следующем: нужно удостовериться в соответствии общей длины текста накладываемому на нее ограничению, то есть длина обязана быть кратна периоду транспозиции. Если все хорошо, то далее следует код преобразования текста сообщения с использованием подготовленной транспозиции. Для начала приведем исходный код, который находится в листинге 12.12.