Почти неделю ковыряюсь с шифрованием текста. Началось с того что мне показалось, что AES добавляет к файлу в линуксе 250кб, на самом деле оказалось 32 кб, но интерес к собственному алгоритму не пропал и я продолжил эксперименты.
1. Во первых самым известным является XOR, то есть мы в цикле накладываем на текст некий ключ, но проблема, если для текста использовать строку повторяющихся символов "аааа", то ключ можно определить по повторяющейся строке. В этом случае ключ нужен длинной такой же как длина шифруемого текста.
2. В качестве длинного ключа используем RandomSeed + Random. Но и тут есть проблема. Допустим мы шифруем файл рекордов, при этом мы получаем в позиции числа рекордов некоторые шифрованные символы. Если к кодам этих символов добавить условное число, то и в расшифрованном виде числа в рекорде будут изменены, и по факту надо просто подобрать двухбайтовое число, которое изменит изменит рекорд на нужную величину. Отсюда вывод - нужен алгоритм со сцеплением блоков, то есть предыдущий символ является ключом к следующему.
3. Итак теперь алгоритм кеширует предыдущую букву и делает XOR на следующую с дополнительным наложением XOR Random. Так как XOR можно накладывать многократно и при обратном XOR возвращаются предыдущие результаты, то двойной XOR с символом и рандомом работают. Теперь при попытке изменить букву ломается вся строка, то есть возвращается мусор. Но первоначально у первой буквы не было предыдущего символа, поэтому она была без "XOR+предыдущий символ", в этом был недостаток: если изменить букву в центре шифрованной строки, то ломалась только часть текста после изменённой буквы, поэтому алгоритм берёт последнюю букву и накладывает XOR на первую, теперь ломается вся строка как было задумано.
4. При тесте часто была ситуация когда при расшифровке была обрезка строки, выяснилось (предположением), что "a XOR a" возвращает 0, поэтому цикл "While *c\c" тупо выпрыгивал, когда получал *c\c = 0. Сначала я поборол эту ситуацию тем, что рандом сделал не 1-127, а 65250-65535 и сделал несколько экспериментов, что у меня не ломался текст и даже скачивал русские и арабские книги размером 1Мб, чтобы проверить что шифрование не ломается и возвращается исходный текст. Но всё равно мне не давало покоя, так как ситуация возможна, просто она не воспроизвелась, ведь ничего не стоит получить такую букву, которая с двойным XOR может дать 0, а с обратным действием восстановит, значит 0 нужно признать допустимым символом и не придумывать диапазоны где она не сработает, она всегда имеет вероятность сработать.
5. Итак теперь декодер воспринимает шифровку как бинарные данные, так как мы приняли 0 за валидный символ. Заменяем "While *c\c" на цикл For, в декодер передаём длину данных в байтах.
6. Тест шифрования книг показал что файл 3 Мб превращается в шифр 6 Мб, тут приходит понимание что ANSI при чтении внутри программы превращается в юникод и естественно ширина символов в 2 раза больше. Отсюда вывод, что либо изначально шифровать байтами, либо слегка модифицировать под определённую программу, если она работает только с определённой шириной символов. Если у меня юникодный текст, то код устраивает. Тем более для англ текста если символы будут вида 0065 и я каждый раз буду "65 XOR 00" то один XOR теряет смысл и возможно станет дырой безопасности.
7. В теории становится понятно что кодировщик тоже должен работать не со строкой, а с данными по указателю (как AES) и станет универсальным, так как теперь он сможет кодировать бинарные данные, но ввиду опять же индивидуальности к моему случаю - шифрование текста, то пока нет необходимости переделывать исходник для работы с бинарными данными.
EnableExplicit
#RngMax = $FFFF
#RngMin = $FEEE
#RndSeed = 871
#Shift2 = SizeOf(Character) ; =2
Procedure enc(*c.Character)
Protected k.c ; переменная для кеширования накладываемого символа с помощью XOR
Protected s.c ; переменная для кеширования текущего символа
Protected *c0 ; переменная - указатель начала строки
RandomSeed(#RndSeed) ; зерно рандома, чтобы иметь повторяющуюся последовательность случайных чисел
; Проверяем, что переданный указатель не ноль и не пустой
If *c = 0 Or *c\c = 0
ProcedureReturn 0
EndIf
*c0 = *c ; запоминаем начало строки
k = *c\c ; кешируем для цикла
*c\c ! Random(#RngMax, #RngMin) ; делаем XOR Random для первого символа
*c + #Shift2 ; сдвиг
While *c\c ; *c\c это код текущего юникодного символа в строке, например 1071 для "Я", и в цикле каждый раз сдвиг на 2 байта к следующему, это переобор символов строки
s = *c\c ; кешируем текущий для следующего шага
*c\c ! Random(#RngMax, #RngMin) ; XOR Random (CryptRandom()?)
*c\c ! k ; XOR предыдущий
*c + #Shift2 ; сдвиг на 2 байта
k = s ; исходный сохраняем для применения
Wend
*c - #Shift2 ; сдвиг на 2 байта назад, чтобы получить последний символ и наложить его на первый
k = *c\c ; запоминаем последний символ
*c = *c0 ; устанавливаем указатель в начало
*c\c ! k ; последний символ XOR на первый
EndProcedure
Procedure dec(*c.Character, length)
Protected k, i ; переменная для кеширования накладываемого символа с помощью XOR
Protected *c0 ; переменная - указатель начала строки
RandomSeed(#RndSeed) ; зерно рандома, чтобы иметь повторяющуюся последовательность случайных чисел
; Проверяем, что переданный указатель не ноль и не пустой
If *c = 0 Or *c\c = 0
ProcedureReturn 0
EndIf
*c0 = *c ; запоминаем начало строки
; двигаем к концу строки, чтобы получить последний символ для XOR на первый
*c + length - 2 ; двигаемся к последнему символу
k = *c\c ; получаем последний символ
*c = *c0 ; возвращаем указатель в начало
length / 2
For i = 1 To length
*c\c ! k ; XOR предыдущий
*c\c ! Random(#RngMax, #RngMin) ; XOR Random
k = *c\c ; запоминаем текущий для следующего
*c + #Shift2 ; сдвиг на 2 байта
Next
EndProcedure
Define text$, bytes
text$ = "Hi guys, this is a test for encrypting a text file with game records, where no serious security is required."
; text$ = "Привет ребята, это тест на шифрование текстового файла с рекордами игры, где не требуется серьёзной безопасности"
; text$ = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
Debug text$
enc(@text$)
Debug text$
bytes = StringByteLength(text$)
dec(@text$, bytes)
Debug text$Ещё для теста больших файлов будет удобнее иметь инклуд
; Это инклуд ForCipher.pb
Procedure.s ReadFileToVar(Path$, Format = #PB_UTF8)
Protected id_file, Text$
id_file = ReadFile(#PB_Any, Path$, Format)
If id_file
Text$ = ReadString(id_file, #PB_File_IgnoreEOL)
CloseFile(id_file)
EndIf
ProcedureReturn Text$
EndProcedure
Procedure ReadFileToMemory(Path$, Format = #PB_UTF8)
Protected id_file, length, *m, bytes
id_file = ReadFile(#PB_Any, Path$, Format)
If id_file
length = Lof(id_file)
*m = AllocateMemory(length)
If *m
bytes = ReadData(id_file, *m, length)
EndIf
CloseFile(id_file)
EndIf
ProcedureReturn *m
EndProcedure
Procedure WriteDataToFileBin(*data, bytes, Path$)
Protected id_file
id_file = CreateFile(#PB_Any, Path$)
If id_file
bytes = WriteData(id_file, *data, bytes)
CloseFile(id_file)
EndIf
ProcedureReturn bytes
EndProcedure
Procedure WriteTextToFile(text$, Path$)
Protected id_file, success
id_file = CreateFile(#PB_Any, Path$)
If id_file
success = WriteString(id_file, text$)
CloseFile(id_file)
EndIf
ProcedureReturn success
EndProcedure
; Это поместить в код, взамен отладочных тестов со строками, так как теперь мы открываем файл и сохраняем в файл. ; Здесь работа с большими файлами Define text$, FileSource$, FilePathEncoded$, FilePathDecoded$, bytes FileSource$ = "C:\Source\test_Source.fb2" FilePathDecoded$ = "C:\Source\test_Decoded.fb2" FilePathEncoded$ = "C:\Source\test_Encoded.txt" text$ = ReadFileToVar(FileSource$) ; читаем исходный текст Debug "Записано" + #TAB$ + Str(Len(text$)) ; выводим длину, чтобы сверить что обрезка не произошла enc(@text$) ; кодируем WriteDataToFileBin(@text$, bytes, FilePathEncoded$) ; сохраняем бинарные данные, так как шифровка не может быть текстом bytes = StringByteLength(text$) ; возвращаем размер в байтах dec(@text$, bytes) ; раскодируем Debug "Прочитано" + #TAB$ + Str(Len(text$)) ; выводим длину, чтобы сверить что обрезка не произошла WriteTextToFile(text$, FilePathDecoded$) ; сохраняем раскодированны результат в файл ; Сравниваем два файла в WinMerge или в проге сравнения хешев двух файлов. Или добавляем Fingerprint()
Отредактировано AZJIO (Вчера 11:04:16)