package jdrivesync.encryption;
import jdrivesync.cli.Options;
import jdrivesync.exception.JDriveSyncException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
public class Encryption {
private final Options options;
private final SecureRandom secureRandom;
public Encryption(Options options) {
this.options = options;
secureRandom = new SecureRandom();
secureRandom.setSeed(System.currentTimeMillis());
}
public InputStream encrypt(byte[] bytes) {
try {
char[] password = options.getEncryptPassword().toCharArray();
byte[] salt = generateSalt();
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey secret = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec).getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] encryptedBytes = cipher.doFinal(bytes);
byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
return new ByteArrayInputStream(concat(salt, concat(iv, encryptedBytes)));
} catch (Exception e) {
throw new JDriveSyncException(JDriveSyncException.Reason.Encryption, "Failed to encrypt: " + e.getMessage(), e);
}
}
public InputStream decrypt(InputStream encryptedStream) {
try {
char[] password = options.getEncryptPassword().toCharArray();
byte[] salt = readSalt(encryptedStream);
byte[] iv = readIv(encryptedStream);
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey secret = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec).getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] bytes = toByteArray(encryptedStream);
byte[] decryptedBytes = cipher.doFinal(bytes);
return new ByteArrayInputStream(decryptedBytes);
} catch (Exception e) {
throw new JDriveSyncException(JDriveSyncException.Reason.Encryption, "Failed to decrypt: " + e.getMessage(), e);
}
}
private byte[] generateSalt() {
byte[] salt = new byte[8];
secureRandom.nextBytes(salt);
return salt;
}
private byte[] concat(byte[] first, byte[] second) {
byte[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
private byte[] readSalt(InputStream encryptedStream) throws IOException {
byte[] salt = new byte[8];
int bytesRead = encryptedStream.read(salt);
if (bytesRead < salt.length) {
throw new JDriveSyncException(JDriveSyncException.Reason.Encryption, "Failed to read salt from InputStream.");
}
return salt;
}
private byte[] readIv(InputStream encryptedStream) throws IOException {
byte[] iv = new byte[16];
int bytesRead = encryptedStream.read(iv);
if (bytesRead < iv.length) {
throw new JDriveSyncException(JDriveSyncException.Reason.Encryption, "Failed to read IV from InputStream.");
}
return iv;
}
byte[] toByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer);
while(bytesRead != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
bytesRead = inputStream.read(buffer);
}
return byteArrayOutputStream.toByteArray();
}
}