AES encryption in java vs cryptojs - java

I Have an AES encryption and decrypt both in java and nodejs (cryptojs).
but when I tried to encrypt and decrypt in both have different result.
This is my java code :
import java.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
public class enc {
public static void main(String[] args){
String key = "1234567890123456"; // 128 bit key: must 16 character
String initVector = "1234567890123456"; // 16 bytes IV : must 16 character
System.out.println("Key : "+key);
System.out.println("Init Vector : "+initVector);
String encrypted = encryptAES(key, initVector, "Hello World");
System.out.println("encrypted : "+encrypted);
}
public static String encryptAES(String key, String initVector, 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());
return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decryptAES(String key, String initVector, String encrypted) {
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.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
and java result :
Key : 1234567890123456
Init Vector : 1234567890123456
encrypted : ZyODokM33Io1ZKIA8h7owA==
decrypted : Hello World
My Nodejs using crypto js :
var CryptoJS = require("crypto-js");
var atob = require('atob');
var btoa = require('btoa');
var message = "Hello World";
var key = "1234567890123456";
var iv = "1234567890123456";
console.log("Key : "+key);
console.log("Init Vector : "+iv);
key = CryptoJS.enc.Base64.parse(key);
iv = CryptoJS.enc.Base64.parse(iv);
var chiperData = CryptoJS.AES.encrypt(message, key, { iv: iv });
console.log('encrypted : ',chiperData.toString());
var data = CryptoJS.AES.decrypt(chiperData, key, { iv: iv });
console.log('decrypted : ',hex2a(data.toString()));
// Convert hex string to ASCII.
// Thanks to https://stackoverflow.com/questions/11889329/word-array-to-string
function hex2a(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
and the result :
Key : 1234567890123456
Init Vector : 1234567890123456
encrypted : tHtu5jUs0ZotjjJIHgy0eQ==
decrypted : Hello World
as you can see encrypted in java and cryptojs is different. ZyODokM33Io1ZKIA8h7owA== and tHtu5jUs0ZotjjJIHgy0eQ==
Where is my code wrong? and how to solve this case?
so they have same encruption so can decrypt each other encrypted string
thanks

This is a typical problem that occurs due to the mismatch of encodings. For example, here, it seems like the problem lies in the following lines:
key = CryptoJS.enc.Base64.parse(key);
iv = CryptoJS.enc.Base64.parse(iv);
Here you are treating key and iv as Base64 encoded string, where previously (in Java) you treated them as UTF-8 encoded string.
If you want the same result as Java you should change it to,
key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);
Edit:
As #erickson suggested, you can also change your Java implementation as well so that it interprets your key and iv as Base64.

Related

Need to convert Java AES/CBC/PKCS5 Padding Encryption and decryption to dart code-Flutter

I need to convert java code for encryption and decryption using AES/CBC/PKCS5Padding algorithm to dart code.
The java code of AES/CBC/PKCS5Padding encryption and decryption:
package test_Terminal.classes;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
*
* #author jeena
*/
public class IOTEncodingDecoding {
SecretKeySpec secretKeySpec;
IvParameterSpec ivSpec;
String EncryptionKey = "733D3A17-D8A0-454B-AD22-88608FD0C46A";
String saltString = "FA9A4D0F-5523-4EEF-B226-9A3E8F14FEF8";
String algorithm = "AES/CBC/PKCS5Padding";
int encoding_mode;
test_Terminal.classes.general General = new test_Terminal.classes.general();
void setSecretKey() {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(EncryptionKey.toCharArray(), saltString.getBytes(StandardCharsets.UTF_16LE), 1000, 384);
byte[] derivedData = factory.generateSecret(pbeKeySpec).getEncoded();
byte[] key = new byte[32];
byte[] iv = new byte[16];
System.arraycopy(derivedData, 0, key, 0, key.length);
System.arraycopy(derivedData, key.length, iv, 0, iv.length);
secretKeySpec = new SecretKeySpec(key, "AES");
ivSpec = new IvParameterSpec(iv);
} catch (Exception e) {
General.LogException("setSecretKey", e);
}
}
public String encrypt(String input) {
try {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec);
byte[] cipherText ;
if(encoding_mode==1)
cipherText = cipher.doFinal(input.getBytes(StandardCharsets.UTF_16LE));
else
cipherText = cipher.doFinal(input.getBytes());
return Base64.getEncoder().encodeToString(cipherText);
} catch (Exception e) {
General.LogException("encrypt", e);
}
return "";
}
public String decrypt(String cipherText) {
try {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));
if(encoding_mode==1)
return new String(plainText, StandardCharsets.UTF_16LE);
else
return new String(plainText);
} catch (Exception e) {
General.LogException("decrypt", e);
General.LogActivity("decrypt", e.getMessage());
}
return "Ticket format error";
}
public void setMode() {
setSecretKey();
}
}
I need to get the following result:
Input(PlainText):C123492349C1CT20230206130645.
Output(Encrypted string):8tyHRaQCsxmmGW2xPBFYx/PALmvHkmjx/TzaXC2rIv0=
This is the dart code that I've got so far for decryption, but I'm getting error.
Uint8List? decrypt(String ciphertext, String password) {
Uint8List rawCipher = base64.decode(ciphertext);
var salt = rawCipher.sublist(0, 0 + 8);
var iv = rawCipher.sublist(8, 8 + 16);
var encrypted = rawCipher.sublist(8 + 16);
Uint8List key = generateKey(password, salt);
print('key => $key');
CBCBlockCipher cipher = CBCBlockCipher(AESEngine());
ParametersWithIV<KeyParameter> params =
ParametersWithIV<KeyParameter>(KeyParameter(key), iv);
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>
paddingParams =
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(
params, null);
PaddedBlockCipherImpl paddingCipher =
PaddedBlockCipherImpl(PKCS7Padding(), cipher);
paddingCipher.init(false, paddingParams);
var val = paddingCipher.process(encrypted);
String res = String.fromCharCodes(val);
debugPrint('res => $res');
return val;
}
Uint8List generateKey(String passphrase, Uint8List salt) {
final derivator = PBKDF2KeyDerivator(HMac(SHA1Digest(), 64))
..init(Pbkdf2Parameters(salt, 1024, 16));
return derivator.process(utf8.encode(passphrase) as Uint8List);
}
I got this code from
The Exception that I'm getting is:
Exception has occurred.
ArgumentError (Invalid argument(s): Input data length must be a multiple of cipher's block size)
I think the values inside rawCipher.sublist() function is wrong. I'm stuck on this problem for few days, please help.
Both codes differ:
Regarding encodings: The Dart code does not consider the UTF-16 LE encoding of the salt. Furthermore, the encoding of the plaintext is unclear. For encoding_mode==1 it is UTF-16LE, otherwise it corresponds to the platform encoding in your environment (which only you know).
Regarding PBKDF2: The Java code derives key and IV from a static salt (note that a static salt is a vulnerability), while the Dart code assumes a concatenation in the order salt|IV|ciphertext during encryption (using a random 8 bytes salt and a random IV).
Also, different iteration counts are used: 1000 in the Java code, 1024 in the Dart code (note that both values are generally much too small for PBKDF2).
The differences can be fixed as follows:
Regarding encodings: In the Dart code, the salt must first be UTF-16 LE encoded: Since the utf package is deprecated, see e.g. here for a UTF-16 LE encoding and here for the decoding. The encoding can be adapted to:
Uint8List encodeUtf16LE(String salt) {
var byteData = ByteData(salt.codeUnits.length * 2);
for (var i = 0; i < salt.codeUnits.length; i += 1) {
byteData.setUint16(i * 2, salt.codeUnits[i], Endian.little);
}
return byteData.buffer.asUint8List();
}
Moreover, from the sample data it can be concluded (by testing) that the plaintext in the Java code has been encoded with UTF-8.
Regarding PBKDF2: In the Dart code, key and IV must be derived from the static salt applied in the Java code.
Also, the parameters from the Java code must be applied (digest: SHA-1, iteration count: 1000, keysize: 32 + 16 = 48 bytes):
Uint8List generateKey(String passphrase, Uint8List salt) {
final derivator = PBKDF2KeyDerivator(HMac(SHA1Digest(), 64))
..init(Pbkdf2Parameters(salt, 1000, 32 + 16));
return derivator.process(utf8.encode(passphrase) as Uint8List);
}
With these changes, key and IV can be derived as follows:
var salt = "FA9A4D0F-5523-4EEF-B226-9A3E8F14FEF8";
var passphrase = "733D3A17-D8A0-454B-AD22-88608FD0C46A";
var saltBytes = encodeUtf16LE(salt);
var keyIv = generateKey(passphrase, saltBytes);
var key = keyIv.sublist(0, 32);
var iv = keyIv.sublist(32, 32 + 16);
The decryption code can be applied unchanged, for decoding use utf8.decode() instead of String.fromCharCodes().
import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
...
var ciphertext = "8tyHRaQCsxmmGW2xPBFYx/PALmvHkmjx/TzaXC2rIv0=";
var encrypted = base64.decode(ciphertext);
var paddingCipher = PaddedBlockCipherImpl(PKCS7Padding(), CBCBlockCipher(AESEngine()))
..init(false, PaddedBlockCipherParameters(ParametersWithIV(KeyParameter(key), iv), null));
var decryptedBytes = paddingCipher.process(encrypted);
var decrypted = utf8.decode(decryptedBytes); // C123492349C1CT20230206130645

Encrypt payload using a key and iv by AES/GCM/NoPadding algorithm in node js and decrypt in java

I have encrypt the file using node.js and decrypt in JAVA. Decryption is done in JAVA using "AES/GCM/Nopadding" algorithm and it is third party app hence I cannot see the JAVA code.
I am encrypting the payload in node.js using "aes-128-gcm" algorithm.
for this, I am try mimicking a working java encryption code
I have tried with crypto and node-forge.
iam getting the output but am getting an error "Bad encryption - payload is not encrypted properly" when submitting payload.
pleas help me to find what I did wrong in this code.
working code in java
public void encrypt(#NonNull final byte[] payload, #NonNull final byte[] key) throws GeneralSecurityException
{
SecretKeySpec codingKey = new SecretKeySpec(key, AES);
Cipher cipher = AEC_GCM_THREAD_CIPHER.get();
byte[] iv = new byte[cipher.getBlockSize()];
RANDOM.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, codingKey, new IvParameterSpec(iv));
final byte[] encryptedPayload = cipher.doFinal(payload);
byte[] encryptMerchantKey = encryptMerchantKey(key);
String payloadFinal = encodeToUrlString(encryptedPayload); // final payload
String ivFinal = encodeToUrlString(iv); // final iv
String keyFinal = encodeToUrlString(encryptMerchantKey); // final key
System.out.println("Payload");
System.out.println(payloadFinal);
System.out.println("iv");
System.out.println(ivFinal);
System.out.println("key");
System.out.println(keyFinal);
}
code iam tried in node js
function encrypt(payload) {
let key = forge.random.getBytesSync(16);
let iv = forge.random.getBytesSync(16);
let cipher = forge.cipher.createCipher("AES-GCM", key);
cipher.start({ iv: iv});
cipher.update(forge.util.createBuffer(payload));
cipher.finish();
let encrypted = forge.util.encode64(cipher.output.getBytes());
let tag = forge.util.encode64(cipher.mode.tag.getBytes());
let iv64 = forge.util.encode64(iv);
let encryptedPayload = encrypted+tag;
//RSA Encryption
encryptedkey = RSAencrypt(forge.util.encode64(key));
return {
"payload" : base64url.fromBase64(encryptedPayload) ,
"iv" : base64url.fromBase64(iv64).length,
"key" : base64url.fromBase64(encryptedkey)
};
}
Rsa description is working fine abling to decrypt the key.
some problem with aes encryption. as see the code, I added auth tag and encrypted data together but no use.
I have complete example of encryption and decryption in angular and java you can take this example as it is and make changes according to you.
Install node-forge with command "npm install node-forge".
encrypt(msg, pass) {
const key = CryptoJS.lib.WordArray.random(8).toString();
const iv = CryptoJS.lib.WordArray.random(8).toString();
// encrypt some bytes using GCM mode
const cipher = forge.cipher.createCipher('AES-GCM', key);
cipher.start({
iv: iv,
additionalData: 'nvn', // optional
tagLength: 128 // optional, defaults to 128 bits
});
cipher.update(forge.util.createBuffer(msg));
cipher.finish();
const encrypted = cipher.output;
const encodedB64 = forge.util.encode64(encrypted.data);
const tag = cipher.mode.tag;
const tagB64 = forge.util.encode64(tag.data);
// outputs encrypted hex
const trasmitmsg = key+iv+tagB64+encodedB64;
return trasmitmsg
}
I have used CryptoJS to generate random string because random of node-forge giving nontransferable strings.
java code to decrypt this trasmitmsg is
public String getDecrypt(String transmsg) throws Exception {
String keyString = transmsg.substring(0, 16);
String ivString = transmsg.substring(16, 32);
String additionalString = transmsg.substring(32, 56);
String cipherString = transmsg.substring(56);
byte[] keyBytes = keyString.getBytes();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
byte[] ivBytes = ivString.getBytes();
byte[] one = Base64.getDecoder().decode(cipherString);
byte[] two = Base64.getDecoder().decode(additionalString);
byte[] cipherText = ArrayUtils.addAll(one, two);
return decrypt(cipherText, key, ivBytes);
}
public static String decrypt(byte[] cipherText, SecretKey key, byte[] IV) throws Exception {
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH , IV);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
cipher.updateAAD("nvn".getBytes());
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
Cheers!!!
The problem was with forge buffer it need to convert to node buffer
this code is working now. thanks, #Maarten Bodewes for the advice.
function encrypt(payload) {
//initialize forge random buffer
var key = forge.random.getBytesSync(16);
var iv = forge.random.getBytesSync(16);
let cipher = forge.cipher.createCipher("AES-GCM", key);
cipher.start({iv : iv});
cipher.update(forge.util.createBuffer(payload));
cipher.finish();
let encrypted = cipher.output.data;
let tag = cipher.mode.tag.data;
let encryptedLoad = encrypted+tag;
// node buffer and forge buffer differ, so the forge buffer must be converted to node Buffer
iv = Buffer.from(iv, "binary");
encryptedLoad = Buffer.from(encryptedLoad, "binary");
//Calling RSA Encryption
encryptedKey = RSAencrypt(key);
return {
"payload" : base64url(encryptedLoad) ,
"iv" : base64url(iv),
"key" : base64url.fromBase64(encryptedKey)
};
}

Cannot decrypt AES-256 GCM with Java

I have a node module that can both encrypt and decrypt with AES-256 GCM. Now I am also trying to decrypt with Java whatever the node module encrypts, but I keep getting a AEADBadTagException.
I have tested the node module by itself and can confirm that it works as intended. I know that Java assumes the authentication tag is the last part of the message, so I ensured that the tag is the last thing appended in the node module.
Right now I'm just testing with the word, "hello". This is the encrypted message from node:
Q10blKuyyYozaRf0RVYW7bave8mT5wrJzSdURQQa3lEqEQtgYM3ss825YpCQ70A7hpq5ECPafAxdLMSIBZCxzGbv/Cj4i6W4JCJXuS107rUy0tAAQVQQA2ZhbrQ0gNV9QA==
The salt is not really being used right now because I am trying to keep things simple for testing purposes
Node module:
var crypto = require('crypto');
var encrypt = function(masterkey, plainText) {
// random initialization vector
var iv = crypto.randomBytes(12);
// random salt
var salt = crypto.randomBytes(64);
var key = masterkey;
// AES 256 GCM Mode
var cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
// encrypt the given text
var encrypted = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);
// extract the auth tag
var tag = cipher.getAuthTag();
return Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
};
var decrypt = function(masterkey, encryptedText) {
// base64 decoding
var bData = new Buffer(encryptedText, 'base64');
// convert data to buffers
var salt = bData.slice(0, 64);
var iv = bData.slice(64, 76);
var tag = bData.slice(bData.length - 16, bData.length);
var text = bData.slice(76, bData.length - 16);
var key = masterkey;
// AES 256 GCM Mode
var decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
// encrypt the given text
var decrypted = decipher.update(text, 'binary', 'utf8') + decipher.final('utf8');
return decrypted;
};
module.exports = {
encrypt: encrypt,
decrypt: decrypt
}
Java Class:
The main method is just there for testing right now and will be removed later.
package decryption;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class DecryptAES256 {
private static String salt;
private static byte[] ivBase64;
private static String base64EncryptedText;
private static String key;
public static void main(String[] args) {
String key = "123456789aabbccddeefffffffffffff";
String sourceText = "Q10blKuyyYozaRf0RVYW7bave8mT5wrJzSdURQQa3lEqEQtgYM3ss825YpCQ70A7hpq5ECPafAxdLMSIBZCxzGbv/Cj4i6W4JCJXuS107rUy0tAAQVQQA2ZhbrQ0gNV9QA==";
System.out.println(decrypt(key, sourceText));
}
public static String decrypt(String masterkey, String encryptedText) {
byte[] parts = encryptedText.getBytes();
salt = new String(Arrays.copyOfRange(parts, 0, 64));
ivBase64 = Arrays.copyOfRange(parts, 64, 76);
ivBase64 = Base64.getDecoder().decode(ivBase64);
base64EncryptedText = new String(Arrays.copyOfRange(parts, 76, parts.length));
key = masterkey;
byte[] decipheredText = decodeAES_256_CBC();
return new String(decipheredText);
}
private static byte[] decodeAES_256_CBC() {
try {
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] text = base64EncryptedText.getBytes();
GCMParameterSpec params = new GCMParameterSpec(128, ivBase64, 0, ivBase64.length);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, params);
return cipher.doFinal(Base64.getDecoder().decode(text));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to decrypt");
}
}
}
The exception thrown is normal, you have 2 problems in your Java code:
1- your AES key is not decoded correctly: it is wrapped in hexadecimal representation and you decode it as if it was not. You need to convert it from the hexadecimal representation to bytes, when calling SecretKeySpec().
Replace the following line:
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
By this one:
SecretKeySpec skeySpec = new SecretKeySpec(Hex.decodeHex(key.toCharArray()), "AES");
Note that to get access to the Hex class, you need to import org.apache.commons.codec.binary.Hex in your class file and include the corresponding Apache commons-codec library in your project.
2- you split your base64 cipher text before having converted it to base64, this is not the correct order to do things:
At the start of your decrypt() method, you need to first call Base64.getDecoder().decode() on your cipher text (sourceText) before splitting it into the corresponding fields.
If you want an example of using AES-256-GCM in Java, you can look at the following example I had written some months ago: https://github.com/AlexandreFenyo/kif-idp-client
The source code to encrypt and decrypt is in the following file: https://github.com/AlexandreFenyo/kif-idp-client/blob/master/src/kif/libfc/Tools.java
See the methods named encodeGCM() and decodeGCM().
Those methods are called by the main class here: https://github.com/AlexandreFenyo/kif-idp-client/blob/master/src/kif/libfc/CommandLine.java

AES 128 encryption in Android and .Net with custom key and IV

I have a password string in my android application. I need to the send the password through the .net web service (i.e. end with .aspx) using the SOAP web service. Before sending the password i need to encrypt the password with AES 128 encryption with the custom key and IV.
They have a encrypt/decrypt tool in .net with the custom key and Iv. The tool ask a custom key with 16 digit and IV 8 digit. If give the string it generate the encrypting string. example
Example:
Key : 1234567812345678
IV : 12345678
String : android
Encrypted string : oZu5E7GgZ83Z3yoK4y8Utg==
I didn't have any idea how to do this in android. Need help.
A complete example may help you:
The encrypt/decrypt functions, using IV
public static byte[] encrypt(byte[] data, byte[] key, byte[] ivs) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static byte[] decrypt(byte[] data, byte[] key, byte[] ivs) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
You can use it as below :
String dataToEncryptDecrypt = "android";
String encryptionDecryptionKey = "1234567812345678";
String ivs = "12345678";
byte[] encryptedData = encrypt(dataToEncryptDecrypt.getBytes(), encryptionDecryptionKey.getBytes(),
ivs.getBytes());
// here you will get the encrypted bytes. Now you can use Base64 encoding on these bytes, before sending to your web-service
byte[] decryptedData = decrypt(encryptedData, encryptionDecryptionKey.getBytes(), ivs.getBytes());
System.out.println(new String(decryptedData));
I don't know the details of AES algorithm in use(ie mode & padding method), bit it should be roughly like this:
public static byte[] encrypt(byte[] data, byte[] key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/ZeroBytePadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] empty = new byte[16]; // For better security you should use a random 16 byte key!!!
IvParameterSpec ivps = new IvParameterSpec(empty);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
// ...
}
return null;
}
Function above could be used like this:
String data = "android";
String key = "1234567812345678";
byte encrypted = encrypt(data.getbytes("UTF-8"), key.getbytes("UTF-8"));

Why doesn't this simple AES encryption work?

Why does not this AES encryption work? I've written it in Java to test, but I am not able to decrypt. I get garbage upon decryption. Why? Its so simple - In the main method, print plain text, encrypt, print cipher text, decrypt, print plain text again. Am I doing something wrong? Please help me figure out the problem.
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AESTest {
public static void main(String [] args) {
try {
String plainText = "Hello World!!!!!";
String encryptionKey = "E072EDF9534053A0B6C581C58FBF25CC";
System.out.println("Before encryption - " + plainText);
String cipherText = encrypt(plainText, encryptionKey);
System.out.println("After encryption - " + cipherText);
String decrypted = decrypt(cipherText, encryptionKey);
System.out.println("After decryption - " + decrypted);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String encrypt(String plainText, String passkey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(hexStringToByteArray(passkey), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[cipher.getBlockSize()]));
String cipherText = new String(cipher.doFinal(plainText.getBytes()));
return cipherText;
}
public static String decrypt(String cipherText, String passkey) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(hexStringToByteArray(passkey), "AES");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(new byte[cipher.getBlockSize()]));
String plainText = new String(cipher.doFinal(cipherText.getBytes()));
return plainText;
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}
The output of the cipher is a sequence of random-looking bytes. You have no guarantee that these bytes will be a valid encoding for a character string in whatever is your system's default encoding. So this line:
String cipherText = new String(cipher.doFinal(.....));
is likely to lose information that you'll need for decryption.
Therefore you will not get the right bytes reconstructed in your decrypt operation. For example, if your default encoding is UTF-8, it is overwhelmingly unlikely that the correct ciphertext is something that String.getBytes() is even able to produce.
Two things:
No padding can only work if you use input that is an exact mulitple of your key size, which is 128 bit or 16 bytes. So in your particular case "Hello World!!!!!".getBytes() is actually a multiple of 16, but this is of course not true for arbitrary Strings.
Use "AES/CBC/PKCS5Padding" instead to solve this issue.
Do not turn your encrypted data into a String - this will and change the encrypted output. There's no guarantee that new String(byte[]).getBytes() returns the exact same byte array!
So you should leave the encrypted data as what it is - a stream of bytes. Thus encrypt should return byte[] instead and decrypt should take byte[] as input - this is a working example:
public class NewClass {
public static void main(String [] args) {
try {
String plainText = "Hello World!!!!";
String encryptionKey = "E072EDF9534053A0B6C581C58FBF25CC";
System.out.println("Before encryption - " + plainText);
byte[] cipherText = encrypt(plainText, encryptionKey);
System.out.println("After encryption - " + cipherText);
String decrypted = decrypt(cipherText, encryptionKey);
// -> Hello World!!!!
System.out.println("After decryption - " + decrypted);
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] encrypt(String plainText, String passkey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(hexStringToByteArray(passkey), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[cipher.getBlockSize()]));
return cipher.doFinal(plainText.getBytes());
}
public static String decrypt(byte[] cipherText, String passkey) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(hexStringToByteArray(passkey), "AES");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(new byte[cipher.getBlockSize()]));
return new String(cipher.doFinal(cipherText));
}
You need to create the SecretKeySpec object once and use it for both encrypt and decrypt. Currently the code is creating two different keys for each operation and this will definitely lead to incorrect results.

Categories

Resources