3

So I'm trying to decrypt a message in a method but it doesn't work because I need to do cipher.init(Cipher.ENCRYPT_MODE, secret) before I try to add new IvParameterSpec(iv) to cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));. Otherwise, it just returns a NullPointerException I'm wondering if it's possible to do this in a method rather than writing it all the time. I can't really think of a solution so that's why I'm here. Encrypt works fine but not Decrypt.

Project Running: JRE 7

Encrypt Code:

public static String encrypt(String str) {
    try {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(str.toCharArray(), salt, 65536, 256);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret); //<--- Need to do this before writing IvPerameterSpec,
        // But I think that it's not possible if I have it in another method.
        byte[] encryptedText = cipher.doFinal(str.getBytes("UTF-8"));

        return new String(encryptedText);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

Decrypt Code:

public static String decrypt(String str) {
    try {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(str.toCharArray(), salt, 65536, 256);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        AlgorithmParameters params = cipher.getParameters();
        byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
        cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
        //                                                ^^^ Returns NullPointerException

        byte[] ciphertext = cipher.doFinal(str.getBytes("UTF-8"));
        String decryptedText = new String(cipher.doFinal(ciphertext), "UTF-8");

        return new String(decryptedText);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

Exception:

java.lang.NullPointerException
        at me.Sansanvi.Encryption.api.ComputerAPI.decrypt(ComputerAPI.java:149)
        at me.Sansanvi.Encryption.EncryptionMain.initializeFiles(EncryptionMain.java:46)
        at me.Sansanvi.Encryption.EncryptionMain.<init>(EncryptionMain.java:36)
        at me.Sansanvi.Encryption.EncryptionMain$1.run(EncryptionMain.java:23)
        at java.awt.event.InvocationEvent.dispatch(Unknown Source)
        at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
        at java.awt.EventQueue.access$200(Unknown Source)
        at java.awt.EventQueue$3.run(Unknown Source)
        at java.awt.EventQueue$3.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
        at java.awt.EventQueue.dispatchEvent(Unknown Source)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
        at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
        at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
        at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
        at java.awt.EventDispatchThread.run(Unknown Source)

I changed the methods to the following and they work:

private static final String ALGORITHM = "AES";
    public static byte[] encrypt(byte[] str) {
    try {
        SecretKeySpec secretKey = new SecretKeySpec("MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(str);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
public static byte[] decrypt(byte[] str) {
    try {
        SecretKeySpec secretKey = new SecretKeySpec("MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(str);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

I don't get any errors/exceptions anymore but I get this in the console when I close my application:

[0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64)
[0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64)
[0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64)
[0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64)
[0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64)
[0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64)
[0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64)
[0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64)
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
BeastlyMC956
  • 70
  • 1
  • 3
  • 10

1 Answers1

14

You have at least four problems and you discovered only one of them.

  1. The IV is generated during encryption and used. The exact same IV needs to be used during decryption. The IV is not supposed to be secret. You can simply send/store it along with the ciphertext. Usually, the IV is stored in front of the ciphertext and sliced off before decryption.
    For CBC mode, it just needs to be unpredictable (read: random). For CTR mode, it would need to be unique (when the same key is used).

  2. The random salt needs to be treated in the exact same way as the IV: it is not secret and can be written in front of the ciphertext.

  3. String is not a container for binary data. When you use new String(encryptedText), you probably lose some unprintable bytes which breaks your ciphertext and makes the plaintext unrecoverable. You need to use something like Base64 or Hex encoding to represent binary data as printable text.

  4. If you encrypt something, you need two things: a plaintext and a password (used for key derivation). You also need to two things during decryption: a ciphertext and a password (key). You're using the same string value during encryption. The ciphertext is then mangled in such a way that you would need the original string value during decryption. And that would defeat the purpose of encrypting it in the first place. Your encryption method is basically a hash function.
    Thanks to wittyameta for pointing this out.

So the resulting code would look similar to this:

public static String encrypt(String str, String password) {
    try {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret);
        AlgorithmParameters params = cipher.getParameters();
        byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
        byte[] encryptedText = cipher.doFinal(str.getBytes("UTF-8"));

        // concatenate salt + iv + ciphertext
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(salt);
        outputStream.write(iv);
        outputStream.write(encryptedText);

        // properly encode the complete ciphertext
        return DatatypeConverter.printBase64Binary(outputStream.toByteArray());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

public static String decrypt(String str, String password) {
    try {
        byte[] ciphertext = DatatypeConverter.parseBase64Binary(str);
        if (ciphertext.length < 48) {
            return null;
        }
        byte[] salt = Arrays.copyOfRange(ciphertext, 0, 16);
        byte[] iv = Arrays.copyOfRange(ciphertext, 16, 32);
        byte[] ct = Arrays.copyOfRange(ciphertext, 32, ciphertext.length);

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
        byte[] plaintext = cipher.doFinal(ct);

        return new String(plaintext, "UTF-8");
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

I've used this for Java 7 compatible Base64 encoding/decoding.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • I get `java.security.InvalidKeyException: Illegal key size or default parameters` in encrypt. – BeastlyMC956 Jul 03 '17 at 19:07
  • Either change the `256` to `128` in `PBEKeySpec` to use AES-128 instead of AES-256 or install the [unlimited strength policy files](https://stackoverflow.com/q/6481627/1816580). – Artjom B. Jul 03 '17 at 19:37
  • Changing it to `128` works, Thanks for that! :D, but is there a way to not make the plaintext random? Because I want to write it to a file and then read it later on. It will make a random new String each time I restart my program. – BeastlyMC956 Jul 03 '17 at 19:44
  • Yes, but that decreases the security of your solution. You could set the salt and IV to a static value. – Artjom B. Jul 03 '17 at 19:47
  • How do you know that the iv will always be between 16 and 32? What if its greater? – Nikhil Sep 25 '18 at 19:31
  • @Nikhil For CBC, the IV MUST be as big as the block size of the used block cipher. That is 16 bytes for AES. Note that key size is a different metric and has no dependency to the IV. CFB and OFB [modes](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation) also need an IV exactly as big as the block size. In CTR mode we usually have an IV as big as the block size but a smaller nonce that is combined with an initial counter to make up the IV. – Artjom B. Sep 25 '18 at 20:11
  • @ArtjomB. Shouldn't the password used to create `PBEKeySpec` be same in encrypt and decrypt methods? You have used `str.toCharArray()` in both the methods. However, `str` argument passed would be different in both the cases! – wittyameta Oct 05 '18 at 05:49
  • @wittyameta the str argument is not passed for key creation, please check the code once again thoroughly, Its the password which is passed – yolob 21 Sep 03 '20 at 10:03