JCE was originally developed as an extension package to include APIs and implementations for cryptographic services that were subject to U.S. export control regulations. JCE provides a provider implementation and related set of API packages to provide support for encryption and decryption, secret key generation, and agreement and message authentication code (MAC) algorithms. The encryption and decryption support includes symmetric, asymmetric, block, and stream ciphers. JCE also provides support for secure streams and sealed objects. Initially, JCE was made available as an optional package to the JSDK versions 1.2.x and 1.3.x in accordance with U.S. regulations specific to the export of cryptography. Due to changes in the U.S. regulations related to the export of cryptography, JCE has now been integrated into J2SE 1.4. JCE facilitates the Java platform with cryptographic services and algorithms by providing implementations and interfaces for the following:
Like JCA, JCE also has the notion of provider implementations and a generic API framework for accessing JCE-supported cryptographic services and implementing related functionalities. It is also designed to provide algorithm- and implementation-independence via a standardized API framework. Figure 4-3 illustrates the architectural representation of JCE and its cryptographic services. Figure 4-3. JCE architectural model and its cryptographic services
Let's take a closer look at the JCE provider architecture, core API classes, and its programming model. JCE Cryptographic Service ProviderBecause JCE's design is based on the architectural principles of JCA, like JCA it allows for integration of Cryptographic Service Providers, which implements the JCE-defined cryptographic services from a vendor. JCE also facilitates a pluggable framework architecture that allows qualified JCE providers to be plugged in. As part of the J2SE bundle, the JCE framework provides a default provider implementation named SunJCE, which provides the following cryptographic services and algorithms.
The following services and algorithms are introduced as J2SE 5.0 security enhancements:
JCE Classes and InterfacesIn J2SE, the JCE API framework exists as part of the javax.crypto package. JCE Provider classesJCE employs JCA provider classes, particularly the java.security.Provider and java.security.Security classes that allow querying the cryptographic services offered by the provider. The type of services implemented by the provider are represented using an Engine class available as part of the javax.crypto package. JCE Engine classesLike JCA, JCE defines a set of engine classes that provide interfaces to the functionality of cryptographic services. The application interfaces of the engine class implements the SPI. Each engine API class has a corresponding SPI class for which the cryptographic service provider provides an implementation. The following JCE engine classes are available within J2SE:
Additionally, the javax.crypto.interfaces package provides interfaces for Diffie-Hellman keys, and the javax.crypto.spec provides the key and parameter specifications for the different algorithms such as DES, BlowFish, and Diffie-Hellman. Understanding the JCE API Programming ModelLet's explore the JCE API programming model and the steps involved in using important JCE API classes and mechanisms. Although the details of using specific algorithms and operations vary, the common steps are as follows:
In the next section, we will take a look at some commonly applied JCE cryptographic operations such as encryption and decryption with symmetric keys, using block and stream ciphers, password-based encryption, AES, and computing MAC digests. Encryption and DecryptionEncryption is a cryptographic technique for scrambling a message or files or programs by changing each character string, byte, or bit to another using a mathematical algorithm. A message that is not encrypted is referred to as plaintext or cleartext, and an encrypted message is called ciphertext. Decryption is the reverse process of encryption, which converts the ciphertext back into plaintext. This process generally requires a cryptographic key or code. Let's walk through the steps involved in using JCE to perform basic encryption and decryption operations. First, we will take a look at the process of generating a Data Encryption Standard (DES) key, creating and initializing a cipher object, encrypting a file, and then decrypting it.
Example 4-7 is a full code example (EncryptDecryptWithBlowFish.java) showing encryption and decryption using the Blowfish algorithm. Example 4-7. EncryptDecryptWithBlowFish.javapackage com.csp.ch4; import java.security.*; import javax.crypto.*; public class EncryptDecryptWithBlowFish { public static void main (String[] args) throws Exception { if (args.length != 1) { System.err.println ("Usage: java EncryptDecryptWithBlowFish <Enter text> "); System.exit(1); } String testData = args[0]; System.out.println("Generating a Blowfish key..."); // Create a key using "Blowfish" KeyGenerator myKeyGenerator = KeyGenerator.getInstance("Blowfish"); keyGenerator.init(128); // specifying keysize as 128 Key myKey = myKeyGenerator.generateKey(); System.out.println("Key generation Done."); // Create a cipher using the key Cipher myCipher = Cipher.getInstance("Blowfish/ECB/PKCS5Padding"); myCipher.init(Cipher.ENCRYPT_MODE, myKey); byte[] testBytes = testData.getBytes(); // Perform encryption byte[] encryptedText = cipher.doFinal(testBytes); // Printing out the encrypted data System.out.println ("Encryption Done:" + new String(encryptedText)); // Initialize the cipher for DECRYPTION mode cipher.init(Cipher.DECRYPT_MODE, myKey); // Performing decryption byte[] decryptedText = cipher.doFinal(encryptedText); // Printing out the decrypted data System.out.println("Decryption Done:" + new String(decryptedText)); } } Using Block CiphersA block cipher is a symmetric-key encryption algorithm that encrypts and decrypts a fixed-length block of data (usually 64 bits long) into a block of ciphertext of the same length. To implement block ciphers it becomes important that the data required to be encrypted must be in the multiple of the block size. To fill in the reminder block and to derive the required block size, block ciphers makes use of padding. Padding defines how to fill out the reminder of a block with random data. During decryption, the encrypted data will be retrieved to the original data and removing the padding. PKCS#5 specifies one of the popular padding schemes and it is defined as a standard by RSA. In the SunJCEProvider the PKCS#5 padding scheme is represented using PKCS5Padding. In block ciphers to encrypt bigger blocks of data, it makes use of operation "Modes." The modes define how the blocks of plaintext are encrypted to ciphertext and decrypted from ciphertext to plaintext. The SunJCEprovider provides support for commonly used modes such as ECB (Electronic Code Book), OFB (Output feedback), CFB (Cipher Feedback), CBC (Cipher-block chaining), PCBC (Propagating Cipher Block Chaining). For more information about these modes and their characteristics, refer to RSA Security Web site on Cryptography standards [RSASecurity]. Example 4-8 is a Java code fragment showing how to represent an encryption and decryption using a block cipher. Example 4-8. Encryption and Decryption using a Block cipher// Encryption using DES, ECB Mode and PKCS5Padding Scheme try { // 1. Create the cipher using DES algorithm // ECB Mode and PKCS5Padding scheme Cipher myCipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); // 2. Initialize the Cipher myCipher.init(Cipher.ENCRYPT_MODE, key); // 3. Represent the Plaintext byte[] plaintext = "Eye for an Eye makes the Whole world blind".getBytes(); // 4. Encrypt the Plaintext byte[] myciphertext = myCipher.doFinal(plaintext); // 5. Return the cipher text as String return getString( myciphertext ); } catch( Exception e ) { e.printStackTrace(); } . . . // Decryption using DES, ECB Mode and PKCS5Padding Scheme try { . . . // 1. Create the cipher using DES algorithm // ECB Mode and PKCS5Padding scheme Cipher myCipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); // 2. Get the ciphertext byte[] ciphertext = getBytes( myciphertext ); // 3. Initialize the cipher for decryption myCipher.init(Cipher.DECRYPT_MODE, key); // 4. Decrypt the ciphertext byte[] plaintext = myCipher.doFinal(ciphertext); // 5. Return the plaintext as string return new String( plaintext ); } catch( Exception ex ) { ex.printStackTrace(); } Using Stream CiphersStream ciphers are composed of I/O streams and ciphers. They provide the convenience of reading and writing from underlying InputStream and OutputStream additionally processed by the ciphers. For example, if the cipher is initialized for encryption, the CipherOutputStream will attempt to encrypt data before writing out the encrypted data, and if the cipher is initialized for decryption, the CipherInputStream will attempt to read in data and decrypt them, before returning the decrypted data. Example 4-9 is a code fragment showing reading a text file myTextFile, encrypting. the text using a stream cipher, and then writing it using CipherOutputStream to the file CipherTextFile. Example 4-9. Using stream ciphers to encrypt a fileFileInputStream inputFile = new FileInputStream(myTextFile); FileOutputStream outputFile = new FileOutputStream(cipherTextFile); CipherOutputStream cipherOutputStream = new CipherOutputStream(outputFile, myCipher); int i = 0; while (i=inputFile.read() != -1) { cipherOutputStream.write(i); } cipherOutputStream.close(); outputFile.close(); inputFile.close(); Sealed ObjectJCE introduced the notion of creating sealed objects. A Sealed object is all about encrypting a serializable object using a cipher. Sealed objects provide confidentiality and helps preventing unauthorized viewing of contents of the object by restricting de-serialization. From a programming standpoint, the sealed object creates a copy of given object by serializing the object to an embedded byte array, and then encrypt them using a cipher. To retrieve the original object, the object can be unsealed using the cipher that had been used for sealing the object. Example 4-10 shows how to create a sealed object. Example 4-10. Creating a sealed object// Creating a Sealed Object ObjectOutputStream oos = new ObjectOutputStream(mySocket.getOutputStream()); Cipher myCipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); myCipher.init(Cipher.ENCRYPT_MODE, mykey); SealedObject mySealedObject = new SealedObject(myObject, myCipher); oos.writeObject(mySealedObject); . . . // To deserialize a Sealed object and retrieve its contents ObjectInputStream ois = new ObjectInputStream(mySocket.getInputStream()); SealedObject mso = (SealedObject)ois.readObject(); Cipher mc = Cipher.getInstance("DES/ECB/PKCS5Padding"); mc.init(Cipher.DECRYPT_MODE, mykey); Object myObject = mso.getObject(mc); Password-Based Encryption (PBE)Password-Based Encryption (PBE) is a technique that derives an encryption key from a password, which helps in combating dictionary attacks by hackers and other related vulnerabilities. To use PBE, we have to use a salt (a very large random number also referred to as seed) and an iteration count, which will be specified as parameters with PBEParameterSpec. The salt and iteration count used for encryption must be the same as the ones used for decryption. Example 4-11 is a code fragment that reads a user password from a prompt, stores it in a char array, and then converts it into a SecretKey object using PBEKeySpec and SecretKeyFactory. The PBEKeySpec parameters are specified with the salt and iteration count using PBEParameterSpec. To create the PBE cipher for encryption, we use the PBEWithMD5AndDES algorithm and initialize it with the PBEKey and PBEParameterSpec. Example 4-11. Password-based encryption using PBEWithMD5AndDES algorithmPBEKeySpec pbeKeySpec; PBEParameterSpec pbeParamSpec; SecretKeyFactory keyFac; //Encryption password is obtained //via prompt ex: args[0] char[] password = args[0].toCharArray(); // Salt byte[] salt = { (byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c, (byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99 }; // Iteration count int count = 20; // Create PBE parameter set pbeParamSpec = new PBEParameterSpec(salt, count); // Collect user password as char array, and convert // it into a SecretKey object, using a PBE key // factory. pbeKeySpec = new PBEKeySpec(password); keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec); // Create PBE Cipher Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); // Initialize PBE Cipher with key and parameters pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec); // Text to encrypt String textForEncryption = "This is a test message"; byte[] myText = textForEncryption.getBytes(); // Encrypt the text using PBE Cipher byte[] ciphertext = pbeCipher.doFinal(myText); Advanced Encryption Standard (AES)AES is a new cryptographic algorithm that can be used to protect electronic data. More specifically, AES is a symmetric-key block cipher that can use keys of 128, 192, and 256 bits, and encrypts and decrypts data in blocks of 128 bits (16 bytes). AES has been approved by NIST as a standard to replace the DES algorithms. The AES algorithm is based on the Rijndael algorithm (Developed by Vincent Rijmen and Joan Daemen). AES can also encrypt data much faster than Triple-DES, and it is getting wider acceptance for encrypting data used in business applications, telecommunications, and private and federal government information. As part of the Java platform (J2SE 1.4.2 and later), the JCE provider (SunJCE) implementation provides support for the AES algorithm. The AES algorithm can be used like any other cipher such as DES or Blowfish. The programming model and steps involved are also same as those for other ciphers. Example 4-12 is a full code example showing encryption and decryption using the AES algorithm. Example 4-12. Encryption and decryption using AES algorithmpackage com.csp.ch4; import java.security.*; import javax.crypto.*; public class EncryptDecryptWithAES { public static void main (String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: java EncryptDecryptWithAES <Enter text> "); System.exit(1); } String testData = args[0]; System.out.println("Generating a AES based key..."); // Create a key using "AES" KeyGenerator myKeyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(128); // specifying keysize as 128 SecretKey myKey = myKeyGenerator.generateKey(); byte[] encodeKey = myKey.getEncoded(); SecretKeySpec myKeySpec = new SecretKeySpec(encodeKey, "AES"); System.out.println("Key generation Done."); // Create a cipher using the key Cipher myCipher = Cipher.getInstance("AES"); myCipher.init(Cipher.ENCRYPT_MODE, myKeySpec); byte[] testBytes = testData.getBytes(); // Perform encryption byte[] encryptedText = cipher.doFinal(testBytes); // Printing out the encrypted data System.out.println("Encryption Done:" + new String(encryptedText)); // Initialize the cipher for DECRYPTION mode cipher.init(Cipher.DECRYPT_MODE, myKeySpec); // Performing decryption byte[] decryptedText = cipher.doFinal(encryptedText); // Printing out the decrypted data System.out.println("Decryption Done:" + new String(decryptedText)); } } Computing Message Authentication Code (MAC) objectsMessage Authentication Code (MAC) is generally used for checking the integrity and validity of the information based on a secret key. MAC uses a secret key to generate the hash code for a sequence of specific bytes arrays. Example 4-13 is a code fragment that shows computing a MAC object using the HMAC-MD5 algorithm. Example 4-13. Computing a MAC object using HMAC-MD5 algorithmimport java.security.*; import javax.crypto.*; import javax.crypto.spec.*; import java.util.*; // This is for BASE64 encoding and decoding import sun.misc.*; //... try { // Generate a key using HMAC-MD5 KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5"); SecretKey mySecretkey = keyGen.generateKey(); // Create a MAC object and initialize Mac mac = Mac.getInstance(key.getAlgorithm()); mac.init(mySecretkey); String testString = "This is a test message for MAC digest"; // Encode the string into bytes and digest it byte[] testBytes = testString.getBytes(); byte[] macDigest = mac.doFinal(testBytes); // convert the digest into a string String digestB64 = new sun.misc.BASE64Encoder().encode(macDigest); System.out.println("Printing MAC Digest as String:" + digestB64); } catch (InvalidKeyException e) { } catch (NoSuchAlgorithmException e) { } Using Key Agreement ProtocolsA key agreement protocol is a process that allows carrying out an encrypted communication between two or more parties by securely exchanging a secret key over a network. The Diffie-Hellman (DH) key agreement protocol allows two users to exchange a secret key over an insecure medium without any prior secrets. JCE provides support for the Diffie-Hellman key agreement protocol. To create a Diffie-Hellman KeyAgreement object, instantiate a KeyAgreement object using the getInstance("DH") and then initialize it using the init() method. KeyAgreement keyAgreement = KeyAgreement.getInstance("DH"); If two or more parties are involved in a key agreement, all corresponding parties must create and initialize a KeyAgreement object. After creating and initializing the KeyAgreement object, the corresponding parties execute the different phases specific to the KeyAgreement protocol. The DHParameterSpec constructs a parameter set for Diffie-Hellman, using a prime modulus p, a base generator g, and the size in bits l, of the random exponent (private value). Example 4-14 is a code fragment showing the steps involved in using the Diffie-Hellman KeyAgreement protocol. Example 4-14. Using Diffie-Hellman Keyagreement protocoltry { //1. Use the values to generate a key pair KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH"); DHParameterSpec dhSpec = new DHParameterSpec(p, g, l); keyGen.initialize(dhSpec); KeyPair keypair = keyGen.generateKeyPair(); //2. Get the generated public and private keys PrivateKey privateKey = keypair.getPrivate(); PublicKey publicKey = keypair.getPublic(); //3. Send the public key bytes to the // other party... byte[] publicKeyBytes = publicKey.getEncoded(); //4. Retrieve the public key bytes // of the other party publicKeyBytes = ...; //5. Convert the public key bytes // into a X.509 PublicKey object X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyBytes); KeyFactory keyFact = KeyFactory.getInstance("DH"); publicKey = keyFact.generatePublic(x509KeySpec); //6. Prepare to generate the secret key // with the private key and // public key of the other party KeyAgreement ka = KeyAgreement.getInstance("DH"); ka.init(privateKey); ka.doPhase(publicKey, true); //7. Specify the type of key to generate; String algorithm = "DES"; //8. Generate the secret key SecretKey secretKey = ka.generateSecret(algorithm); //9. Use the secret key to encrypt/decrypt data; /... } catch (java.security.InvalidKeyException e) { } catch(java.security.spec.InvalidKeySpecException e) { } catch (java.security.InvalidAlgorithmParameterException e) { } catch (java.security.NoSuchAlgorithmException e) { } JCE Hardware Acceleration and Smart Card SupportWith the release of J2SE 5.0, JCE provides support for the PKCS#11 standard that allows the following:
To use these services, it is necessary to install a PKCS#11 implementation provided by the hardware accelerator and smart card vendors. As part of the J2SE 5.0 bundle, Sun facilitates a SunPKCS#11 provider. Installing PKCS#11To install the PKCS#11 provider statically, edit the Java security properties file located at <JAVA_HOME>/jre/lib/security/java.security. For example, to install the Sun PKCS#11 provider with the configuration file /opt/hwcryptocfg/pkcs11.cfg, add the following in the Java security properties file: security.provider.7=sun.security.pkcs11.SunPKCS11 \ /opt/hwcryptocfg/pkcs11.cfg To install the provider dynamically (see Example 4-15), create an instance of the provider with the appropriate configuration filename and then install it. Example 4-15. Programmatically installing a PKCS#11 providerString configName ="/opt/hwcryptocfg/pkcs11.cfg"; Provider provider = new sun.security.pkcs11.SunPKCS11(configName); Security.addProvider(provider); Using Smart Cards as Java Key StoresTo use a smart card as a keystore or trust store, set the javax.net.ssl.keyStoreType and javax.net.ssl.trustStoreType system properties to "pkcs11", and set the javax.net.ssl.keyStore and javax.net.ssl.trustStore system properties to NONE. To specify the use of a vendor smart-card provider, use the javax.net.ssl.keyStoreProvider and javax.net.ssl.trustStoreProvider system properties to identify them. (For example: "SunPKCS11-smart card"). By setting these properties, you can configure an application to use a smart-card keystore with no changes to the application that previously accessed a file-based keystore. Configuring a Smart card as a Java KeystoreThe following example shows how to configure OpenSC supported smart card as a Java keystore and list the certificates using the keytool utility. The OpenSC framework can be downloaded from http://www.opensc.org.
With the above settings, it is possible to use the smart card as a keystore and retrieve information about the certificates. For example (see Example 4-16). Using keytool to list the certificate will look like as follows. Example 4-16. Using keytool to list certificate entries from a smart card$ keytool -keystore NONE -storetype PKCS11 \ -providerName SunPKCS11-OpenSC -list -v Enter keystore password: <PIN> Keystore type: PKCS11 Keystore provider: SunPKCS11-OpenSC Your keystore contains 4 entries Alias name: Signature Entry type: keyEntry Certificate chain length: 1 Certificate[1]: Owner: SERIALNUMBER=79797900036, GIVENNAME=Nagappan Expir?e1779, SURNAME=R, CN=Nagappan (Signature), C=US Issuer: CN=Nagappan OpenSSL CA, C=BE Serial number: 1000000000102fdf39941 Valid from: Fri Apr 01 15:29:22 EST 2005 until: Wed Jun 01 15:29:22 EST 2005 Certificate fingerprints: MD5: 12:20:AC:2F:F2:F5:5E:91:0A:53:7A:4B:8A:F7:39:4F SHA1: 77:76:48:DA:EC:5E:9C:26:A2:63:A9:EC:A0:14:42:BF:90:53:0F:BC Alias name: Root Entry type: trustedCertEntry Owner: CN=Nagappan OpenSSL Root CA, C=US Issuer: CN=Nagappan OpenSSL Root CA, C=US Serial number: 11111111111111111111111111111112 Valid from: Wed Aug 13 11:00:00 EST 2003 until: Mon Jan 27 00:00:00 EST 2014 Certificate fingerprints: MD5: 5A:0F:FD:DB:4F:FC:37:D4:CD:95:17:D5:04:01:6E:73 SHA1: 6A:5F:FD:25:7E:85:DC:60:81:82:8D:D1:69:AA:30:4E:7E:37:DD:3B Alias name: Authentication Entry type: keyEntry Certificate chain length: 1 Certificate[1]: Owner: SERIALNUMBER=79797900036, GIVENNAME=Nagappan Expir?e1779, SURNAME=R, CN=NAGAPPAN, C=US Issuer: CN=Nagappan OpenSSL CA, C=US Serial number: 1000000000102fd10d2d9 Valid from: Fri Apr 01 11:21:40 EST 2005 until: Wed Jun 01 11:21:40 EST 2005 Certificate fingerprints: MD5: 29:7E:8A:5C:91:34:9B:05:52:21:4E:49:5B:45:F8:C4 SHA1: 15:B7:EA:27:E1:0E:9D:94:4E:7B:3B:79:00:48:A2:31:7E:9D:72:1A Using Keytool and Jarsigner with Smart Card TokensIf the Sun PKCS#11 provider (for using a smart card) has been configured in the java.security security properties file, then keytool and jarsigner can be used to operate on the PKCS#11 token by specifying the following options.
Here is an example of a command to list the contents of the configured PKCS#11 on a smart-card token. keytool -keystore NONE -storetype PKCS11 list The smart-card token PIN can be specified using the -storepass option. If it is not specified, then the keytool and jarsigner tool will prompt the user for the PIN. If the token has a protected authentication path (such as a dedicated PIN-pad or a biometric reader), then the -protected option must be specified, and no password options can be specified. For more information about installing PKCS#11 providers, refer to the JCE PKCS#11 documentation available at http://java.sun.com/j2se/1.5.0/docs/guide/security/p11guide.html. Strong versus Unlimited Strength CryptographyBy default, JCA allows the use of strong cryptography (128-bit key size). To use 192- and 256-bit key sizes, you are required to use unlimited strength cryptography policy files, which are available as part of a separate download of JCE. U.S. export laws restrict the export and use of JCE with unlimited strength cryptography. Those living in eligible countries may download the unlimited strength version and replace the strong cryptography jar files with the unlimited strength files. Using JCE with unlimited strength cryptography is also subject to the import control restrictions of certain countries. For more information on U.S. export laws related to cryptography, refer to The Bureau of Industry and Security's Web site (U.S. Department of Commerce) at http://www.bxa.doc.gov and the Java Cryptography Extension (JCE) Web site at http://java.sun.com/products/jce/. To summarize, the JCE implementation and API framework feature an enhancement to JCA that provides a full range of cryptographic services, including support for encryption and decryption and key agreement protocols. It also maintains interoperability with other cryptographic provider implementations. In the next section, we will explore the Java CertPath API, which defines an API framework for creating, building, and validating digital certification paths. |