Introduction to BouncyCastle with Java

引自

1. Overview

BouncyCastle is a Java library that complements the default Java Cryptographic Extension (JCE).

In this introductory article, we’re going to show how to use BouncyCastle to perform cryptographic operations, such as encryption and signature.

2. Maven Configuration

Before we start working with the library, we need to add the required dependencies to our pom.xml file:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.58</version>
</dependency>

Note that we can always look up the latest dependencies versions in the Maven Central Repository.

3. Setup Unlimited Strength Jurisdiction Policy Files

The standard Java installation is limited in terms of strength for cryptographic functions, this is due to policies prohibiting the use of a key with a size that exceeds certain values e.g. 128 for AES.

To overcome this limitation, we need to configure the unlimited strength jurisdiction policy files.

In order to do that, we first need to download the package by following this link. Afterwards, we need to extract the zipped file into a directory of our choice – which contains two jar files:

  • local_policy.jar
  • US_export_policy.jar

Finally, we need to look for the {JAVA_HOME}/lib/security folder and replace the existing policy files with the ones that we’ve extracted here.

Note that in Java 9, we no longer need to download the policy files package, setting the crypto.policy property to unlimited is enough:

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

Once done, we need to check that the configuration is working correctly:

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

As a result:

Max Key Size for AES : 2147483647

Based on the maximum key size returned by the getMaxAllowedKeyLength() method, we can safely say that the unlimited strength policy files have been installed correctly.

If the returned value is equal to 128, we need to make sure that we’ve installed the files into the JVM where we’re running the code.

4. Cryptographic Operations

4.1. Preparing Certificate And Private Key

Before we jump into the implementation of cryptographic functions, we first need to create a certificate and a private key.

For test purposes, we can use these resources:

Baeldung.cer is a digital certificate that uses the international X.509 public key infrastructure standard, while the Baeldung.p12 is a password-protected PKCS12 Keystore that contains a private key.

Let’s see how these can be loaded in 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);

First, we’ve added the BouncyCastleProvider as a security provider dynamically using the addProvider() method.

This can also be done statically by editing the {JAVA_HOME}/jre/lib/security/java.security file, and adding this line:

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

Once the provider is properly installed, we’ve created a CertificateFactory object using the getInstance() method.

The getInstance() method takes two arguments; the certificate type “X.509”, and the security provider “BC”.

The certFactory instance is subsequently used to generate an X509Certificate object, via the generateCertificate() method.

In the same way, we’ve created a PKCS12 Keystore object, on which the load() method is called.

The getKey() method returns the private key associated with a given alias.

Note that a PKCS12 Keystore contains a set of private keys, each private key can have a specific password, that’s why we need a global password to open the Keystore, and a specific one to retrieve the private key.

The Certificate and the private key pair are mainly used in asymmetric cryptographic operations:

  • Encryption
  • Decryption
  • Signature
  • Verification

4.2 CMS/PKCS7 Encryption And Decryption

In asymmetric encryption cryptography, each communication requires a public certificate and a private key.

The recipient is bound to a certificate, that is publicly shared between all senders.

Simply put, the sender needs the recipient’s certificate to encrypt a message, while the recipient needs the associated private key to be able to decrypt it.

Let’s have a look at how to implement an encryptData() function, using an encryption certificate:

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;
}

We’ve created a JceKeyTransRecipientInfoGenerator object using the recipient’s certificate.

Then, we’ve created a new CMSEnvelopedDataGenerator object and added the recipient information generator into it.

After that, we’ve used the JceCMSContentEncryptorBuilder class to create an OutputEncrytor object, using the AES CBC algorithm.

The encryptor is used later to generate a CMSEnvelopedData object that encapsulates the encrypted message.

Finally, the encoded representation of the envelope is returned as a byte array.

Now, let’s see what the implementation of the decryptData() method looks like:

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

First, we’ve initialized a CMSEnvelopedData object using the encrypted data byte array, and then we’ve retrieved all the intended recipients of the message using the getRecipients() method.

Once done, we’ve created a new JceKeyTransRecipient object associated with the recipient’s private key.

The recipientInfo instance contains the decrypted/encapsulated message, but we can’t retrieve it unless we have the corresponding recipient’s key.

Finally, given the recipient key as an argument, the getContent() method returns the raw byte array extracted from the EnvelopedData this recipient is associated with.

Let’s write a simple test to make sure everything works exactly as it should:

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);

As a result:

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

4.2 CMS/PKCS7 Signature And Verification

Signature and verification are cryptographic operations that validate the authenticity of data.

Let’s see how to sign a secret message using a digital certificate:

public static byte[] signData(
  byte[] data, 
  X509Certificate signingCertificate,
  PrivateKey signingKey) throws Exception {
  
    byte[] signedMessage = null;
    List<X509Certificate> certList = new ArrayList<X509Certificate>();
    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;
}

First, we’ve embedded the input into a CMSTypedData, then, we’ve created a new CMSSignedDataGenerator object.

We’ve used SHA256withRSA as a signature algorithm, and our signing key to create a new ContentSigner object.

The contentSigner instance is used afterward, along with the signing certificate to create a SigningInfoGenerator object.

After adding the SignerInfoGenerator and the signing certificate to the CMSSignedDataGenerator instance, we finally use the generate() method to create a CMS signed-data object, which also carries a CMS signature.

Now that we’ve seen how to sign data, let’s see how to verify signed data:·

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<X509CertificateHolder> certCollection 
      = certs.getMatches(signer.getSID());
    X509CertificateHolder certHolder = certCollection.iterator().next();
     
    return signer
      .verify(new JcaSimpleSignerInfoVerifierBuilder()
      .build(certHolder));
}

Again, we’ve created a CMSSignedData object based on our signed data byte array, then, we’ve retrieved all signers associated with the signatures using the getSignerInfos() method.

In this example, we’ve verified only one signer, but for generic use, it is mandatory to iterate over the collection of signers returned by the getSigners() method and check each one separately.

Finally, we’ve created a SignerInformationVerifier object using the build() method and passed it to the verify() method.

The verify() method returns true if the given object can successfully verify the signature on the signer object.

Here’s a simple example:

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

As a result:

true

5. Conclusion

In this article, we’ve discovered how to use the BouncyCastle library to perform basic cryptographic operations, such as encryption and signature.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值