Właśnie dlatego odradzam przechowywania szyfrowanych danych w plikach tekstowych, bo prawdopodobieństwo otrzymania któregoś z ww. znaków jako wyniku szyfrowania jest dość spore;
Ale jakie prawdopodobieństwo znowu? Trzeba tylko prawidłowo przetworzyć dane.
Podam przepis ogólny.
Mamy przykładowy zestaw dozwolonych znaków wejściowych:
PlainSet := '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_=+`~[]{};"\:|,./<>?'
oraz wyjściowych
CipherSet := '0123456789ABCDEFabcdef'
Znaków wejściowych jest 92 czyli potrzeba nam 6,52 bita na każdy.
Znaków wyjściowych jest 22 czyli 4,46 bita na każdy.
Tak więc jedna porcja danych wejściowych daje się zapisać przy pomocy 6,52 bita. Jako, że będziemy XOR'ować te dane użyjemy pełnych 7-miu bitów. Każde 7 bitów można zapisać w 2 porcjach danych wyjściowych (7 <= 2 * 4,46).
W celu zmniejszenia redundancji można przyjąć inny stosunek i 2 porcje danych wejściowych kodować na 3 porcjach danych wyściowych. 2 porcje danych wejściowych to 2 * 6,52 = 13,04 bita. Jako, że będziemy XOR'ować użyjemy pełnych 14 bitów. Każde 14 bitów można zapisać w 3 porcjach danych wyjściowych (14 <= 3 * 4,46).
PlainSetLen := Length(PlainSet);
CipherSetLen := Length(CipherSet);
PlainPortionLen := 2;
CipherPortionLen := 3;
PortionBits := 14;
Przejdźmy do liczenia. Niech zdefiniowane będą funkcje:
{ zamienia znak na reprezentację bitową czyli indeks tego znaku w tablicy zestawu znaków }
function CharDecode(Data: Char, DataSet: String): Integer; begin Result := Pos(Data, DataSet); end;
{ odwrotność CharDecode }
function CharCode(Data: Integer; DataSet: String): Char; begin Result := DataSet[Data]; end;
{ zwraca 'count' kolejnych znaków wejściowych }
function ReadInputPortion(count: integer): String;
{ zapisuje znaki na wyjście }
function WriteOutputPortion(portion: String);
{ zwraca kolejne 'NumBits' bitów klucza szyfrującego (cyklicznie) }
function NextKeyPortion(NumBits: Integer): Integer;
{ resetuje 'NextKeyPortion' tak aby zaczęła zwracać bity od początku klucza }
procedure ResetKey();
I teraz transkodujemy informację:
procedure Transcode(
InSet: String; InSetLen, InPortionLen: Integer;
OutSet: String; OutSetLen, OutPortionLen: Integer;
PortionBits: Integer);
var
InPortion, OutPortion: String;
RawPortion: Integer;
i: Integer;
begin
ResetKey();
SetLength(OutPortion, OutPortionLen);
while True do
begin
{ pobieramy porcję informacji }
InPortion := ReadInputPortion(InPortionLen);
if Length(InPortion) = 0 then break;
{ zamieniamy na informację bitową }
RawPortion := 0;
for i := 1 to InPortionLen do
begin
RawPortion := RawPortion * InSetLen;
RawPortion := RawPortion + CharDecode(InPortion[i], InSet);
end;
{ Szyfrujemy. Tutaj użyłem XOR'owania ale algorytm może być dowolny.
Ważne jest aby porcja zaszyfrowana mieściła się w zakładanym 'PortionBits' }
RawPortion := RawPortion xor NextKeyPortion(PortionBits);
{ zamieniamy na format wyjściowy }
for i := 1 to OutPortionLen do
begin
OutPortion[i] := CharCode(RawPortion mod OutSetLen);
RawPortion := RawPortion div OutSetLen;
end;
{ zapisujemy wynik na wyjście }
WriteOutputPortion(OutPortion);
end;
end;
procedure PlainToCipher();
begin
Transcode(
PlainSet, PlainSetLen, PlainPortionLen,
CipherSet, CipherSetLen, CipherPortionLen,
PortionBits);
end;
procedure CipherToPlain();
begin
Transcode(
CipherSet, CipherSetLen, CipherPortionLen,
PlainSet, PlainSetLen, PlainPortionLen,
PortionBits);
end;
Przepis jest ogólny ale sporo może się uprościć np. dobierając tak zestawy znaków, aby dało się kodować 1 do 1.
Jeszcze jest kwestia z końcówką danych wejściowych, co zrobić jeśli ilość znaków wejściowych nie jest podzielna przez wielkość porcji wejściowej? Można sobie z tym radzić na różne sposoby. Np. dopełnić zerami (jeśli kodujemy tylko w jedną stronę) albo kodować redundantywnie po jednym znaku, albo dobrać zestawy znaków aby nie było "części ułamkowych" itp.