1

I'm trying to produce what this site is doing https://codebeautify.org/encrypt-decrypt using Blowfish and CBC

I'm not sure what's the actual term but the encryption method that I'd like to achieve shall produce non-consistent encrypted string despite using same content and key,

For example if I encrypt Hello with key key123, twice, the first result may show abcde, and second should show something else, like fghij. But decrypting both abcde and fghij with key123 shall return the same Hello.

enter image description here

Also may I know what's the type of encoding they are using to produce the final result? Such as hex/base64, because I tried both, but it doesn't seems to produce similar result.

This is what I'm using:

The Crypto class:

public static String enc(String content, String key) {
    String encCon = "";

    try {
        String IV = "12345678";

        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
        Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        String secret = content;
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, new javax.crypto.spec.IvParameterSpec(IV.getBytes("UTF-8")));
        byte[] encoding = cipher.doFinal(secret.getBytes("UTF-8"));

        System.out.println("-- Encrypted -----------");
        encCon = DatatypeConverter.printBase64Binary(encoding);
        System.out.println("-- encCon : " + encCon);
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
    }

    return encCon;
}

public static String dec(String content, String key) {
    String decCon = "";

    try {
        String IV = "12345678";

        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
        Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        // Decode Base64
        byte[] ciphertext = DatatypeConverter.parseBase64Binary(content);

        // Decrypt
        cipher.init(Cipher.DECRYPT_MODE, keySpec, new javax.crypto.spec.IvParameterSpec(IV.getBytes("UTF-8")));
        byte[] message = cipher.doFinal(ciphertext);

        System.out.println("-- Decrypted -----------");
        decCon = new String(message, "UTF-8");
        System.out.println("-- decCon : " + decCon);
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
    }

    return decCon;
}

The calling class (such as Main.java)

// This is what I get from codebeautify site, encrypting Hello with key123
// However, I'm getting javax.crypto.BadPaddingException: Given final block not properly padded
Crypto.dec("08GCpwyZc+qGNuxSvXAD2A==", "key123"); 

// Below 2 lines works fine, the only problem is the result isn't randomized
String encContent = Crypto.enc("Hello", "key123");
Crypto.dec(encContent, "key123");
Chor Wai Chun
  • 3,226
  • 25
  • 41
  • I can't reproduce with the code you poasted (I just replaced the `DatatypeConverter.printBase64Binary(encoding);` by `encCon = Base64.getEncoder().encodeToString(encoding);`). Every call to the method with the same content (the key is ignored anyway) produced the same result. Post a complete minimal example reproducing the problem. – JB Nizet Apr 19 '19 at 06:46
  • 2
    You can get non-deterministic output if you use a random IV. The IV should be random but it is not a secret and you can store it with the ciphertext. The resulting string seems to be base64 encoded. – t.m.adam Apr 19 '19 at 06:50
  • @JBNizet thank you, what do you mean by can't reproduce? the DatatypeConverter is from javax.xml.bind.DatatypeConverter. The same result is the thing I want to avoid, using the external site, every encrypt produces different result, I'd like to achieve this. Also may I know why would you say "the key is ignored anyway"? What does it means? – Chor Wai Chun Apr 19 '19 at 06:51
  • @t.m.adam Oh the IV is something that can be randomized? But it seems I would need the same IV to decrypt it, while the codebeautify site does not seems to require this, may I know what's your opinion on this? – Chor Wai Chun Apr 19 '19 at 06:53
  • 2
    Yes the IV should be random, otherwise it weakens the security of encryption. I think the site concatenates the IV and ciphertext - this is a common practice - so they can decrypt correctly. – t.m.adam Apr 19 '19 at 06:57
  • Ah, sorry, I misread. So you want the same IV, the same key and the same plain text to produce a different encrypted text every time? That won't happen. You need one of those three inputs to be different to produce a different output. Regarding the key: your method takes a key as argument, but never uses it. – JB Nizet Apr 19 '19 at 06:58
  • @JBNizet ohh regarding the key parameter, my mistake, I updated the question with actual code now. But this seems weird, you see, codebeautify only takes 2 parameters, and they generated random result, if they randomize the IV, they should need to know which IV to use for decrypting too, or am I still confused about the IV? – Chor Wai Chun Apr 19 '19 at 07:04
  • @t.m.adam do you have any idea how they know what's the IV to use for decryption, if they are randomizing it during the encryption? – Chor Wai Chun Apr 19 '19 at 07:05
  • 3
    They probably concatenate the IV and ciphertext and then base64-encode the result, pseudocode: `base64(IV + ciphertext)`. In that case the output should be 8 bytes larger and the IV should be the first (or last) 8 bytes. – t.m.adam Apr 19 '19 at 07:09
  • @t.m.adam Ohh wow I'm getting the idea now, let me give it a try, thanks Adam! – Chor Wai Chun Apr 19 '19 at 07:11
  • 1
    You're welcome! So I confirmed that the IV is the first 8 bytes of the decoded output, and also I discovered that they use zero-byte padding. I don't know if Java offers this type of padding (I tested with PHP), but even if it does it's best to use PKCS5 Padding, especially if you want to encrypt binary data. – t.m.adam Apr 19 '19 at 07:43
  • @t.m.adam I got it working in the end, with pkcs5 padding. Unfortunately it seems there's no publicly available Blowfish/CBC/PKCS5 encryption online site to verify I'm doing it right. Would you like to post your PHP code so I can mark it as answer? – Chor Wai Chun Apr 19 '19 at 08:53
  • Thanks but I can't answer a Java question with PHP code. But you could post an answer and accept it, if you want. – t.m.adam Apr 19 '19 at 09:09
  • 1
    The term is [probabilistic encryption](https://en.wikipedia.org/wiki/Probabilistic_encryption). There are some problems. 1. Blowfish is no [more recommended](https://crypto.stackexchange.com/questions/42463/what-encryption-should-i-use-blowfish-twofish-or-threefish). 2. CBC mode has mod need padding that is vunerable to padding oracle attacks and the IV must be [unpredictable](https://crypto.stackexchange.com/q/3883/18298). It is better to use CTR mode that doesn't need padding. Indeed and [authenticated encryption](https://en.wikipedia.org/wiki/Authenticated_encryption) mode as – kelalaka Apr 19 '19 at 12:26
  • as AES-GCM and AES-GCM-SIV much better choice. – kelalaka Apr 19 '19 at 12:27

2 Answers2

3

UPDATE 2019-04-21 09:49 P.M. UTC

After @MaartenBodewes and @MarkJeronimus have pointed out some things to consider, I am updating the answer to make it more correct. But because this question is about the implementation, not about making it more secure, this and old version should be sufficient for at least giving a little insight. Again, more secure solution can be achieved by modifying below code.

Changelog

  • Key Derivation
  • Handling Exceptions with their details
  • Using single SecureRandom instance for each data (iv[8 bytes] and salt[32 bytes])
  • Check for null value and emptiness for plaintext to be encrypted and the encrypted text to be decrypted
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;

public class Crypto {
    private static final char[] tempKey = new char[] {'T', 'E', 'M', 'P', '_', 'G', 'E', 'N', '_', 'K', 'E', 'Y'};
    private static final SecureRandom secureRandomForSalt = new SecureRandom();
    private static final SecureRandom secureRandomForIV = new SecureRandom();

    private static byte[] generateSalt() throws RuntimeException {
        try{
            byte[] saltBytes = new byte[32];

            secureRandomForSalt.nextBytes(saltBytes);

            return saltBytes;
        }
        catch(Exception ex){
            ex.printStackTrace();
            throw new RuntimeException("An error occurred in salt generation part. Reason: " + ex.getMessage());
        }
    }

    public static String enc(String content) throws RuntimeException {
        String encClassMethodNameForLogging = Crypto.class.getName() + ".enc" + " || ";

        byte[] salt;
        byte[] encodedTmpSecretKey;
        SecretKeySpec keySpec;
        Cipher cipher;
        byte[] iv;
        IvParameterSpec ivParameterSpec;
        String finalEncResult;

        if(content == null || content.trim().length() == 0) {
            throw new RuntimeException("To be encrypted text is null or empty");
        }

        System.out.println("-- Encrypting -----------");

        try {
            salt = generateSalt();
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in salt generation part. Reason: " + ex.getMessage());
        }

        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec spec = new PBEKeySpec(Crypto.tempKey, salt, 65536, 256);
            SecretKey tmpSecretKey = factory.generateSecret(spec);

            encodedTmpSecretKey = tmpSecretKey.getEncoded();
            System.out.println("-- Secret Key Derivation in Encryption: " + Base64.getEncoder().encodeToString(encodedTmpSecretKey));
        }
        catch (NoSuchAlgorithmException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
        }
        catch (InvalidKeySpecException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: Key length may not be correct");
        }
        catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage());
        }

        try {
            keySpec = new SecretKeySpec(encodedTmpSecretKey, "Blowfish");
            cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
        }
        catch (NoSuchAlgorithmException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
        }
        catch (NoSuchPaddingException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular padding mechanism is requested but is not available in the environment");
        }
        catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage());
        }

        try {
            iv = new byte[cipher.getBlockSize()];
            secureRandomForIV.nextBytes(iv);
            ivParameterSpec = new IvParameterSpec(iv);
        }
        catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in iv creation part. Reason: " + ex.getMessage());
        }

        try {
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
            byte[] encoding = cipher.doFinal(content.getBytes("UTF-8"));

            String encCon = DatatypeConverter.printBase64Binary(encoding);
            String ivStr = DatatypeConverter.printBase64Binary(iv);
            String saltStr = DatatypeConverter.printBase64Binary(salt);

            System.out.println("-- encCon : " + encCon);
            System.out.println("-- iv : " + ivStr);
            System.out.println("-- salt : " + saltStr);

            finalEncResult = encCon + ":" + ivStr + ":" + saltStr;
            System.out.println("-- finalEncRes : " + finalEncResult + "\n");
        }
        catch (InvalidKeyException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: Most probably you didn't download and copy 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
        }
        catch (InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: IV length may not be correct");
        }
        catch (IllegalBlockSizeException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: The length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher");
        }
        catch (BadPaddingException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: A particular padding mechanism is expected for the input data but the data is not padded properly (Most probably wrong/corrupt key caused this)");
        }
        catch (UnsupportedEncodingException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: The Character Encoding is not supported");
        }
        catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage());
        }

        return finalEncResult;
    }

    public static String dec(String encContent) throws RuntimeException {
        String decClassMethodNameForLogging = Crypto.class.getName() + ".dec" + " || ";

        String decCon;
        byte[] salt;
        byte[] encodedTmpSecretKey;
        SecretKeySpec keySpec;
        Cipher cipher;
        byte[] iv;

        if(encContent == null || encContent.trim().length() == 0) {
            throw new RuntimeException("To be decrypted text is null or empty");
        }

        System.out.println("-- Decrypting -----------");

        try {
            salt = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.lastIndexOf(":") + 1));
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in salt retrieving part. Reason: " + ex.getMessage());
        }

        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec spec = new PBEKeySpec(Crypto.tempKey, salt, 65536, 256);
            SecretKey tmpSecretKey = factory.generateSecret(spec);

            encodedTmpSecretKey = tmpSecretKey.getEncoded();
            System.out.println("-- Secret Key Gathering in Decryption: " + Base64.getEncoder().encodeToString(encodedTmpSecretKey));
        }
        catch (NoSuchAlgorithmException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
        }
        catch (InvalidKeySpecException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: Key length may not be correct");
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage());
        }

        try {
            keySpec = new SecretKeySpec(encodedTmpSecretKey, "Blowfish");
            cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
        }
        catch (NoSuchAlgorithmException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
        }
        catch (NoSuchPaddingException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation : The particular padding mechanism requested is not available in the environment");
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage());
        }

        try {
            iv = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.indexOf(":") + 1, encContent.lastIndexOf(":")));
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in iv creation part. Reason: " + ex.getMessage());
        }

        try {
            cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
            byte[] decoding = cipher.doFinal(Base64.getDecoder().decode(encContent.substring(0, encContent.indexOf(":"))));

            decCon = new String(decoding, "UTF-8");
            System.out.println("-- decCon : " + decCon + "\n");
        }
        catch (InvalidKeyException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: Most probably you didn't download and copy 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
        }
        catch (InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: IV length may not be correct");
        }
        catch (IllegalBlockSizeException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: The length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher");
        }
        catch (BadPaddingException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: A particular padding mechanism is expected for the input data but the data is not padded properly (Most probably wrong/corrupt key caused this)");
        }
        catch (UnsupportedEncodingException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: The Character Encoding is not supported");
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage());
        }

        return decCon;
    }

    public static void main(String args[]) {
        System.out.println("-- Original -------------");
        String plainText = "hello world";
        System.out.println("-- origWord : " + plainText + "\n");

        String e = Crypto.enc(plainText);
        String d = Crypto.dec(e);

        System.out.println("-- Results --------------");
        System.out.println("-- PlainText: " + plainText);
        System.out.println("-- EncryptedText: " + e);
        System.out.println("-- DecryptedText: " + d);
    }
}

Also, executable version is in below;

https://www.jdoodle.com/a/19HT


ORIGINAL ANSWER

I see the written comments meet your needs but I want to share below solution for both your need as a code example and also for future reference;

** Using Randomized IV (Cipher Block Size is given for the IV size but static byte size can also be defined like for example '16 bytes')

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;

public class Crypto {
    public static String enc(String content, String key) {
        String encCon = "";
        String ivStr = "";

        try {
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
            Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

            byte[] iv = new byte[cipher.getBlockSize()];
            SecureRandom secureRandom = new SecureRandom();
            secureRandom.nextBytes(iv);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

            String secret = content;
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
            byte[] encoding = cipher.doFinal(secret.getBytes("UTF-8"));

            System.out.println("-- Encrypted -----------");
            encCon = DatatypeConverter.printBase64Binary(encoding);
            ivStr = DatatypeConverter.printBase64Binary(iv);
            System.out.println("-- encCon : " + encCon);
            System.out.println("-- iv : " + ivStr);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return encCon + ":" + ivStr;
    }

    public static String dec(String encContent, String key) {
        String decCon = "";

        try {
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
            Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

            byte[] iv = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.indexOf(":") + 1));

            String secret = encContent.substring(0, encContent.indexOf(":"));
            cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
            byte[] decoding = cipher.doFinal(Base64.getDecoder().decode(secret));

            System.out.println("-- Decrypted -----------");
            decCon = new String(decoding, "UTF-8");
            System.out.println("-- decCon : " + decCon);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return decCon;
    }

    public static void main(String args[]) {
        String e = Crypto.enc("hello world", "key123");
        String d = Crypto.dec(e, "key123");
    }
}

Note: Of course more secure solution can be achieved. Above solution is given only to give a little insight.

Erdem Savasci
  • 697
  • 5
  • 12
  • 1
    You should not treat a string / password as a key. If you have a password, use a key derivation function such as PBKDF2. Sure, Blowfish/CBC is not as secure as it should be but lets not teach bad crypto practices. The exception handling is very bad, an empty string is a valid ciphertext; if you don't know how to handle (crypto) exceptions, use `throw new RuntimeException(e)` instead of `ex.printStackTrace()`, or take a look [here](https://stackoverflow.com/a/15712409/589259) – Maarten Bodewes Apr 19 '19 at 22:08
  • 1
    Don't create a new `SecureRandom` every time. Reuse the same one as a `static final` (it's threadsafe) – Mark Jeronimus Apr 21 '19 at 08:27
0

The only way you can have different outputs map back to the same input is by adding extra data to your input, and stripping it from the decrypted output. Using PKCS5Padding is not enough as this is not random, and in the worst-case, adds only 1 byte. Using IV is not useful since it needs to be known at time of decryption.

The easiest way is to add a a certain number of bytes (for example equal to the the block size) of random data when encrypting, and ignoring these bytes when decrypting. The name of this random data is 'nonce' from Number Used Once. (Not to be confused with the closely related 'salt' which is a number that you keep for later use).

Btw, I didn't make this to match the website. I don't know what how website is encrypting since it sends all input values to a server and shows the response. Talk about secure...

private static final SecureRandom SECURE_RANDOM = new SecureRandom();

public static String enc(String content, String key) {
    String encCon = "";

    try {
        String IV = "12345678";

        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "Blowfish");
        Cipher        cipher  = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        byte[] nonce = new byte[cipher.getBlockSize()];
        SECURE_RANDOM.nextBytes(nonce);

        // Construct plaintext = nonce + secret
        byte[] secret    = content.getBytes(StandardCharsets.UTF_8);
        byte[] plaintext = new byte[nonce.length + secret.length];
        System.arraycopy(nonce, 0, plaintext, 0, nonce.length);
        System.arraycopy(secret, 0, plaintext, nonce.length, secret.length);

        cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)));
        byte[] encoding = cipher.doFinal(plaintext);

        encCon = DatatypeConverter.printBase64Binary(encoding);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    return encCon;
}

public static String dec(String content, String key) {
    String decCon = "";

    try {
        String IV = "12345678";

        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "Blowfish");
        Cipher        cipher  = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        // Decode Base64
        byte[] ciphertext = DatatypeConverter.parseBase64Binary(content);

        // Decrypt
        cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)));
        byte[] message = cipher.doFinal(ciphertext);

        decCon = new String(message,
                            cipher.getBlockSize(),
                            message.length - cipher.getBlockSize(),
                            StandardCharsets.UTF_8);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    return decCon;
}

Ps. Did you know it's a bad idea to store the secret in a string? Strings are final so the contents cannot be erased. Byte arrays can be erased (for brevity not done in this example). Did you also know that you can just make any Windows program that can peer into the full memory footprint of any other Windows program?

Mark Jeronimus
  • 9,278
  • 3
  • 37
  • 50
  • Thanks for introducing the new element "nonce" to the picture, do you think including nonce, randomized IV altogether would produce an even better security? Yes I'm aware that even opening the .class file with notepad can already have full view of my keys. AWS KMS is one of the choice but our clients may want this project in closed network environment, hence we are considering the use of secured flash drive. – Chor Wai Chun Apr 19 '19 at 15:34
  • CBC requires a random IV. The IV is exactly for making the cipher probabilistic. The nonce only expands the ciphertext for no reason, and the first part of your output won't be pseudo-random. This answer only shows that you don't understand how IV's and nonces work, sorry. @ChorWaiChun No, combining an IV and nonce is **not** a good idea, you don't need a nonce for CBC (possibly you do for other parts of a protocol, of course, like a message sequence number). – Maarten Bodewes Apr 19 '19 at 22:16
  • OK, you didn't include the IV, so your ciphertext is probabilistic. Your nonce just takes the place of the IV, and you have one unnecessary block encrypt. – Maarten Bodewes Apr 19 '19 at 22:26
  • I don't see the problem. In the other answer the IV is randomized, THEN appended to the ciphertext. That's just the same as using a hard-coded IV (i.e. no IV as far as security is concerned) and adding one block to encrypt. – Mark Jeronimus Apr 21 '19 at 08:25
  • I think you're also confusing terms here. 'probabilistic' means that some process has a probability (usually close to 100%) of giving the correct output. – Mark Jeronimus Apr 21 '19 at 08:33