JEP 452: Key Encapsulation Mechanism API
API механизма инкапсуляции ключей
| Ответственный | Weijun Wang |
| Тип | Feature |
| Область | SE |
| Статус | Closed / Delivered |
| Выпуск | 21 |
| Компонент | security-libs / javax.crypto |
| Обсуждение | security dash dev at openjdk dot org |
| Трудоёмкость | M |
| Длительность | M |
| Рецензенты | Alan Bateman, Sean Mullan |
| Одобрен | Sean Mullan |
| Создан | 2023/01/25 03:48 |
| Обновлён | 2026/05/14 20:03 |
| Задача | 8301034 |
Аннотация
Ввести API для механизмов инкапсуляции ключей (KEM). Это метод шифрования, который защищает симметричные ключи с помощью криптографии с открытым ключом.
Цели
-
Дать приложениям возможность использовать алгоритмы KEM: RSA Key Encapsulation Mechanism (RSA-KEM), Elliptic Curve Integrated (интегрирован) Encryption Scheme (ECIES), а также алгоритмы KEM, которые участвуют как кандидаты в процессе стандартизации постквантовой криптографии National Institute of Standards and Technology (NIST).
-
Дать возможность использовать KEM в протоколах более высокого уровня, таких как Transport Level Security (TLS), и в криптографических схемах, таких как Hybrid Public Key Encryption (HPKE, RFC 9180).
-
Позволить провайдерам безопасности реализовывать алгоритмы KEM как на Java, так и в нативном коде.
-
Включить реализацию Diffie-Hellman KEM (DHKEM), определённого в §4.1 RFC 9180.
Что не является целью
-
Включение генерации пар ключей в KEM API не является целью. Существующего API
KeyPairGeneratorдля этого достаточно. -
Поддержка параметра шифрования для функции инкапсуляции, определённого в ISO 18033-2, не является целью.
-
Поддержка функций аутентифицированной инкапсуляции и декапсуляции, определённых в RFC 9180, не является целью.
Мотивация
Инкапсуляция ключей — современный криптографический метод, который защищает симметричные ключи с помощью асимметричной криптографии, то есть криптографии с открытым ключом. Традиционно для этого случайно сгенерированный симметричный ключ шифруют открытым ключом. Но такой способ требует дополнения (padding), и доказать его безопасность бывает трудно. Механизм инкапсуляции ключей (KEM) вместо этого использует свойства открытого ключа, чтобы получить связанный с ним симметричный ключ, и дополнение не требуется.
Понятие KEM ввели Crammer и Shoup в §7.1 работы Design and Analysis of Practical Public-Key Encryption Schemes Secure against Adaptive Chosen Ciphertext Attack. Позднее Shoup предложил сделать его стандартом ISO в §3.1 работы A Proposal for an ISO Standard for Public Key Encryption. Предложение было принято как ISO 18033-2 и опубликовано в мае 2006 года.
KEM — один из базовых элементов Hybrid Public Key Encryption (HPKE). Процесс стандартизации постквантовой криптографии (PQC) NIST прямо предусматривает, что KEM и алгоритмы цифровой подписи оцениваются как кандидаты в следующее поколение стандартных алгоритмов криптографии с открытым ключом. Шаг обмена ключами Diffie-Hellman в TLS 1.3 тоже можно смоделировать как KEM.
KEM станут важным инструментом защиты от квантовых атак. Ни один из существующих криптографических API платформы Java не может естественным образом представить KEM (см. ниже). Разработчики сторонних провайдеров безопасности уже заявили о потребности в стандартном KEM API. Пора добавить такой API в платформу Java.
Описание
KEM состоит из трёх функций:
-
Функция генерации пары ключей, которая возвращает пару ключей из открытого и закрытого ключа.
-
Функция инкапсуляции ключа, которую вызывает отправитель. Она принимает открытый ключ получателя и параметр шифрования и возвращает секретный ключ K и сообщение инкапсуляции ключа (в ISO 18033-2 оно называется шифротекстом). Отправитель передаёт сообщение инкапсуляции ключа получателю.
-
Функция декапсуляции ключа, которую вызывает получатель. Она принимает закрытый ключ получателя и полученное сообщение инкапсуляции ключа и возвращает секретный ключ K.
Функцию генерации пары ключей покрывает существующий API KeyPairGenerator. Для функций инкапсуляции и декапсуляции мы определяем новый класс KEM:
package javax.crypto;
public class DecapsulateException extends GeneralSecurityException;
public final class KEM {
public static KEM getInstance(String alg)
throws NoSuchAlgorithmException;
public static KEM getInstance(String alg, Provider p)
throws NoSuchAlgorithmException;
public static KEM getInstance(String alg, String p)
throws NoSuchAlgorithmException, NoSuchProviderException;
public static final class Encapsulated {
public Encapsulated(SecretKey key, byte[] encapsulation, byte[] params);
public SecretKey key();
public byte[] encapsulation();
public byte[] params();
}
public static final class Encapsulator {
String providerName();
int secretSize(); // Size of the shared secret
int encapsulationSize(); // Size of the key encapsulation message
Encapsulated encapsulate();
Encapsulated encapsulate(int from, int to, String algorithm);
}
public Encapsulator newEncapsulator(PublicKey pk)
throws InvalidKeyException;
public Encapsulator newEncapsulator(PublicKey pk, SecureRandom sr)
throws InvalidKeyException;
public Encapsulator newEncapsulator(PublicKey pk, AlgorithmParameterSpec spec,
SecureRandom sr)
throws InvalidAlgorithmParameterException, InvalidKeyException;
public static final class Decapsulator {
String providerName();
int secretSize(); // Size of the shared secret
int encapsulationSize(); // Size of the key encapsulation message
SecretKey decapsulate(byte[] encapsulation) throws DecapsulateException;
SecretKey decapsulate(byte[] encapsulation, int from, int to,
String algorithm)
throws DecapsulateException;
}
public Decapsulator newDecapsulator(PrivateKey sk)
throws InvalidKeyException;
public Decapsulator newDecapsulator(PrivateKey sk, AlgorithmParameterSpec spec)
throws InvalidAlgorithmParameterException, InvalidKeyException;
}
Методы getInstance создают новый объект KEM, который реализует указанный алгоритм.
Отправитель вызывает один из методов newEncapsulator. Эти методы принимают открытый ключ получателя и возвращают объект Encapsulator. Затем отправитель может вызвать один из двух методов encapsulate этого объекта и получить объект Encapsulated, который содержит SecretKey и сообщение инкапсуляции ключа. Метод encapsulate() возвращает ключ, который содержит общий секрет целиком, с именем алгоритма "Generic". Обычно этот ключ передаётся функции выработки ключа. Метод encapsulate(from, to, algorithm) возвращает ключ с заданным именем алгоритма, ключевой материал которого — подмассив общего секрета.
Получатель вызывает один из методов newDecapsulator. Эти методы принимают закрытый ключ получателя и возвращают объект Decapsulator. Затем получатель может вызвать один из двух методов decapsulate этого объекта. Они принимают полученное сообщение инкапсуляции ключа и возвращают общий секрет. Метод decapsulate(encapsulation) возвращает общий секрет целиком с алгоритмом "Generic", а метод decapsulate(encapsulation, from, to, algorithm) возвращает ключ с ключевым материалом и алгоритмом, которые задал пользователь.
Алгоритм KEM может определить подкласс AlgorithmParameterSpec, чтобы передавать дополнительную информацию полному методу newEncapsulator. Это особенно полезно, если один и тот же ключ можно использовать для получения общих секретов разными способами. Экземпляры подкласса AlgorithmParameterSpec должны быть неизменяемыми. Если какую-либо информацию из объекта AlgorithmParameterSpec нужно передать вместе с сообщением инкапсуляции ключа, чтобы получатель мог создать соответствующий декапсулятор, она будет включена в виде массива байтов в поле params результата Encapsulated. В этом случае провайдер безопасности должен предоставить реализацию AlgorithmParameters с тем же именем алгоритма, что и у KEM. Получатель может инициализировать такой экземпляр AlgorithmParameters полученным массивом байтов params и восстановить объект AlgorithmParameterSpec, который будет использоваться при вызове метода newDecapsulator.
Несколько одновременных вызовов методов encapsulate или decapsulate конкретного объекта Encapsulator или Decapsulator соответственно должны быть безопасными. Каждый вызов метода encapsulate должен генерировать новый общий секрет и новую инкапсуляцию.
Вот пример с гипотетическим KEM "ABC". Перед инкапсуляцией и декапсуляцией ключа получатель генерирует пару ключей "ABC" и публикует открытый ключ.
// Receiver side
KeyPairGenerator g = KeyPairGenerator.getInstance("ABC");
KeyPair kp = g.generateKeyPair();
publishKey(kp.getPublic());
// Sender side
KEM kemS = KEM.getInstance("ABC-KEM");
PublicKey pkR = retrieveKey();
ABCKEMParameterSpec specS = new ABCKEMParameterSpec(...);
KEM.Encapsulator e = kemS.newEncapsulator(pkR, specS, null);
KEM.Encapsulated enc = e.encapsulate();
SecretKey secS = enc.key();
sendBytes(enc.encapsulation());
sendBytes(enc.params());
// Receiver side
byte[] em = receiveBytes();
byte[] params = receiveBytes();
KEM kemR = KEM.getInstance("ABC-KEM");
AlgorithmParameters algParams = AlgorithmParameters.getInstance("ABC-KEM");
algParams.init(params);
ABCKEMParameterSpec specR = algParams.getParameterSpec(ABCKEMParameterSpec.class);
KEM.Decapsulator d = kemR.newDecapsulator(kp.getPrivate(), specR);
SecretKey secR = d.decapsulate(em);
// secS and secR will be identical
Конфигурации KEM
Один алгоритм KEM может иметь несколько конфигураций. Каждая конфигурация может принимать разные типы открытых или закрытых ключей, получать общие секреты разными способами и выдавать разные сообщения инкапсуляции ключа. Каждая конфигурация должна соответствовать конкретному алгоритму, который создаёт общий секрет фиксированного размера и сообщение инкапсуляции ключа фиксированного размера. Конфигурация должна однозначно определяться тремя параметрами:
- именем алгоритма, переданным методу
getInstance, - типом ключа, переданного методу
newEncapsulatorилиnewDecapsulator, и - необязательным объектом
AlgorithmParameterSpec, переданным методуnewEncapsulatorилиnewDecapsulator.
Например, у семейства KEM Kyber мог бы быть один алгоритм с именем "Kyber", а реализация могла бы поддерживать разные конфигурации в зависимости от типов ключей, например Kyber-512, Kyber-768 и Kyber-1024.
Другой пример — семейство KEM RSA-KEM. Имя алгоритма могло бы быть просто "RSA-KEM", а реализация могла бы поддерживать разные конфигурации в зависимости от размеров ключей RSA и настроек функции выработки ключа (KDF). Разные настройки KDF можно было бы передавать через объект RSAKEMParameterSpec.
В обоих случаях конфигурацию можно определить только после вызова одного из методов newEncapsulator или newDecapsulator.
Отложенный выбор провайдера
Провайдер, который выбирается для данного алгоритма KEM, может зависеть не только от имени алгоритма, переданного методу getInstance, но и от ключа, переданного методу newEncapsulator или newDecapsulator. Поэтому выбор провайдера откладывается до вызова одного из этих методов, так же как в других криптографических API, например Cipher и KeyAgreement.
При каждом вызове метода newEncapsulator или newDecapsulator может быть выбран другой провайдер. Узнать, какой провайдер выбран, можно с помощью методов providerName() классов Encapsulator и Decapsulator.
Методы encapsulationSize()
Некоторые протоколы более высокого уровня присоединяют сообщения инкапсуляции ключа к другим данным напрямую, не указывая длину. Например, Hybrid TLS Key Exchange объединяет два сообщения инкапсуляции ключа в одно поле key_exchange, а RSA-KEM объединяет сообщение инкапсуляции ключа с обёрнутыми ключевыми данными. Эти протоколы предполагают, что после того, как конфигурация KEM зафиксирована, длина сообщения инкапсуляции ключа постоянна и заранее известна. Мы предоставляем методы encapsulationSize() для получения размера сообщения инкапсуляции ключа на случай, если приложению нужно извлечь это сообщение из таких объединённых данных.
Общие секреты могут быть неизвлекаемыми
Все существующие реализации KEM возвращают общие секреты в виде массива байтов. Однако провайдер безопасности Java может опираться на реализацию в нативном коде, и тогда общий секрет может оказаться неизвлекаемым. Поэтому вернуть общий секрет в виде массива байтов можно не всегда. По этой причине методы encapsulate и decapsulate всегда возвращают общий секрет в объекте SecretKey.
Если ключ извлекаемый, его формат должен быть "RAW", а его метод getEncoded() должен возвращать либо общий секрет целиком, либо фрагмент общего секрета, заданный параметрами from и to расширенного метода encapsulate или decapsulate.
Если ключ неизвлекаемый, его методы getFormat() и getEncoded() должны возвращать null, даже если внутри ключевой материал представляет собой либо общий секрет целиком, либо фрагмент общего секрета.
Интерфейс провайдера услуг (SPI) для KEM
Реализация KEM должна реализовывать интерфейс KEMSpi:
package javax.crypto;
public interface KEMSpi {
interface EncapsulatorSpi {
int engineSecretSize();
int engineEncapsulationSize();
KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm);
}
interface DecapsulatorSpi {
int engineSecretSize();
int engineEncapsulationSize();
SecretKey engineDecapsulate(byte[] encapsulation, int from, int to,
String algorithm)
throws DecapsulateException;
}
EncapsulatorSpi engineNewEncapsulator(PublicKey pk, AlgorithmParameterSpec spec,
SecureRandom sr)
throws InvalidAlgorithmParameterException, InvalidKeyException;
DecapsulatorSpi engineNewDecapsulator(PrivateKey sk, AlgorithmParameterSpec spec)
throws InvalidAlgorithmParameterException, InvalidKeyException;
}
Реализация должна реализовывать интерфейсы EncapsulatorSpi и DecapsulatorSpi и возвращать объекты этих типов из методов engineNewEncapsulator и engineNewDecapsulator своей реализации KEMSpi. Вызовы методов secretSize, encapsulationSize, encapsulate и decapsulate объектов Encapsulator и Decapsulator делегируются методам engineSecretSize, engineEncapsulationSize, engineEncapsulate и engineDecapsulate в реализациях EncapsulatorSpi и DecapsulatorSpi.
Реализация методов engineEncapsulate и engineDecapsulate должна уметь инкапсулировать и декапсулировать ключи с алгоритмом "Generic", значением from, равным 0, и значением to, равным длине общего секрета. В остальных случаях она может выбросить UnsupportedOperationException, если такая комбинация аргументов не поддерживается. Например, имя алгоритма нельзя сопоставить внутреннему типу ключа, размер ключа не соответствует алгоритму или реализация не поддерживает произвольное разбиение общего секрета на фрагменты.
Дальнейшая работа
Параметры шифрования
ISO 18033-2 определяет параметр шифрования (encryption option) для функции инкапсуляции, потому что некоторые асимметричные шифры позволяют передавать алгоритму шифрования параметры, специфичные для схемы. Однако этот параметр не упоминается ни в RFC 9180, ни в документе NIST PQC KEM API Notes, поэтому мы его здесь не включаем. Если появится веская причина поддержать алгоритм, которому нужен этот параметр, то в будущем улучшении можно будет добавить ещё одну перегрузку метода encapsulate, которая позволит передавать параметры, специфичные для алгоритма.
Функции AuthEncap и AuthDecap
RFC 9180 определяет две необязательные функции KEM, AuthEncap и AuthDecap. Они позволяют отправителю передать при инкапсуляции собственный закрытый ключ, чтобы получатель мог быть уверен, что общий секрет сгенерировал владелец этого закрытого ключа. Однако эти две функции не встречаются ни в одном другом определении KEM, поэтому мы их здесь не включаем. Поддержку этих функций можно добавить в будущем улучшении.
Альтернативы
Использование существующих API
Мы рассматривали возможность представить KEM с помощью существующих API KeyGenerator, KeyAgreement и Cipher, но у каждого из них есть существенные проблемы. Либо они не поддерживают нужный набор возможностей, либо API не соответствует функциям KEM.
-
KeyGeneratorумеет генерироватьSecretKey, но не может одновременно с этим сгенерировать сообщение инкапсуляции ключа. В качестве обходного пути можно было бы закодировать и общий секрет, и сообщение инкапсуляции ключа в кодированной формеSecretKey. Однако это работает, только если общий секрет можно извлечь, а это, как говорилось выше, не всегда так. Для ключей, которые можно извлечь, приложению всё равно придётся извлекать секрет и сообщение инкапсуляции ключа из кодированной формыSecretKey, что сложно и чревато ошибками. Другой вариант — хранить сообщение инкапсуляции ключа внутриSecretKeyкак отдельное поле. Однако для этого потребовался бы новый подклассSecretKeyс публичным методом для получения сообщения инкапсуляции ключа. -
KeyAgreementможет возвращать сообщение инкапсуляции ключа как ключ фазы, а общий секрет — через разные методы. Однако объектKeyAgreementпредполагается инициализировать собственным закрытым ключом вызывающей стороны, а для KEM создавать закрытый ключ на стороне отправителя не нужно. Кроме того, сообщение инкапсуляции ключа в KEM определено как непрозрачный массив байтов, аKeyAgreementвозвращает ключ фазы как объектKey. Для преобразования между сообщениями инкапсуляции ключа и ключами потребовались бы новые подклассыKeyFactoryиEncodedKeySpec. -
Cipherумеет обернуть существующий ключ, а затем развернуть его. Однако в KEM общий секрет генерируется в процессе инкапсуляции. Можно было бы передать фиктивный ключ или ключnullи хранить настоящий общий секрет в выходных данных, но у этого подхода та же проблема, что и уKeyGenerator: он работает, только если общий секрет можно извлечь, а приложение должно извлекать ключ и сообщение инкапсуляции ключа из обёрнутого результата. Более того, обёртывание ключа с последующим развёртыванием должно возвращать тот же ключ, а передача фиктивных входных данных в метод обёртывания этому соглашению не соответствует.
Короче говоря, каждая из этих альтернатив была бы трюком для обхода API, который не проектировался для представления KEM. Потребовались бы дополнительные классы и методы, а реализации были бы сложными и хрупкими. Без стандартного API для KEM провайдеры безопасности, скорее всего, будут реализовывать KEM несогласованно и неудобно, и разработчикам будет трудно ими пользоваться.
Включение функции генерации пары ключей
Все определения KEM содержат функцию генерации пары ключей. Мы могли бы включить такую функцию в API для KEM, но решили этого не делать, поскольку для этой цели специально предназначен существующий API KeyPairGenerator. Включение такой же функции в API для KEM могло бы запутать как разработчиков провайдеров, так и прикладных разработчиков.
Тестирование
Мы добавим тесты на соответствие для входных данных, выходных данных и исключений, а также тесты DHKEM с известными ответами из RFC 9180.