Why is ECB cipher mode not recommended for encryption?

0 votes
asked Dec 22, 2022 by rich-c-2789 (16,120 points)
I noticed in some Java examples they use "AES", "AES/ECB/NoPadding", "AES/CBC/PKCS5Padding", "AES/GCM/NoPadding", and others. In this tutorial:

for ECB mode it states: "it's not recommended for encryption".  I also noticed this statement in the java 11 docs: "Electronic Codebook Mode, as defined in FIPS PUB 81 (generally this mode should not be used for multiple blocks of data)." I am confused!  It seems that AES has different modes that should be avoided. Why is ECB mode not recommended for AES encryption?

4 Answers

0 votes
Best answer

First, to choose the right encryption, it is important to understand the data to be encrypted.  Factors like: performance, is data stored behind a firewall, size, how many records, the life of the data (is it purged after 14 days), does the data contain repeating or predicatable elements etc.  In my own performance comparisons AES encryption with ECB mode outperformed GCM mode.  Below are the results for encrypting then decrypting the same HL7 message containing 668 characters 100,000 times:

ECB took 1152ms about a second

GCM took 23407362ms OUCH!  WHAT!  6.5 bleepen hours!

The above comparison wasn't a fair comparison since the SecretKey generation in the ECB implementation occured outside of the timed code.  This accounts for the bulk of the difference.  There were also other differences between the two above implementations.  For example ECB does not require an Initialzation Vector where GCM does, which was prepended to the encrypted bytes making the GCM implemention propritary.  If we change the GCM implementation by moving the Secret Key generation outside of the timed code, and reuse it, the results are closer:

ECB took 1128ms

GCM took 2054ms

But making this change weakens the GCM implementation. Looking closer we find that the bulk of the time in the original GCM implementation was due to this line used to generate a unique key for each iteration:

KeySpec spec = new PBEKeySpec(new String(password).toCharArray(), salt, 65536, 256); 

The third parameter above is the Iteration Count (described here).  Dropping this from 65536 to 1 in the original implementation gives the following results:

ECB took 1363ms

GCM took 4461ms

For a high transaction system GCM could potentially be a bottle neck depending on the implementation details.  One might then decide to use another strategy.  If the HL7 message can be broken down into individual columns, a system might take a more selective approach and encrypt only the sensitive bits.  Testing with a shorter message, "Hello World", still shows that our ECB implementation is faster.  

ECB took 1097ms

GCM took 3675ms

Second, a coworker pointed out, the image based explanation that follows may oversimplify the issues when encrypting text.  

Using ECB to encrypt "Hello World" results in "0� � �,�̡m�%".  Using another key results in "�Bi
"ds�hK��"�".  Both produce results that appear more opaque than the image based examples below.  Both would present an obstacle for all but the most technical prying eyes.  In fact I would dare bet a $50 amazon gift card for the first person that could tell me what the original text for this "��K/C�G����L:��A��@�H|!:��@J��" is within 90 days of this posted date 12/28/2022.

That said...

The best explanation for not using ECB mode can be explained best visually.

The above image was sourced from this article:

The above Wikipedia article explains:

"The simplest (and not to be used anymore) of the encryption modes is the electronic codebook (ECB) mode...The disadvantage of this method is a lack of diffusion. Because ECB encrypts identical plaintext blocks into identical ciphertext blocks, it does not hide data patterns well. ECB is not recommended for use in cryptographic protocols."

Another visual example (using different keys):

The above image was sourced from this article and describes the procedure used to create the images.

As we can see the penguin is not well hidden in the encrypted versions. In the case of encrypted text, that doesn't have the same visual cues, it is still an issue.  A real-world example of this is the Adobe data breach in 2013.  Where ECB mode was used to encrypt passwords.  In this case textual cues could be used to decrypt the passwords.  For additional details see this article regarding the Adobe breach and how the passwords could be recovered:

The problems with ECB apply to AES encryption, DES encryption, etc.  To avoid using ECB one needs to consider the defaults.  For example, it is legal to obtain an AES encryption Cipher instance like this:

var cipher = javax.crypto.Cipher.getInstance("AES");

However, Java will default the mode and padding. The mode defaults to "ECB" in this case. To avoid ECB it needs to be more specific like this:

var cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding");

To see what modes are available in Java see the Java docs for the version of Java used.  For Java 11 see the "Cipher Algorithm Modes" table in this link:

Quotes from other sources:

"ECB Electronic Codebook Mode, as defined in FIPS PUB 81 (generally this mode should not be used for multiple blocks of data)." Sourced here

"'s not recommended for encryption." Sourced here.

"The simplest (and not to be used anymore) of the encryption modes is the electronic codebook (ECB) mode..." Sourced here.

"ECB - Don't use it. There are a very small number of situations where ECB is the correct choice, but unless you're a cryptographer you'll almost never run into them." Sourced here

See also:

What cipher modes are recommended for encryption?

How can I use "AES/GCM/NoPadding" encryption/cipher mode in QIE?

answered Dec 22, 2022 by rich-c-2789 (16,120 points)
selected Jan 2, 2023 by rich-c-2789
0 votes

This should be a comment rather than an answer.  but...

After creating the content in the original question, I had a conversation with a coworker in which we discussed that it would be interesting to see an example of enrypting/decrypting a Linux penguin rendered as ASCII art.  Being to lazy to generate such an example myself...I asked Chat GPT this question:

Can you write a Java example to create a ASCII art version of the Linux Penguin?

The initial response was just java main with a System.out.println containing the ASCII version on the Linux penguin.  I asked a few following up questions such as:

Can you encrypt the low resolution version with AES using ECB mode and display the result?

Can you change it so that it also prints the result as a UTF-8 string?

The generated code:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class LinuxPenguin {
    public static void main(String[] args) throws Exception {
        // ASCII art of the Linux Penguin
        String penguin = "                            .--.\n" +
                "                           |o_o |\n" +
                "                           |:_/ |\n" +
                "                          //   \\ \\\n" +
                "                         (|     | )\n" +
                "                        /'\\_   _/`\\\n" +
                "                        \\___)=(___/";

        // Set the key and initialization vector
        String key = "your-secret-key1"; // rich-c: changed "your-secret-key" to "your-secret-key1" due to Invalid AES key length: 15 bytes.  Needs 16bytes or 128 bits
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");

        // Create the cipher and initialize it for encrypting
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        // Encrypt the ASCII art
        byte[] encryptedPenguin = cipher.doFinal(penguin.getBytes());

        // Encode the encrypted ASCII art as a base64 string
        String base64EncryptedPenguin = Base64.getEncoder().encodeToString(encryptedPenguin);

        // Decode the encrypted ASCII art from a base64 string to a UTF-8 string
        String utf8EncryptedPenguin = new String(encryptedPenguin, "UTF-8");

        // Print the encrypted ASCII art as both a base64 string and a UTF-8 string
        System.out.println("Base64 Encrypted: \n" + base64EncryptedPenguin); // rich-c: Added "\n"
        System.out.println("UTF-8 Encrypted: \n" + utf8EncryptedPenguin); // rich-c: Added "\n"

Note: I changed three lines that I commented with "rich-c:".  Ideally the key should be more secure but here is the results after running it:

Base64 Encrypted: 
UTF-8 Encrypted: 
��GA��]B?�cn�xZ� ���sq�l:�H������r�߽�u�x�oxZ� ���sq�l:�H��H"s�q�    ����)�xZ� ���sq�l:�H���G����<Rk���/�w����N7�N�t%�ݡ�

So, even though ECB is easier to hack than other cipher modes it isn't easy for a human to see the penguin in this example.

See also this question where I collaborated with Chat GPT. 

In collaboration with Chat GPT, Using Java, what is the recommended AES cipher mode to use today?

And again out of caution: 

The author generated this text in part with GPT-3, OpenAI’s large-scale language-generation model. Upon generating draft language, the author reviewed, edited, and revised the language to their own liking and takes ultimate responsibility for the content of this publication.

answered Jan 2, 2023 by rich-c-2789 (16,120 points)
0 votes

Again this should be a comment but...

Here is another Chat GPT related conversation.  I asked Chat GPT:

Can you write a Java example that compares the performance of using AES encryption with the ECB mode vs using GCM mode?

Which generated this code that ran without modifications:

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;

public class AESPerformanceComparison {
    public static void main(String[] args) throws Exception {
        // Generate a random key
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        SecretKey key = keyGenerator.generateKey();

        // Generate a random initialization vector (IV)
        byte[] iv = new byte[12];
        SecureRandom random = new SecureRandom();

        // Set up the GCM parameters
        GCMParameterSpec gcmParams = new GCMParameterSpec(128, iv);

        // Set up the cipher for ECB mode
        Cipher cipherECB = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipherECB.init(Cipher.ENCRYPT_MODE, key);

        // Set up the cipher for GCM mode
        Cipher cipherGCM = Cipher.getInstance("AES/GCM/NoPadding");
        cipherGCM.init(Cipher.ENCRYPT_MODE, key, gcmParams);

        // Generate some random data to encrypt
        byte[] data = new byte[1024];

        // Measure the time required to encrypt the data using ECB mode
        long startTimeECB = System.nanoTime();
        long endTimeECB = System.nanoTime();

        // Measure the time required to encrypt the data using GCM mode
        long startTimeGCM = System.nanoTime();
        long endTimeGCM = System.nanoTime();

        // Calculate the encryption time for each mode
        long encryptionTimeECB = endTimeECB - startTimeECB;
        long encryptionTimeGCM = endTimeGCM - startTimeGCM;

        // Print the encryption time for each mode
        System.out.println("ECB mode encryption time: " + encryptionTimeECB + " nanoseconds");
        System.out.println("GCM mode encryption time: " + encryptionTimeGCM + " nanoseconds");

And resulted in:

ECB mode encryption time: 1003875 nanoseconds
GCM mode encryption time: 1433208 nanoseconds

This would have saved me some time but I did learn more about encryption going through the same exercise myself.  I must admit though, I was a bit sensitive to posting my own code in my original answer.  The fact that these answers have an 8000 character limit was merely an excess not to share it.

answered Jan 2, 2023 by rich-c-2789 (16,120 points)
0 votes

Another related question posed to Chat GPT:

I asked: 

What Cipher mode and padding will Java use by default when obtaining the Cipher like this: Cipher.getInstance("AES");?

Chat GPT response:

In Java, when you call Cipher.getInstance("AES"), the default mode of operation and padding scheme for the Cipher object will depend on the provider being used.

By default, the Cipher class uses the default security provider on the system, which is typically the "SUN" provider. If you are using the "SUN" provider and call Cipher.getInstance("AES"), the default mode of operation will be Electronic Codebook (ECB) mode and the default padding scheme will be PKCS#5 padding.

For example, the following code will create a Cipher object using the default provider and the default mode and padding:

Cipher cipher = Cipher.getInstance("AES");

If you want to use a different mode of operation or padding scheme, you can specify it as part of the transformation string passed to Cipher.getInstance(). For example, to use Cipher Block Chaining (CBC) mode with PKCS#5 padding, you can use the following code:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

It is important to note that the default mode of operation and padding scheme may vary depending on the provider being used. It is generally recommended to explicitly specify the desired mode and padding scheme in order to ensure that the desired security properties are being used.

answered Jan 2, 2023 by rich-c-2789 (16,120 points)