Как правильно подойти к вопросу: неправильно работает crypto.subtle.deriveKey в Firefox, баг или мой косяк?
По вводным: генерирую пару ключей X25519, из них хочу получить общий секрет длиной в 128 бит для AES-128. Но столкнулся со странным поведением браузеров.
Нужно понять: в Google Chrome всё нормально, полученный ключ имеет длину 128 бит, как и полагается. В Mozilla Firefox почему-то параметр length игнорируется, даже если туда передать что-то отличное от числа, всегда генерируется ключ в 256 бит не зависимо от этого параметра. Это баг Firefox, или я неправильно использую функции?
По вводным: притом если использовать deriveBits вместо deriveKey, или алгоритм ECDH вместо X25519, то всё нормально, ключ 128 бит, как и полагается.
Вот код на Codepen:
Snippet
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос
С
crypto.subtle.deriveKeyдля X25519 и AES важно различать длину сырого общего секрета и длину ключа, который вы импортируете/получаете для AES. У X25519 общий секрет имеет фиксированную длину 256 бит. Если вы просите сразуderiveKeyв AES-128, браузер должен вернуть AES-ключ нужной длины, но поведение может отличаться из-за реализации WebCrypto и поддержки алгоритмов.Более переносимый путь: сначала получить raw bits через
deriveBits, затем пропустить через HKDF/SHA-256 и импортировать AES-128 ключ. Так вы явно контролируете длину.const bits = await crypto.subtle.deriveBits( { name: 'X25519', public: remotePublicKey }, privateKey, 256 ); const hkdfKey = await crypto.subtle.importKey( 'raw', bits, 'HKDF', false, ['deriveKey'] ); const aesKey = await crypto.subtle.deriveKey( { name: 'HKDF', hash: 'SHA-256', salt, info: new TextEncoder().encode('session key') }, hkdfKey, { name: 'AES-GCM', length: 128 }, false, ['encrypt', 'decrypt'] );const bits = await crypto.subtle.deriveBits( { name: 'X25519', public: remotePublicKey }, privateKey, 256 ); const hkdfKey = await crypto.subtle.importKey( 'raw', bits, 'HKDF', false, ['deriveKey'] ); const aesKey = await crypto.subtle.deriveKey( { name: 'HKDF', hash: 'SHA-256', salt, info: new TextEncoder().encode('session key') }, hkdfKey, { name: 'AES-GCM', length: 128 }, false, ['encrypt', 'decrypt'] );
Если Firefox игнорирует
lengthименно в вашем варианте, это может быть баг реализации или ограничение конкретной связки алгоритмов. Но для ECDH/X25519 всё равно криптографически правильнее использовать KDF, а не напрямую брать shared secret как AES-ключ.Проверьте также, что вы не смотрите на длину экспортируемого raw материала X25519 вместо длины AES key. AES-GCM key length и shared secret length — разные вещи.
Итог: используйте
deriveBits+ HKDF + AES-GCM 128. Это переносимее между Chrome и Firefox и лучше с точки зрения криптографии, чем прямойderiveKeyиз X25519 в AES.