Search code examples
javaencryption

Java: ChaCha20 w/ Poly1305 as MAC for general purpose file encryption


I need to use ChaCha20/Poly1305 for general purpose encryption and decryption of any data from any source (the same way we use I/O streams, ex. CipherInputStream).

This question already asks if it's possible to use Bouncy Castle's ChaCha20Poly1305 class to process data but only TLS transactions seem to be supported.

So now I'm left with pure ChaCha20 (ChaCha20Engine). I would like to know if writing my own encrypt-then-MAC scheme is a good idea or not, so that I get exactly what I need.

tl;dr Is it OK to write my own ChaCha20/Poly1305 encrypt-then-MAC mode of operation ?


Solution

  • Java 11 adds a "ChaCha20-Poly1305/None/NoPadding Cipher It is now possible to use without any third party libraries or some other magic -->

    Here’s a reference Implementation

    ATTENTION: this Implementation uses a non-random Nonce and should NOT be used except for Testing purposes:

    package chaCha20Poly1305Encryption;
    
    import javax.crypto.*;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.security.spec.AlgorithmParameterSpec;
    import java.util.Base64;
    
    public class ChaCha20Poly1305 {
    
        public static byte[] encrypt(byte[] data, SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException,
                InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
            if(key == null) throw new InvalidKeyException("SecretKey must NOT be NULL");
        
            byte[] nonceBytes = new byte[12];
        
            // Get Cipher Instance
            Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
        
            // Create IvParamterSpec
            AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(nonceBytes);
        
            // Create SecretKeySpec
            SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "ChaCha20");
        
            // Initialize Cipher for ENCRYPT_MODE
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
        
            // Perform Encryption
            return cipher.doFinal(data);
        }
        
        public static byte[] decrypt(byte[] cipherText, SecretKey key) throws Exception {
            if(key == null) throw new InvalidKeyException("SecretKey must NOT be NULL");
            byte[] nonceBytes = new byte[12];
        
            // Get Cipher Instance
            Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
        
            // Create IvParamterSpec
            AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(nonceBytes);
        
            // Create SecretKeySpec
            SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "ChaCha20");
        
            // Initialize Cipher for DECRYPT_MODE
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
        
            // Perform Decryption
            return cipher.doFinal(cipherText);
        }
        
        public static void main(String[] args) throws Exception {
            SecretKey key = ChaCha20Poly1305KeyGenerator.generateKey();
        
            String testMessage = "hallo!";
            byte[] encryptedBytes = encrypt(testMessage.getBytes(), key);
            String decryptedMessage = new String(decrypt(encryptedBytes,key));
            System.out.println("testMessage: " + testMessage);
            System.out.println(key.getAlgorithm() + " SecretKey: " + Base64.getEncoder().encodeToString(key.getEncoded()));
            System.out.println("encryptedBytes: " + Base64.getEncoder().encodeToString(encryptedBytes));
            System.out.println("decryptedMessage: "+ decryptedMessage);
        
        }
    }
    

    And the corresponding Key-Generator:

    package chaCha20Poly1305Encryption;
    
    import javax.crypto.KeyGenerator;
    import javax.crypto.SecretKey;
    import java.security.NoSuchAlgorithmException;
    import java.util.Base64;
    
    public class ChaCha20Poly1305KeyGenerator {
        public static SecretKey generateKey() throws NoSuchAlgorithmException {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("ChaCha20");
            //Keysize MUST be 256 bit - as of Java11 only 256Bit is supported
            keyGenerator.init(256);
            return keyGenerator.generateKey();
        }
        public static void main(String[] args) throws NoSuchAlgorithmException {
            SecretKey key = generateKey();
            System.out.println(key.getAlgorithm() + " SecretKey: " + Base64.getEncoder().encodeToString(key.getEncoded()));
        }
    }
    

    Code used from my Github Gist can be found here.