I am doing some simple encryption/decryption coding, and I am having a problem, which I cannot figure out by myself.
I have a ciphertext which is hex encoded. The ciphertext is AES with a block length of 128bits and a key length of 256bits. The cipher block mode is CBC. IV is the first block of the cipher text.
The Exception Message is Illegal Key Size.
Here is my decrypt() function:
public static byte[] decrypt() throws Exception
{
try{
byte[] ciphertextBytes = convertToBytes("cb12f5ca1bae224ad44fdff6e66f9a53e25f1000183ba5568958430c11c6eafc62c04de8bf27e0ac7104b598fb492142");
byte[] keyBytes = convertToBytes("CFDC65CB003DD50FF5D6D826D62CF9CA6C64489D60CB02D18C1B58C636F8220D");
byte[] ivBytes = convertToBytes("cb12f5ca1bae224a");
SecretKey aesKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(ivBytes));
byte[] result = cipher.doFinal(ciphertextBytes);
return result;
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
return null;
}
And I have those functions to do the conversion String/ByteArray
//convert ByteArray to Hex String
public static String convertToHex(byte[] byteArray)
{
StringBuilder sb = new StringBuilder();
for (byte b : byteArray)
{
sb.append(String.format("%02X", b));
}
return sb.toString();
}
//convert String to ByteArray
private static byte[] convertToBytes(String input) {
int length = input.length();
byte[] output = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
output[i / 2] = (byte) ((digit(input.charAt(i), 16) << 4) | digit(input.charAt(i+1), 16));
}
return output;
}
Maybe you can help me.
Thank you very much!
You might have hit the key-size limit in Oracle JRE. From the linked document:
If stronger algorithms are needed (for example, AES with 256-bit keys), the JCE Unlimited Strength Jurisdiction Policy Files must be obtained and installed in the JDK/JRE.
It is the user's responsibility to verify that this action is permissible under local regulations.
Related
I have below NodeJS code for decryption and it is working perfectly fine but when I am trying to convert the same code in Java at that time I am getting below error.
Given final block not properly padded. Such issues can arise if a bad key is used during decryption
Node JS code snippet:
let textParts = text.split(':');
let iv = Buffer.from(textParts.shift(), 'hex');
let encryptedText = Buffer.from(textParts.join(':'), 'hex');
let decrypted = decipher.update(encryptedText);
let decipher = crypto.createDecipheriv(
'aes-256-cbc',
Buffer.from(ENCRYPTION_KEY),
iv,
);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
Java code snippet:
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes(StandardCharsets.UTF_8));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.decodeBase64(encyptedData));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
Encryption Key is same for both Node JS and Java code.
Thanks in advance.
If your initial vector is 32 bytes then you need to decrypt as below.
public String decrypt(String encryptedData) {
try {
String data[] = encryptedData.split(":");
IvParameterSpec iv = new IvParameterSpec(getBytes(data[0]));
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec skeySpec = new SecretKeySpec(YOUR_KEY.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] decryptedData = cipher.doFinal(getBytes(data[1]));
return new String(decryptedData);
} catch (Exception e) {
throw new RuntimeException("Error occured while decrypting data", e);
}
}
public byte[] getBytes(String s) {
String tmp;
byte[] b = new byte[s.length() / 2];
int i;
for (i = 0; i < s.length() / 2; i++) {
tmp = s.substring(i * 2, i * 2 + 2);
b[i] = (byte)(Integer.parseInt(tmp, 16) & 0xff);
}
return b;
}
it seems the problem is with size of the variabel in java when you decode.
Check this links maybe those give you some hints :
1- ejava-base64-encode-and-decode
2- given-final-block-not-properly-padded
public long copyStreamsLong(InputStream in, OutputStream out, long sizeLimit) throws IOException {
long byteCount = 0;
IOException error = null;
long totalBytesRead = 0;
try {
String key = "C4F9EA21977047D6"; // user value (16/24/32 bytes)
// byte[] keyBytes = DatatypeConverter.parseHexBinary(aesKey);
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
System.out.println(secretKey.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] buffer = new byte[CustomLimitedStreamCopier.BYTE_BUFFER_SIZE];
// in.read(buffer);
int bytesRead = -1;
while ((bytesRead = in.read(buffer)) != -1) {
// We are able to abort the copy immediately upon limit violation.
totalBytesRead += bytesRead;
if (sizeLimit > 0 && totalBytesRead > sizeLimit) {
StringBuilder msg = new StringBuilder();
msg.append("Content size violation, limit = ").append(sizeLimit);
throw new ContentLimitViolationException(msg.toString());
}
byte[] obuf = cipher.update(buffer, 0, bytesRead);
if (obuf != null) {
out.write(obuf);
}
byteCount += bytesRead;
}
byte[] obuf = cipher.doFinal();
if (obuf != null) {
out.write(obuf);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
error = e;
CustomLimitedStreamCopier.logger.error("Failed to close input stream: " + this, e);
}
try {
out.close();
} catch (IOException e) {
error = e;
CustomLimitedStreamCopier.logger.error("Failed to close output stream: " + this, e);
}
}
if (error != null)
throw error;
return byteCount;
}
public InputStream getContentInputStream() throws ContentIOException {
ReadableByteChannel channel = getReadableChannel();
InputStream is = Channels.newInputStream(channel);
try {
final String ALGORITHM = "AES";
final String TRANSFORMATION = "AES";
String key = "C4F9EA21977047D6";
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] buffer = ByteStreams.toByteArray(is);
System.out.println("in read" + buffer.length);
is.read(buffer);
byte[] outputBytes = cipher.doFinal(buffer);
is = new ByteArrayInputStream(outputBytes);
}
When i m trying to encrypt input stream i will get increased byte array and after that when i will decrypt that input stream at that time in decryption it will take original byte size .so,when i tried to open that file it will give error that premature end tag so i want same file size after encryption ,and this method is part of java class.
What will be the solution for this problem?
You initialize the cipher with the TRANSFORMATION = "AES" and that means that your Java implementation will choose a default AES mode, usually it is "AES/ECB/PKCS5Padding". Beneath the fact that ECB-mode is unsecure and should not any longer used for plaintext/streams longer than 16 bytes the padding will add additional bytes up to a (multiple) blocklength of 16.
So running my small program you see that in your implementation an inputLen of "10" (means a plaintext/stream of 10 byte length) will result in an outputSize of "16".
To avoid this you do need another AES mode and that the output is of the same length as the input on encryption side. You can use AES CTR mode for this - you just need an additional parameter (initialization vector, 16 byte long). It is very important that you never ever use the same iv for more than 1 encryption so it should get generated as random value. For decryption the recipient of the message ("the decryptor") needs to know what initvector was used on encryption side.
cipher algorithm: AES inputLen 10 outputSize 16
cipher algorithm: AES/CTR/NOPADDING inputLen 10 outputSize 10
Edit (security warning): As reminded by President James K. Polk "CTR mode makes it trivial to modify individual bytes of the attacker's choosing, so it needs to be coupled with an authentication tag.". It depends on the kind of data that are going to get encrypted if they need a authentication (e.g. if you are encrypting music files then any modifying will result in a "piep" or "krk", changing of financial data will end catastrophical).
code:
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class Main {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
System.out.println("");
int inputLen = 10;
final String ALGORITHM = "AES";
// aes ecb mode
final String TRANSFORMATION = "AES";
//final String TRANSFORMATION = "AES/ECB/PKCS5PADDING";
String key = "C4F9EA21977047D6";
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
System.out.format("cipher algorithm: %-20s inputLen %3d outputSize %3d%n", cipher.getAlgorithm(), inputLen, cipher.getOutputSize(inputLen));
// aes ctr mode
String TRANSFORMATION2 = "AES/CTR/NOPADDING";
// you need an unique (random) iv for each encryption
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher2 = Cipher.getInstance(TRANSFORMATION2);
cipher2.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
System.out.format("cipher algorithm: %-20s inputLen %3d outputSize %3d%n", cipher2.getAlgorithm(), inputLen, cipher2.getOutputSize(inputLen));
}
}
A running implementation of the AES CTR-mode can be found here: https://stackoverflow.com/a/62662276/8166854
Hi I have java code which decrypt the ciphertext encrypted using CryptoJS library(AES).
Now i wanted to write the javacode which will encrypt the plaintext again.
Please find the below code.
try {
String secret = "René Über";
String cipherText="U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.decode(cipherText, Base64.DEFAULT);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes("utf-8"), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData,"utf-8");
System.out.println("Decrypted "+decryptedText);
//Here I get right plain text as
//System.out: Decrypted The quick brown fox jumps over the lazy dog.
Cipher abc=Cipher.getInstance("AES/CBC/PKCS5Padding");
abc.init(Cipher.ENCRYPT_MODE,key,iv);
byte[] encryptedData=abc.doFinal(decryptedData);
String str=Base64.encodeToString(encryptedData,Base64.DEFAULT);
System.out.println("encrypted "+str);
//Here i want the encrypted text as
// encrypted U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hy//aQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
//but i receive
//System.out: encrypted IZ5IDQruC+Cz0pd5krBsIM0KzbM+j4FeO8pgusm60wr6HFPCX+HJpAs5oPssshGjYjl/J5Ew+//eui
}catch (Exception e)
{}
When I decrypt the code I get correct Plain Text but when I again encrypt the plain text I didnt get the encrypted text as previous.
Please Help.
GenerateKeyAndIV function code:-
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
Your ciphertext has "Salted__<8 byte salt>" at the beginning, which you skip when decrypting. You need to prefix the same in your encryption mode if you want to create OpenSSL compatible ciphertext.
Your encryption code ciphertext seems correct when you view it in a base64 to hex decoder, e.g. the one provided here. However, because each character only contains 64 bits and since the bytes have shifted 16 places (which is not divisible by 3), it just seams that your entire ciphertext is incorrect, while it is just missing 16 bytes at the front.
Here posting my working code for android I have used crypto for decryption on the server. Below code is using AES Algorithm
private static final String key = "aesExamplekey";
private static final String initVector = "exampleintvec";
public static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
// byte[] finalCiphertext = new byte[encrypted.length+2*16];
return Base64.encodeToString(encrypted, Base64.NO_WRAP);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}`
Server side code asp.net
public string DecryptStringAES(string cipherText)
{
// var keybytes = Encoding.UTF8.GetBytes("7061737323313233");
// var iv = Encoding.UTF8.GetBytes("7061737323313233");
var keybytes = Encoding.UTF8.GetBytes("aesExamplekey");
var iv = Encoding.UTF8.GetBytes("exampleintvec");
var encrypted = Convert.FromBase64String(cipherText);
var decriptedFromJavascript = DecryptStringFromBytes(encrypted, keybytes, iv);
return string.Format(decriptedFromJavascript);
}
I am trying to encrypt some text using the AES algorithm on both the Android and IPhone platforms. My problem is, even using the same encryption/decryption algorithm (AES-128) and same fixed variables (key, IV, mode), I get different result on both platforms. I am including code samples from both platforms, that I am using to test the encryption/decryption. I would appreciate some help in determining what I am doing wrong.
Key: “123456789abcdefg”
IV: “1111111111111111”
Plain Text: “HelloThere”
Mode: “AES/CBC/NoPadding”
Android Code:
public class Crypto {
private final static String HEX = "0123456789ABCDEF";
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("CBC");
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 static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
}
IPhone (Objective-C) Code:
- (NSData *) transform:(CCOperation) encryptOrDecrypt data:(NSData *) inputData {
NSData* secretKey = [Cipher md5:cipherKey];
CCCryptorRef cryptor = NULL;
CCCryptorStatus status = kCCSuccess;
uint8_t iv[kCCBlockSizeAES128];
memset((void *) iv, 0x0, (size_t) sizeof(iv));
status = CCCryptorCreate(encryptOrDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
[secretKey bytes], kCCKeySizeAES128, iv, &cryptor);
if (status != kCCSuccess) {
return nil;
}
size_t bufsize = CCCryptorGetOutputLength(cryptor, (size_t)[inputData length], true);
void * buf = malloc(bufsize * sizeof(uint8_t));
memset(buf, 0x0, bufsize);
size_t bufused = 0;
size_t bytesTotal = 0;
status = CCCryptorUpdate(cryptor, [inputData bytes], (size_t)[inputData length],
buf, bufsize, &bufused);
if (status != kCCSuccess) {
free(buf);
CCCryptorRelease(cryptor);
return nil;
}
bytesTotal += bufused;
status = CCCryptorFinal(cryptor, buf + bufused, bufsize - bufused, &bufused);
if (status != kCCSuccess) {
free(buf);
CCCryptorRelease(cryptor);
return nil;
}
bytesTotal += bufused;
CCCryptorRelease(cryptor);
return [NSData dataWithBytesNoCopy:buf length:bytesTotal];
}
+ (NSData *) md5:(NSString *) stringToHash {
const char *src = [stringToHash UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(src, strlen(src), result);
return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
}
Some of my references :
http://code.google.com/p/aes-encryption-samples/wiki/HowToEncryptWithJava
http://automagical.rationalmind.net/2009/02/12/aes-interoperability-between-net-and-iphone/
AES interoperability between .Net and iPhone?
For iPhone I used AESCrypt-ObjC, and for Android use this code:
public class AESCrypt {
private final Cipher cipher;
private final SecretKeySpec key;
private AlgorithmParameterSpec spec;
public AESCrypt(String password) throws Exception
{
// hash password with SHA-256 and crop the output to 128-bit for key
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(password.getBytes("UTF-8"));
byte[] keyBytes = new byte[32];
System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
key = new SecretKeySpec(keyBytes, "AES");
spec = getIV();
}
public AlgorithmParameterSpec getIV()
{
byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
IvParameterSpec ivParameterSpec;
ivParameterSpec = new IvParameterSpec(iv);
return ivParameterSpec;
}
public String encrypt(String plainText) throws Exception
{
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
String encryptedText = new String(Base64.encode(encrypted, Base64.DEFAULT), "UTF-8");
return encryptedText;
}
public String decrypt(String cryptedText) throws Exception
{
cipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] bytes = Base64.decode(cryptedText, Base64.DEFAULT);
byte[] decrypted = cipher.doFinal(bytes);
String decryptedText = new String(decrypted, "UTF-8");
return decryptedText;
}
}
It makes me no wonder that you get different results.
Your problem is that you use misuse a SHA1PRNG for key derivation. AFAIK there is no common standard how a SHA1PRNG work internally. AFAIR even the J2SE and Bouncycaste implementation output different results using the same seed.
Hence your implementation of your getRawKey(byte[] seed) will generate you a random key. If you use the key for encryption you are getting an result that depends on that key. As the key is random you will not get the same key on iOS and therefore you are getting a different result.
If you want a key derivation function use a function like PBKDF2 with is nearly fully standardized regarding the key derivation.
On Android, you are using getBytes(). This is an error as it means you are using the default charset rather than a known charset. Use getBytes("UTF-8") instead so you know exactly what bytes you are going to get.
I don't know the equivalent for Objective-C, but don't rely on the default. Explicitly specify UTF-8 when converting strings to bytes. That way you will get the same bytes on both sides.
I also note that you are using MD5 in the Objective-C code but not in the Android code. Is this deliberate?
See my answer for password-based AES encryption, since, you are effectively using your "seed" as a password. (Just change the key length of 256 to 128, if that's what you want.)
Trying to generate the same key by seeding a DRBG with the same value is not reliable.
Next, you are not using CBC or the IV in your Android encryption. My example shows how to do that properly too. By the way, you need to generate a new IV for every message you encrypt, as my example shows, and send it along with the cipher text. Otherwise, there's no point in using CBC.
Note: For android in java
I have written this manager file and its functions are working perfectly fine for me. This is for AES 128 and without any salt.
public class CryptoManager {
private static CryptoManager shared;
private String privateKey = "your_private_key_here";
private String ivString = "your_iv_here";
private CryptoManager(){
}
public static CryptoManager getShared() {
if (shared != null ){
return shared;
}else{
shared = new CryptoManager();
return shared;
}
}
public String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(ivString.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(privateKey.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return android.util.Base64.encodeToString(encrypted, android.util.Base64.DEFAULT);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public String decrypt(String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(ivString.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(privateKey.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = new byte[0];
original = cipher.doFinal(android.util.Base64.decode(encrypted, android.util.Base64.DEFAULT));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
You need to call the functions like this.
String dataToEncrypt = "I need to encrypt myself";
String encryptedData = CryptoManager.getShared().encrypt(data);
And you will get your encrypted string with the following line
String decryptedString = CryptoManager.getShared().decrypt(encryptedData);
If you want an example of compatible code for Android and iPhone, look at the RNCryptor library for iOS and the JNCryptor library for Java/Android.
Both projects are open source and share a common data format. In these libraries, AES 256-bit is used, however it would be trivial to adapt the code if necessary to support 128-bit AES.
As per the accepted answer, both libraries use PBKDF2.
I am able to encrypt an SMS and send it from one simulator (Android 2.2) to another.
On the receiving end I am able to do the decryption successfully. But the problem is if do the encryption in one OS version (i.e Android 2.2) and trying to decrypt in another OS version ( Android 2.3 ) i am getting 'Bad padding exception'. I checked that i used the same key on both ends.
The code is shown below
public class ED {
private String Key;
public ED() {
Key = "abc12"; // Assigning default key.
}
public ED(String key) {
// TODO Auto-generated constructor stub
Key = key;
}
public String encrypt(String toEncrypt) throws Exception {
byte[] rawKey = getRawKey(Key.getBytes("UTF-8"));
byte[] result = encrypt(rawKey, toEncrypt.getBytes("UTF-8"));
return toHex(result);
}
public byte[] encrypt(byte[] key, byte[] toEncodeString) throws Exception {
SecretKeySpec sKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
byte[] encrypted = cipher.doFinal(toEncodeString);
return encrypted;
}
private byte[] getRawKey(byte[] key) throws Exception {
KeyGenerator kGen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(key);
kGen.init(128, sr);
SecretKey sKey = kGen.generateKey();
byte[] raw = sKey.getEncoded();
return raw;
}
/************************************* Decription *********************************************/
public String decrypt(String encryptedString) throws Exception {
byte[] rawKey = getRawKey(Key.getBytes("UTF-8"));
System.out.println("Decrypted Key in bytes : "+rawKey);
System.out.println("Key in decryption :"+rawKey);
SecretKeySpec sKeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
byte[] decrypted = cipher.doFinal(toByte(encryptedString));
System.out.println("Decrypted mess in bytes---------->" +decrypted);
return new String(decrypted);
}
public 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 String HEX = "0123456789ABCDEF";
private void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
}
public 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;
}
}
And I am using sendTextMessage() function to send an sms. I read that encryption/decryption doesn't depend on OS but in this case that is not true. Am I missing any important things while configuring the Cipher (in AES) ? Please let me know.
It's setSeed(). It does not do what you think it does: it just adds the entropy of the given seed to the underlying algorithm. You'll probably find out that it returns somehthing different on both platforms. SHA1PRNG is a pseudo random function, but if it is already seeded, it's likely to return different results.
If the problem is in the key length, you could derivate a key from your password, instead of using it directly. You could use a Hash (like SHA-1, MD5, etc) and crop it to the correct size (128, 192 or 256 bits), or use PBEKeySpec instead of SecretKeySpec.
That to remove problems with the key length. If the padding problems were in the plaintext, I suggest you to use CipherInputStream and CipherOutputStream, which are more programmer-friendly to use than Cipher.doFinal.
Don't rely on KeyGenerator to generate the same key just because you seeded the RNG the same way. If you are pre-sharing a key, share the key, not the seed.
You should also specify the encryption transform completely: "AES/ECB/PKCS5Padding"
Finally, ECB mode is not secure for general use.
See another answer of mine for an example to perform encryption correctly with the JCE.
The problem is with SecureRandom generation. It is giving different results on different platforms. It's because of a bug fix on line 320 (in Gingerbread source) of SHA1PRNG_SecureRandomImpl.java in the engineNextBytes() method where
bits = seedLength << 3 + 64;
was changed to
bits = (seedLength << 3) + 64;
Use SecretKeyFactory() to generate a Secure key instead of secure random.
public class Crypto {
Cipher ecipher;
Cipher dcipher;
byte[] salt = { 1, 2, 4, 5, 7, 8, 3, 6 };
int iterationCount = 1979;
Crypto(String passPhase) {
try {
// Create the key
KeySpec keySpec = new PBEKeySpec(passPhase.toCharArray(), salt, iterationCount);
SecretKey key = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC").generateSecret(keySpec);
ecipher = Cipher.getInstance(key.getAlgorithm());
dcipher = Cipher.getInstance(key.getAlgorithm());
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
} catch (Exception e) {
// TODO: handle exception
//Toast.makeText(this, "I cought ", Toast.LENGTH_LONG).show();
}
}
public String encrypt(String str) {
String rVal;
try {
byte[] utf8 = str.getBytes("UTF8");
byte[] enc = ecipher.doFinal(utf8);
rVal = toHex(enc);
} catch (Exception e) {
// TODO: handle exception
rVal = "Exception Caught "+e.getMessage();
}
return rVal;
}
public String decrypt(String str) {
String rVal;
try {
byte[] dec = toByte(str);
byte[] utf8 = dcipher.doFinal(dec);
rVal = new String(utf8, "UTF8");
} catch(Exception e) {
rVal = "Error in decrypting :"+e.getMessage();
}
return rVal;
}
private 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;
}
private 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));
}
}