Уроки Phptoshop, Linux, Windows 7
23 Май 2012, 01:28:50 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
Новости:
 
  Начало   Форум   Помощь Поиск Календарь Пользователи Downloads Войти Регистрация  
Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Криптография  (Прочитано 21559 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Nm0n
Гость
« : 03 Август 2009, 09:19:59 »

Методы криптографической защиты информации Windows
Криптографические возможности Windows

Сразу договоримся, что никакая система защиты информации не может быть абсолютно надежной.
Итак, ОС мы доверяем. Чтобы криптозащиту нельзя было «обойти» с другой стороны — к примеру, перехватить из незащищенной области памяти секретные пароли — криптографические функции должны быть частью операционной системы. В семействе Windows, начиная с Windows 95, обеспечивается реализация шифрования, генерации ключей, создания и проверки цифровых подписей и других криптографических задач. Эти функции необходимы для работы операционной системы, однако ими может воспользоваться и любая прикладная программа — для этого программисту достаточно обратиться к нужной подпрограмме так, как предписывает криптографический интерфейс прикладных программ (CryptoAPI).

Разумеется, по мере совершенствования Windows расширялся и состав ее криптографической подсистемы. Помимо базовых операций, в настоящее время в CryptoAPI 2.0 поддерживается работа с сертификатами, шифрованными сообщениями в формате PKCS #7 и пр.

Описание функций CryptoAPI, помимо специальных книг, можно найти в MSDN Library, или в CD-версии, в файле crypto.chm.

Взаимодействие с CryptoAPI

Функции CryptoAPI можно вызвать из программы, написанной на любимом многими (в том числе и авторами) языке С++. Тем не менее, Pascal де-факто признан стандартом в области обучения программированию. (Не будем спорить о том, хорошо это или плохо, чтобы не ввязываться в драку, пусть даже и виртуальную.) Кроме того, в ряде отечественных компаний Delphi является базовым средством разработки. Поэтому все примеры были реализованы в среде Delphi. Хотя в качестве инструмента можно было бы выбрать и MS Visual C++.

Код функций криптографической подсистемы содержится в нескольких динамически загружаемых библиотеках Windows (advapi32.dll, crypt32.dll). Для обращения к такой функции из прикладной программы на Object Pascal следует объявить ее как внешнюю. Заголовок функции в интерфейсной части модуля будет выглядеть, например, так:
function CryptAcquireContext(
  phPROV: PHCRYPTPROV;
  pszContainer: LPCTSTR;
  pszProvider: LPCTSTR;
  dwProvType: DWORD;
  dwFlags: DWORD): BOOL; stdcall;

а в исполняемой части вместо тела функции нужно вписать директиву extern с указанием библиотеки, в которой содержится функция, и, возможно, ее имени в этой библиотеке (если оно отличается от имени функции в создаваемом модуле), например:
function CryptAcquireContext; external ‘advapi32.dll’
name 'CryptAcquireContextA';

Таким образом, имея описание функций CryptoAPI, можно собрать заголовки функций в отдельном модуле, который будет обеспечивать взаимодействие прикладной программы с криптографической подсистемой. Разумеется, такая работа была проделана программистами Microsoft, и соответствующий заголовочный файл (wincrypt.h) был включен в поставку MS Visual C++. К счастью, появилась и Delphi-версия (wcrypt2.pas). Ее можно найти здесь. Подключив модуль к проекту, вы сможете использовать не только функции CryptoAPI, но и мнемонические константы режимов, идентификаторы алгоритмов и прочих параметров, необходимых на практике.

И последнее замечание перед тем, как опробовать CryptoAPI в деле. Ряд функций был реализован только в Windows 2000. Но и на старушку Windows 98 можно найти управу: при установке Internet Explorer 5 интересующие нас библиотеки обновляются, позволяя использовать новейшие криптографические возможности. Нужно лишь задать для Delphi-проекта параметр условной компиляции NT5, после чего вызовы функций, появившихся лишь в Windows 2000, будут нормально работать.

Знакомство с криптопровайдерами

Функции CryptoAPI обеспечивают прикладным программам доступ к криптографическим возможностям Windows. Однако они являются лишь «передаточным звеном» в сложной цепи обработки информации. Основную работу выполняют скрытые от глаз программиста функции, входящие в специализированные программные (или программно-аппаратные) модули — провайдеры (поставщики) криптографических услуг (CSP — Cryptographic Service Providers), или криптопровайдеры

Программная часть криптопровайдера представляет собой dll-файл, подписанный Microsoft; периодически Windows проверяет цифровую подпись, что исключает возможность подмены криптопровайдера.

Криптопровайдеры отличаются друг от друга:
составом функций (например, некоторые криптопровайдеры не выполняют шифрование данных, ограничиваясь созданием и проверкой цифровых подписей);
требованиями к оборудованию (специализированные криптопровайдеры могут требовать устройства для работы со смарт-картами для выполнения аутентификации пользователя);
алгоритмами, осуществляющими базовые действия (создание ключей, хеширование и пр.).

По составу функций и обеспечивающих их алгоритмов криптопровайдеры подразделяются на типы. Например, любой CSP типа PROV_RSA_FULL поддерживает как шифрование, так и цифровые подписи, использует для обмена ключами и создания подписей алгоритм RSA, для шифрования — алгоритмы RC2 и RC4, а для хеширования — MD5 и SHA.

В зависимости от версии операционной системы состав установленных криптопровайдеров может существенно изменяться. Однако на любом компьютере с Windows можно найти Microsoft Base Cryptographic Provider, относящийся к уже известному нам типу PROV_RSA_FULL. Именно с этим провайдером по умолчанию будут взаимодействовать все программы.

Использование криптографических возможностей Windows напоминает работу программы с графическим устройством. Криптопровайдер подобен графическому драйверу: он может обеспечивать взаимодействие программного обеспечения с оборудованием (устройство чтения смарт-карт, аппаратные датчики случайных чисел и пр.). Для вывода информации на графическое устройство приложение не должно непосредственно обращаться к драйверу — вместо этого нужно получить у системы контекст устройства, посредством которого и осуществляются все операции. Это позволяет прикладному программисту использовать графическое устройство, ничего не зная о его аппаратной реализации. Точно так же для использования криптографических функций приложение обращается к криптопровайдеру не напрямую, а через CryptoAPI. При этом вначале необходимо запросить у системы контекст криптопровайдера.

Первым делом, хотя бы из любопытства, выясним, какие же криптопровайдеры установлены в системе. Для этого нам понадобятся четыре функции CryptoAPI (выходные параметры выделены жирным шрифтом, а входные — курсивом):
CryptEnumProviders (i, резерв, флаги, тип, имя, длина_имени) — возвращает имя и тип i-го по порядку криптопровайдера в системе (нумерация начинается с нуля);
CryptAcquireContext (провайдер, контейнер, имя, тип, флаги) — выполняет подключение к криптопровайдеру с заданным типом и именем и возвращает его дескриптор (контекст). При подключении мы будем передавать функции флаг CRYPT_VERIFYCONTEXT, служащий для получения контекста без подключения к контейнеру ключей;
CryptGetProvParam (провайдер, параметр, данные, размер_данных, флаги) — возвращает значение указанного параметра провайдера, например, версии (второй параметр при вызове функции — PP_VERSION), типа реализации (программный, аппаратный, смешанный — PP_IMPTYPE), поддерживаемых алгоритмов (PP_ENUMALGS). Список поддерживаемых алгоритмов при помощи этой функции может быть получен следующим образом: при одном вызове функции возвращается информация об одном алгоритме; при первом вызове функции следует передать значение флага CRYPT_FIRST, а при последующих флаг должен быть равен 0;
CryptReleaseContext (провайдер, флаги) — освобождает дескриптор криптопровайдера.

Каждая из этих функций, как и большинство других функций CryptoAPI, возвращает логическое значение, равное true, в случае успешного завершения, и false — если возникли ошибки. Код ошибки может быть получен при помощи функции GetLastError. Возможные значения кодов ошибки приведены в упоминавшейся выше документации. Например, при вызове функции CryptGetProvParam для получения версии провайдера следует учесть возможность возникновения ошибок следующим образом:
if not CryptGetProvParam(hProv, PP_VERSION, (@vers), @DataLen, 0) then
begin
  case int64(GetLastError) of
    ERROR_INVALID_HANDLE: err := 'ERROR_INVALID_HANDLE';
    ERROR_INVALID_PARAMETER: err := 'ERROR_INVALID_PARAMETER';
    ERROR_MORE_DATA: err := 'ERROR_MORE_DATA';
    ERROR_NO_MORE_ITEMS: err := 'ERROR_NO_MORE_ITEMS';
    NTE_BAD_FLAGS: err := 'NTE_BAD_FLAGS';
    NTE_BAD_TYPE: err := 'NTE_BAD_TYPE';
    NTE_BAD_UID: err := 'NTE_BAD_UID';
  else
    err := 'Unknown error';
  end;
  MessageDlg('Error of CryptGetProvParam: ' + err, mtError, [mbOK], 0);
  exit
end;

Текст процедуры, выводящей в Memo-поле FileMemo формы информацию об установленных в системе криптопровайдерах, приведен ниже. Предполагается, что процедура вызывается при выборе соответствующего элемента в главном меню формы. Для краткости в тексте программы опущены фрагменты, выполняющие обработку ошибок.
type
  algInfo = record
    algID: ALG_ID;
    dwBits: DWORD;
    dwNameLen: DWORD;
    szName: array[0..100] of char;
  end;
  {вспомогательная функция, преобразующая тип провайдера в строку}
function ProvTypeToStr(provType: DWORD): string;
begin
  case provType of
    PROV_RSA_FULL: ProvTypeToStr := 'RSA full provider';
    PROV_RSA_SIG: ProvTypeToStr := 'RSA signature provider';
    PROV_DSS: ProvTypeToStr := 'DSS provider';
    PROV_DSS_DH: ProvTypeToStr := 'DSS and Diffie-Hellman provider';
    PROV_FORTEZZA: ProvTypeToStr := 'Fortezza provider';
    PROV_MS_EX: ProvTypeToStr := 'MS Ex provider';
    PROV_RSA_SCHANNEL: ProvTypeToStr := 'RSA secure channel provider';
    PROV_SSL: ProvTypeToStr := 'SSL provider';
  else
    ProvTypeToStr := 'Unknown provider';
  end;
end;
{вспомогательная функция, преобразующая тип реализации в строку}
function ImpTypeToStr(it: DWORD): string;
begin
  case it of
    CRYPT_IMPL_HARDWARE: ImpTypeToStr := 'аппаратный';
    CRYPT_IMPL_SOFTWARE: ImpTypeToStr := 'программный';
    CRYPT_IMPL_MIXED: ImpTypeToStr := 'смешанный';
    CRYPT_IMPL_UNKNOWN: ImpTypeToStr := 'неизвестен';
  else
    ImpTypeToStr := 'неверное значение';
  end;
end;
{процедура вывода информации о криптопровайдерах}
procedure TMainForm.InfoItemClick(Sender: TObject);
var
  i: DWORD;
  dwProvType, cbName, DataLen: DWORD;
  provName: array[0..200] of char;
  vers: array[0..3] of byte;
  impType: DWORD;
  ai: algInfo;
  err: string;
begin
  i := 0;
  FileMemo.Clear;
  while (CryptEnumProviders(i, nil, 0, {проверяем наличие еще одного}
    @dwProvType, nil, @cbName)) do
  begin
    if CryptEnumProviders(i, nil, 0, {получаем имя CSP}
      @dwProvType, @provName, @cbName) then
    begin
      FileMemo.Lines.Add('Криптопровайдер: ' + provName);
      FileMemo.Lines.Add('Тип: ' + IntToStr(dwProvType) + ' - ' +
        ProvTypeToStr(dwProvType));
      if not CryptAcquireContext(@hProv, nil, provName, dwProvType,
        CRYPT_VERIFYCONTEXT) then
      begin
        {обработка ошибок}
      end;
      DataLen := 4;
      if not CryptGetProvParam(hProv, PP_VERSION, (@vers), @DataLen, 0) then
      begin
        {обработка ошибок}
      end;
      FileMemo.Lines.Add('Версия: ' + chr(vers[1] + ) + '.' + chr(vers[0] + ));
      if not CryptGetProvParam(hProv, PP_IMPTYPE, @impType, @DataLen, 0) then
      begin
        {обработка ошибок}
      end;
      FileMemo.Lines.Add('Тип реализации: ' + ImpTypeToStr(impType));
      FileMemo.Lines.Add('Поддерживает алгоритмы:');
      DataLen := sizeof(ai);
      if not CryptGetProvParam(hProv, PP_ENUMALGS, @ai, @DataLen, CRYPT_FIRST)
        then
      begin
        {обработка ошибок}
      end;
      with ai do
        FileMemo.Lines.Add(szName + #9 + 'длина ключа - ' + IntToStr(dwBits) +
          ' бит' + #9 + 'ID: ' + IntToStr(AlgID));
      DataLen := sizeof(ai);
      while CryptGetProvParam(hProv, PP_ENUMALGS, @ai, @DataLen, 0) do
      begin
        with ai do
          FileMemo.Lines.Add(szName + #9 + 'длина ключа - '
            + IntToStr(dwBits) + ' бит' + #9 + 'ID: ' + IntToStr(AlgID));
        DataLen := sizeof(ai);
      end;
      FileMemo.Lines.Add('');
      CryptReleaseContext(hProv, 0);
    end;
    inc(i);
  end;
end;

На рис. 2 показан пример отчета, выдаваемого приведенным выше кодом, выполненным в среде Windows 98.

Шифрование с использованием паролей

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

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

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

Хешированием (от англ. hash — разрезать, крошить, перемешивать) называется преобразование строки произвольной длины в битовую последовательность фиксированной длины (хеш-значение, или просто хеш) с обеспечением следующих условий:
по хеш-значению невозможно восстановить исходное сообщение;
практически невозможно найти еще один текст, дающий такой же хеш, как и наперед заданное сообщение;
практически невозможно найти два различных текста, дающих одинаковые хеш-значения (такие ситуации называют коллизиями).

При соблюдении приведенных условий хеш-значение служит компактным цифровым отпечатком (дайджестом) сообщения. Существует множество алгоритмов хеширования. CryptoAPI поддерживает, например, алгоритмы MD5 (MD — Message Digest) и SHA (Secure Hash Algorithm).

Итак, чтобы создать ключ шифрования на основании пароля, нам нужно вначале получить хеш этого пароля. Для этого следует создать с помощью CryptoAPI хеш-объект, воспользовавшись функцией CryptCreateHash (провайдер, ID_алгоритма, ключ, флаги, хеш), которой нужно передать дескриптор криптопровайдера (полученный с помощью CryptAcquireContext) и идентификатор алгоритма хеширования (остальные параметры могут быть нулями). В результате мы получим дескриптор хеш-объекта. Этот объект можно представить себе как черный ящик, который принимает любые данные и «перемалывает» их, сохраняя внутри себя лишь хеш-значение. Подать данные на вход хеш-объекта позволяет функция CryptHashData (дескриптор, данные, размер_данных, флаги).

Непосредственно создание ключа выполняет функция CryptDeriveKey (провайдер, ID_алгоритма, хеш-объект, флаги, ключ), которая принимает хеш-объект в качестве исходных данных и строит подходящий ключ для алгоритма шифрования, заданного своим ID. Результатом будет дескриптор ключа, который можно использовать для шифрования (рис. 3).

Следует обратить внимание, что при работе с CryptoAPI мы все время имеем дело не с самими объектами или их адресами, а с дескрипторами — целыми числами, характеризующими положение объекта во внутренних таблицах криптопровайдера. Сами таблицы располагаются в защищенной области памяти, так что программы-«шпионы» не могут получить к ним доступ.

Алгоритмы шифрования, поддерживаемые CryptoAPI, можно разделить на блочные и поточные: первые обрабатывают данные относительно большими по размеру блоками (например, 64, 128 битов или более), а вторые — побитно (теоретически, на практике же — побайтно). Если размер данных, подлежащих шифрованию, не кратен размеру блока, то последний, неполный блок данных, будет дополнен необходимым количеством случайных битов, в результате чего размер зашифрованной информации может несколько увеличиться. Разумеется, при использовании поточных шифров размер данных при шифровании остается неизменным.

Шифрование выполняется функцией CryptEncrypt (ключ, хеш, финал, флаги, данные, рамер_данных, размер_буфера):
через параметр ключ передается дескриптор ключа шифрования;
параметр хеш используется, если одновременно с шифрованием нужно вычислить хеш-значение шифруемого текста;
параметр финал равен true, если шифруемый блок текста — последний или единственный (шифрование можно осуществлять частями, вызывая функцию CryptEncrypt несколько раз);
значение флага должно быть нулевым;
параметр данные представляет собой адрес буфера, в котором при вызове функции находится исходный текст, а по завершению работы функции — зашифрованный;
следующий параметр, соответственно, описывает размер входных/выходных данных,
последний параметр задает размер буфера — если в результате шифрования зашифрованный текст не уместится в буфере, возникнет ошибка.

Для расшифровки данных используется функция CryptDecrypt (ключ, хеш, финал, флаги, данные, рамер_данных), отличающаяся от шифрующей функции только тем, что размер буфера указывать не следует: поскольку размер данных при расшифровке может только уменьшиться, отведенного под них буфера наверняка будет достаточно.

Приведем лишь фрагменты программы, реализующей шифрование файла с использованием заданного пароля, опустив громоздкие проверки успешности выполнения криптографических операций (что в реальной программе делать крайне нежелательно).
{«описание» используемых переменных}
hProv: HCRYPTPROV;
hash: HCRYPTHASH;
password: string;
key: HCRYPTKEY;
plaintext, ciphertext: string;
inFile, : file;
data: PByte;
l: DWORD;
{получаем контекст криптопровайдера}
CryptAcquireContext(@hProv, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
{создаем хеш-объект}
CryptCreateHash(hProv, CALG_SHA, 0, 0, @hash);
{хешируем пароль}
CryptHashData(hash, @password[1], length(password), 0);
{создаем ключ на основании пароля для потокового шифра RC4}
CryptDeriveKey(hProv, CALG_RC4, hash, 0, @key);
{уничтожаем хеш-объект}
CryptDestroyHash(hash);
{открываем файлы}
AssignFile(inFile, plaintext);
AssignFile(, ciphertext);
reset(inFile, 1);
rewrite(, 1);
{выделяем место для буфера}
GetMem(data, 512);
{шифруем данные}
while not eof(inFile) do
begin
  BlockRead(inFile, data^, 512, l);
  CryptEncrypt(key, 0, eof(inFile), 0, data, @l, l);
  BlockWrite(, data^, l);
end;
{освобождаем место и закрываем файлы}
FreeMem(data, 512);
CloseFile(inFile);
CloseFile();
{освобождаем контекст криптопровайдера}
CryptReleaseContext(hProv, 0);

Конечно, шифрование вами всех файлов одним и тем же паролем облегчает «противнику» задачу их расшифровки, запоминание огромного числа паролей сильно усложняет жизнь, а их записывание в незашифрованном виде создает опасность раскрытия всей системы. CryptoAPI может предложить на этот случай ряд решений.
Записан
Nm0n
Гость
« Ответ #1 : 03 Август 2009, 09:21:21 »

Повышение криптоустойчивости шифрования текста любым алгоритмом
Повышение криптоустойчивости шифрования текста любым алгоритмом
Данная функция (AddDisturbToText) представляет собой
подготовительную операцию перед шифрацией текста любым
алгоритмом.

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

const
  NPCS: set of char = [#0..#8, #11, #12, #14..#31, '' ];
  // Добавление в текст непечатных символов
function AddDisturbToText(Source: string): string;
var
  n, c: integer;
begin
  Randomize;
  Result := Source;
  n := (Length(Source) * 2) + Random(Length(Source));
  while Length(Result) < n do
  begin
    c := Random(128);
    if Chr(c) in NPCS then
      (Chr(c), Result, Random(Length(Result)) + 1)
  end;
end;
// Убрать из текста непечатные символы
function RemoveDisturbFromText(Source: string): string;
var
  i: integer;
begin
  Result := Source;
  i := 1;
  while i < Length(Result) do
    if Result in NPCS then
      (Result, i, 1)
    else
      Inc(i);
end;
Записан
Nm0n
Гость
« Ответ #2 : 03 Август 2009, 09:23:10 »

Модуль для подсчета хеш-суммм с помощью MD5
unit psnMD5;
 
interface
 
uses Windows, SysUtils, Classes;
 
type
   PMD5Digest = ^TMD5Digest;
   TMD5Digest = record
      case Integer of
         0: (A, B, C, D: LongInt);
         1: (v: array[0..15] of Byte);
       end;
function MD5String(const S: string): TMD5Digest;
function MD5File(const FileName: string): TMD5Digest;
function MD5Stream(const Stream: TStream): TMD5Digest;
function MD5Buffer(const Buffer;
  Size: Integer): TMD5Digest;
function MD5DigestToStr(const Digest:
  TMD5Digest): string;
function MD5DigestCompare(const Digest1,
  Digest2: TMD5Digest): Boolean;
 
  implementation
 
type
   UINT4 = LongWord;
   
     PArray4UINT4 = ^TArray4UINT4;
   TArray4UINT4 = array[0..3] of UINT4;
   PArray2UINT4 = ^TArray2UINT4;
   TArray2UINT4 = array[0..1] of UINT4;
   PArray16Byte = ^TArray16Byte;
   TArray16Byte = array[0..15] of Byte;
   PArray64Byte = ^TArray64Byte;
   TArray64Byte = array[0..63] of Byte;
   
     PByteArray = ^TByteArray;
   TByteArray = array[0..0] of Byte;
   
     PUINT4Array = ^TUINT4Array;
   TUINT4Array = array[0..0] of UINT4;
   
     PMD5Context = ^TMD5Context;
   TMD5Context = record
       state: TArray4UINT4;
       count: TArray2UINT4;
       buffer: TArray64Byte;
     end;
     
      const
        S11 = 7;
      S12 = 12;
      S13 = 17;
      S14 = 22;
      S21 = 5;
      S22 = 9;
      S23 = 14;
      S24 = 20;
      S31 = 4;
      S32 = 11;
      S33 = 16;
      S34 = 23;
      S41 = 6;
      S42 = 10;
      S43 = 15;
      S44 = 21;
     
      var
       Padding: TArray64Byte =
     ($80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
     
       
function _F(x, y, z:
  UINT4): UINT4;
begin
   Result := (((x) and (y)) or ((not x) and (z)));
end;
 
function _G(x, y, z:
  UINT4): UINT4;
begin
   Result := (((x) and (z)) or ((y)
    and (not z)));
end;
 
function _H(x, y, z:
  UINT4): UINT4;
begin
   Result := ((x)xor (y)
    xor (z));
end;
 
function _I(x, y, z:
  UINT4): UINT4;
begin
   Result := ((y)xor ((x)
    or (not z)));
end;
 
function ROTATE_LEFT(x, n: UINT4): UINT4;
begin
   Result := (((x)shl (n))
    or ((x)shr (32 - (n))));
end;
 
procedure FF(var a: UINT4; b,
  c, d, x, s, ac: UINT4);
begin
    a := a + _F(b, c, d)
    + x + ac;
    a := ROTATE_LEFT(a, s);
    a := a + b;
end;
 
procedure GG(var a: UINT4; b,
  c, d, x, s, ac: UINT4);
begin
   a := a + _G(b, c, d)
    + x + ac;
   a := ROTATE_LEFT(a, s);
   a := a + b;
end;
 
procedure HH(var a: UINT4; b,
  c, d, x, s, ac: UINT4);
begin
   a := a + _H(b, c, d)
    + x + ac;
   a := ROTATE_LEFT(a, s);
   a := a + b;
end;
 
procedure II(var a: UINT4; b,
  c, d, x, s, ac: UINT4);
begin
   a := a + _I(b, c, d)
    + x + ac;
   a := ROTATE_LEFT(a, s);
   a := a + b;
end;
 
procedure MD5Encode(Output: PByteArray; Input: PUINT4Array; Len: LongWord);
var
   i, j: LongWord;
begin
   j := 0;
   i := 0;
   while j  '<';
  Len do  begin
      output[j] := Byte(input and $FF);
    output[j + 1] := Byte((inputshr 8)
    and $FF);
    output[j + 2] := Byte((inputshr 16)
    and $FF);
    output[j + 3] := Byte((inputshr 24)
    and $FF);
    Inc(j, 4);
    Inc(i);
   end;
end;
 
procedure MD5Decode(Output: PUINT4Array; Input: PByteArray; Len: LongWord);
var
   i, j: LongWord;
begin
   j := 0;
   i := 0;
   while j  '<';
  Len do
  begin
      Output := UINT4(input[j])
      or (UINT4(input[j + 1])shl 8)
      or
         (UINT4(input[j + 2])shl 16)
      or (UINT4(input[j + 3])shl 24);
      Inc(j, 4);
      Inc(i);
     end;
  end;
   
procedure MD5_memcpy(Output:
  PByteArray; Input: PByteArray; Len: LongWord);
begin
   Move(Input^, Output^, Len);
end;
 
procedure MD5_memset(Output:
  PByteArray; Value: Integer; Len: LongWord);
begin
   FillChar(Output^, Len, Byte(Value));
end;
 
procedure MD5Transform(State: PArray4UINT4; Buffer: PArray64Byte);
var
   a, b, c, d: UINT4;
 x: array[0..15] of UINT4;
begin
   a := State[0];
  b := State[1];
  c := State[2];
  d := State[3];
   MD5Decode(PUINT4Array(@x), PByteArray(Buffer), 64);
   
     FF(a, b, c, d,
    x[0], S11, $D76AA478);
   FF(d, a, b,
    c, x[1], S12, $E8C7B756);
   FF(c, d, a, b,
    x[2], S13, $242070DB);
   FF(b, c, d,
    a, x[3], S14, $C1BDCEEE);
   FF(a, b, c, d,
    x[4], S11, $F57C0FAF);
   FF(d, a, b,
    c, x[5], S12, $4787C62A);
   FF(c, d, a, b,
    x[6], S13, $A8304613);
   FF(b, c, d,
    a, x[7], S14, $FD469501);
   FF(a, b, c, d,
    x[8], S11, $698098D8);
   FF(d, a, b,
    c, x[9], S12, $8B44F7AF);
   FF(c, d, a, b,
    x[10], S13, $FFFF5BB1);
   FF(b, c, d,
    a, x[11], S14, $895CD7BE);
   FF(a, b, c, d,
    x[12], S11, $6B901122);
   FF(d, a, b,
    c, x[13], S12, $FD987193);
   FF(c, d, a, b,
    x[14], S13, $A679438E);
   FF(b, c, d,
    a, x[15], S14, $49B40821);
   
     GG(a, b, c, d,
    x[1], S21, $F61E2562);
   GG(d, a, b,
    c, x[6], S22, $C040B340);
   GG(c, d, a, b,
    x[11], S23, $265E5A51);
   GG(b, c, d,
    a, x[0], S24, $E9B6C7AA);
   GG(a, b, c, d,
    x[5], S21, $D62F105D);
   GG(d, a, b,
    c, x[10], S22,  $2441453);
   GG(c, d, a, b,
    x[15], S23, $D8A1E681);
   GG(b, c, d,
    a, x[4], S24, $E7D3FBC8);
   GG(a, b, c, d,
    x[9], S21, $21E1CDE6);
   GG(d, a, b,
    c, x[14], S22, $C33707D6);
   GG(c, d, a, b,
    x[3], S23, $F4D50D87);
   
     GG(b, c, d,
    a, x[8], S24, $455A14ED);
   GG(a, b, c, d,
    x[13], S21, $A9E3E905);
   GG(d, a, b,
    c, x[2], S22, $FCEFA3F8);
   GG(c, d, a, b,
    x[7], S23, $676F02D9);
   GG(b, c, d,
    a, x[12], S24, $8D2A4C8A);
   
     HH(a, b, c, d,
    x[5], S31, $FFFA3942);
   HH(d, a, b,
    c, x[8], S32, $8771F681);
   HH(c, d, a, b,
    x[11], S33, $6D9D6122);
   HH(b, c, d,
    a, x[14], S34, $FDE5380C);
   HH(a, b, c, d,
    x[1], S31, $A4BEEA44);
   HH(d, a, b,
    c, x[4], S32, $4BDECFA9);
   HH(c, d, a, b,
    x[7], S33, $F6BB4B60);
   HH(b, c, d,
    a, x[10], S34, $BEBFBC70);
   HH(a, b, c, d,
    x[13], S31, $289B7EC6);
   HH(d, a, b,
    c, x[0], S32, $EAA127FA);
   HH(c, d, a, b,
    x[3], S33, $D4EF3085);
   HH(b, c, d,
    a, x[6], S34,  $4881d05);
   HH(a, b, c, d,
    x[9], S31, $D9D4D039);
   HH(d, a, b,
    c, x[12], S32, $E6DB99E5);
   HH(c, d, a, b,
    x[15], S33, $1FA27CF8);
   HH(b, c, d,
    a, x[2], S34, $C4AC5665);
   
     II(a, b, c, d,
    x[0], S41, $F4292244);
   II(d, a, b,
    c, x[7], S42, $432AFF97);
   II(c, d, a, b,
    x[14], S43, $AB9423A7);
   II(b, c, d,
    a, x[5], S44, $FC93A039);
   II(a, b, c, d,
    x[12], S41, $655B59C3);
   II(d, a, b,
    c, x[3], S42, $8F0CCC92);
   II(c, d, a, b,
    x[10], S43, $FFEFF47D);
   II(b, c, d,
    a, x[1], S44, $85845DD1);
   II(a, b, c, d,
    x[8], S41, $6FA87E4F);
   II(d, a, b,
    c, x[15], S42, $FE2CE6E0);
   II(c, d, a, b,
    x[6], S43, $A3014314);
   II(b, c, d,
    a, x[13], S44, $4E0811A1);
   II(a, b, c, d,
    x[4], S41, $F7537E82);
   II(d, a, b,
    c, x[11], S42, $BD3AF235);
   II(c, d, a, b,
    x[2], S43, $2AD7D2BB);
   II(b, c, d,
    a, x[9], S44, $EB86D391);
   
     Inc(State[0], a);
   Inc(State[1], b);
   Inc(State[2], c);
   Inc(State[3], d);
   
     MD5_memset (PByteArray(@x), 0, SizeOf(x));
end;
 
   
procedure MD5Init(var Context: TMD5Context);
begin
   FillChar(Context, SizeOf(Context), 0);
   Context.state[0] := $67452301;
   Context.state[1] := $EFCDAB89;
   Context.state[2] := $98BADCFE;
   Context.state[3] := $10325476;
end;
 
procedure MD5(var Context:
  TMD5Context; Input: PByteArray; InputLen: LongWord);
var
   i, index, partLen: LongWord;
 
begin
   index := LongWord((context.count[0]shr 3)
    and $3F);
   Inc(Context.count[0], UINT4(InputLen)shl 3);
   if Context.count[0]'<';
  UINT4(InputLen)shl 3
    then Inc(Context.count[1]);
   Inc(Context.count[1], UINT4(InputLen)shr 29);
   partLen := 64 - index;
   if inputLen '>';
  = partLen then
  begin
      MD5_memcpy(PByteArray(@Context.buffer[index]),
      Input, PartLen);
      MD5Transform(@Context.state, @Context.buffer);
      i := partLen;
      while i + 63 '<';
    inputLen do
    begin
         MD5Transform(@Context.state,
        PArray64Byte(@Input));
         Inc(i, 64);
       
    end;
      index := 0;
     end
  else
    i := 0;
     MD5_memcpy(PByteArray(@Context.buffer[index]),
      PByteArray(@Input), inputLen - i);
end;
 
 
procedure MD5Final(var Digest: TMD5Digest; var Context:
  TMD5Context);
var
   bits: array[0..7] of Byte;
 index, padLen: LongWord;
begin
   MD5Encode(PByteArray(@bits), PUINT4Array(@Context.count),
    8);
   index := LongWord((Context.count[0]shr 3)
    and $3F);
   if index '<';
  56 then padLen := 56 - index
else
  padLen := 120 - index;
   MD5(Context, PByteArray(@PADDING), padLen);
   MD5(Context, PByteArray(@Bits), 8);
   MD5Encode(PByteArray(@Digest), PUINT4Array(@Context.state),
    16);
   MD5_memset(PByteArray(@Context), 0, SizeOf(Context));
end;
 
function MD5DigestToStr(const Digest:
  TMD5Digest): string;
var
   i: Integer;
begin
   Result := '';
   for i := 0 to 15 do Result := Result + IntToHex(Digest.v,
    2);
end;
 
function MD5String(const S: string): TMD5Digest;
begin
   Result := MD5Buffer(PChar(S)^, Length(S));
end;
 
function MD5File(const FileName: string): TMD5Digest;
var
   F: TFileStream;
begin
   F := TFileStream.Create(FileName, fmOpenRead);
   try
      Result := MD5Stream(F);
   finally
      F.Free;
   end;
end;
 
function MD5Stream(const Stream: TStream): TMD5Digest;
var
   Context: TMD5Context;
 Buffer: array[0..4095] of Byte;
 Size: Integer;
 ReadBytes: Integer;
 TotalBytes: Integer;
 SavePos: Integer;
begin
   MD5Init(Context);
   Size := Stream.Size;
   SavePos := Stream.Position;
   TotalBytes := 0;
   try
      Stream.Seek(0, soFromBeginning);
    repeat
       ReadBytes := Stream.Read(Buffer, SizeOf(Buffer));
       Inc(TotalBytes, ReadBytes);
       MD5(Context, @Buffer, ReadBytes);
    until (ReadBytes = 0) or (TotalBytes = Size);
   finally
      Stream.Seek(SavePos, soFromBeginning);
   end;
   MD5Final(Result, Context);
end;
 
function MD5Buffer(const Buffer;
  Size: Integer): TMD5Digest;
var
   Context: TMD5Context;
begin
   MD5Init(Context);
   MD5(Context, PByteArray(@Buffer), Size);
   MD5Final(Result, Context);
end;
 
function MD5DigestCompare(const Digest1,
  Digest2: TMD5Digest): Boolean;
begin
   Result := False;
   if Digest1.A '<';
  '>';
  Digest2.A then Exit;
   if Digest1.B '<';
  '>';
  Digest2.B then Exit;
   if Digest1.C '<';
  '>';
  Digest2.C then Exit;
   if Digest1.D '<';
  '>';
  Digest2.D then Exit;
   Result := True;
end;
 
end.
/////////////////////////////////////
// Данные в модуль можно загнать так
procedure TForm1.Button1Click(Sender: TObject);
var                                                                 
  InFile: TFileStream;                                                 
begin                                                                 
  InFile := TFileStream.Create(fname, fmShareDenyNone);             
  Caption := MD5DigestToStr(MD5Stream(INFILE));                   
end;
Записан
Nm0n
Гость
« Ответ #3 : 03 Август 2009, 09:24:11 »

Пример шифрования данных
procedure DoEncode(var Source: string; const Key: string);
asm
 Push ESI
 Push EDI
 Push EBX
 Or EAX,EAX
 Jz @Done
 Push EAX
 Push EDX
 Call UniqueString
 Pop EDX
 Pop EAX
 Mov EDI,[EAX]
 Or EDI,EDI
 Jz @Done
 Mov ECX,[EDI-4]
 Jecxz @Done
 Mov ESI,EDX
 Or ESI,ESI
 Jz @Done
 Mov EDX,[ESI-4]
 Dec EDX
 Js @Done
 Mov EBX,EDX
 Mov AH,DL
 Cld
 @L1:
 Test AH,8
 Jnz @L3
 Xor AH,1
 @L3:
 Not AH
 Ror AH,1
 Mov AL,[ESI+EBX]
 Xor AL,AH
 Xor AL,[EDI]
 Stosb
 Dec EBX
 Jns @L2
 Mov EBX,EDX
 @L2:
 Dec ECX
 Jnz @L1
 @Done:
 Pop EBX
 Pop EDI
 Pop ESI
end;
Записан
Nm0n
Гость
« Ответ #4 : 03 Август 2009, 09:25:02 »

Простой пример XOR шифрования
Вот ужасно простой пример XOR шифрования - работает без глюков.
var
  key, text, longkey, result: string;
  i: integer;
  toto, c: char;
begin
  for i := 0 to (length(text) div length(key)) do
    longkey := longkey + key;
  for i := 1 to length(text) do
  begin
    toto := chr((ord(text) xor ord(longkey))); // XOR алгоритм
    result := result + toto;
  end;
end;
Записан
Nm0n
Гость
« Ответ #5 : 03 Август 2009, 09:25:58 »

Простое симметричное шифрование строк
const
  StartKey = 471; // Start default key
  MultKey = 62142; // Mult default key
  AddKey = 11719; // Add default key
  // обязательно смените ключи до использования
function Encrypt(const InString: string; StartKey, MultKey, AddKey: Integer):
  string;
var
  I: Byte;
  // Если поменять тип переменной I на Integer, то будет возможно
  // шифрование текста длиной более 255 символом - VID.
begin
  Result := '';
  for I := 1 to Length(InString) do
  begin
    Result := Result + CHAR(Byte(InString) xor (StartKey shr 8));
    StartKey := (Byte(Result) + StartKey) * MultKey + AddKey;
  end;
end;

Пример использования:
if Encrypt(S, StartKey, MultKey, AddKey) <> OriginalPwd then
  ...
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Расшифровка строки
Предназначена для расшифровки строки, ранее зашифрованной фукцией UBPFD.Encrypt
Зависимости: UBPFD.Encrypt
Автор:       Anatoly Podgoretsky, anatoly@podgoretsky.com, Johvi
Copyright:   (c) Anatoly Podgoretsky, 1996
Дата:        26 апреля 2002 г.
***************************************************** }
const
  StartKey = 471; // Start default key
  MultKey = 62142; // Mult default key
  AddKey = 11719; // Add default key
  // обязательно смените ключи до использования
{$R-}
{$Q-}
function Decrypt(const InString: string; StartKey, MultKey, AddKey: Integer):
  string;
var
  I: Byte;
  // Если поменять тип переменной I на Integer, то будет возможно
  // шифрование текста длиной более 255 символом - VID.
begin
  Result := '';
  for I := 1 to Length(InString) do
  begin
    Result := Result + CHAR(Byte(InString) xor (StartKey shr 8));
    StartKey := (Byte(InString) + StartKey) * MultKey + AddKey;
  end;
end;
{$R+}
{$Q+}

Пример использования:
S := 'Ваш старый пароль: <' + Decrypt(S, StartKey, MultKey, AddKey) + '>';
Записан
Nm0n
Гость
« Ответ #6 : 03 Август 2009, 09:27:45 »

Пример шифрования текста
const
  csCryptFirst = 20;
  csCryptSecond = 230;
  csCryptHeader = 'Crypted';
type
  ECryptError = class(Exception);
function CryptString(Str:String):String;
var i,clen : Integer;
begin
  clen := Length(csCryptHeader);
  SetLength(Result, Length(Str)+clen);
  Move(csCryptHeader[1], Result[1], clen);
  For i := 1 to Length(Str) do
   begin
    if i mod 2 = 0 then
     Result[i+clen] := Chr(Ord(Str) xor csCryptFirst)
    else
     Result[i+clen] := Chr(Ord(Str) xor csCryptSecond);
   end;
end;
function UnCryptString(Str:String):String;
var i, clen : Integer;
begin
  clen := Length(csCryptHeader);
  SetLength(Result, Length(Str)-clen);
  if Copy(Str, 1, clen) < > csCryptHeader then
   raise ECryptError.Create('UnCryptString failed');
  For i := 1 to Length(Str)-clen do
   begin
    if (i) mod 2 = 0 then
     Result := Chr(Ord(Str[i+clen]) xor csCryptFirst)
    else
     Result := Chr(Ord(Str[i+clen]) xor csCryptSecond);
   end;
end;
Записан
Nm0n
Гость
« Ответ #7 : 03 Август 2009, 09:30:58 »

PGPSDK - легкий путь к шифрованию
Иногда бывает нужно прикрутить к своей программе какое-нибудь шифрование. Для этих целей разработаны кучи алгоритмов шифрования,
Deiffie-Hellman
CAST
IDEA
3DES
DSS
MD5
SHA1
RIPEMD-160
Реализуемые функции
Шифрование и аунтефикация (с использованием перечисленных алгоритмов);
Управление ключами (создание, сертификация, добавление/удаление из связки, проверка действительности, определения уровня надежности);
Интерфейс с сервером открытых ключей (запрос, подгрузка, удаление и отзыв ключа с удаленного сервера);
Случайные числа (генерация криптографически стойких псевдослучайных чисел и случайных чисел, базируясь на внешних источниках);
Поддержка PGP/MIME;
Вспомогательные функции.

Обзор существующих библиотек

Первое что я сделал – сходил на torry.ru и был удивлен обилием библиотек и функций для разного рода шифрования. Функциональность их я проверять не стал, а остановился на PGP-пишных компонентах.

PGPComp - ДОСовская, работает по принципу запуска внешнего exe-файла, сразу отпала по той причине - что нужно будет устанавливать MSDOS версию PGP (Кроме того библиотека только под 1 и 2 Delphi). Вспомнил что в моей любимой почтовой программе The Bat встроена поддержка PGP, зашел на их сайт - скачал библиотеку dklib.dll, любезно предоставленную ими, но почему то у меня не один из примеров не пошел, а за отсутствием исходников, я не мог понять почему. Пробовал обраться к автору - в ответ тишина, уже больше года не отвечает он. А неплохая библиотека, по крайней мере по тому что написано в документации присутствует тот необходимый минимум функций для шифрования-дешифрования, проверки ключа и сама библиотека весит не очень много – 184'832 Байт.

Т.е. меня не устроили эти библиотеки и я пошел на http://www.pgpi.org, в поисках истины. Нашел там упоминание про библиотеку для разработчиков – PGPsdk.

Собственно сам PGPsdk

28 октября 1997 г. PGP, Inc. объявила о поставке PGPsdk сторонним производителям программного обеспечения. PGPsdk - это средство разработки для программистов на С, позволяющее разработчикам программного обеспечения встраивать в него стойкие криптографические функции. Можно сказать что в PGPsdk реализованы все функции пакета PGP, мало того - версия PGP начиная с 5.0 хранит криптографические функции в динамических библиотеках – dll (о том насколько это не безопасно – вопрос к Крису Касперски, я лишь скажу что насколько я силен в математике).

PGPsdk - это динамическая библиотека, состоящая из трех файлов [табл. 1], поддерживающая базовые алгоритмы криптования (перечислены выше), гибкое управление ключами, сетевой интерфейс и др. (можно использовать одну библиотеку - PGP_sdk.dll, если Вы не будите использовать фирменный интерфейс пользователя от NAI и сетевую библиотеку).

Установка

Скачайте архив с PGPsdk [9], на момент написания статьи доступна версия 1.7.2 (должен заметить что архив занимает 3 с лишним мегабайт), необходимо его разархивировать и из каталога LibrariesDLLRelease взять следующие файлы - табл. 1 Табл.1
PGP_SDK.dll   для криптования, управление ключами и т.д.
PGPsdkUI.dll   (UI= user interface) интерфейсные штучки, если Вам нужно будет только шифровать/расшифровывать, то этот файл необязателен. Но очень полезен для ввода пароля, выбора получателей сообщений, генерации ключей и другое.
PGPsdkNL.dll   (NL= network library) сетевая библиотека для работы с сервером ключей или для transport layer security. Ее мы рассматривать не будем, но в ближайшем будущем я попытаюсь ее описать.


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

Переходим к делу.

Для работы система предоставляет ряд низкоуровневых PGP API (Application Programmig Interface) функций. Заголовки (хеадеры, описания) этих функций поставляются вместе с пакетом на Ц и лежат в каталоге Headers. Если Вы как и я пишите на Delphi, можете сами сконвертировать их, а можете взять готовые тут [10]. Это проект по переводу Ц-ных хеадеров на любимый мною язык программирования. Занимается всем этим делом Стивен Хейлер (Steven R. Heller ).

Описатели переведены на Delphi по принципу как это сделано для Ц - разбросаны на кучи модулей (листинг 1). Все названия модулей аналогичны Ц-ным заголовкам, за исключением pgpEncode - переименовано в pgpEncodePas, из-за особенностей объявления в Delphi (нельзя чтоб имя процедуры совпадало с названием модуля).

Листинг 1. Объявление используемых библиотек.
uses
  // PGPsdk
  pgpEncodePas, pgpOptionList, pgpBase, pgpPubTypes,
  pgpUtilities, pgpKeys, pgpErrors,
  // always last
  pgpSdk;



Единственная трудность, которая возникает на пути включения криптования в Ваше приложение - это использование слишком уж низкоуровневых PGP API функций. Для того что бы сделать какую-нибудь операцию - будь то подсчет публичных ключей в связке или просто зашифровать файл - необходимо создавать контекст, указать где находятся ключи, создать фильтр ключей, подготовить файловые дескрипторы, если с памятью - выделить ее (в случае шифрования-/-расшифрования), затем все это в обратном порядке освободить (если контекст неправильно освобождается - файлы с резервными ключиками не удалятся). И все это при том что в системном каталоге WINDOWS создается файл, в котором содержится информация где находятся файлы с публичными и секретными ключами (о нем будет подробно сказано ниже). Для сравнения работы через PGP API предоставлен листинг2.

Листинг 2. Пример использования PGPsdk через PGP API
var
  context: pPGPContext;
  keyFileRef: pPGPKeySet;
  defaultKeyRing: pPGPKeySet;
  foundUserKeys: pPGPKeySet;
  filter: pPGPFilter;
  countKeys: PGPUInt32;
  keyFileName: PChar;
  userID: PChar;
  inFileRef,
    outFileRef: pPGPFileSpec;
  inFileName,
    outFileName: PChar;
begin
  // Init от C++
  context := nil;
  keyFileName := 'pubring.pgp';
  userID := '';
  inFileName := 'myInFile.txt';
  outFileName := 'myOutFile.txt.asc';
  // Begin
  PGPCheckResult('sdkInit', PGPsdkInit);
  PGPCheckResult('PGPNewContext',
    PGPNewContext(
    kPGPsdkAPIVersion,
    context
    ));
  PGPCheckResult('PGPNewFileSpecFromFullPath',
    PGPNewFileSpecFromFullPath(
    context,
    keyFileName,
    keyFileRef
    ));
  PGPCheckResult('PGPOpenKeyRing',
    PGPOpenKeyRing(
    context,
    kPGPKeyRingOpenFlags_None,
    keyFileRef,
    defaultKeyRing
    ));
  PGPCheckResult('PGPNewUserIDStringFilter',
    PGPNewUserIDStringFilter(context, userID, kPGPMatchSubString, filter));
  PGPCheckResult('PGPFilterKeySet',
    PGPFilterKeySet(defaultKeyRing, filter, foundUserKeys));
  // Открываем файловые манипуляторы
  PGPCheckResult('PGPNewFileSpecFromFullPath',
    PGPNewFileSpecFromFullPath(context, inFileName, inFileRef));
  PGPCheckResult('PGPNewFileSpecFromFullPath',
    PGPNewFileSpecFromFullPath(context, outFileName, outFileRef));
  //
  // А вот здесь уже идет кодирование.
  //
  PGPCheckResult('PGPEncode',
    PGPEncode(
    context,
    [
    PGPOEncryptToKeySet(context, foundUserKeys),
      PGPOInputFile(context, inFileRef),
      PGPOOutputFile(context, outFileRef),
      PGPOArmorOutput(context, 1),
      PGPOCommentString(context, PChar('Comments')),
      PGPOVersionString(context,
        PChar('Version 5.0 assembly by Evgeny Dadgoff')),
      PGPOLastOption(context)
      ]
      ));
  //
  // Освобождаем занимаемые ресурсы и контекст PGP
  //
  if (inFileRef <> nil) then
    PGPFreeFileSpec(inFileRef);
  if (outFileRef <> nil) then
    PGPFreeFileSpec(outFileRef);
  if (filter <> nil) then
    PGPFreeFilter(filter);
  if (foundUserKeys <> nil) then
    PGPFreeKeySet(foundUserKeys);
  if (defaultKeyRing <> nil) then
    PGPFreeKeySet(defaultKeyRing);
  if (keyFileRef <> nil) then
    PGPFreeKeySet(keyFileRef);
  if (context <> nil) then
    PGPFreeContext(context);
  PGPsdkCleanup;
end;



Здесь реализован пример из [9] со страницы 39. Функция PGPCheckResult позаимствована у Стивена из его примеров - принимает два параметра - строковую и код выполнения функции PGP API, если была ошибка - генерируется исключение и на экран выводится описание ошибки с именем функции (Очень помогает для ловли ошибок, а при вызове dll-библиотеки, тем более написанной на другом языке – помогает избавиться от Access violation).

Листинг 3. Функция PGPCheckResult.
procedure PGPCheckResult(const ErrorContext: shortstring; const TheError:
  PGPError);
var
  s: array[0..1024] of Char;
begin
  if (TheError <> kPGPError_NoError) then
  begin
    PGPGetErrorString(TheError, 1024, s);
    if (PGPGetErrorString(TheError, 1024, s) = kPGPError_NoError) then
      raise exception.create(ErrorContext + ' [' + IntToStr(theError) + '] : ' +
        StrPas(s))
    else
      raise exception.create(ErrorContext +
        ': Error retrieving error description');
  end;
end;



Там же у Стивена я нашел еще один проект - написанная на Delphi библиотека для VB, проект под названием SimplePGP (SPGP). Дело в том, что VB не может использовать библиотеку PGPsdk из-за ограничения импортирования библиотек dll [9, раздел FAQ]. Сам Стивен предложил мне добавить к проекту еще одну dll, тем самым забыть про PGP API, и использовать облегченную модель вызова функций криптований.

Сам интерфейс к доступу функциям выполнен не плохо, продуманно и вызов их не должен вызвать затруднений у Вас.

Открыв ее я подумал - а не убрать ли мне все эти "stdcall;export;" и просто присоединить библиотеку к ехе-файлу (ну не устраивает меня хитросплетение dll). Сказано сделано.

Итак, поехали!

Создадим подкаталог для объявления функций PGPsdk, скопировав туда файлики DELPHI PGP API - pgp*.pas и spgp*.pas. Удалим в файлах spgp*.pas - "stdcall;export;"(уже полученные в итоге заголовочные файлы можно взять тут [12]). Теперь к Вашему проекту нужно приписать использование библиотек (это там где uses):
uses
  // PGPsdk
  pgpEncodePas, pgpOptionList, pgpBase, pgpPubTypes,
  pgpUtilities, pgpKeys, pgpErrors,
  // SPGP
  spgpGlobals, spgpEncrypt, spgpKeyUtil, spgpUtil, spgpKeyMan,
  spgpPreferences, spgpKeyProp, spgpKeyIO, spgpKeyGen, spgpMisc,
  spgpUIDialogs,
  // always last
  pgpSdk;



Можно использовать только необходимые модули.

Первое что мы попробуем сделать - это зашифровать и подписать произвольный файл и получить зашифрованный в текстовом виде (ASC). Здесь следует отметить что PGPsdk может работать не только с файлами, но и с памятью, а также комбинировать - память - файл, файл - память.
PGPCheckResult
(
  'Ошибка при шифровании файла',
  spgpencodefile(
  PChar(edtFileIn.Text),
  PChar(edtFileOut.Text),
  1, // Encrypt.Value
  1, // Sign.Value
  kPGPHashAlgorithm_MD5,
  0,
  kPGPCipherAlgorithm_CAST5,
  1,
  0,
  0,
  'Steven R. Heller', // Кто может расшифровать
  'Evgeny Dadgoff', // Чем подписывать
  'MyPassPhrase', // Хех, это пароль
  '',
  PChar(edtComment.Text)
  )
);



Сравним что получится если переделать пример [9,стр. 18] на Delphi - на чистом API.

Лично для меня проще было использовать spgp-модель чем тяжелые PGPAPI вызовы.

Про преференс

Для работы библиотеке необходимо знать где лежат файлы с ключиками (pubring.prk и secring.prk). PGP API позволяет сохранять свои настройки в файле PGPsdk.dat (почему то он всегда сохраняется в каталоге с виндами). Для работы с этим файлом предназначены следующие функции:
spgpgetpreferences(Prefs: pPreferenceRec; Flags: Longint):LongInt;
spgpsetpreferences(Prefs: pPreferenceRec; Flags: Longint):LongInt;



Соответственно для получения преференса и установки его (кстати ключики могут лежать не только в файлах). Замечу что это не единственный способ – PGP API позволяет напрямую указывать где расположены ключи, но тогда Вам придется отказаться от SPGP, или поправлять SPGP под себя.

Как получить список всех имеющихся ключей

Здесь я покажу как получить список всех ключей - заполнение LVKeys:TListView именами ключей и шестнадцатеричными ID-значениями ключей, используя SPGP-модель.
var
  P: TPreferenceRec;
  Flags: LongInt;
  outBuf: array[1..30000] of Char;
  i, KeyCount: Integer;
  TempStr, StrKeys: AnsiString;
begin
  LVKeys.Items.Clear;
  FillChar(P, 1024, 0);
  FillChar(outbuf, 30000, 0);
  Flags := PGPPrefsFlag_PublicKeyring or
    PGPPrefsFlag_PrivateKeyring or
    PGPPrefsFlag_RandomSeedFile;
  if (spgpGetPreferences(@P, Flags) <> 0) then
    ShowEvent('Error!', 1);
  // GetWindowsDirectory
  if (LowerCase(WinDir + 'pubring.pkr') = LowerCase(StrPas(P.PublicKeyring))) or
    not (FileExists(StrPas(P.PublicKeyring))) then
  begin
    StrPCopy(P.PublicKeyring, ExtractFilePath(Application.ExeName) +
      'KEYSpubring.pgp');
    StrPCopy(P.PrivateKeyring, ExtractFilePath(Application.ExeName) +
      'KEYSsecring.pgp');
    StrPCopy(P.RandomSeedFile, ExtractFilePath(Application.ExeName) +
      'KEYSrandseed.bin');
    if (CreateDir(ExtractFilePath(Application.ExeName) + 'KEYS')) then
      ShowEvent('Каталог ключей ' + ExtractFilePath(Application.ExeName) + 'KEYS'
        +
        '  не существует, Будет создан заново... ', 0);
    spgpSetPreferences(@P, Flags);
    //Создать файлы с ключами - такой хитрый прием.
    spgpSubKeyGenerate('mmmh', 'sssl', 'ssss', 1, 1024, 0, 0, 0, 0);
  end;
  btnPubKeys.Caption := StrPas(P.PublicKeyring);
  btnSecKeys.Caption := StrPas(P.PrivateKeyring);
  btnRndBin.Caption := StrPas(P.RandomSeedFile);
  PGPCheckResult('Ошибка при инициализации PGP-SDK, убедитесь что все DLL
    установленны правильно', Init(FContext, PubKey, false, false));
  spgpKeyRingID(@outBuf, 30000);
  KeyCount := spgpkeyringcount;
  StrKeys := StrPas(@outBuf);
  for i := 1 to KeyCount do
  begin
    TempStr := Copy(StrKeys, 1, Pos(#13 + #10, StrKeys));
    Delete(StrKeys, 1, Pos(#13 + #10, StrKeys) + 1);
    with (LVKeys.Items.Add) do
    begin
      Caption := Copy(TempStr, 14, Length(TempStr) - 14);
      SubItems.Add(TempStr[1]);
      SubItems.Add(Copy(TempStr, 3, 10));
    end;
  end;
  QuitIt(FContext, PubKey);
end;



Про то, как вычисляется размер зашифрованного текста.

Не всегда можно предположить какой размер будет выходного шифрованного текста, а функции проводящие преобразование требуют что бы память под него была уже выделена (разработчики PGPsdk почему-то это не предусмотрели), и если памяти не хватает - возникает исключение о нехватки памяти. Мною опытным путем была установлена формула для вычисления размера блока:
outBufLen := inBufLen * 5;
if (outBufLen < 10000) then
  outBufLen := 10000;
outBufRef := StrAlloc(outBufLen);



Временные ключики

В процессе работы программы появляются резервные файлы ключей, имеющие следующий вид - (pub|sec)ring-bak-##.pgp – предусмотрен откат от изменений. В принципе, если Вы правильно используете контекст и правильно его закрываете, этот файл корректно удаляется при освобождение контекста. Но на всякий случай можно его удалять следующим образом (повесить можно на закрытие формы или вызывать принудительно):
procedure DeleteBakPGPFiles;
var
  P: TPreferenceRec;
  FileSearch: string;
  SearchRec: TSearchRec;
begin
  spgpGetPreferences(@P, PGPPrefsFlag_PublicKeyring or
    PGPPrefsFlag_PrivateKeyring);
  FileSearch := P.PublicKeyring;
  Insert('-bak-*', FileSearch, Pos('.', FileSearch));
  FindFirst(FileSearch, faAnyFile, SearchRec);
  if (SearchRec.Name <> '') then
    if not (DeleteFile(ExtractFilePath(FileSearch) + SearchRec.Name)) then
      ShowEvent('Not delete file::' +
        ExtractFilePath(FileSearch) + SearchRec.Name, 0);
  while (FindNext(SearchRec) = 0) do
    if not (DeleteFile(ExtractFilePath(FileSearch) + SearchRec.Name)) then
      ShowEvent('Not delete file::' +
        ExtractFilePath(FileSearch) + SearchRec.Name, 0);
  FindClose(SearchRec);
end;



Интерфейс пользователя
PGP_sdkUI.dll – это библиотека пользовательских интерфейсов, фирменные штучки от Network Associates, использовав их у Вас будут диалоги такие же как у фирменного пакета PGP. Вам уже не нужно будет строить диалоги самому:
Для Генерации ключей;
При выборе получателей сообщений;
При запросе пароля и т.п.

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

Перечень функций SPGP
  { spgpDecrypt - decryption & signature verification functions            }
  function spgpdecode(BufferIn, BufferOut: PChar; BufferOutLen: LongInt; Pass,
    SigProps: PChar): LongInt;
  function spgpdecodefile(FileIn, FileOut, Pass, SigProps: PChar): LongInt;
  function spgpdetachedsigverify(SigFile, SignedFile, SigProps: PChar):LongInt;
  { spgpEncrypt - encryption & signing functions                           }
  function spgpencode(BufferIn, BufferOut: PChar; BufferOutLen: LongInt;
           Encrypt, Sign, SignAlg, ConventionalEncrypt, ConventionalAlg, Armor,
           TextMode, Clear: LongInt; CryptKeyID, SignKeyID, SignKeyPass,
           ConventionalPass, Comment: PChar): LongInt;
  function spgpencodefile(FileIn, FileOut: PChar; Encrypt, Sign, SignAlg,
           ConventionalEncrypt, ConventionalAlg, Armor, TextMode,
           Clear: LongInt; CryptKeyID, SignKeyID, SignKeyPass, ConventionalPass,
           Comment: PChar): LongInt;
  { spgpFeatures - functions to determine PGPsdk version and availability  }
  { of PGPsdk features                                                     }
  function spgpsdkapiversion: Longint;
  function spgppgpinfo(Info: pPGPInfoRec): LongInt;
  function countkeyalgs: LongInt;
  function countcipheralgs: LongInt;
  { spgpKeyGen - key-generation functions                                  }
  function spgpkeygenerate(UserID, PassPhrase, NewKeyHexID: PChar;
           KeyAlg, CipherAlg, Size, ExpiresIn, FastGeneration, FailWithoutEntropy,
           WinHandle: Longint): LongInt;
  function spgpsubkeygenerate(MasterKeyHexID, MasterKeyPass, NewSubKeyHexID: PChar;
           KeyAlg, Size: Longint; ExpiresIn, FastGeneration, FailWithoutEntropy,
           WinHandle: Longint): LongInt;
  { spgpKeyIO - Key import/export functions                                }
  function spgpkeyexport(pKeyID,BufferOut: PChar;BufferOutLen,ExportPrivate,
    ExportCompatible: LongInt):LongInt;
  function spgpkeyexportfile(pKeyID,FileOut: PChar; ExportPrivate,ExportCompatible:
    LongInt):LongInt;
  function spgpkeyimport(BufferIn,KeyProps: PChar; KeyPropsLen: LongInt):LongInt;
  function spgpkeyimportfile(FileIn,KeyProps: PChar; KeyPropsLen: LongInt):LongInt;
Записан
Nm0n
Гость
« Ответ #8 : 03 Август 2009, 09:32:26 »

Методы криптографической защиты информации Windows 2
для шифрования файла или сообщения используется быстрый симметричный алгоритм, причем ключ шифрования генерируется случайным образом с обеспечением "хороших" статистических свойств;
небольшой по размерам симметричный ключ шифрования шифруется при помощи асимметричного алгоритма с использованием открытого ключа адресата и в зашифрованном виде пересылается вместе с сообщением;
получив сообщение, адресат своим закрытым ключом расшифровывает симметричный ключ, а с его помощью - и само сообщение.

Описанная схема реализована и в CryptoAPI.

Целостность и аутентичность информации

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

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

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

Контейнеры ключей

Каждый криптопровайдер располагает базой данных, в которой хранятся долговременные ключи пользователей. База данных содержит один или более контейнеров ключе. Пользователь может создать несколько контейнеров с различными именами (именем контейнера по умолчанию является имя пользователя в системе).

Подключение к контейнеру производится одновременно с получением контекста криптопровайдера при вызове функции CryptAcquireContext - имя контейнера ключей передается функции вторым ее аргументом. Если второй аргумент содержит пустой указатель (nil), то используется имя по умолчанию, т. е. имя пользователя. В том случае, если доступ к контейнеру не нужен, можно передать в последнем аргументе функции флаг CRYPT_VERIFYCONTEXT; при необходимости создать новый контейнер используется флаг CRYPT_NEWKEYSET; а для удаления существующего контейнера вместе с хранящимися в нем ключами - CRYPT_DELETEKEYSET.

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

Создание ключевых пар

После создания контейнера ключей необходимо сгенерировать ключевые пары обмена ключами и подписи. Эту работу в CryptoAPI выполняет функция CryptGenKey (провайдер, алгоритм, флаги, ключ):
провайдер - дескриптор криптопровайдера, полученный в результате обращения к функции CryptAcquireContext;
алгоритм - указывает, какому алгоритму шифрования будет соответствовать создаваемый ключ. Информация об алгоритме, таким образом, является частью описания ключа. Каждый криптопровайдер использует для обмена ключами и подписи строго определенные алгоритмы. Так, провайдеры типа PROV_RSA_FULL, к которым относится и Microsoft Base Cryptographic Provider, реализуют алгоритм RSA. Но при генерации ключей знать это не обязательно: достаточно указать, какой ключ мы собираемся создать - обмена ключами или подписи. Для этого используются мнемонические константы AT_KEYEX и AT_SIGNATURE;
флаги - при создании асимметричных ключей управляет их размером. Используемый нами криптопровайдер позволяет генерировать ключ обмена ключами длиной от 384 до 512 бит**, а ключ подписи - от 512 до 16384 бит. Чем больше длина ключа, тем выше его надежность, поэтому трудно найти причины для использования ключа обмена ключами длиной менее 512 бит, а длину ключа подписи не рекомендуется делать меньше 1024 бит**. По умолчанию криптопровайдер создает оба ключа длиной 512 бит. Необходимую длину ключа можно передать в старшем слове параметра флаги;
ключ - в случае успешного завершения функции в этот параметр заносится дескриптор созданного ключа.


Рассмотрим пример создания ключевых пар при помощи формы. В поле "Контейнер" можно указать имя контейнера ключей; если оставить это поле пустым, будет использован контейнер по умолчанию. Назначение остальных элементов управления должно быть интуитивно понятным. После генерации ключа в memo-поле выводится отчет о его параметрах. Для этого используется функция CryptGetKeyParam (ключ, параметр, буфер, размер, флаги). Чтобы получить информацию о требуемом параметре, нужно через второй аргумент функции передать соответствующую константу: KP_ALGID - идентификатор алгоритма, KP_KEYLEN - размер ключа, и т. д. Ниже приведен текст процедуры генерации ключей без операторов обработки ошибок:
procedure TGenerateForm.OKBtnClick(Sender: TObject);
var
  cont: PChar;
  err: string;
  hProv: HCRYPTPROV;
  KeyExchKey, SignKey: HCRYPTKEY;
  flag, keyLen: DWORD;
begin
  {если ни один ключ не выбран - выход}
  if not (KEKCheckBox.Checked or SKCheckBox.Checked) then
    exit;
  {"считываем" имя контейнера}
  if length(ContainerEdit.Text) = 0 then
    cont := nil
  else
  begin
    err := ContainerEdit.Text;
    cont := StrAlloc(length(err) + 1);
    StrPCopy(cont, err);
  end;
  CryptAcquireContext(@hProv, cont, nil, PROV_RSA_FULL, 0);
  {генерация ключа обмена ключами (Key Ex Key)}
  if KEKCheckBox.Checked then
  begin
    {"считываем" длину ключа и помещаем ее в
    старшее слово параметра ФЛАГИ}
    keyLen := strtoint(KeyExchLenEdit.text);
    flag := keyLen shl 16;
    if not CryptGenKey(hProv, AT_KEYEX, flag, @KeyExchKey) then
    begin
      јобработка ошибокј
    end
    else
    begin
      ReportMemo.Lines.Add('');
      ReportMemo.Lines.Add('Создан ключ обмена ключами:');
      flag := 4;
      if not CryptGetKeyParam(KeyExchKey, KP_KEYLEN, @keyLen, @flag, 0) then
      begin
        јобработка ошибокј
      end
      else
        ReportMemo.Lines.Add(' длина ключа - ' + inttostr(keyLen));
      flag := 4;
      if not CryptGetKeyParam(KeyExchKey, KP_ALGID, @keyLen, @flag, 0) then
      begin
        јобработка ошибокј
      end
      else
        ReportMemo.Lines.Add(' алгоритм - ' + algIDtostr(keyLen));
      {функция algIDtostr здесь не приводится. Она состоит из единственного
      оператора case, отображающего целый идентификатор алгоритма в строку}
    end;
  end;
  {генерация ключа подписи (Signature Key)}
  if SKCheckBox.Checked then
  begin
    јвыполняется аналогично генерации ключа обмена ключамиј
  end;
  CryptReleaseContext(hProv, 0);
end;

Обмен ключами

Теперь мы располагаем набором ключей, однако все они останутся мертвым грузом, до тех пор пока мы не получим возможности обмена с другими пользователями открытыми ключами. Для этого необходимо извлечь их из базы данных ключей и записать в файл, который можно будет передать своим корреспондентам. При экспорте данные ключа сохраняются в одном из трех возможных форматов:
PUBLICKEYBLOB - используется для сохранения открытых ключей. Поскольку открытые ключи не являются секретными, они сохраняются в незашифрованном виде;
PRIVATEKEYBLOB - используется для сохранения ключевой пары целиком (открытого и закрытого ключей). Эти данные являются в высшей степени секретными, поэтому сохраняются в зашифрованном виде, причем для шифрования используется сеансовый ключ (и, соответственно, симметричный алгоритм);
SIMPLEBLOB - используется для сохранения сеансовых ключей. Для обеспечения секретности данные ключа шифруются с использованием открытого ключа получателя сообщения.

Экспорт ключей в CryptoAPI выполняется функцией CryptExportKey (экспортируемый ключ, ключ адресата, формат, флаги, буфер, размер буфера):
экспортируемый ключ - дескриптор нужного ключа;
ключ адресата - в случае сохранения открытого ключа должен быть равен нулю (данные не шифруются);
формат - указывается один из возможных форматов экспорта (PUBLICKEYBLOB, PRIVATEKEYBLOB, SIMPLEBLOB);
флаги - зарезервирован на будущее (должен быть равен нулю);
буфер - содержит адрес буфера, в который будет записан ключевой BLOB (Binary Large OBject - большой двоичный объект);
размер буфера - при вызове функции в этой переменной должен находиться доступный размер буфера, а по окончании работы в нее записывается количество экспортируемых данных. Если размер буфера заранее не известен, то функцию нужно вызвать с параметром буфер, равным пустому указателю, тогда размер буфера будет вычислен и занесен в переменную размер буфера.

Экспорт ключевой пары целиком, включая и закрытый ключ, может понадобиться для того, чтобы иметь возможность подписывать документы на различных компьютерах (например, дома и на работе), или для сохранения страховочной копии. В этом случае нужно создать ключ шифрования на основании пароля (см. "К+П", № 10/2002) и передать дескриптор этого ключа в качестве второго параметра функции CryptExportKey.

Запросить у криптопровайдера дескриптор самого' экспортируемого ключа позволяет функция CryptGetUserKey (провайдер, описание ключа, дескриптор ключа). Описание ключа - это либо AT_KEYEX, либо AT_SIGNATURE.

Экспорт асимметричных ключей во всем возможном многообразии можно осуществить при помощи формы.



Ниже приведены наиболее важные фрагменты программы:
procedure TExportForm.OKBtnClick(Sender: TObject);
var
  cont: PChar;
  err: string;
  hProv: HCRYPTPROV;
  key, expKey: HCRYPTKEY;
  pbuf: PBYTE;
  buflen: DWORD;
  f: file;
  hash: HCRYPTHASH;
begin
  {если ни один ключ не выбран - выход}
  if not (KEKCheckBox.Checked or SKCheckBox.Checked) then
    exit;
  {если нужен пароль, т.е. экспортируется ключевая пара целиком}
  if PasswEdit.Enabled and (PasswEdit.Text < > Passw2Edit.Text) then
  begin
    MessageDlg('Ошибка при вводе пароля! Повторите ввод.', mtError, [mbOK], 0);
    exit;
  end;

"считываем" имя контейнера и подключаемся к криптопровайдеру

если нужен ключ шифрования - создаем его на основании пароля

{ключ обмена ключами}
if KEKCheckBox.Checked then
  repeat
    {получаем дескриптор ключа}
    CryptGetUserKey(hProv, AT_KEYEX, @key);
    {пытаемся определить размер буфера для экспорта ключа}
    if (WhatRadioGroup.ItemIndex = 0) then
      CryptExportKey(key, 0, PUBLICKEYBLOB, 0, nil, @bufLen)
    else
      CryptExportKey(key, expKey, PRIVATEKEYBLOB, 0, nil, @bufLen);
    GetMem(pbuf, bufLen);
    {экспортируем данные}
    if (WhatRadioGroup.ItemIndex = 0) then
      CryptExportKey(key, 0, PUBLICKEYBLOB, 0, pbuf, @bufLen)
    else
      CryptExportKey(key, expKey, PRIVATEKEYBLOB, 0, pbuf, @bufLen);
    {освобождаем дескриптор ключа обмена ключами
    (сам ключ при этом не уничтожается)}
    CryptDestroyKey(key);
    SaveDialog1.Title := 'Укажите файл для сохранения ключа обмена ключами';
    if SaveDialog1.ute then
    begin
      AssignFile(f, SaveDialog1.FileName);
      rewrite(f, 1);
      BlockWrite(f, pbuf^, bufLen);
      CloseFile(f);
      MessageDlg('Ключ обмена ключами успешно сохранен', mtInformation, [mbOK],
        0);
    end;
  until true; {KeyEx}
{ключ подписи}
if SKCheckBox.Checked then
  repeat
    јаналогично ключу обмена ключамиј
  until true; {Signature}
end;

если создавался ключ на основании пароля - уничтожаем его,
после чего освобождаем контекст криптопровайдера


Экспортированные таким образом открытые части ключей понадобятся нам для проверки подписи и расшифровки сеансового ключа.

Импорт ключевых пар во вновь созданный контейнер - это самостоятельная процедура. Необходимо запросить у пользователя название контейнера и пароль, подключиться к провайдеру, создать на основании пароля ключ, считать из файла импортируемые данные в буфер, после чего воспользоваться функцией CryptImportKey (провайдер, буфер, длина буфера, ключ для расшифровки, флаги, импортируемый ключ). Если нужно обеспечить возможность экспорта импортируемой ключевой пары впоследствии, то в параметре флаги необходимо передать значение CRYPT_EXPORTABLE; в противном случае вызов для данной ключевой пары функции CryptExportKey приведет к ошибке.

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

Электронная цифровая подпись

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

Подписать вычисленный хеш в CryptoAPI позволяет функция CryptSignHash (хеш, описание ключа, комментарий, флаги, подпись, длина подписи). Вторым параметром может быть либо AT_KEYEX, либо AT_SIGNATURE (в нашем случае логичнее использовать ключ подписи). Третий параметр в целях безопасности настоятельно рекомендуется оставлять пустым (nil). Флаги в настоящее время также не используются - на месте этого аргумента должен быть нуль. Готовую электронную подпись функция запишет в буфер, адрес которого содержится в предпоследнем параметре, последний же параметр будет содержать длину подписи в байтах.
procedure TSigningForm.SignBtnClick(Sender: TObject);
var
  cont: PChar;
  err: string;
  hProv: HCRYPTPROV;
  key: HCRYPTKEY;
  alg: ALG_ID;
  hash: HCRYPTHASH;
  infile, outfile: file;
  size: DWORD;
  buf: array[0..511] of byte;
  signature: PBYTE;
begin
  {проверка существования выбранного файла}
  if not FileExists(DataNameEdit.Text) then
  begin
    MessageDlg('Неверное имя файла!', mtError, [mbOK], 0);
    exit;
  end;
  AssignFile(infile, DataNameEdit.Text);
  …
    "считываем" имя контейнера и подключаемся к нему
    …
    case HashRadioGroup.ItemIndex of
    0: alg := CALG_MD5;
    1: alg := CALG_SHA;
  end;
  CryptCreateHash(hProv, alg, 0, 0, @hash);
  SaveDialog1.Title := 'Задайте имя файла для хранения подписанных данных';
  if SaveDialog1.ute then
  begin
    AssignFile(outfile, SaveDialog1.FileName);
    rewrite(outfile, 1);
    {записываем в файл идентификатор алгоритма хеширования}
    BlockWrite(outfile, alg, 4);
    reset(infile, 1);
    size := FileSize(infile);
    {записываем размер подписываемых данных}
    BlockWrite(outfile, size, 4);
    {пишем сами данные и вычисляем хеш:}
    while not eof(infile) do
    begin
      BlockRead(infile, buf, 512, size);
      BlockWrite(outFile, buf, size);
      CryptHashData(hash, @buf, size, 0);
    end;
    CloseFile(infile);
    {выясняем размер подписи}
    CryptSignHash(hash, AT_SIGNATURE, nil, 0, nil, @size);
    {создаем подпись}
    GetMem(signature, size);
    CryptSignHash(hash, AT_SIGNATURE, nil, 0, signature, @size);
    BlockWrite(outfile, size, 4);
    BlockWrite(outfile, signature^, size);
    CloseFile(outfile);
  end;
  …
    уничтожаем хеш - объект и освобождаем контекст
    …
end;

Чтобы проверить правильность подписи, получатель подписанного сообщения должен иметь файл с открытым ключом подписи отправителя. В процессе проверки подписи этот ключ импортируется внутрь криптопровайдера. Проверка выполняется функцией CryptVerifySignature (хеш, подпись, длина подписи, открытый ключ, комментарий, флаги). О последних двух аргументах можно сказать то же, что и о параметрах комментарий и флаги функции CryptSignHash, назначение же остальных должно быть понятно. Если подпись верна, функция возвращает true. Значение false в качестве результата может свидетельствовать либо о возникновении ошибки в процессе проверки, либо о том, что подпись оказалась неверной. В последнем случае функция GetLastError вернет ошибку NTE_BAD_SIGNATURE. Для примера приведем наиболее значимые фрагменты программы проверки подписи:
procedure TMainForm.VerifyItemClick(Sender: TObject);
var
  err: string;
  hProv: HCRYPTPROV;
  key: HCRYPTKEY;
  alg: ALG_ID;
  hash: HCRYPTHASH;
  infile: file;
  size, test, textsize: DWORD;
  buf: PBYTE;
  signature, signkey: PBYTE;
begin
  …
    получаем контекст криптопровайдера
    …
    OpenDialog1.Title := 'Укажите файл с подписанными данными';
  if OpenDialog1.ute then
  begin
    AssignFile(infile, OpenDialog1.FileName);
    reset(infile, 1);
    {считываем идентификатор алгоритма хеширования}
    BlockRead(infile, alg, 4);
    {считываем размер подписанных данных и сами данные}
    BlockRead(infile, textsize, 4);
    GetMem(buf, textsize);
    BlockRead(infile, buf^, textsize, test);
    if test <
    textsize then
    begin
      MessageDlg('Неверный формат файла! Процесс прерван.', mtError, [mbOK], 0);
      exit;
    end;
    {считываем размер подписи и саму подпись}
    BlockRead(infile, test, 4);
    GetMem(signature, test);
    BlockRead(infile, signature^, test);
    CloseFile(infile);
  end
  else
    exit;
  …
    создаем хеш - объект и хешируем данные
    …
    OpenDialog1.Title := 'Укажите файл с открытым ключом подписи';
  if OpenDialog1.ute then
  begin
    AssignFile(infile, OpenDialog1.FileName);
    reset(infile, 1);
    size := FileSize(infile);
    GetMem(signkey, size);
    BlockRead(infile, signkey^, size);
    CloseFile(infile);
  end
  else
    exit;
  {импортируем открытый ключ подписи отправителя}
  CryptImportKey(hProv, signkey, size, 0, 0, @key);
  FreeMem(signkey, size);
  {проверяем подпись}
  if CryptVerifySignature(hash, signature, test, key, nil, 0) then
  begin
    MessageDlg('Подпись верна.', mtInformation, [mbOK], 0);
    {сохраняем подписанные данные}
    SaveDialog1.Title := 'Укажите имя файла для сохранения данных';
    if SaveDialog1.ute then
    begin
      AssignFile(infile, SaveDialog1.FileName);
      rewrite(infile, 1);
      BlockWrite(infile, buf^, textsize);
      CloseFile(infile);
    end;
  end
  else
  begin
    case int64(GetLastError) of
      NTE_BAD_SIGNATURE: err := 'Подпись неверна!';
      {обработка других ошибок}
    else
      err := 'Ошибка при проверке подписи: Unknown error';
    end;
    MessageDlg(err, mtError, [mbOK], 0);
  end;
  …
    уничтожаем хеш - объект и импортированный ключ
    и освобождаем контекст криптопровайдера
    …
end;
Записан
Nm0n
Гость
« Ответ #9 : 03 Август 2009, 09:33:23 »

Криптоалгоритм DES
Особенностью этой криптосистемы является использование операций циклического сдвига, зависящих от преобразуемых данных RC5 [12,13].

Это задает непредопределенность операций преобразования, выполняемых над преобразуемыми подблоками данных, что по замыслу разработчиков должно привести к до-стижению высокой криптостойкости. В этой криптосистеме пре-дусмотрена возможность задания пользователем числа раундов и размера входного блока данных. Входной блок разбивается на два подблока одинаковой длины. Обозначим длину подблока в битах через Ъ. Шифрование заключается в поочередном преоб-разовании подблоков с использованием операций поразрядно-го суммирования по модулю два, суммирования по модулю 26 и управляемых операций циклического сдвига. Варианты опера-ции циклического сдвига различаются величиной сдвига от 0 до Ь - 1 бит. Для обозначения операции циклического сдвига подблока (слова) W на х бит будем применять запись W <<< х (сдвиг влево) и W >>> х (сдвиг вправо). Для выбора конкретной модификации операции циклического сдвига используется log2 b младших разрядов управляющего блока.
На каждом раунде преобразования используется отдельная пара подключей, поэтому при выборе различного числа раун-дов преобразования используется ключ шифрования различной длины. Для удобства пользователей в шифре RC5 используется алгоритм инициализации, выполняемый однократно при запуске криптосистемы. На этом этапе предвычислений по секретному ключу пользователя в зависимости от заданного числа раундов R формируется ключ шифрования в виде последовательности, содержащей 2(R+1) b-битовых подключей. Подключи используются по фиксированному расписанию, т. е. для всех входных блоков на данном шаге преобразования используется один и тот же подключ. Для того, чтобы указать значения конкретных па-раметров настройки алгоритма применяется запись RC5-b/R/l, где l -длина секретного ключа в байтах.
Типичной является модификация RC5-32/12/16, ко-торая соответствует 64-битовому входному блоку данных, 128-битовому секретному ключу и 12 раундам шифрования. Эта модификация является стойкой к известным методам криптоанализа и может применяться для защиты данных в авто-матизированных системах обработки информации и управления. В случае применения 64-разрядных микропроцессоров целе-сообразно применение модификации RC5-64/12/16, которая обеспечит более высокую скорость преобразования. Кроме того, при преобразовании 128-битовых блоков исходного текста повышается стойкость шифрования за счет увеличения числа управляющих битов с 5 до 6.
Алгоритм предвычислений формирует ключ шифрования необходимого размера, при этом используются процедуры, которые обеспечивают влияние каждого бита секретного ключа на каждый бит ключа шифрования. По значению клю-ча шифрования трудно восстановить секретный ключ. Мы не будем останавливаться на описании алгоритма предвы-числений, поскольку могут быть применены различные его варианты. Рассмотрим процедуры шифрования и дешифро-вания, которые составляют сущность криптосистемы RC5. Обозначим ключ шифрования как последовательность подключей Q0, Q1, ..., Q2R+1. Процедуры шифрования и дешифрования блока Т = A | В описываются следующими простыми алгоритмами:

BХОД: Исходное значение Ь-битовых подблоков A и В.
1. Установить счетчик i == 1 и число раундов R и выполнить преобразования:
А := (A + Qo) mod
2 b;
В := (B + Q1)mod2b.2. Преобразовать: A := {[(A ф В)
<<< В] +Q2i}mod 2 bB := [(B ф A) < < < A] + Q2i + 1} mod
2 b3.Если i? R, то прирастить i := i + 1 и перейти к шагу 2.4.
СТОП.
ВЫХОД: Преобразованное значение подблоков A и В.
Алгоритм дешифрования.
1. Установить счетчик i = R.
2. Преобразовать: B :=
{[(В - Q2i+i) mod 2b] >>> A} Ф AA := {[(A - Q2i ) mod2b]
>>> В} Ф В.3. Если i ? 1, то уменьшить на 1 значение счетчика
i := i - 1 и перейти к шагу 2.4. Преобразовать: B := (B - Q1)
mod2b;
A := (A - Q0) mod 2 b
Схема шифрующих преобразований одного раунда пред-ставлена на рис. Операция циклического сдвига относится к быстрым элементарным операциям современных процессоров, Кроме того, время выполнения операции циклического сдвига не зависит от величины сдвига. При программной реализации модификация RC5-32/12/16 обеспечивает скорость шифро-вания порядка нескольких Мбайт/с для микропроцессора Pentium. В приведенных алгоритмах не используются операция табличной подстановки, которые являются типичными нели-нейными операциями для многих блочных криптосистем, Нелинейными операциями рассмотренного шифра явля-ются операции циклического сдвига, зависящие от пре-образуемых данных. Как шифрование, так и дешифрова-ние начинается с выполнения операции над подблоками и подключами. Это обусловливает различие в операци-ях циклического сдвига на каждом раунде шифрования в дешифрования. Двухместные операции (Ф), выполняемые над двумя подблоками, усиливают эффект размножения ошибки.
Записан
Nm0n
Гость
« Ответ #10 : 04 Август 2009, 21:58:38 »

CryptoAPI для шифрования файлов случайными ключами
Для конфиденциального обмена информацией с корреспондентом в любой точке земного шара приходится использовать целый арсенал современных криптографических инструментов: симметричные и асимметричные алгоритмы шифрования, механизмы генерирования криптографических ключей и случайных последовательностей, специфические режимы работы шифров и пр. Рассмотрим реализацию этих инструментов в CryptoAPI и воспользуемся ими для шифрования файла случайным ключом.

Цифровые конверты

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

Создание сеансовых ключей

CryptoAPI позволяет генерировать сеансовые ключи случайным образом - эту работу выполняет функция CryptGenKey, о которой шла речь в предыдущей статье. Однако при использовании этой возможности за пределами США и Канады приходится учитывать американские ограничения на экспорт средств "сильной криптографии". В частности, до января 2000 года был запрещен экспорт программного обеспечения для шифрования с использованием ключей длиной более 40 бит. Этим объясняется разработка Microsoft двух версий своего криптопровайдера - базовой и расширенной. Базовая версия предназначалась на экспорт и поддерживала симметричные ключи длиной 40 бит; расширенная же версия (Microsoft Enhanced Cryptographic Provider) работала с "полной" длиной ключа (128 бит). Поскольку алгоритм шифрования, как правило, требует использования ключа строго определенной длины, недостающее количество битв в урезанном "экспортном" ключе могло быть заполнено либо нулями, либо случайными данными, которые предлагалось передавать открыто.

В криптографической практике внесение в состав ключа определенной части несекретных данных, которые сменяются несколько раз в ходе обработки исходного или шифр-текста, используется для того, чтобы воспрепятствовать взлому шифра атакой "по словарю". В английской терминологии такие вставки называются salt values: их назначение - "подсолить" ключ (с учетом нашей ментальности можно перевести как "насолить" противнику). Поскольку этот термин используется и в CryptoAPI, будем употреблять его в транслитерированном виде - солт-значения.

Итак, CryptoAPI, в экспортном исполнении практически вынуждает нас использовать солт-значения, составляющие бОльшую часть ключа - 88 бит из 128-ми для симметричных алгоритмов в RC2; и RC4. Конечно, при такой эффективной длине ключа криптозащита не может считаться достаточно надежной. В реальной ситуации выход один - воспользоваться криптопровайдером, не ограничивающим длину ключа. Обладатели Windows XP могут прибегнуть к услугам расширенных версий провайдера Microsoft (Enhanced или Strong). Пользователям более старых версий Windows, по-видимому, придется воспользоваться продуктами сторонних разработчиков. Например, свои версии криптопровайдеров предлагают российская компания "Крипто-Про" и шведская "StreamSec". В Украине, насколько известно авторам, разработкой национального провайдера криптографических услуг занимается коллектив харьковских ученых под руководством профессора Горбенко, однако до широкого внедрения дело пока не дошло. Тем не менее, благодаря архитектуре CryptoAPI, прикладные программы могут разрабатываться и отлаживаться и с базовым провайдером Microsoft - так как интерфейс взаимодействия остается неизменным. Поэтому вернемся к обсуждению создания случайных сеансовых ключей.

Солт может быть сгенерирован вместе с ключом: для этого нужно в качестве флага передать функции CryptGenKey (или CryptDeriveKey) константу CRYPT_CREATE_SALT. Правда, при сохранении ключа (с помощью функции CryptExportKey) система уже не заботится о солт-значении, перекладывая ответственность на прикладную программу. Таким образом, корректная процедура создания и сохранения симметричного ключа предполагает:

1. при создании ключа функции CryptGenKey передается значение флага CRYPT_EXPORTABLE or CRYPT_CREATE_SALT;

2. с помощью функции CryptGetKeyParam с параметром KP_SALT сгенерированное солт-значение сохраняется в буфере;

3. ключ в зашифрованном виде сохраняется в буфере при помощи функции CryptExportKey, которой передается открытый ключ обмена ключами адресата;

4. зашифрованные ключевые данные сохраняются или передаются адресату вместе с экспортированным на втором шаге солт-значением.

С другой стороны, солт-значение может быть сгенерировано и отдельно от ключа. Для этого используется функция CryptGenRandom (провайдер, длина, буфер). Здесь параметр длина задает размер генерируемой случайной последовательности в байтах, а последний аргумент задает адрес буфера, в который будет записан результат. Полученное таким образом солт-значение может быть внесено в ключ с помощью функции CryptSetKeyParam (ключ, параметр, данные, флаги). Ей вторым аргументом нужно передать KP_SALT, а третьим - адрес буфера, содержащего сгенерированную последовательность. (Последний аргумент функции зарезервирован на будущее и должен быть равен нулю.)

Блочные шифры

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

Наиболее простой и интуитивно понятный способ состоит в том, чтобы разбить исходный текст на блоки соответствующего размера, а затем отдельно каждый блок подвергнуть шифрующему преобразованию. Такой режим использования блочных шифров называют электронной кодовой книгой (ECB - electronic codebook). Его главный недостаток состоит в том, что одинаковые блоки исходного текста при шифровании дадут одинаковые же блоки шифр-текста - а это может существенно облегчить противнику задачу взлома. Поэтому режим ECB не рекомендуется использовать при шифровании текстов, по длине превышающих один блок - в таких случаях лучше воспользоваться одним из режимов, связывающих различные блоки между собой. По умолчанию в CryptoAPI блочные шифры используются в режиме сцепления блоков шифр-текста (CBC - cipher block chaining). В этом режиме при шифровании очередной блок исходного текста вначале комбинируется с предыдущим блоком шифр-текста (при помощи побитового исключающего ИЛИ), а затем полученная последовательность битов поступает на вход блочного шифра. Образующийся на выходе блок шифр-текста используется для шифрования следующего блока. Самый первый блок исходного текста также должен быть скомбинирован с некоторой последовательностью битов, но "предыдущего блока шифр-текста" еще нет; поэтому режимы шифрования с обратной связью требуют использования еще одного параметра - он называется инициализирующим вектором (IV - initialization vector).

Инициализирующий вектор должен генерироваться отдельно с помощью уже известной нам функции CryptGenRandom и, как и солт-значение, передаваться вместе с ключом в открытом виде. Размер IV равен длине блока шифра. Например, для алгоритма RC2, поддерживаемого базовым криптопровайдером Microsoft, размер блока составляет 64 бита (8 байтов).

От слов - к делу

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

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

Приведем основные фрагменты процедуры, осуществляющей шифрование и расшифровку файла (обработка ошибок опущена):
procedure TMainForm.BitBtn1Click(Sender: TObject);
var
  hProv: HCRYPTPROV;
  KeyExchKey, SessionKey: HCRYPTKEY;
  flag, keyLen: DWORD;
  infile, outfile: file;
  tmp: PBYTE;
  buf: array[0..511] of byte;
  alg: ALG_ID;
  stream: boolean;
begin
  …
    подключение к криптопровайдеру
    …
    if ActionRadioGroup.ItemIndex = 0 {шифрование} then
  begin
    OpenDlg.Title := 'Укажите файл для шифрования';
    if OpenDlg.ute then
      AssignFile(infile, OpenDlg.FileName)
    else
      exit;
    OpenDlg.Title := 'Укажите файл с открытым ключом обмена ключами получателя';
    if OpenDlg.ute then
    begin
      AssignFile(outfile, OpenDlg.FileName);
      reset(outfile, 1);
      keyLen := FileSize(outfile);
      GetMem(tmp, keyLen);
      BlockRead(outfile, tmp^, keyLen);
      CloseFile(outfile);
    end
    else
      exit;
    CryptImportKey(hProv, tmp, keyLen, 0, 0, @KeyExchKey);
    FreeMem(tmp, keyLen);
    SaveDlg.Title := 'Задайте имя файла для зашифрованных данных';
    if SaveDlg.ute then
      AssignFile(outfile, SaveDlg.FileName)
    else
      exit;
    rewrite(outfile, 1);
    case AlgRadioGroup.ItemIndex of {установка алгоритма шифрования}
      0:
        begin
          alg := CALG_RC2; {алгоритм RC2}
          stream := false; {блочный шифр}
        end;
      1:
        begin
          alg := CALG_RC4; {алгоритм RC4}
          stream := true; {поточный шифр}
        end;
    end;
    CryptGenKey(hProv, alg, CRYPT_EXPORTABLE or CRYPT_CREATE_SALT, @SessionKey);
      {создание сеансового ключа}
    keyLen := 128; {размер буфера "с запасом"}
    GetMem(tmp, keyLen);
    CryptExportKey(SessionKey, KeyExchKey, SIMPLEBLOB, 0, tmp, @keyLen);
    BlockWrite(outfile, keyLen, 4); {запись в файл размера ключа}
    BlockWrite(outfile, tmp^, keyLen); {и самого зашифрованного ключа}
    CryptDestroyKey(KeyExchKey);
    keyLen := 512; {размер буфера "с запасом"}
    CryptGetKeyParam(SessionKey, KP_SALT, @buf, @keyLen, 0);
    BlockWrite(outfile, keyLen, 4); {запись в файл размера солта}
    BlockWrite(outfile, buf, keyLen); {и самого солт-значения}
    if not stream then {если шифр - блочный}
    begin
      //генерируем IV
      keyLen := 512; {размер буфера "с запасом"}
      // запрос IV ради выяснения его размера
      CryptGetKeyParam(SessionKey, KP_IV, @buf, @keyLen, 0);
      CryptGenRandom(hProv, keyLen, @buf); {генерация IV}
      CryptSetKeyParam(SessionKey, KP_IV, @buf, 0);
      BlockWrite(outfile, keyLen, 4); {запись в файл размера IV}
      BlockWrite(outfile, buf, keyLen); {и самого IV}
    end;
    reset(infile, 1);
    while not eof(infile) do
    begin {собственно шифрование и запись в файл}
      BlockRead(infile, buf, 496, keyLen);
      CryptEncrypt(SessionKey, 0, eof(infile), 0, @buf, @keyLen, 512);
      BlockWrite(outfile, buf, keyLen);
    end;
    CloseFile(infile);
    CloseFile(outfile);
    CryptDestroyKey(SessionKey);
  end
  else {расшифровывание}
  begin {получаем дескриптор своего ключа обмена ключами}
    CryptGetUserKey(hProv, AT_KEYEXCHANGE, @KeyExchKey);
    OpenDlg.Title := 'Укажите файл с зашифрованными данными';
    if OpenDlg.ute then
      AssignFile(infile, OpenDlg.FileName)
    else
      exit;
    reset(infile, 1);
    BlockRead(infile, keyLen, 4); {читаем размер ключа}
    GetMem(tmp, keyLen);
    BlockRead(infile, tmp^, keyLen); {читаем сам ключ}
    CryptImportKey(hProv, tmp, keyLen, KeyExchKey, 0, @SessionKey);
    FreeMem(tmp, keyLen);
    CryptDestroyKey(KeyExchKey);
    BlockRead(infile, keyLen, 4); {читаем солт-значение}
    BlockRead(infile, buf, keyLen);
    CryptSetKeyParam(SessionKey, KP_SALT, @buf, 0);
    keyLen := 4; {выясняем алгоритм шифрования}
    CryptGetKeyParam(SessionKey, KP_ALGID, @alg, @keyLen, 0);
    case alg of
      CALG_RC2: stream := false;
      CALG_RC4: stream := true;
    end;
    if not stream then {если шифр - блочный}
    begin
      //читаем и устанавливаем IV
      BlockRead(infile, keyLen, 4);
      BlockRead(infile, buf, keyLen);
      CryptSetKeyParam(SessionKey, KP_IV, @buf, 0);
    end;
    SaveDlg.Title := 'Задайте имя файла для расшифрованных данных';
    if SaveDlg.ute then
    begin
      AssignFile(outfile, SaveDlg.FileName);
      rewrite(outfile, 1);
      while not eof(infile) do
      begin {собственно расшифровывание}
        BlockRead(infile, buf, 512, keyLen);
        CryptDecrypt(SessionKey, 0, eof(infile), 0, @buf, @keyLen);
        BlockWrite(outfile, buf, keyLen);
      end;
      CloseFile(outfile);
    end;
    CloseFile(infile);
    CryptDestroyKey(SessionKey);
  end;
  CryptReleaseContext(hProv, 0);
end;

В рассмотренной нами процедуре обмена шифрованными сообщениями остается одно слабое звено - обмен открытыми ключами. Ведь при этом мы не обеспечиваем подлинность полученного ключа - во время пересылки его может подменить злоумышленник. CryptoAPI для решения этой проблемы предполагает использование сертификатов. Но об этом - в следующий раз.
Записан
Nm0n
Гость
« Ответ #11 : 04 Август 2009, 21:59:42 »

Шифрование текста 2
{$I-,R-}
Unit Crypter;
interface
Uses Objects;
procedure EnCrypt(var Pntr: Array of Char; ArrLen: Word; password: string);
{ - Закpиптовать блок }
procedure DeCrypt(var Pntr: Array of Char; ArrLen: Word; password: string);
{ - Раскиптовать блок }
procedure EnCryptStream(var st: tStream; Password: String);
{ - Закpиптовать поток }
procedure DeCryptStream(var st: tStream; Password: String);
{ - Раскиптовать поток }
implementation
procedure EnCrypt(var Pntr: Array of Char; ArrLen:Word; password: string);
var
  len,pcounter: byte;
  x:Word;
begin
  len := length(password) div 2;
  pcounter := 1;
  for x:=0 to ArrLen-1 do begin
    Pntr
  • := chr(ord(password[pcounter]) + ord(Pntr
  • ) + len);
    inc(pcounter);
    if pcounter > length(password) then pcounter := 1;
  end;
end;
procedure DeCrypt(var Pntr: Array of Char; ArrLen:Word; password: string);
var
  len,pcounter: byte;
  x:Word;
begin
  len := length(password) div 2;
  pcounter := 1;
  for x:=0 to ArrLen-1 do begin
    Pntr
  • := chr(ord(Pntr
  • ) - ord(password[pcounter]) - len);
    inc(pcounter);
    if pcounter > length(password) then pcounter := 1;
  end;
end;
type
 pBuffer = ^tBuffer;
 tBuffer = Array[1..$FFFF] of Char;
procedure EnCryptStream(var st: tStream; Password: String);
 var
  buf: pBuffer;
  StSize, StPos, p: Longint;
 begin
  if (@st=nil) or (Password='') then exit;
  New(buf);
  StPos:=st.GetPos;
  StSize:=st.GetSize;
  st.Reset;
  st.Seek(0);
  repeat
   p:=st.GetPos;
   if SizeOf(Buf^)> St.GetSize-St.GetPosthen st.Read(buf^,St.GetSize-St.GetPos)
else st.Read(buf^,SizeOf(Buf^));
   EnCrypt(buf^,SizeOf(buf^),password);
   st.Reset;
   st.Seek(p);
   st.Write(buf^,SizeOf(Buf^));
  until (St.GetSize=St.GetPos);
  st.Seek(StSize);
  st.Truncate;
  st.Seek(StPos);
  Dispose(buf);
 end;
procedure DeCryptStream(var st: tStream; Password: String);
 var
  buf: pBuffer;
  StSize, StPos, p: Longint;
 begin
  if (@st=nil) or (Password='') then exit;
  New(buf);
  StPos:=st.GetPos;
  StSize:=st.GetSize;
  st.Reset;
  st.Seek(0);
  repeat
   p:=st.GetPos;
   if SizeOf(Buf^)> St.GetSize-St.GetPosthen st.Read(buf^,St.GetSize-St.GetPos)
else st.Read(buf^,SizeOf(Buf^));
   DeCrypt(buf^,SizeOf(buf^),password);
   st.Reset;
   st.Seek(p);
   st.Write(buf^,SizeOf(Buf^));
  until (St.GetSize=St.GetPos);
  st.Seek(StSize);
  st.Truncate;
  st.Seek(StPos);
  Dispose(buf);
 end;
end.
Записан
Nm0n
Гость
« Ответ #12 : 04 Август 2009, 22:00:32 »

Шифрование Bitmap
procedure EncryptBMP(const BMP: TBitmap; Key: Integer);
 var
   BytesPorScan: Integer;
   w, h: integer;
   p: pByteArray;
 begin
   try
     BytesPorScan := Abs(Integer(BMP.ScanLine[1]) -
       Integer(BMP.ScanLine[0]));
   except
     raise Exception.Create('Error');
   end;
   RandSeed := Key;
   for h := 0 to BMP.Height - 1 do
   begin
     P := BMP.ScanLine[h];
     for w := 0 to BytesPorScan - 1 do
       P^[w] := P^[w] xor Random(256);
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   EncryptBMP(Image1.Picture.Bitmap, 623);
   Image1.Refresh;
 end;
 { Call the function again to decrypt it }
 { Zum Entschlusseln die Funktion nochmals aufrufen }
Записан
Nm0n
Гость
« Ответ #13 : 04 Август 2009, 22:01:53 »

Шифрование текста по введенному паролю
Процедура шифрует текст основываясь на введенном пароле.
var
  s: string;
procedure Code(var text: string; password: string;
  decode: boolean);
var
  i, PasswordLength: integer;
  sign: shortint;
begin
  PasswordLength := length(password);
  if PasswordLength = 0 then
    Exit;
  if decode then
    sign := -1
  else
    sign := 1;
  for i := 1 to Length(text) do
    text := chr(ord(text) + sign *
      ord(password[i mod PasswordLength + 1]));
end;

Пример использования:
procedure TForm1.Button1Click(Sender: TObject);
begin
  s := Memo1.Text;
  code(s, Edit1.Text, false);
  Memo1.Text := s;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  code(s, Edit1.Text, true);
  Memo1.Text := s;
end;
Записан
Nm0n
Гость
« Ответ #14 : 04 Август 2009, 22:02:51 »

UUE кодирование
 того, чтобы ОНО заработало, необходимо создать проект в составе:
Форма (form) - 1 шт.
Поле ввода (edit) - 2 шт., используются события OnDblClick.
Кнопка (button) - 1 шт., используется событие OnClick.
Диалог открытия файла (Open Dialog) - 1 шт.
Диалог сохранения файла (Save Dialog) - 1 шт.
Имена файлов будут вводится либо вручную, либо из диалога (double-click на поле ввода edit), причем в edit1.text должно лежать имя входного файла, в edit2.text - выходного. По нажатии кнопки пойдет процесс, который завершится сообщением "DONE."

Всего хорошего.

P. S. Функция toanysys обнаружена в книге "Для чего нужны и как работают персональные ЭВМ" от 1990 г. Там она присутствует в виде программы на BASIC'e.

P.P.S. Для стимулирования фантазии читателей "Советов..." высылаю так же мессагу из эхи, на основе которой я сваял свое чудо.

Файл Unit1.pas
//UUE кодирование
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtDlgs, StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    procedure Edit1DblClick(Sender: TObject);
    procedure Edit2DblClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
const
  ssz = (High(Cardinal) - $F) div sizeof(byte);
  //эта константа используется при выделении памяти
  p: string = '0123456789ABCDEF';
  //эта константа используется функцией toanysys
  //выбор входного файла
procedure TForm1.Edit1DblClick(Sender: TObject);
begin
  if opendialog1.execute then
    edit1.text := opendialog1.filename;
end;
//выбор выходного (UUE) файла
procedure TForm1.Edit2DblClick(Sender: TObject);
begin
  if savedialog1.execute then
    edit2.text := savedialog1.filename;
end;
//выделение подстроки
function mid(s: string; fromc, toc: byte): string;
var
  s1: string;
  i: byte;
begin
  s1 := '';
  for i := fromc to toc do
    s1 := s1 + s;
  mid := s1;
end;
//перевод числа (a) из десятичной системы в другую
//с основанием (r)
function toanysys(a, r: byte): string;
var
  s,
  k: string;
  n,
    m,
    i: byte;
begin
  s := '';
  m := 1;
  while m <> 0 do
  begin
    m := a div r;
    n := a - m * r + 1;
    k := p[n];
    s := k + s;
    a := m;
  end;
  //добавляет незначащие нули
  for i := 1 to 8 - length(s) do
    s := '0' + s;
  toanysys := s;
end;
//перевод 6-разрядного числа из двоичной системы в десятичную
//двоичное число подставляется в виде строки символов
function frombin(s: string): byte;
var
  i,
  e,
    b: byte;
begin
  b := 0;
  for i := 1 to 6 do
  begin
    e := 1 shl (6 - i);
    if s = '1' then
      b := b + e;
  end;
  frombin := b;
end;
//непосредственно кодирование
type
  tcoola = array[1..1] of byte;
  pcoola = ^tcoola;
procedure TForm1.Button1Click(Sender: TObject);
var
  inf: file of byte;
  ouf: textfile;
  uue: pcoola;
  b: array[1..4] of byte;
  bin,
    t: string;
  szf,
    oum,
    szl,
    szh,
    sxl,
    sxh,
    i,
    j: longint;
begin
{$I-}
  assignfile(inf, edit1.text); //входной файл
  reset(inf);
  szf := filesize(inf); //
  szh := (szf * 8) div 6; //
  if szf * 8 - szh * 6 = 0 then
    szl := 0
  else
    szl := 1; //
  getmem(uue, szh + szl); //выделение памяти
  oum := 1;
  while not (eof(inf)) do
  begin
    b[1] := 0;
    b[2] := 0;
    b[3] := 0;
    b[4] := 0;
    //чтение должно быть сделано посложнее,
    //дабы избежать "read beyond end of file"
    read(inf, b[1], b[2], b[3]);
    //читаем 3 байта из входного файла
    //и формируем "двоичную" строку
    bin := toanysys(b[1], 2) +
      toanysys(b[2], 2) +
      toanysys(b[3], 2);
    //разбиваем строку на куски по 6 бит и добавляем 32
    t := mid(bin, 19, 24);
    b[4] := frombin(t) + 32;
    t := mid(bin, 13, 18);
    b[3] := frombin(t) + 32;
    t := mid(bin, 07, 12);
    b[2] := frombin(t) + 32;
    t := mid(bin, 01, 06);
    b[1] := frombin(t) + 32;
    //запихиваем полученнные байты во временный массив
    uue[oum] := b[1];
    oum := oum + 1;
    uue[oum] := b[2];
    oum := oum + 1;
    uue[oum] := b[3];
    oum := oum + 1;
    uue[oum] := b[4];
    oum := oum + 1;
  end;
  //входной файл больше не нужен - закрываем его
  closefile(inf);
  //формируем выходной файл
  assignfile(ouf, edit2.text); //выходной файл
  rewrite(ouf);
  oum := 1;
  sxh := (szh + szl) div 60; //число строк в UUE файле
  sxl := (szh + szl) - sxh * 60;
  //заголовок UUE-файла
  writeln(ouf, 'begin 644 ' + extractfilename(edit1.text));
  //записываем строки в файл
  for i := 1 to sxh do
  begin
    write(ouf, 'M');
    // 'M' значит, что в строке 60 символов
    for j := 1 to 60 do
    begin
      write(ouf, chr(uue[oum]));
      oum := oum + 1;
    end;
    writeln(ouf);
  end;
  //записываем последнюю строку, которая
  //обычно короче 60 символов
  sxh := (sxl * 6) div 8;
  write(ouf, chr(sxh + 32));
  for i := 1 to sxl do
  begin
    write(ouf, chr(uue[oum]));
    oum := oum + 1;
  end;
  // "добиваем" строку незначащими символами
  for i := sxl + 1 to 60 do
    write(ouf, '`');
  //записываем последние строки файла
  writeln(ouf);
  writeln(ouf, '`');
  writeln(ouf, 'end');
  closefile(ouf);
  freemem(uue, szh + szl); //освобождаем память
  showmessage('DONE.'); //Готово. Забирайте!
end;
end.


1) Читаем из исходного хфайла 3 байта.
2) Разбиваем полyченные 24 бита (8x3=24) на 4 части, т.е. по 6 бит.
3) Добавляем к каждой части число 32 (десятичн.)
Пpимеp: Имеем тpи числа 234 12 76. Побитово бyдет так -
11101010 00001100 01001100 pазбиваем и полyчаем -
 111010  100000  110001  001100 добавляем 32 -
+100000 +100000 +100000 +100000
       
1011010 1000000 1010001  101100 или в бyквах -
   Z       @       Q       ,
Вот собственно и все. В UUE файле в пеpвой позиции стоит кол-во закодиpованных
символов + 32. Т.е. вся стpока содеpжит 61 символ. 1 символ идет на кол-во.
Остается 60 символов _кода_. Если подсчитать, то мы yвидим, что для полyчения
60
символов кода необходимо 45 исходных символов. Для полной стpоки в начале стоит
бyква "M", а ее ASCII код = 77. 45+32=77.
Записан
Уроки Phptoshop, Linux, Windows 7
   

 Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  



* Счетчики
Наша Кнопка

Powered by SMF 1.1.16 | SMF © 2006-2009, Simple Machines | Sitemap
SimplePortal 2.3 © 2008-2009, SimplePortal