Валерий Борисок - Delphi. Трюки и эффекты
Листинг 12.16.
Шифрование/дешифрование строки
function TfmCryptography.EncryptDecryptString(strMsg: String;
nKey: Integer): String;
var
i: Integer;
begin
//каждый символ строки заменяется соответствующим символом
//алфавита шифрования
for i := 1 to Length(strMsg) do
strMsg[i] := RusDstAlphabet[strMsg[i]];
Result := strMsg;
end;
Теперь у нас есть все, чтобы перейти к решению основной задачи. Процесс шифрования аналогичен процессу дешифрования текста сообщения. Для начала нужно попытаться получить ключ, который ввел пользователь, что мы и делаем. После проверяем значение ключа. Если он равен -1, то это значит, что ключ введен неверно и преобразование текста невозможно. Когда все отлично, перед преобразованием текста мы вызываем метод подготовки алфавита с полученным ключом. Стоит отметить, что, когда происходит процесс дешифрования, вычисляется обратный ключ. С его помощью можно получить алфавит, используя который аналогично процессу шифрования получаем открытый текст сообщения. Далее просто: для каждой строки текста сообщения вызывается функцияпреобразования. На этом каждый метод заканчивает свою работу. Исходный код, соответствующий приведенному выше описанию, показан в листинге 12.17.
...Листинг 12.17.
Шифрование/дешифрование текста сообщения
procedure TfmCryptography.btnEncryptMessageClick(Sender: TObject);
var
i: Integer;
nKey: Integer;
begin
//получаем ключ, с помощью которого будет
//шифроваться сообщение
nKey := GetKey;
//ключ задан верно?
if nKey = –1 then
Begin
MessageDlg('Ошибка: ключ задан неверно', mtError, [mbOk], 0);
Exit;
End;
//получаем алфавит, с помощью которого будет
//происходить шифрование
RecalcAlphabet(nKey);
//предотвращаем перерисовку компонента до тех пор, пока не
//зашифруем все строки сообщения
mmEncryptMessage.Lines.BeginUpdate;
//освобождаем список от любых старых значений
mmEncryptMessage.Clear;
//шифруем сообщение построчно
for i := 0 to mmDecryptMessage.Lines.Count – 1 do
mmEncryptMessage.Lines.Add(
EncryptDecryptString(mmDecryptMessage.Lines[i], nKey));
//заново разрешаем перерисовку компонента
mmEncryptMessage.Lines.EndUpdate;
end;
procedure TfmCryptography.btnDecpyptMessageClick(Sender: TObject);
var
i: Integer;
nKey: Integer;
begin
nKey := GetKey;
if nKey = –1 then
Begin
MessageDlg('Ошибка: ключ задан неверно', mtError, [mbOk], 0);
Exit;
End;
//получаем алфавит, с помощью которого будет происходить
//дешифрование
RecalcAlphabet(SizeOf(TRusSrcAlphabet) – nKey
mod SizeOf(TRusSrcAlphabet));
mmDecryptMessage.Lines.BeginUpdate;
mmDecryptMessage.Clear;
for i := 0 to mmEncryptMessage.Lines.Count – 1 do
mmDecryptMessage.Lines.Add(
EncryptDecryptString(mmEncryptMessage.Lines[i], nKey));
mmDecryptMessage.Lines.EndUpdate;
end;
Первое, что бросается в глаза при рассмотрении всего текста приложения, это практически полная идентичность интерфейса и основной части исходного кода. На самом деле это совсем не случайно. Достаточно часто программы пишутся универсально (даже более универсально, чем здесь!). Это основывается на очень простом предположении, что код должен быть многоразовым, то есть его можно повторно использовать в других приложениях. В результате у вас получается некий шаблон, который позволяет решать целый класс задач. Для этого нужно выполнить несколько маленьких изменений и потом просто можно забыть об этом. Результат выполнения итогового приложения можно увидеть на рис. 12.6.
Рис. 12.6. Результат работы приложения «Шифр Цезаря»
12.5. Шифр с автоключом
Шифр, основывающийся на шифре Виженера, в котором или само сообщение, или результирующая криптограмма используются в качестве ключа, называется шифром с автоключом. Шифрование начинается с помощью «первичного ключа» (который является настоящим ключом в нашем смысле) и продолжается с помощью сообщения или криптограммы, смещенной на длину первичного ключа. Рассмотрим пример, в котором первичным ключом является набор букв ЗЕБРА. В табл. 12.2 приведено шифрование, когда в качестве ключа используется сообщение.
Таблица 12.2.
Шифр с автоключом (ключ – сообщение)
Если же в качестве ключа использовать криптограмму, то получится шифрование, как в табл. 12.3.
Таблица 12.3.
Шифр с автоключом (ключ – криптограмма)
Теперь, когда понятно, как работает данный шифр, реализуем второй вариант как чуть более сложный, чем первый. В интерфейсе программы менять ничего не станем, поэтому он будет выглядеть как в предыдущем примере (см. рис. 12.5). Только поменяем назначение текстового поля. Теперь оно будет содержать ключ уже не в виде целого числа, а в виде произвольной строки, полностью состоящей из русских букв верхнего и нижнего регистров, за исключением буквы «ё» обож регистров.
Как обычно, сначала приведем код с объявлением необходимых типов, констант и переменных, а также объявление класса нашей формы. Все это содержится в листинге 12.18. ...Листинг 12.18.
Объявление типов и класса нашей формы
type
TRusLetters = set of Char;
TfmEncryptingAutoKey = class(TForm)
mmDecryptMessage: TMemo;
mmEncryptMessage: TMemo;
lbDecryptMessage: TLabel;
lbEncryptMessage: TLabel;
btnEncryptMessage: TButton;
btnDecpyptMessage: TButton;
edKey: TEdit;
lbKey: TLabel;
procedure btnEncryptMessageClick(Sender: TObject);
procedure btnDecpyptMessageClick(Sender: TObject);
private
{ Private declarations }
function GetKey: String;
function EncryptString(strEncryptMsg: String;
var strKey: String): String;
function DecryptString(strDecryptMsg: String;
var strKey: String): String;
procedure EncryptDecrypt(SrcLines, DstLines: TStrings;
bEncrypt: Boolean);
public
{ Public declarations }
end;
const
RusLetters: TRusLetters = ['А'..’я’];
var
fmEncryptingAutoKey: TfmEncryptingAutoKey;
Начнем рассмотрение, как и в предыдущем примере, с функции получения введенного пользователем ключа. Ее работа заключается в следующем. Сначала каждый символ ключа проверяется на принадлежность алфавиту русского языка. Если найден посторонний символ, то результатом работы функции будет пустая строка, что свидетельствует об ошибке ввода ключа пользователем. В случае успешного завершения функции она возвращает исходную строку ключа. Код этой функции приведен в листинге 12.19.
...Листинг 12.19.
Функция получения ключа
function TfmEncryptingAutoKey.GetKey: String;
var
i: Integer;
begin
Result := '
for i := 1 to Length(edKey.Text) do
if not (edKey.Text[i] in RusLetters) then
Exit;
Result := edKey.Text;
end;
Рассмотрим работу функций EncryptString и DecryptString. На входе они получают строку, которую требуется преобразовать, и первичный ключ. Внешне они очень похожи, но все же отличаются, и эти отличия существенны. Функция шифрования выполняет следующие действия. В цикле осуществляется проход по строке и проверяется, является ли очередной символ буквой русского алфавита. В случае положительного ответа этот символ преобразуется при помощи очередного символа ключа и добавляется в его конец. Преобразование осуществляется по правилу, которое мы указывали при рассмотрении шифра Виженера: li =xi + yi (modm), то есть символ открытого текста и символ ключа складываются с последующим сокращением этой суммы по модулю т, где т – общее количество букв в алфавите (листинг 12.20).
...Листинг 12.20.
Функция шифрования строки с помощью ключа и криптограммы
function TfmEncryptingAutoKey.EncryptString(strEncryptMsg: String;
var strKey: String): String;
var
i: Integer;
begin
for i := 1 to Length(strEncryptMsg) do
if strEncryptMsg[i] in RusLetters then
begin
strEncryptMsg[i] := Chr(((Ord(strEncryptMsg[i]) –
Ord('А')) + (Ord(strKey[1]) – Ord('А'))) mod 64 + Ord('А'));
Delete(strKey, 1, 1);
strKey := strKey + strEncryptMsg[i];
end;
Result := strEncryptMsg;
end;
Функция дешифрования строки с помощью ключа и криптограммы делает следующее. Как и в предыдущей функции, в цикле осуществляется проход по строке и проверяется, является ли очередной символ буквой русского алфавита. При положительном ответе данный символ сначала добавляется в конец ключа, а потом только осуществляется его преобразование. Обратное преобразование символа проходит по следующему правилу: li = xi – yi (mod m), то есть из символа преобразованного текста вычитается символ ключа с последующим сокращением этой разности по модулю т, где т – общее количество букв в алфавите. Если результат отрицателен, то происходит дополнение до положительного числа значением т. Как это реализовано, показано в листинге 12.21.
...Листинг 12.21.
Функция дешифрования строки с помощью ключа и криптограммы
function TfmEncryptingAutoKey.DecryptString(strDecryptMsg: String;
var strKey: String): String;
var
i: Integer;
begin
for i := 1 to Length(strDecryptMsg) do
if strDecryptMsg[i] in RusLetters then
begin
strKey := strKey + strDecryptMsg[i];
strDecryptMsg[i] := Chr((((Ord(strDecryptMsg[i]) –
Ord('А')) – (Ord(strKey[1]) – Ord('А'))) + 64) mod 64 +
Ord('А'));
Delete(strKey, 1, 1);
end;
Result := strDecryptMsg;
end;
Обработчики событий OnClick вызывают функцию EncryptDecrypt с необходимыми параметрами. У этой функции всего три параметра. Первый указывает на источник текста сообщения, требующего преобразования, второй указывает на приемник преобразованного текста сообщения. Последний параметр определяет тип преобразования текста сообщения. Если он равен True, то текст сообщения шифруется и помещается в приемник. В противном случае текст сообщения дешифруется и также помещается в приемник. Это происходит следующим образом. Сначала получается ключ, при помощи которого будет осуществляться преобразование текста сообщения. Если ключ некорректен, то выдаем соответствующее предупреждение и больше ничего не делаем. Если ключ корректен, то в зависимости от последнего параметра вызываем соответствующую функцию преобразования для каждой строки источника текста сообщения и добавляем результат в приемник (листинг 12.22).