基于CBC的Java河豚加密

基于CBC的Java河豚加密,java,encryption,blowfish,Java,Encryption,Blowfish,我正在尝试使用Blowfish和CBC 我不确定实际的术语是什么,但我想要实现的加密方法将产生不一致的加密字符串,尽管使用相同的内容和密钥 例如,如果我用keykey123加密Hello,两次,第一个结果可能显示abcde,第二个结果应该显示其他内容,如fghij。但是用key123解密abcde和fghij将返回相同的Hello 我还可以知道他们用来产生最终结果的编码类型吗?例如hex/base64,因为我尝试了这两种方法,但似乎并没有产生类似的结果 这就是我正在使用的: 加密类: publ

我正在尝试使用
Blowfish
CBC

我不确定实际的术语是什么,但我想要实现的加密方法将产生不一致的加密字符串,尽管使用相同的内容和密钥

例如,如果我用key
key123
加密
Hello
,两次,第一个结果可能显示
abcde
,第二个结果应该显示其他内容,如
fghij
。但是用
key123
解密
abcde
fghij
将返回相同的
Hello

我还可以知道他们用来产生最终结果的编码类型吗?例如hex/base64,因为我尝试了这两种方法,但似乎并没有产生类似的结果

这就是我正在使用的:

加密类:

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;
}
调用类(如Main.java)


让不同的输出映射回同一输入的唯一方法是向输入中添加额外数据,并将其从解密的输出中剥离。使用PKCS5Padding是不够的,因为这不是随机的,在最坏的情况下,只添加1个字节。使用IV没有用处,因为需要在解密时知道它

最简单的方法是在加密时添加一定数量的随机数据字节(例如,等于块大小),并在解密时忽略这些字节。此随机数据的名称为“nonce”,来自使用过一次的数字。(不要与密切相关的“salt”混淆,salt是一个数字,您可以保留该数字供以后使用)

顺便说一句,我做这个不是为了匹配网站。我不知道网站是如何加密的,因为它将所有输入值发送到服务器并显示响应。谈论安全

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;
}

另外,你知道把秘密存储在字符串中是个坏主意吗?字符串是最终的,因此无法删除内容。字节数组可以被擦除(为了简洁起见,本例中没有这样做)。您是否还知道,您可以只制作任何可以查看任何其他Windows程序的全部内存占用的Windows程序?

更新2019-04-21 09:49 p.M.UTC

@ MaMattBoDeWes和@ McJeloimimu指出了一些需要考虑的问题,我正在更新答案以使其更正确。但是因为这个问题是关于实现的,而不是关于使它更安全,所以这个版本和旧版本至少应该足以提供一些见解。同样,通过修改下面的代码可以实现更安全的解决方案

变更日志

  • 密钥派生
  • 处理异常及其详细信息
  • 对每个数据使用单个SecureRandom实例(iv[8字节]和salt[32字节])
  • 检查要加密的明文和要解密的加密文本的空值和空值
另外,可执行版本如下所示


原始答案

我看到书面意见满足您的需要,但我想分享以下解决方案,既作为您的代码示例的需要,也作为未来的参考

**使用随机化IV(为IV大小提供密码块大小,但也可以定义静态字节大小,例如“16字节”)


注意:当然可以实现更安全的解决方案。给出上述解决方案只是为了提供一点见解。

如果使用随机IV,您可以获得非确定性输出。IV应该是随机的,但它不是秘密,您可以将其与密文一起存储。结果字符串似乎是base64编码的。是的,IV应该是随机的,否则会削弱加密的安全性。我认为该网站将IV和密文连接起来——这是一种常见的做法——这样他们就可以正确解密。他们可能将IV和密文连接起来,然后对结果进行base64编码,伪代码:
base64(IV+密文)
。在这种情况下,输出应该大8字节,IV应该是第一个(或最后一个)8字节。不客气!所以我确认IV是解码输出的前8个字节,并且我发现它们使用零字节填充。我不知道Java是否提供了这种类型的填充(我用PHP进行了测试),但即使提供了,最好还是使用PKCS5填充,特别是如果您想要加密二进制数据的话。有一些问题。1.河豚不是。2.CBC模式有mod need padding,这对于填充oracle攻击是脆弱的,并且IV必须是。最好使用不需要填充的CTR模式。事实上,作为在图片中引入新元素“nonce”的银行,您认为将nonce、随机化IV全部包括在内会产生更好的安全性吗?是的,我知道即使用记事本打开.class文件也可以看到我的钥匙的全貌。AWS KMS是一种选择,但我们的客户可能希望在封闭的网络环境中进行该项目,因此我们正在考虑使用安全闪存驱动器。CBC需要随机IV。IV正是为了使密码具有概率性。nonce只会无缘无故地扩展密文,并且输出的第一部分不会是伪随机的。这个答案只表明你不明白IV和nonce是如何工作的,对不起@ChorWaiChun不,将IV和nonce结合起来不是一个好主意,CBC不需要nonce(当然,协议的其他部分也可能需要nonce,比如消息序列号)。好的,你没有包含IV,所以你的密文是概率的。你的nonce只是取代了IV,你有一个不必要的块加密。我看不出问题。在另一个答案中,IV是随机的,然后附加到密文中。这与使用硬编码IV(即,就安全性而言,不使用IV)并添加一个块进行加密是一样的。我认为您在这里也混淆了术语“概率”表示某些进程有给出正确输出的概率(通常接近100%)。您不应将字符串/密码视为
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;
}
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);
    }
}
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");
    }
}