X.690 — это фундаментальный стандарт телекоммуникационного сектора Международного союза электросвязи (МСЭ-Т / ITU-T), совместно разработанный с Международной организацией по стандартизации (ИСО / ISO) и Международной электротехнической комиссией (МЭК / IEC). Стандарт определяет правила преобразования абстрактных структур данных, описанных на языке ASN.1 (Abstract Syntax Notation One), в конкретные последовательности байтов (октетов) для передачи по сетям связи или хранения в файлах.
Актуальная версия стандарта соответствует ISO/IEC 8825-1. Ранее (до 1994 года) данные правила были частью рекомендации X.209, однако с переходом на серию рекомендаций X;680/X.690 они были выделены в отдельный документ.
Назначение и область применения
Главная цель X.690 — обеспечить платформонезависимое представление данных. Разные компьютерные архитектуры используют разный порядок байтов (endianness), разные форматы чисел с плавающей точкой и строк. X.690 устраняет эти различия, задавая строгие правила сериализации.
- Интероперабельность: Позволяет системам на разных языках (C, Java, Python, Go) и ОС обмениваться сложными структурами данных.
- Телекоммуникации: База для протоколов SNMP, LDAP, X.509 (сертификаты PKI), H.323, SIP, MAP (GSM/UMTS), Diameter и многих других;
- Хранение: Используется для сохранения конфигураций, логов и баз данных в бинарном виде.
Три набора правил кодирования
Стандарт X.690 описывает три основных набора правил (Encoding Rules), различающихся степенью строгости и областью применения:
| Аббревиатура | Полное название | Характеристика | Основное применение |
|---|---|---|---|
| BER | Basic Encoding Rules (Базовые правила кодирования) | Гибкие, позволяют несколько способов кодирования одного значения (например, разная длина поля длины, неопределенная длина для составных типов). | Общие цели, отладка, протоколы, где важна простота кодировщика/декодировщика. |
| CER | Canonical Encoding Rules (Канонические правила кодирования) | Подмножество BER. Жестко фиксирует один способ кодирования для каждого типа. Использует неопределенную длину для больших составных объектов. | Редко используется напрямую; исторически для криптографии (заменен DER). |
| DER | Distinguished Encoding Rules (Особые/Различительные правила кодирования) | Строгое подмножество BER. Гарантирует уникальное бинарное представление для каждого значения. Запрещает неопределенную длину, требует минимальной длины поля длины, сортировку SET по тегам. | Криптография, цифровые подписи, сертификаты X.509, блокчейн. Критично, когда хэш от данных должен быть детерминированным. |
Структура кодированного блока: TLV
Вся архитектура X.690 строится на принципе TLV (Tag-Length-Value) — Тег, Длина, Значение. Любой закодированный элемент данных состоит из трех последовательных полей:
- Identifier (Идентификатор / Тег) — определяет тип данных и класс.
- Length (Длина) — указывает количество октетов в поле Value.
- Contents (Значение / Содержимое) — сами закодированные данные.
Поле Identifier (Тег)
Первый октет идентификатора имеет следующую битовую структуру:
Биты: 8 7 6 5 4 3 2 1 +---+---+---+---+---+---+---+---+ | Класс | П/К | Номер тега | (короткая форма, тег 0..30) +---+---+---+---+---+---+---+---+
Класс тега (Биты 8-7)
- 00, Universal (Универсальный): Типы, определенные в самом стандарте X.690 (INTEGER, SEQUENCE, OCTET STRING и т.д.). Смысл одинаков во всех приложениях.
- 01 — Application (Прикладной): Типы, смысл которых зависит от конкретного протокола/приложения (например, в SNMP: GetRequest, GetResponse).
- 10 — Context-specific (Контекстно-зависимый): Типы, смысл которых определен внутри конкретной структуры (например, поля внутри SEQUENCE, помеченные [0], [1]…).
- 11 — Private (Частный): Типы, определенные конкретной организацией (Enterprise numbers).
Тип кодирования (Бит 6 — P/C бит)
- 0 — Primitive (Примитивное): Значение кодируется напрямую (INTEGER, BOOLEAN, NULL, строки).
- 1 — Constructed (Конструктивное): Значение содержит в себе другие TLV-блоки (SEQUENCE, SET, SEQUENCE OF, явная маркировка EXPLICIT).
Номер тега (Биты 5-1)
Если номер тега 0..30, он помещается в биты 5-1 первого октета.
Если номер тега > 30, используется длинная форма:
- Биты 5-1 первого октета устанавливаются в 11111 (0x1F).
- Последующие октеты кодируют номер тега по 7 бит (бит 8 = 1 для продолжения, бит 8 = 0 для последнего октета).
Таблица основных универсальных тегов (Universal Class)
| Тип данных ASN.1 | Номер тега (дек) | Номер тега (hex) | Примитив/Конструктив | HEX Тег (Primitive) |
|---|---|---|---|---|
| EOC (End-of-Content) | 0 | 0x00 | Prim | 0x00 |
| BOOLEAN | 1 | 0x01 | Prim | 0x01 |
| INTEGER | 2 | 0x02 | Prim | 0x02 |
| BIT STRING | 3 | 0x03 | Prim/Constr | 0x03 |
| OCTET STRING | 4 | 0x04 | Prim/Constr | 0x04 |
| NULL | 5 | 0x05 | Prim | 0x05 |
| OBJECT IDENTIFIER (OID) | 6 | 0x06 | Prim | 0x06 |
| ENUMERATED | 10 | 0x0A | Prim | 0x0A |
| UTF8String | 12 | 0x0C | Prim | 0x0C |
| SEQUENCE / SEQUENCE OF | 16 | 0x10 | Constr | 0x30 |
| SET / SET OF | 17 | 0x11 | Constr | 0x31 |
| PrintableString | 19 | 0x13 | Prim | 0x13 |
| IA5String (ASCII) | 22 | 0x16 | Prim | 0x16 |
| UTCTime | 23 | 0x17 | Prim | 0x17 |
| GeneralizedTime | 24 | 0x18 | Prim | 0x18 |
| BMPString (UCS-2) | 30 | 0x1E | Prim | 0x1E |
Примечание: Для конструктивных типов (SEQUENCE, SET) к базовому тегу добавляется бит 6 (0x20), поэтому SEQUENCE = 0x30, SET = 0x31.
Поле Length (Длина)
Кодирует количество октетов в поле Value.
- Короткая форма (Short form): Бит 8 = 0. Биты 7-1 содержат длину (0..127 байт). Пример:
0x05— длина 5. - Длинная форма (Long form): Бит 8 = 1. Биты 7-1 содержат количество последующих октетов длины (1..126). Следующие октеты представляют длину как big-endian целое число.
- Пример:
0x82 0x01 0x00— 2 октета длины (0x0100 = 256 байт).
- Пример:
- Неопределенная длина (Indefinite length):
0x80. Разрешен только в BER/CER для конструктивных типов. Завершается специальным маркером EOC (End-of-Content) — два нулевых октета0x00 0x00. В DER запрещено.
Примеры кодирования
Пример 1: INTEGER (Примитивный тип)
Значение: 127 (0x7F)
TLV: 02 01 7F Tag: 02 (Universal, Primitive, Tag=2 INTEGER) Len: 01 (Short form, length=1) Val: 7F (Value = 127)
Значение: -1 (0xFF в дополнительном коде)
TLV: 02 01 FF
Пример 2: SEQUENCE (Конструктивный тип)
ASN.1 схема:
MySeq ::= SEQUENCE {
id INTEGER,
name UTF8String
}
Данные: id = 1, name = "test"
30 0C -- SEQUENCE, длина 12 байт (конструктивный тег 0x10 | 0x20 = 0x30) -2 01 01 -- INTEGER id = 1 0C 04 74 65 73 74 -- UTF8String "test" (тег 0x0C, длина 4, байты 't','e','s','t')
Пример 3: Контекстно-зависимая маркировка (IMPLICIT/EXPLICIT)
Схема: [0] IMPLICIT INTEGER внутри SEQUENCE.
-- IMPLICIT: тег заменяет универсальный тег INTEGER (0x02) на контекстный [0] (0x80) -- Класс Context (01), Primitive (0), Tag 0 -> 1000 0000 = 0x80 80 01 05 -- Значение 5 с тегом [0]
Схема: [1] EXPLICIT INTEGER.
-- EXPLICIT: добавляется внешний обертка-тег [1] (Constructed), внутри лежит обычный INTEGER A1 03 -- Context (01), Constructed (1), Tag 1 -> 1010 0001 = 0xA1. Len=3 02 01 0A -- Внутри: UNIVERSAL INTEGER (0x02), Len=1, Val=10
Ключевые различия BER, CER и DER (Критически важно для разработчиков)
| Правило | BER | CER | DER |
|---|---|---|---|
| Уникальность кодирования | Нет (множество вариантов) | Да (каноническая форма) | Да (строгая уникальная форма) |
| Длина для примитивов | Короткая или длинная | Короткая (если <128), иначе длинная | Минимально необходимая (короткая если <128, иначе длинная без ведущих нулей) |
| Длина для конструктивных | Короткая, длинная или неопределенная (0x80) | Неопределенная (0x80 + EOC), если длина > 1000 байт, иначе определенная | Только определенная (короткая или длинная). Неопределенная ЗАПРЕЩЕНА. |
| Порядок элементов SET | Любой (как задал отправитель) | По возрастанию тегов | По возрастанию тегов |
| BIT STRING / OCTET STRING (фрагментация) | Могут быть разбиты на части (конструктивная форма) | Обязательно фрагментированы на куски по 1000 байт (конструктивная) | Всегда примитивные (один кусок) |
| STRING типы (UTF8, GeneralString и др.) | Могут быть конструктивными | Конструктивные (фрагменты) | Всегда примитивные |
| Использование в криптографии | Неподходит (хэш будет разным) | Подходит, но устарел | Стандарт де-факто (X.509, CMS, JWT, Blockchain) |
Типичные ошибки и подводные камни
- Нарушение DER при подписании: Использование BER-энкодера для формирования TBSCertificate (To Be Signed Certificate) приводит к тому, что проверка подписи не пройдет на строгих валидаторах (OpenSSL, Windows CryptoAPI, Java keytool), так как они ожидают строгий DER.
- Порядок в SET OF: В DER элементы SET OF обязательно должны быть отсортированы по тегам (лексикографически по байтам TLV). Несортированный SET — это валидный BER, но невалидный DER.
- Ведущие нули в INTEGER: DER требует минимальной длины.
02 02 00 01(два байта значения) — невалидный DER для числа 1. Валидный:02 01 01. Однако для отрицательных чисел старший бит должен быть 1 (знаковое расширение). - Неопределенная длина в DER: Попытка декодировать поток с неопределенной длиной как DER должна вызывать ошибку.
- IMPLICIT vs EXPLICIT: Неправильная интерпретация тегов при ручном парсинге или неверная ASN.1 схема приводит к сдвигу парсинга и чтению мусора.
Инструменты для работы с X.690 / ASN.1
- OpenSSL (asn1parse): Стандарт де-факто для разбора сертификатов и DER-блоков.
openssl asn1parse -in cert.pem -inform pem -i
- dumpasn1 (Peter Gutmann): Отличная утилита для глубокого разбора неизвестных бинарных блобов с выводом структуры.
- ASN.1 Compiler (asn1c, OSS Nokalva, Objective Systems): Генерация кода (C/C++, Java, C#, Python) из .asn схем.
- Python библиотеки:
pyasn1(чистый Python),asn1crypto(быстрый, фокус на криптографию),pyasn1-modules(готовые схемы RFC). - Online декодеры: lapo.it/asn1js (удобно для быстрой проверки base64/hex дампов в браузере).
Безопасность и лучшие практики
- DoS через рекурсию: Злоумышленник может передать глубоко вложенную структуру (SEQUENCE в SEQUENCE…), вызвав переполнение стека при рекурсивном парсинге. Всегда лимитируйте глубину вложенности (например, max depth = 64 или 128).
- DoS через длину: Поле длины в BER может указывать на гигантский размер (например, 2 ГБ), хотя реальных данных нет. Проверяйте доступные байты в буфере перед аллокацией памяти.
- Неканоническое кодирование: При проверке цифровых подписей всегда требовать DER. Никогда не подписывайте/не проверяйте BER-данные напрямую.
- Парсинг OID: OID кодируется специфическим образом (первые два арка в первом байте, затем base-128). Ошибки в декодере OID могут привести к подмене идентификаторов алгоритмов.
Стандарт X.690 (ISO/IEC 8825-1) остается невидимым, но критически важным фундаментом современной цифровой инфраструктуры. От TLS-хендшейков в браузере до SNMP-мониторинга серверов и сигнализации в 5G-сетях — везде работает TLV-машина, описанная в этом документе.
Понимание различий между BER (гибкость), CER (каноничность для потоков) и DER (детерминизм для криптографии) является обязательным навыком для инженеров, работающих с протоколами, PKI и безопасностью информации. Использование проверенных библиотек и строгая валидация входящих бинарных данных — залог устойчивости систем к ошибкам и атакам.
Статья подготовлена на основе материалов ITU-T X.690 (08/2015), ISO/IEC 8825-1:2015 и открытых источников (Wikipedia, RFC 5280, RFC 3279). Дата актуализации: .