Формат лицензионного ключа MRDN (версия 1)
Нормативная спецификация. Реализации:
kernel/crates/meridian-license/src/key.rs(разбор без секрета),services/license-serviceиtools/keygen(полная, с MAC). Контекст —docs/10-licensing-accounts.md, раздел 4.
1. Внешний вид
MRDN-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
│ └──────────── 25 символов Crockford Base32 = 125 бит ────────────┘
└─ префикс-литерал (не кодирует данных)
- Группы по 5 символов через дефис — только для чтения человеком; при
разборе дефисы, пробелы, регистр и префикс
MRDN(с дефисом или без) игнорируются. - Канонический вид для хранения/печати — заглавные буквы, 5 групп, дефисы, префикс.
2. Алфавит Crockford Base32
| Значение | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Символ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| Значение | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Символ | G | H | J | K | M | N | P | Q | R | S | T | V | W | X | Y | Z |
Исключены I, L, O,
U. При вводе: I, i,
L, l → 1; O,
o → 0; U/u — ошибка
формата. Строка алфавита:
0123456789ABCDEFGHJKMNPQRSTVWXYZ.
3. Битовая раскладка (big-endian, бит 0 — старший)
бит: 0 4 8 12 28 76 92 120 125
├────┼────┼────┼────────────────┼─────────────────────────────────────┼────────────────┼────────────────────────────┼─────┤
поле: ver ed kind seats (16) serial (48) = batch (16) ‖ rnd (32) issued (16) mac (28) check(5)
символ: 0 │ 1 2 3 4 5 │ 6 … 15 │ 15 16 17 18 │ 18 … 23 │ 24
| Поле | Биты | Размер | Тип | Допустимые значения |
|---|---|---|---|---|
version |
0–3 | 4 | uint | 1; остальные —
KEY_UNSUPPORTED_VERSION |
edition |
4–7 | 4 | enum | 1 free (резерв, ключи не выпускаются), 2
home, 3 pro, 4 business, 5
enterprise, 6 education, 7 trial;
0, 8–15 — резерв |
kind |
8–11 | 4 | enum | 1 perpetual, 2 subscription, 3
volume, 4 trial, 5 partner, 6
float_server; 0, 7–15 — резерв |
seats |
12–27 | 16 | uint | 1…65535; 0 недопустим |
batch |
28–43 | 16 | uint | идентификатор партии (key_batches.batch_id); 0 —
служебная (тесты) |
rnd |
44–75 | 32 | uint | CSPRNG, уникален внутри партии |
issued |
76–91 | 16 | uint | дней от 2027-01-01 (UTC); 0 — выпуск до эпохи или тестовый |
mac |
92–119 | 28 | uint | старшие 28 бит
HMAC-SHA256(K_issuer, "MRDN-KEY-v1" ‖ P12) |
check |
120–124 | 5 | symbol | контрольный символ по первым 24 символам |
payload92 — биты 0–91 (поля
version…issued). P12 — 12 байт:
payload92 << 4 (92 бита + 4 нулевых бита дополнения,
big-endian). bits120 — биты 0–119 =
payload92 ‖ mac28; первые 24 символа кодируют ровно
bits120 (24 × 5 = 120).
Семантика seats по kind:
perpetual/subscription личные — устройства;
subscription Business/Education — пользователи;
volume — активации; float_server — размер
пула; trial/partner — устройства.
4. Алгоритмы
4.1 MAC
input = "MRDN-KEY-v1" (11 байт ASCII) ‖ P12 (12 байт)
tag = HMAC-SHA256(K_issuer, input) ; K_issuer — 32 байта, только издатель и license-service
mac28 = (tag[0] << 20 | tag[1] << 12 | tag[2] << 4 | tag[3] >> 4) ; старшие 28 бит
Клиент MAC не проверяет (секрета нет) и не должен делать вид, что
проверяет. Сервер проверяет MAC до обращения к БД
(отсев мусора и подделок), затем ищет запись по (batch,
rnd).
4.2 Контрольный символ
v[i] = значение символа i (0…31), i = 0…23
w[i] = 2·i + 1 ; нечётные веса 1, 3, 5, …, 47
check = ALPHABET[ (Σ v[i]·w[i]) mod 32 ]
Свойства: любая одиночная ошибка символа обнаруживается (нечётный вес
× ненулевая разница ≢ 0 mod 32); перестановка соседних символов
обнаруживается, кроме случая |v[i] − v[i+1]| = 16
(вероятность 1/31); две случайные ошибки — 31/32.
4.3 Кодирование
1. Проверить диапазоны полей.
2. payload92 = version<<88 | edition<<84 | kind<<80 | seats<<64 | batch<<48 | rnd<<16 | issued
3. mac28 = MAC(payload92) (4.1)
4. bits120 = payload92<<28 | mac28
5. sym[i] = ALPHABET[(bits120 >> (5·(23−i))) & 31], i = 0…23
6. sym[24] = check(sym[0..24]) (4.2)
7. вывод: "MRDN-" + группы по 5 символов через "-"
4.4 Декодирование (клиент и сервер)
1. normalize: удалить пробелы/дефисы, в верхний регистр, снять префикс "MRDN"; I/L→1, O→0
2. длина == 25 и все символы из алфавита иначе KEY_INVALID_FORMAT
3. check(sym[0..24]) == sym[24] иначе KEY_CHECKSUM
4. bits120 = Σ value(sym[i]) << (5·(23−i)); payload92 = bits120 >> 28; mac28 = bits120 & 0x0FFFFFFF
5. version == 1 иначе KEY_UNSUPPORTED_VERSION
6. edition ∈ {2..7}, kind ∈ {1..6}, seats ≥ 1 иначе KEY_INVALID_FORMAT
7. (сервер) MAC(payload92) == mac28 иначе KEY_NOT_FOUND (не раскрываем, что именно не так)
8. (сервер) SELECT … WHERE batch = ? AND rnd = ? нет записи → KEY_NOT_FOUND
4.5 Отпечаток ключа
key_fingerprint = hex( SHA-256( "MRDN-KEY-FP-v1" ‖ 25 символов канонического вида без дефисов и префикса ) )[0..32]
(первые 16 байт в hex). Используется в .mlic, CRL, журналах
и поиске; сам ключ в журналах маскируется:
MRDN-28800-…-T2E7W.
5. Тестовые векторы
Секрет для векторов:
K_issuer = "issuer-test-secret-do-not-use" (ASCII, 29
байт). Все реализации обязаны воспроизводить эти ключи.
| # | Ключ | version | edition | kind | seats | batch | rnd | issued (дата) | mac28 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | MRDN-28800-C0G3N-EQW3V-00JBK-T2E7W |
1 | 2 home | 1 perpetual | 3 | 0x0101 | 0xD5D7E0F6 | 73 (2027-03-15) | 0x73D09C7 |
| 2 | MRDN-2CG00-M005N-MCBZE-007X7-KT6MP |
1 | 3 pro | 2 subscription | 5 | 0x0002 | 0xD68C5FDC | 31 (2027-02-01) | 0xA79E8D4 |
| 3 | MRDN-2GG03-4011J-5C77N-01D1H-T6YY1 |
1 | 4 business | 2 subscription | 25 | 0x0010 | 0xC8AC39EA | 180 (2027-06-30) | 0x31D1BDE |
| 4 | MRDN-2MR1Y-G101X-KW9KV-G2XH2-NCQQN |
1 | 5 enterprise | 3 volume | 500 | 0x0200 | 0xF67C4CF7 | 374 (2028-01-10) | 0x22AB2F7 |
| 5 | MRDN-2RG03-R1G1K-A117G-G1WRX-JGZ6A |
1 | 6 education | 2 subscription | 30 | 0x0300 | 0xCD4109E1 | 243 (2027-09-01) | 0x1D943E6 |
| 6 | MRDN-2X000-47G12-BC1TB-00PJB-0JWWQ |
1 | 7 trial | 4 trial | 1 | 0x0F00 | 0x896C0E96 | 90 (2027-04-01) | 0x4B04B9C |
| 7 | MRDN-2NG0S-0102F-SB3B4-G333C-EFXEX |
1 | 5 enterprise | 6 float_server | 200 | 0x0201 | 0x3F2B1AC9 | 396 (2028-02-01) | 0x6C73FAE |
Промежуточные значения (hex):
| # | bits120 (30 hex) |
P12 (вход HMAC после домена) |
key_fingerprint |
|---|---|---|---|
| 1 | 12100030101D5D7E0F6004973D09C7 |
12100030101D5D7E0F600490 |
2b55530fe3a20f6fc38a3b13dcc983cc |
| 2 | 13200050002D68C5FDC001FA79E8D4 |
13200050002D68C5FDC001F0 |
136acb0924097e5febcc424907458f8c |
| 3 | 14200190010C8AC39EA00B431D1BDE |
14200190010C8AC39EA00B40 |
e67c1836719d19b24fe0eb86f06619db |
| 4 | 15301F40200F67C4CF7017622AB2F7 |
15301F40200F67C4CF701760 |
38a2abbdf456ef374f630bb4c67ab8c4 |
| 5 | 162001E0300CD4109E100F31D943E6 |
162001E0300CD4109E100F30 |
9389295a77992197c8ba04b20d1d39c6 |
| 6 | 17400010F00896C0E96005A4B04B9C |
17400010F00896C0E96005A0 |
0fcddd28ac2b4f55764c89e539d0102b |
| 7 | 15600C802013F2B1AC9018C6C73FAE |
15600C802013F2B1AC9018C0 |
84ab1e09460f710774e48af15ce1f908 |
5.1 Полный разбор вектора 1
Ключ: MRDN-28800-C0G3N-EQW3V-00JBK-T2E7W
Символы: 2 8 8 0 0 C 0 G 3 N E Q W 3 V 0 0 J B K T 2 E 7 | W
Значения: 2 8 8 0 0 12 0 16 3 21 14 23 28 3 27 0 0 18 11 19 26 2 14 7 | check
bits120: 0001 0010 0001 0000000000000011 0000000100000001 11010101110101111110000011110110 0000000001001001 0111001111010000100111000111
ver ed kind seats=3 batch=0x0101 rnd=0xD5D7E0F6 issued=73 mac28=0x73D09C7
HMAC вход: 4D52444E2D4B45592D7631 ‖ 12100030101D5D7E0F600490
("MRDN-KEY-v1") (P12)
HMAC tag: 73D09C7DE1310ED9F486BCB5ED98E6FC5BF987463E7128F77BAD440BD59E42F4
mac28: tag[0..4] >> 4 = 0x73D09C7 ✓ совпадает с битами 92–119
check: Σ v[i]·(2i+1) = 7164; 7164 mod 32 = 28 → 'W' ✓
5.2 Отрицательные векторы
| Ввод | Результат |
|---|---|
MRDN-28800-C0G3N-EQW3V-00JBK-T2E7X |
KEY_CHECKSUM (последний символ) |
MRDN-28A00-C0G3N-EQW3V-00JBK-T2E7W |
KEY_CHECKSUM (одиночная ошибка в символе 2) |
mrdn 28800 c0g3n eqw3v 00jbk t2e7w |
OK — равен вектору 1 (регистр, пробелы) |
MRDN-288OO-C0G3N-EQW3V-00JBK-T2E7W |
OK — O→0 |
MRDN-28800-C0G3N-EQW3V-00JBK-T2E7 |
KEY_INVALID_FORMAT (24 символа) |
MRDN-28800-C0G3N-EQW3U-00JBK-T2E7W |
KEY_INVALID_FORMAT (символ U) |
вектор 1, но K_issuer = "other" |
клиент: OK; сервер: KEY_NOT_FOUND |
6. Хранение и отображение
- В БД (
license_keys):batch,rnd(UNIQUE),key_fingerprint(индекс),key_enc(AES-256-GCM, ключ KMS) — для показа владельцу. - В журналах и телеметрии: только
key_fingerprintили маскаMRDN-XXXXX-…-XXXXX(первая и последняя группы). - В UI ввода: автоформатирование в группы по 5, проверка контрольного символа «на лету» после 25-го символа, подсказка «похоже на опечатку» без указания позиции (она не определяется однозначно).
7. Расширение формата
- Новая версия (
version = 2) может менять раскладку бит 4–119 целиком; клиент, не знающий версию, возвращаетKEY_UNSUPPORTED_VERSIONи советует обновиться. Первый символ ключа v1 всегда2или3(биты0001+ старший бит edition), что позволяет мгновенно отличить версию по первому символу (v2 будет начинаться с4/5). - Резервные значения
edition/kindне должны использоваться для «скрытых» типов — только через новую версию формата или документированное расширение этой таблицы.