Демонстрационная программа - исходные тексты (Delphi)
Демонстрационная программа - исполняемый файл (для запуска необходимы dll от PGPsdk)
PGP_SDK.dll, PGPsdkNL.dll, PGPsdkUI.dll
Иногда бывает нужно прикрутить к своей программе какое-нибудь шифрование. Для этих целей разработаны кучи алгоритмов шифрования, дешифрования, электронной подписи и т.п., основанных на различных математических аппаратах. Но этого мало – алгоритмы необходимо реализовать. Мы не будем делать этого сами, а возьмем готовую библиотеку PGPsdk. Я только повторю слова классиков [2] о том, что для реальных приложений использовать самодельные шифры не рекомендуется, если вы не являетесь экспертом и не уверены на 100 процентов в том, что делаете. Отговаривать же вас от разработки собственных шифров или реализации какого-либо стандарта не входит в задачи этой статьи, здесь пойдет речь о том, как быстро и правильно реализовать в своих приложениях защиту от посторонних глаз и, самое главное - не обмануться. В моем приложении уже использовалось работающее через командные (*.bat) файлы шифрование от PGP., что явилось весомым аргументом для выбора средства шифрования. Все работало, но меня это не устраивало – DOS-овская версия PGP (5.0) затрудняла установку программы, поддержку, и не имела некоторых полезных вещей, нужных для расширения системы в будущем. Но PGP привлекала бесплатностью для некоммерческих программ, генерацией произвольного количества ключей и своей широкой распространенностью.
Pretty Good Privacy (PGP) выпущено фирмой Phil's Pretty Good Software и является криптографической системой с высокой степенью секретности для операционных систем MS-DOS, Unix, VAX/VMS и других. PGP позволяет пользователям обмениваться файлами или сообщениями с использованием функций секретности, установлением подлинности, и высокой степенью удобства. Секретность означает, что прочесть сообщение сможет только тот, кому оно адресовано. Установление подлинности позволяет установить, что сообщение, полученное от какого-либо человека, было послано именно им. Нет необходимости использовать секретные каналы связи, что делает PGP простым в использовании программным обеспечением. Это связано с тем, что PGP базируется на мощной новой технологии, которая называется шифрованием с "открытым ключом".
Первое, что я сделал – сходил на torry.ru и был удивлен обилием библиотек и функций для разного рода шифрования. Функциональность их я проверять не стал, а остановился на компонентах PGP.
DOS-овская PGPComp, работающая по принципу запуска внешнего exe-файла, сразу отпала по простой причине – нужно будет устанавливать DOS-версию PGP (кроме того, библиотека работает только под Delphi 1 и 2). Вспомнил, что в мою любимую почтовую программу The Bat встроена поддержка PGP, зашел на ее сайт и скачал любезно предоставляемую библиотеку dklib.dll, . Но почему-то у меня ни один из примеров не пошел, а за отсутствием исходников, я не мог понять почему. Пробовал обратиться к автору – в ответ тишина, уже больше года он не отвечает. А неплохая библиотека, по крайней мере, судя по документации. Присутствует необходимый минимум функций для шифрования - расшифрования, проверки ключа, а сама библиотека весит не очень много – 184’832 Байт.
Меня не устроили эти библиотеки, и я отправился на http://www.pgpi.org/ в поисках истины. Там я нашел упоминание о библиотеке для разработчиков – PGPsdk.
28 октября 1997 г. PGP, Inc. объявила о поставке PGPsdk сторонним производителям программного обеспечения. PGPsdk - это средство разработки для программистов на С, позволяющее разработчикам программного обеспечения встраивать в свои приложения стойкие криптографические функции. Можно сказать, что в PGPsdk реализованы все функции пакета PGP, мало того - Win-версия PGP, начиная с 5.0, хранит криптографические функции в динамических библиотеках.
PGPsdk – это динамическая библиотека, состоящая из трех файлов [табл. 1]. Она поддерживает базовые криптографические алгоритмы (перечислены выше), гибкое управление ключами, сетевой интерфейс и др. (можно обойтись одной библиотекой - PGP_sdk.dll, если вы не будете использовать фирменный интерфейс пользователя от NAI и сетевые функции).
Скачайте архив с PGPsdk [7], (на момент написания статьи доступна версия 1.7.2, размер архива около 3-х мегабайт), разверните его и возьмите следующие файлы из каталога \Libraries\DLL\Release (табл. 1)
PGP_SDK.dll | для шифрования, управления ключами и т.д. |
---|---|
PGPsdkUI.dll | (UI= user interface) интерфейсные штучки, если нужно только шифровать/расшифровывать, этот файл необязателен. Но он очень полезен для ввода пароля, выбора получателей сообщений, генерации ключей и многого другого. |
PGPsdkNL.dll | (NL= network library) сетевая библиотека для работы с сервером ключей или для transport layer security. Сейчас мы ее рассматривать не будем, но в ближайшем будущем я попытаюсь ее описать. |
Распространять приложение придется с этими файлами, их следует поместить или в системный каталог, или в каталог приложения - главное, чтобы библиотека была доступна .
Для работы система предоставляет ряд низкоуровневых PGP API-функций. Заголовочные файлы этих функций на С поставляются вместе с пакетом и лежат в каталоге Headers. Если вы, как и я, пишете на Delphi, можете сами конвертировать их, а можете взять готовые тут [10]. Это проект по переводу заголовочных файлов С на любимый мною язык программирования. Занимается всем этим делом Стивен Хейлер (Steven R. Heller <srheller@oz.net>).
При переводе на Delphi была сохранена структура заголовочных файлов С. Все названия модулей аналогичны заголовкам С, за исключением того, что pgpEncode переименовано в pgpEncodePas из-за особенностей объявления в Delphi (нельзя, чтобы имя процедуры совпадало с названием модуля).
// Объявление используемых библиотек. uses // PGPsdk pgpEncodePas, pgpOptionList, pgpBase, pgpPubTypes, pgpUtilities, pgpKeys, pgpErrors, // always last pgpSdk; |
Единственная трудность, которая возникает на пути включения криптографии в приложение – это использование слишком уж низкоуровневых API-функций PGP. Чтобы сделать какую-нибудь операцию – будь то подсчет открытых ключей в связке или просто шифрование файлов, необходимо произвести множество действий. Применение PGP API в Delphi показано в листинге 2.
Листинг 2. Пример использования PGPsdk через PGP APIVar 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; |
При написании этого кода за основу взят пример из PGPsdk Users Guide [9].
Для обработки ошибок использована функция PGPCheckResult, позаимствованная у Стивена Хейлера. Она имеет два параметра – строку, через которую передается некоторое описание, и код выполнения функции PGP API. Если передан код ошибки, эта функция генерирует исключение, избавляя от долгих ручных проверок.
Листинг 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 осложнено [9, раздел FAQ]. Сам Стивен предложил мне добавить к проекту еще одну dll, забыть про PGP API, и использовать облегченную модель вызова криптографических функций.
Я принял решение избавиться от DLL и присоединить весь код к ехе-файлу. Сказано - сделано.
Создадим каталог PGPsdk и скопируем туда файлы DELPHI PGP API - pgp*.pas и spgp*.pas. Удалим в файлах spgp*.pas "stdcall;export;" (получившиеся в итоге файлы можно взять в исходных текстах демонстрационной программы). Теперь нужно добавить описание используемых библиотек:
uses // PGPsdk pgpEncodePas, pgpOptionList, pgpBase, pgpPubTypes, pgpUtilities, pgpKeys, pgpErrors, // SPGP spgpGlobals, spgpEncrypt, spgpKeyUtil, spgpUtil, spgpKeyMan, spgpPreferences, spgpKeyProp, spgpKeyIO, spgpKeyGen, spgpMisc, spgpUIDialogs, // always last pgpSdk; |
Первое, что мы попробуем сделать - это зашифровать и подписать произвольный файл, и получить результат в текстовом виде (ASCII). Следует отметить, что 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], становится ясно, насколько проще использовать SPGP.
Для работы библиотеке необходимо знать, где лежат файлы с ключами (pubring.prk и secring.prk). Настройки PGP API хранятся в файле PGPsdk.dat (он находится в каталоге Windows). Для работы с этим файлом предназначены следующие функции:
spgpGetPreferences(Prefs: pPreferenceRec; Flags: Longint):LongInt; spgpSetPreferences(Prefs: pPreferenceRec; Flags: Longint):LongInt; |
для чтения и сохранения настроек соответственно.
Замечу, что это не единственный способ – PGP API позволяет напрямую указывать, где расположены ключи. Но тогда придется отказаться от использования SPGP или править исходный код SPGP.
Код, приведенный ниже, показывает, как заполнить список LVKeys именами и шестнадцатеричными 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)+'KEYS\pubring.pgp'); StrPCopy(P.PrivateKeyring, ExtractFilePath(Application.ExeName)+'KEYS\secring.pgp'); StrPCopy(P.RandomSeedFile, ExtractFilePath(Application.ExeName)+'KEYS\randseed.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; |
Не всегда можно предположить, какой размер будет иметь зашифрованный текст. Функции, проводящие преобразование, требуют выделить память под него заранее, и, если памяти не хватает, возвращают сообщение об ошибке. Опытным путем мне удалось установить формулу вычисления размера блока:
outBufLen := inBufLen*5; if(outBufLen<10000) then outBufLen:=10000; outBufRef := StrAlloc(outBufLen); |
PGP_sdkUI.dll – это библиотека пользовательских интерфейсов от Network Associates, включающая такие же диалоги ввода пароля, генерации ключей и выбора получателей сообщений, как и у фирменного пакета PGP.
Функции криптографии можно применить в самых разных приложениях, а PGP позволяет делать это быстро, надежно, открыто и самое главное – переносимо. Но я могу посоветовать еще одно применение - это защита программ от несанкционированного копирования - зашить открытый ключ в exe-файл, и рассылать секретный нужным людям.