- Почему подписи шнорра появились именно в bitcoin cash, а не в bitcoin?
- Что такое подписи шнорра?
- I. портирование кода модуля tclpkcs11 в модуль pyp11 для python
- Iii. управление токенами pkcs#11
- Vi. работа с объектами токена
- Аргумент безопасности
- Доказательство правильности
- Загрузка сертификата в хранилище.
- Обозначение
- Подписание
- Подписи шнорра уменьшают размер блока
- Постороение цепочки сертификата.
- Прверка подписи.
- Разбираем и просматриваем квалифицированные сертификаты средствами python/tkinter
- Утечка ключа из-за повторного использования nonce
Почему подписи шнорра появились именно в bitcoin cash, а не в bitcoin?
Изначально, продвижение этой идеи вели несколько разработчиков из лагеря Bitcoin Core, такие как Люк Дэш Джуниор, Крис Бельчер и так далее. Кроме них, идеей заинтересовался Омари Сеше, ведущий разработчик Bitcoin Cash клиента «Bitcoin ABC», а также его коллега из лагеря BCH, программист Марк Лундеберг.
Но почему разработчики сети Bitcoin Cash справились с задачей внедрения первыми? Несмотря на то, что многие ждали этого от Bitcoin Core, как от более старой и опытной команды программистов.
За последние два года в репозиторий Bitcoin Core были внесены существенные изменения. Но ни одно не касается дальнейшей анонимизации транзакций, точно как и расширения транзакционной способности. Причиной может стать тот факт, что существенные ресурсы Core сейчас направлены на создание и продвижение сторонних решений анонимизации и масштабирования, таких как Liquid, SegWit или Lightning. Занятно, что все эти протоколы до сих пор содержат огромное количество мелких недоработок.
Вполне возможно, что это также связано с неофициальной позицией лидеров мнений в лагере Core. А позиция эта такова, что биткоин уже «перерос» стадию цифровой наличности, и должен восприниматься людьми как «цифровое золото». А золото, как известно, это не наличность. Это неповоротливый актив с низкой степенью использования в качестве средства оплаты.
Средняя комиссия в сети биткоина, время подтверждения транзакций, периодически увеличиваются, что делает первую криптовалюту неудобной в повседневных расчетах. Никогда нельзя угадать, «за сколько часов» подтвердится одна транзакция. Кроме того, в последние месяцы транзакции с низкой комиссией висят по четыре дня без подтверждений, так что использовать биткоин в качестве наличных средств для повседневных расчетов для большинства пользователей было бы слишком неудобным и дорогим занятием.
Что такое подписи шнорра?
Подписями Шнорра называют улучшение, которое добавляет новый формат подписи транзакций в Bitcoin Cash (в будущем и в другие криптовалюты). Как известно, каждая транзакция подписывается при помощи приватного ключа, что означает добавление особой метки в качестве доказательства личности подписавшего. Можно сравнить это с гербовой сургучной печатью на старых письмах.
Если у вас есть криптовалютный кошелек, вы воспринимаете его как обычный кожаный кошелек, в котором криптовалюты лежат как бумажные купюры. На самом же деле, правильнее называть криптовалютный кошелек «связкой ключей», а не «кошельком». Слово wallet, которое прочно засело в подсознание масс, не отражает истинной сути и функций подобного софта. «Связка ключей» – вот правильный образ, помогающий сформировать правильное представление.
Для подписи любой транзакции нужна авторизация. Или по другому, нужно взять правильный ключ из «связки ключей» и использовать его для открытия монет, лежащих на том или ином адресе.
Во время транзакции, кошельком (или «связкой») используется один из приватных ключей пользователя, чтобы подписать ее. Каждый адрес BCH содержит свой приватный ключ, отличающийся от приватных ключей других адресов BCH.
Таким образом, если у вас есть приватный ключ от конкретного адреса с монетами, вы можете потратить эти активы, «подписав» транзакцию приватным ключом. В большинстве кошельков процедура подписи заключается в вводе пользователем пароля, подпись же совершается кошельком автоматически.
В рамках протокола Bitcoin (BTC) используются стандартные подписи из стека криптографических протоколов, используемых в военной промышленности, связи, передаче данных по сетям и т.п. Эти подписи зарекомендовали себя как хорошее средство защиты монет, но они не идеальны.
I. портирование кода модуля tclpkcs11 в модуль pyp11 для python
Портирование заключается в адаптации кода модуля tclpkcs11 к требованиям со стороны Python. Все изменения в проекте будут касаться только модуля tclpkcs11.c. Поэтому, первое, что мы сделаем, скопируем модуль tclpkcs11.c в файл pythonpkcs11.c и в дальнейшем будем работать именно с ним. Модуль для Python назовем
pyp11
Использовать для его создания будем C API Python. Почему-то этот способ многие (но не я) считают самым трудным, но зато он самый эффективный. Анализ C API для Tcl и C API для Python показал их значительное сходство, что и позволило очень быстро провести портирование.
Первое, в файле pythonpkcs11.c заменяем все объявления Tcl_Obj на PyObject, что вполне естественно: Tcl работает со своими объектами, а Python со своими.
Второе касается передачи параметров.
В общем виде объявление функции, реализующей ту или иную команду Tcl, в С-коде выглядит следующим образом (применительно к нашему коду):
name_proc_tcl (CliendData cd, Tcl_Interp *interp, int objc, Tcl_Obj[] *objv[] ){
. . .
};
В Python аналогичный заголовок функции будет выглядеть так:
name_proc_py (PyObject *self, PyObject *args){
. . .
};
В C-коде для tcl проверка количества входных параметров проводится с использованием переменной objc.
name_proc_tcl (CliendData cd, Tcl_Interp *interp, int objc, Tcl_Obj[] *objv[] ){
if (objc != 4) {
. . .
Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be "pki::pkcs11::login handle slot password"", -1));
return(TCL_ERROR);
}
. . .
};
В Python параметры передаются в виде кортежа. Поэтому число переданных параметров вычисляется функцией PyTuple_Size(args):
name_proc_py (PyObject *self, PyObject *args){
//Вводим переменную для числа параметров
int objc;
objc = PyTuple_Size(args);
. . .
if (objc != 3) {
PyErr_SetString(PyExc_TypeError, "pyp11_login args error (count args != 3)");
return NULL;
}
. . .
};
Отметим, что число параметров в коде для Tcl на единицу больше, т.к. в objv[0] хранится имя функции (аналогично функции main в C).
В приведенном коде наглядно видно как обрабатываются ошибки в Tcl и Python.
Вызов прерывания в случае ошибки для Tcl выполняется оператором return (TCL_ERROR);
Текстовое сообщение об ошибке формируется оператором TclSetObjResult.
Для Python будут использоваться операторы return NULL и PyErr_SetString.
Теперь самое главное — разбор параметров.
В Tcl каждый параметр передается как отдельный Tcl-объект, а в Python — как кортеж параметров в виде Python-объектов. Поэтому, если мы хотим вносить минимальные изменения в код, целесообразно сначала распаковать кортеж по отдельным объектам, например (применительно к функции pyp11_login):
…
char *tcl_handle;
long slotid_long;
char *password;
//Массив PyObject-ов для входных параметров
PyObject *argspy[3];
//Растаскиваем входные параметры/объекты ("OOO" - три объекта) по своим ячейкам
PyArg_ParseTuple(args, "OOO", &argspy[0], &argspy[1], &argspy[2])
…
Полученные объекты распаковываем с их функциональным назначением:
…
//Получаем строку (s) с handle библиотеки PKCS11
PyArg_Parse(argspy[0], "s", &tcl_handle);
//Получаем номер слота (l), в котором находится токен
PyArg_Parse(argspy[1], "l", &slotid_long);
//Получаем строку (s) с PIN-кодом владельца
PyArg_Parse(argspy[2], "s", &password);
...
Сразу оговоримся, что в C API Python имеется функция, которая позволяет сразу разбирать кортеж параметров. В этом случае можно обойтись одним оператором:
PyArg_ParseTuple(args, «sls», &tcl_handle, &slotid_long, &password);
Как ни парадоксально, это практически все рекомендации.
Осталось последнее, — возвращаемые значения.
Результаты выполнения команд возвращаются либо в виде строки, либо в виде списка, либо в виде словаря.
Приведём некоторые соответствия. Так для создания списка в коде для Tcl используется функция Tcl_NewObj(), а в коде для Python используется функция PyListNew(0).
Для добавления элемента в список для Tcl используется функция TclListObjAppendElement, а для Python — функция PyList_Append. Все эти соответствия можно найти, сравнив код TclPKCS11 и код pyp11 (ССЫЛКА).
Также вместо используемых функций ckalloc и ckfree в tclpkcs11.c для Tcl, в модуле pythonpkcs11.c используются стандартные функции работы с памятью — malloc и free.После проведенного анализа модификация кода вместе с тестированием заняла пару рабочих дней.
Iii. управление токенами pkcs#11
Для тестирования функций управления подойдет любой токен PKCS#11, даже токен без поддержки какой-либо криптографии, например RuTokenLite. Но поскольку мы ведём речь о российской криптографии, то целесообразно сразу иметь токен с поддержкой российской криптографии.
Установить программный токен или получить доступ к облачному токену можно, воспользовавшись утилитой cryptoarmpkcs.
После запуска утилиты необходимо зайти на вкладку «Создать токены»:
На вкладке можно найти инструкции для получения токенов.
Итак, у нас токен и библиотека для работы с ним. После загрузки модуля pyp11 требуется загрузить библиотеку для работы с нашим токеном. В примерах будут использоваться библиотека librtpkcs11ecp-2.0 для работы с аппаратным токеном, библиотека libls11sw2021 для работы с программным токеном и библиотека libls11cloud.so для работы с облачным токеном.
Итак, загружаем библиотеку командой loadmodule:
bash-4.4$ python3
Python 3.7.9 (default, Feb 1 2021, 16:55:33)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> import pyp11
>>> #Выбираем библиотеку pkcs11
>>> lib = "/usr/local/lib64/librtpkcs11ecp_2.0.so"
>>> #Обработка ошибки при загрузке библиотеки PKCS#11
>>> try:
... #Вызываем команду загрузки библиотеки и плохим числом параметров
... handlelib = pyp11.loadmodule(lib, 2)
... except:
... print ('Ошибка загрузки библиотеки: ')
... e = sys.exc_info()[1]
... e1 = e.args[0]
... print (e1)
...
Ошибка загрузки библиотеки:
pyp11_load_module args error (count args != 1)
>>> #Загружаем с правильным синтаксисом
>>> idlib = pyp11.loadmodule(lib)
>>> #Печатаем дескриптор библиотеки
>>> print (idlib)
pkcs0
>>>
Дескриптор загруженной библиотеки используется при её выгрузке:
>>> pyp11.unloadmodule(idlib)
Теперь, когда библиотека загружена, можно получить список поддерживаемых её слотов и узнать есть ли в каких слотах токены. Для получения списка слотов с полной информацией о них и содержащихся в них токенах используется команда:
>>> slots = pyp11.listslots(idlib)
>>>
Команда pyp11.listslots возвращает список, каждый элемент которого содержит информацию о слоте:
[<info slot1>, <info slot2>, ... , <info slotN>]
В свою очередь, каждый элемент этого списка также является списком, состоящим из четырех элементов:
[<номер слота>, <метка токена, находящегося в слоте>, <флаги слота и токена>, <информация о токене>]
Если слот не содержит токен, то элементы и содержат пустое значение.
Наличие токена в слоте определяется по наличию флага TOKEN_PRESENT в списке <флаги слота и токена>:
bash-4.4$ python3
Python 3.7.9 (default, Feb 1 2021, 16:55:33)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> import pyp11
>>> #Выбираем библиотеку
>>> #lib = '/usr/local/lib64/libls11sw2021.so'
>>> lib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'
>>> #Загружаем библиотеку
>>> libid = pyp11.loadmodule(lib)
>>> #Дескриптор библиотеки
>>> print (libid)
pkcs0
>>> #Загружаем список слотов
>>> slots = pyp11.listslots(libid)
>>> tokpr = 0
>>> #Ищем первый подключенный токен
>>> while (tokpr == 0):
... #Перебираем слоты
... for v in slots:
... #Список флагов текущего слота
... flags = v[2]
... #Проверяем наличие в стоке токена
... if (flags.count('TOKEN_PRESENT') !=0):
... tokpr = 1
... #Избавляемся от лишних пробелов у метки слота
... lab = v[1].strip()
... infotok = v[3]
... slotid = v[0]
... break
... if (tokpr == 0):
... input ('Нет ни одного подключенного токена.nВставьте токен и нажмите ВВОД')
... slots = pyp11.listslots(libid)
... #Информация о подключенном токене
...
Нет ни одного подключенного токена.
Вставьте токен и нажмите ВВОД
''
>>> #Информация о подключенном токене
>>> print ('LAB="' lab '", SLOTID=' str(slotid))
LAB="Rutoken lite <no label>", SLOTID=0
>>> print ('FLAGS:', flags)
FLAGS: ['TOKEN_PRESENT', 'RNG', 'LOGIN_REQUIRED', 'SO_PIN_TO_BE_CHANGED', 'REMOVABLE_DEVICE', 'HW_SLOT']
>>>
Если взглянуть на флаги (FLAGS:) подключенного токена, то в них отсутствует флаг ‘TOKEN_INITIALIZED’. Отсутствие этого флага говорит о том, что токен не инициализирован и требуется его инициализация:
#Проверяем, что токен проинициализирован
>>> if (flags.count('TOKEN_INITIALIZED') == 0''):
... #Инициализируем токен
... dd = pyp11.inittoken (libid, 0, '87654321',"TESTPY2")
...
>>>
Как видим, для инициализации токена используется следующая команда:
pyp11.inittoken (<дескриптор библиоткети>, <номер слота>, <SO-PIN>, <метка токена>)
Естественно, токен можно переинициализировать независимо от наличия флага ‘TOKEN_INITIALIZED’, только надо иметь в виду, что переинициализация токена ведет к уничтожению на нем всех объектов (ключи, сертификаты и т.д).
Vi. работа с объектами токена
Основными объектами, с которыми приходится иметь дело, работая с токенами PKCS#11, являются сертификаты и ключи. И те и другие имеют атрибуты. Нас в первую очередь интересуют атрибуты CKA_LABEL или метка объекта и СКА_ID или идентификатор объекта. Именно атрибут CKA_ID используется для доступа и к сертификатам и ключам.
Уже имея в своем распоряжении рассмотренные выше команды модуля pyp11, можно создать ключевую пару и сформировать подписанный запрос на сертификат. Отправить полученный запрос в удостоверяющий центр и получить там сертификат.
Как работает команда? Первым делом она вычисляет по открытому ключу сертификата идентификатор CKA_ID. Именно этот идентификатор будет возвращен в hex-кодировке после успешного размещения сертификата на токене. После установки сертификата на токен в DER-формате, устанавливаются его атрибуты CKA_ID и CKA_LABEL.
Если вам необходимо связать тройку <сертификат> x <открытый ключ> x <закрытый ключ> не только по CKA_ID, но и по метке CKA_LABEL, то необходимо установить метку у ключевой пары аналогичную метке сертификата. Для этого используется команда rename:
pyp11.rename(<идентификатор библиотеки>, <слот токена>, <тип объекта>, <ассоциированный список>)
В указывается, к каким типам объектов будет применяться команда: ‘cert’ | ‘key’ | ‘all’ (сертификаты, ключевая пара, к тому и другому).
Команда rename позволяет менять не только CKA_LABEL, но и CKA_ID. Конкретные объекты могут задаваться идентификаторами объектов CKA_ID (pkcs11_id), например:
#Импортируем сертификат и получаем его CKA_ID
labcert = 'LabelNEW'
ckaid = pyp11.importcert(aa, 0, cert_der_hex, labcert)
#Устанавливаем метку сертификата и для ключей
#Готовим словарь
ldict = dict(pkcs11_id=ckaid, pkcs11_label=labcert)
#Меняем метки у ключей
pyp11.rename(aa, 0, 'key', ldict)
Аналогичным образом меняется атрибут CKA_ID. В этом случае в словарь вместо метки указывается новый CKA_ID:
ldict = dict(pkcs11_id=ckaid, pkcs11_id_new=11111)
Аналогичным образом можно удалить объекты:
pyp11.delete(<идентификатор библиотеки>, <слот токена>, <тип объекта>, <ассоциированный список>)
При уничтожении в словарь попадает только один элемент, который будет указывать на удаляемые объекты. Это либо CKA_ID (ключ pkcs11_id) либо непосредственно handle-объекта (как правило, его можно получить по команде pyp11.listobjects, ключ pkcs11_handle):
ldict = dict(pkcs11_id=ckaid)
#Или с handle-объекта:
#ldict = dict(hobj=pkcs11_handle)
#Уничтожить личный сертификат с ключами
pyp11.login(aa. 0, '01234567')
pyp11.delete(aa, 0, 'all', ldict)
pyp11.logout(aa, 0)
Упомянем еще об одной очень редко используемой команде. Это команда закрытия сессий на токене:
pyp11.closesession(<идентификатор библиотеки>)
Эту команду следует вызывать, когда возникнет ошибка «PKCS11_ERROR SESSION_HANDLE_INVALID», а затем повторить команду, на которой возникла ошибка. Эта ошибка может возникнуть при кратковременном извлечении токена из компьютера при работе вашей программы.
И завершим мы рассмотрение командой pyp11.listcerts:
Вот пример кода:
#!/usr/bin/python3
#-*- coding: utf-8 -*-
import sys
import time
import pyp11
print('Список сертификатов токена')
aa = pyp11.loadmodule('/usr/local/lib64/libls11sw2021.so')
lcerts = pyp11.listcerts(aa, 0)
if (len(lcerts) == 0):
print ('На токене нет сертификатов')
quit()
#Перебираем сертификаты
for cert in lcerts:
#Информация о сертификате
for key in cert:
print (key ': ' cert[key])
#Сравним с pyp11.listobjects
lm = pyp11.listobjects(aa, 0, 'cert', 'value')
print('Работа с listobjects:')
for obj in lm:
for key in obj:
print (key ': ' obj[key])
quit()
Команды pyp11.listobjects для сертификатов и команда pyp11.listcerts фактически дублируют друг друга, но так сложилось исторически.
Аргумент безопасности
Схема подписи была построена путем применения преобразования Фиата – Шамира к протоколу идентификации Шнорра. Следовательно (согласно аргументам Фиата и Шамира), это безопасно, если моделируется как случайный оракул .
ЧАС { displaystyle H} Его безопасность также может быть аргументирована в рамках общей модели группы при условии, что она «устойчива к случайному префиксу префикса» и «устойчива к второму префиксу случайного префикса». В частности, это не должно быть устойчивым столкновением .
ЧАС { displaystyle H} ЧАС { displaystyle H} В 2021 году Сёрин представил точное доказательство схемы подписи Шнорра. В частности, Сёрен показывает, что доказательство безопасности с использованием леммы о разветвлении является наилучшим возможным результатом для любых схем подписей, основанных на односторонних гомоморфизмах групп, включая подписи типа Шнорра и схемы подписи Гийу – Кискватера . А именно, согласно предположению ROMDL, любая алгебраическая редукция должна терять фактор в своем отношении времени к успеху, где – функция, которая остается близкой к 1, пока « заметно меньше 1», где – вероятность подделки ошибка при выполнении не более чем запросов к случайному оракулу.
ж ( ϵ F ) q час { displaystyle f ({ epsilon} _ {F}) q_ {h}} ж ≤ 1 { displaystyle f leq 1} ϵ F { displaystyle { epsilon} _ {F}} ϵ F { displaystyle { epsilon} _ {F}} q час { displaystyle q_ {h}}
Доказательство правильности
Относительно легко увидеть, что если подписанное сообщение равно проверенному сообщению:
е v знак равно е { displaystyle e_ {v} = e} р v знак равно грамм s y е знак равно грамм k – Икс е грамм Икс е знак равно грамм k знак равно р { displaystyle r_ {v} = g ^ {s} y ^ {e} = g ^ {k-xe} g ^ {xe} = g ^ {k} = r} , а значит .
е v знак равно ЧАС ( р v ∥ M ) знак равно ЧАС ( р ∥ M ) знак равно е { displaystyle e_ {v} = H (r_ {v} parallel M) = H (r parallel M) = e} Общественные элементы: , , , , , , . Частные элементы: , .
грамм { displaystyle G} грамм { displaystyle g} q { displaystyle q} y { displaystyle y} s { displaystyle s} е { displaystyle e} р { displaystyle r} k { displaystyle k} Икс { displaystyle x}
Это показывает только то, что правильно подписанное сообщение будет правильно проверено; многие другие свойства требуются для безопасного алгоритма подписи.
Загрузка сертификата в хранилище.
Для того, чтобы загрузить сертификат в хранилище нужно выполнить следующие шаги:
- Считать сертификат из файла
- Открыть хранилище сертификатов
- Положить в него сертификат
- Закрыть хранилище
Тут меня ждал первый ньюанс, что в CPCSP нет функции для чтения сертификата из файла, поэтому ее нужно будет написать вручную. Она выглядит следующим образом:
typedef struct CERT {
BYTE *content;
DWORD size;
} CERT;
CERT readFile(char *filename)
{
CERT cert = {NULL, 0};
FILE *fCert;
fCert = fopen(filename, "r");
if (fCert)
{
fseek(fCert, 0, SEEK_END);
cert.size = ftell(fCert);
rewind(fCert);
cert.content = (unsigned char *)malloc(cert.size * sizeof(unsigned char));
fread(cert.content, cert.size, 1, fCert);
}
else
{
perror("Error open certificate file");
}
fclose(fCert);
return cert;
}
PCCERT_CONTEXT ReadCertificateFromFile(char *filename)
{
CERT fileCert;
PCCERT_CONTEXT cert = NULL;
fileCert = readFile(filename);
cert = CertCreateCertificateContext(
X509_ASN_ENCODING,
fileCert.content,
fileCert.size
);
if (!(cert))
{
perror("Error create cert");
}
return cert;
}
В коде выше файл считывается специальную структуру CERT, которая содержит размер и содержимое сертификата. Затем на основе этой информации формируется структура PCCERT_CONTEXT, которая в дальнейшем будет загружаться в хранилище CPCSP.
Далее в описании функций будут использоваться следующие коды ошибок:
# define OPERATION_SUCCESS 0
# define OPEN_STORE_ERROR 1
# define ADD_CERT_TO_STORE_ERROR 2
# define CLOSE_STORE_ERROR 3
# define ADD_CRL_TO_STORE_ERROR 4
# define STR_TO_BIN_LEN_ERROR 5
# define STR_TO_BIN_CONTENT_ERROR 6
# define VERIFY_MSG_SIGNATURE 7
# define GET_CERT_CHAIN_ERROR 8
# define READ_CERT_ERROR 9
# define READ_CRL_ERROR 10
Функция загрузки сертификата в хранилище будет выглядеть следующим образом:
int LoadCertificateToSystemStore(char *cert_file_path, char *store_name)
{
HCERTSTORE cpcsp_cert_store = NULL;
PCCERT_CONTEXT cert_context;
cert_context = ReadCertificateFromFile(cert_file_path);
if (!cert_context)
return READ_CERT_ERROR;
cpcsp_cert_store = CertOpenSystemStore(0, store_name);
if (!cpcsp_cert_store)
return OPEN_STORE_ERROR;
if (!CertAddCertificateContextToStore(
cpcsp_cert_store,
cert_context,
CERT_STORE_ADD_REPLACE_EXISTING,
NULL))
return ADD_CERT_TO_STORE_ERROR;
if (!CertCloseStore(cpcsp_cert_store, 0))
return CLOSE_STORE_ERROR;
if (cert_context)
CertFreeCertificateContext(cert_context);
return OPERATION_SUCCESS;
}
В этой функции считывается файл сертификата (функция ReadCertificateFromFile), затем открываем системное хранилище методом CertOpenSystemStore. Если системное хранилище открылось успешно, то с помощью метода CertAddCertificateContextToStore, сертификат загрузается в хранилище. И в заключении хранилище закрывается функцией CertCloseStore.
Нужно отметить что функция CertOpenSystemStore ипользуется только для чтения системных хранилищ (root, ca, my), для остальных надо использовать CertOpenStore.
Обозначение
В следующих,
- Возведение в степень означает повторное применение групповой операции.
- Сопоставление означает умножение на множестве классов конгруэнтности или применение групповой операции (если применимо)
- Вычитание означает вычитание на множестве классов сравнения.
- M ∈ { 0 , 1 } * { Displaystyle М ин {0,1 } ^ {*}} , набор конечных битовых строк
- s , е , е v ∈ Z q { displaystyle s, e, e_ {v} in mathbb {Z} _ ?} , множество классов сравнения по модулю q { displaystyle q}
- Икс , k ∈ Z q × { Displaystyle х, к ин mathbb {Z} _ ? ^ { times}} , То мультипликативная группа целых чисел по модулю q { displaystyle q} (для простой , ) q { displaystyle q} Z q × знак равно Z q ∖ 0 ¯ q { Displaystyle mathbb {Z} _ ? ^ { times} = mathbb {Z} _ ? setminus { overline {0}} _ ?}
- y , р , р v ∈ грамм { displaystyle y, r, r_ {v} in G} .
Подписание
Чтобы подписать сообщение :
M { displaystyle M} Подпись пара, .
( s , е ) { displaystyle (s, e)} Обратите внимание : если , то представление подписи может уместиться в 40 байтов.
s , е ∈ Z q { displaystyle s, e in mathbb {Z} _ ?} q < 2 160 { displaystyle q <2 ^ {160}}
Подписи шнорра уменьшают размер блока
До недавнего времени, подпись транзакций в сети Bitcoin Cash осуществлялась при помощи стандарта ECDSA, который подразумевает, что каждая подпись равна около 70 байт. Тот же стандарт использует и Bitcoin, а еще – целая куча различных форков вроде Bitcoin Gold, Bitcoin Diamond, Bitcoin Private и так далее.
Подпись Шнорра поднимает процесс использования «ключей» на новый уровень.
- Во первых, она занимает лишь 64 байта, когда попадает в блок, что уменьшает занимаемое транзакциями место на 4%. Так как транзакции с подписью Шнорра имеют одинаковый размер, это делает возможным предварительный подсчет общего размера той части блока, что содержит именно такие подписи. А предварительный подсчет размера блока – ключ к его безопасному увеличению в будущем, вне зависимости от того, в какой криптовалюте это применяется.
- Во-вторых, экономится пространство в транзакциях мультиподписи. Мультиподпись – это когда ваш криптовалютный кошелек отправляет транзакцию, для успешного подтверждения которой необходимы несколько «подписей-ключей» (возможно, от нескольких людей), а не только ваша. Эти подписи берутся либо от ваших нескольких собственных адресов отправки. Либо из адресов нескольких людей, участвующих в транзакции. Ранее, транзакция собирала подписи с каждого человека (или с каждого отдельного вашего адреса) по отдельности.
Но если в транзакции задействовать подпись Шнорра, тогда множество подписей от разных лиц объединяется в одну общую «мета-подпись». Бесспорно, одна подпись это меньше чем пять или десять.
Таким образом, значительно уменьшается место, занимаемое подписями в блоке и соответственно блокчейне. Разработчики Bitcoin Cash Омари Сеше и Марк Лундеберг уверены, что при помощи подписей Шнорра можно будет сэкономить 20-25% пространства блока.
Такая экономия могла бы быть достигнута и в Bitcoin, если бы подписи Шнорра внедрили еще в 2021 году, когда об этом задумался видный разработчик, Люк Джуниор. Но с тех пор ничего не поменялось, в Bitcoin все еще нет этого стандарта, несмотря на многочисленные обещания.
Постороение цепочки сертификата.
Код функции проверки цепочки сертификатов выглядит следующим образом
int VerifyCertChain(char *certFilePath)
{
PCCERT_CONTEXT pCertContext;
PCCERT_CHAIN_CONTEXT pChainContext;
CERT_ENHKEY_USAGE EnhkeyUsage;
CERT_USAGE_MATCH CertUsage;
CERT_CHAIN_PARA ChainPara;
/*
инициализация парметров поиска и сопоставления, которые
будут использоваться для построения цепочки сертификатов
*/
EnhkeyUsage.cUsageIdentifier = 0;
EnhkeyUsage.rgpszUsageIdentifier = NULL;
CertUsage.dwType = USAGE_MATCH_TYPE_AND;
CertUsage.Usage = EnhkeyUsage;
ChainPara.cbSize = sizeof(CERT_CHAIN_PARA);
ChainPara.RequestedUsage=CertUsage;
pCertContext = ReadCertificateFromFile(certFilePath);
if (!CertGetCertificateChain(
NULL,
pCertContext,
NULL,
NULL,
&ChainPara,
0,
NULL,
&pChainContext))
{
perror("The chain could not be created");
}
int result = pChainContext->TrustStatus.dwErrorStatus;
if (pChainContext)
{
CertFreeCertificateChain(pChainContext);
}
return result;
}
Помимо настроек цепочки, тут вызывается функция CertGetCertificateChain, которая формирует собственно цепочку сертификатов и записывает ее в структуру PCCERT_CHAIN_CONTEXT. В данной структуре поле TrustStatus отвечает за статус опреации, если цепочка построена корректно, то dwErrorStatus будет 0, иначе будет записан код ошибки.
Прверка подписи.
Для начала я подумал сорфировать самоподписной сертификат для проверки функционирования функции, но оказалось, что CPCSP не поддерживает их, поэтому я создал сертификат в Тестовом УЦ КриптоПро. Я не буду описывать данный процесс, так как к библиотике он имеет посредственное отношение.
Также надо отметить, что сертификат ЦС, надо загрузить в хранилище “Доверенные корневые…”. Иначе сгенерированный тестовый сертификат не установиться и плагин для ЭЦП не будет корректно работать. Код функции проверки подписи приведен ниже:
int VerifySignedMessage(char *signature)
{
DWORD blob_size = 0;
/*
определяем размер выходного der блоба
для подписанного сообщения
*/
if (!CryptStringToBinaryA(
signature,
strlen(signature),
CRYPT_STRING_BASE64,
NULL,
&blob_size,
NULL,
NULL))
return STR_TO_BIN_LEN_ERROR;
/*
заполняем блоб подписанного сообщения
*/
BYTE *msg_blob;
msg_blob = (BYTE *)malloc(blob_size);
if (!CryptStringToBinaryA(
signature,
strlen(signature),
CRYPT_STRING_BASE64,
msg_blob,
&blob_size,
NULL,
NULL))
return STR_TO_BIN_CONTENT_ERROR;
/*
выполняем проверку подписи
*/
CRYPT_VERIFY_MESSAGE_PARA verify_params;
verify_params.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
verify_params.dwMsgAndCertEncodingType = ENCODING_TYPE;
verify_params.hCryptProv = 0;
verify_params.pfnGetSignerCertificate = NULL;
verify_params.pvGetArg = NULL;
if(!CryptVerifyMessageSignature(
&verify_params,
0,
msg_blob,
blob_size,
NULL,
NULL,
NULL))
return VERIFY_MSG_SIGNATURE;
return OPERATION_SUCCESS;
}
Код снабжен коментариями, которые поясняют за что какой кусок кода отвечает.
Также надо отметить что функция CryptStringToBinaryA вызывается 2 раза, первый для получения размер подписи, а второй, чтобы получить данные раскодированные из base64 данные. Ну и затем подпись соответственно проверяется.
Разбираем и просматриваем квалифицированные сертификаты средствами python/tkinter

Квалифицированные сертификаты быстро стали неотъемлемой частью повседневной жизни. И все больше людей хотят увидеть этого «зверя» изнутри. Это с одной стороны. А с другой стороны разрабатывается все больше приложений, в которых задействуется информация иэ этих сертификатов. И это не только атрибуты ИНН или ОГРН владельца или издателя сертификата. Это может быть и информация о том какой криптопровайдер использован владельцем сертификата (атрибут subjectSignTool) для генерации закрытого ключа или на базе каких сертифицированных средств создан удостоверяющий центр (УЦ), выпустивший тот или иной сертификат. И если написать программку, которая будет анализировать выпускаемые сертификаты, то можно будут собрать интересную статистику по тому какие СКЗИ используют владельцы сертификатов и на базе каких (правда это менее интересно) сертифицированных (или несертифицированных) средств развернуты УЦ (атрибут issuerSignTools):
На просторах Хабра уже предпринималась успешная попытка разобрать квалифицированный сертификат. К сожалению, разбор коснулся только получения атрибутов ИНН, ОГРН и СНИЛС, входящие в состав отличительного имени DN (Distinguished Name). Хотя, почему к сожалению? Перед автором стояла конкретная задача и она была решена. Мы же хотим получить доступ к атрибутам квалифицированного сертификата через Python и дать графическую утилиту для их просмотра.
Для доступа к атрибутам сертификата будем использовать пакет fsb795. Пакет доступен как для Pytho2, так и для Python3, как для Linux, так и для Windows. Для его установки достаточно выполнить традиционную команду:
# python -m pip install fsb795
Collecting fsb795
Requirement already satisfied: pyasn1-modules>=0.2.2 in /usr/lib/python2.7/site-packages (from fsb795) (0.2.2)
Collecting pyasn1>=0.4.4 (from fsb795)
Using cached https://files.pythonhosted.org/packages/d1/a1/7790cc85db38daa874f6a2e6308131b9953feb1367f2ae2d1123bb93a9f5/pyasn1-0.4.4-py2.py3-none-any.whl
Requirement already satisfied: six in /usr/lib/python2.7/site-packages (from fsb795) (1.11.0)
Installing collected packages: pyasn1, fsb795
Successfully installed fsb795-1.5.2 pyasn1-0.4.4
[root@localhost GCryptGOST]#
Пакет fsb795 в своей работе использует пакеты pyasn1 и pyasn1-modules. Поэтому если они не установлены, то будет предпринята попытка их установить.
Для python3 эта команда выглядит следующим образом:
# python -m pip install fsb795
...
#
Можно также скачать установочные пакеты
python3
и
python2
и локально их установить.
Название пакета, по аналогии с модулями из пакета pyasn1-modules, например, rfc2459 и т.д., указывает на то, что он предназначен для работы с сертификатами, соответствующими требованиям Приказа ФСБ РФ от 27 декабря 2021 г.
№ 795
«Об утверждении требований к форме квалифицированного сертификата…».
Доступ к сертификату в пакете fsb795 реализован через класс Certificate:
# -*- coding: utf-8 -*-
import os, sys
import pyasn1
import binascii
import six
from pyasn1_modules import rfc2459, pem
from pyasn1.codec.der import decoder
from datetime import datetime, timedelta
class Certificate:
#Атрибуты класса
cert_full = ''
cert = ''
pyver = ''
formatCert = ''
def __init__ (self,fileorstr):
#Проверка наличия файла с сертификатом
if not os.path.exists(fileorstr):
#Если файла нет, то предполагается, что это может быть
#строка с сертификатом в PEM-формате
strcert = fileorstr.strip('n')
if (strcert[0:27] != '-----BEGIN CERTIFICATE-----'):
return
idx, substrate = pem.readPemBlocksFromFile(six.StringIO(
strcert), ('-----BEGIN CERTIFICATE-----',
'-----END CERTIFICATE-----')
)
self.pyver = sys.version[0]
try:
self.cert_full, rest = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())
self.cert = self.cert_full["tbsCertificate"]
self.formatCert = 'PEM'
except:
self.pyver = ''
self.formatCert = ''
return
#Сертификат в фвйле
#В атрибут self.pyver заносится версия python
self.pyver = sys.version[0]
filename = fileorstr
if (self.pyver == '2'):
if sys.platform != "win32":
filename = filename.encode("UTF-8")
else:
filename = filename.encode("CP1251")
#Проверяем на DER
file1 = open(filename, "rb")
substrate = file1.read()
if (self.pyver == '2'):
b0 = ord(substrate[0])
b1 = ord(substrate[1])
else:
b0 = substrate[0]
b1 = substrate[1]
#Проверка на PEM/DER, наличие последовательности 0x30, длина сертификата не может быть меньше 127 байт
if (b0 == 48 and b1 > 128) :
self.formatCert = 'DER'
else:
self.formatCert = 'PEM'
file1 = open(filename, "r")
idx, substrate = pem.readPemBlocksFromFile(
file1, ('-----BEGIN CERTIFICATE-----',
'-----END CERTIFICATE-----')
)
file1.close()
try:
self.cert_full, rest = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())
self.cert = self.cert_full["tbsCertificate"]
except:
self.pyver = ''
self.formatCert = ''
#Методы класса для доступа к атрибутам сертификата
def subjectSignTool(self):
. . .
#Тест, запускаемый из командной строки
if __name__ == "__main__":
. . .
Для создании экземпляра объекта для конкретного сертификата достаточно выполнить следующий оператор:
$ python
Python 2.7.15 (default, May 23 2021, 14:20:56)
[GCC 5.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>import fsb795
>>tek_cert = fsb795.Certificate(<файл/строка с сертификатом>)
>>
В качестве параметра при создании экземпляра класса указывается сертификат, который может находится либо в файле формата PEM или DER или быть строкой в формате PEM.
После создания каждый экземпляр имеет четыре атрибута: pyver, formatCert, cert_full и cert.
По атрибуту pyver можно проверить как прошло распарсивание сертификата. Если pyver равен пустой строке, то файл или строка не содержит сертификата. В противном случае атрибут pyver содержит версию языка python:
>>> c1=fsb795.Certificate('Эта строка не может быть сертификатом')
>>> if (c1.pyver == ''):
... print ('Вы не предоставили сертификат')
...
Вы не предоставили сертификат
>>> c2 = fsb795.Certificate('/home/a513/cert_nss.der')
>>> if (c2.pyver != ""):
... print(c2.pyver)
...
2
>>> print(c2.formatCert)
DER
>>>
Атрибут formatCert при успешном создании экземпляра класса Certificate содержит тип формата файла/строки с сертификатом. Это может быть PEM или DER. Зачем этот атрибут нужен станет ясно ниже.
Пакет fsb795 создавался с использованием пакета pyasn1. Итак, осталось нерассмотренными два атрибута. В атрибуте cert хранится tbs-сертификат, готовый к использованию с пакетом pyasn1. Другой атрибут cert_full хранит весь декодированный сертификат с учетом rfc2459. Покажем, как можно получить алгоритм публичного ключа, имея атрибут cert и подключенный пакет pyasn1:
>>> pubkey = c2.cert['subjectPublicKeyInfo']
>>> ff = pubkey['algorithm']
>>> ff1 = ff['algorithm']
>>> print (ff1)
1.2.643.2.2.19
>>>
В конце можно будет оценить возможности пакета fsb795 по получению информации о публичном ключе квалифицированного сертификата.
Когда экземпляр класса Certificate успешно создан, то в нашем распоряжении оказываются методы которые позволяют легко получить необходимые данные из сертификата. Всю информацию о публичном ключе мы можем получить следующим образом:
>>> c3 = fsb795.Certificate('cert.der')
>>> key_info=c3.publicKey()
>>> for opt in key_info.keys():
... val = str(key_info[opt])
... print (opt '=' val)
...
curve=1.2.643.2.2.36.0
hash=1.2.643.2.2.30.1
valuepk=5b785f86f0dd5316ba37c8440e398e83f2ec0c34478f90da9c0c8046d341ff66f9044cd00a0e25530
acefd51e6be852dbecacbaabc55e807be8e1f861658bd58
algo=1.2.643.2.2.19
>>>
На данный момент класс Certificate содержит следующие методы:
В спойлере лежит тестовый пример, который наглядно демонстрирует работу этих методов.
Для запуска тестового примера достаточно выполнить команду:
$python test795.py
Имея в своем распоряжении пакет fsb795 было естественным написать на языке python самодостаточную платформонезависимую графическую утилиту для просмотра квалифицированных сертификатов. В качестве графической поддержки использован пакет Tkinter:
Утилита viewCertFL63 имеет три вкладки. На вкладке «О сертификате » помимо прочего отображается текущее время. Мы еще вернемся к нему ниже. Для выбора сертификата достаточно нажать кнопку «Выбрать»:
Обратите внимание на кнопку (те кто работают на Windows этой кнопки не увидят), она позволяет скрывать так называемые невидимые файлы/каталоги (hidden). Для того чтобы эта кнопка появилась достаточно выполнить следующие команды:
if sys.platform != "win32":
root.tk.call('set', '::tk::dialog::file::showHiddenBtn', '1')
root.tk.call('set', '::tk::dialog::file::showHiddenVar', '0')
Очень полезная кнопка. Итак, после выбора сертификата вкладка «О сертификате» примет вид:
Что здесь примечательного так это то, что если во время просмотра сертификата закончится срок его действия, то на иконке в левом верхнем углу печать разломается на две половинки. Каждый может убедится в этом, переставив на компьютере часы на один год вперед.
На вкладке «Детали» можно подробно просмотреть характеристики выбранного атрибута квалифицированного сертификата:
И, наконец, третья вкладка «Текст». В этой вкладке отображается содержимое всего сертификата:
Для просмотра сертификата можно использовать не только Python (кнопка «Python»), то и утилиты openssl и pp из состава Network Serurity Services (NSS). Если у кого-то не окажется этих утилит, то первую можно получить, собрав openssl с поддержкой российской криптографии. При использовании утилиты pp вывод сертификата выглядит так:
Выше мы упоминали про атрибут formatCert класса Certificate пакета fsb795. Так вот значение этого атрибута нам необходимо для указания формата файла с сертификатом при запуске той или утилиты. Например, вызов утилиты pp при формате файле PEM выглядит следующим образом:
$pp –t c –u –a –i <файл сертификата>
Параметр «-a» и указывает на формат файла PEM. Для формата DER он не указывается.
Аналогичным образом задается и параметр “–inform ” для openssl.
Кнопка «Утилита» служит для указания пути к утилитам openssl или pp.
Дистрибутивы утилиты viewCertFL63 находятся
здесь
.
Сборка дистрибутивов была сделана с использованием пакета pyinstaller:
$python pyinstaller.py --noconsole -F viewCertFL63.py
Утечка ключа из-за повторного использования nonce
Как и в случае с тесно связанными алгоритмами подписи DSA , ECDSA и ElGamal , повторное использование значения секретного одноразового номера в двух подписях Шнорра разных сообщений позволит наблюдателям восстановить закрытый ключ. В случае подписей Шнорра это просто требует вычитания значений:
k { displaystyle k} s { displaystyle s}
- s ′ – s знак равно ( k ′ – k ) – Икс ( е ′ – е ) { displaystyle s’-s = (k’-k) -x (e’-e)} .
Если но то можно просто изолировать. Фактически, даже незначительные отклонения в значении или частичная утечка могут раскрыть закрытый ключ после сбора достаточного количества подписей и решения проблемы скрытого числа .
k ′ знак равно k { Displaystyle к ‘= к} е ′ ≠ е { displaystyle e ‘ neq e} Икс { displaystyle x} k { displaystyle k} k { displaystyle k}