Encrypt/Decrypt Strings

17 votes · 29 comments

Simple helper class to encrypt and decrypt strings using AES128. The result is Ascii-encoded (actually hex, no base64), so no byte[] has to be stored. A SEED value is used as a shared secret ("Master-Password"). Only with the same SEED the stored values can be decrypted.

raw ·
copy
· download
package net.sf.andhsli.hotspotlogin; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; /** * Usage: * <pre> * String crypto = SimpleCrypto.encrypt(masterpassword, cleartext) * ... * String cleartext = SimpleCrypto.decrypt(masterpassword, crypto) * </pre> * @author ferenc.hechler */ public class SimpleCrypto { public static String encrypt(String seed, String cleartext) throws Exception { byte[] rawKey = getRawKey(seed.getBytes()); byte[] result = encrypt(rawKey, cleartext.getBytes()); return toHex(result); } public static String decrypt(String seed, String encrypted) throws Exception { byte[] rawKey = getRawKey(seed.getBytes()); byte[] enc = toByte(encrypted); byte[] result = decrypt(rawKey, enc); return new String(result); } private static byte[] getRawKey(byte[] seed) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance("AES"); SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); sr.setSeed(seed); kgen.init(128, sr); // 192 and 256 bits may not be available SecretKey skey = kgen.generateKey(); byte[] raw = skey.getEncoded(); return raw; } private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception { SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(clear); return encrypted; } private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception { SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, skeySpec); byte[] decrypted = cipher.doFinal(encrypted); return decrypted; } public static String toHex(String txt) { return toHex(txt.getBytes()); } public static String fromHex(String hex) { return new String(toByte(hex)); } public static byte[] toByte(String hexString) { int len = hexString.length()/2; byte[] result = new byte[len]; for (int i = 0; i < len; i++) result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue(); return result; } public static String toHex(byte[] buf) { if (buf == null) return ""; StringBuffer result = new StringBuffer(2*buf.length); for (int i = 0; i < buf.length; i++) { appendHex(result, buf[i]); } return result.toString(); } private final static String HEX = "0123456789ABCDEF"; private static void appendHex(StringBuffer sb, byte b) { sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f)); } }
Add a comment

29 Comments

At the end, why did it change to HEX ? That causes data's size to bigger than original size.

Reply · March 17, 2011, 5:49 a.m.

There are several reasons why you could want a HEX output -- eg. logging and copy&pasting the encoded string. If you don't need it, just skip the toHex step and use the byte[] object directly.

Reply · March 18, 2011, 3:22 p.m.

The best would be to use Base64 encoding. This has the same advantages as the Hex-Encoding and inflates the size only by one third. As Sissi said, just replace the toHex() Call.

Reply · March 27, 2011, 8:41 p.m.

The Hex is needed else in certain cases you can get the Bad-Padding Exception.

Reply · June 27, 2012, 3:07 p.m.

Hi.., is this method compatible if want use encrypt/ decrypt in php? if yes, can show how to encrypt/ decrypt with php?

Reply · April 25, 2011, 9:25 a.m.

If you look in the home page... you will see at the bottom (exactly 19 snippets before this one). that I put a complete example of how to encrypt/decrypt between PHP & Java (Android)

link: http://www.androidsnippets.com/encrypt-decrypt-between-android-and-php

Reply · Sept. 21, 2011, 2:29 p.m.

i am sad to say ,i just can not manage it .what goes wrong

Reply · May 5, 2011, 10:10 a.m.

Thanks for sharing! It works just fine. I was curious about one thing: the code yields different results when I run it on my computer as a Java application and when I run it on Android on my app (so I can't share encrypted data from one to the other). Is that expected or even desired?

Reply · May 23, 2011, 10:56 a.m.

Tested the snippet with a couple of values... encryption & decryption, and it gives at decryption a Exception:

javax.crypto.BadPaddingException: Given final block not properly padded

when either the seed from decrypt(String seed, String encrypted) differs from the original seed from encrypt(String seed, String cleartext). Tested with same & different length seed.

This doesn't seem to be normal behavior for a encryption mechanism; shouldn't decrypt() return a unreadable/mixture of symbols when either the seed or the encrypted parameter from decrypt() differ from the correct values (and not a Exception)

Reply · Aug. 26, 2011, 11:42 a.m.

Is this really that safe to use say a cracker were to come and find this and after decompiling your code using say dex2jar they could easily find your key and with this source backtrack and unencode what ever you were encoding I myself could do this in 30 seconds or less

Reply · Sept. 20, 2011, 4:18 a.m.

People doing this kind of encryption are just trying to stop the casual snooper.

Reply · March 13, 2012, 11:01 p.m.

I tried to modify it so it didn't convert to Hex but got "IllegalBlockSizeException: last block incomplete in decryption" error.

I only changed 2 lines (see below) and got rid of the Hex subs.

Any suggestions? The code worked great but gave huge results. After modification it seemed to encrypt well but couldn't decrypt.

public class SimpleCrypto2 {

public static String encrypt(String seed, String cleartext) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext.getBytes());          
        return result.toString();  // <-- new line

// return toHex(result); <-- old line }

public static String decrypt(String seed, String encrypted) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] enc = encrypted.getBytes();  // <-- new line

// byte[] enc = toByte(encrypted); <--old line byte[] result = decrypt(rawKey, enc); return new String(result); }

Reply · Oct. 12, 2011, 1:02 a.m.

I think I figured out the problem. When I convert the encrypted byte array to a string some of the information is lost because not all characters can be in a string (like return, line feed, whatever). I'm a noob at all this and I'm just guessing. Someone let me know if I'm wrong. Anyway, I think I see the advantages of using Hex. I plan to store the encrypted info in a SQLite database so it can't have any freaky characters.

Could someone explain in more detail how to use Base 64 encoding instead? It'd be great if it used less space.

Thanks!

Reply · Oct. 12, 2011, 7:45 p.m.

Use the one I created.. that problem was solved:

http://www.androidsnippets.com/encrypt-decrypt-between-android-and-php

Reply · Oct. 20, 2011, 2:34 p.m.

Use the one I created.. that problem was solved:

http://www.androidsnippets.com/encrypt-decrypt-between-android-and-php

Reply · Oct. 20, 2011, 2:34 p.m.

Yes, This doesn't seem to be normal behavior for a encryption mechanism.

Reply · March 15, 2012, 10:26 a.m.

I do not recommend anybody to use this code as it contains several portability errors. The following are critical:

1) Uses a key derivation method (seeding a SecureRandom instance) that will not work on any other Oracle Java runtime. It only works on Android. 2) Uses the no-args String.getBytes() method. This uses a default Charset, the problem being that the default Charset is different on different platforms. Always explicitly specify the Charset. Almost always the correct Charset to specify is "UTF-8".

Less critical:

3) Uses default transformation components in Cipher.getInstance(). Always completely specify the "algorithm/mode/padding". A good choice is "AES/CBC/PKCS5PADDING". 4) No explicit IV handling or generation. Everything is left up to defaults.

Reply · May 18, 2012, 11:57 a.m.

Your so-called critical errors are kinda irrelevant, this site is called ANDROIDsnippets.com. Whats the point of telling everyone its incompatible with any other oracle java runtime???

Other less critical errors are actually relevant, thx for reporting them and giving some suggestions.

Reply · Sept. 2, 2012, 3:13 p.m.

Just because the code here is for Android doesn't mean people won't try to use it elsewhere. Myself, I'm glad that the person above warned me that this code is not portable.

Reply · Dec. 11, 2012, 12:18 a.m.

Base64 Implementation for the lazy users here:

http://pastie.org/4651260

Reply · Sept. 2, 2012, 3:11 p.m.

http://pastie.org/4651432 <- fixed version, now actually works... Try this code to test:

String hash = SimpleCrypto.md5("test");
    String encrypted = SimpleCrypto.encrypt("secret", hash);
    String decrypted = SimpleCrypto.decrypt("secret", encrypted);
    Toast.makeText(getApplicationContext(), decrypted, Toast.LENGTH_LONG).show();

Should give you a toast with the following content: 098f6bcd4621d373cade4e832627b4f6

Reply · Sept. 2, 2012, 3:56 p.m.

This is so wrong. It's called SecureRandom because it's random. You absolutely SHOULD NOT use it to derive keys.

You should be using SecretKeyFactory with a PBEKeySpec for that!

Reply · Sept. 5, 2012, 6:46 p.m.

As the code above is broken with Android 4.2 I assume changing the getRawKey() method with the following code (It also is more interoperable, because the MD5 hash is calculated to the same on all platforms).

private static byte[] getRawKey(byte[] seed) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] md5Bytes = md.digest(seed); // 128 Bit = 16 byte SecretKey skey = new SecretKeySpec(md5Bytes, "AES"); byte[] raw = skey.getEncoded(); return raw; }

It is possible to read the old encrpyted strings with Android 4.2 by explicitly defining the package of the crypto-provider.

private static byte[] getCompatibleRawKey(byte[] seed) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance("AES"); SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto"); sr.setSeed(seed); kgen.init(128, sr); // 192 and 256 bits may not be available SecretKey skey = kgen.generateKey(); byte[] raw = skey.getEncoded(); return raw; }

If I can recover my login I will patch the above code.

Best regards,

feri

Reply · Feb. 6, 2013, 12:14 a.m.

[sorry, once again with formated code the fix for Android 4.2]

As the code above is broken with Android 4.2 I assume changing the getRawKey() method with the following code (It also is more interoperable, because the MD5 hash is calculated to the same on all platforms).

private static byte[] getRawKey(byte[] seed) throws Exception { 
    MessageDigest md = MessageDigest.getInstance("MD5"); 
    byte[] md5Bytes = md.digest(seed); // 128 Bit = 16 byte SecretKey 
    skey = new SecretKeySpec(md5Bytes, "AES"); 
    byte[] raw = skey.getEncoded(); 
    return raw; 
}

It is possible to read the old encrpyted strings with Android 4.2 by explicitly defining the package of the crypto-provider.

private static byte[] getCompatibleRawKey(byte[] seed) throws Exception {
    KeyGenerator kgen = KeyGenerator.getInstance("AES"); 
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
    sr.setSeed(seed); 
    kgen.init(128, sr); // 192 and 256 bits may not be available 
    SecretKey skey = kgen.generateKey(); 
    byte[] raw = skey.getEncoded(); 
    return raw; 
}

If I can recover my login I will patch the above code.

Best regards,

feri

Reply · Feb. 6, 2013, 1 a.m.

and the winner is.. not author of post from page u read.. why? check it here.. http://android-developers.blogspot.de/2013/02/using-cryptography-to-store-credentials.html

Reply · Feb. 21, 2013, 9:11 p.m.

This article recommends that you do not use this approach.

Reply · Sept. 4, 2013, 11:19 a.m.

Having a problem decrypting in the toByte(String hexString) method. I am encrypting a simple string and then decrypting it later. Sometimes the output of the encryption is a non hex string, which then causes the program to crash when I later try to decrypt this string.

Here is the top snippet of the stacktrace:

Caused by: java.lang.NumberFormatException: Invalid int: "ya" at java.lang.Integer.invalidInt(Integer.java:137) at java.lang.Integer.parse(Integer.java:374) at java.lang.Integer.parseInt(Integer.java:365) at java.lang.Integer.valueOf(Integer.java:509) at com.myxer.android.utils.MXRCrypt.toByte(MXRCrypt.java:54) at com.myxer.android.utils.MXRCrypt.decrypt(MXRCrypt.java:23)

Reply · May 8, 2014, 5:45 p.m.

I used the same seed for both encryption and decryption, I still get a BadPadding exception, pad block corrupted. I encrypts fine but does not decrypt, why is that?

Reply · May 15, 2014, 3:27 p.m.

ios source is there?

Reply · July 28, 2014, 9:03 a.m.

how to convert object c?

Reply · July 28, 2014, 9:04 a.m.