Для работы с токенами в Java разработана специальная библиотека-обертка pkcs11jna. Эта обертка предоставляет интерфейс над динамическими PKCS#11 библиотеками.
Исходный код расположен на: https://github.com/AktivCo/pkcs11jnaРабота с библиотекой pkcs11jna достаточно проста. С помощью net.java.dev.jna нужно загрузить PKCS#11 библиотеку и далее работать с возвращенным объектом по интерфейсу класса RtPkcs11:
Code Block language java title RtPkcs11 import com.sun.jna.Native; import ru.rutoken.pkcs11jna.RtPkcs11; RtPkcs11 rtpkcs11 = Native.load(Native.extractFromResourcePath("rtpkcs11ecp").getAbsolutePath(), RtPkcs11.class);
Класс RtPkcs11 предоставляет интерфейс над PKCS#11 функциям обернутой библиотекой. Для примера покажем, как можно проинициализировать токен:
Code Block language java title Пример инициализации токена import com.sun.jna.NativeLong; import com.sun.jna.Native; import com.sun.jna.ptr.NativeLongByReference; import ru.rutoken.pkcs11jna.CK_C_INITIALIZE_ARGS; import ru.rutoken.pkcs11jna.Pkcs11Constants; import ru.rutoken.pkcs11jna.RtPkcs11; public class Init { private final static byte[] TOKEN_LABEL = {'M', 'y', ' ', 'R', 'u', 't', 'o', 'k', 'e', 'n', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}; public final static CK_C_INITIALIZE_ARGS INITIALIZE_ARGUMENTS = new CK_C_INITIALIZE_ARGS(null, null, null, null, new NativeLong(Pkcs11Constants.CKF_OS_LOCKING_OK), null); public static final byte[] DEFAULT_USER_PIN = {'1', '2', '3', '4', '5', '6', '7', '8'}; public static final byte[] DEFAULT_SO_PIN = {'8', '7', '6', '5', '4', '3', '2', '1'}; public static void main(String[] args) { NativeLong hSession = new NativeLong(Pkcs11Constants.CK_INVALID_HANDLE); RtPkcs11 rtpkcs11 = null; try { System.out.println("Example of token initialization using rtpkcs11ecp via JNA"); rtpkcs11 = Native.load(Native.extractFromResourcePath("rtpkcs11ecp").getAbsolutePath(), RtPkcs11.class); System.out.println("Library initialization and acquiring of function list"); NativeLong rv = rtpkcs11.C_Initialize(INITIALIZE_ARGUMENTS); if (!Pkcs11Constants.equalsPkcsRV(Pkcs11Constants.CKR_OK, rv)) { throw new Exception("C_Initialize failed"); } System.out.println("Acquiring list of slots with connected tokens"); NativeLongByReference slotsCount = new NativeLongByReference(); rv = rtpkcs11.C_GetSlotList(Pkcs11Constants.CK_TRUE, null, slotsCount); if (!Pkcs11Constants.equalsPkcsRV(Pkcs11Constants.CKR_OK, rv)) throw new Exception("C_GetSlotList failed"); if (slotsCount.getValue().intValue() == 0) { throw new Exception("No Rutoken is available!"); } NativeLong[] pSlotList = new NativeLong[slotsCount.getValue().intValue()]; rv = rtpkcs11.C_GetSlotList(Pkcs11Constants.CK_TRUE, pSlotList, slotsCount); if (!Pkcs11Constants.equalsPkcsRV(Pkcs11Constants.CKR_OK, rv)) throw new Exception("C_GetSlotList failed"); System.out.println("Token initialization"); rv = rtpkcs11.C_InitToken(pSlotList[0], DEFAULT_SO_PIN, new NativeLong(DEFAULT_SO_PIN.length), TOKEN_LABEL); if (!Pkcs11Constants.equalsPkcsRV(Pkcs11Constants.CKR_OK, rv)) throw new Exception("C_GetSlotList failed"); System.out.println("Opening session"); NativeLongByReference phSession = new NativeLongByReference(); rv = rtpkcs11.C_OpenSession(pSlotList[0], new NativeLong(Pkcs11Constants.CKF_SERIAL_SESSION | Pkcs11Constants.CKF_RW_SESSION), null, null, phSession); if (!Pkcs11Constants.equalsPkcsRV(Pkcs11Constants.CKR_OK, rv)) throw new Exception("C_GetSlotList failed"); hSession = phSession.getValue(); System.out.println("Logging in as administrator"); rv = rtpkcs11.C_Login(hSession, new NativeLong(Pkcs11Constants.CKU_SO), DEFAULT_SO_PIN, new NativeLong(DEFAULT_SO_PIN.length)); if (!Pkcs11Constants.equalsPkcsRV(Pkcs11Constants.CKR_OK, rv)) throw new Exception("C_GetSlotList failed"); System.out.println("User PIN initialization"); rv = rtpkcs11.C_InitPIN(hSession, DEFAULT_USER_PIN, new NativeLong(DEFAULT_USER_PIN.length)); if (!Pkcs11Constants.equalsPkcsRV(Pkcs11Constants.CKR_OK, rv)) throw new Exception("C_GetSlotList failed"); } catch (Exception e) { System.out.println(e.getMessage()); } finally { if (rtpkcs11 != null) { System.out.println("Logging out"); NativeLong rv = rtpkcs11.C_Logout(hSession); System.out.println("Closing session"); rv = rtpkcs11.C_CloseSession(hSession); System.out.println("Finalizing PKCS11 library"); rv = rtpkcs11.C_Finalize(null); System.out.println("Test has been completed."); } } } }
Больше примеров по работе с библиотекой pkcs11jna можно найти в Рутокен SDK в директории sdk/java/samples.
Руководство разработчика
Attachments |
---|
Table of Contents | ||||||||
---|---|---|---|---|---|---|---|---|
|
Введение
Данный документ содержит описание Java-провайдера JRT11 и способов его использования.
Провайдер JRT11 предназначен для использования Рутокен ЭЦП в программных продуктах, разработанных на Java.
Провайдер JRT11 функционирует под управлением виртуальных машин Java 2 Runtime Environment версии 1.6 и выше, производства Oracle и Open JDK.
Провайдер JRT11 позволяет работать с российскими криптографическими алгоритмами ГОСТ 28147-89, ГОСТ Р 34.10-2001, ГОСТ Р 34.11-94 и стандартом RFC 4357 в стандартном интерфейсе Java Cryptography Architecture (JCA). В соответствии с этим интерфейсом обеспечивается выполнение следующих криптографических операций:
- генерация ключей подписи, соответствующих алгоритму ГОСТ Р 34.10-2001;
- генерация ключей шифрования, соответствующих алгоритму ГОСТ 28147-89;
- выработка общего ключа парной связи по алгоритму Диффи-Хеллмана;
- хранение ключей на отделяемом ключевом носителе;
- шифрование данных в соответствии с ГОСТ 28147-89;
- подпись / проверка данных в соответствии с ГОСТ Р 34.10-2001;
- хеширование данных в соответствии с ГОСТ Р 34.11-94;
- вычисление имитовставки в соответствии с ГОСТ 28147-89;
- генерация последовательности случайных чисел требуемой длины;
- поддержка алгоритма RSA, с ключами до 2048 бит.
Провайдер JRT11 выполнен в стандартном интерфейсе JCA и может использоваться непосредственно через него или же через свой собственный интерфейс.
Использование провайдера JRT11 через интерфейс JCA
JCA представляет собой часть платформы Java, предоставляющую API для криптографических операций электронной подписи, вычисления хеш-суммы сообщений, работы с сертификатами и проверки подлинности сертификатов, шифрования (симметричного/асимметричного, блочного/потокового), генерации ключей и управления ключами. JCA упрощает интеграцию функций безопасности в Java-приложения, избавляя разработчиков от необходимости реализовывать криптографические алгоритмы.
Таким образом, JCA позволяет использовать реализации алгоритмов через стандартный интерфейс по имени алгоритма, не указывая ни класса, ни производителя, реализующего требуемый алгоритм. Это значительно облегчает встраивание провайдера в уже существующие решения.
Исторически сложилось так, что классы для работы с шифрованием содержатся в пакете javax.crypto, классы для работы с подписью находятся в пакете java.security.
Info |
---|
Подробное описание классов пакета java.security доступно в документации по API. |
Стандартная схема работы с классами этого пакета выглядит примерно одинаково для всех алгоритмов и не вызовет трудностей даже у начинающего разработчика.
Подключение провайдера
JCA имеет архитектуру, основанную на криптопровайдерах – независимых модулях, непосредственно выполняющих криптографические операции по определенным алгоритмам. Программа может просто отправить запрос с определённым типом объекта, реализующего какой-либо криптографический сервис (например, подпись данных по алгоритму ГОСТ 34.10-2001), и получить реализацию от одного из установленных криптопровайдеров.
Для использования провайдера JRT11 в стандартном интерфейсе JCA его необходимо установить и зарегистрировать в списке провайдеров динамическим или статическим способом.
Установка провайдера
Чтобы провайдер считался установленным расширением, его необходимо поместить в стандартную директорию расширений, имеющую адрес
<jre>\lib\ext\
,
где <jre>
– директория с установленным JRE или JDK.
Динамическая регистрация провайдера
Провайдер JRT11 реализован классом ru.rutoken.jrt11.JRT11Provider
, который наследуется от стандартного класса Provider
и может быть добавлен/удален в списке провайдеров по необходимости. Провайдер регистрируется под именем "JRT11".
Для управления списком провайдеров предназначены следующие методы класса Security
:
Метод | Назначение |
---|---|
addProvider(Provider provider) | добавление провайдера в конец списка |
insertProviderAt(Provider provider, int position) | добавление провайдера в нужную позици |
removeProvider(String name) | удаление провайдера из списка |
Code Block | ||||
---|---|---|---|---|
| ||||
Security.addProvider(new ru.rutoken.jrt11.JRT11Provider()); |
Статическая регистрация провайдера
Статическое подключение провайдера выполняется добавлением в конфигурационный файл
<jre>\lib\security\java.security
строчки типа
security.provider.n = masterClassName,
где n
– порядок приоритета провайдера при поиске требуемого алгоритма (1 – самый предпочтительный), masterClassName
– полное имя родительского класса провайдера.
Code Block | ||||
---|---|---|---|---|
| ||||
security.provider.7=ru.rutoken.jrt11.JRT11Provider |
Если необходимо указать конфигурационный файл, то его имя можно задать после провайдера:
Code Block | ||||
---|---|---|---|---|
| ||||
security.provider.7=ru.rutoken.jrt11.JRT11Provider /jrt11/jrt11.properties |
При добавлении имени конфигурационного файла в Windows разделитель каталогов надо удваивать:
Code Block | ||||
---|---|---|---|---|
| ||||
security.provider.7=ru.rutoken.jrt11.JRT11Provider C:\\jrt11\\jrt11.properties |
Подробнее о конфигурационном файле и его назначении см. раздел Сценарии задания конфигурации.
Общая схема работы с классами java.security
Создание объектов JCA
Для осуществления любой криптографической операции, как шифрование/подпись данных или генерация ключевой пары, первым шагом создается объект, реализующий алгоритм, с помощью метода getInstance()
. Этот метод является статическим и возвращает ссылку на класс, который обеспечивает выполнение требуемой операции. В качестве аргумента методу getInstance()
необходимо передать имя алгоритма. Вторым необязательным аргументом может быть передано либо имя провайдера либо сам объект провайдера. Настоятельно рекомендуется использовать форму getInstance(String algorithm, String provider)
или getInstance(String algorithm, Provider provider)
с указанием провайдера. Явное указание имени провайдера позволит обеспечить использование алгоритма из нужного провайдера и избавит от возможных конфликтов между различными провайдерами.
Code Block | ||||
---|---|---|---|---|
| ||||
MessageDigest digest = MessageDigest.getInstance("rtGOST3411", "JRT11"); |
Использование параметров через интерфейс AlgorithmParameterSpec
После создания объекта его можно инициализировать. В некоторых случаях при инициализации класса предусмотрена возможность передачи параметров, реализующих интерфейс AlgorithmParameterSpec
(например, KeyPairGenerator.initialize(java.security.spec.AlgorithmParameterSpec) или Signature.setParameter(java.security.spec.AlgorithmParameterSpec)). Полный список методов, использующих AlgorithmParameterSpec
, доступен по следующей ссылке.
В качестве параметров могут выступать:
- Config - конфигурация PKCS#11;
- Context - созданный контекст PKCS#11;
- Token - Рутокен;
- Session - открытая сессия;
- ParamPin - PIN-код Рутокен;
- ParamSet - набор параметров.
Эти параметры предназначены для взаимодействия с PKCS#11 токеном и описаны в разделе Сценарии задания конфигурации.
Такая инициализация не является обязательной, например, алгоритмы хеширования (MessageDigest
) и генерации случайных чисел (SecureRandom
) будут работать и без нее, но для создания ключа подписи на Рутокен необходимо знание PIN-кода, значение которого и передается с помощью параметра. После инициализации объект можно использовать по назначению.
Задать параметры можно непосредственно в имени алгоритма. При создании алгоритма после его имени через разделитель "/" следует указать строковое представление OID.
Code Block | ||||
---|---|---|---|---|
| ||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("rtGOST3410/1.2.643.2.2.35.1", "JRT11"); |
Генерация ключей
Генерация ключей шифрования
Провайдер JRT11 позволяет осуществлять генерацию ключей для шифрования данных по алгоритму ГОСТ 28147-89 через стандартный интерфейс JCA при помощи класса KeyGenerator
.
Для генерации ключей шифрования для алгоритма ГОСТ 28147-89 при создании объекта типа KeyGenerator
методом getInstance()
в качестве имени алгоритма надо указать значение "rt11GOST28147
".
Затем генерация ключа осуществляется вызовом метода generateKey()
.
Подробное описание использования классаKeyGenerator
доступно в разделе JCA (вместе с подробным примером).
Code Block | ||||
---|---|---|---|---|
| ||||
KeyGenerator generator = KeyGenerator.getInstance("rt11GOST28147", "JRT11");
SecretKey secret = generator.generateKey(); |
Генерация ключей подписи
Провайдер JRT11 позволяет осуществлять генерацию ключей для подписи данных по алгоритму ГОСТ Р 34.10-2001 через стандартный интерфейс JCA при помощи класса KeyPairGenerator
.
Для генерации ключевых пар для алгоритма ГОСТ Р 34.10-2001 при создании объекта типа KeyPairGenerator
методом getInstance()
в качестве имени алгоритма надо указать значение "rtGOST3410
".
Затем генерация ключа осуществляется вызовом метода generateKeyPair()
.
Подробное описание использования класса KeyPairGenerator
доступно в разделе JCA (вместе с подробным примером).
Code Block | ||||
---|---|---|---|---|
| ||||
KeyGenerator generator = KeyGenerator.getInstance("rt11GOST28147", "JRT11");
SecretKey secret = generator.generateKey(); |
Так как провайдер JRT11 методом KeyPairGenerator()
генерирует ключевую пару сразу на Рутокен, то при ее создание следует сразу указать имя ключевой пары. Это можно сделать путем передачи методу initialize()
классом ParamAlias
значения имени "alias
".
Code Block | ||||
---|---|---|---|---|
| ||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("rtGOST3410", "JRT11");
generator.initialize(new ru.rutoken.security.spec.ParamAlias("alias"));
KeyPair pair = generator.generateKeyPair(); |
По этому имени станет возможным извлечение ключа из хранилища.
Для генерации ключевой пары по алгоритму RSA в качестве имени алгоритма надо задать "rtRSA
". Размер ключа задается методом KeyPairGenerator.initialize(int)
.
Выработка общего сессионного ключа парной связи
Провайдер JRT11 позволяет осуществлять выработку общего ключа парной связи по алгоритму Диффи-Хеллмана через стандартный интерфейс JCA при помощи класса KeyAgreement
.
Для выработки ключа шифрования для алгоритма ГОСТ 28147-89 при создании объекта типа KeyAgreement
методом getInstance()
в качестве имени алгоритма надо указать значение"rtGOST3410
".
Созданный объект необходимо инициализировать собственным закрытым ключом и параметрами. В качестве параметров, участвующих в выработке общего ключа, передается объект класса IvParameterSpec
, представляющий собой стартовый вектор для выработки общего ключа. После инициализации необходимо передать открытый ключ методом doPhase()
, параметры которого должны совпадать с соответствующими параметрами закрытого ключа. При генерации общего ключа парной связи в качестве имени алгоритма надо передать методу generateSecret()
значение "rt11GOST28147
".
Подробное описание использования классаKeyAgreement
доступно в разделе JCA (вместе с подробным примером).
Code Block | ||||
---|---|---|---|---|
| ||||
KeyAgreement agreement = KeyAgreement.getInstance("rtGOST3410", "JRT11");
agreement.init(privateKey, new IvParameterSpec(ivBytes));
agreement.doPhase(publicKey, true);
SecretKey secret = agreement.generateSecret("rt11GOST28147"); |
Шифрование и расшифрование
Провайдер JRT11 позволяет осуществлять шифрование данных и ключей по алгоритму ГОСТ 28147-89 через стандартный интерфейс JCA при помощи класса Cipher
.
Шифрование и расшифрование данных
Для шифрования по алгоритму ГОСТ 28147-89 при создании объекта типа Cipher
методом getInstance()
в качестве имени алгоритма необходимо указать значение "rt11GOST28147
".
Для шифрования данных созданный объект сначала необходимо инициализировать ключом шифрования и синхропосылкой с помощью метода init().
Затем объекту передаются данные методом update()
(в случае шифрования/расшифрования в несколько этапов) и затем получают результат шифрования/расшифрования вызовом doFinal().
В случае шифрования в один шаг вызов метода
опускаетсяupdate()
.
Подробное описание использования класса Cipher
доступно в разделе JCA (вместе с подробным примером).
Code Block | ||||
---|---|---|---|---|
| ||||
cipher = Cipher.getInstance("rt11GOST28147", "JRT11");
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] result = cipher.doFinal(data); |
Code Block | ||||
---|---|---|---|---|
| ||||
cipher = Cipher.getInstance("rt11GOST28147", "JRT11");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] result = cipher.doFinal(data); |
Шифрование и расшифрование ключей
Для шифрования по алгоритму ГОСТ 28147-89 при создании объекта типа Cipher
методом getInstance()
в качестве имени алгоритма необходимо указать значение "rt11GOST28147
".
Для шифрования ключа созданный объект необходимо инициализировать ключом шифрования с помощью метода init().
Затем зашифрованный ключ становится доступен вызовом метода wrap()
, а расшифрованный - методом unwrap()
.
Подробное описание использования класса Cipher
доступно в разделе JCA (вместе с подробным примером).
Code Block | ||||
---|---|---|---|---|
| ||||
cipher = Cipher.getInstance("rt11GOST28147", "JRT11");
cipher.init(Cipher.WRAP_MODE, secret);
byte[] result = cipher.wrap(wrappedKey); |
Code Block | ||||
---|---|---|---|---|
| ||||
cipher = Cipher.getInstance("rt11GOST28147", "JRT11");
cipher.init(Cipher.UNWRAP_MODE, secret);
byte[] result = cipher.unwrap(unwrappedKey); |
Хеширование данных
Провайдер JRT11 позволяет осуществлять хеширование данных по алгоритму ГОСТ Р 34.11-94 через стандартный интерфейс JCA при помощи класса MessageDigest
.
Для создания объекта хеширования по алгоритму ГОСТ Р 34.11-94 методу getInstance()
необходимо в качестве параметра передать имя, идентифицирующее данный алгоритм –"rtGOST3411
". Следующим шагом необходимо предоставить данные для хеширования методом update()
. Вычисление хеш-суммы осуществляется методом digest()
.
Подробное описание использования класса MessageDigest
доступно в разделе JCA (вместе с подробным примером).
Code Block | ||||
---|---|---|---|---|
| ||||
MessageDigest messageDigest = MessageDigest.getInstance("rtGOST3411", "JRT11");
messageDigest.update("rtGOST3411".getBytes());
byte[] digestValue = messageDigest.digest(); |
Подпись и проверка подписи данных
Провайдер JRT11 позволяет осуществлять подпись и проверку подписи данных по алгоритму ГОСТ Р 34.10-2001 через стандартный интерфейс JCA при помощи класса Signature
.
Для создания объекта подписи/проверки подписи по алгоритму ГОСТ Р 34.10-94 методу getInstance()
необходимо в качестве параметра передать имя, идентифицирующее данный алгоритм –"rtGOST3411withrtGOST3410
". Следующим шагом необходимо предоставить закрытый ключ для подписи данных методом initSign()
или открытый ключ/сертификат для проверки подписи методом initVerify()
. Сами данные для подписи/проверки подписи передаются методом update()
. Подпись данных осуществляется методом sign()
, проверка подписи – методом verify()
.
Подробное описание использования класса Signature
доступно в разделе JCA (вместе с подробными примерами).
Code Block | ||||
---|---|---|---|---|
| ||||
Signature signature = Signature.getInstance("rtGOST3411withrtGOST3410", "JRT11");
signature.initSign(privateKey);
signature.update(TEXT);
byte[] signatureValue = signature.sign(); |
Code Block | ||||
---|---|---|---|---|
| ||||
Signature signature = Signature.getInstance("rtGOST3411withrtGOST3410", "JRT11");
signature.initVerify(publicKey);
signature.update(TEXT);
boolean result = signature.verify(signatureValue)); |
Для подписи ключом RSA в качестве имени алгоритма надо использовать "MD5withRSA
".
Имитозащита данных
Провайдер JRT11 позволяет осуществлять имитозащиту данных по алгоритму ГОСТ 28147-89 через стандартный интерфейс JCA при помощи класса Mac.
Для имитозащиты данных по алгоритму ГОСТ 28147-89 при создании объекта типа Mac
методу getInstance()
в качестве имени алгоритма надо передать значение "rt11GOST28147
".
Созданный объект сначала необходимо инициализировать ключом шифрования с помощью метода init().
Затем объекту передаются данные методом update()
(в случае выполнения операции в несколько этапов) и затем получают результат вызовом doFinal().
В случае вычисления имитовставки в один шаг вызов метода
опускаетсяupdate()
.
Подробное описание использования класса Mac
доступно в разделе JCA (вместе с подробным примером).
Code Block | ||||
---|---|---|---|---|
| ||||
Mac mac = Mac.getInstance("rt11GOST28147", "JRT11");
mac.init(secret);
mac.update(dataBytes);
byte[] macValue = mac.doFinal(); |
Генерация последовательности случайных чисел
Провайдер JRT11 позволяет получать последовательности случайных чисел с криптографически стойкого датчика через стандартный интерфейс JCA при помощи класса SecureRandom
.
При создании объекта типа SecureRandom
методу getInstance()
в качестве имени алгоритма надо передать значение "rtRandom
". Для генерации случайной последовательности чисел вызывается метод nextBytes()
, в качестве аргумента передается массив произвольного размера для заполнения случайными байтами.
Подробное описание использования класса SecureRandom
доступно в разделе JCA.
Code Block | ||||
---|---|---|---|---|
| ||||
byte[] bytes = new byte[8];
SecureRandom random = SecureRandom.getInstance("rtRandom", "JRT11");
random.nextBytes(bytes); |
Хранение ключей
Провайдер JRT11 позволяет осуществлять извлечение ключа из хранилища через стандартный интерфейс JCA при помощи класса KeyStore
. Операция извлечения используется для ключей, создаваемых с помощью класса KeyPairGenerator
и записываемых сразу на Рутокен.
При создании объекта типа KeyStore
методу getInstance()
в качестве имени алгоритма надо передать значение "rtStore
". Для извлечения ключа необходимо передать методом KeyStore.load(InputStream, char[])
PIN-код Рутокен вторым значением, а значение InputStream
оставить пустым.
Подробное описание использования класса KeyStore
доступно в разделе JCA.
Code Block | ||||
---|---|---|---|---|
| ||||
KeyStore keyStore = KeyStore.getInstance("rtStore", "JRT11");
keyStore.load(null, "12345678".toCharArray());
PrivateKey privateKey = (PrivateKey)keyStore.getKey("alias", null); |
Так как для извлечения открытого ключа в KeyStore
нет специальных возможностей, а хранилище "rtStore
" хранит ключи в контейнерах, предлагается использовать следующий механизм: сначала извлечь KeyStore.Entry
методом KeyStore.getEntry(String, ProtectionParameter)
, а затем из полученного контейнера извлечь открытый ключ.
Code Block | ||||
---|---|---|---|---|
| ||||
KeyStore.Entry entry = keyStore.getEntry("alias", null);
PublicKey publicKey = ((ru.rutoken.security.KeyContainer)entry).getPublicKey(); |
Использование провайдера JRT11 без интерфейса JCA
Несмотря на удобства своего использования, интерфейс JCA имеет ряд недостатков:
- невозможность одновременной работы с несколькими токенами;
- необходимость регистрации провайдера в списке провайдеров с соответствующими правами;
- возможность появления конфликтов при использовании одним провайдером ключей, сгенерированных другим провайдером;
- необходимость контроля целостности производителем провайдера для исключения возможности его подмены;
- снижение эффективности выполнения криптографических операций за счет экспортных ограничений некоторых Java-машин и использования дополнительной прослойки.
Провайдер JRT11 имеет собственный интерфейс, аналогичный JCA и лишенный описанных недостатков.
Краткое описание собственного интерфейса провайдера JRT11
Аналогично интерфейсу JCA, где классы SecureRandom
, MessageDigest
, KeyPairGenerator
, Signature
располагаются в пакете java.security
, аналогичные классы собственного интерфейса провайдера JRT11 SecureRandom
, MessageDigest
, KeyPairGenerator
, Signature
располагаются в пакете в пакете ru.rutoken.security
. В этих классах полностью сохранен интерфейс JCA как в плане создания объекта, так и в плане использования: аналогичны используемые методы, параметры, исключения.
Например, код
Code Block | ||||
---|---|---|---|---|
| ||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
digest.update(data);
byte[] result = digest.digest(); |
будет одинаково работать как в случае использования стандартного класса java.security.MessageDigest
, так и в случае использования класса ru.rutoken.security.MessageDigest
.
Переход от одного интерфейса к другому легко реализуется заменой оператора import
. Подробное описание интерфейса классов ru.rutoken.security
предоставлено в JavaDoc.
Классы для работы с шифрованием средствами JCA/JCE находятсяв пакете javax.crypto
. По аналогии, классы для работы с шифрованием средствами JRT11 находятся в пакете ru.rutoken.crypto
.
Очистка объектов
Во время выполнения операций криптографической защиты конфиденциальная информация (защищаемые пользовательские данные, закрытые и секретные ключи, датчики случайных чисел, участвующие в генерации ключей) неизбежно располагается в оперативной памяти. Важно следить за тем, чтобы конфиденциальные данные располагались в памяти минимально необходимый промежуток времени, после чего немедленно стирались для снижения вероятности их компрометации противником.
Java самостоятельно следит за использованием объектов и удаляет неиспользуемые по мере необходимости, однако перед этим объекты могут находиться в памяти довольно долго. Для самостоятельного удаления разработчиком конфиденциальной информации провайдер JRT11 предусматривает механизм очистки.
Для очистки конфиденциальной информации в провайдере JRT11 предназначен класс ru.rutoken.security.Cleaner
. Все объекты, которые содержат конфиденциальную информацию, должны быть по окончании использования очищены с помощью статического метода Cleaner.clean(Object)
, вызванного в finally
секции для гарантированной очистки в случае любых ошибок.
Code Block | ||||
---|---|---|---|---|
| ||||
public byte[] sign(byte[] data, PrivateKey key) throws Exception
{
Signature signature = null;
try
{
signature = Signature.getInstance(JRT11Provider.GOST_SIGNATURE_ALGORITHM, JRT11Provider.PROVIDER_NAME);
signature.initSign(key);
signature.update(data);
return signature.sign();
}
finally
{
Cleaner.clean(signature);
}
} |
Таким образом, следует очищать в вызывающей функции
- подписываемые данные,
- закрытый ключ, с помощью которого производится подпись,
- объект хранилища, из которого прочитан ключ,
- PIN-код Пользователя и Администратора Рутокен,
- и все остальные объекты, которые могут содержать конфиденциальную информацию или способствовать ее получению.
Использование параметров алгоритмов
Провайдер JRT11 позволяет работать с параметрами российских алгоритмов, описанными в стандарте RFC 4357 и обеспечивающими необходимую стойкость. Они обеспечивают совместимость с продуктами других производителей и не рекомендуются к использованию без необходимости. Использование других параметров, стойкость которых неизвестна, не допускается. Использование тестовых параметров допустимо только для целей тестирования.
Для работы параметрами алгоритмов предназначен класс ru.rutoken.security.spec.gost
(подробное описание класса предоставлено в JavaDoc).
Используется четыре типа параметров: шифрования (Crypt), хеширования (Digest), подписи (Elliptic) и обмена (Exchange).
Параметры обмена (Exchange) теоретически являются эллиптическими параметрами для алгоритма ГОСТ Р 34.10-2001 и ключи, созданные с параметрами обмена (Exchange), могут быть использованы как для подписи, так и для выработки общего ключа парной связи.
Параметры подписи (Elliptic) предназначены для ключей подписи и использовать такие ключи для выработки ключа парной связи по алгоритму Диффи-Хеллмана нельзя.
Реализация каждого параметра состоит из трех классов: интерфейс, реализация и фабрика.
Интерфейс определяет, соответствует ли OID присвоенному ему параметру в стандарте RFC 4357. Каждый OID может быть закодирован в соответствии с ITU-T Rec. X.690 (07/2002), поэтому в интерфейсе есть метод public byte[] getEncodedOID()
.
Фабрика определяет возможности перечисления всех возможных параметров и получения параметров:
- по умолчанию;
- по номеру;
- по OID;
- по строковому представленияю OID;
- по ASN.1 кодированному представлению.
Все алгоритмы, которые реализует провайдер JRT11, имеют параметры по умолчанию и работают без явного указания параметров. Предусмотрено два способа явного задания параметров алгоритмов.
...
Параметры можно получить из фабрики и установить их в алгоритм.
Code Block | ||||
---|---|---|---|---|
| ||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("rtGOST3410", "JRT11");
generator.initialize(EllipticParamFactory.getInstance());
KeyPair pair = generator.generateKeyPair(); |
Code Block | ||||
---|---|---|---|---|
| ||||
Mac mac = Mac.getInstance("rt11GOST28147", "JRT11");
mac.init(key, CryptParamFactory.getInstance("1.2.643.2.2.31.1")); |
В целях обеспечения совместимости c java-классами все используемые параметры реализуют стандартный интерфейс java.security.spec.AlgorithmParameterSpec
. Методы, позволяющие устанавливать в алгоритм параметры, реализующие интерфейс AlgorithmParameterSpec
, есть в большинстве алгоритмов (см. следующую ссылку) .
Использование расширенных имен алгоритмов
Задать параметры можно непосредственно в имени алгоритма. При создании алгоритма после его имени через разделитель "/" следует указать строковое представление OID.
Code Block | ||||
---|---|---|---|---|
| ||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("rtGOST3410/1.2.643.2.2.35.1", "JRT11"); |
Этот способ будет работать как при создании алгоритма стандартными средствами JCA из пакета java.security
, так и при создании алгоритмов средствами пакета ru.rutoken.security.
Конфигурация PKCS#11
В состав конфигурации PKCS#11 входит 3 параметра:
- библиотека PKCS#11 (параметр Library),
- серийный номер Рутокен (параметр Serial) и
- PIN-код Рутокен (параметр Pin).
Параметр Library указывает полный путь к библиотеке, реализующей стандарт PKCS#11, и зависит от операционной системы. Если путь не задан явно в конфигурации, провайдер JRT11 будет пытаться найти библиотеку по умолчанию. Например, для ОС Windows JRT11 будет проверять путь C:\Windows\System32\rtPKCS11ECP.dll.
Параметр Serial содержит уникальный серийный номер Рутокен, с которым должен работать провайдер JRT11. Его можно узнать через Панель управления Рутокен (по нажатию кнопки Информация будет выдан ID в hex). В конфигурации серийный номер также задается в шестнадцатеричном виде. Если серийный номер не задан, то провайдер JRT11 будет пытаться использовать первый присутствующий Рутокен.
Параметр Pin определяет PIN-код Рутокен и значения по умолчанию не имеет.
Конфигурация PKCS#11 может быть сохранена в файл.
Code Block | ||||
---|---|---|---|---|
| ||||
Library=C:\Windows\System32\rtPKCS11ECP.dll
Serial=2b356eb7
Pin=12345678 |
Для работы с конфигурацией предназначен класс ru.rutoken.jrt11.Config
(подробное описание класса предоставлено в JavaDoc).
Класс Config
реализует интерфейс AlgorithmParameterSpec,
поэтому его можно передавать в качестве параметра в некоторые алгоритмы, например, KeyPairGenerator.initialize(int).
Использование класса SessionFactory
Предназначение класса ru.rutoken.jrt11.SessionFactory
заключается не только в определении конфигурации PKCS#11, но и выполнении предварительных действий для работы по стандарту PKCS#11: инициализации библиотеки, поиска подключенного Рутокен, открытии сессии и пр. и выдаче параметра для инициализации JCA алгоритма. Подробное описание класса доступно в JavaDoc.
Схема использования класса:
- создание объекта
SessionFactory
; - инициализация объекта
SessionFactory
методомsetParameter()
; - получение параметра.
Code Block | ||||
---|---|---|---|---|
| ||||
SessionFactory factory = new SessionFactory();
factory.setParameter(new ParamPin(tokenPin));
AlgorithmParameterSpec token = factory.getToken(); |
Для инициализации объекта SessionFactory
может быть использован класс Config
или ParamPin
.
Полученный в результате параметр token
может быть использован для инициализации различных JCA алгоритмов.
Также можно с помощью функции getSession()
получить дескриптор открытой сессии и работать с ним.
Сценарии задания конфигурации PKCS#11
Ниже перечислены различные сценарии задания конфигурации PKCS#11.
1. Отсутствие задания конфигурации. Алгоритмы, не требующие работы с секретным ключом, будут работать и без нее.
2. Задать конфигурацию можно с помощью задания системных свойств.
Code Block | ||||
---|---|---|---|---|
| ||||
System.setProperty("ru.rutoken.jrt11.ConfigLibrary", "C:\\Windows\\System32\\rtPKCS11ECP.dll");
System.setProperty("ru.rutoken.jrt11.ConfigSerial", "2b356eb7");
System.setProperty("ru.rutoken.jrt11.ConfigPin", "12345678"); |
Этот способ в первую очередь подходит в случае невозможности изменения уже работающего кода. Установить эти свойства можно и при запуске программы из командной строки.
3. Задать конфигурацию можно при установке провайдера.
Code Block | ||||
---|---|---|---|---|
| ||||
Config config = new Config("C:\\Windows\\System32\\rtPKCS11ECP.dll", "2b356eb7", "12345678");
Security.addProvider(new JRT11Provider(config)); |
Этот способ эквивалентен предыдущему. Задание конфигурации в провайдере приводит к установке системных свойств.
4. При установке провайдера можно задать имя конфигурационного файла.
Code Block | ||||
---|---|---|---|---|
| ||||
String configFileName = "C:\\jrt11\\jrt11.properties";
Security.addProvider(new JRT11Provider(configFileName)); |
5. Для случая статической регистрации провайдера имя конфигурационного файла можно указать в файле <jre>\lib\security\java.security:
security.provider.7=ru.rutoken.jrt11.JRT11Provider C:\\jrt11\\jrt11.properties
6. Создать конфигурацию можно прямо в коде, и передать параметром в алгоритм
Code Block | ||||
---|---|---|---|---|
| ||||
Config config = new Config("C:\\Windows\\System32\\rtPKCS11ECP.dll", "2b356eb7", "12345678");
generator.initialize(config); |
Необязательно задавать все параметры конфигурации.
Code Block | ||||
---|---|---|---|---|
| ||||
Config config = new Config(null, null, "12345678");
generator.initialize(config); |
7. PIN-код Рутокен можно устанавливать в алгоритм напрямую, без конфигурации.
Code Block | ||||
---|---|---|---|---|
| ||||
generator.initialize(new ru.rutoken.security.spec.ParamPin("12345678")); |
8. Можно устанавливать в алгоритм PKCS#11 токен.
Code Block | ||||
---|---|---|---|---|
| ||||
Config config = new Config("C:\\Windows\\System32\\rtPKCS11ECP.dll", "2b356eb7", "12345678");
sessionFactory.setParameter(config);
generator.initialize(sessionFactory.getToken()); |
Это способ является рекомендуемым.
9. Можно устанавливать в алгоритм PKCS#11 контекст.
Code Block | ||||
---|---|---|---|---|
| ||||
generator.initialize(sessionFactory.getContext()); |
10. Можно устанавливать в алгоритм PKCS#11 сессию.
Code Block | ||||
---|---|---|---|---|
| ||||
generator.initialize(sessionFactory.getSession()); |
Вы можете выбрать один из этих вариантов, или Вы можете организовать любую комбинацию сценариев, наиболее подходящую для Ваших целей.
Related links
- Руководство по применению JCA: http://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html
- Спецификация Java SE 6 API http://docs.oracle.com/javase/6/docs/api/index.html
- Загрузка JDK/JDE http://www.oracle.com/technetwork/java/javase/downloads/index.html
- Русскоязычное руководство по maven http://www.apache-maven.ru/index.html
Вложения
- JavaDoc для разработчиков jrt11-0.5-javadoc.zip