Рубрики
Без рубрики

Введение в BouncyCastle с Java

Откройте для себя BouncyCastle – библиотеку Java, дополняющая криптографическое расширение Java по умолчанию (JCE).

Автор оригинала: baeldung.

Введение в BouncyCastle с Java

1. Обзор

Надувной замок — библиотека Java, дополняемая Java Cryptographic Extension (JCE).

В этой вступительной статье мы покажем, как использовать BouncyCastle для выполнения криптографических операций, таких как шифрование и подпись.

2. Конфигурация Maven

Прежде чем начать работу с библиотекой, мы должны добавить необходимые зависимости в наш пом.xml файл:


    org.bouncycastle
    bcpkix-jdk15on
    1.58

Обратите внимание, что мы всегда можем искать последние версии зависимостей в Maven Центральный репозиторий .

3. Настройка неограниченной прочности юрисдикция политики файлов

Стандартная установка Java ограничена по прочности для криптографических функций, это связано с политиками, запрещающими использование ключа с размером, превышающим определенные значения, например 128 для AES.

Чтобы преодолеть это ограничение, мы должны настроить файлы политики неограниченной силы юрисдикции .

Для этого нам сначала нужно загрузить пакет, следуя этой ссылка . После этого, мы должны извлечь молнии файл в каталог по нашему выбору – который содержит два файла банку:

  • local_policy.jar
  • US_export_policy.jar

Наконец, мы должны искать (JAVA_HOME)/lib/security папку и заменить существующие файлы политики на те, которые мы извлекли здесь.

Обратите внимание, что в Java 9 нам больше не нужно загружать пакет политических файлов , установка crypto.policy имущество для неограниченное достаточно:

Security.setProperty("crypto.policy", "unlimited");

После этого необходимо проверить, правильно ли работает конфигурация:

int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
System.out.println("Max Key Size for AES : " + maxKeySize);

В результате:

Max Key Size for AES : 2147483647

На основе максимального размера ключа, возвращенного getMaxAllowedKeyLength () метод, мы можем с уверенностью сказать, что неограниченные файлы политики прочности были установлены правильно.

Если возвращенное значение равно 128, мы должны убедиться, что мы установили файлы в JVM, где мы запускаем код.

4. Криптографические операции

4.1. Подготовка сертификата и частного ключа

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

Для тестовых целей мы можем использовать эти ресурсы:

Баелдунг.cer это цифровой сертификат, который использует международный стандарт инфраструктуры ключа X.509, в то время как Baeldung.p12 является защищенным паролем PKCS12 Keystore, содержащий частный ключ.

Давайте посмотрим, как они могут быть загружены в Java:

Security.addProvider(new BouncyCastleProvider());
CertificateFactory certFactory= CertificateFactory
  .getInstance("X.509", "BC");
 
X509Certificate certificate = (X509Certificate) certFactory
  .generateCertificate(new FileInputStream("Baeldung.cer"));
 
char[] keystorePassword = "password".toCharArray();
char[] keyPassword = "password".toCharArray();
 
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream("Baeldung.p12"), keystorePassword);
PrivateKey key = (PrivateKey) keystore.getKey("baeldung", keyPassword);

Во-первых, мы добавили BouncyCastleПровидер в качестве поставщика услуг по обеспечению безопасности, динамично использующих addProvider () метод.

Это также может быть сделано статично путем редактирования (JAVA_HOME/jre/lib/security/java.security файл, и добавить эту строку:

security.provider.N = org.bouncycastle.jce.provider.BouncyCastleProvider

После правильной установки поставщика мы создали СертификатФактория объект с использованием getInstance() метод.

getInstance() метод принимает два аргумента; тип сертификата “X.509”, и поставщик безопасности “БК”.

certFactory экземпляр впоследствии используется для генерации X509Ертвимый объект, через генерироватьЕртификат () метод.

Таким же образом мы создали объект PKCS12 Keystore, на котором находится нагрузки () метод называется.

getKey () метод возвращает частный ключ, связанный с определенным псевдонимом.

Обратите внимание, что PKCS12 Keystore содержит набор частных ключей, каждый частный ключ может иметь определенный пароль, поэтому нам нужен глобальный пароль, чтобы открыть Keystore, и конкретный для получения частного ключа.

Сертификат и частная ключ-пара в основном используются в асимметричных криптографических операциях:

  • шифрование
  • расшифровка
  • подпись
  • проверка

4.2 Шифрование и расшифровка CMS/PKCS7

В асимметричной криптографии шифрования каждое сообщение требует публичного сертификата и частного ключа.

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

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

Давайте посмотрим, как реализовать encryptData () функции, используя сертификат шифрования:

public static byte[] encryptData(byte[] data,
  X509Certificate encryptionCertificate)
  throws CertificateEncodingException, CMSException, IOException {
 
    byte[] encryptedData = null;
    if (null != data && null != encryptionCertificate) {
        CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator
          = new CMSEnvelopedDataGenerator();
 
        JceKeyTransRecipientInfoGenerator jceKey 
          = new JceKeyTransRecipientInfoGenerator(encryptionCertificate);
        cmsEnvelopedDataGenerator.addRecipientInfoGenerator(transKeyGen);
        CMSTypedData msg = new CMSProcessableByteArray(data);
        OutputEncryptor encryptor
          = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC)
          .setProvider("BC").build();
        CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator
          .generate(msg,encryptor);
        encryptedData = cmsEnvelopedData.getEncoded();
    }
    return encryptedData;
}

Мы создали JceKeyTransRecipientInfoГенератор объект с использованием сертификата получателя.

Затем мы создали новую CMSEnvelopedDataГенератор объект и добавил генератор информации получателя в него.

После этого мы использовали JceCMSContentEncryptorBuilder класс для создания ВыходИгратор объект, используя алгоритм AES CBC.

Шифровальщик используется позже для создания CMSEnvelopedData объект, который инкапсулирует зашифрованное сообщение.

Наконец, закодированное представление конверта возвращается в качестве массива карт.

Теперь давайте посмотрим, что реализация decryptData () метод выглядит так:

public static byte[] decryptData(
  byte[] encryptedData, 
  PrivateKey decryptionKey) 
  throws CMSException {
 
    byte[] decryptedData = null;
    if (null != encryptedData && null != decryptionKey) {
        CMSEnvelopedData envelopedData = new CMSEnvelopedData(encryptedData);
 
        Collection recipients
          = envelopedData.getRecipientInfos().getRecipients();
        KeyTransRecipientInformation recipientInfo 
          = (KeyTransRecipientInformation) recipients.iterator().next();
        JceKeyTransRecipient recipient
          = new JceKeyTransEnvelopedRecipient(decryptionKey);
        
        return recipientInfo.getContent(recipient);
    }
    return decryptedData;
}

Во-первых, мы CMSEnvelopedData объект с помощью зашифрованного массива данных, а затем мы извлекли всех предполагаемых получателей сообщения с помощью getRecipients () метод.

После этого мы создали новую JceKeyТрансРекрециент объект, связанный с личным ключом получателя.

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

Наконец, учитывая ключ получателя в качестве аргумента, getContent () метод возвращает необработанный массив тотализатора, извлеченный из EnvelopedData этот получатель связан с.

Давайте напишем простой тест, чтобы убедиться, что все работает именно так, как должно:

String secretMessage = "My password is 123456Seven";
System.out.println("Original Message : " + secretMessage);
byte[] stringToEncrypt = secretMessage.getBytes();
byte[] encryptedData = encryptData(stringToEncrypt, certificate);
System.out.println("Encrypted Message : " + new String(encryptedData));
byte[] rawData = decryptData(encryptedData, privateKey);
String decryptedMessage = new String(rawData);
System.out.println("Decrypted Message : " + decryptedMessage);

В результате:

Original Message : My password is 123456Seven
Encrypted Message : 0�*�H��...
Decrypted Message : My password is 123456Seven

4.3 CMS/PKCS7 Подпись и проверка

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

Давайте посмотрим, как подписать секретное сообщение с помощью цифрового сертификата:

public static byte[] signData(
  byte[] data, 
  X509Certificate signingCertificate,
  PrivateKey signingKey) throws Exception {
 
    byte[] signedMessage = null;
    List certList = new ArrayList();
    CMSTypedData cmsData= new CMSProcessableByteArray(data);
    certList.add(signingCertificate);
    Store certs = new JcaCertStore(certList);

    CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
    ContentSigner contentSigner 
      = new JcaContentSignerBuilder("SHA256withRSA").build(signingKey);
    cmsGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
      new JcaDigestCalculatorProviderBuilder().setProvider("BC")
      .build()).build(contentSigner, signingCertificate));
    cmsGenerator.addCertificates(certs);
    
    CMSSignedData cms = cmsGenerator.generate(cmsData, true);
    signedMessage = cms.getEncoded();
    return signedMessage;
}

Во-первых, мы встроили вход в CMSTypedData , то, мы создали новый CMSSignedDataГенератор объект.

Мы использовали SHA256сРСА в качестве алгоритма подписи и нашего ключа к подписанию для создания нового СодержаниеСенье объект.

contentSigner экземпляр используется впоследствии вместе с сертификатом подписи для создания ПодписаниеИнфогенератор объект.

После добавления SignerInfoГенератор и свидетельство о подписании в CMSSignedDataГенератор например, мы, наконец, использовать генерировать () метод создания объекта с подписанными данными CMS, который также несет подпись CMS.

Теперь, когда мы видели, как подписать данные, давайте посмотрим, как проверить подписанные данные:

public static boolean verifSignedData(byte[] signedData)
  throws Exception {
 
    X509Certificate signCert = null;
    ByteArrayInputStream inputStream
     = new ByteArrayInputStream(signedData);
    ASN1InputStream asnInputStream = new ASN1InputStream(inputStream);
    CMSSignedData cmsSignedData = new CMSSignedData(
      ContentInfo.getInstance(asnInputStream.readObject()));
    
    SignerInformationStore signers 
      = cmsSignedData.getCertificates().getSignerInfos();
    SignerInformation signer = signers.getSigners().iterator().next();
    Collection certCollection 
      = certs.getMatches(signer.getSID());
    X509CertificateHolder certHolder = certCollection.iterator().next();
    
    return signer
      .verify(new JcaSimpleSignerInfoVerifierBuilder()
      .build(certHolder));
}

Опять же, мы создали CMSSignedData объект на основе нашего подписанного массива данных, то, мы получили все подписавшиеся, связанные с подписями с помощью getSignerInfos () метод.

В этом примере мы проверили только одного подписи, но для общего использования, он является обязательным для итерации над коллекцией подписавшихся, возвращенных getSigners () метод и проверить каждый отдельно.

Наконец, мы создали SignerInformationVerifier объект с использованием сборка () метод и передал его в проверить () метод.

Метод проверки () возвращает истинное если данный объект может успешно проверить подпись на объекте подписи.

Вот простой пример:

byte[] signedData = signData(rawData, certificate, privateKey);
Boolean check = verifSignData(signedData);
System.out.println(check);

В результате:

true

5. Заключение

В этой статье мы обнаружили, как использовать библиотеку BouncyCastle для выполнения основных криптографических операций, таких как шифрование и подпись.

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

Фрагменты кода можно найти, как всегда, более на GitHub .