Skip to end of metadata
Go to start of metadata

Security-intensive applications must avoid use of insecure or weak cryptographic primitives to protect sensitive information. The computational capacity of modern computers permits circumvention of such cryptography via brute-force attacks. For example, the Data Encryption Standard (DES) encryption algorithm is considered highly insecure; messages encrypted using DES have been decrypted by brute force within a single day by machines such as the Electronic Frontier Foundation's (EFF) Deep Crack.

Noncompliant Code Example

This noncompliant code example encrypts a String input using a weak cryptographic algorithm (DES):

SecretKey key = KeyGenerator.getInstance("DES").generateKey();
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key); 

// Encode bytes as UTF8; strToBeEncrypted contains
// the input string that is to be encrypted 
byte[] encoded = strToBeEncrypted.getBytes("UTF8");
    
// Perform encryption
byte[] encrypted = cipher.doFinal(encoded);

Noncompliant Code Example

This noncompliant code example uses the Electronic Codebook (ECB) mode of operation, which is generally insecure.

Cipher cipher = Cipher.getInstance("AES");  // defaults to ECB mode
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may be unavailable

SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();

SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);

// Encode bytes as UTF8; strToBeEncrypted contains
// the input string that is to be encrypted 
byte[] encoded = strToBeEncrypted.getBytes("UTF8");
    
// Perform encryption
byte[] encrypted = cipher.doFinal(encoded);   

Compliant Solution

This compliant solution uses the Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) to perform the encryption.  GCM has the benefit of providing authenticity (integrity) in addition to confidentiality.  GCM is available by default in Java 8, but not Java 7.  The same secret key can be used to encrypt multiple messages in GCM mode, but it is very important that a different initialization vector (IV) be used for each message.  The below encrypt_gcm method uses SecureRandom to generate a unique (with very high probability) IV for each message encrypted.  Logically, the encrypt_gcm method produces a pair of (IV, ciphertext), which the decrypt_gcm method consumes.  However, at the Java level, the encrypt_gcm method returns a single byte array that consists of the IV followed by the ciphertext, since in practice this is often easier to handle than a pair of byte arrays.

import java.util.Arrays;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;

class Msc61 {
    public static final int GCM_TAG_LENGTH = 16;
    public static final int GCM_IV_LENGTH = 12;

    public static SecretKey generateKey() {
        try {
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            kgen.init(128);
            return kgen.generateKey();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e.toString());
        }
    }

    public static byte[] encrypt_gcm(SecretKey skey, String plaintext)  {
        /* Precond: skey is valid and GCM mode is available in the JRE;
         * otherwise IllegalStateException will be thrown. */
        try {
            byte[] ciphertext = null;
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");            
            byte[] initVector = new byte[GCM_IV_LENGTH];
            (new SecureRandom()).nextBytes(initVector);
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * java.lang.Byte.SIZE, initVector);
            cipher.init(Cipher.ENCRYPT_MODE, skey, spec);
            byte[] encoded = plaintext.getBytes(java.nio.charset.StandardCharsets.UTF_8);
            ciphertext = new byte[initVector.length + cipher.getOutputSize(encoded.length)];
            for (int i=0; i < initVector.length; i++) {
                ciphertext[i] = initVector[i];
            }
            // Perform encryption
            cipher.doFinal(encoded, 0, encoded.length, ciphertext, initVector.length);
            return ciphertext;
        } catch (NoSuchPaddingException | InvalidAlgorithmParameterException | ShortBufferException |
            BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException e) 
        {
            /* None of these exceptions should be possible if precond is met. */
            throw new IllegalStateException(e.toString());
        }
    }

    public static String decrypt_gcm(SecretKey skey, byte[] ciphertext) 
        throws BadPaddingException, IllegalBlockSizeException /* these indicate corrupt or malicious ciphertext */
        /* Note that AEADBadTagException may be thrown in GCM mode; this is a subclass of BadPaddingException */
    {
        /* Precond: skey is valid and GCM mode is available in the JRE;
         * otherwise IllegalStateException will be thrown. */
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");            
            byte[] initVector = Arrays.copyOfRange(ciphertext, 0, GCM_IV_LENGTH);
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * java.lang.Byte.SIZE, initVector);
            cipher.init(Cipher.DECRYPT_MODE, skey, spec);
            byte[] plaintext = cipher.doFinal(ciphertext, GCM_IV_LENGTH, ciphertext.length - GCM_IV_LENGTH);
            return new String(plaintext);
        } catch (NoSuchPaddingException | InvalidAlgorithmParameterException |
            InvalidKeyException | NoSuchAlgorithmException e) 
        {
            /* None of these exceptions should be possible if precond is met. */
            throw new IllegalStateException(e.toString());
        }
    }
} 

Compliant Solution

This compliant solution uses the Advanced Encryption Standard (AES) algorithm in Cipher Block Chaining (CBC) mode to perform the encryption.  It uses the "AES/CBC/PKCS5Padding" transformation, which the Java documentation guarantees to be available on all conforming implementations of the Java platform.  However, CBC mode does not incorporate any authentication checks.  Therefore, a separate message authentication code (MAC) should be generated by the sender after encryption and verified by the receiver before decryption.  (Note that verifying the MAC after decryption, rather than before decryption, can introduce a "padding oracle" vulnerability.)

import java.util.Arrays;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;

class Msc61 {
    public static SecretKey generateKey() {
        try {
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            kgen.init(128);
            return kgen.generateKey();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e.toString());
        }
    }

    public static byte[] encrypt_cbc(SecretKey skey, String plaintext) {
        /* Precond: skey is valid; otherwise IllegalStateException will be thrown. */
        try {
            byte[] ciphertext = null;
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");            
            final int blockSize = cipher.getBlockSize();
            byte[] initVector = new byte[blockSize];
            (new SecureRandom()).nextBytes(initVector);
            IvParameterSpec ivSpec = new IvParameterSpec(initVector);
            cipher.init(Cipher.ENCRYPT_MODE, skey, ivSpec);
            byte[] encoded = plaintext.getBytes(java.nio.charset.StandardCharsets.UTF_8);
            ciphertext = new byte[initVector.length + cipher.getOutputSize(encoded.length)];
            for (int i=0; i < initVector.length; i++) {
                ciphertext[i] = initVector[i];
            }
            // Perform encryption
            cipher.doFinal(encoded, 0, encoded.length, ciphertext, initVector.length);
            return ciphertext;
        } catch (NoSuchPaddingException | InvalidAlgorithmParameterException | ShortBufferException |
            BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException e) 
        {
            /* None of these exceptions should be possible if precond is met. */
            throw new IllegalStateException(e.toString());
        }
    }

    public static String decrypt_cbc(SecretKey skey, byte[] ciphertext)
        throws BadPaddingException, IllegalBlockSizeException /* these indicate corrupt or malicious ciphertext */
    {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");            
            final int blockSize = cipher.getBlockSize();
            byte[] initVector = Arrays.copyOfRange(ciphertext, 0, blockSize);
            IvParameterSpec ivSpec = new IvParameterSpec(initVector);
            cipher.init(Cipher.DECRYPT_MODE, skey, ivSpec);
            byte[] plaintext = cipher.doFinal(ciphertext, blockSize, ciphertext.length - blockSize);
            return new String(plaintext);
        } catch (NoSuchPaddingException | InvalidAlgorithmParameterException |
            InvalidKeyException | NoSuchAlgorithmException e) 
        {
            /* None of these exceptions should be possible if precond is met. */
            throw new IllegalStateException(e.toString());
        }
    }
} 


Both of the above compliant solutions use 128-bit AES keys.  Longer keys (192-bit and 256-bit) may be available if the "Unlimited Strength Jurisdiction Policy" files are installed and available to the Java runtime environment.  A brute-force attack against 128-bit AES keys would take billions of years with current computational resources, so absent a cryptographic weakness in AES, 128-bit keys are likely suitable for secure encryption.

Applicability

Use of mathematically and computationally insecure cryptographic algorithms can result in the disclosure of sensitive information.

Weak cryptographic algorithms can be disabled in Java SE 7; see the Java PKI Programmer's Guide, Appendix D: Disabling Cryptographic Algorithms [Oracle 2011a].

Automated Detection

ToolVersionCheckerDescription
SonarQube
6.7
S2278

Related Guidelines

Bibliography



6 Comments

  1. I think this rule needs a list of 'insecure' cryptographic algorithms supported by Java SE. I wouldn't know DES was verboten w/o the NCCE.

    1. There's an appendix in the Java security documentation that could be referred to, I think.

  2. This recommendation should be vastly changed or scrapped.

    First of all, it uses:

    Cipher.getInstance("AES");

    Which will result in AES in ECB mode and PKCS#7 compatible padding. This is against the code rules for Android. Java 8 from Oracle will however exhibit the exact same behavior. I'd recommend GCM mode encryption as sensible default. An IV would be required as well. I'd also indicate how to possibly handle the key and IV.

    // 192 and 256 bits may be unavailable

    this is because the "Unlimited Strength Jurisdiction Policy Files" should be installed. This should be indicated in the comment rather than recommending not to use these key sizes.

    The following should absolutely not be executed:

    byte[] raw = skey.getEncoded();
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");

    This is converting an AES key to an AES key. Basically you'd break hardware token support and leave a key in possibly unprotected memory.

    byte[] encoded = strToBeEncrypted.getBytes("UTF8");

    This is OK, but nowadays I'd use StandardCharsets.UTF_8 as using that enum constant won't require you to handle the checked exception.

    "Weak cryptographic algorithms may be used in scenarios that specifically call for a breakable cipher.". This last part is a recommendation that should definitely be scrapped altogether. Java doesn't include ROT13. Toy ciphers are nice to play with, but they have no place in a securely programmed application.

    1. Thank you for your comments.  I have revised this page accordingly.

      1. Great, thank you for the quick edit! Funny that you put the previous code as non-compliant example.

        Of course I do have a few nitpicks:

        1. for GCM mode generally the IV is 12 bytes (the default) and the tag size is as large as possible, up to 16 bytes (i.e. the block size, as returned by Cipher#getBlockSize()), currently it is the other way around;
        2. ((encoded.length / blockSize) + 1) * blockSize is an interesting calculation, but I'd rather use Cipher#getOutputSize(int): int;
        3. the comment // defaults to ECB mode should of course be used for the Cipher, not for the key;
        4. you could have a look at my answer http://stackoverflow.com/a/15712409/589259 to see how to correctly handle cryptographic exceptions;
        5. I tend to use Byte.SIZE to indicate the size of bytes in bits

        Issue 1 to 3 should probably be resolved. I think 4 and certainly 5 are rather extreme nitpicks, even to my standards (wink).

        1. Thank you again.  I have revised the page to address all 5 of your points.