Генерация и проверка отпечатков ключей: calculateJwkThumbprint

JWK thumbprint представляет собой детерминированный идентификатор JSON Web Key, вычисляемый по строго определённым правилам RFC 7638. В контексте криптографических систем он используется для сравнения ключей, кэширования, привязки ключей к сущностям и проверки соответствия ключевых материалов без необходимости сравнивать их полностью.

В библиотеке jose для JavaScript работа с thumbprint реализована через функцию calculateJwkThumbprint, которая позволяет получить компактное представление JWK в виде хеша, нормализованного по стандарту.

Алгоритм вычисления основан на нескольких жёстких правилах:

  • используется только определённый набор полей JWK в зависимости от типа ключа
  • поля сортируются лексикографически
  • JSON сериализуется без пробелов и лишнего форматирования
  • применяется криптографическая хеш-функция SHA-256
  • результат кодируется в base64url без padding

Ключевой момент — идентичность результата для одного и того же ключа независимо от порядка полей в исходном объекте.

Поддерживаемые типы ключей

Разные типы JWK используют разные обязательные поля:

RSA ключи

Используются параметры:

  • e
  • kty
  • n

EC ключи

Используются параметры:

  • crv
  • kty
  • x
  • y

OKP ключи (Ed25519, X25519)

Используются параметры:

  • crv
  • kty
  • x

Любые дополнительные поля игнорируются при расчёте thumbprint.

Функция calculateJwkThumbprint

В jose функция calculateJwkThumbprint принимает JWK и возвращает строку thumbprint.

Базовое использование

import { calculateJwkThumbprint } from 'jose'

const jwk = {
  kty: 'RSA',
  n: '0vx7agoebGcQSuuPiLJXZptN9...',
  e: 'AQAB',
  alg: 'RS256',
  kid: 'key-1'
}

const thumbprint = await calculateJwkThumbprint(jwk)
console.log(thumbprint)

Результат представляет собой строку base64url.

Внутренняя логика вычисления

Процесс можно разложить на несколько этапов.

1. Извлечение канонических параметров

Из JWK удаляются все служебные поля:

  • alg
  • use
  • key_ops
  • kid
  • любые пользовательские атрибуты

Остаются только поля, определённые спецификацией для конкретного типа ключа.

2. Формирование канонического JSON

Создаётся объект с фиксированным порядком ключей:

{"e":"...","kty":"RSA","n":"..."}

Важно, что порядок всегда одинаковый, независимо от входного объекта.

3. Сериализация

JSON преобразуется в строку без пробелов:

{"e":"AQAB","kty":"RSA","n":"..."}

Любое отличие в сериализации приводит к изменению результата, поэтому процесс строго стандартизирован.

4. Хеширование SHA-256

Полученная строка преобразуется в байты и хешируется алгоритмом SHA-256.

Внутренне используется криптографический API Node.js или WebCrypto в зависимости от среды выполнения.

5. Кодирование base64url

Финальный шаг — преобразование бинарного хеша в base64url:

  • + заменяется на -
  • / заменяется на _
  • удаляются = в конце

Итог — компактная строка фиксированной длины.

Пример полного процесса

RSA ключ:

const jwk = {
  kty: 'RSA',
  n: 'sXchpQ...',
  e: 'AQAB',
  use: 'sig',
  kid: 'demo-key'
}

После обработки остаётся:

{"e":"AQAB","kty":"RSA","n":"sXchpQ..."}

Далее:

  • SHA-256 → 32 байта
  • base64url → строка thumbprint

Проверка соответствия ключа

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

import { calculateJwkThumbprint } from 'jose'

const thumb1 = await calculateJwkThumbprint(jwkA)
const thumb2 = await calculateJwkThumbprint(jwkB)

if (thumb1 === thumb2) {
  // ключи эквивалентны
}

Такой подход особенно полезен при работе с наборами JWK из разных источников.

Использование в идентификации ключей

В системах JWT thumbprint часто применяется как:

  • стабильный kid (Key ID)
  • индекс в key store
  • идентификатор при ротации ключей

Пример генерации kid на основе thumbprint:

import { calculateJwkThumbprint } from 'jose'

const kid = await calculateJwkThumbprint(jwk)
const key = { ...jwk, kid }

Особенности работы с асимметричными ключами

RSA

Чувствительность к параметрам n и e делает thumbprint стабильным идентификатором публичной части ключа.

EC

Кривые (P-256, P-384, P-521) влияют на вычисление через поле crv, что делает thumbprint уникальным для каждой кривой и точки.

OKP

В Ed25519 ключах используется только координата x, что упрощает структуру и делает вычисление более компактным.

Ошибки и ограничения

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

  • отсутствие обязательных полей (n, e, x, y)
  • некорректный тип kty
  • попытка передать приватные параметры (d, p, q), которые игнорируются
  • модификация JWK после генерации thumbprint

В случае некорректного ключа функция выбрасывает исключение.

Криптографическая значимость

Thumbprint не является подписью и не предназначен для защиты данных. Его свойства:

  • детерминированность
  • уникальность для эквивалентных ключей
  • невозможность восстановления ключа из хеша

Он выполняет роль идентификатора, а не механизма безопасности.

Применение в архитектуре систем

В реальных системах thumbprint часто используется:

  • в OAuth2/JWT инфраструктуре для сопоставления публичных ключей
  • в системах федерации идентификации
  • в сервисах с динамической ротацией ключей
  • в кешировании криптографических материалов

Особенно важно его использование в распределённых системах, где ключи приходят из внешних JWKS endpoints.

Сравнение с kid

Поле kid может быть произвольным и не стандартизированным, тогда как thumbprint:

  • всегда вычисляется одинаково
  • не зависит от внешних соглашений
  • соответствует RFC 7638

Поэтому thumbprint часто используется как основа для генерации kid, а не наоборот.