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.
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.
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
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.
How do you determine the mac length? Is it always 16 bytes or it depends on key size?