Home > Articles

This chapter is from the book

7.9 Objects-Natural Security Case Study: pMa5tfEKwk59dTvC04Ft1IFQz9mEXnkfYXZwxk4ujGE=

 

You probably noticed that

  • this chapter’s last Objectives bullet,

  • Section 7.9’s title in the chapter outline and here, and

  • the last subheading in Section 7.1

all look like gibberish. These are not mistakes—they’re encrypted text. Here, we continue our objects-natural approach with a case study on encrypting and decrypting messages using objects of Java API classes provided by the

  • Java Cryptography Architecture (java.security package9) and

  • Java Cryptography Extensions (javax.crypto10 and related packages).

The Java Cryptography Architecture (JCA) defines the framework for using cryptography capabilities. The Java Cryptography Extensions enhance the JCA with advanced cryptography algorithms and other capabilities that industrial-strength applications require to securely transmit and store data.

Cryptography

Cryptography has been used for thousands of years11,12 and is critically important in today’s connected world. Every day, it’s used behind the scenes to ensure that your Internet-based communications are private and secure. For example, most websites now use the HTTPS protocol to encrypt and decrypt your web interactions, as we do at deitel.com.

Ciphers

Julius Caesar used a simple substitution cipher to encrypt military communications.13 His technique—known as the Caesar cipher14—replaces every letter in a communication (the plaintext) with the letter three ahead in the alphabet. So, A is replaced with D, B with E, C with F, …, X with A, Y with B and Z with C. Thus, the plaintext

Caesar Cipher

would be encrypted as

Fdhvdu Flskhu

The encrypted text is known as the ciphertext.

Symmetric-Key Cryptography

In this objects-natural case study, you’ll work with symmetric-key cryptography (also called private-key cryptography or secret-key cryptography) in which the message sender’s plaintext will be encrypted with a secret key to form ciphertext. The term “symmetric” comes from the fact that the receiver must use the same secret key to decode the ciphertext and restore the original plaintext. A problem with secret-key cryptography is that the security of the ciphertext is only as good as the security of the secret key, and several copies of that key are “floating around.” In a Chapter 11 objects-natural case study, we’ll discuss asymmetric-key cryptography in which different keys are used for encryption and decryption—this technique is often used to transmit the secret keys used in symmetric encryption. Asymmetric-key cryptography is much slower than symmetric key, so symmetric-key cryptography is preferred for encrypting and decrypting lengthy messages.15

Brief Intro to Advanced Encryption Standard (AES)

In this objects-natural case study, you’ll work with a simplified version of the Advanced Encryption Standard (AES)—one of the most widely used symmetric-key cryptography algorithms and the choice of the U.S. government for securing communications.16 AES supports secret keys of 128, 192 or 256 bits—the more bits, the more secure the key and the more computing power the algorithm requires to perform encryption and decryption. Depending on an application’s security requirements, you might choose keys with fewer bits for better performance.

For Demo Purposes Only: Reproducible Secret Keys

Sometimes, you may want to guarantee reproducibility of a random sequence. Later in this section, we’ll show how to do this with the SecureRandom class’s setSeed method. Reproducibility is also useful for debugging applications with random numbers.

For our encryption example, we’ll use reproducible secret keys so you can decrypt the encrypted text you saw earlier in this chapter and in this section’s title. Industrial-strength applications should use randomly generated secret keys that are not reproducible.

To make our secret keys reproducible so you can decrypt our sample ciphertext, we’ll generate secret keys using a SecureRandom random-number generator that’s seeded with the byte representation of the following arbitrary 11-character Stringyou will not seed the SecureRandom random-number generator in industrial-strength applications:

XMWUJBVYHXZ

The secret key itself will have 256 bits. To decrypt the ciphertext, you must know the secret key used to create the ciphertext.

Encrypting and Decrypting Text with AES

Class CryptographyDemo (Fig. 7.23) uses AES to encrypt and decrypt messages. Lines 3–8 import the features we use from packages javax.crypto, java.security and java.util.

 1   // Fig. 7.23: CryptographyDemo.java
 2   // Encrypting and decrypting messages with AES.
 3   import javax.crypto.Cipher;
 4   import javax.crypto.KeyGenerator;
 5   import javax.crypto.SecretKey;
 6   import java.security.SecureRandom;
 7   import java.util.Base64;
 8   import java.util.Scanner;
 9
10   public class CryptographyDemo {

Fig. 7.23 | Encrypting and decrypting messages with AES.

Method main

The main method handles the user interactions. Lines 14–15 prompt for and input the plaintext to encrypt:

11      public static void main(String[] args) {
12         var scanner = new Scanner(System.in);
13
14         System.out.println("Enter the text to encrypt:");
15         String plaintext = scanner.nextLine();
16
Enter the text to encrypt:
Welcome to encryption and decryption using AES in Java!

Lines 17–19 prompt for and input the String used to help create a reproducible key so you can decrypt the ciphertext in Section 7.9’s title—again, this is for demo purposes only:

17         System.out.println(
18            "\nEnter the secret key seed (for reproducibility):");
19         String seedString = scanner.nextLine();
20
Enter the secret key seed (for reproducibility):
XMWUJBVYHXZ

Lines 22–39 in main are enclosed in a try block because many cryptography API methods throw exceptions that you’re required to “catch or declare”—we’ll discuss this requirement in Section 10.4. Lines 41–46 catch this program’s potential exceptions and display an error message. Line 23 calls our generateKey method (lines 52–61) to obtain the secret key we’ll use to encrypt the plaintext String and, later, decrypt the ciphertext String—you’ll soon see that generateKey uses a Java API class to create the secret key:

21         try { // encryption/decryption might throw exceptions
22            // generate a reproducible AES key from the seedString
23            SecretKey secretKey = generateKey(seedString);
24            System.out.printf("%nSecretKey:%n%s%n",
25               Base64.getEncoder().encodeToString(secretKey.getEncoded()));
26
SecretKey:
8ew8jBJVSpkRtG4zorwxd34JBpUxEdOt+ec0QTfr7cE=

Secret keys and ciphertext are stored as byte arrays. When these are transmitted over networks (like the Internet) and between systems that handle the raw bytes differently, the secret keys and ciphertext can get corrupted. However, those systems typically understand lowercase letters, uppercase letters, digits and various special symbols. So, raw bytes are often transformed into character representations for transmission. One format for this is called Base64—named for the Base64 alphabet’s 64 characters, consisting of 52 letters (AZ and az), 10 digits (09) and two special symbols (+ and /), all of which are printable and easily understood by the world’s computer systems.17 Lines 24–25 convert the secret key’s byte representation to Base64 format as follows:

  • SecretKey’s getEncoded method returns a byte array representation of the key.

  • Base64’s getEncoder method returns a Base64 object that can convert a byte array to a String representation containing only Base64-alphabet characters. We then use the Base64 object’s encodeToString method to convert the byte array to a String. This enables us to display the secret key (and momentarily, the ciphertext) for demo purposes.18

Line 28 calls our encrypt method (lines 64–75) to encrypt the plaintext using the secretKey, and line 29 displays the resulting ciphertext, which our method returns as a Base64 encoded String:

27            // encrypt the plaintext
28            String ciphertext = encrypt(plaintext, secretKey);
29            System.out.printf("%nEncrypted:%n%s%n", ciphertext);
30
Encrypted:
Cz9o3YoFcX+yebfAOuMPMi+UZz2bc5jJdstfzapaNKwJldZuQH1f3QKNKrh9Zh4aB5tRbns6ZL9b
XF8Bq2YNeQ==

Lines 32–33 call method decrypt (lines 78–89) to decrypt the ciphertext using the secretKey then display the resulting plaintext, which matches the original message:

31            // decrypt the text
32            System.out.printf("%nDecrypted:%n%s%n",
33               decrypt(ciphertext, secretKey));
34
Decrypted:
Welcome to encryption and decryption using AES in Java!

Lines 36–37 prompt for and input existing ciphertext, so you can decrypt this section’s title. For your convenience, we placed that text in the file encrypted.txt in this example’s folder so you can copy and paste it into the program. Lines 38–39 call our decrypt method to decrypt that ciphertext using the secretKey then display the resulting plaintext for the encrypted part of Section 7.9’s title.

35            // decrypt ciphertext entered by the user
36            System.out.println("\nEnter the ciphertext to decipher:");
37            ciphertext = scanner.nextLine();
38            System.out.printf("%nDecrypted:%n%s%n",
39               decrypt(ciphertext, secretKey));
40         }
41         catch (Exception ex) {
42            System.out.printf("""
43               An exception occurred during encryption/decryption:
44               %s%n
45               """, ex);
46         }
47      }
48
Enter the ciphertext to decipher:
pMa5tfEKwk59dTvC04Ft1IFQz9mEXnkfYXZwxk4ujGE=

Decrypted:
Symmetric-Key Cryptography

Method generateKey

The generateKey method (lines 52–61) generates a 256-bit AES SecretKey using its argument to seed a SecureRandom generator for reproducibility. This allows our example to generate the same SecretKey from the same seed. This is useful for testing and demonstration purposes and will enable you to decrypt the ciphertext in this section’s title. SecretKeys for industrial-strength encryption should be randomly generated so they are not reproducible. The clause “throws Exception” after main’s parameter list and before its body is required for this example to compile. Many encryption and decryption methods can cause exceptions the Java compiler requires us to handle. We’ll explain the “throws Exception” clause in detail in Section 10.3.

49      // Generate and return a SecretKey. Note: We are seeding a
50      // SecureRandom object for reproducibility so you can decipher this
51      // chapter's encrypted text. Don't do this in production code.
52      public static SecretKey generateKey(String seed) throws Exception {
53         // get a secure random number generator
54         var random = SecureRandom.getInstance("SHA1PRNG");
55         random.setSeed(seed.getBytes()); // for demo reproducibility
56
57         // get an AES SecretKey generator
58         var keyGen = KeyGenerator.getInstance("AES");
59         keyGen.init(256, random); // set up for 256-bit keys
60         return keyGen.generateKey(); // generate and return new SecretKey
61      }
62

The generateKey method operates as follows:

  • Line 54 gets a SecureRandom object configured to use the "SHA1PRNG" random-number generation algorithm, which can be seeded for reproducibility.

  • Line 55 seeds the generator with seed String’s byte array representation (returned by String method getBytes). Seeding ensures the same random values are generated each time we use the same seed String, making the SecretKey generation reproducible, which we do only for demo purposes.

  • Line 58 uses the KeyGenerator static method getInstance to get a KeyGenerator configured for generating AES secret keys.

  • Line 59 calls KeyGenerator method init with the AES key size and the seeded SecureRandom instance random—256 bits produces the most secure AES keys. For better security, use the one-argument init method that receives only the key size, or provide as init’s second argument a fully randomized (i.e., not seeded) SecureRandom object so the KeyGenerator can generate truly random keys.

  • Finally, line 60 calls the KeyGenerator method generateKey to get and return a SecretKey for encrypting and decrypting messages.

Method encrypt

The encrypt method (lines 64–75) receives the plaintext to encrypt and a SecretKey, performs AES encryption and returns a Base64 encoded version of the ciphertext. Again, the clause “throws Exception” is required for the code to compile.

63      // encrypt a String using AES
64      public static String encrypt(
65         String plaintext, SecretKey secretKey) throws Exception {
66
67         var cipher = Cipher.getInstance("AES"); // get a Cipher object
68         cipher.init(Cipher.ENCRYPT_MODE, secretKey); // configure to encrypt
69
70         // encrypt the byte[] representation of plaintext
71         byte[] encrypted = cipher.doFinal(plaintext.getBytes());
72
73         // Base64 encode encrypted as a ciphertext String and return
74         return Base64.getEncoder().encodeToString(encrypted);
75      }
76

Method encrypt operates as follows:

  • Line 67 calls the Cipher19 class static getInstance method to get a Cipher object we can use to encrypt plaintext or decrypt ciphertext. This Cipher object will use the AES algorithm.

  • Line 68 initializes the Cipher object for encryption (Cipher.ENCRYPT_MODE), using the secretKey.

  • Line 71 gets the plaintext’s byte array representation and passes it to the Cipher object’s doFinal method, which encrypts the data and returns it as a byte array.

  • Line 74 Base64 encodes encrypted as a String and returns the ciphertext.

Method decrypt

The decrypt method (lines 78–89) receives the Base64-encoded ciphertext to decrypt and the SecretKey used to encrypt the data, then performs AES decryption and returns the plaintext String. Again, the clause “throws Exception” is required for the code to compile.

77      // decrypt an AES-encrypted String
78      public static String decrypt(
79         String ciphertext, SecretKey secretKey) throws Exception {
80
81         var cipher = Cipher.getInstance("AES"); // get a Cipher object
82         cipher.init(Cipher.DECRYPT_MODE, secretKey); // configure to decrypt
83
84         // Base64 decode ciphertext to byte array
85         byte[] ciphertextBytes = Base64.getDecoder().decode(ciphertext);
86
87         // create plaintext String from ciphertextBytes and return it
88         return new String(cipher.doFinal(ciphertextBytes));
89      }
90   }

Method decrypt operates as follows:

  • As in the encrypt method, line 81 calls the Cipher class’s static getInstance method to get a Cipher that we’ll use for decryption with "AES".

  • Line 82 initializes the Cipher object for decryption (Cipher.DECRYPT_MODE) using the secretKey.

  • Line 85 decodes the Base64-encoded ciphertext String into the byte array ciphertextBytes. First, we call Base64’s getDecoder method to get an object that can convert a Base64-encoded String to a byte array. Then, we call that object’s decode method to perform the conversion.

  • To decrypt the bytes in ciphertextBytes, line 88 calls Cipher’s doFinal method, passing ciphertextBytes. The doFinal method returns an array of decrypted bytes.

  • Line 88 creates a new String from the byte array and returns the original plaintext.

Weakness in Secret-Key Cryptography—and a Look to Public-Key Cryptography

Secret-key cryptography has a weakness—the ciphertext is only as secure as the secret key. Ciphertext can be decrypted by anyone who discovers or steals the secret key. In a Chapter 11 objects-natural case study, we’ll complete our introduction to Java cryptography with a discussion of public-key cryptography. This technique performs encryption with a public key known to every sender who may want to send a secret message to a particular receiver. The public key can be used to encrypt messages but not decrypt them. The messages can be decrypted only with a paired private key known only to the receiver. We’ll use public-key cryptography to securely transmit the secret keys used in secret-key cryptography.

A Note about Cryptography and Computing Power

Ideally, ciphertext should be impossible to “break”—that is, it should be impossible to determine the plaintext from the ciphertext without the decryption key. For various reasons, that goal is not realizable. So, designers of cryptography schemes settle for making them extraordinarily difficult to break. Unfortunately, today’s increasingly powerful computers (with quantum computers on the horizon) are making it possible to break many of the encryption schemes the world has used confidently over the last few decades.20,21 Cryptography is at the root of the blockchain technologies that implement cryptocurrencies such as Bitcoin.22 The phenomenally powerful computers that quantum computing will enable are putting cryptography schemes and cryptocurrencies at risk.23,24 The cryptocurrency community is working on these challenges.25,26,27

Generative AI

1 Prompt genAIs to explain why developers don’t automatically use 256-bit keys.

2 Prompt genAIs to assess the risks posed to today’s cryptography schemes by emerging quantum computers. Ask them to explain what is being done in the cryptography community today to meet those challenges.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.