![JAR search and dependency download from the Maven repository](/logo.png)
com.github.jlangch.venice.util.crypt.FileEncryptor_ChaCha20 Maven / Gradle / Ivy
/* __ __ _
* \ \ / /__ _ __ (_) ___ ___
* \ \/ / _ \ '_ \| |/ __/ _ \
* \ / __/ | | | | (_| __/
* \/ \___|_| |_|_|\___\___|
*
*
* Copyright 2017-2024 Venice
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.jlangch.venice.util.crypt;
import java.io.File;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* Encrypt and decrypt files using "ChaCha20". Available on Java 11+
*
* Uses a counter and a nonce for each file and writes the counter
* and the nonce to start of the encrypted file.
*
*
* Encrypted binary file format when passphrase is used
*
* +-----------------------+
* | salt | 16 bytes
* +-----------------------+
* | nonce | 12 bytes
* +-----------------------+
* | counter | 4 bytes
* +-----------------------+
* | encrypted file data | n bytes
* +-----------------------+
*
*
*
* Encrypted binary file format when key is used
*
* +-----------------------+
* | nonce | 12 bytes
* +-----------------------+
* | counter | 4 bytes
* +-----------------------+
* | encrypted file data | n bytes
* +-----------------------+
*
*/
public class FileEncryptor_ChaCha20 {
public static boolean isSupported() {
return supported;
}
public static void encryptFileWithPassphrase(
final String passphrase,
final File inputFile,
final File outputFile
) throws Exception {
// Read file data
byte[] fileData = Files.readAllBytes(inputFile.toPath());
// Encrypt
byte[] encryptedData = encryptFileWithPassphrase(passphrase, fileData);
// Write to output file
Files.write(outputFile.toPath(), encryptedData);
}
public static byte[] encryptFileWithPassphrase(
final String passphrase,
final byte[] fileData
) throws Exception {
// Generate a random salt
byte[] salt = new byte[SALT_LEN];
new SecureRandom().nextBytes(salt);
// Generate a random nonce
byte[] nonce = new byte[NONCE_LEN];
new SecureRandom().nextBytes(nonce);
// Generate an counter
int counter = new SecureRandom().nextInt();
byte[] counterData = counterToBytes(counter);
// Derive key from passphrase
byte[] key = Util.deriveKeyFromPassphrase(passphrase, salt, 65536, 256);
// Perform Encryption
byte[] encryptedData = processData(Cipher.ENCRYPT_MODE, fileData, key, nonce, counter);
// Combine salt, nonce, counter, and encrypted data
byte[] outData = new byte[SALT_LEN + NONCE_LEN + COUNTER_LEN + encryptedData.length];
System.arraycopy(salt, 0, outData, 0, salt.length);
System.arraycopy(nonce, 0, outData, SALT_LEN, nonce.length);
System.arraycopy(counterData, 0, outData, SALT_LEN + NONCE_LEN, counterData.length);
System.arraycopy(encryptedData, 0, outData, SALT_LEN + NONCE_LEN + COUNTER_LEN, encryptedData.length);
return outData;
}
public static void encryptFileWithKey(
final byte[] key,
final File inputFile,
final File outputFile
) throws Exception {
// Read file data
byte[] fileData = Files.readAllBytes(inputFile.toPath());
// Encrypt
byte[] encryptedData = encryptFileWithKey(key, fileData);
// Write to output file
Files.write(outputFile.toPath(), encryptedData);
}
public static byte[] encryptFileWithKey(
final byte[] key,
final byte[] fileData
) throws Exception {
// Generate a random nonce
byte[] nonce = new byte[NONCE_LEN];
new SecureRandom().nextBytes(nonce);
// Generate an counter
int counter = new SecureRandom().nextInt();
byte[] counterData = counterToBytes(counter);
byte[] encryptedData = processData(Cipher.ENCRYPT_MODE, fileData, key, nonce, counter);
// Combine salt, nonce, counter, and encrypted data
byte[] outData = new byte[NONCE_LEN + COUNTER_LEN + encryptedData.length];
System.arraycopy(nonce, 0, outData, 0, nonce.length);
System.arraycopy(counterData, 0, outData, NONCE_LEN, counterData.length);
System.arraycopy(encryptedData, 0, outData, NONCE_LEN + COUNTER_LEN, encryptedData.length);
return outData;
}
public static void decryptFileWithPassphrase(
final String passphrase,
final File inputFile,
final File outputFile
) throws Exception {
// Read file data
byte[] fileData = Files.readAllBytes(inputFile.toPath());
// Decrypt
byte[] decryptedData = decryptFileWithPassphrase(passphrase, fileData);
// Write to output file
Files.write(outputFile.toPath(), decryptedData);
}
public static byte[] decryptFileWithPassphrase(
final String passphrase,
final byte[] fileData
) throws Exception {
// Extract salt, nonce, counter, and encrypted data
byte[] salt = new byte[SALT_LEN];
System.arraycopy(fileData, 0, salt, 0, SALT_LEN);
byte[] nonce = new byte[NONCE_LEN];
System.arraycopy(fileData, SALT_LEN, nonce, 0, NONCE_LEN);
byte[] counterBytes = new byte[COUNTER_LEN];
System.arraycopy(fileData, SALT_LEN + NONCE_LEN, counterBytes, 0, COUNTER_LEN);
byte[] encryptedData = new byte[fileData.length - SALT_LEN - NONCE_LEN - COUNTER_LEN];
System.arraycopy(fileData, SALT_LEN + NONCE_LEN + COUNTER_LEN, encryptedData, 0, encryptedData.length);
int counter = counterToInt(counterBytes);
// Derive key from passphrase
byte[] key = Util.deriveKeyFromPassphrase(passphrase, salt, 65536, 256);
// Perform Decryption
return processData(Cipher.DECRYPT_MODE, encryptedData, key, nonce, counter);
}
public static void decryptFileWithKey(
final byte[] key,
final File inputFile,
final File outputFile
) throws Exception {
// Read file data
byte[] fileData = Files.readAllBytes(inputFile.toPath());
// Decrypt
byte[] decryptedData = decryptFileWithKey(key, fileData);
// Write to output file
Files.write(outputFile.toPath(), decryptedData);
}
public static byte[] decryptFileWithKey(
final byte[] key,
final byte[] fileData
) throws Exception {
// Extract nonce, counter, and encrypted data
byte[] nonce = new byte[NONCE_LEN];
System.arraycopy(fileData, 0, nonce, 0, NONCE_LEN);
byte[] counterBytes = new byte[COUNTER_LEN];
System.arraycopy(fileData, NONCE_LEN, counterBytes, 0, COUNTER_LEN);
byte[] encryptedData = new byte[fileData.length - NONCE_LEN - COUNTER_LEN];
System.arraycopy(fileData, NONCE_LEN + COUNTER_LEN, encryptedData, 0, encryptedData.length);
int counter = counterToInt(counterBytes);
return processData(Cipher.DECRYPT_MODE, encryptedData, key, nonce, counter);
}
private static AlgorithmParameterSpec createChaCha20ParameterSpec(
final byte[] nonce,
final int counter
) {
// return new ChaCha20ParameterSpec(nonce, counter);
// Note: ChaCha20 is only available with Java11+
try {
Class> clazz = Util.classForName("javax.crypto.spec.ChaCha20ParameterSpec");
Constructor> c = clazz.getConstructor(new Class[]{byte[].class, int.class});
return (AlgorithmParameterSpec)c.newInstance(nonce, counter);
}
catch(Exception ex) {
throw new RuntimeException("Java Crypto algorithm ChaCha20 is not available!");
}
}
private static byte[] processData(
final int mode,
final byte[] data,
final byte[] key,
final byte[] nonce,
final int counter
) throws Exception {
// Secret key
SecretKeySpec keySpec = new SecretKeySpec(key, "ChaCha20");
// Initialize ChaCha20 Parameters
AlgorithmParameterSpec param = createChaCha20ParameterSpec(nonce, counter);
// Initialize Cipher for ChaCha20
Cipher cipher = Cipher.getInstance("ChaCha20");
cipher.init(mode, keySpec, param);
// encryption
return cipher.doFinal(data);
}
private static byte[] counterToBytes(final int counter) {
// convert int to byte[]
return ByteBuffer.allocate(COUNTER_LEN)
.order(ENDIAN)
.putInt(counter)
.array();
}
private static int counterToInt(final byte[] counter) {
// convert byte[] to int
return ByteBuffer.wrap(counter)
.order(ENDIAN)
.getInt(0);
}
private static boolean checkSupported() {
try {
final Class> clazz = Util.classForName("javax.crypto.spec.ChaCha20ParameterSpec");
return clazz != null;
}
catch(Exception ex) {
return false;
}
}
private static final boolean supported = checkSupported();
private static ByteOrder ENDIAN = ByteOrder.BIG_ENDIAN; // ensure same byte order on all machines
private static int SALT_LEN = 16;
private static int NONCE_LEN = 12;
private static int COUNTER_LEN = 4;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy