Кратко о форматах TLV, BER, CER, DER, PER
Я хотел бы рассказать о форматах данных, распространенных в ИТ-индустрии, в том числе в области инфраструктур открытых ключей (ИОК), смарт-картах, включая документы нового поколения на базе смарт-карт, в мобильной связи. Хотя рассматриваемые форматы и связаны с ASN.1, но некоторые из них ушли далеко за пределы этой области. О некоторых из них многие знают, но не все знают настолько, чтобы, допустим, уметь отличать BER от DER, а некоторые варианты типа PER вообще являются экзотикой.
Глубоко в тему погружаться не буду. Просто познакомлю с главными особенностями, чтобы понимать, что это такое и с чем это едят. Досконально и в полном объеме всё это описано в соответствующих стандартах ITU-T X.690 и ISO 7816.
Одна из моих мотивирующих задач — это уложить тему в своей голове по полочкам.
Правила абстрактной нотации (ASN.1) используются, когда надо специфицировать формат некой структуры данных. Сами правила описаны в стандартах ITU-T X.680–X.683. Пожалуй, что наиболее распространенный вариант применения — это форматы сертификатов X.509 и всего, что имеет к ним отношение. Пример текстовой нотации может выглядеть как-то так:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
Но это всего лишь текстовая запись. К ней должны прилагаться правила кодирования (encoding rules), чтобы её можно было преобразовать к бинарному виду и, например, сохранить в файл конкретные данные в правильном формате или передать их в канал связи. Тут на сцене появляются правила представления в бинарном виде: BER, CER, DER, PER, XER, OER, JER. Последние три пока что трогать не буду, а остальные рассмотрим.
Для полноты картины добавим в эту компанию и формат TLV из стандарта ISO7816–4. Итак, я кратко поведаю о форматах:
Обозначение | Название | Стандарт |
TLV | Tag, Length, Value | ISO 7816–4 |
BER | Basic Encoding Rules | ITU-T X.690–2021 |
CER | Canonical Encoding Rules | ITU-T X.690–2021 |
DER | Distinguished Encoding Rules | ITU-T X.690–2021 |
PER | Packed Encoding Rules | ITU-T X.691–2021 |
TLV — tag, length, value
Это, пожалуй, самый простой формат из приведенных. Стандарт ISO 7816–4 — базовый для смарт-карт и их файловых систем. В нем упоминается SIMPLE-TLV. Строго говоря, термин TLV — это неформальное название семейства форматов.
Согласно SIMPLE-TLV каждый объект данных (DO, data object) состоит из трех полей: обязательно присутствуют тэг (T) и поле длины (L) и опционально поле данных (V). Текстовое обозначение: {T-L-V}.
Тэг состоит из одного байта, принимающего значения от 1 до 254. Значения 0 и FF запрещены.
Поле длины состоит из одного или трех байт. Если первый байт не равен FF, то это и есть значение длины и данное поле состоит из одного байта. Если первый байт — FF, то следующие два байта обозначают длину в диапазоне от 0 до 65 535.
Если длина L не нулевая, то далее следует L байт данных. Причем, в качестве данных могут быть и просто данные (primitive DO), и другие объекты (constructed DO). В последнем случае получается конструкция примерно такого вида: {T-L-{T1-L1-V1}-{T2-L2-V2}-…-{Tn-Ln-Vn}}.
Пример: данные »82 02 D4 AF» обозначают примитивный объект, где T=82, L=2, а V=D4AF. А данные «D1 0A A4 FF 00 02 BD 27 82 02 D4 AF» обозначают составной объект {D1–0A-{A4-FF0002-BD27}-{82–02-D4AF}}.
BER — Basic Encoding Rules
Формат BER похож на SIMPLE-TLV в том смысле, что сохраняется идея трёх полей: тег, длина, значение, но каждое из этих полей кодируется по-другому. Иногда можно встретить название BER-TLV. Он является базовым для следующих двух форматов (CER и DER), поэтому задержимся на нем чуть дольше.
Во-первых, в ITU-T X.690 используется несколько иная терминология: «identifier, length, content» вместо «tag, length, value», соответственно. Далее для простоты буду пользоваться последним вариантом.
Теперь тег — это идентификатор, который описывается тремя параметрами:
Класс;
Флаг-индикатор «примитивный/составной»;
Номер.
Тег может принадлежать одному из четырех классов: Universal, Application, Context-specific, Private. Эти классы описаны в стандарте ASN.1, где сказано, что класс Universal используется только спецификацией ASN.1 и пользователям нельзя задействовать его для своих нужд. Отличий между другими тремя классами особо нет.
Флаг-индикатор «примитивный/составной» указывает, является ли данный объект примитивным (primitive, значение 0), т.е. содержит неструктурированные данные, или содержит другие объекты (constructed, значение 1).
Номер тега — это целое беззнаковое число, уникально идентифицирующее тип данных, содержащихся в самом объекте. Например, тег «Universal 2» — это целочисленный тип (INTEGER) в ASN.1, а «Universal 6» — это OID (Object Identifier).
Кодируются теги так. В первом байте тега старшие биты 8 и 7 обозначают класс (00 — Universal, 01 — Application, 10 — Context-specific, 11 — Private). Бит 6 — это флаг-индикатор. Остальные младшие 5 битов кодируют номер тега, если таковой не превышает 30 (краткая однобайтовая форма, short form). Если превышает, то эти 5 битов должны быть равны »11111», а далее у каждого последующего байта, относящегося к тегу, должен быть взведен старший бит. У последнего байта, относящегося к тегу, старший бит нулевой. Во всех этих байтах младшие 7 бит все вместе кодируют номер тега как целое беззнаковое число в формате big-endian. Такая форма представления тега называется long form.
short form
биты 8 7 6 5 4 3 2 1
----- --- -------------
| | +--> номер тега от 0 до 30
| +--> флаг "примитивный/составной"
+--> класс тега
long form
Байты 1 2,3,... последний
биты 8 7 6 5 4 3 2 1 8 7 6 5 4 3 2 1 8 7 6 5 4 3 2 1
значение x x x 1 1 1 1 1 1 x x x x x x x 0 x x x x x x x
----- --- ------------- ------------------- -------------------
| | номер тега номер тега
| +--> флаг "примитивный/составной"
+--> класс тега
Примеры кодирования поля тег
Тег | Кодированное поля, hex |
Universal, primitive, 6 | 00000110b = 06h |
Application, constructed, 17 | 01110001b = 71h |
Private, primitive, 532 | 11011111 10000100 00010100b = DF 84 14h |
Поле длины может быть представлено в одной из двух форм:
Конечная форма (definite form);
Бесконечная форма (indefinite form).
Конечная форма длины представляется в следующем виде.
Если первый байт поля длины имеет нулевой старший бит, то остальные семь битов — это значение длины. Так можно закодировать длины от 0 до 127 включительно.
биты 8 7 6 5 4 3 2 1
значение 0 x x x x x x x
|-------------------|
длина
Если первый байт поля длины имеет взведенный старший бит, то остальные семь битов указывают на количество последующих байт для значения длины. Далее следует указанное количество байт с целым числом в формате big-endian.
Байты 1 2 3
биты 8 7 6 5 4 3 2 1 8 7 6 5 4 3 2 1 ... 8 7 6 5 4 3 2 1
значение 1 x x x x x x x x x x x x x x x x x x x x x x x
|-------------| |---------------| |---------------|
количество следуюющих 1й байт длины n-й длины
байт
Примеры кодирования поля длины
Длина, dec | Кодированное поле длины, hex |
20 | 14 или 81 14 или 82 00 14 или 83 00 00 14 и т.д. |
124 | 7C или 81 7С или 82 00 7С или 83 00 00 7С и т.д. |
200 | 81 C8 или 82 00 С8 или 83 00 00 С8 и т.д. |
10459 | 82 28 DB или 83 00 28 DB и т.д. |
После поля длины следуют сами данные в указанном размере.
Если поле длины начинается с байта 80, то это обозначает бесконечную форму. Сразу после байта 80 начинаются данные, и длятся они до тех пор, пока не встретятся два нуля »00 00». Такой вариант кодирования длины подходит только для составных объектов, которые хранят другие объекты. В этом случае два нуля — это тоже объект: объект с тегом «Universal 0» и нулевой длиной.
Следует отметить важное свойство BER — это неоднозначность представления объектов данных. Одно и тоже значение длины можно закодировать несколькими вариантами, поэтому существуют более строгие форматы — CER и DER.
Интересно, что известен пример ошибочного применения формата BER в международном стандарте. Речь идет о ICAO Doc 9303 про международные считываемые проездные документы (т.н. загранпаспорт с микросхемой).
Во-первых, в нем используются теги, например, 5F01 или 5F08, что невозможно согласно BER (их номера требуют short form), но это настолько широко внедрено, что исправить невозможно. По этому поводу в седьмом издании самого стандарта есть специальный раздел, разъясняющий эту нестыковку (см. Doc 9303, edition 7, part 10, clause 4.3.1).
4.3.1 Data elements encoding normative note
There is a mismatch between the LDS (version 1.7 and 1.8) specifications and [ISO/IEC 8825–1] (BER/DER encoding rules) wherein [ISO/IEC 8825–1] States for Tags with a number ranging from zero to 30 (inclusive), …
Интересно, что из текущего восьмого издания ICAO Doc 9303 этот раздел исчез! А само несоответствие, естественно, осталось.
Во-вторых, в Doc 9303 используются теги для обозначения смысловой нагрузки данных, а не их типов (в каком-то смысле это можно отнести к особенностям, а не к ошибкам, но всё же). Что я имею в виду? Там, например, тег 5F51 обозначает имя человек, тег 5F53 — адрес, 5F13 — профессия. У всех этих полей разная смысловая нагрузка, но тип данных один и тот же! Просто строка. В BER тег обозначает тип данных, а не их смысл. Вот в SIMPLE-TLV из ISO тег обозначает смысл данных. У ICAO получилась собственная вариация BER-TLV, не совместимая с типовой реализацией, поэтому стандартные парсеры ASN.1 тут не подойдут.
CER — Canonical Encoding Rules
Формат CER можно воспринимать как уточнённый BER со следующими ограничениями:
Если объект данных составной, то должна использоваться бесконечная форма длины.
Если объект данных примитивный, то поле длины должно быть минимально возможного размера, т.е., например, вариант »81 23» недопустим, потому что длину 23h байтов можно упаковать короче:»23»
Некоторые другие ограничения, касающиеся отдельных типов ASN.1. Например, объект типа Octet String должен быть примитивным, если в нем меньше 1000 байтов, иначе он должен быть составным. Подобных ограничений, относящихся к содержимому ASN.1 целый список. Ознакомиться с ним можно в стандарте.
DER — Distinguished Encoding Rules
Формат DER подобно CER является уточнённым BER, но с иными ограничениями:
Поле длины кодируется всегда только в конечной форме и минимально возможного размера.
Ограничения, касающиеся содержимого типов ASN.1. Главная цель — однозначность представления данных. Например, объект типа Octet String никогда не должен кодироваться как составной. Или, тип Boolean может принимать значение только 00 как false или FF как true, в то время как в BER используется 00 как false и любое ненулевое значение как true. Или, неиспользуемые биты в BitString должны обязательно быть нулевыми (в BER они могут быть любыми), и т.д.
Касательно п.2. у DER и CER есть много общего.
PER — Packed Encoding Rules
Самый компактный формат из обсуждаемых это PER. Он не имеет отношения к семейству TLV, потому как в нем нет тегов, а иногда даже и поля длины нет. Тут вообще нет всего «лишнего», т.е. того, что можно узнать из спецификации формата данных. Есть только данные! PER допускает два варианта кодировки:
Можно собирать структуры на уровне битов (Unaligned PER);
Можно собирать структуры из байтовых полей (Aligned PER). В этом случае битовые поля выравниваются до длины 8 битов.
PER по максимуму лишен избыточности. Например, если в спецификации некой структуры данных указано, что сначала идет число, а потом строка, то в бинарном представлении нет смысла указывать теги типов: первый объект — это число, а следующий за ним, значит, строка. Следовательно, отмечаем важное свойство: разобрать данные в формате PER без точного знания их структуры невозможно.
Зачем, например, передавать длину поля Boolean? Она всегда одна и та же: достаточно одного бита.
Если есть, допустим, число n типа INTEGER (lb…ub), т.е. от lb до ub, то оно должно быть представлено как значение (n-lb) из диапазона от 0 до (ub–lb), и задействовано должно быть минимально возможное число битов для представления диапазона от 0 до (ub–lb). Например, тип INTEGER (250…257) требует всего лишь три бита для передачи любого значения из восьми возможных.
Значение BitString представляется просто как последовательность битов. Если длина последовательности может быть переменной, то перед значением она должна быть указана как величина типа INTEGER (lb…ub), где lb и ub — минимальный и максимальный допустимый размер строки битов.
Применяется PER, например, в протоколах мобильных сетей UMTS (3G) и LTE (4G).
Ряд примеров можно найти в самом стандарте Rec. ITU-T X.691.