Java 11 – ChaCha20-Poly1305 encryption examples

This article shows you how to encrypt and decrypt a message with the ChaCha20-Poly1305 algorithm, defined in RFC 7539.

P.S The ChaCha20-Poly1305 encryption algorithm is available at Java 11.

1. FAQs

Some commonly asked questions:

1.1 What is ChaCha20-Poly1305?
ChaCha20-Poly1305 means the ChaCha20 (encryption and decryption algorithm) running in AEAD mode with the Poly1305 authenticator.

1.2 What is AE or AEAD?
Authenticated encryption (AE) and authenticated encryption with associated data (AEAD) is a form of encrypting a message and authenticate the encryption together.

1.3 What do you mean to authenticate the encryption?
Make sure nobody modifies the ciphertext (encrypted message), it works like verify SHA or MD5 hash of a file. Poly1305 generates a MAC (Message Authentication Code) (128 bits, 16 bytes) and appending it to the ChaCha20 ciphertext (encrypted text). During decryption, the algorithm checks the MAC to assure no one modifies the ciphertext.

1.4 How ChaCha20-Poly1305 works?
ChaCha20 encryption uses the key and IV (initialization value, nonce) to encrypt the plaintext into a ciphertext of equal length. Poly1305 generates a MAC (Message Authentication Code) and appending it to the ciphertext. In the end, the length of the ciphertext and plaintext is different.

1.5 Can I reuse the same nonce for the different keys?
No, the nonce and key must be unique for each encryption, otherwise, the ciphertext will compromise!

Note
If we don’t want the extra authentication (poly1305) feature, consider encrypt the message with ChaCha20 only, refer to this Java 11 – ChaCha20 Stream Cipher examples

1. ChaCha20-Poly1305

This example uses ChaCha20-Poly1305 to encrypt and decrypt a message. Furthermore, we manually append the nonce to the final encrypted text so that we no need to provide the nonce during decryption. The nonce is ok to be publicly known, but the key must be private.

The inputs to ChaCha20-Poly1305 are:

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

P.S For ChaCha20-Poly1305, we don’t need to define the initial counter value; it begins at 1.

ChaCha20Poly1305.java

package com.mkyong.java11.jep329.poly1305;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
import java.security.SecureRandom;

public class ChaCha20Poly1305 {

    private static final String ENCRYPT_ALGO = "ChaCha20-Poly1305";
    private static final int NONCE_LEN = 12; // 96 bits, 12 bytes

    // if no nonce, generate a random 12 bytes nonce
    public byte[] encrypt(byte[] pText, SecretKey key) throws Exception {
        return encrypt(pText, key, getNonce());
    }

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

        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

        // IV, initialization value with nonce
        IvParameterSpec iv = new IvParameterSpec(nonce);

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

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

        // append nonce to the encrypted text
        byte[] output = ByteBuffer.allocate(encryptedText.length + NONCE_LEN)
                .put(encryptedText)
                .put(nonce)
                .array();

        return output;
    }

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

        ByteBuffer bb = ByteBuffer.wrap(cText);

        // split cText to get the appended nonce
        byte[] encryptedText = new byte[cText.length - NONCE_LEN];
        byte[] nonce = new byte[NONCE_LEN];
        bb.get(encryptedText);
        bb.get(nonce);

        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

        IvParameterSpec iv = new IvParameterSpec(nonce);

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

        // decrypted text
        byte[] output = cipher.doFinal(encryptedText);

        return output;

    }

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

}

2. Test it.

Encrypt and decrypt a message with the ChaCha20-Poly1305 algorithm.

TestChaCha20Poly1305.java

package com.mkyong.java11.jep329.poly1305;

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

public class TestChaCha20Poly1305 {

  private static final int NONCE_LEN = 12;                    // 96 bits, 12 bytes
  private static final int MAC_LEN = 16;                      // 128 bits, 16 bytes

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

      String input = "Java & ChaCha20-Poly1305.";

      ChaCha20Poly1305 cipher = new ChaCha20Poly1305();

      SecretKey key = getKey();                               // 256-bit secret key (32 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);   // encrypt

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

      System.out.println("\n---Print Mac and Nonce---");

      ByteBuffer bb = ByteBuffer.wrap(cText);

      // This cText contains chacha20 ciphertext + poly1305 MAC + nonce

      // ChaCha20 encrypted the plaintext into a ciphertext of equal length.
      byte[] originalCText = new byte[input.getBytes().length];
      byte[] nonce = new byte[NONCE_LEN];     // 16 bytes , 128 bits
      byte[] mac = new byte[MAC_LEN];         // 12 bytes , 96 bits

      bb.get(originalCText);
      bb.get(mac);
      bb.get(nonce);

      System.out.println("Cipher (original) (hex): " + convertBytesToHex(originalCText));
      System.out.println("MAC               (hex): " + convertBytesToHex(mac));
      System.out.println("Nonce             (hex): " + convertBytesToHex(nonce));

      System.out.println("\n---Decryption---");
      System.out.println("Input             (hex): " + convertBytesToHex(cText));

      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));

  }

  // https://mkyong.com/java/java-how-to-convert-bytes-to-hex/
  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();
  }

}

Output

Terminal

Input                  : Java & ChaCha20-Poly1305.
Input             (hex): 4a61766120262043686143686132302d506f6c79313330352e

---Encryption---
Key               (hex): 7b47d646547928f2ac962562d613bdac7db351893224c6c1b03adeb859da8251
Encrypted         (hex): c04a463086c545d0053f8a279d50815aa9cb5db04da5ba0c4dc57e225f46074af793ccb81a15908a8fd15561deb55f01ef0d93932a

---Print Mac and Nonce---
Cipher (original) (hex): c04a463086c545d0053f8a279d50815aa9cb5db04da5ba0c4d
MAC               (hex): c57e225f46074af793ccb81a15908a8f
Nonce             (hex): d15561deb55f01ef0d93932a

---Decryption---
Input             (hex): c04a463086c545d0053f8a279d50815aa9cb5db04da5ba0c4dc57e225f46074af793ccb81a15908a8fd15561deb55f01ef0d93932a
Key               (hex): 7b47d646547928f2ac962562d613bdac7db351893224c6c1b03adeb859da8251
Decrypted         (hex): 4a61766120262043686143686132302d506f6c79313330352e
Decrypted              : Java & ChaCha20-Poly1305.

Download Source Code

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

$ cd java-11

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

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

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