3

I'm trying to encrypt and decrypt Strings in Java and PHP, so i use AES/CFB8/NoPadding that sould work on both sides.

Now my Cryptor works quit well with Latin Characters, as long as i set the Charset to Latin. But when i set it to UTF-8 (which is what i use in my DB), the encryption is not properly done.

Here is the output:

/*
* 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?*
*/

Since "ʞ" is not a latin character, I understand it can not be encrypted properly. But why does UTF-8 not work?

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);
        }
    }
}
cheesemacfly
  • 11,622
  • 11
  • 53
  • 72
Michael
  • 53
  • 1
  • 4
  • What character encoding is your source file? Are the characters in the `String` called `original` in your `main` method `UTF-8` or `ISO-8859-1`? – laz Apr 09 '13 at 15:45
  • @laz In the example the OP is using a `String` so there is no encoding per-se. A `String` is `String` (internally stored in UTF-16 but that is of no consequence). – Boris the Spider Apr 09 '13 at 15:48
  • I meant the encoding of the actual `.java` file. since it isn't using the `\u` escape syntax for Unicode characters, I though there might be a chance that the `.java` file was encoded as `ISO-8859-1`. I don't think that is the problem here though after all. – laz Apr 09 '13 at 15:57

2 Answers2

14

I think turning the encrypted bytes into a String is a bad idea. The bytes are not valid for any encoding, they are random.

You need to encode the resulting byte[] to base64 to get a consistent outcome. See sun.misc.BASE64Encoder/sun.misc.BASE64Decoder.

Here is an example of decoding a base64 String to a byte[], the reverse process is very similar.

You can declare the decoder/encoder and the top of the class:

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

Then in your decypt method you need to call

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

And in your encrypt method

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

Output using UTF-8:

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

One side note is that it's not strictly speaking good practice to use packages from sun.* as they are not part of the java spec and hence may change/vanish from version to version.

Here is a little article on moving to the Apache Commons Codec implementation.
This is also a similar class in the Guava API.

Community
  • 1
  • 1
Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • Your answer was right. Thanks alot! I now use DatatypeConverter for the base64 parsing and it works just fine (even with php). I'm going to post my code down here. – Michael Apr 10 '13 at 09:51
2

Thanks alot for that answer, it really helped me alot.

Here is the working code i use to encrypt and decrypt JSON stuff in JAVA and PHP:

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>
Minding
  • 1,383
  • 1
  • 17
  • 29
Michael
  • 53
  • 1
  • 4