DER (Distinguished Encoding Rules) представляет собой строгий бинарный формат кодирования ASN.1-структур. В криптографических библиотеках он используется как каноническое представление сертификатов, ключей и подписей. В JavaScript работа с DER чаще всего опирается на библиотеку jsrsasign, где реализованы низкоуровневые инструменты для чтения ASN.1 без необходимости внешних зависимостей.
Ключевая особенность DER заключается в детерминированности: одна и та же структура всегда кодируется одинаково. Это критично для криптографии, где любое расхождение в байтах делает подпись недействительной.
Любой DER-объект строится по принципу TLV:
Формально каждый элемент выглядит как:
| Tag | Length | Value |
Тег определяет тип структуры:
0x30 — SEQUENCE (последовательность)0x02 — INTEGER0x03 — BIT STRING0x04 — OCTET STRING0x06 — OBJECT IDENTIFIER0x05 — NULLКаждый тег может быть универсальным, контекстным или специфическим.
Поле Length может быть представлено двумя способами:
Если длина меньше 128:
0x05 0x03 ...
Здесь 0x03 — длина 3 байта.
Если длина ≥ 128:
0x04 0x82 0x01 0x2C ...
0x82 означает, что длина записана в 2 байтах0x012C = 300 байтJsrsasign предоставляет низкоуровневый доступ к ASN.1 через
пространство KJUR.asn1.ASN1Util и функции работы с
HEX-представлением.
Основной подход — работа с HEX-строкой DER:
const derHex = "3082010A0282010100A3...";
В библиотеке используется модуль:
ASN1HEX
Ключевые функции:
ASN1HEX.getTLVList() — разбиение на TLVASN1HEX.getChildIdx() — индексы дочерних элементовASN1HEX.getV() — значение элементаASN1HEX.getDecendantHexTLVOfObj() — доступ к вложенным
структурам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 в DER кодируется в big-endian формате.
Пример:
02 03 01 00 01
Интерпретация:
02 — INTEGER03 — длина01 00 01 — значение (65537)В jsrsasign:
const valueHex = ASN1HEX.getV(derHex, index);
Преобразование в число:
parseInt(valueHex, 16);
Для больших чисел используется BigInteger внутри jsrsasign:
const bi = new BigInteger(valueHex, 16);
OID кодируется особым способом:
40 * X + YПример:
06 03 2A 03 04
Расшифровка:
2A = 42 → 1.2Jsrsasign:
KJUR.asn1.ASN1Util.oidHexToInt(oidHex);
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 сертификат — это SEQUENCE из:
Пример извлечения:
const rootIdx = ASN1HEX.getChildIdx(certHex, 0);
const tbs = rootIdx[0];
const sigAlg = rootIdx[1];
const sigVal = rootIdx[2];
Jsrsasign содержит внутреннюю таблицу OID:
KJUR.asn1.x509.OID
Позволяет переводить:
1.2.840.113549.1.1.11 → sha256WithRSAEncryptionПри ручной интерпретации важно учитывать:
Любое отклонение делает структуру невалидной с точки зрения 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);
Общий подход:
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 может быть представлена как дерево:
Jsrsasign фактически работает с этим деревом через индексы HEX-позиции, а не через объектную модель.
Низкоуровневые функции позволяют:
Это критично при реализации:
Каждый элемент можно проверять через пересчёт длины:
const expectedLen = ASN1HEX.getVblen(derHex, idx);
Несоответствие длины указывает на повреждение структуры или некорректное кодирование.