5
\$\begingroup\$

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);
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ Not a good idea to implement crypto algorithms by yourself, except for exercising. \$\endgroup\$ – Billal Begueradj Oct 14 '17 at 14:58
  • 5
    \$\begingroup\$ @BillalBEGUERADJ I wouldn't say that this post necessarily does that. It seems to use existing algorithms. \$\endgroup\$ – Simon Forsberg Oct 14 '17 at 15:12
  • \$\begingroup\$ Possible duplicate of AES Encryption/Decryption with key \$\endgroup\$ – Martin Frank Feb 14 '18 at 14:44
  • 1
    \$\begingroup\$ Looks like a follow-up post to me... \$\endgroup\$ – 23fc9a62-56de-47fb-97b4-737890 Feb 14 '18 at 16:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.