3

I am trying to convert the below Command in SSL to Java

openssl enc -in <inputfilename> -out <file_to_encrypt> -e -aes256 -k s_key

s_key is the file provided which contains the key that will be used to encrypt and decrypt

Steps to be done: 1 - Read the key file 2 - Use it to AES encryption to encrypt file inputfilename 3 - Use the key to decrypt the same.

I am new to encryption and below is the code i have written so far to encrypt but I am getting issue.

Path path = Paths.get("/home/debashishd/Downloads/s_key");
String content = new String(Files.readAllBytes(Paths.get("/home/debashishd/Downloads/s_key")));
    
String Test_message = "Hello this is Roxane";
    
byte[] keyValue = Files.readAllBytes(path);
ByteArrayInputStream byteIS = new ByteArrayInputStream(keyValue);
    
OpenSSLPBEParametersGenerator gen = new OpenSSLPBEParametersGenerator();
OpenSSLPBEParametersGenerator gen1 = gen;
byte[] saltBytes = Hex.decode(salt.getBytes());
gen1.init(keyValue);
CipherParameters cp = gen1.generateDerivedParameters(256);

byte[] keyBytes = ((KeyParameter)cp);           
SecretKeySpec secretKey = new SecretKeySpec(keyBytes,"AES");
System.out.println(secretKey);
    
Cipher cipher;
Cipher decryptCipher;
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey,new IvParameterSpec(new byte[16]));

String encrypt_value = getEncoder().encodeToString(cipher.doFinal(Test_message.getBytes(StandardCharsets.UTF_8)));
    
System.out.println("Encrypted value: " + encrypt_value);
    
decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, secretKey,new IvParameterSpec(new byte[16]));
String Decrypt_result = new String(decryptCipher.doFinal(getDecoder().decode(encrypt_value)));
System.out.println("Decrypted value: " + Decrypt_result);

Is there any changes need to be done to achieve the above encrypt and decrypt

Expected output:

Encrypted value: jvggHDPa58+/zQ+HyGUEk/ypndXbatE+b+hBBqiinABOIwxJ7FXqnDb5j813fPwwm/D6d2Y2uh+k4qD77QMqOg==
Decrypted value: Hello this is Roxane
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Doe this help? https://stackoverflow.com/questions/29354133/how-to-fix-invalid-aes-key-length – stefanobaghino Jul 04 '22 at 12:51
  • @stefanobaghino No I referred your code to read the key file and use it but i am getting the same error when reading the file – Roxane Felton Jul 04 '22 at 13:00
  • 2
    `-k` means that `s_key` is interpreted as password and the actual key and an IV are derived from it using a key derivation function (KDF) in combination with a random (8 bytes) salt (`-aes256` means AES-256 in CBC mode). The output is in OpenSSL format: *||*. The code is missing the key derivation (`EVP_BytesToKey`) and the formatting of the result. [Here](https://stackoverflow.com/a/11786924) you can find an implementation of the KDF for Java. – Topaco Jul 04 '22 at 13:08
  • @Topaco I was trying to follow the link Provided I have few doubts what is the ARG_INDEX_PASSWORD and ARG_INDEX_FILENAME in the example – Roxane Felton Jul 04 '22 at 19:52
  • @Topaco I want to first use the file to encrypt aswell – Roxane Felton Jul 04 '22 at 20:00
  • You should just copy the `EVP_BytesToKey()` method from the linked answer to derive key and IV (you don't need the constants `ARG_...` at all, they don't appear anywhere in `EVP_BytesToKey`). After encryption, concatenate salt and ciphertext in OpenSSL format. If necessary, add the file I/O. That's all. – Topaco Jul 04 '22 at 20:21
  • @Topaco Ok. what will be the values for these parameters required by EVP_BytesToKey() ---- int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data, int count – Roxane Felton Jul 04 '22 at 20:42
  • The BouncyCastle class `OpenSSLPBEParametersGenerator` used in your other question also implements `EVP_BytesToKey()`. Instead of the linked implementation in my comment you can of course also use `OpenSSLPBEParametersGenerator`. Note that OpenSSL originally used MD5 as default digest and since v1.1.0 SHA256. Depending on your OpenSSL version you may have to change the digest in your code accordingly. – Topaco Jul 04 '22 at 21:01
  • @Topoco using some changes to this code I was able to encrypt and decrypt a String. I am reposting my code can you review the same and let me know if i need to do any changes to encryption and decryption part of the code – Roxane Felton Jul 04 '22 at 21:24
  • For compatibility with OpenSSL, 1. also the IV must be derived with `EVP_BytesToKey` (the first 32 bytes are the key, the following 16 bytes the IV), 2. a random 8 bytes salt must be generated and used, and 3. the OpenSSL format must be applied. Note that for code review https://codereview.stackexchange.com/ is more appropriate. – Topaco Jul 04 '22 at 21:35
  • @Topaco how to derive salt from key using OpenSSLPBEParametersGenerator? can you help me with a code – Roxane Felton Jul 05 '22 at 05:44
  • See my answer please. – Topaco Jul 05 '22 at 09:41

2 Answers2

2

For compatibility with the OpenSSL statement:

  • a random 8 bytes of salt must be generated
  • a 32 bytes key and 16 bytes IV must be derived using EVP_BytesToKey() and the salt
  • the result must be given in OpenSSL format:
    <ASCII Encoding of Salted__>|<salt>|<ciphertext>

For EVP_BytesToKey() you can apply the OpenSSLPBEParametersGenerator class you already suggested.

EVP_BytesToKey() uses a digest. In earlier versions of OpenSSL MD5 was applied by default, from v1.1.0 SHA256. The digest can be set with the -md5 option. Code and OpenSSL statement must both use the same digest to be compatible. OpenSSLPBEParametersGenerator allows the specification of the digest in the constructor, default is MD5.

The following code, is based on your code, i.e. uses OpenSSLPBEParametersGenerator for EVP_BytesToKey() but additionally considers above points. Instead of encrypting the entire data, streams are applied and the data is encrypted chunk by chunk, so that even large files can be processed:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;

...

String inputPath = "...";
String outputPath = "...";
String passwordStr = "...";

// Generate random 8 bytes salt
SecureRandom random = new SecureRandom();
byte salt[] = new byte[8];
random.nextBytes(salt);

// Derive 32 bytes key (AES_256) and 16 bytes IV
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
pbeGenerator.init(password, salt);
ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128); // keySize, ivSize in bits

// Encrypt with AES-256, CBC using streams
try (FileOutputStream fos = new FileOutputStream(outputPath)) {

    // Apply OpenSSL format
    fos.write("Salted__".getBytes(StandardCharsets.UTF_8));
    fos.write(salt);

    // Encrypt chunkwise (for large data)
    PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
    cipher.init(true, parameters);
    try (FileInputStream fis = new FileInputStream(inputPath);
         CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {
        int bytesRead = -1;
        byte[] buffer = new byte[64 * 1024 * 1024]; // chunksize, e.g. 64 MiB
        while ((bytesRead = fis.read(buffer)) != -1) {
            cos.write(buffer, 0, bytesRead);
        }    
    }
}

A file encrypted with this code can be decrypted with OpenSSL as follows:

openssl enc -d -aes256 -k <passpharse> -in <enc file> -out <dec file>

Therefore the code is the programmatic analogue of the OpenSSL statement posted at the beginning of your question (whereby the ambiguity regarding the digest still has to be taken into account, i.e. for an OpenSSL version from v1.1.0 SHA256 has to be used instead of MD5).

Note that because of the random salt, different key/IV pairs are generated for each encryption, so there is no reuse, which also removes the vulnerability mentioned in the comment.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • OpenSSLPBEParametersGenerator() does it accept a argument new MD5Digest()?? – Roxane Felton Jul 05 '22 at 16:12
  • @RoxaneFelton - Yes. See the documentation of [`OpenSSLPBEParametersGenerator`](https://www.bouncycastle.org/docs/docs1.8on/org/bouncycastle/crypto/generators/OpenSSLPBEParametersGenerator.html#OpenSSLPBEParametersGenerator-org.bouncycastle.crypto.Digest-). – Topaco Jul 05 '22 at 16:23
  • yes there are two constructors and one accepts the digest but i dont know why Intellij is not detecting it with all imports and its shows error constructor OpenSSLPBEParametersGenerator in class org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator cannot be applied to given types; required: no arguments found: org.bouncycastle.crypto.digests.MD5Digest – Roxane Felton Jul 05 '22 at 17:09
  • @RoxaneFelton - The `OpenSSLPBEParametersGenerator` constructor with the digest as parameter is available since release 1.69 (June 2021), see [here](https://www.bouncycastle.org/releasenotes.html). You may be using an older BC version. – Topaco Jul 05 '22 at 17:24
  • Yes Indeed. I am using lower version will update it – Roxane Felton Jul 05 '22 at 19:30
  • What changes will be needed for decryption? – Roxane Felton Jul 07 '22 at 18:28
  • Decryption is beyond the scope of this question. Please post a new question with all the information needed to answer it. If necessary, you can reference this question/answer. If my answer provided a solution to your problem, you could kindly accept it. – Topaco Jul 07 '22 at 18:46
  • Thanks @Topaco. i will surely do the needful – Roxane Felton Jul 07 '22 at 19:35
  • I have posted the question for decryption. Can you help https://stackoverflow.com/questions/72944988/java-aes-decryption-with-keyfile-using-bouncycastle-ssl – Roxane Felton Jul 11 '22 at 21:49
0

I was able to achive my result with the below code. The code can indeed be written in a better way and remove the unnecessary initialisation. I will be doing the same in my practical Implementation

public class test2 {
    public static void main(String[] args) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {

        Path path = Paths.get("/home/debashishd/Downloads/key");
        String Test_message = FileUtils.readFileToString(new File("/home/debashishd/Downloads/sample.txt"), StandardCharsets.UTF_8);

        /**
         * Read the keyfile provided to encrypt
         */
        byte[] keyValue = Files.readAllBytes(path);
        OpenSSLPBEParametersGenerator gen = new OpenSSLPBEParametersGenerator();
        OpenSSLPBEParametersGenerator gen1 = gen;
        final String salt = "";
        byte[] saltBytes = Hex.decode(salt.getBytes());
        gen1.init(keyValue,saltBytes);
        CipherParameters cp = gen1.generateDerivedParameters(256);
        byte[] keyBytes = ((KeyParameter)cp).getKey();
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes,"AES");

        /**
         * Initialize the Encrypt Ciphers and encrypt the data from input file
         */
        Cipher encryptcipher;
        Cipher decryptCipher;
        encryptcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        encryptcipher.init(Cipher.ENCRYPT_MODE, secretKey,new IvParameterSpec(new byte[16]));
        String encrypt_value = getEncoder().encodeToString(encryptcipher.doFinal(Test_message.getBytes(StandardCharsets.UTF_8)));
        System.out.println("Encrypted value: " + encrypt_value);

        /**
         * Initialize the Decrypt Ciphers and decrypt the encrypted data
         */
        decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        decryptCipher.init(Cipher.DECRYPT_MODE, secretKey,new IvParameterSpec(new byte[16]));
        String Decrypt_result = new String(decryptCipher.doFinal(getDecoder().decode(encrypt_value)));
        System.out.println("Decrypted value: " + Decrypt_result);

    }
}
  • However, this is not compatible with the above OpenSSL expression (for the reasons described in the comments), i.e. a plaintext encrypted with the code cannot be decrypted with OpenSSL and vice versa. In addition, the missing salt and the static IV are security vulnerabilities. – Topaco Jul 04 '22 at 22:48