Using the Cipher class

I have tried and tried, but I cannot seem to figure out how to decrypt things in Java. I encrypted a string using PHP's mcrypt_encrypt function:

mcrypt_encrypt (MCRYPT_BLOWFISH, 'password', 'hi', MCRYPT_MODE_ECB); // Returns 9Y g

but I can't get Java to decrypt it. This is my current code:

import javax.crypto.*;

import javax.crypto.spec.*;

publicclass Decrypt

{

publicstaticvoid main (String[] args)

{

try{

Cipher ci = Cipher.getInstance ("Blowfish");

ci.init (Cipher.DECRYPT_MODE,new SecretKeySpec ("password".getBytes(),"Blowfish"));

// This gives me a BadPaddingException.

System.out.println (new String (ci.doFinal ("9Y g".getBytes())));

}catch(Exception ex){ex.printStackTrace();}

}

}

I can get PHP to decrypt it using:

mcrypt_decrypt (MCRYPT_BLOWFISH, 'password', '9Y g', MCRYPT_MODE_ECB); // Returns hi (and then 6 null chars)

I guess there are some extra steps PHP is doing for me that Java isn't? Mainly I notice that PHP returns strings in multiples of 8 using null chars. Is that the "padding" Java is talking about? I would greatly appreciate any help.

[1914 byte] By [spanglercoa] at [2007-11-27 11:02:04]
# 1

"9Y g".getBytes()

implies that you converted the bytes of your PHP encryption to a string. You should keep them as bytes since the resulting string will depend on the character encoding being used and more often than not corrupts the bytes.

sabre150a at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 2

PHP's mcrypt_encrypt returns a string.

spanglercoa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 3

which you have to treat as an array of bytes. If you turn the PHP 'string' into a Java String you will lose data.

ejpa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 4

Right, I forgot about that part. This my first inter-language project, so the differences between PHP and Java finally matter. I don't have time tonight, but tomorrow I'll modify the PHP code to get me the bytes.

Oh, and thanks already for the help so far.

Message was edited by:

spanglerco

spanglercoa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 5

Actually, I wanted to give it one more try, but it didn't work:

String hexStr = "9f1301d239592067";

byte[] byteStr = new java.math.BigInteger (hexStr, 16).toByteArray();

System.out.println (new String (ci.doFinal (byteStr)));

I got an exception about the size not being a multiple of 8. So I added 7 sets of 00s to the end of the string and got a BadPaddingException.

By the way, this is not how I would do the final code, I just want to get the Cipher class working. Then I'll make the code nice. Now I'm off to bed.

spanglercoa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 6

Can you post your PHP and your Java. It is very difficult to see what is wrong when all you have is a small fragment of the code being used.

sabre150a at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 7

Because this interested me, I have just learned enough PHP and mcrypt to be able to create an example able to encrypt in PHP/Java and decrypt in Java/PHP. It is very easy but the following should be noted -

1) In PHP, padding of bytes of zero are used. This means that in one's Java one needs to use "NoPadding" and remove the trailing zero byes before converting back to a String. It would not be a big problem to create a PHP function to pad using PKCS5. I did this in Python.

2) The output of PHP encryption is a string not bytes. The characters of the string need to be converted to bytes before being used in Java. I created two very simple PHP functions that converted the string to hex characters and hex characters to string - I expect that a little more searching would have found PHP library functions to do this. In my Java I used the Jakarta Commons Code class Hex.

3) The key in PHP is a string which I use a hex representation and convert to the string needed. In Java I used the Jakarta Commons Codec class Hex.

4) If using CBC mode then the IV also needs to be passed between the PHP and the Java. My preferred approach to this is to create a random IV and prepend this to the encrypted bytes. Schneier has shown that this has no security implications.

One interesting point I noted. Many of the PHP examples on the web seem to have come from the same source and for some reason use an IV when using ECB mode! See http://uk.php.net/mcrypt_encrypt . As I expected, this IV seems to do nothing useful. I wonder if I am missing something or is is just a documentation error or are the people who wrote the documentation just not familiar with cryptography?

sabre150a at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 8

Okay, I added the ECB/NoPadding option. Also, instead of me trying to convert to bytes or hex or copying and pasting, I had my Java program download the exact bytes output by the PHP script's print function. Finally, this worked! In the final system, both the Java and the PHP will already know the password, so I don't have to worry about transferring that.

However, when I tried to use a real password (32-byte md5 hash), it gave me an InvallidKeyException. The Blowfish specification says I can use keys up to 448 bits. Am I creating the Key incorrectly? I tried using PBEKeySpec instead, but it said it couldn't do Blowfish.

BufferedReader in = ...

String key = in.readLine();

System.out.println (key);

in.close();

Cipher ci = Cipher.getInstance ("Blowfish/ECB/NoPadding");

ci.init (Cipher.DECRYPT_MODE, new SecretKeySpec ("password".getBytes(), "Blowfish"));

System.out.println (new String (ci.doFinal (key.getBytes())));

spanglercoa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 9

1) Don't use Readers for anything to do with encryption. Use a DataInputStream wrapped round a a FileInputStream to read the key using the readFully() method.

2) When converting to and from a String always specify the encoding. It is rarely safe to use the default encoding as you do. Your PSP encoding may not be the same as your Java default encoding.

3) I don't see where you have stripped the padding from your decrypted data.

4) I don't see where the MD5 hash comes into your code.

5) What is the relationship between your password and your key. It is not at all obvious to me.

6) MD5 produces 16 bytes (32 hex bytes). I can't believe that you want the hex bytes as the key so you will need to hex decode them before using them as the key.

Without seeing the full PSP code and the full Java code I can't do anything else to help you.

Message was edited by:

sabre150

sabre150a at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 10

Okay, here's everything...

// This class returns a String md5 hash from a String message.

public class MD5

{

// Convert the byte array to a hex string so that it can be sent to the server

// and so that PHP's md5 function returns the same result.

public static String hex (byte[] array)

{

StringBuffer sb = new StringBuffer();

for (int i = 0; i < array.length; ++i)

sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1,3));

return sb.toString();

}

// Returns the hex String of the md5 hash of message.

public static String md5 (String message)

{

try

{

MessageDigest md = MessageDigest.getInstance("MD5");

return hex (md.digest(message.getBytes("CP1252")));

} catch (NoSuchAlgorithmException e) {

} catch (UnsupportedEncodingException e) {

}

return null;

}

}

//-

// The public class before this has a String for the username, password,

// and url that the user entered.

private class LoginTask extends SwingWorker<String, Object>

{

private URL managerURL;

protected String doInBackground()

{

try

{

managerURL = new URL (url);

// Hash the password before sending to the server.

password = MD5.md5 (password);

// Send the POST request to the server.

BufferedReader in = postData ("action=login&version=0.1&platform=exe&username=" + URLEncoder.encode (username, "UTF-8") + "&password=" + URLEncoder.encode (password, "UTF-8"));

// If the username and password (hash) are valid, return the decryption key.

// The key will be encrypted using the user's password (hash) for security.

String key = in.readLine();

if (key == null)

return null; // Invalid authentication.

in.close();

Cipher ci = Cipher.getInstance ("Blowfish/ECB/NoPadding");

ci.init (Cipher.DECRYPT_MODE, new SecretKeySpec (password.getBytes(), "Blowfish"));

// Return the decrypted decryption key.

return new String (ci.doFinal (key.getBytes()));

}

catch (Exception ex)

{

ex.printStackTrace();

return null;

}

}

private BufferedReader postData (String data) throws UnsupportedEncodingException, IOException

{

URLConnection conn = managerURL.openConnection();

conn.setDoOutput (true);

OutputStreamWriter out = new OutputStreamWriter (conn.getOutputStream());

out.write (data);

out.flush();

out.close();

return new BufferedReader (new InputStreamReader (conn.getInputStream()));

}

}

//-

// Here's the PHP code.

// ...

// All of the authentication code. This part works just fine how it is.

// It uses a mysql_query to confirm the username and password (hash)

// ...

// CACHE_KEY is defined as a 32 character string constant and is the

// decryption key that will be encrypted with the user's password (hash).

$key = @mcrypt_encrypt (MCRYPT_BLOWFISH, $_POST['password'], CACHE_KEY, MCRYPT_MODE_ECB);

// Send it to the client.

print $key;

So, looking at your number 6, I guess I need a method that does the opposite of MD5.hex()? As for number 1, I am using a UrlConnection and will eventually be receiving much more data at the same time (>2 MB).

Should I use the same encoding that my MD5.md5 method uses (CP1252)?

What padding?

And sorry for the bad variable names. Hopefully the comments make up for it.

spanglercoa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 11

The key being printed by PHP is in binary but you are reading it as a line with a BufferedReader so it is being destroyed, similar to the byte/String issue discussed above. Also the binary key can contain line-terminators internally so it is liable to be misinterpreted when read as a line. You need to hex-encode the key at the PHP level then decode it when you read the line before you decrypt it.

ejpa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 12

Okay, I finally got it working! Thanks to everyone who helped. Here is what I did:

On the PHP side, I pack()ed the password md5 hash to convert it back to binary and 16-bytes. Then, I bin2hex()ed the encrypted message before sending it to the Java.

On the Java side, I also converted the hex password to binary bytes. I did the same thing with the encrypted message. To do this, I used the following:

// PHP:

$password = pack ("H*", $_POST['password']);

// I use the @ because mcrypt_encrypt issues a warning because I don't give it an iv (in ECB mode?).

$key = @mcrypt_encrypt (MCRYPT_BLOWFISH, $password, CACHE_KEY, MCRYPT_MODE_ECB);

print bin2hex ($key);

//-

// Java:

// I have a new method in my MD5 class that converts a hex string to bytes,

// the opposite of the MD5.hex() method.

public static byte[] hexToBytes (String str)

{

if (str == null)

return null;

else if (str.length() < 2)

return null;

else

{

int len = str.length() / 2;

byte[] buffer = new byte[len];

for (int i=0; i<len; i++)

buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);

return buffer;

}

}

// Then in the decrypting method...

protected String[] doInBackground()

{

try

{

managerURL = new URL (url);

BufferedReader in = postData ("action=login&version=0.1&platform=exe&username=" + URLEncoder.encode (username, "UTF-8") + "&password=" + URLEncoder.encode (password, "UTF-8"));

String key = in.readLine();

in.close();

Cipher ci = Cipher.getInstance ("Blowfish/ECB/NoPadding");

byte[] passwordBytes = MD5.hexToBytes (password);

SecretKeySpec spec = new SecretKeySpec (passwordBytes, "Blowfish");

ci.init (Cipher.DECRYPT_MODE, spec);

byte[] keyBytes = MD5.hexToBytes (key);

byte[] finalKey = ci.doFinal (keyBytes);

return new String (finalKey);

}

catch (Exception ex)

{

ex.printStackTrace();

return null;

}

}

>

spanglercoa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 13

> return new String (finalKey);

But that's still a problem. Don't do that unless you hex-encode the finalKey first. And isn't this method supposed to return a String[], not a String? best if it just returned byte[] actually.

ejpa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 14

> > return new String (finalKey);

>

> But that's still a problem. Don't do that unless you

> hex-encode the finalKey first. And isn't this method

> supposed to return a String[], not a String? best if

> it just returned byte[] actually.

True. Getting this key is not the only purpose of this method, and it's not done yet. When it's done I'll leave the finalKey as byte[] and the method will probably return an object[] so I can have multiple types of data returned. This is just a small piece of the final login script. (As I mentioned earlier, it will process >2MB of data, this key being the first part). Thanks for mentioning that.

spanglercoa at 2007-7-29 12:40:50 > top of Java-index,Security,Cryptography...
# 15

> > > return new String (finalKey);

> >

> > But that's still a problem. Don't do that unless

> you

> > hex-encode the finalKey first. And isn't this

> method

> > supposed to return a String[], not a String? best

> if

> > it just returned byte[] actually.

>

> True. Getting this key is not the only purpose of

> this method, and it's not done yet. When it's done

> I'll leave the finalKey as byte[] and the method will

> probably return an object[] so I can have multiple

> types of data returned.

Bad design! Return an object that has fields for the various return values.

> This is just a small piece of

> the final login script. (As I mentioned earlier, it

> will process >2MB of data, this key being the first

> part). Thanks for mentioning that.

I'm obviously do not understanding what you are doing.

1) Since you are talking of decrypting using Java I assume that this Java is being executed on the server and the PHP encryption is being done on the client. How does the key get to the client?

2) I don't see how login requires greater than 2MB of data.

3) I don't seen why the key has to be returned.

4) You imply that you have a first version that works yet your Java code does not even compile.

sabre150a at 2007-7-29 12:40:56 > top of Java-index,Security,Cryptography...
# 16

err, PHP executes at the server, but you're right. I wonder why the OP isn't using SSL.

ejpa at 2007-7-29 12:40:56 > top of Java-index,Security,Cryptography...
# 17

> err, PHP executes at the server, but you're right. I

> wonder why the OP isn't using SSL.

I'm just showing my ignorance! So how is Java involved?

Even if PHP did execute on the client then SSL would still be the correct approach.

:-( I'm now even more confused about what the OP is trying to do.

sabre150a at 2007-7-29 12:40:56 > top of Java-index,Security,Cryptography...
# 18

Okay, the PHP is server-side and has access to all of the databases. The Java client logins with the username and password typed in by the user. Then, any changes that were made on the server since the last login (this is a website manager - edit page content, retrieve rows from people filling out forms, images, etc.) are downloaded (hence >2MB sometimes, such as the first run). The changes are stored in a cache file. However, in order to protect the cache file from tampering, a SHA-256 checksum is calculated of the data. The checksum along with the "index" for the cache file is encrypted using Blowfish. In order for multiple users to access the cache file, the server stores the decryption key, encrypted with what only that user will know: their own password. By storing this encrypted key, this allows password authentication without having to contact the server, so changes can be made in "offline mode." Additionally, the server can simply change the key at the next login so even encrypted it won't be stored for very long.

A little complicated, but I've thought a long time about designing this, especially user authentication without a server. Unfortunately, I may not always have SSL available (like right now), and I want the PHP as versatile as possible. Even with SSL, I would still have to do this encryption. I may have some parts wrong are my design though, especially the Java specifics.

spanglercoa at 2007-7-29 12:40:56 > top of Java-index,Security,Cryptography...
# 19

Use SSL (using https?) for basic communication with the server i.e. login and download of cache.

Use PBE locally to encrypt the cache and access the cache when off line.

This way there is no encryption performed in PHP.

sabre150a at 2007-7-29 12:40:56 > top of Java-index,Security,Cryptography...
# 20

Why isn't SSL available? It has shipped with every JRE since 1.4.0.

ejpa at 2007-7-29 12:40:56 > top of Java-index,Security,Cryptography...
# 21

> Why isn't SSL available? It has shipped with every

> JRE since 1.4.0.

It's not available server-side. SSL certificates cost money.

spanglercoa at 2007-7-29 12:40:56 > top of Java-index,Security,Cryptography...
# 22

And all this troublesome and error-prone development you're doing, which doesn't have a security proof attached, is free?

ejpa at 2007-7-29 12:40:56 > top of Java-index,Security,Cryptography...