Using BouncyCastle's ChaCha for file encryption

I’m hoping to encrypt a few files using ChaCha, so I wonder if it’s appropriate to use Chacha20Poly1305. It seems that this class is designed for TLS, so does that mean it’s not designed for file encryption? The methods inside, e.g., encodePlaintext() and decodeCiphertext() appear to work with text rather than binary files.

If that’s the case, does anyone know how to work with BC’s ChaCha implementation for file encryption?

You can simply use the ChaChaEngine class that is referenced by the Chacha20Poly1305 class. The Engine classes contain implementations of the various cipher classes.

Besides that, the JCA provides a higher level API to work with the various ciphers. So you can also use:

Security.addProvider(new BouncyCastleProvider());
Cipher c = Cipher.getInstance("ChaCha");

After which the normal Java CipherInputStream and CipherOutputStream will become available.

Note that using Poly1305 does provide additional authentication. This is often not a requirement for file encryption, but it does provide an additional layer of security. If you want to have authenticated encryption and you cannot work out how, then please ask a separate question.

You could try something line this:

public void doChaCha(boolean encrypt, InputStream is, OutputStream os,
        byte[] key, byte[] iv) throws IOException {
    CipherParameters cp = new KeyParameter(key);
    ParametersWithIV params = new ParametersWithIV(cp, iv);
    StreamCipher engine = new ChaChaEngine();
    engine.init(encrypt, params);

    byte in[] = new byte[8192];
    byte out[] = new byte[8192];
    int len = 0;
    while(-1 != (len = is.read(in))) {
        len = engine.processBytes(in, 0 , len, out, 0);
        os.write(out, 0, len);
    }
}

public void encChaCha(InputStream is, OutputStream os, byte[] key,
        byte[] iv) throws IOException {
    doChaCha(true, is, os, key, iv);
}

public void decChaCha(InputStream is, OutputStream os, byte[] key,
        byte[] iv) throws IOException {
    doChaCha(false, is, os, key, iv);
}

Note that the ChaChaEngine supports only keys of the length 128 or 256 bits with an IV of 64 bits.

Here is some tests:

@Test
public void chachaText() throws IOException, NoSuchAlgorithmException {
    String text = "chacha.txt";
    Files.write(Paths.get(text), "Hello, World!".getBytes(StandardCharsets.UTF_8), 
        StandardOpenOption.CREATE);
    chacha(text);
}

@Test
public void chachaBin() throws IOException, NoSuchAlgorithmException {
    SecureRandom sr = SecureRandom.getInstanceStrong();
    byte[] data = new byte[1024*1024*4];
    sr.nextBytes(data);
    String bin = "chacha.bin";
    Files.write(Paths.get(bin), data, StandardOpenOption.CREATE);
    chacha(bin);
}

private void chacha(String file) throws NoSuchAlgorithmException, IOException {
    SecureRandom sr = SecureRandom.getInstanceStrong();
    byte[] key = new byte[32]; // 32 for 256 bit key or 16 for 128 bit
    byte[] iv = new byte[8]; // 64 bit IV required by ChaCha20
    sr.nextBytes(key);
    sr.nextBytes(iv);
    String dat = String.format("%s.dat", file);

    try(InputStream is = new FileInputStream(file); 
        OutputStream os = new FileOutputStream(dat)) {
        encChaCha(is, os, key, iv);
    }

    try(InputStream is = new FileInputStream(dat); 
        ByteArrayOutputStream os = new ByteArrayOutputStream()) {
        decChaCha(is, os, key, iv);
        byte[] actual = os.toByteArray();
        byte[] expected = Files.readAllBytes(Paths.get(file));
        Assert.assertArrayEquals(expected, actual);
    }
}

EDIT: encode / decode Strings

@Test
public void chachaString() throws IOException, NoSuchAlgorithmException
{
    String test = "Hello, World!";

    try (InputStream isEnc = new ByteArrayInputStream(test.getBytes(StandardCharsets.UTF_8));
            ByteArrayOutputStream osEnc = new ByteArrayOutputStream())
    {
        SecureRandom sr = SecureRandom.getInstanceStrong();
        byte[] key = new byte[32]; // 32 for 256 bit key or 16 for 128 bit
        byte[] iv = new byte[8]; // 64 bit IV required by ChaCha20
        sr.nextBytes(key);
        sr.nextBytes(iv);

        encChaCha(isEnc, osEnc, key, iv);

        byte[] encoded = osEnc.toByteArray();

        try (InputStream isDec = new ByteArrayInputStream(encoded);
                ByteArrayOutputStream osDec = new ByteArrayOutputStream())
        {
            decChaCha(isDec, osDec, key, iv);

            byte[] decoded = osDec.toByteArray();

            String actual = new String(decoded, StandardCharsets.UTF_8);

            Assert.assertEquals(test, actual);
        }
    }
}