Чтение и интерпретация DER вручную

DER (Distinguished Encoding Rules) представляет собой строгий бинарный формат кодирования ASN.1-структур. В криптографических библиотеках он используется как каноническое представление сертификатов, ключей и подписей. В JavaScript работа с DER чаще всего опирается на библиотеку jsrsasign, где реализованы низкоуровневые инструменты для чтения ASN.1 без необходимости внешних зависимостей.

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


Структура DER: модель TLV

Любой DER-объект строится по принципу TLV:

  • T (Tag) — тип ASN.1 элемента
  • L (Length) — длина значения
  • V (Value) — сами данные

Формально каждый элемент выглядит как:

| Tag | Length | Value |

Байтовое представление и типы тегов

Тег определяет тип структуры:

  • 0x30 — SEQUENCE (последовательность)
  • 0x02 — INTEGER
  • 0x03 — BIT STRING
  • 0x04 — OCTET STRING
  • 0x06 — OBJECT IDENTIFIER
  • 0x05 — NULL

Каждый тег может быть универсальным, контекстным или специфическим.


Длина: фиксированная и расширенная форма

Поле Length может быть представлено двумя способами:

Короткая форма

Если длина меньше 128:

0x05 0x03 ...

Здесь 0x03 — длина 3 байта.

Длинная форма

Если длина ≥ 128:

0x04 0x82 0x01 0x2C ...
  • 0x82 означает, что длина записана в 2 байтах
  • 0x012C = 300 байт

Интерпретация DER вручную через jsrsasign

Jsrsasign предоставляет низкоуровневый доступ к ASN.1 через пространство KJUR.asn1.ASN1Util и функции работы с HEX-представлением.

Основной подход — работа с HEX-строкой DER:

const derHex = "3082010A0282010100A3...";

Чтение структуры через ASN1HEX

В библиотеке используется модуль:

ASN1HEX

Ключевые функции:

  • ASN1HEX.getTLVList() — разбиение на TLV
  • ASN1HEX.getChildIdx() — индексы дочерних элементов
  • ASN1HEX.getV() — значение элемента
  • ASN1HEX.getDecendantHexTLVOfObj() — доступ к вложенным структурам

Разбор SEQUENCE

SEQUENCE является контейнером для других элементов.

Пример DER:

30 0A
   02 01 01
   02 01 02

JavaScript-разбор:

const idxList = ASN1HEX.getChildIdx(derHex, 0);

Результат — индексы дочерних элементов внутри SEQUENCE.

Далее извлекаются элементы:

const int1 = ASN1HEX.getV(derHex, idxList[0]);
const int2 = ASN1HEX.getV(derHex, idxList[1]);

Чтение INTEGER

INTEGER в DER кодируется в big-endian формате.

Пример:

02 03 01 00 01

Интерпретация:

  • 02 — INTEGER
  • 03 — длина
  • 01 00 01 — значение (65537)

В jsrsasign:

const valueHex = ASN1HEX.getV(derHex, index);

Преобразование в число:

parseInt(valueHex, 16);

Для больших чисел используется BigInteger внутри jsrsasign:

const bi = new BigInteger(valueHex, 16);

Разбор OBJECT IDENTIFIER

OID кодируется особым способом:

  • первые два числа упакованы в один байт: 40 * X + Y
  • остальные числа — base128 encoding

Пример:

06 03 2A 03 04

Расшифровка:

  • 2A = 42 → 1.2
  • далее 3, 4 → 1.2.3.4

Jsrsasign:

KJUR.asn1.ASN1Util.oidHexToInt(oidHex);

BIT STRING и смещение байтов

BIT STRING содержит дополнительный байт смещения:

03 04 00 DE AD BE EF
  • 00 — количество неиспользуемых битов

Извлечение:

const bitString = ASN1HEX.getV(derHex, idx);

Удаление первого байта:

bitString.substring(2);

Вложенные структуры и рекурсивный разбор

DER часто содержит многоуровневые SEQUENCE.

Рекурсивный подход:

function parseASN1(derHex, idx) {
    const children = ASN1HEX.getChildIdx(derHex, idx);
    if (!children) return;

    children.forEach(c => {
        const tag = ASN1HEX.getTLV(derHex, c).substring(0, 2);
        parseASN1(derHex, c);
    });
}

Каждый узел дерева представляет ASN.1-структуру.


Чтение X.509 сертификата через низкоуровневый DER

X.509 сертификат — это SEQUENCE из:

  1. tbsCertificate
  2. signatureAlgorithm
  3. signatureValue

Пример извлечения:

const rootIdx = ASN1HEX.getChildIdx(certHex, 0);

const tbs = rootIdx[0];
const sigAlg = rootIdx[1];
const sigVal = rootIdx[2];

Работа с ASN.1 через Object Identifier таблицы

Jsrsasign содержит внутреннюю таблицу OID:

KJUR.asn1.x509.OID

Позволяет переводить:

  • 1.2.840.113549.1.1.11 → sha256WithRSAEncryption

Особенности ручного анализа DER

При ручной интерпретации важно учитывать:

  • смещение индексов (offset-based parsing)
  • обязательную точность длины
  • отсутствие padding или альтернативных форм
  • строгую последовательность элементов

Любое отклонение делает структуру невалидной с точки зрения DER.


Практика извлечения публичного ключа из DER

Публичный ключ внутри SubjectPublicKeyInfo:

const spkiIdx = ASN1HEX.getChildIdx(certHex, tbsIdx)[5];
const bitStrIdx = ASN1HEX.getChildIdx(certHex, spkiIdx)[1];

const pubKeyHex = ASN1HEX.getV(certHex, bitStrIdx);

Далее используется:

KJUR.asn1.x509.X509Util.getPublicKeyFromCertHex(certHex);

Декодирование произвольных ASN.1 структур

Общий подход:

  1. Получение HEX DER
  2. Определение корневого SEQUENCE
  3. Рекурсивное разбиение через getChildIdx
  4. Интерпретация тегов
  5. Извлечение значений через getV

Работа с сырым HEX без абстракций

Jsrsasign позволяет обходить высокоуровневые API:

const tag = derHex.substring(offset, offset + 2);
const len = parseInt(derHex.substring(offset + 2, offset + 4), 16);
const value = derHex.substring(offset + 4, offset + 4 + len * 2);

Такой подход применяется при отладке ASN.1 структур и анализе нестандартных сертификатов.


Ошибки интерпретации DER

Типичные проблемы:

  • неверная интерпретация длины (short/long form)
  • игнорирование padding BIT STRING
  • неправильная обработка отрицательных INTEGER
  • смешение DER и BER (нестрогих правил кодирования)

Представление DER как дерева

Любая структура DER может быть представлена как дерево:

  • узлы = ASN.1 элементы
  • ветви = SEQUENCE/SET
  • листья = примитивные типы

Jsrsasign фактически работает с этим деревом через индексы HEX-позиции, а не через объектную модель.


Глубокая интерпретация ASN.1 через jsrsasign primitives

Низкоуровневые функции позволяют:

  • обходить структуру без парсинга в объекты
  • минимизировать накладные расходы
  • работать напрямую с криптографическими структурами

Это критично при реализации:

  • проверки цифровых подписей
  • анализа X.509 цепочек
  • работы с PKCS#7 и PKCS#8

Контроль целостности DER при разборе

Каждый элемент можно проверять через пересчёт длины:

const expectedLen = ASN1HEX.getVblen(derHex, idx);

Несоответствие длины указывает на повреждение структуры или некорректное кодирование.