- Принцип работы электронной подписи
- .png» alt=» Принцип работы»> Объясним принцип работы ЭЦП на пальцах. Подписание документа производится в несколько этапов: Хеш документа шифруется с помощью закрытого ключа. Полученная подпись добавляется к документу. К документу прикрепляется сертификат проверки. Как устроена электронная подпись Электронная цифровая подпись — это устройство со сложной технической составляющей. <img src="https://astral.ru/upload/iblock/571/vcrrtp1wi9ep226omt1xfmadqkxm8gv2/%D0%A4%D0%BE%D1%80%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%BE%D0%BD%D0%BD%D0%BE%D0%B9%20%D1%86%D0%B8%D1%84%D1%80%D0%BE%D0%B2%D0%BE%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%BF%D0%B8%D1%81%D0%B8%20 .png» alt=» цифровая подпись»> Электронная подпись состоит из двух основных частей: Открытый ключ, он же сертификат. Закрытый ключ — криптографическая часть. Эти составные части выполняют разные функции: с помощью закрытого ключа, доступного только владельцу, документ шифруется, а с помощью сертификата, доступного для всех, документ дешифруется. Таким образом, достигается цель использования ЭЦП — подтверждается то, кем был подписан документ, и заверяется его неизменность с момента подписания. Закрытый ключ не содержит в себе ничего, кроме механизма, с помощью которого он может шифровать документы. Сертификат же несёт в себе такую полезную информацию, как сведения о владельце, сведения об удостоверяющем центре, срок действия цифровой электронной подписи и т.д. Сертификат выступает в роли главного носителя информации о ЭЦП. Сущность технологии ЭЦП Электронная подпись предусматривает применение закрытого и открытого ключей одновременно. Сама технология реализована на базе связки компонентов ЭЦП. Ключи связаны за счёт математического соотношения. Подобная технология обеспечивает безопасность шифрования данных. Даже в случае перехвата информации её практически невозможно расшифровать. Понятие закрытого ключа Данные шифруются с помощью открытого ключа, а расшифровать их можно только тем закрытым ключом, который связан именно с этим открытым ключом. Закрытый ключ ЭЦП именуется также секретным. Этот компонент криптосистемы считается более уязвимым и подверженным взлому. Его получение злоумышленником позволяет создать действительную электронную подпись от имени автора. Поэтому особую важность приобретает хранение криптографической составляющей в надёжном месте. Персональный компьютер не может обеспечить надлежащую защиту ключевой пары. Закрытый ключ ЭЦП — это уникальное сочетание символов, для хранения которых используется цифровой носитель. Им могут служить: Похищение либо потеря устройства хранения данных могут быть сразу обнаружены пользователем — можно успеть своевременно отозвать сертификат. Использование токена или смарт-карты предполагает двухфакторную аутентификацию — введение PIN-кода. Скопировать информацию с носителя, не зная PIN-кода, представляется довольно сложной задачей. Однако токены более универсальны в связи с возможностью их использования на любом устройстве, оснащённом USB-портом. Хранение закрытого ключа осуществляется только у владельца электронной цифровой подписи. Но при этом дубликаты могут создаваться самим владельцем ЭЦП. Хранение составляющей ключевой пары с истёкшим сроком действия целесообразно с целью возможности в дальнейшем расшифровать документы из давнего электронного архива. Закрытый ключ является конфиденциальным. Ответственность за его сохранность в полной мере возлагается на владельца ЭЦП, что прописано на законодательном уровне. Сертификаты X. 509 Так повелось, что основным «активом» в PKI является сертификат X.509 . Сертификат — это что-то вроде паспорта, он содержит информацию, позволяющую идентифицировать субъект, которому выдан сертификат (поле Subject), указывает, кем он был выпущен (поле Issuer), серийный номер сертификата и многое другое. В Windows управлять сертификатами можно с помощью оснастки «Сертификаты» ( run->certmgr.msc ). Менеджер сертификатов При получении сертификата важно установить его в правильное хранилище. Так, сертификаты, которые ты хочешь использовать для электронной подписи, должны быть установлены в хранилище «Личное», а сертификаты получателей, которым нужно будет отправлять зашифрованные сообщения, — в хранилище «Доверенные лица». Сертификаты удостоверяющих центров (УЦ) должны быть установлены в хранилище «Доверенные корневые центры сертификации». При установке сертификата система предлагает два варианта: выбрать хранилище автоматически либо указать вручную. Рекомендую использовать второй вариант, так как автоматика иногда устанавливает сертификат не в то хранилище. Сертификат, которым мы хотим подписывать сообщения, должен иметь закрытый ключ. О наличии закрытого ключа можно узнать, посмотрев на свойства сертификата, где русским по белому будет написано: «есть закрытый ключ для этого сертификата». Закрытый ключ для сертификата Самое интересное о сертификате мы можем узнать на вкладке «Состав». Состав сертификата Обрати внимание на поля «Алгоритм подписи», «Алгоритм хеширования подписи» и «Открытый ключ». Если хочешь использовать сертификат для осуществления транзакций в России, во всех этих полях ты должен видеть слово «ГОСТ». Также следует обратить внимание на значение поля «Использование ключа» и поля «Действителен с» и «Действителен по»: первое позволит понять, возможно ли использование сертификата для выполнения нужной нам операции (шифрование, подпись), а второе и третье — возможно ли использовать данный сертификат в указанный момент времени. В дополнение к этому следует убедиться, что сертификат действителен. В этом нам поможет вкладка «Путь сертификации». Если с сертификатом все хорошо, мы увидим надпись: «Этот сертификат действителен». Состояние сертификата Как работает цифровая подпись Если вспомнить формальное определение, то ЭЦП — это реквизит электронного документа. Другими словами, последовательность битов, вычисленная уникально для каждого конкретного сообщения. Подпись может быть вычислена как с применением секретного ключа, так и без него. Без секретного ключа подпись представляет собой просто код, который может доказать, что документ не был изменен. С использованием секретного ключа подпись докажет целостность сообщения, позволит убедиться в его подлинности и аутентифицировать источник. Если ты читал вторую часть нашего цикла, то помнишь, что существуют симметричный и асимметричный подходы к шифрованию. С электронной подписью дела обстоят очень похоже — есть подписи с симметричным механизмом, а есть с асимметричным. Симметричный механизм подписи малоприменим на практике — никому не хочется генерировать ключи для каждой подписи заново. А как ты помнишь, именно в одинаковых ключах кроется фишка симметричной криптографии. В лучших традициях асимметричной криптографии — имеем пару открытый и секретный ключ. Но не спеши пролистывать все это описание. Электронная подпись концептуально отличается от шифрования применением ключей, описанного ранее. От документа или сообщения подсчитывается хеш-функция, которая сократит сообщение любого объема до определенного количества байтов. Посредством криптографических преобразований вычисляется сама электронная подпись. В отличие от асимметричного шифрования, подпись основана на закрытом ключе, а вот проверить с помощью открытого ключа ее может любой его обладатель. Если помнишь, в шифровании все происходит наоборот: шифруют для нас на открытом ключе, а вот расшифровывать мы будем с помощью секретного ключа. Электронная подпись предоставляется вместе с исходным документом на проверку. По полученной композиции можно доказать, что документ с момента вычисления подписи не был изменен. Схемы электронной подписи так же многообразны, как и способы шифрования. Чтобы схема подписи была стойкой, нужно, чтобы она основывалась на трудновычислимой математической задаче. Есть два типа таких задач: факторизация больших чисел и дискретное логарифмирование. Зачем нужны открытый и закрытый ключ ЭЦП Открытый и закрытый ключ электронной подписи решают разные задачи. Открытый ключ ЭЦП предназначен для зашифровки информации, в то время как закрытый призван обеспечить её расшифровку. Открытый ключ можно без опасений передавать, не рискуя при этом сохранностью данных. Работа ключевой пары осуществляется только при взаимодействии двух составляющих. Надёжная криптосистема успешно используется для заверения электронных документов. Удобный инструмент обеспечивает надлежащую конфиденциальность данных и защиту от фальсификации. Проверка электронной подписи Проверить открытую часть электронной подписи можно с помощью СКЗИ — средств криптографической защиты информации. Это может быть, например, программа КриптоПро CSP. Но это же можно проделать и в сторонних программах, и на веб-сайтах. Доступ к открытому элементу цифровой подписи публичный — воспользоваться им может кто угодно. Открытая часть ключа ЭЦП: как сделать Выдача этой части ключевой пары ЭЦП производится удостоверяющим центром, аккредитованным Минцифры, но это относится только к квалифицированным сертификатам. В его функции входит формирование собственного сертификата, сертификата конечного пользователя, заверение их аутентичности. Для учёта выданных сертификатов УЦ ведёт специальный реестр. Спектр выполняемых органом задач охватывает также аннулирование скомпрометированных сертификатов с последующим обновлением существующей базы. Цифровая подпись Представь, дорогой читатель, что ты занимаешься некой очень ответственной работой. И результаты своей работы отправляешь в виде отчетов, от которых в конечном итоге зависят чьи-то конкретные судьбы и жизни. Получатели твоих отчетов принимают на их основе очень важные решения, и, если ты напортачишь, вполне можешь получить срок. Так вот, в таких ответственных организациях без электронной подписи никуда. Она позволяет тебе подписать тот самый суперважный секретный отчет своим сертификатом с закрытым ключом. Закрытый ключ, в идеале, может храниться на токене — специальном съемном устройстве, похожем на флешку, которое ты в редкие моменты достаешь из сейфа. Подпись гарантирует, что твой отчет отправлен именно тобой, а не уборщицей или сторожем. С другой стороны, ты не сможешь отказаться от авторства (это называется «неотрекаемость») и, если накосячишь в своем суперважном документе, на сторожа свалить вину не получится. Электронная подпись применяется не только в спецслужбах и органах, но и в бизнесе. Например, для перевода пенсионных накоплений в НПФ: мы генерируем запрос на сертификат, отправляем его в удостоверяющий центр (УЦ). У Ц выпускает сертификат, мы подписываем сертификатом заявление на перевод пенсионных накоплений, отправляем — и вуаля. Подпись также позволяет осуществлять контроль целостности подписываемых данных . Если данные будут изменены, подпись не пройдет проверку. Для программирования подписи необходимо ознакомиться с несколькими классами . NET Framework: Перед тем как заюзать наш сертификат, необходимо его проверить. Процедура включает в себя проверку цепочки сертификации, проверку срока действия и проверку, не отозван ли сертификат. Если мы подпишем файл недействительным сертификатом, подпись будет недействительной. X509Chain certificateChain = new X509Chain { ChainPolicy = { RevocationMode = X509RevocationMode.Online, VerificationFlags = X509VerificationFlags.IgnoreNotTimeValid, RevocationFlag = X509RevocationFlag.ExcludeRoot } }; bool chainOk = certificateChain.Build(certificate); bool certNotExpired = (certificate.NotAfter >= DateTime.Now) && (certificate.NotBefore <= DateTime.Now); Мы проверили сертификат и убедились, что он в порядке. Переходим непосредственно к подписыванию данных. Подпись бывает двух видов: прикрепленная и открепленная . Прикрепленная и открепленная подписи Результатом прикрепленной подписи будет CMS (Cryptography Message Syntax) — сообщение, содержащее как подписываемые данные, так и саму подпись. Открепленная подпись содержит только саму подпись. Рекомендую использовать именно открепленную подпись, потому что с ней намного меньше мороки. В нее проще поставить метку времени, она меньше весит, так как не содержит подписываемые данные. Подписываемые данные легко открыть, посмотреть. В случае прикрепленной подписи для того, чтобы просмотреть подписанные данные, CMS-сообщение необходимо сначала декодировать. В общем, прикрепленной подписи я рекомендую избегать всеми силами. Если потребуется передавать подпись и контент вместе, рассмотри вариант архивирования (вместо использования прикрепленной подписи используй открепленную, просто заархивируй подписываемый файл и открепленную подпись). Посмотрим на код подписи (С#): public byte[] SignAttached(X509Certificate2 certificate, byte[] dataToSign) { ContentInfo contentInfo = new ContentInfo(dataToSign); SignedCms cms = new SignedCms(contentInfo, false); CmsSigner signer = new CmsSigner(certificate); cms.ComputeSignature(signer, false); return cms.Encode(); } public byte[] SignDetached(X509Certificate2 certificate, byte[] dataToSign) { ContentInfo contentInfo = new ContentInfo(dataToSign); SignedCms cms = new SignedCms(contentInfo, true); CmsSigner signer = new CmsSigner(certificate); cms.ComputeSignature(signer, false); return cms.Encode(); } Но, как обычно это бывает у Microsoft, стоит сделать маленький шаг в сторону, и розовый волшебный мир рушится Глядя на примеры кода, можно подумать, что работа с подписью в . NET реализована достаточно хорошо. Но рассмотрим, например, случай, в котором необходимо осуществить подпись большого файла, размером 600 MiB. Внимательные читатели обратили внимание на сигнатуру метода подписи — он принимает на вход массив байтов. При попытке загрузить в массив байтов 600 MiB мы получим OutOfMemoryException. Что же делать, спросишь ты? Обращаться к основам — отвечу я! Очевидно, раз нельзя загрузить в память 600 MiB, то необходимо файл грузить и обрабатывать по кусочкам. . NET-обертки над CMS так не умеют. На помощь нам приходит MS Crypto API. M S Crypto API содержит два набора функций для работы с CMS: Simplified Message Fuctions и Low Level Message Functions . Для работы с большими файлами нам нужны Low Level. Полную реализацию на C# можно посмотреть тут . Я же предпочитаю работать с криптографией на языке C++. Кода в результате писать приходится меньше, а работает он быстрее. Рассмотрим порядок действий для реализации подписи в поточном режиме: Получаем PCCERT\_CONTEXT ; Заполняем структуры CMSG\_STREAM\_INFO , CRYPT\_ALGORITHM\_IDENTIFIER , CMSG\_SIGNER\_ENCODE\_INFO , CMSG\_SIGNED\_ENCODE\_INFO ; Получаем хендл сообщения с помощью функции CryptMsgOpenToEncode . Для открепленной подписи необходимо передать соответствующий флаг CMSG\_DETACHED\_FLAG ; В цикле вызываем функцию CryptMsgUpdate и «скармливаем» ей по кусочкам файл, который необходимо подписать. На C++ будет что-то вроде: ISigner signer = null; // Заполняем структуры ... // Открываем сообщение для кодирования HCRYPTMSG hMsg = CryptMsgOpenToEncode ( (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING), // Message encoding type dwFlags, // Flags CMSG_SIGNED, // Message type &SignedMsgEncodeInfo, // Pointer to structure NULL, // Inner content object ID &stStreamInfo // Stream information (not used) ); ... // Обрабатываем файл для подписи по кусочкам while ( ( bytesRead = inputStream->Read(buf, blockSize, 0, blockSize ) ) > 0 ) { processedDataLen += bytesRead; BOOL lastcall = (processedDataLen == streamLength); BOOL successful = CryptMsgUpdate(hMsg, (const BYTE*)buf, bytesRead, lastcall); } // Закрываем хендл CryptMsgClose(hMsg); return S_OK; Вызов кода на C++ из C# будет выглядеть примерно так: ISigner signer = null; PkiFactory.CreateSigner(out signer); X509Store store = new X509Store("My"); store.Open(OpenFlags.ReadOnly); var cert = GetCertificates(store); string file = @"d:\tmp\masyanya.bin"; using (var inputStream = File.OpenRead(file)) using (var outputStream = File.Create(file + ".sig")) { var reader = new StreamReader(inputStream); var writer = new StreamWriter(outputStream); int result = signer.Sign(reader, writer, cert, 1); Debug.Assert(result == 0, "Подпись не прошла."); } Внимательный читатель удивится — что это за IStreamReader* inputStream , IStreamWriter* outputStream , ICertificate* signCertificate ? Ответ следует из названия переменных, но есть одна тонкость, которая для многих окажется сюрпризом. I StreamReader, IStreamWriter, ICertificate — это интерфейсы, реализованные на C#, и это не COM-объекты. При этом мы спокойно можем вызывать их методы в нативном C++. Как сделать такую красоту — тема отдельной статьи. В результате успешного выполнения операции мы получим криптографическое сообщение. Для кодирования сертификатов X.509 и криптографических сообщений используется Abstract Syntax Notation One , или, по-простому, ASN 1. Для просмотра файлов, закодированных в ASN 1, можно воспользоваться бесплатным ASN.1 Editor . Подпись изнутри Файл masks. key Содержит 32 байта маски ключа в формате Asn1, зашифрованного на ключе хранения pwd_key. Далее 12 байт «затравочной» информации для генерации ключа хранения pwd_key, если криптоконтейнер защищен паролем, то пароль также участвует в генерации ключа хранения. Далее контрольная сумма (имитозащита) 4 байта. Контрольной информацией для простоты мы пользоваться не будем, общий контроль будет осуществляться путем генерации открытого ключа и сравнения первых 8 байт полученного ключа с соответствующим полем из файла header.key: Картина мира Перед погружением в код давай разберем немного терминологии. P KI — инфраструктура открытых ключей. Как несложно догадаться, PKI основана на асимметричном шифровании. В симметричных шифрах для шифрования и расшифрования используется один ключ. В асимметричных для шифрования используется один ключ, а для расшифрования — другой. Вместе они образуют ключевую пару. Информация, необходимая для работы PKI, содержится в сертификате X.509 . В PKI участвуют как минимум три стороны: Алиса, Боб и удостоверяющий центр (УЦ). У Алисы и Боба есть сертификаты с закрытым ключом, подписанные так называемым корневым сертификатом УЦ. У Алисы есть сертификат Боба с открытым ключом, а у Боба — сертификат Алисы с открытым ключом. Алиса и Боб доверяют УЦ и благодаря этому могут доверять друг другу. Упрощенная структура PKI WARNING Приведенный ниже код предназначен исключительно для ознакомления с PKI. Не следует без оглядки использовать его в реальной работе. Читаем закрытый ключ и конвертируем #include <stdio.h> #include <stdlib.h> #include <string.h> #include <openssl/pem.h> #include <openssl/cms.h> #include <openssl/err.h> #include "gost_lcl.h" /* Convert little-endian byte array into bignum */ BIGNUM *reverse32bn(char *b, BN_CTX *ctx) { BIGNUM *res; char buf[32]; BUF_reverse(buf, b, 32); res = BN_bin2bn(buf, 32, BN_CTX_get(ctx)); OPENSSL_cleanse(buf, sizeof(buf)); return res; } void xor_material(char *buf36, char *buf5C, char *src) { int i; for(i = 0; i < 32; i++) { buf36[i] = src[i] ^ 0x36; buf5C[i] = src[i] ^ 0x5C; } } int make_pwd_key(char *result_key, char *start12, int start12_len, char *passw) { int result; int i; char pincode4[1024]; int pin_len; char current[32]; char material36[32]; char material5C[32]; char hash_result[32]; gost_hash_ctx ctx; init_gost_hash_ctx(&ctx, &GostR3411_94_CryptoProParamSet); memset(pincode4, 0, sizeof(pincode4)); pin_len = strlen(passw); if (pin_len*4 > sizeof(pincode4)) { result = 1; goto err; } for(i = 0; i < pin_len; i++) pincode4[i*4] = passw[i]; start_hash(&ctx); hash_block(&ctx, start12, start12_len); if (pin_len) hash_block(&ctx, pincode4, pin_len * 4); finish_hash(&ctx, hash_result); memcpy(current, (char*)"DENEFH028.760246785.IUEFHWUIO.EF", 32); for(i = 0; i < (pin_len?2000:2); i++) { xor_material(material36, material5C, current); start_hash(&ctx); hash_block(&ctx, material36, 32); hash_block(&ctx, hash_result, 32); hash_block(&ctx, material5C, 32); hash_block(&ctx, hash_result, 32); finish_hash(&ctx, current); } xor_material(material36, material5C, current); start_hash(&ctx); hash_block(&ctx, material36, 32); hash_block(&ctx, start12, start12_len); hash_block(&ctx, material5C, 32); if (pin_len) hash_block(&ctx, pincode4, pin_len * 4); finish_hash(&ctx, current); start_hash(&ctx); hash_block(&ctx, current, 32); finish_hash(&ctx, result_key); result = 0; //ok err: return result; } BIGNUM *decode_primary_key(char *pwd_key, char *primary_key, BN_CTX *bn_ctx) { BIGNUM *res; char buf[32]; gost_ctx ctx; gost_init(&ctx, gost_cipher_list->sblock); gost_key(&ctx, pwd_key); gost_dec(&ctx, primary_key, buf, 4); res = reverse32bn(buf, bn_ctx); OPENSSL_cleanse(buf, sizeof(buf)); return res; } BIGNUM *remove_mask_and_check_public(char *oid_param_set8, BIGNUM *key_with_mask, BIGNUM *mask, char *public8, BN_CTX *ctx) { int result; EC_KEY *eckey = NULL; const EC_POINT *pubkey; const EC_GROUP *group; BIGNUM *X, *Y, *order, *raw_secret, *mask_inv; char outbuf[32], public_X[32]; ASN1_OBJECT *obj; int nid; order = BN_CTX_get(ctx); mask_inv = BN_CTX_get(ctx); raw_secret = BN_CTX_get(ctx); X = BN_CTX_get(ctx); Y = BN_CTX_get(ctx); if (!order || !mask_inv || !raw_secret || !X || !Y) { result = 1; goto err; } obj = ASN1_OBJECT_create(0, oid_param_set8+1, *oid_param_set8, NULL, NULL); nid = OBJ_obj2nid(obj); ASN1_OBJECT_free(obj); if (!(eckey = EC_KEY_new())) { result = 1; goto err; } if (!fill_GOST2001_params(eckey, nid)) { result = 1; goto err; } if (!(group = EC_KEY_get0_group(eckey))) { result = 1; goto err; } if (!EC_GROUP_get_order(group, order, ctx)) { result = 1; goto err; } if (!BN_mod_inverse(mask_inv, mask, order, ctx)) { result = 1; goto err; } if (!BN_mod_mul(raw_secret, key_with_mask, mask_inv, order, ctx)) { result = 1; goto err; } if (!EC_KEY_set_private_key(eckey, raw_secret)) { result = 1; goto err; } if (!gost2001_compute_public(eckey)) { result = 1; goto err; } if (!(pubkey = EC_KEY_get0_public_key(eckey))) { result = 1; goto err; } if (!EC_POINT_get_affine_coordinates_GFp(group, pubkey, X, Y, ctx)) { result = 1; goto err; } store_bignum(X, outbuf, sizeof(outbuf)); BUF_reverse(public_X, outbuf, sizeof(outbuf)); if (memcmp(public_X, public8, 8) != 0) { result = 1; goto err; } result = 0; //ok err: if (eckey) EC_KEY_free(eckey); if (result == 0) return raw_secret; return NULL; } int file_length(char *fname) { int len; FILE *f = fopen(fname, "rb"); if (f == NULL) return -1; fseek(f, 0, SEEK_END); len = ftell(f); fclose(f); return len; } int read_file(char *fname, int start_pos, char *buf, int len) { int read_len; FILE *f = fopen(fname, "rb"); if (f == NULL) return 1; if (start_pos) fseek(f, start_pos, SEEK_SET); read_len = fread(buf, 1, len, f); fclose(f); if (read_len != len) return 1; return 0; //ok } int get_asn1_len(unsigned char *buf, int *size_hdr) { int n, i, res; int pos = 0; if ((buf[pos]&0x80) == 0) { *size_hdr = 1; return buf[pos]; } n = buf[pos++]&0x7f; res = 0; for(i = 0; i < n; i++) { res = res*256 + buf[pos++]; } *size_hdr = n+1; return res; } #define MAX_HEADER 20000 int read_container(char *fpath, int flag2, char *salt12, char *primary_key, char *masks_key, char *public8, char *oid_param_set8) { int result; char primary_path[1024+30]; char masks_path[1024+30]; char header_path[1024+30]; char header_buf[MAX_HEADER]; int header_len; int i, len, pos, size_hdr; if (strlen(fpath)>1024) { result = 1; goto err; } sprintf(header_path, "%s/header.key", fpath); if (flag2 == 0) { sprintf(primary_path, "%s/primary.key", fpath); sprintf(masks_path, "%s/masks.key", fpath); } else { sprintf(primary_path, "%s/primary2.key", fpath); sprintf(masks_path, "%s/masks2.key", fpath); } if (read_file(primary_path, 4, primary_key, 32)) { result = 1; goto err; } if (read_file(masks_path, 4, masks_key, 32)) { result = 1; goto err; } if (read_file(masks_path, 0x26, salt12, 12)) { result = 1; goto err; } header_len = file_length(header_path); if (header_len < 0x42 || header_len > MAX_HEADER) { result = 1; goto err; } if (read_file(header_path, 0, header_buf, header_len)) { result = 1; goto err; } //------------- skip certificate --------------------------- pos = 0; for(i = 0; i < 2; i++) { get_asn1_len(header_buf+pos+1, &size_hdr); pos += size_hdr+1; if (pos > header_len-8) { result = 2; goto err; } } //------------------ get oid_param_set8 ----------------------- #define PARAM_SET_POS 34 if (memcmp(header_buf+pos+PARAM_SET_POS, "\x6\x7", 2) != 0) { result = 2; goto err; } memcpy(oid_param_set8, header_buf+pos+PARAM_SET_POS+1, 8); //------------------ get public8 ----------------------- result = 2; //not found pos += 52; for(i = 0; i < 3; i++) { len = get_asn1_len(header_buf+pos+1, &size_hdr); if (len == 8 && memcmp(header_buf+pos, "\x8a\x8", 2) == 0) { memcpy(public8,header_buf+pos+2,8); result = 0; //ok break; } pos += len+size_hdr+1; if (pos > header_len-8) { result = 2; goto err; } } err: OPENSSL_cleanse(header_buf, sizeof(header_buf)); return result; } #define START_OID 0x12 #define START_KEY 0x28 unsigned char asn1_private_key[72] = { 0x30,0x46,2,1,0,0x30,0x1c,6,6,0x2a,0x85,3,2,2,0x13,0x30,0x12,6,7,0x11, 0x11,0x11,0x11,0x11,0x11,0x11,6,7,0x2a,0x85,3,2,2,0x1e,1,4,0x23,2,0x21,0 }; int main(int argc, char **argv) { int result; char *container_path; char *passw; char salt12[12]; char primary_key[32]; char masks_key[32]; char public8[8]; char oid_param_set8[8]; BN_CTX *ctx; BIGNUM *key_with_mask; BIGNUM *mask; BIGNUM *raw_key; char pwd_key[32]; char outbuf[32]; ctx = BN_CTX_new(); if (argc == 2) { container_path = argv[1]; passw = ""; } else if (argc == 3) { container_path = argv[1]; passw = argv[2]; } else { printf("get_private container_path [passw]\n"); result = 1; goto err; } if (read_container(container_path, 0, salt12, primary_key, masks_key, public8, oid_param_set8) != 0 && read_container(container_path, 1, salt12, primary_key, masks_key, public8, oid_param_set8) != 0) { printf("can not read container from %s\n", container_path); result = 2; goto err; } make_pwd_key(pwd_key, salt12, 12, passw); key_with_mask = decode_primary_key(pwd_key, primary_key, ctx); OPENSSL_cleanse(pwd_key, sizeof(pwd_key)); mask = reverse32bn(masks_key, ctx); raw_key = remove_mask_and_check_public(oid_param_set8, key_with_mask, mask, public8, ctx); if (raw_key) { BIO *bio; store_bignum(raw_key, outbuf, sizeof(outbuf)); memcpy(asn1_private_key+START_OID, oid_param_set8, 8); memcpy(asn1_private_key+START_KEY, outbuf, 32); //bio = BIO_new_file("private.key", "w"); bio = BIO_new_fp(stdout, BIO_NOCLOSE | BIO_FP_TEXT); PEM_write_bio(bio, "PRIVATE KEY", "", asn1_private_key, sizeof(asn1_private_key)); BIO_free(bio); OPENSSL_cleanse(outbuf, sizeof(outbuf)); OPENSSL_cleanse(asn1_private_key, sizeof(asn1_private_key)); result = 0; //ok } else { printf("Error check public key\n"); result = 3; } err: BN_CTX_free(ctx); OPENSSL_cleanse(salt12, sizeof(salt12)); OPENSSL_cleanse(primary_key, sizeof(primary_key)); OPENSSL_cleanse(masks_key, sizeof(masks_key)); return result; } Основную работу выполняют следующие 3 функции: 1. Создаем ключ хранения исходя из 12-ти байтовой «соли» и пароля. make_pwd_key(pwd_key, salt12, 12, passw); 2. Расшифровываем основной ключ на ключе хранения. key_with_mask = decode_primary_key(pwd_key, primary_key, ctx); 3. Делим ключ с маской на маску. raw_key = remove_mask_and_check_public(oid_param_set8, key_with_mask, mask, public8, ctx); Но так как в библиотеке OpenSSL операция деления по модулю традиционно отсутствует, пользуемся операцией взятия обратного числа и умножением. if (!BN_mod_inverse(mask_inv, mask, order, ctx)) { result = 1; goto err; } if (!BN_mod_mul(raw_secret, key_with_mask, mask_inv, order, ctx)) { result = 1; goto err; } Факторизация больших чисел Рассмотрим на практике электронную подпись на основе знаменитого алгоритма RSA. Шифрование RSA мы рассматривать не стали — это мейнстрим, и в той же «Википедии» есть его подробное описание . 1. Генерация ключей Причина стойкости RSA кроется в сложности факторизации больших чисел. Другими словами, перебором очень трудно подобрать такие простые числа, которые в произведении дают модуль n. Ключи генерируются одинаково для подписи и для шифрования. Когда ключи сгенерированы, можно приступить к вычислению электронной подписи. 2. Вычисление электронной подписи 3. Проверка электронной подписи RSA, как известно, собирается уходить на пенсию, потому что вычислительные мощности растут не по дням, а по часам. Недалек тот день, когда 1024-битный ключ RSA можно будет подобрать за считаные минуты. Впрочем, о квантовых компьютерах мы поговорим в следующий раз. В общем, не стоит полагаться на стойкость этой схемы подписи RSA, особенно с такими «криптостойкими» ключами, как в нашем примере. Расшифрование При расшифровании необходимо, чтобы сертификат, указанный при шифровании в коллекции получателей, был установлен в хранилище сертификатов. Так как сообщение может быть зашифровано и адресовано нескольким получателям, для расшифрования нам необходимо найти того получателя, сертификат которого установлен в нашем хранилище сертификатов. public byte[] Decrypt(byte[] encryptedData) { var envelopedCms = new EnvelopedCms(); envelopedCms.Decode(encryptedData); X509Store store = new X509Store("My"); store.Open(OpenFlags.ReadOnly); RecipientInfo recipientInfo = envelopedCms.RecipientInfos.Cast<RecipientInfo>() .FirstOrDefault(x => FindCertificate((X509IssuerSerial)x.RecipientIdentifier.Value) != null); envelopedCms.Decrypt(recipientInfo); return envelopedCms.ContentInfo.Content; } private X509Certificate2 FindCertificate(X509IssuerSerial issuerSerial) { var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); return store.Certificates .Find(X509FindType.FindByIssuerDistinguishedName, issuerSerial.IssuerName, false) .Find(X509FindType.FindBySerialNumber, issuerSerial.SerialNumber, false).Cast<X509Certificate2>() .FirstOrDefault(); } Файл header. key Из этого файла нам потребуется параметры электронной подписи CryptoProParamSet (подчеркнуто красным). GostR3410_2001_CryptoPro_A_ParamSet — 1.2.643.2.2.35.1 GostR3410_2001_CryptoPro_B_ParamSet — 1.2.643.2.2.35.2 GostR3410_2001_CryptoPro_C_ParamSet — 1.2.643.2.2.35.3 GostR3410_2001_CryptoPro_XchA_ParamSet — 1.2.643.2.2.36.0 GostR3410_2001_CryptoPro_XchB_ParamSet — 1.2.643.2.2.36.1 А также первые 8 байт открытого ключа (подчеркнуто) для контроля правильности чтения закрытого. Цифровая подпись в Bitcoin Помимо прочего, электронная подпись используется в криптовалютах, в частности — в Bitcoin. У каждого пользователя Bitcoin есть пара из секретного и открытого ключа. Хеш-значение открытого ключа служит основным адресом для передачи монет. Это значение не секретно, и сообщать его можно кому угодно. Но по значению хеша вычислить значение открытого ключа невозможно. Сама пара ключей будет использована лишь однажды — при передаче прав собственности. На этом жизнь пары ключей заканчивается. PUB1 — публичный ключ; PRIV1 — секретный ключ; HASH1 или HASH(PUB1) — хеш-значение открытого ключа (биткойн-адрес); HASH2 или HASH(PUB2) — хеш открытого ключа следующего владельца. Вот как устроен сам процесс передачи прав собственности на биткойны. Владелец монеты открыто сообщает хеш своего публичного ключа HASH(PUB1), это и будет идентифицировать биткойн. До момента продажи оба ключа PUB1, PRIV1 продавца остаются в секрете. Известен только HASH(PUB1) и соответствующий ему биткойн. Как только появляется покупатель, владелец формирует открытое письмо, в котором указывает адрес биткойна HASH(PUB1) и хеш-значение публичного ключа нового владельца HASH(PUB2). И конечно же, подписывает письмо своим секретным ключом PRIV1, прилагая публичный ключ PUB1. После этого пара ключей владельца PUB1 и PRIV1 теряют свою актуальность. Публичным ключом можно проверить само письмо, узнать новый адрес монеты. О втором собственнике ничего не известно, кроме HASH(PUB2), до тех пор пока он не передаст права третьему владельцу. И эта цепочка может быть бесконечной. Подписывая передачу прав с использованием ЭЦП, собственник подтверждает не только свою личность, но и свое согласие на проведение сделки. То есть вернуть монетку он уже не может и с этим согласился, подписавшись электронной подписью. Такая технология построения цепи передачи прав и называется блокчейном. Благодаря этой технологии можно отследить историю владения до самых истоков, но изменить эту историю никак нельзя. INFO Среди . NET-девелоперов бытует мнение, что программировать на C++ сложнее и дольше. Уверяю тебя, в случае с криптографией ситуация противоположна. Гораздо быстрее написать код на C++ и вызвать его из . NET. Дискретное логарифмирование Это вторая сложная проблема, на которой основаны цифровые подписи. Для начала хорошо бы усвоить, что такое дискретный логарифм. Для кого-то такое словосочетание может звучать пугающе, но на самом деле это одна из самых простых для понимания вещей в этой статье. Предположим, дано уравнение 4x = 13 (mod 15) . Задача нахождения x и есть задача дискретного логарифмирования. Почему же она так сложна для вычисления? Попробуй решить это уравнение перебором! Компьютер, ясное дело, будет более успешен, но и задачи дискретного логарифмирования обычно далеко не так просты. Возьмем для примера схему Эль-Гамаля. 1. Генерация подписи 2. Проверка подписи Даже если не вникать в схему, понятно, что такой алгоритм сложнее. Кроме того, нигде уже не используется простой модуль, его сменили эллиптические кривые. Эллиптическая кривая — это кривая, которая задана кубическим уравнением и имеет невообразимо сложное представление. Задача решения логарифма в группе точек, которые принадлежат эллиптической кривой, вычислительно сложная, и на данный момент не существует таких мощностей, которые решали бы это уравнение за полиномиальное время, если длина секретного ключа составляет 512 бит. Согласно задаче дискретного логарифмирования, невероятно сложно найти на кривой две такие точки, которые связывает операция возведения в некоторую степень. ЭЦП на практике В России, как и во многих развитых странах, электронная подпись имеет официальный юридический статус. У нас этот факт регламентирует закон № 63-ФЗ «Об электронной подписи». Однако он утверждает, что юридической силой обладает далеко не любая электронная подпись, а только соответствующая определенным критериям: подпись сгенерирована посредством криптографического преобразования с секретным ключом; этот ключ и соответствующий ему открытый ключ выданы квалифицированным удостоверяющим центром; по подписи можно достоверно установить ее обладателя. Подпись также должна быть вычислена средствами, соответствующими требованиям закона. Этим требованиям удовлетворяет отечественный алгоритм шифрования ГОСТ 34.10—2012. Он использует математический аппарат эллиптических кривых, является достаточно стойким и официально используется для разработки криптографических средств, реализующих электронную подпись. Для того чтобы попробовать неквалифицированную подпись — без сертификата удостоверяющего центра, можно воспользоваться известной PGP. Потестировать подпись можно, к примеру, на сайте ReadVerify . Стоит сказать, что в нашей стране электронная подпись используется чаще, чем можно себе представить. В банках, налоговых, торгово-закупочных операциях, бухгалтерии — во всех этих организациях используется или внедряется ЭЦП. Электронная подпись отважно борется со злом бюрократии, однако до полной победы еще далеко. За рубежом электронный документооборот процветает еще дольше. Официальный стандарт электронной подписи в США DSS (Digital Signature Standard) также использует эллиптические кривые и основан на описанной выше схеме Эль-Гамаля. Проверка подписи и декодирование А теперь, дорогой читатель, представь, что ты большой начальник и должен принять важное стратегическое решение на основе отчета, который тебе прислал сотрудник по электронной почте. Для твоего удобства отчет был подписан открепленной подписью. Открыв почту и скачав отчет, ты, как опытный, знающий жизнь человек, не спешишь принимать на веру содержимое отчета и проверяешь подпись. После проверки выясняешь, что подпись неверна — не сошлась контрольная сумма. В результате оповещаешь службу безопасности, которая проводит расследование и выясняет, что хитрые конкуренты взломали почтовый сервер и отправили тебе фальшивый документ. Тебя наградили за бдительность, конкурентов посадили, а компания наконец-то получила оригинальный отчет с проверенной электронной подписью. Если пользователь прислал тебе отчет в виде прикрепленной подписи, тебе для чтения придется его декодировать: public bool VerifyAttached(byte[] dataToVerify) { try { var cms = new SignedCms(); cms.Decode(dataToVerify); foreach (var signer in cms.SignerInfos) { signer.CheckSignature(true); } return true; } catch (CryptographicException) { return false; } } public byte[] Decode(byte[] signedCms) { var cms = new SignedCms(); cms.Decode(signedCms); return cms.ContentInfo.Content; } Нетрудно догадаться, что и тут разработчики . NET Framework подложили нам свинью. Не можем мы проверить подпись большого файла! По той же самой причине — OutOfMemoryException. Но и эту проблему несложно решить, обратившись к магии MS Crypto API. Так как код поточной проверки подписи достаточно длинный, остановлюсь на основных моментах: // Заполняем структуры ... // Открываем сообщение для декодирования HCRYPTMSG msg = CryptMsgOpenToDecode(...); // Декодируем сообщение по кусочкам while ((bytesRead = inputStream->Read(&buf.at(0), blockSize, 0, blockSize)) > 0) { totalBytesRead += bytesRead; CryptMsgUpdate(msg, &buf.at(0), bytesRead, totalBytesRead == fileSize); } // Получаем информацию о подписанте PCCERT_CONTEXT pSignerCertContext = CertGetSubjectCertificateFromStore( hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (PCERT_INFO)((void*)(&signerCertInfo.at(0)))); // Проверяем подпись BOOL ok = CryptMsgControl(msg, 0, CMSG_CTRL_VERIFY_SIGNATURE, pSignerCertContext->pCertInfo)); А так будет выглядеть код проверки подписи при вызове из C#: ISignatureVerifier verifier = null; PkiFactory.CreateSignatureVerifier(out verifier); using (var inputStream = File.OpenRead(file + ".sig")) using (var contentStream = File.OpenRead(file)) { var reader = new StreamReader(inputStream); var contentReader = new StreamReader(contentStream); var error = verifier.VerifyDetachedSign(contentReader, reader); Debug.Assert(error == 0, "Проверка подписи не прошла"); } Как происходит подписание документа с помощью ЭЦП Система подписания документов с помощью электронной подписи выглядит следующим образом: Электронная подпись присоединяется не к цифровому документу. Э П ставится на его сжатую версию — хэш. Таким образом, сокращается время шифрования, так как хэш файла весит меньше, чем сам файл. Для создания хэша применяются криптографические хэш-функции. При данном способе объёмный текст файла не делится на отдельные модули и сохраняет свой порядок. После создания хэша, закрытый ключ его шифрует и передаёт получателю вместе с сертификатом электронной подписи. Открытый ключ ЭП адресата расшифровывает информацию и проверяет подлинность сертификата отправителя. Закрытый ключ электронной подписи хранят в памяти компьютера или физических носителях: USB-токенах и смарт-картах. Согласно закону «Об электронной подписи» 63-ФЗ, ответственность за хранение закрытого ключа несёт владелец. Программы для работы и алгоритмы шифрования С ЭЦП не получится работать сразу. Чтобы шифровать и подписывать документы, недостаточно только иметь сертификат и закрытый ключ, для работы нужно устанавливать специальные программы. С помощью этих программ, которые работают по определённому стандарту шифрования (в России — ГОСТ 34.10-2018 ), обеспечивается связь закрытого и открытого ключа с документами. Расшифровка ЭЦП: электронная цифровая подпись. Данное понятие является устаревшим. Сегодня используется термин — электронная подпись, сокращённо ЭП. Одной из самых популярных программ-криптопровайдеров в России является «КриптоПро CSP» . С её помощью можно подписывать и шифровать документы, проверять сертификаты на подлинность, контролировать целостность соответствующего программного обеспечения. Файл primary. key Содержит 32 байта ключа в формате Asn1. Это только половина ключа, полный ключ получается при делении этого числа по модулю Q на маску. Поле, хранящее модуль Q в библиотеке OpenSSL имеет название order. Маска лежит в файле masks.key: Электронная подпись Время на прочтение Развитие цифровых технологий упростило многие моменты нашей жизни, в том числе, обмениваться документами и письмами в электронной форме оказалось быстрее и удобнее, чем в бумажной. Однако у электронного документооборота была проблема: нужно было как- то подтверждать подлинность электронных бумаг. На бумаге для этого использовались подписи, однако в цифровом формате их было достаточно легко подделать, поэтому для электронных документов требовался более надежный метод подтверждения. Поэтому и стал популярным аналог ручных подписей — электронная подпись, о которой будет рассказано дальше. Сначала вкратце опишу то, как работает электронная подпись (ЭП) и в чём заключается её надёжность. Электронная подпись – подтверждение того, что какой-либо электронный документ был создан и подписан определённым физическим или юридическим лицом. При этом она должна обладать следующими свойствами: Неотказуемость – подписавшее документ лицо не может утверждать, что это сделал кто-то другой; Целостность – внесение исправлений в уже подписанный документ должно нарушать подпись; Авторство – электронная подпись должна быть жёстко закреплена за определённым физическим или юридическим лицом. Эти базовые принципы и делают ЭП эффективной и безопасной в использовании. Немного теории В качестве алгоритмической базы для электронной подписи обычно применяют методы шифрования с открытым ключом. Подробнее о самих алгоритмах можно почитать на википедии или на хабре . Их суть сводится к тому, что для желающего обзавестись собственной электронной подписью специальным образом выбирается пара ключей – открытый и закрытый. Первый, как следует из названия, доступен всем, а второй владельцу подписи лучше держать в секрете. Естественно, ключи выбираются так, чтобы закрытый ключ нельзя было легко угадать по открытому. После чего закрытый ключ используется для шифрования документа (иными словами, его подписания), а открытый – для расшифровки (то есть для проверки подписи). При этом алгоритмы выбора ключей гарантируют, что открытым ключом можно расшифровать только те документы, которые шифровались соответсвующим ему закрытым ключом. Таким образом, если мы знаем открытый ключ владельца ЭП и смогли расшифровать им полученный от него документ, то он был точно подписан тем самым владельцем. Так работают асимметричные схемы ЭП. Применение хэш-функций В связи с тем, что шифрование закрытым ключом документа большого размера – довольно сложный и долгий процесс, обычно к тексту документа сначала применяют более быстрое и простое хэш-шифрование . Полученный сравнительно короткий результат шифруют закрытым ключом, получая саму цифровую подпись. Вместе с ней открытый текст документа передаётся получателю. Подробная схема с применением хэш-функции Тот должен всего лишь хэшировать текст документа той же хэш-функцией, после чего расшифровать открытым ключом подпись и сравнить оба результата. Если они совпадают – то документ мог быть подписан только отправителем (вот она и неотказуемость) и не был испорчен, дополнен или подменён в процессе пересылки (а это целостность). А дляпоследнего свойства – авторства – необходимо, чтобы пара ключей подписавшего была закреплена за ним. На практике для этого используются так называемые удостоверяющие центры, которые по запросу выдают сертификат на пару ключей, а также гарантируют единственность обладания ими. Что же может пойти не так? Наверное, внимательные читатели уже увидели, что описанная выше схема представляет собой сферическую ЭП в вакууме. И действительно, в теории взломать или подделать такую подпись можно несколькими способами. Самый лакомый способ для взломщика-криптоаналитика – это по открытому ключу угадать закрытый. Тогда злоумышленнику сразу открываются сказочные перспективы – ведь он сможет действовать от лица истинного владельца подписи и даже управлять его имуществом. Однако обычно такой взлом не возможен ввиду того, что подбор закрытого ключа по открытому – вычислительно нерешаемая задача. При генерации пары ключей широко применяются факторизация или дискретное логарифмирование, что оставляет взломщику мало надежды. Конечно можно попробовать подобрать закрытый ключ полным перебором, но при достаточно большом размере ключей такая возможность отпадает. Ещё одно уязвимое место – это хэш-функция. Здесь возможны сразу несколько направлений атак. Если алгоритм хэширования не достаточно надёжный, то взломщик может подобрать какой-нибудь свой документ, применение хэш-функции к которому даст тот же результат, что и её применение к исходному документу. Или же злоумышленник может сгенерировать два документа, дающие одинаковый хэш, после чего при необходимости сможет подменить один документ другим. Названные ситуации считаются коллизиями хэш-функций . Однако жизнь взломщика хэш-функции всё же не так легка: мало того, что подставной документ должен представлять из себя читаемый текст, а не быть бессмысленным набором бит, так еще и придумано достаточно криптостойких алгоритмов хэширования. Для примера, надёжными на момент написания статьи являются SHA-3 , BLAKE2 , семейство JH или отечественный «Стрибог» (он же ГОСТ 34.11-2018 ). Подключаем человеческий фактор, бюрократию и частные организации В самом начале мы говорили о свойствах, которыми должна обладать качественная электронная подпись. Из них под действием уже упомянутых выше атак пока страдали только её целостность и неотказуемость. Однако в реалиях нашего мира наибольшее количество нарушений происходит из-за подмены авторства. В России получение ЭП регулируется законом № 63-ФЗ «Об электронной подписи» . Для её оформления нужно получить сертификат от удостоверяющего центра (УЦ) на выбор . В УЦ нужно предоставить необходимые документы и заплатить некоторую сумму, после чего забрать свой сертификат и заветный eToken с закрытым ключом. Вот на получении сертификатов-то и возникает простор для всевозможных махинаций. Все УЦ являются коммерческими организациями, они обязательно аккредитованы Министерством цифрового развития и имеют лицензию от ФСБ. Однако чего не сделаешь ради прибыли — для УЦ порой желания клиентов выше установленных правил выдачи сертификата. Например, из-за этого сегодня возможно получить ЭП на другое лицо , пользуясь утечками персональных данных или некомпетентностью УЦ. Это приводит к довольно печальным последствиям – вплоть до переоформления квартиры или регистрации фиктивных организаций с целью взятия кредитов. Данные способы подделки подписей не требуют даже знания криптографии, достаточно лишь воспользоваться особенностями сертифицирования ЭП в России. Заключение В статье вкратце было рассмотрено, как работает электронная подпись. Видно, что алгоритм достаточно устойчив и почти не имеет слабых мест. Однако, как и во многих случаях, самое слабое место этого метода — человеческий фактор, оставляющий большой простор для действий злоумышленников.
- Как устроена электронная подпись
- .png» alt=» цифровая подпись»> Электронная подпись состоит из двух основных частей: Открытый ключ, он же сертификат. Закрытый ключ — криптографическая часть. Эти составные части выполняют разные функции: с помощью закрытого ключа, доступного только владельцу, документ шифруется, а с помощью сертификата, доступного для всех, документ дешифруется. Таким образом, достигается цель использования ЭЦП — подтверждается то, кем был подписан документ, и заверяется его неизменность с момента подписания. Закрытый ключ не содержит в себе ничего, кроме механизма, с помощью которого он может шифровать документы. Сертификат же несёт в себе такую полезную информацию, как сведения о владельце, сведения об удостоверяющем центре, срок действия цифровой электронной подписи и т.д. Сертификат выступает в роли главного носителя информации о ЭЦП. Сущность технологии ЭЦП Электронная подпись предусматривает применение закрытого и открытого ключей одновременно. Сама технология реализована на базе связки компонентов ЭЦП. Ключи связаны за счёт математического соотношения. Подобная технология обеспечивает безопасность шифрования данных. Даже в случае перехвата информации её практически невозможно расшифровать. Понятие закрытого ключа Данные шифруются с помощью открытого ключа, а расшифровать их можно только тем закрытым ключом, который связан именно с этим открытым ключом. Закрытый ключ ЭЦП именуется также секретным. Этот компонент криптосистемы считается более уязвимым и подверженным взлому. Его получение злоумышленником позволяет создать действительную электронную подпись от имени автора. Поэтому особую важность приобретает хранение криптографической составляющей в надёжном месте. Персональный компьютер не может обеспечить надлежащую защиту ключевой пары. Закрытый ключ ЭЦП — это уникальное сочетание символов, для хранения которых используется цифровой носитель. Им могут служить: Похищение либо потеря устройства хранения данных могут быть сразу обнаружены пользователем — можно успеть своевременно отозвать сертификат. Использование токена или смарт-карты предполагает двухфакторную аутентификацию — введение PIN-кода. Скопировать информацию с носителя, не зная PIN-кода, представляется довольно сложной задачей. Однако токены более универсальны в связи с возможностью их использования на любом устройстве, оснащённом USB-портом. Хранение закрытого ключа осуществляется только у владельца электронной цифровой подписи. Но при этом дубликаты могут создаваться самим владельцем ЭЦП. Хранение составляющей ключевой пары с истёкшим сроком действия целесообразно с целью возможности в дальнейшем расшифровать документы из давнего электронного архива. Закрытый ключ является конфиденциальным. Ответственность за его сохранность в полной мере возлагается на владельца ЭЦП, что прописано на законодательном уровне. Сертификаты X. 509 Так повелось, что основным «активом» в PKI является сертификат X.509 . Сертификат — это что-то вроде паспорта, он содержит информацию, позволяющую идентифицировать субъект, которому выдан сертификат (поле Subject), указывает, кем он был выпущен (поле Issuer), серийный номер сертификата и многое другое. В Windows управлять сертификатами можно с помощью оснастки «Сертификаты» ( run->certmgr.msc ). Менеджер сертификатов При получении сертификата важно установить его в правильное хранилище. Так, сертификаты, которые ты хочешь использовать для электронной подписи, должны быть установлены в хранилище «Личное», а сертификаты получателей, которым нужно будет отправлять зашифрованные сообщения, — в хранилище «Доверенные лица». Сертификаты удостоверяющих центров (УЦ) должны быть установлены в хранилище «Доверенные корневые центры сертификации». При установке сертификата система предлагает два варианта: выбрать хранилище автоматически либо указать вручную. Рекомендую использовать второй вариант, так как автоматика иногда устанавливает сертификат не в то хранилище. Сертификат, которым мы хотим подписывать сообщения, должен иметь закрытый ключ. О наличии закрытого ключа можно узнать, посмотрев на свойства сертификата, где русским по белому будет написано: «есть закрытый ключ для этого сертификата». Закрытый ключ для сертификата Самое интересное о сертификате мы можем узнать на вкладке «Состав». Состав сертификата Обрати внимание на поля «Алгоритм подписи», «Алгоритм хеширования подписи» и «Открытый ключ». Если хочешь использовать сертификат для осуществления транзакций в России, во всех этих полях ты должен видеть слово «ГОСТ». Также следует обратить внимание на значение поля «Использование ключа» и поля «Действителен с» и «Действителен по»: первое позволит понять, возможно ли использование сертификата для выполнения нужной нам операции (шифрование, подпись), а второе и третье — возможно ли использовать данный сертификат в указанный момент времени. В дополнение к этому следует убедиться, что сертификат действителен. В этом нам поможет вкладка «Путь сертификации». Если с сертификатом все хорошо, мы увидим надпись: «Этот сертификат действителен». Состояние сертификата Как работает цифровая подпись Если вспомнить формальное определение, то ЭЦП — это реквизит электронного документа. Другими словами, последовательность битов, вычисленная уникально для каждого конкретного сообщения. Подпись может быть вычислена как с применением секретного ключа, так и без него. Без секретного ключа подпись представляет собой просто код, который может доказать, что документ не был изменен. С использованием секретного ключа подпись докажет целостность сообщения, позволит убедиться в его подлинности и аутентифицировать источник. Если ты читал вторую часть нашего цикла, то помнишь, что существуют симметричный и асимметричный подходы к шифрованию. С электронной подписью дела обстоят очень похоже — есть подписи с симметричным механизмом, а есть с асимметричным. Симметричный механизм подписи малоприменим на практике — никому не хочется генерировать ключи для каждой подписи заново. А как ты помнишь, именно в одинаковых ключах кроется фишка симметричной криптографии. В лучших традициях асимметричной криптографии — имеем пару открытый и секретный ключ. Но не спеши пролистывать все это описание. Электронная подпись концептуально отличается от шифрования применением ключей, описанного ранее. От документа или сообщения подсчитывается хеш-функция, которая сократит сообщение любого объема до определенного количества байтов. Посредством криптографических преобразований вычисляется сама электронная подпись. В отличие от асимметричного шифрования, подпись основана на закрытом ключе, а вот проверить с помощью открытого ключа ее может любой его обладатель. Если помнишь, в шифровании все происходит наоборот: шифруют для нас на открытом ключе, а вот расшифровывать мы будем с помощью секретного ключа. Электронная подпись предоставляется вместе с исходным документом на проверку. По полученной композиции можно доказать, что документ с момента вычисления подписи не был изменен. Схемы электронной подписи так же многообразны, как и способы шифрования. Чтобы схема подписи была стойкой, нужно, чтобы она основывалась на трудновычислимой математической задаче. Есть два типа таких задач: факторизация больших чисел и дискретное логарифмирование. Зачем нужны открытый и закрытый ключ ЭЦП Открытый и закрытый ключ электронной подписи решают разные задачи. Открытый ключ ЭЦП предназначен для зашифровки информации, в то время как закрытый призван обеспечить её расшифровку. Открытый ключ можно без опасений передавать, не рискуя при этом сохранностью данных. Работа ключевой пары осуществляется только при взаимодействии двух составляющих. Надёжная криптосистема успешно используется для заверения электронных документов. Удобный инструмент обеспечивает надлежащую конфиденциальность данных и защиту от фальсификации. Проверка электронной подписи Проверить открытую часть электронной подписи можно с помощью СКЗИ — средств криптографической защиты информации. Это может быть, например, программа КриптоПро CSP. Но это же можно проделать и в сторонних программах, и на веб-сайтах. Доступ к открытому элементу цифровой подписи публичный — воспользоваться им может кто угодно. Открытая часть ключа ЭЦП: как сделать Выдача этой части ключевой пары ЭЦП производится удостоверяющим центром, аккредитованным Минцифры, но это относится только к квалифицированным сертификатам. В его функции входит формирование собственного сертификата, сертификата конечного пользователя, заверение их аутентичности. Для учёта выданных сертификатов УЦ ведёт специальный реестр. Спектр выполняемых органом задач охватывает также аннулирование скомпрометированных сертификатов с последующим обновлением существующей базы. Цифровая подпись Представь, дорогой читатель, что ты занимаешься некой очень ответственной работой. И результаты своей работы отправляешь в виде отчетов, от которых в конечном итоге зависят чьи-то конкретные судьбы и жизни. Получатели твоих отчетов принимают на их основе очень важные решения, и, если ты напортачишь, вполне можешь получить срок. Так вот, в таких ответственных организациях без электронной подписи никуда. Она позволяет тебе подписать тот самый суперважный секретный отчет своим сертификатом с закрытым ключом. Закрытый ключ, в идеале, может храниться на токене — специальном съемном устройстве, похожем на флешку, которое ты в редкие моменты достаешь из сейфа. Подпись гарантирует, что твой отчет отправлен именно тобой, а не уборщицей или сторожем. С другой стороны, ты не сможешь отказаться от авторства (это называется «неотрекаемость») и, если накосячишь в своем суперважном документе, на сторожа свалить вину не получится. Электронная подпись применяется не только в спецслужбах и органах, но и в бизнесе. Например, для перевода пенсионных накоплений в НПФ: мы генерируем запрос на сертификат, отправляем его в удостоверяющий центр (УЦ). У Ц выпускает сертификат, мы подписываем сертификатом заявление на перевод пенсионных накоплений, отправляем — и вуаля. Подпись также позволяет осуществлять контроль целостности подписываемых данных . Если данные будут изменены, подпись не пройдет проверку. Для программирования подписи необходимо ознакомиться с несколькими классами . NET Framework: Перед тем как заюзать наш сертификат, необходимо его проверить. Процедура включает в себя проверку цепочки сертификации, проверку срока действия и проверку, не отозван ли сертификат. Если мы подпишем файл недействительным сертификатом, подпись будет недействительной. X509Chain certificateChain = new X509Chain { ChainPolicy = { RevocationMode = X509RevocationMode.Online, VerificationFlags = X509VerificationFlags.IgnoreNotTimeValid, RevocationFlag = X509RevocationFlag.ExcludeRoot } }; bool chainOk = certificateChain.Build(certificate); bool certNotExpired = (certificate.NotAfter >= DateTime.Now) && (certificate.NotBefore <= DateTime.Now); Мы проверили сертификат и убедились, что он в порядке. Переходим непосредственно к подписыванию данных. Подпись бывает двух видов: прикрепленная и открепленная . Прикрепленная и открепленная подписи Результатом прикрепленной подписи будет CMS (Cryptography Message Syntax) — сообщение, содержащее как подписываемые данные, так и саму подпись. Открепленная подпись содержит только саму подпись. Рекомендую использовать именно открепленную подпись, потому что с ней намного меньше мороки. В нее проще поставить метку времени, она меньше весит, так как не содержит подписываемые данные. Подписываемые данные легко открыть, посмотреть. В случае прикрепленной подписи для того, чтобы просмотреть подписанные данные, CMS-сообщение необходимо сначала декодировать. В общем, прикрепленной подписи я рекомендую избегать всеми силами. Если потребуется передавать подпись и контент вместе, рассмотри вариант архивирования (вместо использования прикрепленной подписи используй открепленную, просто заархивируй подписываемый файл и открепленную подпись). Посмотрим на код подписи (С#): public byte[] SignAttached(X509Certificate2 certificate, byte[] dataToSign) { ContentInfo contentInfo = new ContentInfo(dataToSign); SignedCms cms = new SignedCms(contentInfo, false); CmsSigner signer = new CmsSigner(certificate); cms.ComputeSignature(signer, false); return cms.Encode(); } public byte[] SignDetached(X509Certificate2 certificate, byte[] dataToSign) { ContentInfo contentInfo = new ContentInfo(dataToSign); SignedCms cms = new SignedCms(contentInfo, true); CmsSigner signer = new CmsSigner(certificate); cms.ComputeSignature(signer, false); return cms.Encode(); } Но, как обычно это бывает у Microsoft, стоит сделать маленький шаг в сторону, и розовый волшебный мир рушится Глядя на примеры кода, можно подумать, что работа с подписью в . NET реализована достаточно хорошо. Но рассмотрим, например, случай, в котором необходимо осуществить подпись большого файла, размером 600 MiB. Внимательные читатели обратили внимание на сигнатуру метода подписи — он принимает на вход массив байтов. При попытке загрузить в массив байтов 600 MiB мы получим OutOfMemoryException. Что же делать, спросишь ты? Обращаться к основам — отвечу я! Очевидно, раз нельзя загрузить в память 600 MiB, то необходимо файл грузить и обрабатывать по кусочкам. . NET-обертки над CMS так не умеют. На помощь нам приходит MS Crypto API. M S Crypto API содержит два набора функций для работы с CMS: Simplified Message Fuctions и Low Level Message Functions . Для работы с большими файлами нам нужны Low Level. Полную реализацию на C# можно посмотреть тут . Я же предпочитаю работать с криптографией на языке C++. Кода в результате писать приходится меньше, а работает он быстрее. Рассмотрим порядок действий для реализации подписи в поточном режиме: Получаем PCCERT\_CONTEXT ; Заполняем структуры CMSG\_STREAM\_INFO , CRYPT\_ALGORITHM\_IDENTIFIER , CMSG\_SIGNER\_ENCODE\_INFO , CMSG\_SIGNED\_ENCODE\_INFO ; Получаем хендл сообщения с помощью функции CryptMsgOpenToEncode . Для открепленной подписи необходимо передать соответствующий флаг CMSG\_DETACHED\_FLAG ; В цикле вызываем функцию CryptMsgUpdate и «скармливаем» ей по кусочкам файл, который необходимо подписать. На C++ будет что-то вроде: ISigner signer = null; // Заполняем структуры ... // Открываем сообщение для кодирования HCRYPTMSG hMsg = CryptMsgOpenToEncode ( (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING), // Message encoding type dwFlags, // Flags CMSG_SIGNED, // Message type &SignedMsgEncodeInfo, // Pointer to structure NULL, // Inner content object ID &stStreamInfo // Stream information (not used) ); ... // Обрабатываем файл для подписи по кусочкам while ( ( bytesRead = inputStream->Read(buf, blockSize, 0, blockSize ) ) > 0 ) { processedDataLen += bytesRead; BOOL lastcall = (processedDataLen == streamLength); BOOL successful = CryptMsgUpdate(hMsg, (const BYTE*)buf, bytesRead, lastcall); } // Закрываем хендл CryptMsgClose(hMsg); return S_OK; Вызов кода на C++ из C# будет выглядеть примерно так: ISigner signer = null; PkiFactory.CreateSigner(out signer); X509Store store = new X509Store("My"); store.Open(OpenFlags.ReadOnly); var cert = GetCertificates(store); string file = @"d:\tmp\masyanya.bin"; using (var inputStream = File.OpenRead(file)) using (var outputStream = File.Create(file + ".sig")) { var reader = new StreamReader(inputStream); var writer = new StreamWriter(outputStream); int result = signer.Sign(reader, writer, cert, 1); Debug.Assert(result == 0, "Подпись не прошла."); } Внимательный читатель удивится — что это за IStreamReader* inputStream , IStreamWriter* outputStream , ICertificate* signCertificate ? Ответ следует из названия переменных, но есть одна тонкость, которая для многих окажется сюрпризом. I StreamReader, IStreamWriter, ICertificate — это интерфейсы, реализованные на C#, и это не COM-объекты. При этом мы спокойно можем вызывать их методы в нативном C++. Как сделать такую красоту — тема отдельной статьи. В результате успешного выполнения операции мы получим криптографическое сообщение. Для кодирования сертификатов X.509 и криптографических сообщений используется Abstract Syntax Notation One , или, по-простому, ASN 1. Для просмотра файлов, закодированных в ASN 1, можно воспользоваться бесплатным ASN.1 Editor . Подпись изнутри Файл masks. key Содержит 32 байта маски ключа в формате Asn1, зашифрованного на ключе хранения pwd_key. Далее 12 байт «затравочной» информации для генерации ключа хранения pwd_key, если криптоконтейнер защищен паролем, то пароль также участвует в генерации ключа хранения. Далее контрольная сумма (имитозащита) 4 байта. Контрольной информацией для простоты мы пользоваться не будем, общий контроль будет осуществляться путем генерации открытого ключа и сравнения первых 8 байт полученного ключа с соответствующим полем из файла header.key: Картина мира Перед погружением в код давай разберем немного терминологии. P KI — инфраструктура открытых ключей. Как несложно догадаться, PKI основана на асимметричном шифровании. В симметричных шифрах для шифрования и расшифрования используется один ключ. В асимметричных для шифрования используется один ключ, а для расшифрования — другой. Вместе они образуют ключевую пару. Информация, необходимая для работы PKI, содержится в сертификате X.509 . В PKI участвуют как минимум три стороны: Алиса, Боб и удостоверяющий центр (УЦ). У Алисы и Боба есть сертификаты с закрытым ключом, подписанные так называемым корневым сертификатом УЦ. У Алисы есть сертификат Боба с открытым ключом, а у Боба — сертификат Алисы с открытым ключом. Алиса и Боб доверяют УЦ и благодаря этому могут доверять друг другу. Упрощенная структура PKI WARNING Приведенный ниже код предназначен исключительно для ознакомления с PKI. Не следует без оглядки использовать его в реальной работе. Читаем закрытый ключ и конвертируем #include <stdio.h> #include <stdlib.h> #include <string.h> #include <openssl/pem.h> #include <openssl/cms.h> #include <openssl/err.h> #include "gost_lcl.h" /* Convert little-endian byte array into bignum */ BIGNUM *reverse32bn(char *b, BN_CTX *ctx) { BIGNUM *res; char buf[32]; BUF_reverse(buf, b, 32); res = BN_bin2bn(buf, 32, BN_CTX_get(ctx)); OPENSSL_cleanse(buf, sizeof(buf)); return res; } void xor_material(char *buf36, char *buf5C, char *src) { int i; for(i = 0; i < 32; i++) { buf36[i] = src[i] ^ 0x36; buf5C[i] = src[i] ^ 0x5C; } } int make_pwd_key(char *result_key, char *start12, int start12_len, char *passw) { int result; int i; char pincode4[1024]; int pin_len; char current[32]; char material36[32]; char material5C[32]; char hash_result[32]; gost_hash_ctx ctx; init_gost_hash_ctx(&ctx, &GostR3411_94_CryptoProParamSet); memset(pincode4, 0, sizeof(pincode4)); pin_len = strlen(passw); if (pin_len*4 > sizeof(pincode4)) { result = 1; goto err; } for(i = 0; i < pin_len; i++) pincode4[i*4] = passw[i]; start_hash(&ctx); hash_block(&ctx, start12, start12_len); if (pin_len) hash_block(&ctx, pincode4, pin_len * 4); finish_hash(&ctx, hash_result); memcpy(current, (char*)"DENEFH028.760246785.IUEFHWUIO.EF", 32); for(i = 0; i < (pin_len?2000:2); i++) { xor_material(material36, material5C, current); start_hash(&ctx); hash_block(&ctx, material36, 32); hash_block(&ctx, hash_result, 32); hash_block(&ctx, material5C, 32); hash_block(&ctx, hash_result, 32); finish_hash(&ctx, current); } xor_material(material36, material5C, current); start_hash(&ctx); hash_block(&ctx, material36, 32); hash_block(&ctx, start12, start12_len); hash_block(&ctx, material5C, 32); if (pin_len) hash_block(&ctx, pincode4, pin_len * 4); finish_hash(&ctx, current); start_hash(&ctx); hash_block(&ctx, current, 32); finish_hash(&ctx, result_key); result = 0; //ok err: return result; } BIGNUM *decode_primary_key(char *pwd_key, char *primary_key, BN_CTX *bn_ctx) { BIGNUM *res; char buf[32]; gost_ctx ctx; gost_init(&ctx, gost_cipher_list->sblock); gost_key(&ctx, pwd_key); gost_dec(&ctx, primary_key, buf, 4); res = reverse32bn(buf, bn_ctx); OPENSSL_cleanse(buf, sizeof(buf)); return res; } BIGNUM *remove_mask_and_check_public(char *oid_param_set8, BIGNUM *key_with_mask, BIGNUM *mask, char *public8, BN_CTX *ctx) { int result; EC_KEY *eckey = NULL; const EC_POINT *pubkey; const EC_GROUP *group; BIGNUM *X, *Y, *order, *raw_secret, *mask_inv; char outbuf[32], public_X[32]; ASN1_OBJECT *obj; int nid; order = BN_CTX_get(ctx); mask_inv = BN_CTX_get(ctx); raw_secret = BN_CTX_get(ctx); X = BN_CTX_get(ctx); Y = BN_CTX_get(ctx); if (!order || !mask_inv || !raw_secret || !X || !Y) { result = 1; goto err; } obj = ASN1_OBJECT_create(0, oid_param_set8+1, *oid_param_set8, NULL, NULL); nid = OBJ_obj2nid(obj); ASN1_OBJECT_free(obj); if (!(eckey = EC_KEY_new())) { result = 1; goto err; } if (!fill_GOST2001_params(eckey, nid)) { result = 1; goto err; } if (!(group = EC_KEY_get0_group(eckey))) { result = 1; goto err; } if (!EC_GROUP_get_order(group, order, ctx)) { result = 1; goto err; } if (!BN_mod_inverse(mask_inv, mask, order, ctx)) { result = 1; goto err; } if (!BN_mod_mul(raw_secret, key_with_mask, mask_inv, order, ctx)) { result = 1; goto err; } if (!EC_KEY_set_private_key(eckey, raw_secret)) { result = 1; goto err; } if (!gost2001_compute_public(eckey)) { result = 1; goto err; } if (!(pubkey = EC_KEY_get0_public_key(eckey))) { result = 1; goto err; } if (!EC_POINT_get_affine_coordinates_GFp(group, pubkey, X, Y, ctx)) { result = 1; goto err; } store_bignum(X, outbuf, sizeof(outbuf)); BUF_reverse(public_X, outbuf, sizeof(outbuf)); if (memcmp(public_X, public8, 8) != 0) { result = 1; goto err; } result = 0; //ok err: if (eckey) EC_KEY_free(eckey); if (result == 0) return raw_secret; return NULL; } int file_length(char *fname) { int len; FILE *f = fopen(fname, "rb"); if (f == NULL) return -1; fseek(f, 0, SEEK_END); len = ftell(f); fclose(f); return len; } int read_file(char *fname, int start_pos, char *buf, int len) { int read_len; FILE *f = fopen(fname, "rb"); if (f == NULL) return 1; if (start_pos) fseek(f, start_pos, SEEK_SET); read_len = fread(buf, 1, len, f); fclose(f); if (read_len != len) return 1; return 0; //ok } int get_asn1_len(unsigned char *buf, int *size_hdr) { int n, i, res; int pos = 0; if ((buf[pos]&0x80) == 0) { *size_hdr = 1; return buf[pos]; } n = buf[pos++]&0x7f; res = 0; for(i = 0; i < n; i++) { res = res*256 + buf[pos++]; } *size_hdr = n+1; return res; } #define MAX_HEADER 20000 int read_container(char *fpath, int flag2, char *salt12, char *primary_key, char *masks_key, char *public8, char *oid_param_set8) { int result; char primary_path[1024+30]; char masks_path[1024+30]; char header_path[1024+30]; char header_buf[MAX_HEADER]; int header_len; int i, len, pos, size_hdr; if (strlen(fpath)>1024) { result = 1; goto err; } sprintf(header_path, "%s/header.key", fpath); if (flag2 == 0) { sprintf(primary_path, "%s/primary.key", fpath); sprintf(masks_path, "%s/masks.key", fpath); } else { sprintf(primary_path, "%s/primary2.key", fpath); sprintf(masks_path, "%s/masks2.key", fpath); } if (read_file(primary_path, 4, primary_key, 32)) { result = 1; goto err; } if (read_file(masks_path, 4, masks_key, 32)) { result = 1; goto err; } if (read_file(masks_path, 0x26, salt12, 12)) { result = 1; goto err; } header_len = file_length(header_path); if (header_len < 0x42 || header_len > MAX_HEADER) { result = 1; goto err; } if (read_file(header_path, 0, header_buf, header_len)) { result = 1; goto err; } //------------- skip certificate --------------------------- pos = 0; for(i = 0; i < 2; i++) { get_asn1_len(header_buf+pos+1, &size_hdr); pos += size_hdr+1; if (pos > header_len-8) { result = 2; goto err; } } //------------------ get oid_param_set8 ----------------------- #define PARAM_SET_POS 34 if (memcmp(header_buf+pos+PARAM_SET_POS, "\x6\x7", 2) != 0) { result = 2; goto err; } memcpy(oid_param_set8, header_buf+pos+PARAM_SET_POS+1, 8); //------------------ get public8 ----------------------- result = 2; //not found pos += 52; for(i = 0; i < 3; i++) { len = get_asn1_len(header_buf+pos+1, &size_hdr); if (len == 8 && memcmp(header_buf+pos, "\x8a\x8", 2) == 0) { memcpy(public8,header_buf+pos+2,8); result = 0; //ok break; } pos += len+size_hdr+1; if (pos > header_len-8) { result = 2; goto err; } } err: OPENSSL_cleanse(header_buf, sizeof(header_buf)); return result; } #define START_OID 0x12 #define START_KEY 0x28 unsigned char asn1_private_key[72] = { 0x30,0x46,2,1,0,0x30,0x1c,6,6,0x2a,0x85,3,2,2,0x13,0x30,0x12,6,7,0x11, 0x11,0x11,0x11,0x11,0x11,0x11,6,7,0x2a,0x85,3,2,2,0x1e,1,4,0x23,2,0x21,0 }; int main(int argc, char **argv) { int result; char *container_path; char *passw; char salt12[12]; char primary_key[32]; char masks_key[32]; char public8[8]; char oid_param_set8[8]; BN_CTX *ctx; BIGNUM *key_with_mask; BIGNUM *mask; BIGNUM *raw_key; char pwd_key[32]; char outbuf[32]; ctx = BN_CTX_new(); if (argc == 2) { container_path = argv[1]; passw = ""; } else if (argc == 3) { container_path = argv[1]; passw = argv[2]; } else { printf("get_private container_path [passw]\n"); result = 1; goto err; } if (read_container(container_path, 0, salt12, primary_key, masks_key, public8, oid_param_set8) != 0 && read_container(container_path, 1, salt12, primary_key, masks_key, public8, oid_param_set8) != 0) { printf("can not read container from %s\n", container_path); result = 2; goto err; } make_pwd_key(pwd_key, salt12, 12, passw); key_with_mask = decode_primary_key(pwd_key, primary_key, ctx); OPENSSL_cleanse(pwd_key, sizeof(pwd_key)); mask = reverse32bn(masks_key, ctx); raw_key = remove_mask_and_check_public(oid_param_set8, key_with_mask, mask, public8, ctx); if (raw_key) { BIO *bio; store_bignum(raw_key, outbuf, sizeof(outbuf)); memcpy(asn1_private_key+START_OID, oid_param_set8, 8); memcpy(asn1_private_key+START_KEY, outbuf, 32); //bio = BIO_new_file("private.key", "w"); bio = BIO_new_fp(stdout, BIO_NOCLOSE | BIO_FP_TEXT); PEM_write_bio(bio, "PRIVATE KEY", "", asn1_private_key, sizeof(asn1_private_key)); BIO_free(bio); OPENSSL_cleanse(outbuf, sizeof(outbuf)); OPENSSL_cleanse(asn1_private_key, sizeof(asn1_private_key)); result = 0; //ok } else { printf("Error check public key\n"); result = 3; } err: BN_CTX_free(ctx); OPENSSL_cleanse(salt12, sizeof(salt12)); OPENSSL_cleanse(primary_key, sizeof(primary_key)); OPENSSL_cleanse(masks_key, sizeof(masks_key)); return result; } Основную работу выполняют следующие 3 функции: 1. Создаем ключ хранения исходя из 12-ти байтовой «соли» и пароля. make_pwd_key(pwd_key, salt12, 12, passw); 2. Расшифровываем основной ключ на ключе хранения. key_with_mask = decode_primary_key(pwd_key, primary_key, ctx); 3. Делим ключ с маской на маску. raw_key = remove_mask_and_check_public(oid_param_set8, key_with_mask, mask, public8, ctx); Но так как в библиотеке OpenSSL операция деления по модулю традиционно отсутствует, пользуемся операцией взятия обратного числа и умножением. if (!BN_mod_inverse(mask_inv, mask, order, ctx)) { result = 1; goto err; } if (!BN_mod_mul(raw_secret, key_with_mask, mask_inv, order, ctx)) { result = 1; goto err; } Факторизация больших чисел Рассмотрим на практике электронную подпись на основе знаменитого алгоритма RSA. Шифрование RSA мы рассматривать не стали — это мейнстрим, и в той же «Википедии» есть его подробное описание . 1. Генерация ключей Причина стойкости RSA кроется в сложности факторизации больших чисел. Другими словами, перебором очень трудно подобрать такие простые числа, которые в произведении дают модуль n. Ключи генерируются одинаково для подписи и для шифрования. Когда ключи сгенерированы, можно приступить к вычислению электронной подписи. 2. Вычисление электронной подписи 3. Проверка электронной подписи RSA, как известно, собирается уходить на пенсию, потому что вычислительные мощности растут не по дням, а по часам. Недалек тот день, когда 1024-битный ключ RSA можно будет подобрать за считаные минуты. Впрочем, о квантовых компьютерах мы поговорим в следующий раз. В общем, не стоит полагаться на стойкость этой схемы подписи RSA, особенно с такими «криптостойкими» ключами, как в нашем примере. Расшифрование При расшифровании необходимо, чтобы сертификат, указанный при шифровании в коллекции получателей, был установлен в хранилище сертификатов. Так как сообщение может быть зашифровано и адресовано нескольким получателям, для расшифрования нам необходимо найти того получателя, сертификат которого установлен в нашем хранилище сертификатов. public byte[] Decrypt(byte[] encryptedData) { var envelopedCms = new EnvelopedCms(); envelopedCms.Decode(encryptedData); X509Store store = new X509Store("My"); store.Open(OpenFlags.ReadOnly); RecipientInfo recipientInfo = envelopedCms.RecipientInfos.Cast<RecipientInfo>() .FirstOrDefault(x => FindCertificate((X509IssuerSerial)x.RecipientIdentifier.Value) != null); envelopedCms.Decrypt(recipientInfo); return envelopedCms.ContentInfo.Content; } private X509Certificate2 FindCertificate(X509IssuerSerial issuerSerial) { var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); return store.Certificates .Find(X509FindType.FindByIssuerDistinguishedName, issuerSerial.IssuerName, false) .Find(X509FindType.FindBySerialNumber, issuerSerial.SerialNumber, false).Cast<X509Certificate2>() .FirstOrDefault(); } Файл header. key Из этого файла нам потребуется параметры электронной подписи CryptoProParamSet (подчеркнуто красным). GostR3410_2001_CryptoPro_A_ParamSet — 1.2.643.2.2.35.1 GostR3410_2001_CryptoPro_B_ParamSet — 1.2.643.2.2.35.2 GostR3410_2001_CryptoPro_C_ParamSet — 1.2.643.2.2.35.3 GostR3410_2001_CryptoPro_XchA_ParamSet — 1.2.643.2.2.36.0 GostR3410_2001_CryptoPro_XchB_ParamSet — 1.2.643.2.2.36.1 А также первые 8 байт открытого ключа (подчеркнуто) для контроля правильности чтения закрытого. Цифровая подпись в Bitcoin Помимо прочего, электронная подпись используется в криптовалютах, в частности — в Bitcoin. У каждого пользователя Bitcoin есть пара из секретного и открытого ключа. Хеш-значение открытого ключа служит основным адресом для передачи монет. Это значение не секретно, и сообщать его можно кому угодно. Но по значению хеша вычислить значение открытого ключа невозможно. Сама пара ключей будет использована лишь однажды — при передаче прав собственности. На этом жизнь пары ключей заканчивается. PUB1 — публичный ключ; PRIV1 — секретный ключ; HASH1 или HASH(PUB1) — хеш-значение открытого ключа (биткойн-адрес); HASH2 или HASH(PUB2) — хеш открытого ключа следующего владельца. Вот как устроен сам процесс передачи прав собственности на биткойны. Владелец монеты открыто сообщает хеш своего публичного ключа HASH(PUB1), это и будет идентифицировать биткойн. До момента продажи оба ключа PUB1, PRIV1 продавца остаются в секрете. Известен только HASH(PUB1) и соответствующий ему биткойн. Как только появляется покупатель, владелец формирует открытое письмо, в котором указывает адрес биткойна HASH(PUB1) и хеш-значение публичного ключа нового владельца HASH(PUB2). И конечно же, подписывает письмо своим секретным ключом PRIV1, прилагая публичный ключ PUB1. После этого пара ключей владельца PUB1 и PRIV1 теряют свою актуальность. Публичным ключом можно проверить само письмо, узнать новый адрес монеты. О втором собственнике ничего не известно, кроме HASH(PUB2), до тех пор пока он не передаст права третьему владельцу. И эта цепочка может быть бесконечной. Подписывая передачу прав с использованием ЭЦП, собственник подтверждает не только свою личность, но и свое согласие на проведение сделки. То есть вернуть монетку он уже не может и с этим согласился, подписавшись электронной подписью. Такая технология построения цепи передачи прав и называется блокчейном. Благодаря этой технологии можно отследить историю владения до самых истоков, но изменить эту историю никак нельзя. INFO Среди . NET-девелоперов бытует мнение, что программировать на C++ сложнее и дольше. Уверяю тебя, в случае с криптографией ситуация противоположна. Гораздо быстрее написать код на C++ и вызвать его из . NET. Дискретное логарифмирование Это вторая сложная проблема, на которой основаны цифровые подписи. Для начала хорошо бы усвоить, что такое дискретный логарифм. Для кого-то такое словосочетание может звучать пугающе, но на самом деле это одна из самых простых для понимания вещей в этой статье. Предположим, дано уравнение 4x = 13 (mod 15) . Задача нахождения x и есть задача дискретного логарифмирования. Почему же она так сложна для вычисления? Попробуй решить это уравнение перебором! Компьютер, ясное дело, будет более успешен, но и задачи дискретного логарифмирования обычно далеко не так просты. Возьмем для примера схему Эль-Гамаля. 1. Генерация подписи 2. Проверка подписи Даже если не вникать в схему, понятно, что такой алгоритм сложнее. Кроме того, нигде уже не используется простой модуль, его сменили эллиптические кривые. Эллиптическая кривая — это кривая, которая задана кубическим уравнением и имеет невообразимо сложное представление. Задача решения логарифма в группе точек, которые принадлежат эллиптической кривой, вычислительно сложная, и на данный момент не существует таких мощностей, которые решали бы это уравнение за полиномиальное время, если длина секретного ключа составляет 512 бит. Согласно задаче дискретного логарифмирования, невероятно сложно найти на кривой две такие точки, которые связывает операция возведения в некоторую степень. ЭЦП на практике В России, как и во многих развитых странах, электронная подпись имеет официальный юридический статус. У нас этот факт регламентирует закон № 63-ФЗ «Об электронной подписи». Однако он утверждает, что юридической силой обладает далеко не любая электронная подпись, а только соответствующая определенным критериям: подпись сгенерирована посредством криптографического преобразования с секретным ключом; этот ключ и соответствующий ему открытый ключ выданы квалифицированным удостоверяющим центром; по подписи можно достоверно установить ее обладателя. Подпись также должна быть вычислена средствами, соответствующими требованиям закона. Этим требованиям удовлетворяет отечественный алгоритм шифрования ГОСТ 34.10—2012. Он использует математический аппарат эллиптических кривых, является достаточно стойким и официально используется для разработки криптографических средств, реализующих электронную подпись. Для того чтобы попробовать неквалифицированную подпись — без сертификата удостоверяющего центра, можно воспользоваться известной PGP. Потестировать подпись можно, к примеру, на сайте ReadVerify . Стоит сказать, что в нашей стране электронная подпись используется чаще, чем можно себе представить. В банках, налоговых, торгово-закупочных операциях, бухгалтерии — во всех этих организациях используется или внедряется ЭЦП. Электронная подпись отважно борется со злом бюрократии, однако до полной победы еще далеко. За рубежом электронный документооборот процветает еще дольше. Официальный стандарт электронной подписи в США DSS (Digital Signature Standard) также использует эллиптические кривые и основан на описанной выше схеме Эль-Гамаля. Проверка подписи и декодирование А теперь, дорогой читатель, представь, что ты большой начальник и должен принять важное стратегическое решение на основе отчета, который тебе прислал сотрудник по электронной почте. Для твоего удобства отчет был подписан открепленной подписью. Открыв почту и скачав отчет, ты, как опытный, знающий жизнь человек, не спешишь принимать на веру содержимое отчета и проверяешь подпись. После проверки выясняешь, что подпись неверна — не сошлась контрольная сумма. В результате оповещаешь службу безопасности, которая проводит расследование и выясняет, что хитрые конкуренты взломали почтовый сервер и отправили тебе фальшивый документ. Тебя наградили за бдительность, конкурентов посадили, а компания наконец-то получила оригинальный отчет с проверенной электронной подписью. Если пользователь прислал тебе отчет в виде прикрепленной подписи, тебе для чтения придется его декодировать: public bool VerifyAttached(byte[] dataToVerify) { try { var cms = new SignedCms(); cms.Decode(dataToVerify); foreach (var signer in cms.SignerInfos) { signer.CheckSignature(true); } return true; } catch (CryptographicException) { return false; } } public byte[] Decode(byte[] signedCms) { var cms = new SignedCms(); cms.Decode(signedCms); return cms.ContentInfo.Content; } Нетрудно догадаться, что и тут разработчики . NET Framework подложили нам свинью. Не можем мы проверить подпись большого файла! По той же самой причине — OutOfMemoryException. Но и эту проблему несложно решить, обратившись к магии MS Crypto API. Так как код поточной проверки подписи достаточно длинный, остановлюсь на основных моментах: // Заполняем структуры ... // Открываем сообщение для декодирования HCRYPTMSG msg = CryptMsgOpenToDecode(...); // Декодируем сообщение по кусочкам while ((bytesRead = inputStream->Read(&buf.at(0), blockSize, 0, blockSize)) > 0) { totalBytesRead += bytesRead; CryptMsgUpdate(msg, &buf.at(0), bytesRead, totalBytesRead == fileSize); } // Получаем информацию о подписанте PCCERT_CONTEXT pSignerCertContext = CertGetSubjectCertificateFromStore( hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (PCERT_INFO)((void*)(&signerCertInfo.at(0)))); // Проверяем подпись BOOL ok = CryptMsgControl(msg, 0, CMSG_CTRL_VERIFY_SIGNATURE, pSignerCertContext->pCertInfo)); А так будет выглядеть код проверки подписи при вызове из C#: ISignatureVerifier verifier = null; PkiFactory.CreateSignatureVerifier(out verifier); using (var inputStream = File.OpenRead(file + ".sig")) using (var contentStream = File.OpenRead(file)) { var reader = new StreamReader(inputStream); var contentReader = new StreamReader(contentStream); var error = verifier.VerifyDetachedSign(contentReader, reader); Debug.Assert(error == 0, "Проверка подписи не прошла"); } Как происходит подписание документа с помощью ЭЦП Система подписания документов с помощью электронной подписи выглядит следующим образом: Электронная подпись присоединяется не к цифровому документу. Э П ставится на его сжатую версию — хэш. Таким образом, сокращается время шифрования, так как хэш файла весит меньше, чем сам файл. Для создания хэша применяются криптографические хэш-функции. При данном способе объёмный текст файла не делится на отдельные модули и сохраняет свой порядок. После создания хэша, закрытый ключ его шифрует и передаёт получателю вместе с сертификатом электронной подписи. Открытый ключ ЭП адресата расшифровывает информацию и проверяет подлинность сертификата отправителя. Закрытый ключ электронной подписи хранят в памяти компьютера или физических носителях: USB-токенах и смарт-картах. Согласно закону «Об электронной подписи» 63-ФЗ, ответственность за хранение закрытого ключа несёт владелец. Программы для работы и алгоритмы шифрования С ЭЦП не получится работать сразу. Чтобы шифровать и подписывать документы, недостаточно только иметь сертификат и закрытый ключ, для работы нужно устанавливать специальные программы. С помощью этих программ, которые работают по определённому стандарту шифрования (в России — ГОСТ 34.10-2018 ), обеспечивается связь закрытого и открытого ключа с документами. Расшифровка ЭЦП: электронная цифровая подпись. Данное понятие является устаревшим. Сегодня используется термин — электронная подпись, сокращённо ЭП. Одной из самых популярных программ-криптопровайдеров в России является «КриптоПро CSP» . С её помощью можно подписывать и шифровать документы, проверять сертификаты на подлинность, контролировать целостность соответствующего программного обеспечения. Файл primary. key Содержит 32 байта ключа в формате Asn1. Это только половина ключа, полный ключ получается при делении этого числа по модулю Q на маску. Поле, хранящее модуль Q в библиотеке OpenSSL имеет название order. Маска лежит в файле masks.key: Электронная подпись Время на прочтение Развитие цифровых технологий упростило многие моменты нашей жизни, в том числе, обмениваться документами и письмами в электронной форме оказалось быстрее и удобнее, чем в бумажной. Однако у электронного документооборота была проблема: нужно было как- то подтверждать подлинность электронных бумаг. На бумаге для этого использовались подписи, однако в цифровом формате их было достаточно легко подделать, поэтому для электронных документов требовался более надежный метод подтверждения. Поэтому и стал популярным аналог ручных подписей — электронная подпись, о которой будет рассказано дальше. Сначала вкратце опишу то, как работает электронная подпись (ЭП) и в чём заключается её надёжность. Электронная подпись – подтверждение того, что какой-либо электронный документ был создан и подписан определённым физическим или юридическим лицом. При этом она должна обладать следующими свойствами: Неотказуемость – подписавшее документ лицо не может утверждать, что это сделал кто-то другой; Целостность – внесение исправлений в уже подписанный документ должно нарушать подпись; Авторство – электронная подпись должна быть жёстко закреплена за определённым физическим или юридическим лицом. Эти базовые принципы и делают ЭП эффективной и безопасной в использовании. Немного теории В качестве алгоритмической базы для электронной подписи обычно применяют методы шифрования с открытым ключом. Подробнее о самих алгоритмах можно почитать на википедии или на хабре . Их суть сводится к тому, что для желающего обзавестись собственной электронной подписью специальным образом выбирается пара ключей – открытый и закрытый. Первый, как следует из названия, доступен всем, а второй владельцу подписи лучше держать в секрете. Естественно, ключи выбираются так, чтобы закрытый ключ нельзя было легко угадать по открытому. После чего закрытый ключ используется для шифрования документа (иными словами, его подписания), а открытый – для расшифровки (то есть для проверки подписи). При этом алгоритмы выбора ключей гарантируют, что открытым ключом можно расшифровать только те документы, которые шифровались соответсвующим ему закрытым ключом. Таким образом, если мы знаем открытый ключ владельца ЭП и смогли расшифровать им полученный от него документ, то он был точно подписан тем самым владельцем. Так работают асимметричные схемы ЭП. Применение хэш-функций В связи с тем, что шифрование закрытым ключом документа большого размера – довольно сложный и долгий процесс, обычно к тексту документа сначала применяют более быстрое и простое хэш-шифрование . Полученный сравнительно короткий результат шифруют закрытым ключом, получая саму цифровую подпись. Вместе с ней открытый текст документа передаётся получателю. Подробная схема с применением хэш-функции Тот должен всего лишь хэшировать текст документа той же хэш-функцией, после чего расшифровать открытым ключом подпись и сравнить оба результата. Если они совпадают – то документ мог быть подписан только отправителем (вот она и неотказуемость) и не был испорчен, дополнен или подменён в процессе пересылки (а это целостность). А дляпоследнего свойства – авторства – необходимо, чтобы пара ключей подписавшего была закреплена за ним. На практике для этого используются так называемые удостоверяющие центры, которые по запросу выдают сертификат на пару ключей, а также гарантируют единственность обладания ими. Что же может пойти не так? Наверное, внимательные читатели уже увидели, что описанная выше схема представляет собой сферическую ЭП в вакууме. И действительно, в теории взломать или подделать такую подпись можно несколькими способами. Самый лакомый способ для взломщика-криптоаналитика – это по открытому ключу угадать закрытый. Тогда злоумышленнику сразу открываются сказочные перспективы – ведь он сможет действовать от лица истинного владельца подписи и даже управлять его имуществом. Однако обычно такой взлом не возможен ввиду того, что подбор закрытого ключа по открытому – вычислительно нерешаемая задача. При генерации пары ключей широко применяются факторизация или дискретное логарифмирование, что оставляет взломщику мало надежды. Конечно можно попробовать подобрать закрытый ключ полным перебором, но при достаточно большом размере ключей такая возможность отпадает. Ещё одно уязвимое место – это хэш-функция. Здесь возможны сразу несколько направлений атак. Если алгоритм хэширования не достаточно надёжный, то взломщик может подобрать какой-нибудь свой документ, применение хэш-функции к которому даст тот же результат, что и её применение к исходному документу. Или же злоумышленник может сгенерировать два документа, дающие одинаковый хэш, после чего при необходимости сможет подменить один документ другим. Названные ситуации считаются коллизиями хэш-функций . Однако жизнь взломщика хэш-функции всё же не так легка: мало того, что подставной документ должен представлять из себя читаемый текст, а не быть бессмысленным набором бит, так еще и придумано достаточно криптостойких алгоритмов хэширования. Для примера, надёжными на момент написания статьи являются SHA-3 , BLAKE2 , семейство JH или отечественный «Стрибог» (он же ГОСТ 34.11-2018 ). Подключаем человеческий фактор, бюрократию и частные организации В самом начале мы говорили о свойствах, которыми должна обладать качественная электронная подпись. Из них под действием уже упомянутых выше атак пока страдали только её целостность и неотказуемость. Однако в реалиях нашего мира наибольшее количество нарушений происходит из-за подмены авторства. В России получение ЭП регулируется законом № 63-ФЗ «Об электронной подписи» . Для её оформления нужно получить сертификат от удостоверяющего центра (УЦ) на выбор . В УЦ нужно предоставить необходимые документы и заплатить некоторую сумму, после чего забрать свой сертификат и заветный eToken с закрытым ключом. Вот на получении сертификатов-то и возникает простор для всевозможных махинаций. Все УЦ являются коммерческими организациями, они обязательно аккредитованы Министерством цифрового развития и имеют лицензию от ФСБ. Однако чего не сделаешь ради прибыли — для УЦ порой желания клиентов выше установленных правил выдачи сертификата. Например, из-за этого сегодня возможно получить ЭП на другое лицо , пользуясь утечками персональных данных или некомпетентностью УЦ. Это приводит к довольно печальным последствиям – вплоть до переоформления квартиры или регистрации фиктивных организаций с целью взятия кредитов. Данные способы подделки подписей не требуют даже знания криптографии, достаточно лишь воспользоваться особенностями сертифицирования ЭП в России. Заключение В статье вкратце было рассмотрено, как работает электронная подпись. Видно, что алгоритм достаточно устойчив и почти не имеет слабых мест. Однако, как и во многих случаях, самое слабое место этого метода — человеческий фактор, оставляющий большой простор для действий злоумышленников.
- Сущность технологии ЭЦП
- Понятие закрытого ключа
- Сертификаты X. 509
- Как работает цифровая подпись
- Зачем нужны открытый и закрытый ключ ЭЦП
- Проверка электронной подписи
- Открытая часть ключа ЭЦП: как сделать
- Цифровая подпись
- Файл masks. key
- Картина мира
- WARNING
- Читаем закрытый ключ и конвертируем
- Факторизация больших чисел
- 1. Генерация ключей
- 2. Вычисление электронной подписи
- 3. Проверка электронной подписи
- Расшифрование
- Файл header. key
- Цифровая подпись в Bitcoin
- INFO
- Дискретное логарифмирование
- 1. Генерация подписи
- 2. Проверка подписи
- ЭЦП на практике
- Проверка подписи и декодирование
- Как происходит подписание документа с помощью ЭЦП
- Программы для работы и алгоритмы шифрования
- Файл primary. key
- Электронная подпись
- Немного теории
- Применение хэш-функций
- Что же может пойти не так?
- Подключаем человеческий фактор, бюрократию и частные организации
- Заключение
Принцип работы электронной подписи
Электронная подпись работает по асимметричному принципу шифрования. То есть документ зашифровывается с помощью закрытого ключа, а расшифровывается с помощью открытого.
<img src="https://astral.ru/upload/iblock/817/7ead9ue8nkkllb5uoqq8a4eu9sh4np3x/%D0%A1%D1%85%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%AD%D0%A6%D0%9F%20%D0%B8%20%D0%B5%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B8%20
.png» alt=»
Принцип работы»>
Объясним принцип работы ЭЦП на пальцах. Подписание документа производится в несколько этапов:
- Хеш документа шифруется с помощью закрытого ключа.
- Полученная подпись добавляется к документу.
- К документу прикрепляется сертификат проверки.
Как устроена электронная подпись
Электронная цифровая подпись — это устройство со сложной технической составляющей.
<img src="https://astral.ru/upload/iblock/571/vcrrtp1wi9ep226omt1xfmadqkxm8gv2/%D0%A4%D0%BE%D1%80%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%BE%D0%BD%D0%BD%D0%BE%D0%B9%20%D1%86%D0%B8%D1%84%D1%80%D0%BE%D0%B2%D0%BE%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%BF%D0%B8%D1%81%D0%B8%20
.png» alt=»
цифровая подпись»>
Электронная подпись состоит из двух основных частей:
- Открытый ключ, он же сертификат.
- Закрытый ключ — криптографическая часть.
Эти составные части выполняют разные функции: с помощью закрытого ключа, доступного только владельцу, документ шифруется, а с помощью сертификата, доступного для всех, документ дешифруется. Таким образом, достигается цель использования ЭЦП — подтверждается то, кем был подписан документ, и заверяется его неизменность с момента подписания.
Закрытый ключ не содержит в себе ничего, кроме механизма, с помощью которого он может шифровать документы. Сертификат же несёт в себе такую полезную информацию, как сведения о владельце, сведения об удостоверяющем центре, срок действия цифровой электронной подписи и т.д. Сертификат выступает в роли главного носителя информации о ЭЦП.
Сущность технологии ЭЦП
Электронная подпись предусматривает применение закрытого и открытого ключей одновременно. Сама технология реализована на базе связки компонентов ЭЦП. Ключи связаны за счёт математического соотношения. Подобная технология обеспечивает безопасность шифрования данных. Даже в случае перехвата информации её практически невозможно расшифровать.
Понятие закрытого ключа
Данные шифруются с помощью открытого ключа, а расшифровать их можно только тем закрытым ключом, который связан именно с этим открытым ключом.
Закрытый ключ ЭЦП именуется также секретным. Этот компонент криптосистемы считается более уязвимым и подверженным взлому. Его получение злоумышленником позволяет создать действительную электронную подпись от имени автора. Поэтому особую важность приобретает хранение криптографической составляющей в надёжном месте. Персональный компьютер не может обеспечить надлежащую защиту ключевой пары. Закрытый ключ ЭЦП — это уникальное сочетание символов, для хранения которых используется цифровой носитель. Им могут служить:




Похищение либо потеря устройства хранения данных могут быть сразу обнаружены пользователем — можно успеть своевременно отозвать сертификат. Использование токена или смарт-карты предполагает двухфакторную аутентификацию — введение PIN-кода. Скопировать информацию с носителя, не зная PIN-кода, представляется довольно сложной задачей. Однако токены более универсальны в связи с возможностью их использования на любом устройстве, оснащённом USB-портом.
Хранение закрытого ключа осуществляется только у владельца электронной цифровой подписи. Но при этом дубликаты могут создаваться самим владельцем ЭЦП. Хранение составляющей ключевой пары с истёкшим сроком действия целесообразно с целью возможности в дальнейшем расшифровать документы из давнего электронного архива.
Закрытый ключ является конфиденциальным. Ответственность за его сохранность в полной мере возлагается на владельца ЭЦП, что прописано на законодательном уровне.
Сертификаты X. 509
Так повелось, что основным «активом» в PKI является сертификат X.509
. Сертификат — это что-то вроде паспорта, он содержит информацию, позволяющую идентифицировать субъект, которому выдан сертификат (поле Subject), указывает, кем он был выпущен (поле Issuer), серийный номер сертификата и многое другое. В Windows управлять сертификатами можно с помощью оснастки «Сертификаты» ( run->certmgr.msc
).

При получении сертификата важно установить его в правильное хранилище. Так, сертификаты, которые ты хочешь использовать для электронной подписи, должны быть установлены в хранилище «Личное», а сертификаты получателей, которым нужно будет отправлять зашифрованные сообщения, — в хранилище «Доверенные лица». Сертификаты удостоверяющих центров (УЦ) должны быть установлены в хранилище «Доверенные корневые центры сертификации». При установке сертификата система предлагает два варианта: выбрать хранилище автоматически либо указать вручную. Рекомендую использовать второй вариант, так как автоматика иногда устанавливает сертификат не в то хранилище. Сертификат, которым мы хотим подписывать сообщения, должен иметь закрытый ключ. О наличии закрытого ключа можно узнать, посмотрев на свойства сертификата, где русским по белому будет написано: «есть закрытый ключ для этого сертификата».

Самое интересное о сертификате мы можем узнать на вкладке «Состав».

Обрати внимание на поля «Алгоритм подписи», «Алгоритм хеширования подписи» и «Открытый ключ». Если хочешь использовать сертификат для осуществления транзакций в России, во всех этих полях ты должен видеть слово «ГОСТ». Также следует обратить внимание на значение поля «Использование ключа» и поля «Действителен с» и «Действителен по»: первое позволит понять, возможно ли использование сертификата для выполнения нужной нам операции (шифрование, подпись), а второе и третье — возможно ли использовать данный сертификат в указанный момент времени. В дополнение к этому следует убедиться, что сертификат действителен. В этом нам поможет вкладка «Путь сертификации». Если с сертификатом все хорошо, мы увидим надпись: «Этот сертификат действителен».

![]()
Как работает цифровая подпись
Если вспомнить формальное определение, то ЭЦП — это реквизит электронного документа. Другими словами, последовательность битов, вычисленная уникально для каждого конкретного сообщения. Подпись может быть вычислена как с применением секретного ключа, так и без него. Без секретного ключа подпись представляет собой просто код, который может доказать, что документ не был изменен. С использованием секретного ключа подпись докажет целостность сообщения, позволит убедиться в его подлинности и аутентифицировать источник.
Если ты читал вторую часть
нашего цикла, то помнишь, что существуют симметричный и асимметричный подходы к шифрованию. С электронной подписью дела обстоят очень похоже — есть подписи с симметричным механизмом, а есть с асимметричным.
Симметричный механизм подписи малоприменим на практике — никому не хочется генерировать ключи для каждой подписи заново. А как ты помнишь, именно в одинаковых ключах кроется фишка симметричной криптографии.
- В лучших традициях асимметричной криптографии — имеем пару открытый и секретный ключ. Но не спеши пролистывать все это описание. Электронная подпись концептуально отличается от шифрования применением ключей, описанного ранее.
- От документа или сообщения подсчитывается хеш-функция, которая сократит сообщение любого объема до определенного количества байтов.
- Посредством криптографических преобразований вычисляется сама электронная подпись. В отличие от асимметричного шифрования, подпись основана на закрытом ключе, а вот проверить с помощью открытого ключа ее может любой его обладатель. Если помнишь, в шифровании все происходит наоборот: шифруют для нас на открытом ключе, а вот расшифровывать мы будем с помощью секретного ключа.
- Электронная подпись предоставляется вместе с исходным документом на проверку. По полученной композиции можно доказать, что документ с момента вычисления подписи не был изменен.

Схемы электронной подписи так же многообразны, как и способы шифрования. Чтобы схема подписи была стойкой, нужно, чтобы она основывалась на трудновычислимой математической задаче. Есть два типа таких задач: факторизация больших чисел и дискретное логарифмирование.
Зачем нужны открытый и закрытый ключ ЭЦП
Открытый и закрытый ключ электронной подписи решают разные задачи. Открытый ключ ЭЦП предназначен для зашифровки информации, в то время как закрытый призван обеспечить её расшифровку. Открытый ключ можно без опасений передавать, не рискуя при этом сохранностью данных. Работа ключевой пары осуществляется только при взаимодействии двух составляющих. Надёжная криптосистема успешно используется для заверения электронных документов. Удобный инструмент обеспечивает надлежащую конфиденциальность данных и защиту от фальсификации.
Проверка электронной подписи
Проверить открытую часть электронной подписи можно с помощью СКЗИ — средств криптографической защиты информации. Это может быть, например, программа КриптоПро CSP. Но это же можно проделать и в сторонних программах, и на веб-сайтах.
Доступ к открытому элементу цифровой подписи публичный — воспользоваться им может кто угодно.
Открытая часть ключа ЭЦП: как сделать
Выдача этой части ключевой пары ЭЦП производится удостоверяющим центром, аккредитованным Минцифры, но это относится только к квалифицированным сертификатам. В его функции входит формирование собственного сертификата, сертификата конечного пользователя, заверение их аутентичности. Для учёта выданных сертификатов УЦ ведёт специальный реестр. Спектр выполняемых органом задач охватывает также аннулирование скомпрометированных сертификатов с последующим обновлением существующей базы.
Цифровая подпись
Представь, дорогой читатель, что ты занимаешься некой очень ответственной работой. И результаты своей работы отправляешь в виде отчетов, от которых в конечном итоге зависят чьи-то конкретные судьбы и жизни. Получатели твоих отчетов принимают на их основе очень важные решения, и, если ты напортачишь, вполне можешь получить срок. Так вот, в таких ответственных организациях без электронной подписи никуда. Она позволяет тебе подписать тот самый суперважный секретный отчет своим сертификатом с закрытым ключом. Закрытый ключ, в идеале, может храниться на токене — специальном съемном устройстве, похожем на флешку, которое ты в редкие моменты достаешь из сейфа. Подпись гарантирует, что твой отчет отправлен именно тобой, а не уборщицей или сторожем. С другой стороны, ты не сможешь отказаться от авторства (это называется «неотрекаемость») и, если накосячишь в своем суперважном документе, на сторожа свалить вину не получится.
Электронная подпись применяется не только в спецслужбах и органах, но и в бизнесе. Например, для перевода пенсионных накоплений в НПФ: мы генерируем запрос на сертификат, отправляем его в удостоверяющий центр (УЦ). У Ц выпускает сертификат, мы подписываем сертификатом заявление на перевод пенсионных накоплений, отправляем — и вуаля. Подпись также позволяет осуществлять контроль целостности подписываемых данных
. Если данные будут изменены, подпись не пройдет проверку.
Для программирования подписи необходимо ознакомиться с несколькими классами . NET Framework:
Перед тем как заюзать наш сертификат, необходимо его проверить. Процедура включает в себя проверку цепочки сертификации, проверку срока действия и проверку, не отозван ли сертификат. Если мы подпишем файл недействительным сертификатом, подпись будет недействительной.
X509Chain certificateChain = new X509Chain {
ChainPolicy = {
RevocationMode = X509RevocationMode.Online,
VerificationFlags = X509VerificationFlags.IgnoreNotTimeValid,
RevocationFlag = X509RevocationFlag.ExcludeRoot
}
};
bool chainOk = certificateChain.Build(certificate);
bool certNotExpired = (certificate.NotAfter >= DateTime.Now) && (certificate.NotBefore <= DateTime.Now);
Мы проверили сертификат и убедились, что он в порядке. Переходим непосредственно к подписыванию данных. Подпись бывает двух видов: прикрепленная
и открепленная
.

Результатом прикрепленной подписи будет CMS (Cryptography Message Syntax) — сообщение, содержащее как подписываемые данные, так и саму подпись. Открепленная подпись содержит только саму подпись. Рекомендую использовать именно открепленную подпись, потому что с ней намного меньше мороки. В нее проще поставить метку времени, она меньше весит, так как не содержит подписываемые данные. Подписываемые данные легко открыть, посмотреть. В случае прикрепленной подписи для того, чтобы просмотреть подписанные данные, CMS-сообщение необходимо сначала декодировать. В общем, прикрепленной подписи я рекомендую избегать всеми силами. Если потребуется передавать подпись и контент вместе, рассмотри вариант архивирования (вместо использования прикрепленной подписи используй открепленную, просто заархивируй подписываемый файл и открепленную подпись). Посмотрим на код подписи (С#):
public byte[] SignAttached(X509Certificate2 certificate, byte[] dataToSign) {
ContentInfo contentInfo = new ContentInfo(dataToSign);
SignedCms cms = new SignedCms(contentInfo, false);
CmsSigner signer = new CmsSigner(certificate);
cms.ComputeSignature(signer, false);
return cms.Encode();
}
public byte[] SignDetached(X509Certificate2 certificate, byte[] dataToSign) {
ContentInfo contentInfo = new ContentInfo(dataToSign);
SignedCms cms = new SignedCms(contentInfo, true);
CmsSigner signer = new CmsSigner(certificate);
cms.ComputeSignature(signer, false);
return cms.Encode();
}
Но, как обычно это бывает у Microsoft, стоит сделать маленький шаг в сторону, и розовый волшебный мир рушится
Глядя на примеры кода, можно подумать, что работа с подписью в . NET реализована достаточно хорошо. Но рассмотрим, например, случай, в котором необходимо осуществить подпись большого файла, размером 600 MiB. Внимательные читатели обратили внимание на сигнатуру метода подписи — он принимает на вход массив байтов. При попытке загрузить в массив байтов 600 MiB мы получим OutOfMemoryException.
Что же делать, спросишь ты? Обращаться к основам — отвечу я! Очевидно, раз нельзя загрузить в память 600 MiB, то необходимо файл грузить и обрабатывать по кусочкам. . NET-обертки над CMS так не умеют. На помощь нам приходит MS Crypto API. M S Crypto API содержит два набора функций для работы с CMS: Simplified Message Fuctions
и Low Level Message Functions
. Для работы с большими файлами нам нужны Low Level. Полную реализацию на C# можно посмотреть тут
. Я же предпочитаю работать с криптографией на языке C++. Кода в результате писать приходится меньше, а работает он быстрее. Рассмотрим порядок действий для реализации подписи в поточном режиме:
- Получаем
PCCERT\_CONTEXT
; - Заполняем структуры
CMSG\_STREAM\_INFO
,CRYPT\_ALGORITHM\_IDENTIFIER
,CMSG\_SIGNER\_ENCODE\_INFO
,CMSG\_SIGNED\_ENCODE\_INFO
; - Получаем хендл сообщения с помощью функции
CryptMsgOpenToEncode
. Для открепленной подписи необходимо передать соответствующий флагCMSG\_DETACHED\_FLAG
; - В цикле вызываем функцию
CryptMsgUpdate
и «скармливаем» ей по кусочкам файл, который необходимо подписать.
На C++ будет что-то вроде:
ISigner signer = null;
// Заполняем структуры
...
// Открываем сообщение для кодирования
HCRYPTMSG hMsg = CryptMsgOpenToEncode (
(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING), // Message encoding type
dwFlags, // Flags
CMSG_SIGNED, // Message type
&SignedMsgEncodeInfo, // Pointer to structure
NULL, // Inner content object ID
&stStreamInfo // Stream information (not used)
);
...
// Обрабатываем файл для подписи по кусочкам
while ( ( bytesRead = inputStream->Read(buf, blockSize, 0, blockSize ) ) > 0 ) {
processedDataLen += bytesRead;
BOOL lastcall = (processedDataLen == streamLength);
BOOL successful = CryptMsgUpdate(hMsg, (const BYTE*)buf, bytesRead, lastcall);
}
// Закрываем хендл
CryptMsgClose(hMsg);
return S_OK;
Вызов кода на C++ из C# будет выглядеть примерно так:
ISigner signer = null;
PkiFactory.CreateSigner(out signer);
X509Store store = new X509Store("My");
store.Open(OpenFlags.ReadOnly);
var cert = GetCertificates(store);
string file = @"d:\tmp\masyanya.bin";
using (var inputStream = File.OpenRead(file))
using (var outputStream = File.Create(file + ".sig")) {
var reader = new StreamReader(inputStream);
var writer = new StreamWriter(outputStream);
int result = signer.Sign(reader, writer, cert, 1);
Debug.Assert(result == 0, "Подпись не прошла.");
}
Внимательный читатель удивится — что это за IStreamReader* inputStream
, IStreamWriter* outputStream
, ICertificate* signCertificate
? Ответ следует из названия переменных, но есть одна тонкость, которая для многих окажется сюрпризом. I StreamReader, IStreamWriter, ICertificate — это интерфейсы, реализованные на C#, и это не COM-объекты. При этом мы спокойно можем вызывать их методы в нативном C++. Как сделать такую красоту — тема отдельной статьи. В результате успешного выполнения операции мы получим криптографическое сообщение. Для кодирования сертификатов X.509
и криптографических сообщений используется Abstract Syntax Notation One
, или, по-простому, ASN 1. Для просмотра файлов, закодированных в ASN 1, можно воспользоваться бесплатным ASN.1 Editor
.

![]()
Файл masks. key
Содержит 32 байта маски ключа в формате Asn1, зашифрованного на ключе хранения pwd_key. Далее 12 байт «затравочной» информации для генерации ключа хранения pwd_key, если криптоконтейнер защищен паролем, то пароль также участвует в генерации ключа хранения.
Далее контрольная сумма (имитозащита) 4 байта. Контрольной информацией для простоты мы пользоваться не будем, общий контроль будет осуществляться путем генерации открытого ключа и сравнения первых 8 байт полученного ключа с соответствующим полем из файла header.key:

Картина мира
Перед погружением в код давай разберем немного терминологии. P KI — инфраструктура открытых ключей. Как несложно догадаться, PKI основана на асимметричном шифровании. В симметричных шифрах для шифрования и расшифрования используется один ключ. В асимметричных для шифрования используется один ключ, а для расшифрования — другой. Вместе они образуют ключевую пару.
Информация, необходимая для работы PKI, содержится в сертификате X.509
. В PKI участвуют как минимум три стороны: Алиса, Боб и удостоверяющий центр (УЦ). У Алисы и Боба есть сертификаты с закрытым ключом, подписанные так называемым корневым сертификатом УЦ. У Алисы есть сертификат Боба с открытым ключом, а у Боба — сертификат Алисы с открытым ключом. Алиса и Боб доверяют УЦ и благодаря этому могут доверять друг другу.

WARNING
Приведенный ниже код предназначен исключительно для ознакомления с PKI. Не следует без оглядки использовать его в реальной работе.
Читаем закрытый ключ и конвертируем
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/pem.h>
#include <openssl/cms.h>
#include <openssl/err.h>
#include "gost_lcl.h"
/* Convert little-endian byte array into bignum */
BIGNUM *reverse32bn(char *b, BN_CTX *ctx)
{
BIGNUM *res;
char buf[32];
BUF_reverse(buf, b, 32);
res = BN_bin2bn(buf, 32, BN_CTX_get(ctx));
OPENSSL_cleanse(buf, sizeof(buf));
return res;
}
void xor_material(char *buf36, char *buf5C, char *src)
{
int i;
for(i = 0; i < 32; i++)
{
buf36[i] = src[i] ^ 0x36;
buf5C[i] = src[i] ^ 0x5C;
}
}
int make_pwd_key(char *result_key, char *start12, int start12_len, char *passw)
{
int result;
int i;
char pincode4[1024];
int pin_len;
char current[32];
char material36[32];
char material5C[32];
char hash_result[32];
gost_hash_ctx ctx;
init_gost_hash_ctx(&ctx, &GostR3411_94_CryptoProParamSet);
memset(pincode4, 0, sizeof(pincode4));
pin_len = strlen(passw);
if (pin_len*4 > sizeof(pincode4)) { result = 1; goto err; }
for(i = 0; i < pin_len; i++)
pincode4[i*4] = passw[i];
start_hash(&ctx);
hash_block(&ctx, start12, start12_len);
if (pin_len)
hash_block(&ctx, pincode4, pin_len * 4);
finish_hash(&ctx, hash_result);
memcpy(current, (char*)"DENEFH028.760246785.IUEFHWUIO.EF", 32);
for(i = 0; i < (pin_len?2000:2); i++)
{
xor_material(material36, material5C, current);
start_hash(&ctx);
hash_block(&ctx, material36, 32);
hash_block(&ctx, hash_result, 32);
hash_block(&ctx, material5C, 32);
hash_block(&ctx, hash_result, 32);
finish_hash(&ctx, current);
}
xor_material(material36, material5C, current);
start_hash(&ctx);
hash_block(&ctx, material36, 32);
hash_block(&ctx, start12, start12_len);
hash_block(&ctx, material5C, 32);
if (pin_len)
hash_block(&ctx, pincode4, pin_len * 4);
finish_hash(&ctx, current);
start_hash(&ctx);
hash_block(&ctx, current, 32);
finish_hash(&ctx, result_key);
result = 0; //ok
err:
return result;
}
BIGNUM *decode_primary_key(char *pwd_key, char *primary_key, BN_CTX *bn_ctx)
{
BIGNUM *res;
char buf[32];
gost_ctx ctx;
gost_init(&ctx, gost_cipher_list->sblock);
gost_key(&ctx, pwd_key);
gost_dec(&ctx, primary_key, buf, 4);
res = reverse32bn(buf, bn_ctx);
OPENSSL_cleanse(buf, sizeof(buf));
return res;
}
BIGNUM *remove_mask_and_check_public(char *oid_param_set8, BIGNUM *key_with_mask, BIGNUM *mask, char *public8, BN_CTX *ctx)
{
int result;
EC_KEY *eckey = NULL;
const EC_POINT *pubkey;
const EC_GROUP *group;
BIGNUM *X, *Y, *order, *raw_secret, *mask_inv;
char outbuf[32], public_X[32];
ASN1_OBJECT *obj;
int nid;
order = BN_CTX_get(ctx);
mask_inv = BN_CTX_get(ctx);
raw_secret = BN_CTX_get(ctx);
X = BN_CTX_get(ctx);
Y = BN_CTX_get(ctx);
if (!order || !mask_inv || !raw_secret || !X || !Y) { result = 1; goto err; }
obj = ASN1_OBJECT_create(0, oid_param_set8+1, *oid_param_set8, NULL, NULL);
nid = OBJ_obj2nid(obj);
ASN1_OBJECT_free(obj);
if (!(eckey = EC_KEY_new())) { result = 1; goto err; }
if (!fill_GOST2001_params(eckey, nid)) { result = 1; goto err; }
if (!(group = EC_KEY_get0_group(eckey))) { result = 1; goto err; }
if (!EC_GROUP_get_order(group, order, ctx)) { result = 1; goto err; }
if (!BN_mod_inverse(mask_inv, mask, order, ctx)) { result = 1; goto err; }
if (!BN_mod_mul(raw_secret, key_with_mask, mask_inv, order, ctx)) { result = 1; goto err; }
if (!EC_KEY_set_private_key(eckey, raw_secret)) { result = 1; goto err; }
if (!gost2001_compute_public(eckey)) { result = 1; goto err; }
if (!(pubkey = EC_KEY_get0_public_key(eckey))) { result = 1; goto err; }
if (!EC_POINT_get_affine_coordinates_GFp(group, pubkey, X, Y, ctx)) { result = 1; goto err; }
store_bignum(X, outbuf, sizeof(outbuf));
BUF_reverse(public_X, outbuf, sizeof(outbuf));
if (memcmp(public_X, public8, 8) != 0) { result = 1; goto err; }
result = 0; //ok
err:
if (eckey) EC_KEY_free(eckey);
if (result == 0) return raw_secret;
return NULL;
}
int file_length(char *fname)
{
int len;
FILE *f = fopen(fname, "rb");
if (f == NULL) return -1;
fseek(f, 0, SEEK_END);
len = ftell(f);
fclose(f);
return len;
}
int read_file(char *fname, int start_pos, char *buf, int len)
{
int read_len;
FILE *f = fopen(fname, "rb");
if (f == NULL) return 1;
if (start_pos) fseek(f, start_pos, SEEK_SET);
read_len = fread(buf, 1, len, f);
fclose(f);
if (read_len != len) return 1;
return 0; //ok
}
int get_asn1_len(unsigned char *buf, int *size_hdr)
{
int n, i, res;
int pos = 0;
if ((buf[pos]&0x80) == 0) {
*size_hdr = 1;
return buf[pos];
}
n = buf[pos++]&0x7f;
res = 0;
for(i = 0; i < n; i++) {
res = res*256 + buf[pos++];
}
*size_hdr = n+1;
return res;
}
#define MAX_HEADER 20000
int read_container(char *fpath, int flag2, char *salt12, char *primary_key, char *masks_key, char *public8, char *oid_param_set8)
{
int result;
char primary_path[1024+30];
char masks_path[1024+30];
char header_path[1024+30];
char header_buf[MAX_HEADER];
int header_len;
int i, len, pos, size_hdr;
if (strlen(fpath)>1024) { result = 1; goto err; }
sprintf(header_path, "%s/header.key", fpath);
if (flag2 == 0)
{
sprintf(primary_path, "%s/primary.key", fpath);
sprintf(masks_path, "%s/masks.key", fpath);
}
else
{
sprintf(primary_path, "%s/primary2.key", fpath);
sprintf(masks_path, "%s/masks2.key", fpath);
}
if (read_file(primary_path, 4, primary_key, 32)) { result = 1; goto err; }
if (read_file(masks_path, 4, masks_key, 32)) { result = 1; goto err; }
if (read_file(masks_path, 0x26, salt12, 12)) { result = 1; goto err; }
header_len = file_length(header_path);
if (header_len < 0x42 || header_len > MAX_HEADER) { result = 1; goto err; }
if (read_file(header_path, 0, header_buf, header_len)) { result = 1; goto err; }
//------------- skip certificate ---------------------------
pos = 0;
for(i = 0; i < 2; i++)
{
get_asn1_len(header_buf+pos+1, &size_hdr);
pos += size_hdr+1;
if (pos > header_len-8) { result = 2; goto err; }
}
//------------------ get oid_param_set8 -----------------------
#define PARAM_SET_POS 34
if (memcmp(header_buf+pos+PARAM_SET_POS, "\x6\x7", 2) != 0) { result = 2; goto err; }
memcpy(oid_param_set8, header_buf+pos+PARAM_SET_POS+1, 8);
//------------------ get public8 -----------------------
result = 2; //not found
pos += 52;
for(i = 0; i < 3; i++)
{
len = get_asn1_len(header_buf+pos+1, &size_hdr);
if (len == 8 && memcmp(header_buf+pos, "\x8a\x8", 2) == 0)
{
memcpy(public8,header_buf+pos+2,8);
result = 0; //ok
break;
}
pos += len+size_hdr+1;
if (pos > header_len-8) { result = 2; goto err; }
}
err:
OPENSSL_cleanse(header_buf, sizeof(header_buf));
return result;
}
#define START_OID 0x12
#define START_KEY 0x28
unsigned char asn1_private_key[72] = {
0x30,0x46,2,1,0,0x30,0x1c,6,6,0x2a,0x85,3,2,2,0x13,0x30,0x12,6,7,0x11,
0x11,0x11,0x11,0x11,0x11,0x11,6,7,0x2a,0x85,3,2,2,0x1e,1,4,0x23,2,0x21,0
};
int main(int argc, char **argv)
{
int result;
char *container_path;
char *passw;
char salt12[12];
char primary_key[32];
char masks_key[32];
char public8[8];
char oid_param_set8[8];
BN_CTX *ctx;
BIGNUM *key_with_mask;
BIGNUM *mask;
BIGNUM *raw_key;
char pwd_key[32];
char outbuf[32];
ctx = BN_CTX_new();
if (argc == 2)
{
container_path = argv[1];
passw = "";
}
else
if (argc == 3)
{
container_path = argv[1];
passw = argv[2];
}
else
{
printf("get_private container_path [passw]\n");
result = 1;
goto err;
}
if (read_container(container_path, 0, salt12, primary_key, masks_key, public8, oid_param_set8) != 0 &&
read_container(container_path, 1, salt12, primary_key, masks_key, public8, oid_param_set8) != 0)
{
printf("can not read container from %s\n", container_path);
result = 2;
goto err;
}
make_pwd_key(pwd_key, salt12, 12, passw);
key_with_mask = decode_primary_key(pwd_key, primary_key, ctx);
OPENSSL_cleanse(pwd_key, sizeof(pwd_key));
mask = reverse32bn(masks_key, ctx);
raw_key = remove_mask_and_check_public(oid_param_set8, key_with_mask, mask, public8, ctx);
if (raw_key)
{
BIO *bio;
store_bignum(raw_key, outbuf, sizeof(outbuf));
memcpy(asn1_private_key+START_OID, oid_param_set8, 8);
memcpy(asn1_private_key+START_KEY, outbuf, 32);
//bio = BIO_new_file("private.key", "w");
bio = BIO_new_fp(stdout, BIO_NOCLOSE | BIO_FP_TEXT);
PEM_write_bio(bio, "PRIVATE KEY", "", asn1_private_key, sizeof(asn1_private_key));
BIO_free(bio);
OPENSSL_cleanse(outbuf, sizeof(outbuf));
OPENSSL_cleanse(asn1_private_key, sizeof(asn1_private_key));
result = 0; //ok
}
else
{
printf("Error check public key\n");
result = 3;
}
err:
BN_CTX_free(ctx);
OPENSSL_cleanse(salt12, sizeof(salt12));
OPENSSL_cleanse(primary_key, sizeof(primary_key));
OPENSSL_cleanse(masks_key, sizeof(masks_key));
return result;
}
Основную работу выполняют следующие 3 функции:
1. Создаем ключ хранения исходя из 12-ти байтовой «соли» и пароля.
make_pwd_key(pwd_key, salt12, 12, passw);
2. Расшифровываем основной ключ на ключе хранения.
key_with_mask = decode_primary_key(pwd_key, primary_key, ctx);
3. Делим ключ с маской на маску.
raw_key = remove_mask_and_check_public(oid_param_set8, key_with_mask, mask, public8, ctx);
Но так как в библиотеке OpenSSL операция деления по модулю традиционно отсутствует, пользуемся операцией взятия обратного числа и умножением.
if (!BN_mod_inverse(mask_inv, mask, order, ctx)) { result = 1; goto err; }
if (!BN_mod_mul(raw_secret, key_with_mask, mask_inv, order, ctx)) { result = 1; goto err; }
Факторизация больших чисел
Рассмотрим на практике электронную подпись на основе знаменитого алгоритма RSA. Шифрование RSA мы рассматривать не стали — это мейнстрим, и в той же «Википедии» есть его подробное описание
.
1. Генерация ключей
Причина стойкости RSA кроется в сложности факторизации больших чисел. Другими словами, перебором очень трудно подобрать такие простые числа, которые в произведении дают модуль n. Ключи генерируются одинаково для подписи и для шифрования.

Когда ключи сгенерированы, можно приступить к вычислению электронной подписи.
2. Вычисление электронной подписи

3. Проверка электронной подписи

RSA, как известно, собирается уходить на пенсию, потому что вычислительные мощности растут не по дням, а по часам. Недалек тот день, когда 1024-битный ключ RSA можно будет подобрать за считаные минуты. Впрочем, о квантовых компьютерах мы поговорим в следующий раз.
В общем, не стоит полагаться на стойкость этой схемы подписи RSA, особенно с такими «криптостойкими» ключами, как в нашем примере.
Расшифрование
При расшифровании необходимо, чтобы сертификат, указанный при шифровании в коллекции получателей, был установлен в хранилище сертификатов. Так как сообщение может быть зашифровано и адресовано нескольким получателям, для расшифрования нам необходимо найти того получателя, сертификат которого установлен в нашем хранилище сертификатов.
public byte[] Decrypt(byte[] encryptedData) {
var envelopedCms = new EnvelopedCms();
envelopedCms.Decode(encryptedData);
X509Store store = new X509Store("My");
store.Open(OpenFlags.ReadOnly);
RecipientInfo recipientInfo = envelopedCms.RecipientInfos.Cast<RecipientInfo>()
.FirstOrDefault(x => FindCertificate((X509IssuerSerial)x.RecipientIdentifier.Value) != null);
envelopedCms.Decrypt(recipientInfo);
return envelopedCms.ContentInfo.Content;
}
private X509Certificate2 FindCertificate(X509IssuerSerial issuerSerial) {
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
return store.Certificates
.Find(X509FindType.FindByIssuerDistinguishedName, issuerSerial.IssuerName, false)
.Find(X509FindType.FindBySerialNumber, issuerSerial.SerialNumber, false).Cast<X509Certificate2>()
.FirstOrDefault();
}
Файл header. key
Из этого файла нам потребуется параметры электронной подписи CryptoProParamSet (подчеркнуто красным).
- GostR3410_2001_CryptoPro_A_ParamSet — 1.2.643.2.2.35.1
- GostR3410_2001_CryptoPro_B_ParamSet — 1.2.643.2.2.35.2
- GostR3410_2001_CryptoPro_C_ParamSet — 1.2.643.2.2.35.3
- GostR3410_2001_CryptoPro_XchA_ParamSet — 1.2.643.2.2.36.0
- GostR3410_2001_CryptoPro_XchB_ParamSet — 1.2.643.2.2.36.1
А также первые 8 байт открытого ключа (подчеркнуто) для контроля правильности чтения закрытого.

Цифровая подпись в Bitcoin
Помимо прочего, электронная подпись используется в криптовалютах, в частности — в Bitcoin. У каждого пользователя Bitcoin есть пара из секретного и открытого ключа. Хеш-значение открытого ключа служит основным адресом для передачи монет. Это значение не секретно, и сообщать его можно кому угодно. Но по значению хеша вычислить значение открытого ключа невозможно.
Сама пара ключей будет использована лишь однажды — при передаче прав собственности. На этом жизнь пары ключей заканчивается.
- PUB1 — публичный ключ;
- PRIV1 — секретный ключ;
- HASH1 или HASH(PUB1) — хеш-значение открытого ключа (биткойн-адрес);
- HASH2 или HASH(PUB2) — хеш открытого ключа следующего владельца.
Вот как устроен сам процесс передачи прав собственности на биткойны.
- Владелец монеты открыто сообщает хеш своего публичного ключа HASH(PUB1), это и будет идентифицировать биткойн.
- До момента продажи оба ключа PUB1, PRIV1 продавца остаются в секрете. Известен только HASH(PUB1) и соответствующий ему биткойн.
- Как только появляется покупатель, владелец формирует открытое письмо, в котором указывает адрес биткойна HASH(PUB1) и хеш-значение публичного ключа нового владельца HASH(PUB2). И конечно же, подписывает письмо своим секретным ключом PRIV1, прилагая публичный ключ PUB1.
- После этого пара ключей владельца PUB1 и PRIV1 теряют свою актуальность. Публичным ключом можно проверить само письмо, узнать новый адрес монеты.

О втором собственнике ничего не известно, кроме HASH(PUB2), до тех пор пока он не передаст права третьему владельцу. И эта цепочка может быть бесконечной.
Подписывая передачу прав с использованием ЭЦП, собственник подтверждает не только свою личность, но и свое согласие на проведение сделки. То есть вернуть монетку он уже не может и с этим согласился, подписавшись электронной подписью.
Такая технология построения цепи передачи прав и называется блокчейном. Благодаря этой технологии можно отследить историю владения до самых истоков, но изменить эту историю никак нельзя.
INFO
Среди . NET-девелоперов бытует мнение, что программировать на C++ сложнее и дольше. Уверяю тебя, в случае с криптографией ситуация противоположна. Гораздо быстрее написать код на C++ и вызвать его из . NET.
Дискретное логарифмирование
Это вторая сложная проблема, на которой основаны цифровые подписи. Для начала хорошо бы усвоить, что такое дискретный логарифм. Для кого-то такое словосочетание может звучать пугающе, но на самом деле это одна из самых простых для понимания вещей в этой статье.
Предположим, дано уравнение 4x = 13 (mod 15)
. Задача нахождения x и есть задача дискретного логарифмирования. Почему же она так сложна для вычисления? Попробуй решить это уравнение перебором! Компьютер, ясное дело, будет более успешен, но и задачи дискретного логарифмирования обычно далеко не так просты. Возьмем для примера схему Эль-Гамаля.
1. Генерация подписи

2. Проверка подписи

Даже если не вникать в схему, понятно, что такой алгоритм сложнее. Кроме того, нигде уже не используется простой модуль, его сменили эллиптические кривые. Эллиптическая кривая — это кривая, которая задана кубическим уравнением и имеет невообразимо сложное представление. Задача решения логарифма в группе точек, которые принадлежат эллиптической кривой, вычислительно сложная, и на данный момент не существует таких мощностей, которые решали бы это уравнение за полиномиальное время, если длина секретного ключа составляет 512 бит. Согласно задаче дискретного логарифмирования, невероятно сложно найти на кривой две такие точки, которые связывает операция возведения в некоторую степень.
ЭЦП на практике
В России, как и во многих развитых странах, электронная подпись имеет официальный юридический статус. У нас этот факт регламентирует закон № 63-ФЗ «Об электронной подписи». Однако он утверждает, что юридической силой обладает далеко не любая электронная подпись, а только соответствующая определенным критериям:
- подпись сгенерирована посредством криптографического преобразования с секретным ключом;
- этот ключ и соответствующий ему открытый ключ выданы квалифицированным удостоверяющим центром;
- по подписи можно достоверно установить ее обладателя.
Подпись также должна быть вычислена средствами, соответствующими требованиям закона. Этим требованиям удовлетворяет отечественный алгоритм шифрования ГОСТ 34.10—2012. Он использует математический аппарат эллиптических кривых, является достаточно стойким и официально используется для разработки криптографических средств, реализующих электронную подпись. Для того чтобы попробовать неквалифицированную подпись — без сертификата удостоверяющего центра, можно воспользоваться известной PGP. Потестировать подпись можно, к примеру, на сайте ReadVerify
.
Стоит сказать, что в нашей стране электронная подпись используется чаще, чем можно себе представить. В банках, налоговых, торгово-закупочных операциях, бухгалтерии — во всех этих организациях используется или внедряется ЭЦП. Электронная подпись отважно борется со злом бюрократии, однако до полной победы еще далеко.
За рубежом электронный документооборот процветает еще дольше. Официальный стандарт электронной подписи в США DSS (Digital Signature Standard) также использует эллиптические кривые и основан на описанной выше схеме Эль-Гамаля.
Проверка подписи и декодирование
А теперь, дорогой читатель, представь, что ты большой начальник и должен принять важное стратегическое решение на основе отчета, который тебе прислал сотрудник по электронной почте. Для твоего удобства отчет был подписан открепленной подписью. Открыв почту и скачав отчет, ты, как опытный, знающий жизнь человек, не спешишь принимать на веру содержимое отчета и проверяешь подпись. После проверки выясняешь, что подпись неверна — не сошлась контрольная сумма. В результате оповещаешь службу безопасности, которая проводит расследование и выясняет, что хитрые конкуренты взломали почтовый сервер и отправили тебе фальшивый документ. Тебя наградили за бдительность, конкурентов посадили, а компания наконец-то получила оригинальный отчет с проверенной электронной подписью.
Если пользователь прислал тебе отчет в виде прикрепленной подписи, тебе для чтения придется его декодировать:
public bool VerifyAttached(byte[] dataToVerify) {
try {
var cms = new SignedCms();
cms.Decode(dataToVerify);
foreach (var signer in cms.SignerInfos) {
signer.CheckSignature(true);
}
return true;
}
catch (CryptographicException) {
return false;
}
}
public byte[] Decode(byte[] signedCms) {
var cms = new SignedCms();
cms.Decode(signedCms);
return cms.ContentInfo.Content;
}
Нетрудно догадаться, что и тут разработчики . NET Framework подложили нам свинью. Не можем мы проверить подпись большого файла! По той же самой причине — OutOfMemoryException. Но и эту проблему несложно решить, обратившись к магии MS Crypto API. Так как код поточной проверки подписи достаточно длинный, остановлюсь на основных моментах:
// Заполняем структуры
...
// Открываем сообщение для декодирования
HCRYPTMSG msg = CryptMsgOpenToDecode(...);
// Декодируем сообщение по кусочкам
while ((bytesRead = inputStream->Read(&buf.at(0), blockSize, 0, blockSize)) > 0) {
totalBytesRead += bytesRead;
CryptMsgUpdate(msg, &buf.at(0), bytesRead, totalBytesRead == fileSize);
}
// Получаем информацию о подписанте
PCCERT_CONTEXT pSignerCertContext = CertGetSubjectCertificateFromStore(
hStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
(PCERT_INFO)((void*)(&signerCertInfo.at(0))));
// Проверяем подпись
BOOL ok = CryptMsgControl(msg, 0, CMSG_CTRL_VERIFY_SIGNATURE, pSignerCertContext->pCertInfo));
А так будет выглядеть код проверки подписи при вызове из C#:
ISignatureVerifier verifier = null;
PkiFactory.CreateSignatureVerifier(out verifier);
using (var inputStream = File.OpenRead(file + ".sig"))
using (var contentStream = File.OpenRead(file)) {
var reader = new StreamReader(inputStream);
var contentReader = new StreamReader(contentStream);
var error = verifier.VerifyDetachedSign(contentReader, reader);
Debug.Assert(error == 0, "Проверка подписи не прошла");
}
Как происходит подписание документа с помощью ЭЦП
Система подписания документов с помощью электронной подписи выглядит следующим образом:
- Электронная подпись присоединяется не к цифровому документу. Э П ставится на его сжатую версию — хэш. Таким образом, сокращается время шифрования, так как хэш файла весит меньше, чем сам файл.
- Для создания хэша применяются криптографические хэш-функции. При данном способе объёмный текст файла не делится на отдельные модули и сохраняет свой порядок.
- После создания хэша, закрытый ключ его шифрует и передаёт получателю вместе с сертификатом электронной подписи.
- Открытый ключ ЭП адресата расшифровывает информацию и проверяет подлинность сертификата отправителя.

Закрытый ключ электронной подписи хранят в памяти компьютера или физических носителях: USB-токенах и смарт-картах. Согласно закону «Об электронной подписи» 63-ФЗ, ответственность за хранение закрытого ключа несёт владелец.
Программы для работы и алгоритмы шифрования
С ЭЦП не получится работать сразу. Чтобы шифровать и подписывать документы, недостаточно только иметь сертификат и закрытый ключ, для работы нужно устанавливать специальные программы. С помощью этих программ, которые работают по определённому стандарту шифрования (в России — ГОСТ 34.10-2018
), обеспечивается связь закрытого и открытого ключа с документами.
Расшифровка ЭЦП: электронная цифровая подпись. Данное понятие является устаревшим. Сегодня используется термин — электронная подпись, сокращённо ЭП.
Одной из самых популярных программ-криптопровайдеров в России является «КриптоПро CSP»
. С её помощью можно подписывать и шифровать документы, проверять сертификаты на подлинность, контролировать целостность соответствующего программного обеспечения.
Файл primary. key
Содержит 32 байта ключа в формате Asn1. Это только половина ключа, полный ключ получается при делении этого числа по модулю Q на маску. Поле, хранящее модуль Q в библиотеке OpenSSL имеет название order. Маска лежит в файле masks.key:

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

Развитие цифровых технологий упростило многие моменты нашей жизни, в том числе, обмениваться документами и письмами в электронной форме оказалось быстрее и удобнее, чем в бумажной. Однако у электронного документооборота была проблема: нужно было как- то подтверждать подлинность электронных бумаг. На бумаге для этого использовались подписи, однако в цифровом формате их было достаточно легко подделать, поэтому для электронных документов требовался более надежный метод подтверждения. Поэтому и стал популярным аналог ручных подписей — электронная подпись, о которой будет рассказано дальше.
Сначала вкратце опишу то, как работает электронная подпись (ЭП) и в чём заключается её надёжность. Электронная подпись – подтверждение того, что какой-либо электронный документ был создан и подписан определённым физическим или юридическим лицом. При этом она должна обладать следующими свойствами:
Неотказуемость – подписавшее документ лицо не может утверждать, что это сделал кто-то другой;
Целостность – внесение исправлений в уже подписанный документ должно нарушать подпись;
Авторство – электронная подпись должна быть жёстко закреплена за определённым физическим или юридическим лицом.
Эти базовые принципы и делают ЭП эффективной и безопасной в использовании.
Немного теории
В качестве алгоритмической базы для электронной подписи обычно применяют методы шифрования с открытым ключом. Подробнее о самих алгоритмах можно почитать на википедии
или на хабре
. Их суть сводится к тому, что для желающего обзавестись собственной электронной подписью специальным образом выбирается пара ключей – открытый и закрытый. Первый, как следует из названия, доступен всем, а второй владельцу подписи лучше держать в секрете. Естественно, ключи выбираются так, чтобы закрытый ключ нельзя было легко угадать по открытому. После чего закрытый ключ используется для шифрования документа (иными словами, его подписания), а открытый – для расшифровки (то есть для проверки подписи). При этом алгоритмы выбора ключей гарантируют, что открытым ключом можно расшифровать только те документы, которые шифровались соответсвующим ему закрытым ключом. Таким образом, если мы знаем открытый ключ владельца ЭП и смогли расшифровать им полученный от него документ, то он был точно подписан тем самым владельцем. Так работают асимметричные схемы ЭП.
Применение хэш-функций
В связи с тем, что шифрование закрытым ключом документа большого размера – довольно сложный и долгий процесс, обычно к тексту документа сначала применяют более быстрое и простое хэш-шифрование
. Полученный сравнительно короткий результат шифруют закрытым ключом, получая саму цифровую подпись. Вместе с ней открытый текст документа передаётся получателю.

Тот должен всего лишь хэшировать текст документа той же хэш-функцией, после чего расшифровать открытым ключом подпись и сравнить оба результата. Если они совпадают – то документ мог быть подписан только отправителем (вот она и неотказуемость) и не был испорчен, дополнен или подменён в процессе пересылки (а это целостность). А дляпоследнего свойства – авторства – необходимо, чтобы пара ключей подписавшего была закреплена за ним. На практике для этого используются так называемые удостоверяющие центры, которые по запросу выдают сертификат на пару ключей, а также гарантируют единственность обладания ими.
Что же может пойти не так?
Наверное, внимательные читатели уже увидели, что описанная выше схема представляет собой сферическую ЭП в вакууме.
И действительно, в теории взломать или подделать такую подпись можно несколькими способами. Самый лакомый способ для взломщика-криптоаналитика – это по открытому ключу угадать закрытый. Тогда злоумышленнику сразу открываются сказочные перспективы – ведь он сможет действовать от лица истинного владельца подписи и даже управлять его имуществом. Однако обычно такой взлом не возможен ввиду того, что подбор закрытого ключа по открытому – вычислительно нерешаемая задача. При генерации пары ключей широко применяются факторизация или дискретное логарифмирование, что оставляет взломщику мало надежды. Конечно можно попробовать подобрать закрытый ключ полным перебором, но при достаточно большом размере ключей такая возможность отпадает.
Ещё одно уязвимое место – это хэш-функция. Здесь возможны сразу несколько направлений атак. Если алгоритм хэширования не достаточно надёжный, то взломщик может подобрать какой-нибудь свой документ, применение хэш-функции к которому даст тот же результат, что и её применение к исходному документу. Или же злоумышленник может сгенерировать два документа, дающие одинаковый хэш, после чего при необходимости сможет подменить один документ другим. Названные ситуации считаются коллизиями хэш-функций
. Однако жизнь взломщика хэш-функции всё же не так легка: мало того, что подставной документ должен представлять из себя читаемый текст, а не быть бессмысленным набором бит, так еще и придумано достаточно криптостойких алгоритмов хэширования. Для примера, надёжными на момент написания статьи являются SHA-3
, BLAKE2
, семейство JH
или отечественный «Стрибог» (он же ГОСТ 34.11-2018
).
Подключаем человеческий фактор, бюрократию и частные организации
В самом начале мы говорили о свойствах, которыми должна обладать качественная электронная подпись. Из них под действием уже упомянутых выше атак пока страдали только её целостность и неотказуемость. Однако в реалиях нашего мира наибольшее количество нарушений происходит из-за подмены авторства.

В России получение ЭП регулируется законом № 63-ФЗ «Об электронной подписи»
. Для её оформления нужно получить сертификат от удостоверяющего центра (УЦ) на выбор
. В УЦ нужно предоставить необходимые документы и заплатить некоторую сумму, после чего забрать свой сертификат и заветный eToken с закрытым ключом.
Вот на получении сертификатов-то и возникает простор для всевозможных махинаций. Все УЦ являются коммерческими организациями, они обязательно аккредитованы Министерством цифрового развития и имеют лицензию от ФСБ. Однако чего не сделаешь ради прибыли — для УЦ порой желания клиентов выше установленных правил выдачи сертификата. Например, из-за этого сегодня возможно получить ЭП на другое лицо
, пользуясь утечками персональных данных или некомпетентностью УЦ. Это приводит к довольно печальным последствиям – вплоть до переоформления квартиры
или регистрации фиктивных организаций
с целью взятия кредитов. Данные способы подделки подписей не требуют даже знания криптографии, достаточно лишь воспользоваться особенностями сертифицирования ЭП в России.
Заключение
В статье вкратце было рассмотрено, как работает электронная подпись. Видно, что алгоритм достаточно устойчив и почти не имеет слабых мест. Однако, как и во многих случаях, самое слабое место этого метода — человеческий фактор, оставляющий большой простор для действий злоумышленников.
