From 49cbbf691d473615821b09c8be8b8d2e49139498 Mon Sep 17 00:00:00 2001 From: olivier Date: Fri, 24 Oct 2025 16:51:37 -0700 Subject: [PATCH] =?UTF-8?q?Classes=20utilitaire=20qui=20fournit=20des=20fo?= =?UTF-8?q?nctionnalit=C3=A9s=20cryptographiques=20pour=20le=20chiffrement?= =?UTF-8?q?=20et=20le=20d=C3=A9chiffrement=20sym=C3=A9triques=20(AES)=20et?= =?UTF-8?q?=20asym=C3=A9triques=20(RSA).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/common/CryptoUtils.java | 267 ++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 src/main/java/common/CryptoUtils.java diff --git a/src/main/java/common/CryptoUtils.java b/src/main/java/common/CryptoUtils.java new file mode 100644 index 0000000..f4353da --- /dev/null +++ b/src/main/java/common/CryptoUtils.java @@ -0,0 +1,267 @@ +package common; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.Base64; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static javax.crypto.Cipher.DECRYPT_MODE; +import static javax.crypto.Cipher.ENCRYPT_MODE; + +/** + * Static utility methods to support encryption and decryption. ePub uses only AES and RSA. + * RC4 may be needed in the future to support encrypted PDF files. + */ +@SuppressWarnings("UnnecessaryLocalVariable") +public class CryptoUtils +{ + public enum CRYPTO_ALGO + { + ALGO_AES, ALGO_RC4, ALGO_RSA, ALGO_MD5, ALGO_SHA1, ALGO_SHA256 + } + + public enum CHAINING_MODE + { + CHAIN_ECB, CHAIN_CBC + } + + enum RSAKeyType + { + RSA_KEY_PKCS12, RSA_KEY_X509, RSA_KEY_PKCS8, RSA_KEY_UNKNOWN + } + + public static int SHA1_LEN = 20; + public static int RSA_KEY_SIZE = 128; // NOTE: this is less than the standard key size of 256 bytes + + // static because the methods in this class are all static + private static final Logger LOGGER = Logger.getLogger( CryptoUtils.class.getName() ); + + private CryptoUtils() {} // prevents instantiation + + /** + * Encrypts or Decrypts data using a symmetric key + * + * @param algo Algorithm to use + * @param chain Chaining mode + * @param key Key + * @param initialzationVector IV key - not used for rc4 + * @param inData Data to encrypt + + * @return The encrypted data + */ + public static byte[] crypt( CRYPTO_ALGO algo, CHAINING_MODE chain, int cryptMode, + byte[] key, byte[] initialzationVector, byte[] inData ) + { + try + { + Cipher cipher; + /* + This class specifies a secret key in a provider-independent fashion. + It can be used to construct a SecretKey from a byte array, without having to go through a + (provider-based) SecretKeyFactory. + This class is only useful for raw secret keys that can be represented as a byte array and + have no key parameters associated with them, e.g., DES or Triple DES keys. + */ + SecretKeySpec secretKeySpec; + if (algo == CRYPTO_ALGO.ALGO_AES && chain == CHAINING_MODE.CHAIN_CBC) + { + cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" ); + secretKeySpec = new SecretKeySpec( key, "AES" ); + cipher.init( cryptMode, secretKeySpec, + new IvParameterSpec( initialzationVector ) ); + } + else if (algo == CRYPTO_ALGO.ALGO_RC4 && chain == CHAINING_MODE.CHAIN_ECB) + { + cipher = Cipher.getInstance( "RC4" ); // No padding for RC4 + secretKeySpec = new SecretKeySpec( key, "RC4" ); + cipher.init( cryptMode, secretKeySpec ); + } + else + { + throw new NoSuchAlgorithmException( "Unsupported algorithm or chain: " + algo + "/" + chain ); + } + /* + Encrypts or decrypts the data depending on the value of cryptMode. + Acts in a single-part operation, or finishes a multiple-part operation. + */ + return cipher.doFinal( inData ); + } + catch (Exception e) + { + LOGGER.log( Level.SEVERE, "Encryption failed: " + e.getMessage(), e ); + throw new GourouException( "Encryption failed", e ); + } + } + + /** + * Decrypts data using a symmetric key and the AES algorithm + * @param key 16 byte aes encryption key + * @param initializationVector a 16 byte initialization vector, usually prepended to the actual encrypted data + * @param encryptedData The encrypted data to decrypt. Does not include the initialization vector. + * @return The decrypted data + * @throws GeneralSecurityException If any part of the decryption fails + */ + public static byte[] aesDecrypt( byte[] key, byte[] initializationVector, byte[] encryptedData ) + throws GeneralSecurityException + { + Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" ); + SecretKeySpec secretKey = new SecretKeySpec( key, "AES" ); + cipher.init( DECRYPT_MODE, secretKey, new IvParameterSpec( initializationVector ) ); + return cipher.doFinal( encryptedData ); + } + + /** + * Encrypts data using the AES algorithm. It uses the random initialization vector + * created by default in cipher. + * + * @param key 16 byte aes encryption key + * @param unencryptedData The data to encrypt + * @return The encrypted data preceded by the 16 byte initialization vector + * @throws GeneralSecurityException If any part of the encryption fails + */ + public static byte[] aesEncrypt( byte[] key, byte[] unencryptedData ) + throws GeneralSecurityException + { + Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" ); + SecretKeySpec secretKey = new SecretKeySpec( key, "AES" ); + + cipher.init( ENCRYPT_MODE, secretKey ); + byte[] encryptedData = cipher.doFinal( unencryptedData ); + byte[] iv = cipher.getIV(); + byte[] finalEncrypteData = new byte[16 + encryptedData.length]; + System.arraycopy( iv, 0, finalEncrypteData, 0, 16 ); + System.arraycopy( encryptedData, 0, finalEncrypteData, 16, encryptedData.length ); + return finalEncrypteData; + } + + /** + * Encrypts data using an RSA key. + * + * @param rsaKey The RSA key bytes. May be public or private + * @param data The data to encrypt. + * @return The encrypted data. + * + * @throws GeneralSecurityException If any part of the encryption fails + */ + public static byte[] rsaEncrypt( Key rsaKey, byte[] data ) + throws GeneralSecurityException + { + Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding" ); + cipher.init( ENCRYPT_MODE, rsaKey ); + byte[] enc = cipher.doFinal( data ); + return enc; + } + + /* * + * Decrypts data using an RSA key. + * + * @param rsaKey The RSA Key bytes. + * @param data The data to decrypt. + * + * @return The decrypted data. + */ +// public static byte[] rsaDecrypt( Key rsaKey, byte[] data ) +// throws GeneralSecurityException +// { +// Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding" ); +// cipher.init( Cipher.DECRYPT_MODE, rsaKey ); +// byte[] decrytped = cipher.doFinal( data ); +// return decrytped; +// } + + + + /** + * Encrypts or decrypts data using the RSA algorithm and the public key embedded + * in an X509 Certificate. Useful for creating or verifying digital signatures. + * + * @param x509Cert a base64 representation of an X509 certificate + * @param cryptMode flag for encryption or decryption + * @param data The data to treat + * + * @return The converted data + * + * @throws GeneralSecurityException if any part of the encryption or decryption fails. + */ + public static byte[] x509Crypt( String x509Cert, int cryptMode, byte[] data ) + throws GeneralSecurityException + { + Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding" ); + PublicKey pk = extractPKFromCert( x509Cert ); + cipher.init( cryptMode, pk ); + return cipher.doFinal( data ); + } + + /** + * Create a digital signature for arbitrary data, using a PKCS#12 private key. + * + * @param data data to hash using the SHA1 algorithm, and then RSA encrypt + * @param pkcs12 a base 64 representation of the PKCS#12 certificate + * @param alias the subject name associated with the key. It is assumed + * that it is stored without a password + * @param key a 16 byte key to unlock the private key. + * @return a hash of the data encrypted with the private key from the PKCS#12 entry + * + * @throws GeneralSecurityException if any part of the signing process fails. + * @throws IOException if the PKCS#12 data cannot be loaded + */ + + public static byte[] signData( byte[] data, String pkcs12, String alias, byte[] key ) + throws GeneralSecurityException, IOException + { + MessageDigest shaMd = MessageDigest.getInstance( "SHA1" ); + byte[] shaOut = shaMd.digest( data ); + + Base64.Decoder decoder = Base64.getDecoder(); + byte[] pkcs12Data = decoder.decode( pkcs12 ); + KeyStore keyStore = // KeyStore.getInstance( "PKCS12" ); + extractPkcsKeystore( pkcs12Data, null ); + + String dk = Base64.getEncoder().encodeToString( key ); + char[] keyPassword = dk.toCharArray(); + + // this keystore contains a private key entry, but may not have a public key certificate! + RSAPrivateKey rsaKey = (RSAPrivateKey) keyStore.getKey( alias, keyPassword ); + + byte[] encryptedHash = CryptoUtils.rsaEncrypt( rsaKey, shaOut ); + return encryptedHash; + } + + static public PublicKey extractPKFromCert( String authenticationCertificate ) + throws CertificateException + { + byte[] certBytes = Base64.getDecoder().decode( authenticationCertificate ); + CertificateFactory cf = CertificateFactory.getInstance( "X.509" ); + X509Certificate x509AuthCertificate = (X509Certificate) cf.generateCertificate( + new ByteArrayInputStream( certBytes ) ); + PublicKey pk = x509AuthCertificate.getPublicKey(); + return pk; + } + + /** + * + * @param pkcs12Data The pkcs12 data as a byte array, usually decoded from base64 + * @param password The password protecting the pkcs12 data; may be empty + * @return A java KeyStore object initialized with a PKCS12 key pair. + */ + public static KeyStore extractPkcsKeystore( byte[] pkcs12Data, char[] password ) + throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException + { + KeyStore keyStore; + ByteArrayInputStream inputStream = new ByteArrayInputStream( pkcs12Data) ; + keyStore = KeyStore.getInstance( "PKCS12" ); + // Load the keystore from the input stream and password + keyStore.load( inputStream, password ); + return keyStore; + } +} +