Introduction à BouncyCastle avec Java

1. Vue d'ensemble

BouncyCastle est une bibliothèque Java qui complète l'extension cryptographique Java (JCE) par défaut.

Dans cet article d'introduction, nous allons montrer comment utiliser BouncyCastle pour effectuer des opérations cryptographiques, telles que le cryptage et la signature.

2. Configuration Maven

Avant de commencer à travailler avec la bibliothèque, nous devons ajouter les dépendances requises à notre fichier pom.xml :

 org.bouncycastle bcpkix-jdk15on 1.58 

Notez que nous pouvons toujours rechercher les dernières versions des dépendances dans le référentiel central Maven.

3. Configurer les fichiers de stratégie de juridiction de force illimitée

L'installation standard de Java est limitée en termes de puissance pour les fonctions cryptographiques, ceci est dû à des politiques interdisant l'utilisation d'une clé dont la taille dépasse certaines valeurs par exemple 128 pour AES.

Pour surmonter cette limitation, nous devons configurer les fichiers de stratégie de juridiction de force illimitée .

Pour ce faire, nous devons d'abord télécharger le package en suivant ce lien. Ensuite, nous devons extraire le fichier compressé dans un répertoire de notre choix - qui contient deux fichiers jar:

  • local_policy.jar
  • US_export_policy.jar

Enfin, nous devons rechercher le dossier {JAVA_HOME} / lib / security et remplacer les fichiers de politique existants par ceux que nous avons extraits ici.

Notez que dans Java 9, nous n'avons plus besoin de télécharger le package des fichiers de stratégie , il suffit de définir la propriété crypto.policy sur unlimited :

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

Une fois cela fait, nous devons vérifier que la configuration fonctionne correctement:

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

Par conséquent:

Max Key Size for AES : 2147483647

Sur la base de la taille de clé maximale renvoyée par la méthode getMaxAllowedKeyLength () , nous pouvons affirmer en toute sécurité que les fichiers de stratégie de force illimitée ont été installés correctement.

Si la valeur renvoyée est égale à 128, nous devons nous assurer que nous avons installé les fichiers dans la JVM où nous exécutons le code.

4. Opérations cryptographiques

4.1. Préparation du certificat et de la clé privée

Avant de nous lancer dans l'implémentation des fonctions cryptographiques, nous devons d'abord créer un certificat et une clé privée.

À des fins de test, nous pouvons utiliser ces ressources:

  • Baeldung.cer
  • Baeldung.p12 (mot de passe = «mot de passe»)

Baeldung.cer est un certificat numérique qui utilise la norme internationale d'infrastructure à clé publique X.509, tandis que Baeldung.p12 est un keystore PKCS12 protégé par mot de passe qui contient une clé privée.

Voyons comment ceux-ci peuvent être chargés en 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);

Tout d'abord, nous avons ajouté le BouncyCastleProvider en tant que fournisseur de sécurité de manière dynamique à l'aide de la méthode addProvider () .

Cela peut également être fait de manière statique en éditant le fichier {JAVA_HOME} /jre/lib/security/java.security , et en ajoutant cette ligne:

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

Une fois le fournisseur correctement installé, nous avons créé un objet CertificateFactory à l'aide de la méthode getInstance () .

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

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 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.

Enfin, nous avons créé un objet SignerInformationVerifier à l'aide de la méthode build () et l' avons passé à la méthode verify () .

La méthode verify () renvoie true si l'objet donné peut vérifier avec succès la signature sur l'objet signataire.

Voici un exemple simple:

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

Par conséquent:

true

5. Conclusion

Dans cet article, nous avons découvert comment utiliser la bibliothèque BouncyCastle pour effectuer des opérations cryptographiques de base, telles que le cryptage et la signature.

Dans une situation réelle, nous souhaitons souvent signer puis crypter nos données, de cette manière, seul le destinataire est capable de les décrypter à l'aide de la clé privée, et de vérifier son authenticité sur la base de la signature numérique.

Les extraits de code peuvent être trouvés, comme toujours, sur GitHub.