0

Hi I'm a new user and this is my first question: I state that I have no extensive knowledge of cryptography. I'm trying to encrypt files with a user-supplied password and I have found this method:

fileProcessor(Cipher.ENCRYPT_MODE,key,inputFile,newFile);

static void fileProcessor(int cipherMode,String key,File inputFile,File outputFile) {
    try {
        Key secretKey = new SecretKeySpec(key.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(cipherMode, secretKey);

        FileInputStream inputStream = new FileInputStream(inputFile);
        byte[] inputBytes = new byte[(int) inputFile.length()];
        inputStream.read(inputBytes);

        byte[] outputBytes = cipher.doFinal(inputBytes);

        FileOutputStream outputStream = new FileOutputStream(outputFile);
        outputStream.write(outputBytes);

        inputStream.close();
        outputStream.close();

        } catch (NoSuchPaddingException | NoSuchAlgorithmException 
                     | InvalidKeyException | BadPaddingException
                 | IllegalBlockSizeException | IOException e) {
        e.printStackTrace();
            }
}

The problem is that the program only works if I enter a 16 byte password (I think even a multiple of it is fine). How can I use a password that is not necessarily a multiple of 16 bytes?

5
  • 1
    Have you heard of padding? Commented Jan 4, 2020 at 16:13
  • No, what is it? Commented Jan 4, 2020 at 17:13
  • 1
    Look for articles about PBKDF2 (a PBKDF is a password based key derivation function). Ignore and downvote all articles about using SHA-256 directly and please keep in mind that you would still need a pretty secure password even if you use a strengthening function such as PBKDF2. Commented Jan 4, 2020 at 17:38
  • 2
    If you don't know cryptography, then the chance that a "found method" is secure is about zero. You'd better use a library that is specific to encryption (Fernet, CMS, PGP) so that at least some of the choices are made to be secure. For instance, you are using ECB mode above, a mode that immediately leaks information about the file and doesn't provide message integrity or authentication either. Commented Jan 4, 2020 at 17:41
  • 5
    That code has terrible stream and error handling as well, by the way, I'd use it as an example on how not to implement file encryption. There is about a mistake per line of code. Commented Jan 4, 2020 at 17:42

2 Answers 2

4

A key (SecretKeySpec) is a cryptographic key and not a simple plaintext password supplied by user. AES standard specifies the following key sizes: 128, 192 or 256 bits. A key can be created from a text password using a key derivation function, for example PBKDF2.

As Maarten-reinstateMonica mentioned in the comment, Cipher.getInstance("AES") results in AES encryption in ECB mode that is insecure. AES-GCM is strong approved authenticated encryption modes based on AES algorithm.

Also, you need to understand the following concepts before proceeding to the sample code:

Sample code:

// The number of times that the password is hashed during the derivation of the symmetric key
private static final int PBKDF2_ITERATION_COUNT = 300_000;
private static final int PBKDF2_SALT_LENGTH = 16; //128 bits
private static final int AES_KEY_LENGTH = 256; //in bits
// An initialization vector size
private static final int GCM_NONCE_LENGTH = 12; //96 bits
// An authentication tag size
private static final int GCM_TAG_LENGTH = 128; //in bits

private static byte[] encryptAES256(byte[] input, String password) {
  try {
    SecureRandom secureRandom = SecureRandom.getInstanceStrong();
    // Derive the key, given password and salt
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    // A salt is a unique, randomly generated string
    // that is added to each password as part of the hashing process
    byte[] salt = new byte[PBKDF2_SALT_LENGTH];
    secureRandom.nextBytes(salt);
    KeySpec keySpec =
        new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATION_COUNT, AES_KEY_LENGTH);
    byte[] secret = factory.generateSecret(keySpec).getEncoded();
    SecretKey key = new SecretKeySpec(secret, "AES");

    // AES-GCM encryption
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    // A nonce or an initialization vector is a random value chosen at encryption time
    // and meant to be used only once
    byte[] nonce = new byte[GCM_NONCE_LENGTH];
    secureRandom.nextBytes(nonce);
    // An authentication tag
    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
    byte[] encrypted = cipher.doFinal(input);
    // Salt and nonce can be stored together with the encrypted data
    // Both salt and nonce have fixed length, so can be prefixed to the encrypted data
    ByteBuffer byteBuffer = ByteBuffer.allocate(salt.length + nonce.length + encrypted.length);
    byteBuffer.put(salt);
    byteBuffer.put(nonce);
    byteBuffer.put(encrypted);
    return byteBuffer.array();
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

private static byte[] decryptAES256(byte[] encrypted, String password) {
  try {
    // Salt and nonce have to be extracted
    ByteBuffer byteBuffer = ByteBuffer.wrap(encrypted);
    byte[] salt = new byte[PBKDF2_SALT_LENGTH];
    byteBuffer.get(salt);
    byte[] nonce = new byte[GCM_NONCE_LENGTH];
    byteBuffer.get(nonce);
    byte[] cipherBytes = new byte[byteBuffer.remaining()];
    byteBuffer.get(cipherBytes);

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    KeySpec keySpec =
        new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATION_COUNT, AES_KEY_LENGTH);
    byte[] secret = factory.generateSecret(keySpec).getEncoded();
    SecretKey key = new SecretKeySpec(secret, "AES");

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    // If encrypted data is altered, during decryption authentication tag verification will fail
    // resulting in AEADBadTagException
    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
    return cipher.doFinal(cipherBytes);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

public static void main(String[] args) throws Exception {
  String password = "Q8yRrM^AvV5r8Yx+"; //Password still has to be strong ehough
  String input = "Sample text to encrypt";
  byte[] encrypted = encryptAES256(input.getBytes(UTF_8), password);
  System.out.println(Base64.getEncoder().encodeToString(encrypted));
  //s+AwwowLdSb3rFZ6jJlxSXBvzGz7uB6+g2e97QXGRKUY5sHPgf94AOoybkzuR3rNREMj56Ik1+Co682s4vT2sAQ/
  byte[] decrypted = decryptAES256(encrypted, password);
  System.out.println(new String(decrypted, UTF_8));
  //Sample text to encrypt
}

A few more words about random nonces. If only a few records are encrypted with the same key, then a random nonce does not pose a risk. However, if a large number of records is encrypted with the same key, the risk may become relevant.

A single repeated nonce is usually enough to fully recover the connection’s authentication key. In such faulty implementations, authenticity is lost and an attacker is able to manipulate TLS-protected content.

For safety reasons random nonces should be avoided and a counter should be used.

Sign up to request clarification or add additional context in comments.

2 Comments

Cipher.getInstance("AES"); ... ooh, I did already warn about ECB, right? The iteration count is not up to 2019 specs either. Please be sure that what you post is secure before you do post, cryptography has the nasty problem that code can run perfectly and still be insecure.
@Maarten-reinstateMonica, thanks. Updated that sample code to use AES-GCM and more iterations in PBKDF2.
0

I'm trying to encrypt files with a user-supplied password
How can I use a password that is not necessarily a multiple of 16 bytes?

To create an encryption key from a user provided password you may check some examples, generally search for "password based encryption"

Here is en example how to create an encryption key using a user password

 private static final String PBKDF_ALG = "PBKDF2WithHmacSHA256";
 private static final int PBKDF_INTERATIONS = 800000;

// create key from password
 SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance(PBKDF_ALG);
 KeySpec pbeSpec = new PBEKeySpec(password.toCharArray(), psswdSalt, PBKDF_INTERATIONS, SYMMETRIC_KEY.length*8);
 SecretKey pbeSecretKey = secKeyFactory.generateSecret(pbeSpec);
SecretKey secKey = new SecretKeySpec(pbeSecretKey.getEncoded(), SYMMETRIC_KEY_ALG);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(cipherMode, secKey) ;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.