Источник: rsdn.ru/article/crypto/pgpsdk1.xml

R U S S I A N   S O F T W A R E   D E V E L O P E R   N E T W O R K
 

Использование PGP SDK
Часть 1. Шифрование с открытым ключом.

Введение

“Q: Вот говорят иногда "симметричные шифры", "криптография с открытым ключом". Поясните, что это за разделение?

A1: Симметричные шифры (криптосистемы) - это такие шифры, в которых для зашифрования и расшифрования информации используется один и тот же ключ. В несимметричных системах (системах с открытым ключом) для зашифрования используется открытый (публичный) ключ, известный всем и каждому, а для расшифрования - секретный (личный, закрытый) ключ, известный только получателю.”

RU.CRYPT FAQ /Часто задаваемые вопросы конференции RU.CRYPT/

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

PGP для Персональной Приватности. Руководство пользователя.

"Q: What operating systems does PGPsdk support?"

A:

PGP Software Developer's Kit. User's Guide.

Алгоритмы, реализованные в PGP SDK:

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

Установка PGP SDK

В PGP SDK входят заголовочные файлы, библиотеки, документация с описанием функций и тестовые примеры. Установка заключается в копировании архива PGP SDK, распаковки его в отдельный каталог и прописывании путей к каталогам Headers (Include files) и Libraries\DLL\Release (Library files) в настройках Visual Studio (Tools - Options - Projects - VC++ Directories).

ПРИМЕЧАНИЕ

К сожалению, разработчики SDK дали одинаковые имена debug и release – версиям dll PGP SDK, поэтому приходится копировать debug версии dll в каталог с debug – версией каждой разрабатываемой программы, либо настраивать все проекты так, чтобы исполняемые файлы создавались в одном каталоге с debug версией PGP_SDK.dll.

Вот несколько адресов, по которым можно скачать PGP SDK:

http://www.pgpi.org/products/sdk/c++/pgpsdk/

ftp://ftp.hacktic.nl/pub/crypto/pgp/pgpsdk/

ftp://ftp.no.pgpi.org/pub/pgp/sdk/

При компиляции демонстрационной программы использовался PGP SDK версии 1.7.2.

Если вам встретятся затруднения в процессе использования функций PGP SDK вам прямая дорога в исходники одной из версий PGP.

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

Функции PGP SDK, используемые при шифровании с открытым ключом

Рассмотрим вкратце функции участвующие в процессе шифрования данных с помощью PGP SDK. Общие сведения, необходимые для понимания работы некоторых функций будут даваться по ходу дела. Самой первой функцией, вызываемой при работе с PGP SDK должна быть

PGPsdkInit

PGPError PGPsdkInit( void );

Соответственно завершается работа с PGP SDK вызовом

PGPsdkCleanup

PGPError PGPsdkCleanup( void );

Возвращаемое значение функций PGP SDK имеет тип PGPError, и может быть проверено при помощи макросов

IsPGPError и IsntPGPError

#define IsPGPError( err )            ( (err) != kPGPError_NoErr )
#define IsntPGPError( err )          ( (err) == kPGPError_NoErr )

Для получения текстового описания ошибки по ее коду предназначена

PGPGetErrorString

PGPError PGPGetErrorString(
             PGPError theErrorCode,
             PGPsize availLength,
             char *theErrorText );

theErrorCode Код ошибки для анализа
availLength Размер передаваемого в функцию буфера
theErrorText Указатель на буфер, в котором будет размещен текст ошибки

Собственно шифрование данных с открытым ключом осуществляется функцией

PGPEncode

PGPError PGPEncode(
            PGPContextRef pgpContext,
            PGPOptionListRef firstOption,
            ...,
            PGPOLastOption() );

pgpContext Используемый PGP-контекст. Фактически представляет собой совокупность глобальных переменных, совместно используемых функциями PGP SDK.
firstOption Первая опция шифрования
... Список других необходимых опций
PGPOLastOption() Последняя опция в списке, показывает, что список опций закончен

Используемый в качестве одного из параметров PGP-контекст создается после успешного вызова PGPsdkInit при помощи функции

PGPNewContext

PGPError PGPNewContext(
           PGPUInt32 clientAPIVersion,
           PGPContextRef *pgpContext );

clientAPIVersion Версия клиентского API PGP SDK. Чтобы это не значило : -) при вызове функции этот параметр должен быть равен kPGPsdkAPIVersion.
pgpContext Указатель на создаваемый PGP контекст.

По окончании работы с функциями SDK, но до вызова PGPsdkCleanup, нужно вызвать

PGPFreeContext

PGPError PGPFreeContext( PGPContextRef pgpContext );

pgpContext Освобождаемый PGP контекст.
ПРЕДУПРЕЖДЕНИЕ

К моменту вызова этой функции должны быть освобождены все ресурсы, созданные при работе с функциями SDK. Если вы забудете это сделать, debug версии библиотек PGP SDK напомнят вам об этом одним из своих ASSERT-ов.

Для передачи в PGPEncode и в PGPImportKeySet (см. ниже) нам понадобятся опции, создаваемые следующими функциями –

PGPOArmorOutput

PGPOptionListRef PGPOArmorOutput(
          PGPContextRef pgpContext,
          PGPBoolean armorOutput );

pgpContext Используемый PGP-контекст.
armorOutput Устанавливается в TRUE, если нужно получить выходные данные в виде ASCII.

Без явного указания этой опции выходные данные создаются в бинарном формате.

PGPODataIsASCII

PGPOptionListRef PGPODataIsASCII(
         PGPContextRef pgpContext,
         PGPBoolean dataIsASCII );

pgpContext Используемый PGP-контекст.
dataIsASCII Установите в TRUE, если входные данные - в виде ASCII.

Если при зашифровании текста была установлена данная опция, при его расшифровке символы перевода строки <CR><LF> будут оттранслированы в символы соответствующего назначения для платформы, на которой происходит расшифровка. (см. перечень платформ во Введении)

PGPOInputFile

PGPOptionListRef PGPOInputFile(
         PGPContextRef pgpContext,
         PGPFileSpecRef fileSpec );

pgpContext Используемый PGP-контекст.
fileSpec Заранее подготовленная файловая спецификация

Опция указывает, что входные данные функция должна будет брать из файла.

PGPOOutputFile

PGPOptionListRef PGPOOutputFile(
         PGPContextRef pgpContext,
         PGPFileSpecRef fileSpec );

pgpContext Используемый PGP-контекст.
fileSpec Заранее подготовленная файловая спецификация

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

Файловую спецификацию, необходимую для передачи в PGPOInputFile и PGPOOutputFile можно создать вызовом функции

PGPNewFileSpecFromFullPath

PGPError PGPNewFileSpecFromFullPath(
         PGPContextRef pgpContext,
         char const *pathname,
         PGPFileSpecRef *fileRef );

pgpContext Используемый PGP-контекст.
pathname Имя файла, для которого создается спецификация
fileRef Указатель на создаваемую спецификацию

По окончании использования (до вызова PGPFreeContext) файловая спецификация должна быть освобождена вызовом

PGPFreeFileSpec

PGPError PGPFreeFileSpec( PGPFileSpecRef fileSpecRef );

fileSpecRef Освобождаемая файловая спецификация.

PGPOInputBuffer

PGPOptionListRef PGPOInputBuffer(
         PGPContextRef pgpContext,
         void const *inBuf,
         PGPSize inBufLength );

pgpContext Используемый PGP-контекст.
inBuf Указатель на буфер с данными
inBufLength Размер передаваемого буфера

Опция указывает, что входные данные функция должна будет брать из буфера.

PGPOOutputBuffer

PGPOptionListRef PGPOOutputBuffer(
         PGPContextRef pgpContext,
         void *outBuf,
         PGPSize outBufLength,
         PGPSize *outBufDataLength );

pgpContext Используемый PGP-контекст.
outBuf Указатель на буфер, в который будут помещены данные
outBufLength Размер передаваемого буфера
outBufDataLength Размер данных, которые помещены (должны быть помещены) в буфер. Заполняется вызываемой функцией.

Если размер данных, которые нужно поместить в буфер окажется больше, чем размер переданного буфера, PGPEncode вернет ошибку kPGPError_OutputBufferTooSmall, а в outBufDataLength будет помещен требуемый размер буфера. Можно либо эмпирически подсчитывать (с запасом) размер выходного буфера на основании размера входных данных, либо вызывать PGPEncode минимум 2 раза - сначала чтобы получить размер выходных данных, а затем чтобы получить собственно требуемые данные:

    // начальный размер буффера
    // заведомо маленький, после первого вызова PGPEncode в 
    // параметре PGPOOutputBuffer будет передано действительно требуемое 
    // значение размера буфера
    // ( 0 в качестве начального значения не проходит, поэтому 1 )
    BuffSize = 1;
    // собственно шифрование
    do
    {
        delete[] OutData;
        // Выделенную здесь память надо освобождать по окончании использования
        // выходного буфера
        OutData = new BYTE[ BuffSize ];

        err = PGPEncode( m_context,
                         // входной буфер
                         PGPOInputBuffer( m_context,
                                          ( void* ) inData,
                                          ( PGPSize ) dwDataSize ),
                         // выходной буфер
                         PGPOOutputBuffer( m_context,
                                           ( void* ) OutData,
                                           ( PGPSize ) BuffSize,
                                           ( PGPSize *) &BuffSize ),
                         // шифровать на всех ключах из набора
                         PGPOEncryptToKeySet( m_context, pubKeySet ),
                         // и предварительно сформированный при
                         // инициализации список опций
                         m_optsEncode,
                         // больше опций не будет
                         PGPOLastOption( m_context ) );
    }
    while ( err == kPGPError_OutputBufferTooSmall );
ПРИМЕЧАНИЕ

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

PGPOEncryptToKeySet

PGPOptionListRef PGPOEncryptToKeySet(
         PGPContextRef pgpContext,
         PGPKeySetRef keySet );

pgpContext Используемый PGP-контекст.
keySet Набор ключей шифрования.

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

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

Ключи можно импортировать в набор из файла или буфера в памяти при помощи функции

PGPImportKeySet

PGPError PGPImportKeySet(
            PGPContextRef pgpContext,
            PGPKeySetRef *keySet,
            PGPOptionListRef firstOption,
            ...,
            PGPOLastOption() );

pgpContext Используемый PGP-контекст.
KeySet Указатель на набор, в который нужно импортировать ключи
firstOption Первая опция настройки импорта
... Список других необходимых опций
PGPOLastOption() Последняя опция в списке, показывает, что список опций закончен

Для импорта ключей из файла нужно указывать опцию PGPOInputFile, а для импорта из буфера – PGPOInputBuffer.

По окончании использования (до вызова PGPFreeContext) набор ключей должен быть освобожден вызовом функции

PGPFreeKeySet

PGPError PGPFreeKeySet( PGPKeySetRef keySet );

KeySet Освобождаемый набор ключей.

PGPOVersionString

PGPOptionListRef PGPOVersionString(
         PGPContextRef pgpContext,
         char const *versionString );

pgpContext Используемый PGP-контекст.
versionString Строка с информацией о версии, которая будет включена в заголовок зашифрованного сообщения

PGPOCommentString

PGPOptionListRef PGPOCommentString(
       PGPContextRef pgpContext,
       char const * commentString );

pgpContext Используемый PGP-контекст.
commentString Строка-комментарий, которая будет включена в заголовок зашифрованного сообщения

PGPOCipherAlgorithm

PGPOptionListRef PGPOCipherAlgorithm(
         PGPContextRef pgpContext,
         PGPCipherAlgorithm algID );

pgpContext Используемый PGP-контекст.
algID Идентификатор алгоритма, используемого для симметричного шифрования.

В качестве идентификатора алгоритма algID могут применяться значения из следующего перечисления (pgpPubTypes.h):

enum PGPCipherAlgorithm_
{
    /* do NOT change these values */
    kPGPCipherAlgorithm_None    = 0,
    kPGPCipherAlgorithm_IDEA    = 1,
    kPGPCipherAlgorithm_3DES    = 2,
    kPGPCipherAlgorithm_CAST5   = 3,
    
    kPGPCipherAlgorithm_First           = kPGPCipherAlgorithm_IDEA,
    kPGPCipherAlgorithm_Last            = kPGPCipherAlgorithm_CAST5,

    PGP_ENUM_FORCE( PGPCipherAlgorithm_ )
};
PGPENUM_TYPEDEF( PGPCipherAlgorithm_, PGPCipherAlgorithm );
ПРИМЕЧАНИЕ

Так как алгоритмы шифрования с открытым ключом значительно медленнее алгоритмов для обычного, симметричного шифрования, при использовании высокоуровневой функции PGPEncode открытым ключом шифруется только случайный сеансовый ключ, все остальные данные шифруются этим сеансовым ключом одним из симметричных алгоритмов – IDEA, 3DES или CAST5. Сказанное можно пояснить следующей картинкой из "Руководства пользователя PGP":


ПРИМЕЧАНИЕ

Алгоритм для несимметричного шифрования, определяется используемым открытым ключом.

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

PGPBuildOptionList

PGPError PGPBuildOptionList(
            PGPContextRef pgpContext,
            PGPOptionListRef *outList,
            PGPOptionListRef firstOption,
            ...,
            PGPOLastOption() );

pgpContext Используемый PGP-контекст.
OutList Указатель на создаваемый список опций
firstOption Первая опция в списке
... Список других необходимых опций
PGPOLastOption() Последняя опция в списке, показывает, что список опций закончен

По окончании использования (до вызова PGPFreeContext) список опций нужно очистить вызовом

PGPFreeOptionList

PGPError PGPFreeOptionList(
            PGPOptionListRef optionList );

optionList Существующий список опций, который нужно очистить.

ПРИМЕЧАНИЕ

Выше перечислены только функции и опции, использованные в CSimplePGP. Полный список возможностей, предоставляемых PGP SDK для шифрования данных гораздо шире, ознакомиться с ним можно в Reference Guide PGP SDK.

Класс CSimplePGP

Назначение

Использование PGP SDK напрямую не представляет особой сложности, однако весьма неудобно, т.к. для выполнения какой-либо законченной операции, например, шифрования файла, нужно последовательно выполнить с десяток вызовов функций SDK – инициализацию, подготовку ключей, файлов, выбор необходимых опций, определяющих форматы входящих и исходящих данных, используемые алгоритмы и т.д., а потом еще и провести очистку использованных ресурсов. Напрашивается создание класса-обертки, который был бы прост в использовании и в то же время закрывал на 90% наиболее часто используемые операции.

На данном этапе в классе реализованы только функции шифрования данных:

simplepgp.h

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

Использование

Использование CSimplePGP в программе предельно просто:

#include "simplepgp.h"

CSimplePGP pgp;

// инициализация PGP
if ( !pgp.Init() )
    ::MessageBox( this->m_hWnd, pgp.GetErrDesc(), pgp.GetErrPlace(), MB_OK | MB_ICONSTOP );

// добавляем комментарий
if ( !pgp.SetComment( _T("Ключ и данные - из файлов") ) )
    ::MessageBox( this->m_hWnd, pgp.GetErrDesc(), pgp.GetErrPlace(), MB_OK | MB_ICONSTOP );

// для симметричного шифрования использовать 3DES
if ( !pgp.SetCipherAlgorithm( kPGPCipherAlgorithm_3DES )
    ::MessageBox( this->m_hWnd, pgp.GetErrDesc(), pgp.GetErrPlace(), MB_OK | MB_ICONSTOP );

// шифрование файла sInFileName на ключе (-ах) из файла sPubKeyFileName
if ( !pgp.EncodeFile2File( sInFileName, sOutFileName,  sPubKeyFileName ) )
    ::MessageBox( this->m_hWnd, pgp.GetErrDesc(), pgp.GetErrPlace(), MB_OK | MB_ICONSTOP );

// меняем комментарий
if ( !pgp.SetComment( _T("Ключ из ресурсов, данные - из поля ввода") ) )
    ::MessageBox( this->m_hWnd, pgp.GetErrDesc(), pgp.GetErrPlace(), MB_OK | MB_ICONSTOP );

// шифрование данных из строки CString sInData с записью результата в буфер
// открытый ключ берется из ресурса "PGPTESTPUBKEY" типа "PGPKEYS"
LPBYTE OUTBUFF = NULL;
DWORD BUFFSIZE = 0; 
if ( !pgp.EncodeBuff2Buff( ( LPCBYTE ) ( LPCTSTR ) sInData,
                           ( DWORD ) sInData.GetLength(),
                           OUTBUFF,
                           BUFFSIZE,
                           _T( "PGPTESTPUBKEY" ),
                           _T( "PGPKEYS" ) ) )
    ::MessageBox( this->m_hWnd, pgp.GetErrDesc(), pgp.GetErrPlace(), MB_OK | MB_ICONSTOP );
else
{
     // Используем данные из буфера OUTBUFF
     ...

     ...
}
delete[] OUTBUFF;

Чтобы добавить ключевой файл в ресурсы программы нужно сначала создать New Custom Resource указав тип ресурса в виде произвольной строки в кавычках, а затем импортировать ключевой файл в ресурсы программы выбрав при добавлении указанный выше тип ресурса и задав имя ресурса также в виде произвольной строки в кавычках.

Подробности реализации

Инициализация

Нужно провести собственно инициализацию PGP SDK, создать pgp-контекст и сформировать лист опций шифрования, общий для всех режимов.

CSimplePGP::Init()

Настройка опций шифрования

Изменение доступных опций (строки комментария, версии, форматов входной и выходной информации выполняется однотипно, соответствующей функцией Setxxxxxx, например, изменение комментария:

CSimplePGP::SetComment()
CSimplePGP::ReBuildEncodeOptionList()

Шифрование

Для примера возьмем функцию шифрования данных из буфера в памяти с выводом результатов в файл:

CSimplePGP::EncodeBuff2File()

Функция ImportKeyFromResorce() создает набор ключей, используемый в PGPEncode() импортируя его из ресурсов программы:

CSimplePGP::ImportKeyFromResorce()

Есть также аналогичная функция ImportKeyFromFile() для загрузки ключей из файла.

Деинициализация при завершении работы

Деинициализация включает в себя очистку списка опций, освобождение pgp-контекста и вызов PGPsdkCleanup(). Все эти действия вынесены в деструктор класса:

CSimplePGP::~CSimplePGP

Демонстрационная программа PGPTest1

Программа позволяет протестировать возможности PGP SDK по шифрованию данных, реализованные в классе CSimplePGP.


Задавая положение переключателей и имена файлов можно протестировать разные варианты получения входных данных и ключевой информации, а также варианты вывода зашифрованной информации. На поле “”Закрытый” ключ (для подписи)” пока не обращайте внимания, в шифровании он не участвует. В подкаталоге test и в ресурсах программы находится тестовый ключ из комплекта PGP SDK. Пароль для расшифрования данных для этого ключа – test.

Если в качестве места для размещения выходных данных выбрать буфер в памяти, то после нажатия кнопки “Зашифровать” на экране должно появится окно для просмотра содержимого буфера:


Если зашифрованная информация выводится в файл на диске, откроется окно программы Блокнот с загруженным выходным файлом. Сняв галочку "Вывод в формате ASCII" получим файл в бинарном формате:


Расшифровать полученный файл можно, импортировав в программу PGP закрытый ключ из файла test\sectest.asc. Выбрав из контекстного меню файла пункт Decrypt & Verify (при установленной программе PGP) и введя пароль test получим расшифрованный файл:


Продолжение следует …

На очереди расшифровка того, что мы зашифровали, электронная подпись и ее проверка, генерация ключей.


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