Использование PGP SDK
Часть 3. Цифровая подпись.
Опубликовано: 22.06.2003
Исправлено: 13.03.2005
Версия текста: 1.1
Класс CSimplePGP – исходные тексты (Win API, STL)
Демонстрационный проект (VC7.1, WTL7)
Исполняемый файл демонстрационной программы – PGPtest3.exe (для запуска необходимы PGP_SDK.dll, PGPsdkUI.dll и PGPsdkNL.dll)
PGP_SDK.dll
PGPsdkUI.dll
PGPsdkNL.dll
Функции PGP SDK, используемые при наложении цифровой подписи
Собственно наложение цифровой подписи осуществляется функцией PGPEncode, описанной в первой части. В дополнение к уже описанным ранее в данной операции участвуют следующие опции:
PGPOClearSign
PGPOptionListRef PGPOClearSign(
PGPContextRef pgpContext,
PGPBoolean clearSign );
|
pgpContext | Используемый PGP-контекст |
clearSign | Устанавливается в TRUE, если не нужно шифровать открытый текст, на который накладывается подпись. |
Данную опцию можно устанавливать только при наложении подписи на текстовую информацию, т.к. при установке данной настройки в TRUE автоматически устанавливаются в TRUE опции PGPOArmorOutput и PGPODataIsASCII. Подписываемый текст и собственно подпись находятся в одном файле (буфере памяти), разделенные соответствующими текстовыми метками:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Сим заверяем вас, глубокоуважаемый Заказчик, в нашем совершеннейшем
почтении и безграничном уважении к Вам и нуждам Вашего Бизнеса. По поводу
некоторой задержки сроков реализации вашего Заказа, а также превышения
предполагаемой суммы затрат на 143%, нижайше просим перечитать пункт 4.7.2
Типового Договора На Разработку Программного Решения, "Риски проекта".
-----BEGIN PGP SIGNATURE-----
Version: CSimplePGP v3.0
Comment: no comment
iQA/AwUBPvPF3sJmEIGmukqAEQJJIACgnQA6Ydj8aGzR5Aln6nUVQ0skZakAmgJr
qBv2VMWGGjjuF6qYKTo6URTy
=ZOZa
-----END PGP SIGNATURE-----
|
PGPODetachedSig
PGPOptionListRef PGPODetachedSig(
PGPContextRef pgpContext,
PGPOptionListRef firstOption,
...,
PGPOLastOption() );
|
pgpContext | Используемый PGP-контекст |
firstOption | Первая дополнительная опция |
... | Список других необходимых опций |
PGPOLastOption() | Последняя опция в списке, показывает, что список опций закончен |
При передаче в PGPEncode указывает на необходимость создания цифровой подписи в отдельном файле, при этом не требуется передавать никаких дополнительных опций в PGPODetachedSig. При передаче в PGPDecode указывает на источник, в котором содержится цифровая подпись для верификации. В этом случае необходимо передать в PGPODetachedSig одну из опций PGPOInputBuffer, PGPOInputFile или PGPOInputFileFSSpec для связи с источником цифровой подписи.
При наложении цифровой подписи с опцией PGPODetachedSig на основе подписываемой информации создается хеш, который затем шифруется секретным ключом (с запросом пароля секретного ключа), полученная таким образом подпись помещается в отдельный файл (либо буфер). Если дополнительно задана опция PGPOArmorOutput (выходные данные в виде ASCII), подпись будет иметь следующий вид:
-----BEGIN PGP SIGNATURE-----
Version: CSimplePGP v3.0
Comment: no comment
iQA/AwUBPvO98sJmEIGmukqAEQJFGQCeMUeux9Ttu0q28pCcnlUj6VV/iSsAn2Ox
8FQcX9zKRf4vbOpAqpQILVic
=PC/t
-----END PGP SIGNATURE-----
|
Если PGPOArmorOutput не установлена, подпись создается в бинарном формате.
При верификации подписи она будет расшифрована открытым ключом подписавшего и извлеченное из нее значение хеша сравнено с вновь рассчитанным для подписанной информации.
Если при наложении цифровой подписи опция PGPODetachedSig не была установлена, секретным ключом подписывающего шифруется вся подписываемая информация, соответственно результат будет иметь вид, близкий к следующему:
-----BEGIN PGP MESSAGE-----
Version: CSimplePGP v3.0
Comment: no comment
owEljzFLXEEQx98paU6OGMRGEAZJiMjjwalBK1NoJWnyASKkUBDExisstVOb+AWC
sdFGm5Sp4iFYiZekTTG3d/Pe3t7u3r5d8IQz6NylWWZ3Zn77+x9G74dfFJbCz7Hq
xuj+2Y/VvVdD03+ib3/L0+H12vjS+eKxWj052rn4N34afZ38tDDxbuXgy+eTmceV
m+/LH99+eHlZ2K+MbG5vbG6tl5PKbqUQRREA/JIaMhTknkiDwDyGRjvUrbJBYIqk
ey24QYVZV6oYBBh84Lnc8sIDGUMtvpaKHdv1ZKQECXXKGg6N7Bqroc8YvCuoouau
CWmTiypTGhauZGYoxwTubJ8hbDOAIWW9dbbFVk1yqZKQO9YR4AjbMsN7xon/+wMx
jAHBo0qJGY5Ej73kE3DZ7Ng2NjgDw/KgdY+R3qHnEFCen3sTg5EpcgIetrns5yBH
XenRP0InGOVhPllIZuG37Mvxh9d8COsQbhFqmDmsW68C3DnLmbU23C4VazQwiGGq
JnPWZzgpj1NJqfgM
=kuyX
-----END PGP MESSAGE-----
|
Если при верификации удастся расшифровать это сообщение открытым ключом подписавшего, значит информация дошла до нас в неизмененном виде.
PGPOSignWithKey
PGPOptionListRef PGPOSignWithKey(
PGPContextRef pgpContext,
PGPKeyRef sigKey,
PGPOptionListRef firstOption,
...,
PGPOLastOption() );
|
pgpContext | Используемый PGP-контекст |
sigKey | Kлюч для подписи |
firstOption | Первая дополнительная опция |
... | Список других необходимых опций |
PGPOLastOption() | Последняя опция в списке, показывает, что список опций закончен |
В списке опций шифрования, требуемых для PGPOSignWithKey должна быть передана одна из опций PGPOPasskeyBuffer, PGPOPassphrase, PGPOPassphraseBuffer, задающих пароль для секретного ключа (см. вторую часть статьи).
PGPNewUserIDStringFilter
PGPError PGPNewUserIDStringFilter(
PGPContextRef pgpContext,
char const *userIDString,
PGPMatchCriterion match,
PGPFilterRef *outFilter );
|
pgpContext | Используемый PGP-контекст |
userIDString | Строка-идентификатор хозяина ключа |
match | Критерий выборки |
outFilter | Поле для получения созданного фильтра |
В отличии от операций шифрования, которые выполняются для набора ключей, наложение цифровой подписи должно производится только с одним определенным ключом. Для выбора этого ключа из набора на него нужно наложить фильтр по необходимому критерию. PGP SDK предоставляет около трех десятков функций для задания фильтра по дате создания ключа, номеру ключа, алгоритму шифрования, email хозяина ключа и т.д. Функция PGPNewUserIDStringFilter создает фильтр по строке – идентификатору хозяина ключа. В качестве критерия выборки PGPMatchCriterion могут использоваться значения из следующего перечисления (pgpKeys.h):
enum PGPMatchCriterion_
{
kPGPMatchDefault = 1,
kPGPMatchEqual = 1,
kPGPMatchGreaterOrEqual = 2,
kPGPMatchLessOrEqual = 3,
kPGPMatchSubString = 4,
PGP_ENUM_FORCE( PGPMatchCriterion_ )
};
PGPENUM_TYPEDEF( PGPMatchCriterion_, PGPMatchCriterion );
|
По окончании использования (до вызова PGPFreeContext) созданный фильтр должен быть освобожден вызовом функции
PGPFreeFilter
PGPError PGPFreeFilter( PGPFilterRef filter );
|
filter | Освобождаемый фильтр |
PGPFilterKeySet
PGPError PGPFilterKeySet(
PGPKeySetRef origSet,
PGPFilterRef filter,
PGPKeySetRef *resultSet );
|
origSet | Исходный набор ключей |
filter | Фильтр, созданый вызовом одной из функций PGPNewxxxxxxxFilter |
resultSet | Результирующий набор ключей – после применения фильтра |
Функция предназначена для получения набора ключей, отобранных по условию заданному в накладываемом фильтре.
PGPOrderKeySet
PGPError PGPOrderKeySet(
PGPKeySetRef keySet,
PGPKeyOrdering order,
PGPKeyListRef *keyList );
|
keySet | Набор ключей для сортировки |
order | Критерий сортировки |
keyList | Результат операции – отсортированный список ключей |
Функция упорядочивает заданный набор ключей согласно заданному критерию сортировки. Критерий сортировки задается указанием одного из следующих параметров (pgpKeys.h):
enum PGPKeyOrdering_
{
kPGPInvalidOrdering = 0,
kPGPAnyOrdering = 1,
kPGPUserIDOrdering = 2,
kPGPReverseUserIDOrdering = 3,
kPGPKeyIDOrdering = 4,
kPGPReverseKeyIDOrdering = 5,
kPGPValidityOrdering = 6,
kPGPReverseValidityOrdering = 7,
kPGPTrustOrdering = 8,
kPGPReverseTrustOrdering = 9,
kPGPEncryptKeySizeOrdering = 10,
kPGPReverseEncryptKeySizeOrdering = 11,
kPGPSigKeySizeOrdering = 12,
kPGPReverseSigKeySizeOrdering = 13,
kPGPCreationOrdering = 14,
kPGPReverseCreationOrdering = 15,
kPGPExpirationOrdering = 16,
kPGPReverseExpirationOrdering = 17,
PGP_ENUM_FORCE( PGPKeyOrdering_ )
};
PGPENUM_TYPEDEF( PGPKeyOrdering_, PGPKeyOrdering );
|
В результате вызова функции создается список ключей с доступом через итератор.
По окончании использования (до вызова PGPFreeContext) созданный список ключей должен быть освобожден вызовом функции
PGPFreeKeyList
PGPError PGPFreeKeyList( PGPKeyListRef keySet );
|
keySet | Освобождаемый список ключей |
PGPNewKeyIter
PGPError PGPNewKeyIter(
PGPKeyListRef keySet,
PGPKeyIterRef *keyIter );
|
keySet | Список ключей, для которого создается итератор |
keyIter | Созданный итератор для доступа к списку ключей |
Функция создает итератор для получения очередного ключа из списка.
По окончании использования (до вызова PGPFreeContext) созданный итератор должен быть освобожден вызовом функции
PGPFreeKeyIter
PGPError PGPFreeKeyIter( PGPKeyIterRef iter );
|
iter | Освобождаемый итератор |
PGPKeyIterNext
PGPError PGPKeyIterNext(
PGPKeyIterRef iter,
PGPKeyRef *key );
|
iter | Итератор для доступа к списку ключей |
key | Очередной ключ в списке |
Функция предназначена для получения очередного ключа из списка ключей.
PGPOHashAlgorithm
PGPOptionListRef PGPOHashAlgorithm(
PGPContextRef pgpContext,
PGPHashAlgorithm algID );
|
pgpContext | Используемый PGP-контекст |
algID | Идентификатор алгоритма хеширования |
Данная опция служит для выбора алгоритма хеширования, используемого при цифровой подписи.
В качестве идентификатора алгоритма используются значения из следующего перечисления (pgpPubTypes.h):
enum PGPHashAlgorithm_
{
kPGPHashAlgorithm_Invalid = 0,
kPGPHashAlgorithm_MD5 = 1,
kPGPHashAlgorithm_SHA = 2,
kPGPHashAlgorithm_RIPEMD160 = 3,
kPGPHashAlgorithm_First = kPGPHashAlgorithm_MD5,
kPGPHashAlgorithm_Last = kPGPHashAlgorithm_RIPEMD160,
PGP_ENUM_FORCE( PGPHashAlgorithm_ )
};
PGPENUM_TYPEDEF( PGPHashAlgorithm_, PGPHashAlgorithm );
|
Данная опция не действует при подписи DSS ключом (DSS - Digital Signature Standard от NIST (National Institute for Standards and Technology USA)), в этом случае всегда используется SHA-1.
|
Поддержка операции наложения цифровой подписи в классе CSimplePGP
Для поддержки операций наложения цифровой подписи в класс CSimplePGP добавлены следующие члены:
public:
BOOL SetClearSigning ( BOOL bClearsign );
BOOL SetDetachedSig ( BOOL bDetachedSig );
void SetSignerIDstring ( string signerID );
BOOL SetHashAlgorithm( PGPHashAlgorithm hashID );
BOOL SignFile2File( LPCTSTR inFileName,
LPCTSTR outFileName,
LPCTSTR keyFileName );
BOOL SignFile2File( LPCTSTR inFileName,
LPCTSTR outFileName,
LPCTSTR resourceName,
LPCTSTR resourceType );
BOOL SignBuff2File( const VOID* inData,
DWORD dwDataSize,
LPCTSTR outFileName,
LPCTSTR keyFileName );
BOOL SignBuff2File( const VOID* inData,
DWORD dwDataSize,
LPCTSTR outFileName,
LPCTSTR resourceName,
LPCTSTR resourceType );
BOOL SignFile2Buff( LPCTSTR inFileName,
LPBYTE& OutData,
DWORD& BuffSize,
LPCTSTR keyFileName );
BOOL SignFile2Buff( LPCTSTR inFileName,
LPBYTE& OutData,
DWORD& BuffSize,
LPCTSTR resourceName,
LPCTSTR resourceType );
BOOL SignBuff2Buff( const VOID* inData,
DWORD dwDataSize,
LPBYTE& OutData,
DWORD& BuffSize,
LPCTSTR keyFileName );
BOOL SignBuff2Buff( const VOID* inData,
DWORD dwDataSize,
LPBYTE& OutData,
DWORD& BuffSize,
LPCTSTR resourceName,
LPCTSTR resourceType );
|
В качестве примера реализации операции цифровой подписи рассмотрим создание в буфере подписи для файла с данными:
BOOL CSimplePGP::SignFile2Buff( LPCTSTR inFileName,
LPBYTE& OutData,
DWORD& BuffSize,
LPCTSTR resourceName,
LPCTSTR resourceType )
{
if ( !m_bIsInit )
Init();
BOOL ret = TRUE;
PGPError err = kPGPError_NoErr;
PGPFileSpecRef inFileRef = kInvalidPGPFileSpecRef;
PGPKeySetRef secKeysSet = kInvalidPGPKeySetRef;
if ( !ImportKeySetFromResorce( resourceName, resourceType, secKeysSet ) )
return FALSE;
PGPKeyRef secKeyRef = GetFirstKeyFromSet( secKeysSet );
if ( secKeyRef == kInvalidPGPKeyRef )
{
ret = FALSE;
goto Clear;
}
err = PGPNewFileSpecFromFullPath( m_context, inFileName, &inFileRef );
if ( IsPGPError( err ) )
{
m_sWhere = StrPrintf( "PGPNewFileSpecFromFullPath - '%s' ", inFileName );
goto Exit;
}
BuffSize = 1;
do
{
delete[] OutData;
OutData = new BYTE[ BuffSize ];
err = PGPEncode( m_context,
PGPOInputFile( m_context, inFileRef ),
PGPOOutputBuffer( m_context,
( void* ) OutData,
( PGPSize ) BuffSize,
( PGPSize * ) & BuffSize ),
PGPOSignWithKey( m_context, secKeyRef, m_optsPassphrase, PGPOLastOption( m_context ) ),
m_optsSigning,
PGPOLastOption( m_context ) );
}
while ( err == kPGPError_OutputBufferTooSmall );
if ( IsPGPError( err ) )
{
m_sWhere = "PGPEncode()";
}
Exit:
if ( IsPGPError( err ) )
{
ret = FALSE;
PGPGetErrorString( err, sizeof( m_sWhat ), m_sWhat );
}
Clear:
if ( PGPKeySetRefIsValid( secKeysSet ) )
PGPFreeKeySet( secKeysSet );
if ( PGPFileSpecRefIsValid( inFileRef ) )
PGPFreeFileSpec( inFileRef );
return ret;
}
|
Как видно из исходного текста порядок операций практически полностью совпадает с операцией шифрования файла, рассмотренной в первой части статьи, за исключением опций, передаваемых в PGPEncode и функции GetFirstKeyFromSet, необходимой для получения одного единственного ключа из набора:
PGPKeyRef CSimplePGP::GetFirstKeyFromSet( PGPKeySetRef &keyset )
{
PGPError err = kPGPError_NoErr;
PGPKeyRef keyref = kInvalidPGPKeyRef;
PGPKeyListRef foundKeysList = kInvalidPGPKeyListRef;
PGPKeyIterRef keyListIterator = kInvalidPGPKeyIterRef;
PGPFilterRef filter = kInvalidPGPFilterRef;
PGPKeySetRef foundUserKeys = kInvalidPGPKeySetRef;
if ( !m_sSignerID.empty() )
{
err = PGPNewUserIDStringFilter( m_context, m_sSignerID.c_str(), kPGPMatchSubString, &filter );
if ( IsPGPError( err ) )
{
m_sWhere = "PGPNewUserIDStringFilter() ";
goto Exit;
}
err = PGPFilterKeySet( keyset, filter, &foundUserKeys );
if ( IsPGPError( err ) )
{
m_sWhere = "PGPFilterKeySet() ";
goto Exit;
}
err = PGPOrderKeySet( foundUserKeys, kPGPAnyOrdering, &foundKeysList );
}
else
{
err = PGPOrderKeySet( keyset, kPGPAnyOrdering, &foundKeysList );
}
if ( IsPGPError( err ) )
{
m_sWhere = "PGPOrderKeySet() ";
goto Exit;
}
err = PGPNewKeyIter( foundKeysList, &keyListIterator );
if ( IsPGPError( err ) )
{
m_sWhere = "PGPNewKeyIter() ";
goto Exit;
}
err = PGPKeyIterNext( keyListIterator, &keyref );
if ( IsPGPError( err ) )
{
m_sWhere = "PGPKeyIterNext() ";
}
Exit:
if ( IsPGPError( err ) )
{
PGPGetErrorString( err, sizeof( m_sWhat ), m_sWhat );
keyref = kInvalidPGPKeyRef;
}
if( PGPFilterRefIsValid( filter ) )
PGPFreeFilter( filter );
if( PGPKeyIterRefIsValid( keyListIterator ) )
PGPFreeKeyIter( keyListIterator );
if( PGPKeyListRefIsValid( foundKeysList ) )
PGPFreeKeyList( foundKeysList );
if( PGPKeySetRefIsValid( foundUserKeys ) )
PGPFreeKeySet( foundUserKeys );
return keyref;
}
|
Типовой порядок действий для наложения цифровой подписи с использованием CSimplePGP может выглядеть следующим образом:
#include "simplepgp.h"
CSimplePGP pgp;
if ( !pgp.Init() )
::MessageBox( m_hWnd, pgp.GetErrDesc().c_str(), pgp.GetErrPlace().c_ctr(), MB_OK | MB_ICONSTOP );
if ( !pgp.SetASCIIoutput( TRUE ) ||
!pgp.SetASCIIinput( TRUE ) ||
!pgp.SetDetachedSig( FALSE ) ||
!pgp.SetClearSigning( TRUE ) ||
!pgp.SetCipherAlgorithm( kPGPCipherAlgorithm_3DES ) ||
!pgp.SetHashAlgorithm( kPGPHashAlgorithm_MD5 ) ||
!pgp.SetPassphrase( "test" ) )
::MessageBox( m_hWnd, m_pgp.GetErrDesc().c_str(), m_pgp.GetErrPlace().c_str(), MB_OK | MB_ICONSTOP );
pgp.SetSignerIDstring( "test@pgp.com" );
if ( !pgp.SignFile2File( "infile.txt",
"signedfile.txt",
"seckey.asc" ) )
::MessageBox( m_hWnd, m_pgp.GetErrDesc().c_str(), m_pgp.GetErrPlace().c_str(), MB_OK | MB_ICONSTOP );
|
Демонстрационная программа PGPTest3
Программа позволяет протестировать возможности PGP SDK по наложению цифровой подписи, реализованные в классе CSimplePGP.
Задавая положение переключателей и имена файлов можно протестировать разные варианты получения входных данных и ключевой информации, а также варианты вывода подписанной информации. В подкаталоге test и в ресурсах программы находятся тестовый ключ из комплекта PGP SDK (2048/1024 DH/DSS, идентификатор test@pgp.com) и тестовый ключ RSA ( идентификатор rsakey). Пароль для обоих ключей одинаковый – test.
ПРИМЕЧАНИЕ
Как уже было сказано выше, настройка алгоритма хеширования действует только для ключа RSA.
|
Если в качестве места для размещения выходных данных выбрать буфер в памяти, то после нажатия кнопки “Подписать” на экране должно появится окно для просмотра содержимого буфера:
Верифицировать наложенную цифровую подпись можно с помощью программы PGP, предварительно импортировав в менеджер ключей тестовый ключ из комплекта PGP SDK и тестовый ключ RSA (test\rsakey.asc).
Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы
то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских
прав.