Шифруем файл с помощью другого файла в delphi

Источник: programmersclub
Alar under

В прошлой статье я вам рассказывал, как можно зашифровать файл с помощью пароля. Разумеется, чем длиннее пароль, тем труднее расшифровать файл. Давайте попробуем сосчитать, сколько времени нам потребуется на расшифровку такого файла. Допустим, что если пароль имеет размер 10 символов и допустим, что если мы попробовали расшифровать файл с верным паролем, то мы сразу поймём что это файл оригинальный (хотя это условие не обязательно потому что, расшифровывая файл мы не знаем что должно быть в оригинале, но в данном случае мы знаем что в оригинале и нам надо узнать только пароль).

    Сосчитаем:
    Один символ пароля может принимать примерно 160 значений это примерно столько значений, сколько мы можем ввести с клавиатуры (26 +26 английский алфавит, 33+33 русский алфавит + ~22 дополнительный символы+10 цифр).
    Следовательно, количество попыток равно 160 в степени 10 это число 5766503906250000000000, и если за одну секунду делается 10 попыток, то нам потребуется 24610789412122 лет чтобы узнать пароль и это с тем учётом, что мы знаем длину пароля. Почти невозможно…. Это только, кажется что сложно. Но даже если пароль имеет 30 символов, а файл 1 МВ, то, имея некоторую сноровку можно заметить повторы через каждые 30 байт, а там и найти пароль нетрудно. >
    Единственный выход это увеличивать длину пароля. А что если пароль будет представлять собой файл длиной несколько сотен килобайт. Объясняю для тех, кто не понял. Символы для пароля мы брать из файла, и теперь уже каждый символ пароля будет принимать не только 160 значений, а все от 0 до 255, т.е. 256 значений, а длина пароля будет равна размеру файла в байтах.
    Итак, перейдём к практике. В процедурах шифровки расшифровки я буду использовать технику файлового мэпинга. Мы все знаем что когда программы используют слишком больше памяти чем есть на компьютере, то система сбрасывает лишнюю память на диск в файл подкачки. А когда происходит попытка обращения к сброшенной памяти, то система загружает с диска память обратно, а другую память, которая давно не использовалась, скидывает на диск. А при использовании техники мэпинга, мы как будто говорим системе, что данную область памяти не надо выгружать в файл подкачки, а надо выгружать в нужный нам файл. При создании такой области памяти мы файл полностью отображается на память, и при изменении этой области памяти изменяется файл.
    Для увеличения скорости работы процедуры шифровать (и расшифровывать) я буду в ассемблерных вставках. А работать с файлами используя ассемблер очень трудно, поэтому я и использую мэпинг потому что файлы отображаются на память. А работать с памятью на ассемблере легче.
    Чтобы создать область памяти надо сначала создать mapped объект с помощью функции CreateFileMapping. Вот описание функции из MS SDK

HANDLE CreateFileMapping(
HANDLE hFile,// хендл файла для мэпинга
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,//атрибуты безопасности
DWORD flProtect,//флаги доступа к объекту
DWORD dwMaximumSizeHigh,//максимальный размер (старший)
DWORD dwMaximumSizeLow,//максимальный размер (младший)
LPCTSTR lpName //имя объекта
);

     Для того чтобы отобразить файл на память используется функция MapViewOfFile

LPVOID MapViewOfFile(
HANDLE hFileMappingObject,//хендл созданного объекта
DWORD dwDesiredAccess,// уровень доступа
DWORD dwFileOffsetHigh,//смещение начала проецирования (старшее)
DWORD dwFileOffsetLow,// смещение начала проецирования (младшее)
DWORD dwNumberOfBytesToMap// размер проецирования
);
    Эта функция возвратит начальный адрес мэпинга, т.е. возвратит адрес первого байта файла в памяти. Короче смотрим всё на примере.

Function FlCriptFile(
SourceFile:string;
DestFile:string;
PSWFileName:string;
Flags:DWORD
):boolean;
var
  DestHFile,SourceHFile,PSWHFile,BackupHFile:THandle;
  SourceMH,PSWMH:THandle;
  SourceFileSize,PSWSize:DWORD;
  SourceMMFAddr,PSWMMFAddr:pointer;

begin
  Result:=False;
  
  ACF_AutoRename :=(Flags and CF_AutoRename) = CF_AutoRename;
  ACF_DeleteSource :=(Flags and CF_DeleteSource) = CF_DeleteSource;
  ACF_Dest_NOT_CREATE:=(Flags and CF_Dest_NOT_CREATE)= CF_Dest_NOT_CREATE;
  ACF_ShowProgress :=(Flags and CF_ShowProgress) = CF_ShowProgress;
   //пользуемся теми же флагами что в предыдущей статье
  if ACF_AutoRename then
  begin
    DestFile:=SourceFile+".cript";
    ACF_Dest_NOT_CREATE:=false;
  end;
   if ACF_Dest_NOT_CREATE then
   begin
     DestFile:="D:\3D9D8F57C3274EF3A6E7C5D5B27ADCF0.dat";
     ACF_DeleteSource:=false;
  end; //думаю что это понятно

SourceHFile:=CreateFile(pchar(SourceFile),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
if SourceHFile=INVALID_HANDLE_VALUE then Exit;
SourceFileSize:=GetFileSize(SourceHFile,nil);
CloseHandle(SourceHFile);
{открываем, получаем размер исходного файла, закрываем файл}

PSWHFile:=CreateFile(pchar(PSWFileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
if PSWHFile=INVALID_HANDLE_VALUE then Exit;
PSWSize:=GetFileSize(PSWHFile,nil);

DeleteFile(BackupFileName);
{удаляем промежуточный файл если он есть и копируем исходный файл в него, задайте имя исходного файла в разделе const}
CopyFile(pchar(SourceFile),pchar(BackupFileName),False);

BackupHFile:=CreateFile(pchar(BackupFileName),GENERIC_READ+GENERIC_WRITE,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
if BackupHFile=INVALID_HANDLE_VALUE then Exit;
{открываем промежуточный файл}
///-----

     Далее создаём объекты мэпинга и проецируем файлы на память. Обратите внимание на атрибуты доступа, давать слишком много доступа тоже не очень хорошо. Имена для объектов мы не задаём т.к. нам не надо что бы этим объектом не пользовались другие процессы. Это не опечатка, используя файловый мэпинг разные процессы могут общаться между собой. Открыв объект с помощью функции OpenFileMapping передав ей, имя объекта и атрибуты доступа, процессы разделяют между собой одну память, при этом, если один процесс изменяет эту память у себя она автоматически изменяется у другого процесса (но не увлекайтесь этой возможностью, потому что без синхронизации процессы быстро зависнут).

SourceMH:=CreateFileMapping(BackupHFile,0,PAGE_READWRITE,0,SourceFileSize,");
if SourceMH=0 then exit;

PSWMH:=CreateFileMapping(PSWHFile,0,PAGE_READONLY,0,PSWSize,");
if PSWMH=0 then exit;

SourceMMFAddr:=MapViewOfFile(SourceMH,FILE_MAP_ALL_ACCESS,0,0,SourceFileSize);
if DWORD(SourceMMFAddr) = 0 then exit;

PSWMMFAddr:=MapViewOfFile(PSWMH,FILE_MAP_READ,0,0,PSWSize);
if DWORD(PSWMMFAddr) = 0 then exit;

   asm
    pushad // сохраняем все регистры в стеке
    mov esi, SourceMMFAddr
    mov edi, PSWMMFAddr
    mov ecx, SourceFileSize
    mov ebx, PSWSize
    add ebx, edi
    xor edx, edx // edx=0
    
  @_loop:
    mov al, byte ptr [edi]
    xor byte ptr [esi], al
    
     inc edi//увеличиваем позицию в пароле
     cmp edi, ebx
{если мы вышли за границу пароля, то возвращаемся к началу пароля}
     jl @_next
     mov edi, PSWMMFAddr
     @_next:
    
     inc esi
     loop @_loop
     Popad // возвращаем регисры
   end;

    UnmapViewOfFile(PSWMMFAddr);//анмэпим
   UnmapViewOfFile(SourceMMFAddr); //анмэпим
{закрываем хендлы, но главное не нарушить порядок}
  if not CloseHandle(SourceMH) then exit;
  if not CloseHandle(PSWMH) then exit;
  CloseHandle(BackupHFile);
  CloseHandle(PSWHFile);

   Заметьте, для того чтобы закрыть хендлы надо сначала анмепить память, в которую мы спроецировали наши файлы. Для этого надо передать функции UnmapViewOfFile базовый адрес мэпинга. Во время работы с промэпированной памятью файл может и не измениться, потому что эта область памяти может ни разу не выгрузиться на диск. Функция UnmapViewOfFile полностью выгружает эту память в файл и высвобождает её. Далее идёт копирование промежуточного файла в файл, который был передан нашей функции через параметр DestFile и его последующее удаление. Потом файлы обрабатывают согласно переданным флагам.

  
   ///-----
  CopyFile(pchar(BackupFileName),pchar(DestFile),false);
  DeleteFile(BackupFileName);
  
  if ACF_DeleteSource then
     DeleteFile(pchar(SourceFile));
  if ACF_Dest_NOT_CREATE then
  begin
    if not DeleteFile(pchar(SourceFile))then exit;
    CopyFile(pchar(DestFile),pchar(SourceFile),false);
    if not DeleteFile(pchar(DestFile)) then exit;
  end;
  Result:=true;
End;

     У данной методики есть некоторые ограничения. Так как в Windows каждому процессу дается область памяти в диапазоне 10000h-7FFFFFFFh, это примерно 2047 МВ, минус ту память, которую занимают системные DLL и сама программа, в среднем 20 МВ. Размер файла-пароля плюс размер исходного файла не должны превышать ~2020 МВ.
    Функция дешифрования почти полностью идентичная за некоторым отличием обработки флагов. Вот описание функции:
Function FlDeCriptFile(
                              SourceFile:string;
                                DestFile:string;
                                PSWFileName:string; 
                                 Flags:DWORD 
                                               ):boolean;

    Полностью код этой функции смотрите в исходнике FileCript.pas там же есть функции шифровки файла с помощью пароля. Кидаем этот модуль в "расшаренную" для Delphi папку и пользуемся модулем на здоровье.

    На последок расскажу о преимуществах данной методики над методикой шифровки файла с помощью пароля. Любой пароль можно запомнить каким бы он и был (например, пароль с рожицей в центре: P@S:-/sVVorDdD), пароль можно записать на бумажке. А файл мы никогда не сможем записать на бумажке тем более если он имеет размер ~800 КВ. И потеряв файл-пароль мы теряем и зашифрованный файл потому расшифровать такой файл почти невозможно.

Вот и всё!


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=21975