Хранилище ключей и сертификатов

Защита данных в приложениях имеет важное значение, защита конфиденциальной информации — первостепенное. Одним из самых распространённых способов защиты информации во все времена является шифрование данных. Криптография, симметричное и асимметричное шифрование, ключи и сертификаты непосредственно связаны с данной задачей. Используемые для защиты информации ключи и сертификаты также нужно надежно защитить. Для этих целей используется keystore — хранилище сертификатов и ключей.

keystore — это специализированное хранилище секретных данных, которое используется Java-приложениями для шифрования, аутентификации и установки HTTPS соединений. Так, для аутентификации клиента и сервера, устанавливающих SSL (Secure Sockets Layer — уровень защищённых cокетов) соединение, требуются приватные ключи и сертификаты. Если используется односторонняя аутентификация, то keystore используется только на серверной стороне. При двусторонней аутентификации клиент и сервер обмениваются сертификатами; соответственно и у сервера, и у клиента должны быть keystore с парой ключей private/public + сертификат. Иными словами keystore используется для хранения ключей и сертификатов, применяемых для идентификации владельца ключа (клиента или сервера).

Java поддерживает несколько форматов хранилищ keystore :

• jks — стандартный тип хранилища в виде файла с расширением jks ("java key storage"); устанавливается по умолчанию и, поэтому, применяется наиболее часто.
• jceks — альтернативная реализация хранилища, которая использует более сильное шифрование на основе triple DES; можно обновить имеющееся jks-хранилище до jceks соответствующей командой утилиты keytool.
• pkcs12 — тип хранилища, предназначенный прежде всего для хранения или переноса закрытых ключей пользователя, сертификатов и пр.

Каждая запись в keystore имеет уникальный псевдоним (alias). Рекомендуется в keystore не использовать alias'ы, отличающиеся только регистром. В стандартной реализации каждый ключ в хранилище защищается паролем; кроме того, всё хранилище целиком может быть защищено отдельным паролем.

Стандартное хранилище доверенных CA-сертификатов (Certificate Authority) для Java приложений располагается в директории jre/lib/security/cacerts (пароль - changeit).

Информацию в хранилище можно разделить на две категории: ключевые записи (пары ключей private/public) и доверенные сертификаты. Ключевая запись, используемая для криптографических целей, включает идентификационные данные объекта и его закрытый ключ. Доверенный сертификат содержит идентификационные данные объекта и открытый ключ. Запись с доверенным сертификатом не может использоваться в тех случаях, где требуется закрытый ключ.

Чтобы отделить ключевые записи от сертификатов целесообразно использовать различные хранилища : один для собственных ключей, а другой — для доверенных сертификатов, включая сертификаты Центров сертификации (CA). Такой подход позволит реализовать разделение между собственными сертификатами и соответствующими закрытыми ключами, и доверенными сертификатами. Дополнительно можно обеспечить более высокую защиту для закрытых ключей в отдельном keystore с ограниченным доступом, а доверенные сертификаты оставить в более свободном доступе.

В конце статьи представлен Java пример просмотра содерживого хранилища ключей и сертификатов CertificateReader.

Утилита keytool

Для управления парами ключей (private/public), сертификатами и хранилищем keystore Java включает утилиту keytool, располагаемую в директории bin. Для запуска keytool можно использовать командную строку. Опции утилиты позволяют выполнять различные операции и получать определенные сведения. Так, чтобы получить информацию об утилите, можно просто выполнить команду keytool без опций :


C:\Program Files\Java\jre1.8.0_121\bin>keytool
Key and Certificate Management Tool

Commands:

-certreq          Generates a certificate request
-changealias      Changes an entry's alias
-delete           Deletes an entry
-exportcert       Exports certificate
-genkeypair       Generates a key pair
-genseckey        Generates a secret key
-gencert          Generates certificate from a certificate request
-importcert       Imports a certificate or a certificate chain
-importpass       Imports a password
-importkeystore   Imports one or all entries from another keystore
-keypasswd        Changes the key password of an entry
-list             Lists entries in a keystore
-printcert        Prints the content of a certificate
-printcertreq     Prints the content of a certificate request
-printcrl         Prints the content of a CRL file
-storepasswd      Changes the store password of a keystore

Use "keytool -command_name -help" for usage of command_name
 

Чтобы получить дополнительную справку о команде необходимо указать ее наименование и волшебное слово help. Не забывайте о дефисе '-' перед опциями :


C:\Program Files\Java\jre1.8.0_121\bin>keytool -certreq -help
keytool -certreq [OPTION]...

Generates a certificate request

Options:

-alias <alias>            alias name of the entry to process
-sigalg <sigalg>          signature algorithm name
-file <filename>          output file name
-keypass <arg>            key password
-keystore <keystore>      keystore name
-dname <dname>            distinguished name
-storepass <arg>          keystore password
-storetype <storetype>    keystore type
. . .
-providerarg <arg>        provider argument
-providerpath <pathlist>  provider classpath
-v                        verbose output
-protected                password through protected mechanism

Use "keytool -help" for all available commands
 

Создание самоподписанного сертификата

Для создания самоподписанного сертификата также необходимо использовать команду -genkey с указанием срока действия сертификата в опции -validity. Следующая команда создаст пару 2048-битных RSA-ключей, действительных на протяжении 365 дней, с указанным псевдонимом (parent) в заданном файле/хранилище ключей (keystore.jks). Закрытый ключ в хранилище «закрывается» паролем, открытый ключ «оборачивается» в самоподписанный сертификат.


keytool -genkey -alias parent -keyalg RSA -validity 365 \
        -keystore keystore.jks
 

Если заданного хранилища ключей (keystore.jks) не существует, то keytool создаст его. При выполнении команды keytool будет запрашивать некоторые необходимые данные : пароль хранилища, Distinguished Name и пароль закрытого ключа. Многие параметры используются со значениями по умолчанию. Так, например, алиас - mykey, хранилище - .keystore в домашней директории пользователя (HOMEPATH), алгоритм шифрвания - SHA1withDSA и пр.

Distinquished Name

Сертификат создается в формате X.509. В этом формате в качестве идентификатора владельца используется Distinquished Name или просто DN в формате X.500. Этот же формат идентификации объектов используется в LDAP-протоколе или в SNMP. Distinquished Name задается в виде разделенных через запятую атрибутов :

  • CN — common name (имя владельца);
  • OU — organizational unit or department/division (департамент/отдел);
  • O — organization name (наименование организации);
  • L — locality or city (город/местоположение);
  • ST — state or province;
  • C — country, two chars (страна).

Часть из атрибутов могут быть пропущены; в этом случае им будет присвоено значение Unknown.

Одним из важных атрибутов сертификата являются альтернативные имена SAN (SubjectAlternativeName). Подробности и пример внесения SAN в самоподписанный сертификат представлены на странице настройки конфигурации сервера Tomcat.

Параметры сертификата можно указывать в качестве опции команды. Так, в следующем варианте примера задаётся информация о компании, наименование алиаса, тип и размещение хранилища, срок действия, алгоритм для генерации ключей, размер ключа, пароли на хранилище и на ключ.


C:\Program Files\Java\jdk1.8.0_121\bin> \
  keytool -v -genkey -dname "CN=java-online.ru, \
   OU=Developers, O=IT Systems Inc., L=Moscow, C=RF" \
  -alias parent -storetype jks -keystore keystore.jks \
  -validity 365 -keyalg RSA -keysize 2048 \
  -storepass mystorepass -keypass mykeypass

Generating 2 048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 365 days
        for: CN=java-online.ru, OU=Developers, O=IT Systems Inc., L=Moscow, C=RF
[Storing keystore.jks]
 

Новое хранилище было размещено в той же директории, где и располагается keytool.

Создание пары ключей

Для создания пары ключей необходимо использовать команду "-genkeypair". Следующая команда создаст пару ключей "keypair" в хранилище keystore.jks, где размещен созданный ранее сертификат.


C:\Program Files\Java\jdk1.8.0_121\bin> \
 keytool -alias keypair -genkeypair -keystore keystore.jks \
         -dname "CN=java-online.ru"

Enter keystore password:
Enter key password for <keypair>
        (RETURN if same as keystore password):
 

Сертификат и закрытый ключ сохранены в виде новой keystore записи, идентифицированной псевдонимом "keypair". Открытый ключ обертывается в формат X.509 — это самоподписанный сертификат, который сохранен как одноэлементная цепочка сертификата.

Опции команды genkeypair

  • {-v}
  • [-storepass storepass]
  • {-alias alias}
  • {-storetype storetype}
  • {-keystore keystore}
  • [-keypass keypass] — является паролем, используемым для защиты закрытого ключа
  • [-dname dname] — определяет отличительное имя в формате X.500, связанное с псевдонимом и используемое в качестве issuer и subject поля в самоподписанном сертификате
  • {-startdate value}
  • {-keyalg keyalg} — определяет алгоритм, который будет использоваться, чтобы генерировать пару ключей
  • {-keysize keysize} — определяет размер каждого ключа, который будет сгенерирован
  • {-sigalg sigalg} — определяет алгоритм, который должен использоваться, чтобы подписать самоподписанный сертификат; алгоритм должен быть совместимым с keyalg
  • {-ext ext}*
  • {-validity valDays}
  • {-providerClass provider_class_name {-providerArg provider_arg}}
  • {-protected}
  • {-Jjavaoption}

Создадим еще две пары ключей с псевдонимами "keypair1" и "keypair2", чтобы при просмотре содержимого хранилища (ниже) был небольшой список пар ключей :


 keytool -alias keypair1 -genkeypair -keystore keystore.jks \
        -dname "CN=java-online.ru"
 keytool -alias keypair2 -genkeypair -keystore keystore.jks \
        -dname "CN=java-online.ru"
 

Экспорт сертификата

Сертификат можно экспортировать из хранилища и предоставить его пользователям Вашей «подписанной» программы. Тогда пользователи могут занести Ваш сертификат в свое хранилище доверенных сертификатов. Для экспорта сертификата используется команда "exportcert". Следующий пример извлекает из хранилища сертификат в файл "parent.cer" :


C:\Program Files\Java\jdk1.8.0_121\bin> \
  keytool -exportcert -keystore keystore.jks \
  -alias parent -file parent.cer

Enter keystore password:
Certificate stored in file <parent.cer>
 

Импорт сертификата

Чтобы импортировать сертификат в хранилище, нужно его сначала получить каким-либо образом. Не будем мудрить и извлечем сертификат с псевдонимом veriSignclass1g3ca из хранилища доверенных сертификатов jre\lib\security\cacerts (пароль хранилища changeit). То есть выполним команду экспорта сертификата с указанием соответствующего хранилища :

Экспорт сертификата из хранилища cacerts


C:\Program Files\Java\jdk1.8.0_121\bin>
  keytool -exportcert -alias veriSignclass1g3ca -keystore \
  "C:\Program Files\Java\jdk1.7.0_67\jre\lib\security\cacerts" \
  -file veriSignclass1g3ca.cer

Enter keystore password:
Certificate stored in file <veriSignclass1g3ca.cer>
 

Импорт сертификата в хранилище

Чтобы импортировать сертификат в хранилище keystore.jks необходимо использовать команду "importcert". Если в качестве опции указать "-trustcacerts", то сертификат импортируется в хранилище доверенных сертификатов, т.е. в jre\lib\security\cacerts. При выполнении команды импорта утилита keytool попросит ввести пароль хранилища :


C:\Program Files\Java\jdk1.8.0_121\bin> \
  keytool -importcert -keystore keystore.jks \
  -file veriSignclass1g3ca.cer

Enter keystore password: \
  Owner: \
   CN=VeriSign Class 1 Public Primary Certification Authority - G3, \
   OU="(c) 1999 VeriSign, Inc. - For authorized use only", \
   OU=VeriSign Trust Network, \
   O="VeriSign, Inc.", \
  C=USIssuer: \
   CN=VeriSign Class 1 Public Primary Certification Authority - G3, \
   OU="(c) 1999 VeriSign, Inc. - For authorized use only", \
   OU=VeriSign Trust Network, \
   O="VeriSign, Inc.", \
   C=US \
   Serial number: 8b5b75568454850b00cfaf3848ceb1a4 \
Valid from: \
   Fri Oct 01 04:00:00 MSD 1999 until: Thu Jul 17 02:59:59 MSK 2036 \
Certificate fingerprints: \
   MD5:  B1:47:BC:18:57:D1:18:A0:78:2D:EC:71:E8:2A:95:73 \
   SHA1: 20:42:85:DC:F7:EB:76:41:95:57:8E:13:6B:D4:B7:D1:E9:8E:46:A5 \
   SHA256: CB:B5:AF:18:5E:94:2A:24:02:F9:EA:CB:C0:ED:5B:B8:76:EE:A3: \
           C1:22:36:23:D0:04:47:E4:F3:BA:55:4B:65
   Signature algorithm name: SHA1withRSA Version: 1 \
Trust this certificate? [no]:  y
Certificate was added to keystore
 

Просмотр хранилища

Для чтения содержимого хранилища необходимо использовать команду "-list". В качестве опции "-keystore" можно указать путь к хранилищу. По умолчанию команда "-list" отображает цифровой отпечаток SHA1 сертификата. Следующий код позволяет просмотреть содержимое созданного хранилища, включающего сертификат и три пары ключей :


C:\Program Files\Java\jdk1.8.0_121\bin> \
   keytool -list -keystore keystore.jks
Enter keystore password:

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 5 entries

keypair2, 14.02.2018, PrivateKeyEntry,
Certificate fingerprint (SHA1): \
    C4:02:BA:D7:24:6B:84:2F:CD:F9:81:16:5F:74:E0:31:7B:C0:19:B1
keypair1, 14.02.2018, PrivateKeyEntry,
Certificate fingerprint (SHA1): \
    AB:BA:92:77:44:BD:B0:65:EB:29:0C:F9:86:64:0F:81:B7:4A:27:9A
keypair, 14.02.2018, PrivateKeyEntry,
Certificate fingerprint (SHA1): \
    8A:8B:21:83:1E:75:4F:C7:62:85:6A:31:84:45:AA:16:2B:20:06:1E
parent, 13.02.2018, PrivateKeyEntry,
Certificate fingerprint (SHA1): \
    DB:8B:9D:9D:DF:5B:B3:82:0E:19:C6:A4:A4:3E:08:C0:AB:20:F9:85
mykey, 18.02.2018, trustedCertEntry,
Certificate fingerprint (SHA1): \
    20:42:85:DC:F7:EB:76:41:95:57:8E:13:6B:D4:B7:D1:E9:8E:46:A5	
 

Опции команды list

  • {-v | -rfc}
  • [-storepass storepass]
  • {-alias alias}
  • {-storetype storetype}
  • {-keystore keystore}
  • {-providerName provider_name}
  • {-providerClass provider_class_name {-providerArg provider_arg}}
  • {-protected} {-Jjavaoption}

Если при просмотре хранилища использовать опцию "-v", то информация о сертификате выводится с дополнительной информацией, включающей владельца, порядковый номер и т.д. При использовании опции "-rfc" содержание сертификата печатается согласно интернет-стандарта RFC-1421.

На странице описания SSL сертификата представлен результат выполнения команды просмотра хранилища keytool -list для опций '-v' и '-rfc'.

Полную англоязычную версию документации на keytool можно найти здесь.

Пример просмотра хранилища и сертификата

На скриншоте представлен пример CertificateReader, позволяющий просматривать хранилище ключей и сертификаты, а также извлекать информацию о сертификате.

Внутри сертификата хранится пара значений Distinqueshed Names. Один DN принадлежит владельцу сертификата, а второй DN указывает идентификатор цента сертификации (CA), подписавшего сертификат. В случае с самоподписанным (self-signed) сертификатом, оба эти DN указывают на владельца сертификата.

Листинг примера

Интерфейс примера выполнен с использованием библиотеки Swing. Поскольку Swing не является предметом данной статьи, то методы формирования интерфейса не представлены в листинге примера. При необходимости желающие могут скачать исходный код примера в конце страницы.

В листинге примера представлены два метода : loadKeyStore, showCertificate. Первый метод позволяет выбрать хранилище сертификатов. Второй метод выполняет чтение сертификата и представление его параметров в интерфейсе. После листинга представлен скриншот, на котором выполнено чтение созданного сертификата.

import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;

public class CertificateReader extends JFrame 
{
    final String   TERMIN        = "Срок действия сертификата%s";
    final String   VALID         = "действителен"               ;
    final String   INVALID       = "не действителен"            ;
    final String   CREATER       = "Издатель%s"                 ;
    final String   NUMBER        = "Серийный номер%s"           ;
    final String   START         = "Начало срока действия%s"    ;
    final String   END           = "Конец срока действия%s"     ;
    final String   OWNER         = "Владелец%s"                 ;
    final String   ALGORITM      = "Алгоритм подписи%s"         ;
    final String   SIGN          = "Подпись сертификата%s"      ;
    final String   LF            = "\n"                         ;
    final String   LF_SPACE      = " :\n      "                 ;

    KeyStore       keyStore      = null; // хранилище
    JList<String>  lstAliases    = null;
    JTextField     txtFileName   = null;
    JTextArea      taCertificate = null;
    final  int     LIST_size     = 140 ;

    public CertificateReader()
    {
        setTitle("Просмотр хранилища сертификатов");
        setSize(600, 480);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        getContentPane().setLayout(new BorderLayout());
        getContentPane().add(createCtrl(), BorderLayout.SOUTH);
        getContentPane().add(createGUI (), BorderLayout.CENTER);

        setVisible(true);
    }
    private JPanel createCtrl()
    {
        . . . 
    }
    private JSplitPane createGUI()
    {
        . . . 
    }
    void loadKeyStore() 
    {
        FileInputStream fis;
        // Выбор хранилища сертификатов
        JFileChooser chooser = new JFileChooser();
        if(chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
            txtFileName.setText(chooser.getSelectedFile().getAbsolutePath());
            try {
                // Чтение хранилище сертификатов
                keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                fis      = new FileInputStream(txtFileName.getText());
                keyStore.load(fis, null);
                Enumeration<String> E = keyStore.aliases();

                // Формирование набор сертификатов
                Vector<String> certs = new Vector<String>();
                while (E.hasMoreElements())
                    certs.add(  (String)E.nextElement() );
                // Размещение сертификатов в компоненте
                lstAliases.setListData(certs);
                invalidate();
            }
            catch (Exception e) {
                JOptionPane.showMessageDialog(this ,
                    "Ошибка чтения хранилища сертификатов:\n" + e);
            }
        }
    }

    void showCertificate(final String name)
    {
        Certificate cert = null;
        try {
            // Чтение сертификата
            cert = keyStore.getCertificate(name);
            X509Certificate xcert = (X509Certificate) cert;

        String valid = "";
            try {
                xcert.checkValidity();
                valid = VALID;
            } catch (Exception ex){
                valid = INVALID;
            }
            SimpleDateFormat sdf;
            sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
            String end   = sdf.format(xcert.getNotAfter ());
            String start = sdf.format(xcert.getNotBefore());
            String sign = new sun.misc.BASE64Encoder()
                                      .encode(cert.getSignature());

            String creater = xcert.getIssuerDN().getName();
            String owner   = xcert.getSubjectDN().getName();
            String number  = String.valueOf(xcert.getSerialNumber());
            String algo    = xcert.getSigAlgName();

            String info;
            info  = createLine(TERMIN  , valid  );
            info += createLine(CREATER , creater); 
            info += createLine(NUMBER  , number );
            info += createLine(START   , start  ); 
            info += createLine(END     , end    ); 
            info += createLine(OWNER   , owner  ); 
            info += createLine(ALGORITM, algo   ); 
            info += createLine(SIGN    , sign   ); 
            taCertificate.setText(info);
        } catch (KeyStoreException ex1) {
            JOptionPane.showMessageDialog(this  ,
                "Ошибка получения из хранилища сертификата с " +
                псевдонимом <<lstAliases.getSelectedValue()+">>");
        }
    }

    protected String createLine (String templ, String text)
    {
        return String.format(templ, LF_SPACE + text + LF);
    }

    public static void main(String[] args) {
        new CertificateReader();
    }
}

Примечание : класс CertificateReader используется в качестве примера на странице цифровой подписи jar файлов

Скачать пример

Рассмотренный на странице пример просмотра хранилища ключей и сертификатов можно скачать здесь (2.5 Кб).

  Рейтинг@Mail.ru