I wrote my first file encryption program, that encrypts a file with AES-256 GCM and stores IV and salt prepended to the file content, so it's likely that I did something worse than possible.
I would like you to look at my code and point out errors or places where it is possible to make better.
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FileCryptor implements Serializable {
private static final Logger LOGGER = Logger.getLogger(FileCryptor.class.getName());
private static final int DEFAULT_GCM_AUTHENTICATION_TAG_SIZE_BITS = 128;
private static final int DEFAULT_GCM_IV_NONCE_SIZE_BYTES = 12;
private static final int DEFAULT_PBKDF2_ITERATIONS = 65536;
private static final int DEFAULT_PBKDF2_SALT_SIZE_BYTES = 32;
private static final int DEFAULT_AES_KEY_LENGTH_BITS = 256;
private static final String DEFAULT_CIPHER = "AES";
private static final String DEFAULT_CIPHERSCHEME = "AES/GCM/NoPadding";
private static final String DEFAULT_PBKDF2_SCHEME = "PBKDF2WithHmacSHA256";
private int gcmAuthenticationTagSizeBits = DEFAULT_GCM_AUTHENTICATION_TAG_SIZE_BITS;
private int gcmIvNonceSizeBytes = DEFAULT_GCM_IV_NONCE_SIZE_BYTES;
private int pbkdf2Iterations = DEFAULT_PBKDF2_ITERATIONS;
private int pbkdf2SaltSizeBytes = DEFAULT_PBKDF2_SALT_SIZE_BYTES;
private int aesKeyLengthBits = DEFAULT_AES_KEY_LENGTH_BITS;
private String cipher = DEFAULT_CIPHER;
private String cipherscheme = DEFAULT_CIPHERSCHEME;
private String pbkdf2Scheme = DEFAULT_PBKDF2_SCHEME;
/**
* Creates a new empty EncryptedFile object
*/
public FileCryptor() {
}
/**
* Generates a randomly filled byte array
*
* @param sizeInBytes length of the array in bytes
* @return byte array containing random values
* @throws NoSuchAlgorithmException
*/
private static byte[] generateRandomArry(int sizeInBytes) throws NoSuchAlgorithmException {
/* generate random salt */
final byte[] salt = new byte[sizeInBytes];
SecureRandom random = SecureRandom.getInstanceStrong();
random.nextBytes(salt);
return salt;
}
/**
* Encrypts the provided file using the provided password and writes it
* in a file with extension "enc"
*
* @param password password which is used to generate the key
* @param path path to a writeable file (may already exist)
* @throws GeneralSecurityException
*/
public void encrypt(String password, String path) throws GeneralSecurityException {
/* Derive the key*/
SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2Scheme);
byte[] newSalt = generateRandomArry(pbkdf2SaltSizeBytes);
KeySpec keyspec = new PBEKeySpec(password.toCharArray(), newSalt, pbkdf2Iterations, aesKeyLengthBits);
SecretKey tmp = factory.generateSecret(keyspec);
SecretKey key = new SecretKeySpec(tmp.getEncoded(), cipher);
Cipher myCipher = Cipher.getInstance(cipherscheme);
byte[] newNonce = generateRandomArry(gcmIvNonceSizeBytes);
GCMParameterSpec spec = new GCMParameterSpec(gcmAuthenticationTagSizeBits, newNonce);
myCipher.init(Cipher.ENCRYPT_MODE, key, spec);
try (
FileInputStream fileInputStream = new FileInputStream(path);
FileOutputStream fileOutputStream = new FileOutputStream(path + ".enc");
CipherOutputStream encryptedOutputStream = new CipherOutputStream(fileOutputStream, myCipher);
) {
// write IV/nonce
fileOutputStream.write(newNonce);
// write salt
fileOutputStream.write(newSalt);
byte[] buffer = new byte[32];
while (fileInputStream.read(buffer) > 0) {
encryptedOutputStream.write(buffer);
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw new SecurityException(e.getMessage(), e);
}
}
/**
* Decrypts the encrypted file using the provided password.
*
* @param password password which is used to generate the key
* @param path path to a previously encrypted file to be decrypted
* @throws GeneralSecurityException
*/
public void decrypt(String password, String path) throws GeneralSecurityException {
// Read configuration from file
byte[] myNonce = new byte[gcmIvNonceSizeBytes];
byte[] mySalt = new byte[pbkdf2SaltSizeBytes];
try (
FileInputStream fileInputStream = new FileInputStream(path);
) {
int countReadBytesNonce = fileInputStream.read(myNonce);
int countReadBytesSalt = fileInputStream.read(mySalt);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw new SecurityException(e.getMessage(), e);
}
/* Derive the key*/
SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2Scheme);
KeySpec keyspec = new PBEKeySpec(password.toCharArray(), mySalt, pbkdf2Iterations, aesKeyLengthBits);
SecretKey tmp = factory.generateSecret(keyspec);
SecretKey key = new SecretKeySpec(tmp.getEncoded(), cipher);
Cipher myCipher = Cipher.getInstance(cipherscheme);
GCMParameterSpec spec = new GCMParameterSpec(gcmAuthenticationTagSizeBits, myNonce);
myCipher.init(Cipher.DECRYPT_MODE, key, spec);
try (
FileOutputStream fileOutputStream = new FileOutputStream(path.substring(0, path.length() - 4));
FileInputStream fileInputStream = new FileInputStream(path);
CipherInputStream cipherInputStream = new CipherInputStream(fileInputStream, myCipher);
) {
// offset the stream by the bytes already read previosly
byte[] skipped = new byte[gcmIvNonceSizeBytes+pbkdf2SaltSizeBytes];
int read = fileInputStream.read(skipped);
byte[] buffer = new byte[32];
while (cipherInputStream.read(buffer) > 0) {
fileOutputStream.write(buffer);
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw new SecurityException(e.getMessage(), e);
}
}
}
import java.util.Base64;
which just isn't used. \$\endgroup\$ – Maarten Bodewes Feb 11 '20 at 3:33