I am working on mobile application and client side we are using JavaScript (kony) at server side its java. This is working fine for all other device except intel chipset devices (ASUS Zenfone). PFB the JS code for encryption
function encryptDataModeCBC()
{
var encData = "Test";
try
{
var encText = CryptoJS.AES.encrypt(encData, "3f4c57006f7d2d9528de3c46b626df06cdc405cb0243b10ca7612d967c688744", {
iv: "31fd1ae51454cd55db81f1fa60a343ed",
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).ciphertext.toString(CryptoJS.enc.Base64);
alert ("encText => "+encText);
kony.print("$$$$ encText => "+encText);
}
catch (e)
{
alert(kony.i18n.getLocalizedString("technicalError"));
}
}
Here creating IV & secret key using sha256 & sha512 hashing algorithm.
PFB the code snippet which we are using at server side for decrypting the encrypted string
secret key generation code
private SecretKeySpec getKey(String mode, String msgDigest, String encryptionKey, boolean is256) throws Exception {
byte[] key = encryptionKey.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance(msgDigest); // This is SHA-256
key = sha.digest(key);
if (is256) { // This is true in our case.
key = Arrays.copyOf(key, 32);
this.logger.debug("Secret Key " + DigestUtils.sha256Hex(encryptionKey).substring(0, 32));
} else {
key = Arrays.copyOf(key, 16);
this.logger.debug("Secret Key " + DigestUtils.sha256Hex(encryptionKey).substring(0, 16));
}
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
String modeStr = mode.equals("ECB") ? "AES/ECB/PKCS5Padding" : "AES/CBC/PKCS5Padding";
cipher = Cipher.getInstance(modeStr);
return secretKeySpec;
}
IV generation at server side
private IvParameterSpec getIV(String uid, String pin) throws Exception {
String ivValue = new StringBuilder(uid).reverse().toString() + new StringBuilder(pin).reverse();
byte[] key = ivValue.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-256");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
IvParameterSpec iv = new IvParameterSpec(key);
return iv;
}
As I mentioned above this is failing in intel chipset devices. This is the exception which I am getting while decrypting the string
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
When I tried encrypting the string "Test" I am getting "Tn2SzI8dmgCmEvQrzdqLxw==" as encrypted string which I have used in below java code and tried to decrypt where I am getting the below error
enc text => 7b9UNDI4IWNITNAQlYNP8w==
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.ust.Encryptor.decrypt(Encryptor.java:92)
at com.ust.Encryptor.main(Encryptor.java:113)
Here is the JAVA code which I have used for decrypting
package com.ust;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
public class Encryptor {
private static final String AES_PASS = "0ca763dc6b05b5230e44beb6b90e346440204b6d334b09623eafd3fcfbad6a302faca28b0994872e3fd782e7353026684b7ac9385662144e0ed1e2a8e3e14fab79059929681e3794eb97271328ecccda6dbfb3a7991ea1324615cf5908fabdf6"; // Hashed into an AES key later
private SecretKeySpec keyObj;
private Cipher cipher;
private IvParameterSpec ivObj;
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public Encryptor() throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException {
// A constant IV, since CBC requires an IV but we don't really need one
String ivValue = new StringBuilder("astring").reverse().toString() + new StringBuilder("0ca763dc6b05b5230e44beb6b90e346440204b6d334b09623eafd3fcfbad6a302faca28b0994872e3fd782e7353026684b7ac9385662144e0ed1e2a8e3e14fab").reverse();
System.out.println("ivValue => "+ivValue);
try {
byte[] ivkey = ivValue.getBytes("UTF-8");
MessageDigest shaIv = MessageDigest.getInstance("SHA-256");
ivkey = shaIv.digest(ivkey);
ivkey = Arrays.copyOf(ivkey, 16);
System.out.println("IV => "+bytesToHex(ivkey));
this.ivObj = new IvParameterSpec(ivkey);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Create an SHA-256 256-bit hash of the key
byte[] key = AES_PASS.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-256");
key = sha.digest(key);
key = Arrays.copyOf(key, 32); // Use only first 256 bit
System.out.println("SEC KEY => "+bytesToHex(key));
this.keyObj = new SecretKeySpec(key, "AES");
// Create a Cipher by specifying the following parameters
// a. Algorithm name - here it is AES
// b. Mode - here it is CBC mode
// c. Padding - e.g. PKCS7 or PKCS5
this.cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
}
public String encrypt(String strDataToEncrypt) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException {
String strCipherText = new String();
this.cipher.init(Cipher.ENCRYPT_MODE, this.keyObj, this.ivObj);
// Encrypt the Data
// a. Declare / Initialize the Data. Here the data is of type String
// b. Convert the Input Text to Bytes
// c. Encrypt the bytes using doFinal method
byte[] byteDataToEncrypt = strDataToEncrypt.getBytes();
byte[] byteCipherText = this.cipher.doFinal(byteDataToEncrypt);
// b64 is done differently on Android
strCipherText = Base64.encodeBase64String(byteCipherText);
return strCipherText;
}
public String decrypt(String strCipherText) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException {
String strDecryptedText = new String();
// Initialize the Cipher for Encryption
this.cipher.init(Cipher.DECRYPT_MODE, this.keyObj, this.ivObj);
// Decode the Base64 text
byte[] cipherBytes = Base64.decodeBase64(strCipherText);
// Decrypt the Data
// a. Initialize a new instance of Cipher for Decryption (normally don't reuse the same object)
// Be sure to obtain the same IV bytes for CBC mode.
// b. Decrypt the cipher bytes using doFinal method
byte[] byteDecryptedText = this.cipher.doFinal(cipherBytes);
strDecryptedText = new String(byteDecryptedText);
return strDecryptedText;
}
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static void main (String args[]) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException{
Encryptor aesCipher = new Encryptor();
try {
String encText = aesCipher.encrypt("Test");
System.out.println("enc text => "+encText);
String plaintext = aesCipher.decrypt("Tn2SzI8dmgCmEvQrzdqLxw==");//("eat6f1uCCXVqJgTNUA8BCqXSA4kG4GhKajXdkyV0TewK+jgDkbQ/lPVaevv4rW3XdSmtVyOKLVJjPw9Akeblrh+ejIv9u48n7PkRKniwfxq/URuPU7lhS/sO5JMiJ7+ufgKFvJapxhSfftCtigtDc8F6Y2lJIPEUeQeQKOVc1noeLqPFggz55hWjWvDtpYh/sG76MwLlWDM7cj+uu6ru3ImmDA7qoM4tJOWBBkfng8u20R1ZcF3gM45TgDLUdL912AE1WO+grGBGjqzTXlK2/jgu3OOsLVI0jndB49K5q3/oKJc7JEoIZb0eZJcuZ80A");
System.out.println("plain text => "+plaintext);
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
CryptoJS assumes that
a key which is passed as a string is actually a password and will hash it again along with a randomly generated salt or it will use the key as-is if it is a WordArray and
the IV should be a WordArray
WordArray is CryptoJS' internal binary data representation.
The code should be:
try {
var key = CryptoJS.enc.Hex.parse("3f4c57006f7d2d9528de3c46b626df06cdc405cb0243b10ca7612d967c688744");
var iv = CryptoJS.enc.Hex.parse("31fd1ae51454cd55db81f1fa60a343ed44");
var encText = CryptoJS.AES.encrypt(encData, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).ciphertext.toString(CryptoJS.enc.Base64);
alert ("encText => "+encText);
kony.print("$$$$ encText => "+encText);
}
catch (e)
{
alert(kony.i18n.getLocalizedString("technicalError"));
}
Something to think about:
If you send the symmetric key from the server to the client, then anyone who might be listening will get the key and can decrypt the ciphertexts you send. This solution doesn't provide security, but rather obfuscation. You should use TLS which would make the connection actually secure.
The IV must be unpredictable (read: random). Don't use a static IV, because that makes the cipher deterministic and therefore not semantically secure. An attacker who observes ciphertexts can determine when the same message prefix was sent before. The IV is not secret, so you can send it along with the ciphertext. Usually, it is simply prepended to the ciphertext and sliced off before decryption.
It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme.
Related
I am trying to decrypt encoded string in javascript using cryto.subtle. I have encoded this string in java using aes gcm. Please let me know the javascript code(using cryto.subtle) equivalent to below java code. Decryption works in java and just for learning I am using same string as key and initialization vector. Javascript code in giving error while decrypting. I am not using nodejs.
JAVA code
package com.hm.fcs.controller;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AesGcmExample
{
static String plainText = "pathname=editOffer&offerId=8ab076727fdbba68017ff98c196b0000&mode=sea";
public static final int GCM_TAG_LENGTH = 16;
// Password derived AES 256 bits secret key
public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
// iterationCount = 65536
// keyLength = 256
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}
public static void main(String[] args) throws Exception
{
char[] secretKey = "ZXE-ERW-QDD-EGXL".toCharArray();
byte[] IV = "ZXE-ERW-QDD-EGXL".getBytes();
// SecureRandom random = new SecureRandom();
// random.nextBytes(IV);
System.out.println("original text : "+plainText);
SecretKey key = getAESKeyFromPassword(secretKey,IV);
byte[] cipherText = encrypt(plainText.getBytes(), key, IV);
System.out.println("Encrypted Text : " + Base64.getEncoder().encodeToString(cipherText));
String decryptedText = decrypt(cipherText, key, IV);
System.out.println("DeCrypted Text : " + decryptedText);
}
public static byte[] encrypt(byte[] plaintext, SecretKey key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
// Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext);
return cipherText;
}
public static String decrypt(byte[] cipherText, SecretKey key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
}
Java script code that I am trying
const pwUtf8 = new TextEncoder().encode("ZXE-ERW-QDD-EGXL");
console.log("before pwHash");
const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8);
console.log("after pwHash");
const ivStr = "ZXE-ERW-QDD-EGXL";
// iv as Uint8Array
const iv = new Uint8Array(Array.from(ivStr).map(ch => ch.charCodeAt(0)));
const alg = { name: 'AES-GCM',
iv: iv };
// generate key from pw
console.log("before key");
const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']);
console.log("after key");
const ctStr = "lZSyv0+aBh6x2rSdNwo683Oz0TtJDDzSjsIe7muCm3jHgUoSOacsYP3Qiaocr2oms1uIAA7jOHKFhG1ntcuyqnEe/0Z/6xNj5lyDSvcDpYPNAEeD";
// ciphertext as Uint8Array
const ctUint8 = new Uint8Array(Array.from(ctStr).map(ch => ch.charCodeAt(0)));
try {
// decrypt ciphertext using key
console.log("before decrypt");
const plainBuffer = await crypto.subtle.decrypt(alg, key, ctUint8);
console.log("after decrypt");
// return plaintext from ArrayBuffer
return new TextDecoder().decode(plainBuffer);
} catch (e) {
console.log("in catch",e);
}
Please help me in resolving this issue.
Trying the above javascript code to decrypt but not working. Need equivalent code to java in javascript using crypto.subtle. I am not using nodejs.
Getting this in console
Uncaught (in promise) Error: OperationError
We are using the services of a third party in our organization in which we have to send some data to them in an encrypted manner. Recently, they updated the encryption algorithm to AES/GCM/NoPadding.
They have their code in java whereas we use javascript. they have shared with us their implementation of the algorithm in Java which we have to replicate and implement in JS because that is what we use.
I am facing challenges in converting this code. Attaching both Java implementation which works like a charm and the JS code which is not working as expected. Although I have tried multiple things but none of them worked for me. So, I am sharing only the latest code that I tried.
I have no knowledge of Java or cryptography so any help in that direction will be highly appreciated.
JAVA Code -
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Encryption class for managing all types of AES encryptions
*/
public class EncryptionUtil {
private final Builder mBuilder;
private final static String HEX = "0123456789ABCDEF";
private EncryptionUtil(Builder builder) {
mBuilder = builder;
}
public static EncryptionUtil getDefault(String key, String salt, byte[] iv) {
try {
return Builder.getDefaultBuilder(key, salt, iv).build();
} catch (NoSuchAlgorithmException e) {
return null;
}
}
public String encryptOrNull(String data) {
try {
return encrypt(data);
} catch (Exception e) {
return "";
}
}
private String encrypt(String data) throws Exception {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
return doEncryptAES(data, secretKey, mBuilder.getAlgorithm(), mBuilder.getCharsetName());
}
private String decrypt(String data) throws Exception {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
return doDecryptAES(data, secretKey, mBuilder.getAlgorithm());
}
private String doEncryptAES(String inputString,
SecretKey key, String xForm, String charset) throws Exception {
byte inpBytes[] = inputString.getBytes(charset);
Cipher cipher = Cipher.getInstance(xForm);
switch (xForm) {
case "AES/ECB/PKCS5Padding":
case "AES/ECB/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key);
break;
case "AES/CBC/PKCS5Padding":
case "AES/CBC/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
break;
case "AES/GCM/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, mBuilder.getIv()));
byte[] encryptedData = cipher.doFinal(inpBytes);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + mBuilder.getIv().length + encryptedData.length);
byteBuffer.putInt(mBuilder.getIv().length);
byteBuffer.put(mBuilder.getIv());
byteBuffer.put(encryptedData);
return toHex(byteBuffer.array());
}
return toHex(cipher.doFinal(inpBytes));
}
/**
* for AES in GCM mode kitkat version is required
*
* #param inputString is String we want to decrypt
* #param key is symmetric key use for decryption and it similar to key used for encryption (128,192,256)
* #param xForm is the transformation form in which form we want to transform
* (AES/ECB/PKCS5Padding,AES/ECB/NoPadding,AES/CBC/PKCS5Padding,AES/CBC/NoPadding,AES/GCM/NoPadding)
* #return it reurn decrypted string
* #throws Exception NOSuchAlgorithmEXception,NoSuchPaddingEXception
*/
private String doDecryptAES(String inputString,
SecretKey key, String xForm) throws Exception {
byte[] inpBytes = toByte(inputString);
Cipher cipher = Cipher.getInstance(xForm);
switch (xForm) {
case "AES/ECB/PKCS5Padding":
case "AES/ECB/NoPadding":
cipher.init(Cipher.DECRYPT_MODE, key);
break;
case "AES/CBC/PKCS5Padding":
case "AES/CBC/NoPadding":
cipher.init(Cipher.DECRYPT_MODE, key, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
break;
case "AES/GCM/NoPadding":
ByteBuffer byteBuffer = ByteBuffer.wrap(inpBytes);
int noonceSize = byteBuffer.getInt();
if (noonceSize < 12 || noonceSize >= 16)
throw new IllegalArgumentException("Nonce size is incorrect. Make sure that the incoming data is an AES encrypted file.");
byte[] iv = new byte[noonceSize];
byteBuffer.get(iv);
byte[] cipherBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherBytes);
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
return new String(cipher.doFinal(cipherBytes), mBuilder.getCharsetName());
}
return new String(cipher.doFinal(inpBytes), mBuilder.getCharsetName());
}
public String decryptOrNull(String data) {
try {
return decrypt(data);
} catch (Exception e) {
return "";
}
}
private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(mBuilder.getSecretKeyType());
KeySpec spec = new PBEKeySpec(key, mBuilder.getSalt().getBytes(mBuilder.getCharsetName()), mBuilder.getIterationCount(), mBuilder.getKeyLength());
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), mBuilder.getKeyAlgorithm());
}
private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance(mBuilder.getDigestAlgorithm());
messageDigest.update(key.getBytes(mBuilder.getCharsetName()));
return toHex(messageDigest.digest()).toCharArray();
}
private 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 String toHex(byte[] stringBytes) {
StringBuffer result = new StringBuffer(2 * stringBytes.length);
for (int i = 0; i < stringBytes.length; i++) {
result.append(HEX.charAt((stringBytes[i] >> 4) & 0x0f)).append(HEX.charAt(stringBytes[i] & 0x0f));
}
return result.toString();
}
private static class Builder {
private byte[] mIv;
private int mKeyLength;
private int mIterationCount;
private String mSalt;
private String mKey;
private String mAlgorithm;
private String mKeyAlgorithm;
private String mCharsetName;
private String mSecretKeyType;
private String mDigestAlgorithm;
private String mSecureRandomAlgorithm;
private SecureRandom mSecureRandom;
private IvParameterSpec mIvParameterSpec;
static Builder getDefaultBuilder(String key, String salt, byte[] iv) {
return new Builder()
.setIv(iv)
.setKey(key)
.setSalt(salt)
.setKeyLength(128)
.setKeyAlgorithm("AES")
.setCharsetName("UTF8")
.setIterationCount(1)
.setDigestAlgorithm("SHA-256")
.setAlgorithm("AES/GCM/NoPadding")
.setSecureRandomAlgorithm("SHA1PRNG")
.setSecretKeyType("PBKDF2WithHmacSHA1");
}
private EncryptionUtil build() throws NoSuchAlgorithmException {
setSecureRandom(SecureRandom.getInstance(getSecureRandomAlgorithm()));
SecureRandom secureRandom = new SecureRandom();
byte[] iv = getIv();
secureRandom.nextBytes(iv);
setIvParameterSpec(new IvParameterSpec(iv));
return new EncryptionUtil(this);
}
private String getCharsetName() {
return mCharsetName;
}
private Builder setCharsetName(String charsetName) {
mCharsetName = charsetName;
return this;
}
private String getAlgorithm() {
return mAlgorithm;
}
private Builder setAlgorithm(String algorithm) {
mAlgorithm = algorithm;
return this;
}
private String getKeyAlgorithm() {
return mKeyAlgorithm;
}
private Builder setKeyAlgorithm(String keyAlgorithm) {
mKeyAlgorithm = keyAlgorithm;
return this;
}
private String getSecretKeyType() {
return mSecretKeyType;
}
private Builder setSecretKeyType(String secretKeyType) {
mSecretKeyType = secretKeyType;
return this;
}
private String getSalt() {
return mSalt;
}
private Builder setSalt(String salt) {
mSalt = salt;
return this;
}
private String getKey() {
return mKey;
}
private Builder setKey(String key) {
mKey = key;
return this;
}
private int getKeyLength() {
return mKeyLength;
}
Builder setKeyLength(int keyLength) {
mKeyLength = keyLength;
return this;
}
private int getIterationCount() {
return mIterationCount;
}
Builder setIterationCount(int iterationCount) {
mIterationCount = iterationCount;
return this;
}
private String getSecureRandomAlgorithm() {
return mSecureRandomAlgorithm;
}
Builder setSecureRandomAlgorithm(String secureRandomAlgorithm) {
mSecureRandomAlgorithm = secureRandomAlgorithm;
return this;
}
private byte[] getIv() {
return mIv;
}
Builder setIv(byte[] iv) {
mIv = iv;
return this;
}
private SecureRandom getSecureRandom() {
return mSecureRandom;
}
Builder setSecureRandom(SecureRandom secureRandom) {
mSecureRandom = secureRandom;
return this;
}
private IvParameterSpec getIvParameterSpec() {
return mIvParameterSpec;
}
Builder setIvParameterSpec(IvParameterSpec ivParameterSpec) {
mIvParameterSpec = ivParameterSpec;
return this;
}
private String getDigestAlgorithm() {
return mDigestAlgorithm;
}
Builder setDigestAlgorithm(String digestAlgorithm) {
mDigestAlgorithm = digestAlgorithm;
return this;
}
}
public static void main(String[] args) {
String secretKey = "some_secret_key";
String salt = "some_secret_salt";
EncryptionUtil encryptionUtil = EncryptionUtil.getDefault(secretKey, salt, new byte[12]);
String data = "Data to encrypt";
System.out.println("Encrypted:");
String encrypted = encryptionUtil.encryptOrNull(data);
System.out.println(encrypted);
System.out.println("Decrypted:");
System.out.println(encryptionUtil.decryptOrNull(encrypted));
}
}
Please note I need help only to encrypt the data
JS Code -
import * as crypto from 'crypto';
export const encData = () => {
const data = 'Data to encrypt';
const secretKey = 'some_secret_key';
const salt = 'some_secret_salt';
let key = '';
const keyHash = key => {
const hash = crypto.createHash('sha256');
const hashedKey = hash.update(key, 'utf-8');
return hashedKey.digest('hex').toUpperCase();
};
const getSecretKey = key => {
return crypto.pbkdf2Sync(key, salt, 1, 16, 'sha1');
};
key = getSecretKey(keyHash(secretKey));
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
const buffer = Buffer.from(_.isPlainObject(data) ? JSON.stringify(data) : data);
// Updating text
let encrypted = cipher.update(buffer);
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('base64');
};
console.log(encData());
To make sure my code is working fine I am decrypting my encoded string generated with JS function by passing it to Java decrypt function.
In the Java code, the result of the encryption is composed as follows:
iv-length (4 bytes, BE) | IV | ciphertext | authentication tag
In contrast, in the NodeJS code the result consists only of the ciphertext, i.e. IV length, IV and tag are missing and must be added.
Here it must be taken into account that Java's SunJCE provider automatically concatenates ciphertext and tag, while this must happen explicitly in the NodeJS code.
Also, the ciphertext is returned hex encoded in the Java code, while it is Base64 encoded in the NodeJS code. This also needs to be changed in the NodeJS code.
The fix is to replace in the NodeJS code the lines:
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('base64');
with:
const length = Buffer.allocUnsafe(4);
length.writeUInt32BE(iv.length);
// Using concatenation
encrypted = Buffer.concat([length, iv, encrypted, cipher.final(), cipher.getAuthTag()]);
return encrypted.toString('hex');
With this, the NodeJS code returns a result that can be decrypted by the Java code.
Note that a static salt is insecure. Instead, the salt should be randomly generated like the IV for each encryption and passed along with the ciphertext.
Also, an iteration count of 1 is not secure, the value should be as high as possible with acceptable performance.
Hashing the key with SHA256 before the PBKDF2 derivation is actually not necessary (at least if PBKDF2 is applied correctly).
I have written the following Java code to encrypt a message/data. Currently it is using default encryption algorithm (AES/ECB/PKCS5PADDING). In JavaScript while decrypting I have used mode ECB. I read articles that ECB is not secure. So I need to move to CBC mode. But changing the mode is causing issue for me. Can you help me to change the mode in proper way so that it is compatible?
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class EncryptDecryptImpl {
private static final String secretKey = "abcdefghijklmnop";
private static final String mySecretKey = "my-secret-key";
private static final String encryptionAlgorithm = "AES"; // need to use AES/CBC/PKCS5Padding
public static String encrypt(String data, String secret) {
try {
Key key = generateKey(secret);
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedValue = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedValue);
} catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException |
BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
}
return null;
}
public static String decrypt(String strToDecrypt, String secret) {
try {
Key key = generateKey(secret);
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException |
BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
}
return null;
}
private static Key generateKey(String secret) {
byte[] decoded = Base64.getDecoder().decode(secret.getBytes());
return new SecretKeySpec(decoded, encryptionAlgorithm);
}
public static String encodeKey(String key) {
byte[] encoded = Base64.getEncoder().encode(key.getBytes());
return new String(encoded);
}
public static String decodeKey(String key) {
byte[] decoded = Base64.getDecoder().decode(key.getBytes());
return new String(decoded);
}
public static String encodedBase64Key() {
return encodeKey(secretKey);
}
public static String decodedBase64Key(String encryptedSecretKey) {
return decodeKey(encryptedSecretKey);
}
public static String aesEncryptedSecretKey() {
return EncryptDecryptImpl.encrypt(mySecretKey, encodedBase64Key());
}
public static String aesDecryptedSecretKey() {
return EncryptDecryptImpl.decrypt(aesEncryptedSecretKey(), encodedBase64Key());
}
}
Test:
String encryptedSecretKey = EncryptDecryptImpl.aesEncryptedSecretKey(); // cipher text
JavaScript:
export const getSecretKey = () => {
const encryptedBase64Key = 'bXVzdEJlMTZCeXRlc0tleQ==';
const parsedBase64Key = enc.Base64.parse(encryptedBase64Key);
const encryptedCipherText = getSessionStorageItem('uselessKey');
let decryptedData = '';
if (encryptedCipherText !== null) {
decryptedData = AES.decrypt(encryptedCipherText, parsedBase64Key, {
mode: mode.ECB, // need to use CBC
padding: pad.Pkcs7
})
}
return decryptedData.toString(enc.Utf8).toString();
}
The roadmap has already been roughly outlined by M. Fehr in his comment. The CBC mode uses an IV. In general it has to be considered that a key/IV pair must not be applied more than once for security reasons. Therefore, the IV is usually randomly generated for each encryption.
The IV must be known during decryption. Hence, it is passed together with the ciphertext. However, since the IV is not secret, it is passed unencrypted, usually concatenated with the ciphertext in the order IV | ciphertext.
For this additional functionality the encrypt() method in the Java code has to be adapted as follows (for simplicity without exception handling):
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
...
private static final String encryptionAlgorithm = "AES/CBC/PKCS5Padding";
private static final String keyAlgorithm = "AES";
...
public static String encrypt(String data, String secret) {
Key key = generateKey(secret);
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
// Generate random IV, encrypt and concatenate IV and ciphertext
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] ciphertext = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
byte[] encryptedValue = ByteBuffer.allocate(iv.length + ciphertext.length).put(iv).put(ciphertext).array();
return Base64.getEncoder().encodeToString(encryptedValue);
}
Note that in generateKey() with this change, keyAlgorithm must be used instead of encryptionAlgorithm.
On the JavaScript side, IV and ciphertext must be separated. CBC and PKCS7 are the default and do not need to be specified explicitly.
The ciphertext in the following CryptoJS code was generated with the above C# code and returns the original plaintext:
const enc = CryptoJS.enc, lib = CryptoJS.lib, AES = CryptoJS.AES;
const encryptedBase64Key = 'bXVzdEJlMTZCeXRlc0tleQ==';
const parsedBase64Key = enc.Base64.parse(encryptedBase64Key);
const encryptedCipherText = 'FZ+lnxu9iZGkxmmBxae32ToSkoi+a2/BpzAd6HYnyBjFjCmpssVUVx9N+KQbhpU2ALpJVG8my25KTG6xg7AOXQ==';
const parsedCipherText = enc.Base64.parse(encryptedCipherText);
const iv = lib.WordArray.create(parsedCipherText.words.slice(0, 16 / 4));
const ciphertext = lib.WordArray.create(parsedCipherText.words.slice(16 / 4));
if (encryptedCipherText !== null) {
decryptedData = AES.decrypt({ciphertext: ciphertext}, parsedBase64Key,{iv: iv});
}
console.log(decryptedData.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Decryption in the Java code and encryption in the JavaScript code are to be changed analogously.
Note that old data must be migrated, because data encrypted with ECB mode cannot be decrypted with CBC mode.
I have a code for encrypting and decrypting in Java as such
public static String decrypt(String strToDecrypt, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(base64Decode(strToDecrypt)));
}
public static String encrypt(String strToEncrypt, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return base64Encode(cipher.doFinal(strToEncrypt.getBytes("UTF-8")));
}
I tried to encrypt the data in forge js, and decrypt it using Java. All I get is
Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
function aesEncrypt(data, secretKey) {
var cipher = forge.cipher.createCipher('AES-ECB', secretKey)
cipher.start()
cipher.update(forge.util.createBuffer(data))
cipher.finish()
return forge.util.encode64(cipher.output.data)
}
function aesDecrypt(data, secretKey) {
var decipher = forge.cipher.createDecipher('AES-ECB', secretKey)
decipher.start()
decipher.update(forge.util.createBuffer(forge.util.decode64(data)))
decipher.finish()
return decipher.output.data
}
Is there a way to solve this?
Below is the way I generate my key. It is 32 length alphanumeric string.
The secret key is sent in the request itself (Symmetric enc).
SecretKey secretKey = AESEncryptionUtil.generateSecretKey();
public static SecretKey generateSecretKey() throws Exception {
String secretStr = RandomGenerator.getAlphaNumericString(32);
log.info("Generated secret key : {}", secretStr);
byte[] decodeSecretKey = base64Decode(secretStr);
return new SecretKeySpec(decodeSecretKey, 0, decodeSecretKey.length, "AES");
}
The data is then encrypted. Now in JS, it is done as below
function generateSecret(length) {
var result = ''
var characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
var charactersLength = characters.length
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
let aesEncryptedData = aesEncrypt(data, generateSecret(32))
I tried to debug it. I get the plain secret key as it is. The signature is also verified. But the data is not decrypted. I am confused if the Java's SecretKey Interface behaves differently, as in JS I use the plain secret key itself, while in Java I use the SecretKey.
The main problem will be (without seeing more code with a full running example) that you need to use the same generated secretKey on both side (encryption and decryption). As you simply use the output of generateSecret(32) as input for your aesEncrypt-function you do not have access to the same key on decryption side.
let aesEncryptedData = aesEncrypt(data, generateSecret(32))
This is even more important when changing the platforms (javascript to Java). Below you find an example code in Java that takes the data of your encryption part (Javascript) and uses it for decryption.
Security warning: the code is UNSECURE as it uses AES in unsecure mode ECB. The codes does not have any exception handling and is for
educational purpose only.
output:
Is there a difference in generating Secret Key in Java (SecretKey Interface) and forge?
generatedSecret: Mwhq5YXBY6d7zUUbQbVxsJeXvuOz7Sqp
aesEncryptedData: 2TxS64Eku0yO7Na7WwEWinnVwkzrbv2yTIdRA/cW5VURtuTdCyud0Bo4orxP1+ky
decryptedString: The quick brown fox jumps over the lazy dog
code:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class MainSo {
public static void main(String[] args) throws Exception {
System.out.println("Is there a difference in generating Secret Key in Java (SecretKey Interface) and forge?");
String generatedSecret = "Mwhq5YXBY6d7zUUbQbVxsJeXvuOz7Sqp";
String aesEncryptedData = "2TxS64Eku0yO7Na7WwEWinnVwkzrbv2yTIdRA/cW5VURtuTdCyud0Bo4orxP1+ky";
SecretKey secretKey = new SecretKeySpec(generatedSecret.getBytes(StandardCharsets.UTF_8), "AES");
String decrypt = decrypt(aesEncryptedData, secretKey);
System.out.println("generatedSecret: " + generatedSecret);
System.out.println("aesEncryptedData: " + aesEncryptedData);
System.out.println("decryptedString: " + decrypt);
}
public static String decrypt(String strToDecrypt, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
//return new String(cipher.doFinal(base64Decode(strToDecrypt)));
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
}
}
I am newbee in both Java and JS and trying to Encrypt Password in java, which should be decrypted by my existing JS code. (Do not want to change my JS !)
I think it has something to do with KEY and IV, which I am totally unaware about.
** JAVA PROGRAM **
public class Helper {
public Cipher dcipher, ecipher;
// Responsible for setting, initializing this object's encrypter and
// decrypter Chipher instances
public Helper(String passPhrase) {
// 8-bytes Salt
byte[] salt = {(byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x34, (byte) 0xE3, (byte) 0x03};
// Iteration count
int iterationCount = 19;
try {
// Generate a temporary key. In practice, you would save this key
// Encrypting with DES Using a Pass Phrase
KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount);
SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
ecipher = Cipher.getInstance(key.getAlgorithm());
// Prepare the parameters to the cipthers
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
} catch (InvalidAlgorithmParameterException e) {
System.out.println("EXCEPTION: InvalidAlgorithmParameterException");
} catch (InvalidKeySpecException e) {
System.out.println("EXCEPTION: InvalidKeySpecException");
} catch (NoSuchPaddingException e) {
System.out.println("EXCEPTION: NoSuchPaddingException");
} catch (NoSuchAlgorithmException e) {
System.out.println("EXCEPTION: NoSuchAlgorithmException");
} catch (InvalidKeyException e) {
System.out.println("EXCEPTION: InvalidKeyException");
}
}
// Encrpt Password
#SuppressWarnings("unused")
public String encrypt(String str) {
try {
// Encode the string into bytes using utf-8
byte[] utf8 = str.getBytes("UTF8");
System.out.println("\n UTF8 : " + utf8);
// Encrypt
byte[] enc = ecipher.doFinal(utf8);
System.out.println("\n enc: " + enc);
// Encode bytes to base64 to get a string
return new sun.misc.BASE64Encoder().encode(enc);
} catch (BadPaddingException e) {
} catch (IllegalBlockSizeException e) {
} catch (UnsupportedEncodingException e) {
}
return null;
}
public static void main(String[] args) {
try {
Helper encrypter = new Helper("");
System.out.print("Enter a password : ");
String password = input.nextLine();
String encrypted = encrypter.encrypt(password);
System.out.println("encrypted String:" + encrypted);
} catch (Exception e) {
}
}
}
Above program should Encrypt key - which will be decrypted by following JS :
var encryptedpassword=this.bodyParams.password;
var bytes = CryptoJS.AES.decrypt(encryptedpassword.toString(), accKey);
var newpassword = bytes.toString(CryptoJS.enc.Utf8);
WHERE accKey = "Nqnzu3RhCJ1h8ql5fdKOaKUAbsuURze*********_
Your problem is that you encrypt with DES and decrypt with AES.
Also you are generating a key from passphrase on your Java code, but using it directly on your JavaScript code.
You use salt on the Java side, but do not appear to incorporate the salt in the message. Having the salt+passphrase you can recover the key and iv.
You will need to look around for a another set of examples that use AES on both ends, that generate the key in the same way, and uses the same padding.
Something along the lines of this:
// Generate a temporary key. In practice, you would save this key
// Encrypting with AES Using a Pass Phrase
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 100, 128);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey aesKey = keyFactory.generateSecret(keySpec);
ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Prepare the parameters to the cipthers
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
ecipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec);
You also need to consider TLS for communication as someone just mentioned in the comments, as it is fairly difficult to secure symmetric encryption keys/passphrases on the JS side.