Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

PKCS#11 и OpenSSL имеют разный формат представления объектов. Так, они по-разному представляют ECDSA ключи и подписи на них. Поэтому, когда мы хотим проверить в OpenSSL сырую ECDSA подпись полученную через PKCS#11, ее необходимо сконвертировать в OpenSSL формат.  Для проверки подписи нужно также сконвертировать и открытый ключ.

Ручной конвертации можно избежать воспользовавшись OpenSSL движком rtengine. Он предназначен для работы с объектами на токене средствами OpenSSL. Но не во всех задачах использование OpenSSL приемлемо, поэтому мы написали статью, как конвертировать объекты вручную.

Конвертация открытого ключа

Описание формата открытого ключа в PKCS#11

Открытый ключ

Библиотека PKCS#11 возвращает открытый ключ ECDSA в через поле атрибут CKA_EC_POINT в формате OCTET_STRING. Значение атрибута – это число представленное в виде TLV структуры – OCTET_STRING:

  1. T – тег типа размером один байт. В нашем случае это 04 – OCTET_STRING.
  2. L – длина значения V. Она может быть представлена в виде нескольких байт. Если длина не превышает 7F байт, то L состоит из одного байта. Если больше 7F байт, то старший бит устанавливается в 0, а младшие 7 бит определяют длину L.  Например, L=7A – длина  0x7A (122). L=81 80 – длина 0x80(128). L=82 7F FF – длина 0x7FFF(32767).  
  3. V –  значение размером L байт.

Пример CKA_EC_POINT

Снизу представлен пример TLV структуры открытого ключа. Первый байт 04 – это тег OCTET_STRING. Второй – длина открытого ключа (64 байта). Последующие 64 байта – значение ключа.

04 41 04 8B 92 09 CC C0 A1 B4 19 BA 80 2E 44 5D A2 16 E6 92 AA 9C BB B9 CC B0 CE 5C 76 71 C6 DF E4 CA 83 46 BB 92 2B C8 6F FC 15 20 D8 11 5F 32 4F 7F CA BB DE 9F E3 62 5E 8E 2C D2 2D C2 51 EC 3B 8C 67

...

Описание формата открытого ключа в OpenSSL

OpenSSL позволяет представлять открытый ECDSA ключ в виде ASN.1  структуры. Эта структура хранит не только значение открытого ключа, но и его параметры.

Пример открытого ключа в формате OpenSSL:

Code Block
languagebash
titleASN.1 структуры открытого ECDSA ключа
30 56 -- SEQUENCE. первый байт -- тег типа (30), второй -- длина значения (56).
	30 10 -- SEQUENCE
		06 07 2A 86  48 CE 3D 02 01 -- OBJECT IDENTIFIER. Идентифицирует типа ключа. В нашем случае это ecPublicKey (открытый ключ эл. кривой).
		06 05 2B 81 04 00 0A -- OBJECT IDENTIFIER. Идентифицирует параметры ключа. В нашем случае это secp256k1 (параметр эл. кривой)
	
	03 42 00 04 8B 92 09 CC C0 A1 B4 19 BA 80 2E 44 5D A2 16 E6 92 AA 9C BB B9 CC B0 CE 5C 76 71 C6 DF E4 CA 83 46 BB 92 2B C8 6F FC 15 20 D8 11 5F 32 4F 7F CA BB DE 9F E3 62 5E 8E 2C D2 2D C2 51 EC 3B 8C 67 -- BIT STRING. Значение открытого ключа  

Описание процесса конвертации

Для того, чтобы сконцентрировать ECDSA ключ из PKCS#11 формата, нужно:

  1. Создать SEQUENCE со значениями типа OBJECT IDENTIFIER. Первый элемент – OID типа открытого ключа на эллиптической кривой. Второй – OID параметра эл. кривой.
  2. Сконвертировать открытый ключ из структуры OCTET_STRING в структуру BIT_STRING.
  3. Положить результаты в SEQUENCE. Нужно проставить правильную длину выходной последовательности.


Конвертация из OCTET_STRING происходит следующим образом 

  1. Тег 04 заменяется на 03 (тег BIT STRING).
  2. После байтов длины (L) ставится байт 00.
  3. L увеличивается на 1.


Пример конвертации

Для открытого ключа сверху

разбор по частям

04 41 04 (открытый ключ - 64 байта)

04 - тег

41 - размер

04 - тег

далее - открытый ключ

Что нужно сделать

  • добавить параметры эллиптической кривой
  • добавить OID открытого ключа
  • изменить первый тег 04 на 03
  • добавить 00 байт перед вторым тегом 04
  • увеличить размер ключа на единицу (до 42)

Что должно получиться

...

  1. На первом этапе сформируем последовательность из идентификаторов типа открытого ключа и параметра эл. кривой: 
  1. 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 0A

...

    1. 30 - тег

56 - размер

30 - тег

    1. 10 - размер
    2. 06 - тег
    3. 07 - размер
    4. 2A 86 48 CE 3D 02 01 - id-ecPublicKey
    5. 06 - тег
    6. 05 - размер
    7. 2B 81 04 00 0A - secp256k1

03 - тег

42 - размер

00 04 - магия

Пример правильной структуры

30 56 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 0A 03 42 00 04 8B 92 09 CC C0 A1 B4 19 BA 80 2E 44 5D A2 16 E6 92 AA 9C BB B9 CC B0 CE 5C 76 71 C6 DF E4 CA 83 46 BB 92 2B C8 6F FC 15 20 D8 11 5F 32 4F 7F CA BB DE 9F E3 62 5E 8E 2C D2 2D C2 51 EC 3B 8C 67

Формат файла

Для утилиты openssl нужен открытый ключ в формате PEM

Чтобы перевести в PEM формат нужно выполнить команду

openssl ec -pubin -inform DER -in pubkey.der -outform PEM -out pubkey.pem

Проверка открытого ключа

openssl ec -check -pubin -in pubkey.pem

Команда напишет EC Key valid. если открытый ключ корректный

Подпись

Библиотека PKCS#11 возвращает подпись через буфер в функции С_Sign в виде R|S

Пример R|S подписи

EC 8D 6D 05 96 B9 8A AE 04 F6 AE 83 D8 04 99 FA D4 A3 EA 37 86 95 A9 DC 0D 3F 09 7F 76 5A F8 40 AA 09 E6 8D D6 56 82 02 4B CF 15 78 21 A6 23 75 81 6C 93 51 90 ED 58 91 33 6E E2 91 4D 57 B5 50

Первые 32 байта - R, остальные 32 байта - S

Что нужно сделать

  • добавить тег 30
  • добавить размер 44, 45 или 46
  • к каждой из половинок добавить тег 02
  • к каждой из половинок добавить длину (размер) 20 или 21
  • в зависимости от первого байта R и S, если первый байт больше 7F то нужно добавить лишний 00 байт в начало строки и увеличить длину на 1

Что должно получиться

30 46 02 21 00 (R - 32 байта) 02 21 00 (S - 32 байта)

30 - тег

46 - размер

02 - тег

21 - размер

00 - нулевой байт (добавлен так как первый байт R - ЕС, а ЕС > 7F)

02 - тег

21 - размер

00 - нулевой байт (добавлен так как первый байт S - AA, а AA > 7F)

Пример правильной структуры

30 46 02 21 00 EC 8D 6D 05 96 B9 8A AE 04 F6 AE 83 D8 04 99 FA D4 A3 EA 37 86 95 A9 DC 0D 3F 09 7F 76 5A F8 40 02 21 00 AA 09 E6 8D D6 56 82 02 4B CF 15 78 21 A6 23 75 81 6C 93 51 90 ED 58 91 33 6E E2 91 4D 57 B5 50

Формат файла

Никак преобразовывать подпись далее не нужно, можно оставить в этом же виде

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

Проверка подписи утилитой openssl

openssl dgst -sha256 -verify pubkey.pem -signature sign.der data.bin

  1. Сконвертируем ключ из OCTET_STRING в BIT_STRING :
    03 42 00 04 8B 92 09 ...
    1. 03 – тег
    2. 42 – размер
    3. 00 – заполнитель
    4. 04 8B 92 09... – 0x41 байт открытого ключа
  2. Сложим результаты в SEQUENCE:
    30 56 30 10 06 07 2A 86 ...
    1. 30 – тег
    2. 56 – размер (0x2+0x2+0x42+0x10)

Результат:

30 56 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 0A 03 42 00 04 8B 92 09 CC C0 A1 B4 19 BA 80 2E 44 5D A2 16 E6 92 AA 9C BB B9 CC B0 CE 5C 76 71 C6 DF E4 CA 83 46 BB 92 2B C8 6F FC 15 20 D8 11 5F 32 4F 7F CA BB DE 9F E3 62 5E 8E 2C D2 2D C2 51 EC 3B 8C 67


Info
title DER и PEM форматы

Если записать в файл байты в том виде, в котором мы их получили, мы получим открытый ключ в DER формате. Если мы хотим представить его в виде печатных символов, его нужно перевести в PEM формат. Это можно сделать с помощью команды:

Code Block
languagebash
titleКонвертация в PEM
openssl ec -pubin -inform DER -in pubkey.der -outform PEM -out pubkey.pem


Проверка открытого ключа

Чтобы проверить, что ключ сконвертирован верно, выполните команду:

Code Block
languagebash
titleПроверка ключа
openssl ec -check -pubin -in pubkey.pem

Конвертация подписи

Представление подписи в PKCS#11

Для подписи данных библиотека PKCS#11 использует функцию С_Sign. Результат возвращается в виде R|S структуры. Формат у этой структуры простой:

  1. 32 байта числа R
  2. 32 байта числа S

Пример R|S подписи

Code Block
languagebash
titleПример R|S
43 1C 62 38 11 04 91 43 76 28 A5 D8 9E 5F EE 90 B0 DC 68 39 D2 B8 11 F3 22 2C D4 DB E2 05 49 BF -- R
8A EF 68 77 8B 0D B8 E6 11 FD 55 56 37 71 62 7E B7 31 22 67 8D 61 7F B6 41 EA 7E 1F 84 C7 36 41 -- S

Описание формата подписи в OpenSSL

OpenSSL представляет подпись в виде ASN.1  структуры.

Code Block
languagebash
titleASN.1 структуры открытого ECDSA ключа
30 45 -- SEQUENCE
	02 20 43 1C 62 38 11 04 91 43 76 28 A5 D8 9E 5F EE 90 B0 DC 68 39 D2 B8 11 F3 22 2C D4 DB E2 05 49 BF     -- R
    02 21 00 8A EF 68 77 8B 0D B8 E6 11 FD 55 56 37 71 62 7E B7 31 22 67 8D 61 7F B6 41 EA 7E 1F 84 C7 36 41  -- S

Описание процесса конвертации

  1. Перевести R и S в TLV тип INTEGER
  2. Результаты положить в SEQUENCE. Нужно проставить правильную длину выходной последовательности.

Числа R и S приводятся к TLV. типу INTEGER с помощью такого алгоритма:

  1. T =  02.
  2. L = кол-во байт для хранения числа. Если первый байт числа превышает 7F, то длина увеличивается на 1 и после L записывается байт 00.
  3. V = значение числа.


Пример конвертации

  1. Число R преобразуется к типу INTEGER:
    02 20 43 1C 62 38 11 04 ...
    1. 02 - тег
    2. 20 - длина
    3. 43 1C 62 38 11 04 ... - само число
  2. Число S преобразуется к типу INTEGER:
    02 21 00 8A EF 68 77 8B
    1. 02 - тег
    2. 21 - длина. На единицу больше т.к. старший байт числа 0x8A больше 0x7F
    3. 00 - заполнитель
    4. 8A EF 68 77 8B ... - само число
  3. Запишем результат в SEQUENCE:
    30 45 02 20 43 1C
    1. 30 - тег
    2. 45 - длина последоватльности (0x02 + 0x02 + 0x20 +0x21)
    3. 02 20 43 1C ... - значение последовательности (R|S)

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

Проверка подписи можно с помощью команды openssl

Code Block
languagebash
titleпроверка подписи
openssl dgst -sha256 -verify pubkey.pem -signature sign.der data.bin

...