PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » PureBasic - форум » PureBasic для Windows » Собственное шифрование


Собственное шифрование

Сообщений 1 страница 27 из 27

1

Почти неделю ковыряюсь с шифрованием текста. Началось с того что мне показалось, что 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 (16.09.2026 11:04:16)

0

2

AZJIO написал(а):

Если к кодам этих символов добавить условное число, то и в расшифрованном виде числа в рекорде будут изменены, и по факту надо просто подобрать двухбайтовое число, которое изменит изменит рекорд на нужную величину.

Нужно добавить контрольную сумму. Если не совпадет считать что данные повреждены. Тогда не получится заменить символы без пересчета контрольной суммы.

0

3

Вижу, что ты разбираешься с размером данных при кодировании. У меня была похожая ситуация с архивированием текстов - когда кодируешь в юникод, объем реально увеличивается в два раза, это факт. Если работаешь только с текстом, то имеет смысл остаться на текущем подходе, лишняя универсальность обычно усложняет код без практической пользы.

0

4

А смысл изобретать велосипед? Нормальная традиционная криптография типа AES-256 при правильном применении более чем надёжна. Основные проблемы обычно возникают не в самом AES, а в обвязке: получение ключа из пароля через медленную выработку ключа с солью, стойкий IV, корректный режим шифрования, ну и проверка целостности данных.
RandomSeed() и обычный Random() для серьезной криптографии, разумеется, не годятся. Когда делал криптографию для криптокошельков, для Windows использовал виндовый генератор случайных чисел через BCryptGenRandom: Не знаю, как в линуксе, но для виндов используется библиотека:

Код:
Import "bcrypt.lib"
  BCryptGenRandom(hAlgorithm, *pbBuffer, cbBuffer, dwFlags)
EndImport

Procedure.i SecureRandom(Max) ;криптографически стойкое случайное число 0..Max (замена Random), Max<=$FFFFFFFF
  Protected Value.l, Range.q, Limit.q, UnsignedValue.q  
  If Max<0
    ProcedureReturn -1
  EndIf
  
  Range=Max+1
  Limit=$100000000-($100000000%Range) ;Количество значений 32-битного unsigned диапазона, которое делится на Range без остатка 
  Repeat
    If BCryptGenRandom(0, @Value, SizeOf(Value), #BCRYPT_USE_SYSTEM_PREFERRED_RNG)<>0
      ProcedureReturn -1
    EndIf    
    UnsignedValue=Value & $FFFFFFFF ;Переводим signed Long в unsigned 0..4294967295
  Until UnsignedValue<Limit 
  ProcedureReturn UnsignedValue%Range
EndProcedure

Я когда-то тоже XOR ил тексты. Намучился знатно. Но- работало)) Но потом мне сказали, что я занимался .... и больше я велосипед не изобретаю.
P.S. сорри, если что упустил. Я весь пост не читал, только начало. Просто в своё время потратил часть своей жизни на эту самую криптографию))

Отредактировано Goga (26.09.2026 09:05:38)

0

5

Goga написал(а):

RandomSeed() и обычный Random() для серьезной криптографии, разумеется, не годятся.

Для CryptRandom отсутствует CryptRandomSeed, а без него любой рандом только шифрует без возможности расшифрования.

Goga написал(а):

А смысл изобретать велосипед?

Уменьшить размер шифровальщика (это было в начале). Интерес.

0

6

AZJIO написал(а):

Для CryptRandom отсутствует CryptRandomSeed, а без него любой рандом только шифрует без возможности расшифрования.

CryptRandomSeed для расшифровки и не нужен. Ну или у тебя какая-то спец цель, чтобы зная сиид получить туже псевдослучайную последовательность, которую ты использовал в первый раз.

0

7

Goga написал(а):

CryptRandomSeed для расшифровки и не нужен. Ну или у тебя какая-то спец цель, чтобы зная сиид получить туже псевдослучайную последовательность, которую ты использовал в первый раз.

Именно так. Это ключ/пароль, работающий на всю последовательность. Если использовать XOR, то достаточно  закодировать длинную последовательность "аааааа", чтобы отследить ключ, после того как данные начинают повторятся. А чтобы невозможно было отследить нужен шифр Вернама, у которого длина ключа равна длине кодируемого текста, тогда повторение не получится отследить, так как его не будет.

0

8

Пётр написал(а):

Нужно добавить контрольную сумму.

Мне это уже предлагали. Есть 2 варианта, контрольная сумма рядом файл или писать в исполняемый. Исполняемый может от админа требовать доступ. Если файл рядом то пользователь может попытаться сгенерировать контрольную сумму, то есть её саму надо делать нестандартным способом. И может ещё быть случай чел исправил файл закрыл, и он перестал работать, назад не может вернуть, забудет какие числа будет и поломает вообще все рекорды. Поэтому меня устраивает зашифровать вместо создания собственной версии хеша, что тоже будет код на столько же килобайт.

0

9

Я тоже сталкивался с таким. У меня была задача с шифрованием текстовых файлов, и сначала не понял почему размер растет. Потом выяснил что в памяти программа все в юникод переводит. Если работаешь только с текстом, то имеет смысл на байтах шифровать сразу, будет эффективнее по размеру и скорости.

0

10

AZJIO написал(а):

Есть 2 варианта, контрольная сумма рядом файл или писать в исполняемый.

Контрольная сумма сохраняется там же где зашифрованные данные. Она рассчитывается исходя из данных.

0

11

Тут главное разобраться с размером данных на входе и выходе. У вас юникод в памяти занимает по 2 байта на символ, поэтому при шифровании Xor каждого символа отдельно файл не растет - это нормально. Если растет в 2 раза, значит где-то данные конвертятся или дублируются в процессе.

0

12

У меня была похожая ситуация с кодированием текстовых файлов - тоже столкнулся с тем, что размер после шифрования вырос в полтора-два раза из-за кодировок. Если работаешь только с текстом, то твой подход имеет смысл, но если когда-нибудь понадобится расширить функционал, лучше сразу переделать под бинарные данные, чтобы потом не переписывать весь код.

0

13

romanyan78
Да там особо то и не надо переписывать, ширину заказал на один байт и всё, алгоритм тот же. Рандом уменьшил до 255, сдвиг на 1 байт.

0

14

А почему вообще зависеть от кодировки при шифровании? Просто работай с сырыми байтами, независимо от того текст там или бинарь - результат все равно будет одинаковый размер на входе и выходе. У меня так Xor маска генерируется побайтово, потом просто применяется к любому файлу.

0

15

romanyan78
Снижается защищённость. Каждый второй байт равен 00. Если не накладывать хор на предыдущий символ, то такой шифр сломать на раз два, вот прям совсем не напрягаясь. Просто выявит каждый второй рандом, потом поискать семя с которым такой рандом генерируется, потом расшифровываешь зная накладываемую последовательность.

0

16

Когда я тестировал что-то похожее, понял что проблема действительно в конвертации между кодировками. У меня текст на русском в Utf-16 занимал ровно в два раза больше после простого Xor, потому что каждый символ уже был двубайтовым. Если работаешь со строками внутри среды - проще один раз нормализовать кодировку и потом уже шифровать поток байтов, так и безопаснее будет.

0

17

romanyan78
Со временем у меня могут 100 сместится приоритеты. Возможно мне не будет критично вставить в прогу 32 кб, то есть AES. Возможно для ANSI я сделаю структуру ".a" вместо ".c". Зачем мне сейчас что-то решать, что потом возможно не будет использовано? Делается ведь всё под задачу.

0

18

Прошу прощения, немного ушел от темы. Просто слышал что китайцы передали квантовое состояние кубита по радио волне благодоря особенному шифрованию.
Проосто интересно, ведь скорость такто нехилая, врятли чтото сложное делали.

0

19

У тебя правильное наблюдение про размер. Когда я шифровал текстовые файлы, столкнулся с тем же - юникод действительно занимает вдвое больше места чем Ansi. Для текста это некритично, но если потом понадобится архивировать, лучше сжимать уже после шифрования, иначе теряешь часть выигрыша. По безопасности - да, байтовый уровень универсальнее, хотя для личного использования твой вариант со строками вполне подойдет.

0

20

Я бы вообще перешел на работу с байтами с самого начала, не страдал бы с юникодом. У тебя же все равно в итоге все к байтам сводится - Xor над одним байтом это просто, быстро и универсально для любых данных. Для текста разница в скорости не почувствуешь, зато головняк меньше будет с кодировками и размерами.

0

21

Интересный подход, хотя мне кажется ты усложняешь себе жизнь с этим расширением символов. Если работаешь с текстом, то проще сразу байтовое представление брать и криптить как есть, без промежуточных преобразований между кодировками. На практике выходит быстрее и меньше ошибок.

0

22

Я когда шифровал текст у себя, тоже столкнулся с этим - Ansi занимает меньше места, чем Utf-16. В итоге проще работать с байтами напрямую, без преобразований. А с Xor на половину символа действительно получится дыра, особенно если текст на английском и часто повторяется. Думаю, переделать под бинарные данные стоит сразу, если планируешь шифровать что-то кроме текста.

0

23

У меня была похожая проблема при шифровании текстов - размер файла рос из-за кодировки. В итоге перешел на работу с байтами напрямую, как в Aes. Хотя если чисто текст шифруешь и работает - переделывать не стоит, пока не появится реальная необходимость.

0

24

Я бы все-таки переделал под работу с бинарными данными с самого начала, даже если сейчас только текст шифруешь. Потом все равно понадобится - либо файлы, либо что-нибудь еще. А переделывать будет больнее, чем сразу правильно сделать через указатели на данные, как ты сам пишешь.

0

25

Да, Xor на половинке кодировки совсем небезопасно получается, практически открытый текст. Я когда юникод переводил в бинарный формат для своего шифра, сразу понял что работать надо именно с байтами напрямую, иначе половина информации теряется. У вас же получается что шифруется только вторая половина символа, первая остается как есть.

0

26

romanyan78, какой смысл писать примерно одно и тоже много раз?

0

27

Пётр написал(а):

romanyan78, какой смысл писать примерно одно и тоже много раз?

мне кажется это бот.

0


Вы здесь » PureBasic - форум » PureBasic для Windows » Собственное шифрование