Java 11 – ChaCha20 encryption examples

In this article, we will show you how to encrypt and decrypt a message with the ChaCha20 stream cipher, defined in RFC 7539.

P.S ChaCha20 stream cipher is available at Java 11, refer to JEP 329.

Note
You may interest at this ChaCha20-Poly1305 encryption examples

1. How it works?

1.1 The inputs to ChaCha20 encryption and decryption:

  • A 256-bit secret key (32 bytes)
  • A 96-bit nonce (12 bytes)
  • A 32-bit initial count (4 bytes)

1.2 ChaCha20 Encryption.


 (plain text) + (secrect key | nonce | initial count) -> `ChaCha20` -> ciphertext (encrypted text).

1.2 ChaCha20 Decryption.


ciphertext + (secrect key | nonce | initial count) -> `ChaCha20` -> plain text.

1.3 The ChaCha20 encryption uses the key and IV (initialization value, nonce + initial count) to encrypt the plaintext into a ciphertext of equal length.

1.4 The nonce and secret key must be unique for each encryption. The nonce and initial count are ok to be publicly known, but the secret key must be private and keep it confidential.

ChaCha20 Java Implementation
Download the JDK source code and find this class ChaCha20Cipher for ChaCha20 algorithm implementation.

ChaCha20Cipher.java

package com.sun.crypto.provider;

/**
 * Implementation of the ChaCha20 cipher, as described in RFC 7539.
 *
 * @since 11
 */
abstract class ChaCha20Cipher extends CipherSpi {
  //...
}

2. ChaCha20 Encryption and Decryption.

2.1 A Java example to encrypt and decrypt a message with the ChaCha20 algorithm.

ChaCha20.java

package com.mkyong.java11.jep329.chacha20;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.ChaCha20ParameterSpec;

/*
The inputs to ChaCha20 encryption, specified by RFC 7539, are:
 - A 256-bit secret key (32 bytes)
 - A 96-bit nonce (12 bytes)
 - A 32-bit initial count (4 bytes)
*/
public class ChaCha20 {

    private static final String ENCRYPT_ALGO = "ChaCha20";

    public byte[] encrypt(byte[] pText, SecretKey key, byte[] nonce, int counter) throws Exception {

        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

        ChaCha20ParameterSpec param = new ChaCha20ParameterSpec(nonce, counter);

        cipher.init(Cipher.ENCRYPT_MODE, key, param);

        byte[] encryptedText = cipher.doFinal(pText);

        return encryptedText;
    }

    public byte[] decrypt(byte[] cText, SecretKey key, byte[] nonce, int counter) throws Exception {

        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

        ChaCha20ParameterSpec param = new ChaCha20ParameterSpec(nonce, counter);

        cipher.init(Cipher.DECRYPT_MODE, key, param);

        byte[] decryptedText = cipher.doFinal(cText);

        return decryptedText;

    }

}

2.1 Test.

TestChaCha20.java

package com.mkyong.java11.jep329.chacha20;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class TestChaCha20 {

    public static void main(String[] args) throws Exception {

        String input = "Java & ChaCha20 encryption example.";

        ChaCha20 cipher = new ChaCha20();

        SecretKey key = getKey();           // 256-bit secret key (32 bytes)
        byte[] nonce = getNonce();          // 96-bit nonce (12 bytes)
        int counter = 1;                    // 32-bit initial count (8 bytes)

        System.out.println("Input          : " + input);
        System.out.println("Input     (hex): " + convertBytesToHex(input.getBytes()));

        System.out.println("\n---Encryption---");
        byte[] cText = cipher.encrypt(input.getBytes(), key, nonce, counter);   // encrypt

        System.out.println("Key       (hex): " + convertBytesToHex(key.getEncoded()));
        System.out.println("Nonce     (hex): " + convertBytesToHex(nonce));
        System.out.println("Counter        : " + counter);
        System.out.println("Encrypted (hex): " + convertBytesToHex(cText));

        System.out.println("\n---Decryption---");

        byte[] pText = cipher.decrypt(cText, key, nonce, counter);              // decrypt

        System.out.println("Key       (hex): " + convertBytesToHex(key.getEncoded()));
        System.out.println("Nonce     (hex): " + convertBytesToHex(nonce));
        System.out.println("Counter        : " + counter);
        System.out.println("Decrypted (hex): " + convertBytesToHex(pText));
        System.out.println("Decrypted      : " + new String(pText));

    }

    // A 256-bit secret key (32 bytes)
    private static SecretKey getKey() throws NoSuchAlgorithmException {
        KeyGenerator keyGen = KeyGenerator.getInstance("ChaCha20");
        keyGen.init(256, SecureRandom.getInstanceStrong());
        return keyGen.generateKey();
    }

    // 96-bit nonce (12 bytes)
    private static byte[] getNonce() {
        byte[] newNonce = new byte[12];
        new SecureRandom().nextBytes(newNonce);
        return newNonce;
    }

    private static String convertBytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte temp : bytes) {
            result.append(String.format("%02x", temp));
        }
        return result.toString();
    }

}

Output

Terminal

Input          : Java & ChaCha20 encryption example.
Input     (hex): 4a617661202620436861436861323020656e6372797074696f6e206578616d706c652e

---Encryption---
Key       (hex): ee416df8b5154a4ac48f3930fcfa53ef7f677c8fd7cd093f1328eedfd831db1a
Nonce     (hex): 9806308f4d1732d2d39beaba
Counter        : 1
Encrypted (hex): 2149db2c32bf82f9e8dc0a709d8c15d5a22eb79d5f692e04f070d46cc7e264631f85e0

---Decryption---
Key       (hex): ee416df8b5154a4ac48f3930fcfa53ef7f677c8fd7cd093f1328eedfd831db1a
Nonce     (hex): 9806308f4d1732d2d39beaba
Counter        : 1
Decrypted (hex): 4a617661202620436861436861323020656e6372797074696f6e206578616d706c652e
Decrypted      : Java & ChaCha20 encryption example.

The key + nonce + counter must be the same for encryption and decryption, a single bit different will generate a different result.

3. ChaCha20… version 2

3.1 In this example, we use System.arraycopy to append the nonce and init counter to the end of the ciphertext (encrypted message), so that we only need the secret key for the decryption.

ChaCha20v2.java

package com.mkyong.java11.jep329.chacha20v2;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.ChaCha20ParameterSpec;
import java.nio.ByteBuffer;

public class ChaCha20v2 {

    private static final String ENCRYPT_ALGO = "ChaCha20";

    private static final int LEN_NONCE = 12;
    private static final int LEN_COUNTER = 4;

    public byte[] encrypt(byte[] pText, SecretKey key, byte[] nonce, int counter) throws Exception {

        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

        ChaCha20ParameterSpec param = new ChaCha20ParameterSpec(nonce, counter);

        cipher.init(Cipher.ENCRYPT_MODE, key, param);

        byte[] encryptedText = cipher.doFinal(pText);

        // append nonce + count
        byte[] output = new byte[encryptedText.length + LEN_NONCE + LEN_COUNTER];

        System.arraycopy(encryptedText, 0, output, 0, encryptedText.length);
        System.arraycopy(nonce, 0, output, encryptedText.length, LEN_NONCE);

        // convert int to byte[]
        byte[] counterByteArray = ByteBuffer.allocate(4).putInt(counter).array();
        System.arraycopy(counterByteArray, 0, output, encryptedText.length + LEN_NONCE, LEN_COUNTER);

        return output;
    }

    public byte[] decrypt(byte[] cText, SecretKey key) throws Exception {

        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

        // get only the encrypted text
        byte[] encryptedText = new byte[cText.length - (LEN_NONCE + LEN_COUNTER)];
        System.arraycopy(cText, 0, encryptedText, 0, cText.length - (LEN_NONCE + LEN_COUNTER));

        // get nonce
        byte[] nonce = new byte[12];
        System.arraycopy(cText, encryptedText.length, nonce, 0, LEN_NONCE);

        // get initial counter
        byte[] counter = new byte[4];
        System.arraycopy(cText, encryptedText.length + LEN_NONCE, counter, 0, LEN_COUNTER);

        /// convert byte array to int
        int ic = ByteBuffer.wrap(counter).getInt();
        ChaCha20ParameterSpec param = new ChaCha20ParameterSpec(nonce, ic);

        cipher.init(Cipher.DECRYPT_MODE, key, param);

        byte[] decryptedText = cipher.doFinal(encryptedText);

        return decryptedText;

    }

}

3.2 For decryption, we only need to provide the secret key, because the nonce and initial counter are at the end of the encrypted text or ciphertext.

TestChaCha20v2.java

package com.mkyong.java11.jep329.chacha20v2;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class TestChaCha20v2 {

    public static void main(String[] args) throws Exception {

        String input = "Java & ChaCha20 encryption example.";

        ChaCha20v2 cipher = new ChaCha20v2();

        SecretKey key = getKey();           // 256-bit secret key (32 bytes)
        byte[] nonce = getNonce();          // 96-bit nonce (12 bytes)
        int counter = 1;                    // 32-bit initial count (8 bytes)

        System.out.println("Input          : " + input);
        System.out.println("Input     (hex): " + convertBytesToHex(input.getBytes()));

        System.out.println("\n---Encryption---");
        byte[] cText = cipher.encrypt(input.getBytes(), key, nonce, counter);   // encrypt

        System.out.println("Key       (hex): " + convertBytesToHex(key.getEncoded()));
        System.out.println("Nonce     (hex): " + convertBytesToHex(nonce));
        System.out.println("Counter        : " + counter);
        System.out.println("Encrypted (hex): " + convertBytesToHex(cText));

        System.out.println("\n---Decryption---");

        byte[] pText = cipher.decrypt(cText, key);              // decrypt

        System.out.println("Key       (hex): " + convertBytesToHex(key.getEncoded()));
        System.out.println("Decrypted (hex): " + convertBytesToHex(pText));
        System.out.println("Decrypted      : " + new String(pText));

    }

    private static String convertBytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte temp : bytes) {
            result.append(String.format("%02x", temp));
        }
        return result.toString();
    }

    // A 256-bit secret key (32 bytes)
    private static SecretKey getKey() throws NoSuchAlgorithmException {
        KeyGenerator keyGen = KeyGenerator.getInstance("ChaCha20");
        keyGen.init(256, SecureRandom.getInstanceStrong());
        return keyGen.generateKey();
    }

    // 96-bit nonce (12 bytes)
    private static byte[] getNonce() {
        byte[] newNonce = new byte[12];
        new SecureRandom().nextBytes(newNonce);
        return newNonce;
    }

}

Output

Terminal

Input          : Java & ChaCha20 encryption example.
Input     (hex): 4a617661202620436861436861323020656e6372797074696f6e206578616d706c652e

---Encryption---
Key       (hex): f95fd5b41783595e41f4cbcd8dc26a782599184e97ccd768ac531aae729781d3
Nonce     (hex): 84133f2261ef44796e3669dc
Counter        : 1
Encrypted (hex): 7738807b409f3349dbefbeae988482e0e5959c35ee8f8ee8987357db459e10d7fb8c7e84133f2261ef44796e3669dc00000001

---Decryption---
Key       (hex): f95fd5b41783595e41f4cbcd8dc26a782599184e97ccd768ac531aae729781d3
Decrypted (hex): 4a617661202620436861436861323020656e6372797074696f6e206578616d706c652e
Decrypted      : Java & ChaCha20 encryption example.

Review the encrypted text; it combines the encrypted message, nonce (12 bytes), and initial counter (4 bytes).


Encrypted (hex): 7738807b409f3349dbefbeae988482e0e5959c35ee8f8ee8987357db459e10d7fb8c7e84133f2261ef44796e3669dc00000001

## split view
Encrypted (hex): 7738807b409f3349dbefbeae988482e0e5959c35ee8f8ee8987357db459e10d7fb8c7e | 84133f2261ef44796e3669dc | 00000001

Note
If you found any error, please comment below, thanks.

Download Source Code

$ git clone https://github.com/mkyong/core-java.git

$ cd java-11

$ cd /src/main/java/com/mkyong/java11/jep329

References

author image

mkyong

Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities. Read all published posts by

Comments

avatar
newest oldest most voted
Pierre
Guest
Pierre

Thanks !