Java AES / CFB8 / NoPadding加密和Charset

时间:2013-04-09 15:26:26

标签: java encryption character-encoding aes

我正在尝试在Java和PHP中加密和解密Strings,因此我使用AES/CFB8/NoPadding可以在双方都工作。

现在,只要我将Charset设置为拉丁语,我的Cryptor就可以很好地退出拉丁字符。但是当我将它设置为UTF-8(这是我在我的数据库中使用的)时,加密没有正确完成。

这是输出:

/*
* Latin (ISO-8859-1) output:
* Original: MiiiMüäöMeeʞ
* Encoded: rQ¶[ÉÐRíD
* Decoded: MiiiMüäöMee?
* 
* UTF-8 output:
* Original: MiiiMüäöMeeʞ
* Encoded: rQ�[�
* Decoded: Mii0SS1])_�ELJI�S�;�W��W?*
*/

由于“ʞ”不是拉丁字符,我知道它无法正确加密。但为什么UTF-8不起作用?

public class Cryptor {

    private Cipher cipher;
    private String secretKey = "1234567890qwertz";
    private String iv = "1234567890qwertz";

    private SecretKey keySpec;
    private IvParameterSpec ivSpec;
    private Charset CHARSET = Charset.forName("ISO-8859-1"); // ISO-8859-1 vs. UTF-8

    public Cryptor() throws CryptingException {

        keySpec = new SecretKeySpec(secretKey.getBytes(CHARSET), "AES");
        ivSpec = new IvParameterSpec(iv.getBytes(CHARSET));
        try {
            cipher = Cipher.getInstance("AES/CFB8/NoPadding");
        } catch (NoSuchAlgorithmException e) {
            throw new SecurityException(e);
        } catch (NoSuchPaddingException e) {
            throw new SecurityException(e);
        }
    }

    public String decrypt(String input) throws CryptingException {

        try {
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            return new String(cipher.doFinal(input.getBytes(CHARSET)), CHARSET).trim();
        } catch (IllegalBlockSizeException e) {
            throw new SecurityException(e);
        } catch (BadPaddingException e) {
            throw new SecurityException(e);
        } catch (InvalidKeyException e) {
            throw new SecurityException(e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new SecurityException(e);
        }
    }

    public String encrypt(String input) throws CryptingException {
        try {
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            return new String(cipher.doFinal(input.getBytes(CHARSET)), CHARSET).trim();
        } catch (InvalidKeyException e) {
            throw new SecurityException(e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new SecurityException(e);
        } catch (IllegalBlockSizeException e) {
            throw new SecurityException(e);
        } catch (BadPaddingException e) {
            throw new SecurityException(e);
        }
    }

    public static void main(String Args[]) {

        try {
            Cryptor c = new Cryptor();
            String original = "MiiiMüäöMeeʞ";
            System.out.println("Original: " + original);
            String encrypted = c.encrypt("MiiiMüäöMeeʞ");
            System.out.println("Encoded: " + encrypted);
            System.out.println("Decoded: " + c.decrypt(encrypted));

        } catch (CryptingException e) {
            e.printStackTrace();
        }
    }

    class CryptingException extends RuntimeException {

        private static final long serialVersionUID = 7123322995084333687L;

        public CryptingException() {
            super();
        }

        public CryptingException(String message) {
            super(message);
        }
    }
}

2 个答案:

答案 0 :(得分:11)

我认为将加密的字节转换为String是一个坏主意。这些字节对任何编码都无效,它们是随机的。

您需要将生成的byte[]编码为base64以获得一致的结果。请参阅sun.misc.BASE64Encoder / sun.misc.BASE64Decoder

Here是将base64 String解码为byte[]的示例,反向过程非常相似。

您可以声明解码器/编码器和班级的顶部:

private final BASE64Decoder base64Decoder = new BASE64Decoder();
private final BASE64Encoder base64Encoder = new BASE64Encoder();

然后在decypt方法中,您需要致电

return new String(cipher.doFinal(base64Decoder.decodeBuffer(input)), CHARSET);

并在您的encrypt方法

return base64Encoder.encode(cipher.doFinal(input.getBytes(CHARSET)));

使用UTF-8输出:

Original: MiiiMüäöMeeʞ
Encoded: clEUtlv2ALXsKYw4ivOfwQ==
Decoded: MiiiMüäöMeeʞ

一方面注意,使用来自sun.*的软件包并不是严格来说的良好做法,因为它们不是Java规范的一部分,因此可能会从版本更改/消失。

Here是一篇关于迁移到Apache Commons Codec实现的文章 这也是Guava API中的a similar类。

答案 1 :(得分:1)

非常感谢你的回答,这对我很有帮助。

以下是我用来加密和解密JAVA和PHP中的JSON内容的工作代码:

/*-- JAVA --*/

/**
 * Is used for encrypting and decrypting Strings and JSONObjects. <br>
 * The JSON Objects can then be sent to a PHP script where they can be encrypted and decrypted with the same algorithm. 
 * @throws CryptingException
 */
public class Cryptor {

    private Cipher cipher;
    private String secretKey = "1234567890qwertz";
    private String iv = "1234567890qwertz";
    private final String CIPHER_MODE = "AES/CFB8/NoPadding";

    private SecretKey keySpec;
    private IvParameterSpec ivSpec;
    private Charset CHARSET = Charset.forName("UTF8");

    public Cryptor() throws CryptingException {

        keySpec = new SecretKeySpec(secretKey.getBytes(CHARSET), "AES");
        ivSpec = new IvParameterSpec(iv.getBytes(CHARSET));
        try {
            cipher = Cipher.getInstance(CIPHER_MODE);
        } catch (NoSuchAlgorithmException e) {
            throw new SecurityException(e);
        } catch (NoSuchPaddingException e) {
            throw new SecurityException(e);
        }
    }

    /**
     * @param input A "AES/CFB8/NoPadding" encrypted String
     * @return The decrypted String
     * @throws CryptingException
     */
    public String decrypt(String input) throws CryptingException {

        try {
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            return  new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(input))); 
        } catch (IllegalBlockSizeException e) {
            throw new SecurityException(e);
        } catch (BadPaddingException e) {
            throw new SecurityException(e);
        } catch (InvalidKeyException e) {
            throw new SecurityException(e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new SecurityException(e);
        }
    }

    /**
     * @param input Any String to be encrypted
     * @return An "AES/CFB8/NoPadding" encrypted String
     * @throws CryptingException
     */
    public String encrypt(String input) throws CryptingException {
        try {
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            return DatatypeConverter.printBase64Binary(cipher.doFinal(input.getBytes(CHARSET))).trim();
        } catch (InvalidKeyException e) {
            throw new SecurityException(e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new SecurityException(e);
        } catch (IllegalBlockSizeException e) {
            throw new SecurityException(e);
        } catch (BadPaddingException e) {
            throw new SecurityException(e);
        }
    }

    /**
     * Encrypts the keys and values of a JSONObject with Cryptor.encrypt(String input)
     * @param o The JSONObject to be encrypted
     * @return A JSONObject with encrypted keys and values
     */
    public JSONObject jsonObjectEncrypt(JSONObject o) {

        Iterator<?> keys = o.keys();
        JSONObject returnObject = new JSONObject();
        while( keys.hasNext() ){
            String key = (String)keys.next();
            returnObject.put(this.encrypt(key), this.encrypt(o.getString(key)));
        }

        return returnObject;
    }

    /**
     * Decrypts the keys and values of a JSONObject with Cryptor.decrypt(String input)
     * @param o The JSONObject to be decrypted
     * @return A JSONObject with decrypted keys and values
     */
    public JSONObject jsonObjectDecrypt(JSONObject o) {

        Iterator<?> keys = o.keys();
        JSONObject returnObject = new JSONObject();
        while( keys.hasNext() ){
            String key = (String)keys.next();
            if(key != null && !key.equals("")) {
                returnObject.put(this.decrypt(key), this.decrypt(o.getString(key)));
            }
        }

        return returnObject;
    }

    /**
     * Encrypts keys and values of every JSONObject in a JSONArray
     * @param a The JSONArray to be encrypted
     * @return A JSONArray with encrypted keys and values
     */
    public JSONArray jsonArrayEncrypt(JSONArray a) {
        JSONArray returnArray = new JSONArray();

        for(int i = 0; i < a.length(); i++) {
                returnArray.put(this.jsonObjectEncrypt((JSONObject)a.get(i)));
        }

        return returnArray;
    }

    /**
     * Decrypts keys and values of every JSONObject in a JSONArray
     * @param a The JSONArray to be decrypted
     * @return A JSONArray with decrypted keys and values
     */
    public JSONArray jsonArrayDecrypt(JSONArray a) {
        JSONArray returnArray = new JSONArray();

        for(int i = 0; i < a.length(); i++) {
            returnArray.put(this.jsonObjectDecrypt((JSONObject)a.get(i)));
        }

        return returnArray;
    }

    public static void main(String Args[]) {

        try {
            Cryptor c = new Cryptor();
            String original = "MiiiMüäöMeeʞ";
            System.out.println("Original: " + original);
            String encrypted = c.encrypt(original);
            System.out.println("Encoded: " + encrypted);
            System.out.println("Decoded: " + c.decrypt(encrypted));

            JSONArray arr = new JSONArray("[{\"id\"=\" 1 ʞ3 \"},{\"id\"=\"4\"}]");

            System.out.println(c.jsonArrayDecrypt(c.jsonArrayEncrypt(arr)).getJSONObject(0).getString("id"));

        } catch (CryptingException e) {
            e.printStackTrace();
        }
    }
}

/*-- PHP ---*/
<html>
<meta charset='utf-8'> 
<?php
$cryptor = new Cryptor();
echo $cryptor->decrypt($cryptor->encrypt("MiiiMüäöMeeʞ"));

class Cryptor
{
    //Use same as in java Cryptor
    private $iv = '1234567890qwertz';
    private $secretKey = '1234567890qwertz'; 

    function encrypt($input) {
        return base64_encode(
        mcrypt_encrypt( 
            MCRYPT_RIJNDAEL_128,
            $this->secretKey,
            $input,  
            MCRYPT_MODE_CFB,
            $this->iv
        )
        );
    }

    function decrypt($input) {
      return mcrypt_decrypt(
        MCRYPT_RIJNDAEL_128, 
        $this->secretKey, 
        base64_decode($input), 
        MCRYPT_MODE_CFB, 
        $this->iv
      );
    }

    function arrayDecrypt($array) {
        $returnArray = array();

        foreach($array as $key=>$value) {
            $newKey = $this->decrypt($key);
            $returnArray[$newKey] = $this->decrypt($value);
        }       

        return $returnArray;
    }

    function arrayEncrypt($array) {
        $returnArray = array();

        foreach($array as $key=>$value) {
            $returnArray[$this->encrypt($key)] = $this->encrypt($value);
        }       

        return $returnArray;
    }
}
?>
</html>

/*-- JAVA --*/ /** * Is used for encrypting and decrypting Strings and JSONObjects. <br> * The JSON Objects can then be sent to a PHP script where they can be encrypted and decrypted with the same algorithm. * @throws CryptingException */ public class Cryptor { private Cipher cipher; private String secretKey = "1234567890qwertz"; private String iv = "1234567890qwertz"; private final String CIPHER_MODE = "AES/CFB8/NoPadding"; private SecretKey keySpec; private IvParameterSpec ivSpec; private Charset CHARSET = Charset.forName("UTF8"); public Cryptor() throws CryptingException { keySpec = new SecretKeySpec(secretKey.getBytes(CHARSET), "AES"); ivSpec = new IvParameterSpec(iv.getBytes(CHARSET)); try { cipher = Cipher.getInstance(CIPHER_MODE); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } catch (NoSuchPaddingException e) { throw new SecurityException(e); } } /** * @param input A "AES/CFB8/NoPadding" encrypted String * @return The decrypted String * @throws CryptingException */ public String decrypt(String input) throws CryptingException { try { cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); return new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(input))); } catch (IllegalBlockSizeException e) { throw new SecurityException(e); } catch (BadPaddingException e) { throw new SecurityException(e); } catch (InvalidKeyException e) { throw new SecurityException(e); } catch (InvalidAlgorithmParameterException e) { throw new SecurityException(e); } } /** * @param input Any String to be encrypted * @return An "AES/CFB8/NoPadding" encrypted String * @throws CryptingException */ public String encrypt(String input) throws CryptingException { try { cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); return DatatypeConverter.printBase64Binary(cipher.doFinal(input.getBytes(CHARSET))).trim(); } catch (InvalidKeyException e) { throw new SecurityException(e); } catch (InvalidAlgorithmParameterException e) { throw new SecurityException(e); } catch (IllegalBlockSizeException e) { throw new SecurityException(e); } catch (BadPaddingException e) { throw new SecurityException(e); } } /** * Encrypts the keys and values of a JSONObject with Cryptor.encrypt(String input) * @param o The JSONObject to be encrypted * @return A JSONObject with encrypted keys and values */ public JSONObject jsonObjectEncrypt(JSONObject o) { Iterator<?> keys = o.keys(); JSONObject returnObject = new JSONObject(); while( keys.hasNext() ){ String key = (String)keys.next(); returnObject.put(this.encrypt(key), this.encrypt(o.getString(key))); } return returnObject; } /** * Decrypts the keys and values of a JSONObject with Cryptor.decrypt(String input) * @param o The JSONObject to be decrypted * @return A JSONObject with decrypted keys and values */ public JSONObject jsonObjectDecrypt(JSONObject o) { Iterator<?> keys = o.keys(); JSONObject returnObject = new JSONObject(); while( keys.hasNext() ){ String key = (String)keys.next(); if(key != null && !key.equals("")) { returnObject.put(this.decrypt(key), this.decrypt(o.getString(key))); } } return returnObject; } /** * Encrypts keys and values of every JSONObject in a JSONArray * @param a The JSONArray to be encrypted * @return A JSONArray with encrypted keys and values */ public JSONArray jsonArrayEncrypt(JSONArray a) { JSONArray returnArray = new JSONArray(); for(int i = 0; i < a.length(); i++) { returnArray.put(this.jsonObjectEncrypt((JSONObject)a.get(i))); } return returnArray; } /** * Decrypts keys and values of every JSONObject in a JSONArray * @param a The JSONArray to be decrypted * @return A JSONArray with decrypted keys and values */ public JSONArray jsonArrayDecrypt(JSONArray a) { JSONArray returnArray = new JSONArray(); for(int i = 0; i < a.length(); i++) { returnArray.put(this.jsonObjectDecrypt((JSONObject)a.get(i))); } return returnArray; } public static void main(String Args[]) { try { Cryptor c = new Cryptor(); String original = "MiiiMüäöMeeʞ"; System.out.println("Original: " + original); String encrypted = c.encrypt(original); System.out.println("Encoded: " + encrypted); System.out.println("Decoded: " + c.decrypt(encrypted)); JSONArray arr = new JSONArray("[{\"id\"=\" 1 ʞ3 \"},{\"id\"=\"4\"}]"); System.out.println(c.jsonArrayDecrypt(c.jsonArrayEncrypt(arr)).getJSONObject(0).getString("id")); } catch (CryptingException e) { e.printStackTrace(); } } } /*-- PHP ---*/ <html> <meta charset='utf-8'> <?php $cryptor = new Cryptor(); echo $cryptor->decrypt($cryptor->encrypt("MiiiMüäöMeeʞ")); class Cryptor { //Use same as in java Cryptor private $iv = '1234567890qwertz'; private $secretKey = '1234567890qwertz'; function encrypt($input) { return base64_encode( mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $this->secretKey, $input, MCRYPT_MODE_CFB, $this->iv ) ); } function decrypt($input) { return mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $this->secretKey, base64_decode($input), MCRYPT_MODE_CFB, $this->iv ); } function arrayDecrypt($array) { $returnArray = array(); foreach($array as $key=>$value) { $newKey = $this->decrypt($key); $returnArray[$newKey] = $this->decrypt($value); } return $returnArray; } function arrayEncrypt($array) { $returnArray = array(); foreach($array as $key=>$value) { $returnArray[$this->encrypt($key)] = $this->encrypt($value); } return $returnArray; } } ?> </html>