Найти в Дзене
Rudoysecurity

Типичные ошибки реализации XOR-шифрования (гаммирования) и одноразового блокнота в Delphi: фундаментальная путаница между текстовыми и бинар

В криптографических реализациях на Delphi, особенно при работе с XOR-шифрованием и одноразовым блокнотом, программисты часто допускают систематические ошибки, коренящиеся в непонимании фундаментального различия между текстовыми (String) и бинарными (TBytes) данными и их представлениями в бинарных строк (Binary string). Это исследование анализирует типичные ошибки, их причины, последствия и предоставляет практические рекомендации для корректной реализации . XOR-шифрование — это аддитивный шифр, использующий операцию исключающего ИЛИ (XOR) между открытым текстом и ключом. Алгоритм основан на свойствах операции XOR: A⊕0=A, A⊕A=0, A⊕B=B⊕A, (A⊕B)⊕C=A⊕(B⊕C). При использовании повторяющегося ключа этот метод уязвим для частотного анализа, но с полностью случайным ключом, равным по длине сообщению и использованным однократно, он превращается в теоретически невзламываемый одноразовый блокнот . Одноразовый блокнот — это криптографический метод, обеспечивающий абсолютную секретность при соблюдени
Оглавление

Введение

В криптографических реализациях на Delphi, особенно при работе с XOR-шифрованием и одноразовым блокнотом, программисты часто допускают систематические ошибки, коренящиеся в непонимании фундаментального различия между текстовыми (String) и бинарными (TBytes) данными и их представлениями в бинарных строк (Binary string). Это исследование анализирует типичные ошибки, их причины, последствия и предоставляет практические рекомендации для корректной реализации .

1. Криптографические основы: XOR-шифрование и одноразовый блокнот

1.1 XOR-шифрование

XOR-шифрование — это аддитивный шифр, использующий операцию исключающего ИЛИ (XOR) между открытым текстом и ключом. Алгоритм основан на свойствах операции XOR: A⊕0=A, A⊕A=0, A⊕B=B⊕A, (A⊕B)⊕C=A⊕(B⊕C). При использовании повторяющегося ключа этот метод уязвим для частотного анализа, но с полностью случайным ключом, равным по длине сообщению и использованным однократно, он превращается в теоретически невзламываемый одноразовый блокнот .

1.2 Одноразовый блокнот (OTP)

Одноразовый блокнот — это криптографический метод, обеспечивающий абсолютную секретность при соблюдении четырёх условий:

  1. Ключ должен быть не короче открытого текста
  2. Ключ должен быть действительно случайным (truly random)
  3. Ключ никогда не должен повторно использоваться (даже частично)
  4. Ключ должен оставаться полностью секретным для всех, кроме законных участников обмена

Нарушение любого из этих условий делает систему уязвимой. Математически доказано, что при соблюдении всех условий OTP обеспечивает совершенную секретность .

2. Типы данных в Delphi: фундаментальное различие

2.1 String (UnicodeString в современных версиях)

В Delphi 2009 и выше тип String представляет собой UnicodeString с кодировкой UTF-16. Каждый символ занимает 2 байта (16 бит), что обеспечивает поддержку всех языков мира, включая кириллицу. Однако это текстовый тип, предназначенный для хранения символов, а не произвольных бинарных данных. Путаницу дополнительно вносят руководящие документы, спецификации, стандарты по алгоритмам шифрования - в криптографии под понятием "строка" понимается Binary string (символьное представление массива байтов в виде 1 и 0), а в языках программирования  String - буквально "строка" - текстовая строка символов.

Внутреннее представление:

  • Хранит символы в кодировке UTF-16LE (Little Endian)
  • Автоматически управляет памятью и кодировками
  • Индексация начинается с 1
  • Функция Length() возвращает количество символов, а не байтов

2.2 TBytes (бинарные данные)

TBytes — это динамический массив байтов (array of Byte), предназначенный для хранения произвольных бинарных данных без преобразований кодировок. Это правильный выбор для криптографических операций, работы с файлами, сетевыми протоколами и другими бинарными форматами. Для визуализации бинарных данных используют "двоичные строки" (Binary string).

Ключевые характеристики:

  • Не выполняет автоматических преобразований кодировок
  • Сохраняет все байтовые значения (0-255) без изменений
  • Индексация начинается с 0
  • Length() возвращает количество байтов

2.3 Преобразования между типами

Для корректного преобразования необходимо явно указывать кодировку:

delphi

// String → TBytes
var
Bytes: TBytes;
Text: string;
begin
Bytes := TEncoding.UTF8.GetBytes(Text); // Для UTF-8
Bytes := TEncoding.Unicode.GetBytes(Text); // Для UTF-16
end;

// TBytes → String
var
Text: string;
Bytes: TBytes;
begin
Text := TEncoding.UTF8.GetString(Bytes);
Text := TEncoding.Unicode.GetString(Bytes);
end;

Прямое приведение типов string(MyBytes) или TBytes(MyString) без учёта кодировки приводит к искажению данных и является распространённой ошибкой .

3. Систематизация типичных ошибок

3.1 Категория 1: Ошибки выбора типа данных для хранения зашифрованных данных

Проблема: Использование String вместо TBytes для результатов шифрования.

Конкретные ошибки:

  • Хранение шифротекста в переменных типа String
  • Возврат String из функций шифрования вместо TBytes
  • Использование TStringList.Text для объединения бинарных данных (добавляет символы перевода строк)

Пример некорректного кода:

delphi

function EncryptXOR(Text, Key: String): String; // Ошибка: возвращает String
var
i: Integer;
begin
Result := '';
for i := 1 to Length(Text) do
Result := Result + Chr(Ord(Text[i]) xor Ord(Key[1 + ((i-1) mod Length(Key))]));
end;

Проблемы:

  1. Результат XOR может содержать байт 0, который интерпретируется как конец строки
  2. Автоматические преобразования кодировок искажают данные
  3. Невозможно корректно сохранить или передать бинарные данные

Решение: Всегда использовать TBytes для криптографических данных .

3.2 Категория 2: Ошибки преобразования между текстовыми и бинарными представлениями

Проблема: Неправильные преобразования между String и TBytes без учёта кодировки.

Конкретные ошибки:

  • Прямое приведение типов: MyString := string(MyBytes)
  • Использование BytesOf()/StringOf() без учёта кодировки UTF-16
  • Смешивание TEncoding.UTF8 и TEncoding.Unicode при работе с одним набором данных

Пример: Приведение TBytes к String без нулевого терминатора приводит к чтению случайных данных из памяти .

Решение: Всегда явно указывать кодировку при преобразованиях:

delphi

// Правильное преобразование
Bytes := TEncoding.UTF8.GetBytes(Text);
Text := TEncoding.UTF8.GetString(Bytes);

3.3 Категория 3: Ошибки обработки нулевых байтов

Проблема: При операции XOR вероятность получения байта 0 составляет 1/256. В строковых типах нулевой байт интерпретируется как конец строки.

Конкретные ошибки:

  • Отображение зашифрованных данных в TMemo или других текстовых контролах
  • Использование строковых функций (Length(), Copy(), Pos()) для обработки шифротекста
  • Логирование бинарных данных как строк без предварительного кодирования (Base64, Hex)

Пример: Если szOriginal[i] == szKey[i], результат XOR будет 0, создавая ложный терминатор строки .

Решение: Для хранения и отображения использовать кодировки Base64 или Hex:

delphi

// Кодирование в Base64 для отображения/передачи
function BytesToBase64(const Bytes: TBytes): string;
begin
Result := TNetEncoding.Base64.EncodeBytesToString(Bytes);
end;

3.4 Категория 4: Ошибки работы с кодировками и Unicode

Проблема: В UnicodeString каждый символ занимает 2+ байта, а криптографические алгоритмы работают с байтами.

Конкретные ошибки:

  • Шифрование String напрямую без преобразования в TBytes
  • Неправильный расчет размеров: j := Length(Str) даёт символы, а нужно байты
  • Использование AnsiString в Unicode-версиях Delphi для “совместимости”

Пример ошибки с Unicode:

delphi

// Некорректно для Unicode
for i := 1 to Length(Text) do // Length возвращает символы, не байты
ByteValue := Ord(Text[i]); // Ord от Char возвращает код символа, а не байт

Решение: Всегда работать с байтами внутри алгоритмов шифрования:

delphi

function XorEncrypt(const Data, Key: TBytes): TBytes;
var
i: Integer;
begin
SetLength(Result, Length(Data));
for i := 0 to High(Data) do
Result[i] := Data[i] xor Key[i mod Length(Key)];
end;

3.5 Категория 5: Ошибки обработки ключей

Проблема: Ключи — это бинарные данные, а не текст. Текстовые пароли должны преобразовываться в бинарные ключи.

Конкретные ошибки:

  • Хранение ключей в переменных типа String
  • Использование текстовых ключей разной длины
  • Отсутствие преобразования текстовых паролей в бинарные ключи фиксированной длины
  • Повторное использование ключей в одноразовом блокноте

Критическая ошибка: Повторное использование ключа в OTP полностью компрометирует безопасность, позволяя восстановить ключ при наличии нескольких зашифрованных сообщений .

Решение: Использовать Key Derivation Function (KDF) для преобразования паролей:

delphi

// Пример использования PBKDF2 для получения ключа из пароля
function DeriveKey(const Password: string; Salt: TBytes; KeyLength: Integer): TBytes;
begin
// Использовать PBKDF2 или аналогичную функцию
end;

3.6 Категория 6: Ошибки реализации побайтовых операций

Проблема: Неправильные индексные вычисления и приведение типов при побайтовых операциях.

Конкретные ошибки:

  • Индексация: в String начинается с 1, в TBytes — с 0
  • Операции над Char (2 байта) вместо Byte (1 байт)
  • Неправильное использование Ord() и Chr() для байтовых значений

Пример некорректного кода:

delphi

// Несколько ошибок в одном примере
function XorStrings(Str1, Str2: UTF8String): UTF8String;
var
i: Integer;
Ci: Char;
begin
for i := 0 to Length(Str1) - 1 do
begin
Ci := Char(Ord(Str1) xor Ord(Str2)); // Ошибка: Ord от всей строки
Result := Result + Ci;
end;
end;

Решение: Единообразно работать с индексами от 0:

delphi

// Корректная реализация
function XorBytes(const Data, Key: TBytes): TBytes;
var
i: Integer;
begin
SetLength(Result, Length(Data));
for i := 0 to High(Data) do
Result[i] := Data[i] xor Key[i mod Length(Key)];
end;

3.7 Категория 7: Ошибки взаимодействия с внешними системами

Проблема: Шифрованные данные передаются между системами с разными представлениями строк.

Конкретные ошибки:

  • Отправка бинарных данных как текст через HTTP/JSON без кодирования
  • Несовпадение кодировок по умолчанию в Delphi, PHP, JavaScript
  • Ожидание текстового представления там, где нужны бинарные данные

Пример проблемы Delphi-PHP: Разное поведение при XOR-операциях из-за различий в обработке строк и кодировок .

Решение: Использовать Base64 для межсистемного взаимодействия:

delphi

// Delphi → PHP совместимость
function EncryptForPHP(const Text, Key: string): string;
var
DataBytes, KeyBytes, EncryptedBytes: TBytes;
begin
DataBytes := TEncoding.UTF8.GetBytes(Text);
KeyBytes := TEncoding.UTF8.GetBytes(Key);
EncryptedBytes := XorBytes(DataBytes, KeyBytes);
Result := TNetEncoding.Base64.EncodeBytesToString(EncryptedBytes);
end;

4. Практические рекомендации

4.1 Общие принципы

  1. Всегда использовать TBytes для криптографических данных — ключи, шифротекст, открытый текст
  2. Явно указывать кодировки при преобразованиях — TEncoding.UTF8 для текста
  3. Работать только с байтами внутри алгоритмов — преобразовывать в TBytes на входе и обратно на выходе
  4. Использовать Base64 или Hex для хранения и передачи — кодировать бинарные данные для логов, БД, сетевой передачи
  5. Тестировать с нулевыми байтами и Unicode-символами — включать в тестовые данные байт 0 и символы за пределами ASCII

4.2 Корректная архитектура функции шифрования

delphi

// Правильная сигнатура функций
function XorEncrypt(const Data, Key: TBytes): TBytes; overload;
function XorEncrypt(const Text, Key: string): string; overload;

// Реализация
function XorEncrypt(const Data, Key: TBytes): TBytes;
var
i: Integer;
begin
if Length(Key) = 0 then
raise Exception.Create('Key cannot be empty');

SetLength(Result, Length(Data));
for i := 0 to High(Data) do
Result[i] := Data[i] xor Key[i mod Length(Key)];
end;

function XorEncrypt(const Text, Key: string): string;
var
DataBytes, KeyBytes, EncryptedBytes: TBytes;
begin
// Явное преобразование с указанием кодировки
DataBytes := TEncoding.UTF8.GetBytes(Text);
KeyBytes := TEncoding.UTF8.GetBytes(Key);

EncryptedBytes := XorEncrypt(DataBytes, KeyBytes);

// Кодирование в Base64 для безопасного возврата как строки
Result := TNetEncoding.Base64.EncodeBytesToString(EncryptedBytes);
end;

4.3 *Учебная демонстрационная реализация одноразового блокнота в Delphi

delphi

type
TOneTimePad = class
private
FKey: TBytes;
FUsed: Boolean;
public
constructor Create(KeyLength: Integer);
function Encrypt(const Data: TBytes): TBytes;
function Decrypt(const Data: TBytes): TBytes;
property Used: Boolean read FUsed;
end;

constructor TOneTimePad.Create(KeyLength: Integer);
begin
SetLength(FKey, KeyLength);
// Генерация действительно случайного ключа
RandomBytes(FKey[0], KeyLength);
FUsed := False;
end;

function TOneTimePad.Encrypt(const Data: TBytes): TBytes;
begin
if FUsed then
raise Exception.Create('Key already used');

if Length(Data) > Length(FKey) then
raise Exception.Create('Data longer than key');

Result := XorEncrypt(Data, FKey);
FUsed := True;
end;

5. Проверка актуальности на 2026 год

Анализ современных источников подтверждает, что основные принципы работы с бинарными данными в Delphi остаются неизменными. Рекомендации по использованию TBytes для криптографических операций актуальны для всех современных версий Delphi, включая Alexandria и более поздние. Проблемы с нулевыми байтами и неправильной интерпретацией бинарных данных как строковых продолжают быть распространёнными ошибками даже в 2026 году .

Ключевые изменения в современных версиях Delphi:

  • Улучшенная поддержка кодировок через класс TEncoding
  • Расширенные возможности работы с TBytes в RTL
  • Более строгая типизация, помогающая избегать некоторых ошибок приведения типов

Заключение

Типичные ошибки программистов на Delphi при реализации XOR-шифрования и одноразового блокнота коренятся в фундаментальной путанице между текстовыми (String) и бинарными (TBytes) данными. Криптографические алгоритмы работают с байтами, а не с символами, и любое некорректное преобразование между этими представлениями приводит к искажению данных и нарушению безопасности.

Ключевые выводы:

  1. Всегда используйте TBytes для хранения и обработки криптографических данных
  2. Явно указывайте кодировки при преобразовании между строками и байтами
  3. Помните о проблеме нулевых байтов и используйте Base64/Hex для хранения и передачи
  4. Для одноразового блокнота соблюдайте все четыре условия безопасности
  5. Тестируйте реализации с разнообразными данными, включая нулевые байты и Unicode-символы

Соблюдение этих принципов позволит избежать распространённых ошибок и создавать корректные, безопасные криптографические реализации на Delphi.

*Учебная демонстрационная реализация - не соответствует принципам, изложенными В.А. Котельниковым в "Основные положения автоматической зашифровки" и К. Шенноном в "Теория связи в секретных системах" (One-Time Pad Cipher)