AES encryption in Java and JavaScript

In this article, I’m going to discuss about both side AES encryption in Java and JavaScript. It means:

  1. Encrypt plain text in Java and decrypt cypher text in JavaScript.
  2. Encrypt plain text in JavaScript and decrypt cypher text in Java.

JavaScript side

I use library CryptoJS for AES encryption.

var CryptoJS = require("crypto-js");

class TravisAes {
    constructor() {
        this._keySize = 256;
        this._ivSize = 128;
        this._iterationCount = 1989;
    }

    generateKey(salt, passPhrase) {
        return CryptoJS.PBKDF2(passPhrase, CryptoJS.enc.Hex.parse(salt), {
            keySize: this._keySize / 32,
            iterations: this._iterationCount
        });
    }

    encryptWithIvSalt(salt, iv, passPhrase, plainText) {
        let key = this.generateKey(salt, passPhrase);
        let encrypted = CryptoJS.AES.encrypt(plainText, key, {iv: CryptoJS.enc.Hex.parse(iv)});
        return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
    }

    decryptWithIvSalt(salt, iv, passPhrase, cipherText) {
        let key = this.generateKey(salt, passPhrase);
        let cipherParams = CryptoJS.lib.CipherParams.create({
            ciphertext: CryptoJS.enc.Base64.parse(cipherText)
        });
        let decrypted = CryptoJS.AES.decrypt(cipherParams, key, {iv: CryptoJS.enc.Hex.parse(iv)});
        return decrypted.toString(CryptoJS.enc.Utf8);
    }

    encrypt(passPhrase, plainText) {
        let iv = CryptoJS.lib.WordArray.random(this._ivSize / 8).toString(CryptoJS.enc.Hex);
        let salt = CryptoJS.lib.WordArray.random(this._keySize / 8).toString(CryptoJS.enc.Hex);
        let cipherText = this.encryptWithIvSalt(salt, iv, passPhrase, plainText);
        return salt + iv + cipherText;
    }

    decrypt(passPhrase, cipherText) {
        let ivLength = this._ivSize / 4;
        let saltLength = this._keySize / 4;
        let salt = cipherText.substr(0, saltLength);
        let iv = cipherText.substr(saltLength, ivLength);
        let encrypted = cipherText.substring(ivLength + saltLength);
        let decrypted = this.decryptWithIvSalt(salt, iv, passPhrase, encrypted);
        return decrypted;
    }

    get keySize() {
        return this._keySize;
    }

    set keySize(value) {
        this._keySize = value;
    }

    get iterationCount() {
        return this._iterationCount;
    }

    set iterationCount(value) {
        this._iterationCount = value;
    }
}

export default TravisAes;

Usage:

let aes = new TravisAes();
let encrypted = aes.encrypt('secret', 'This is plain text.');
let decrypted = aes.decrypt('secret', encrypted);
console.log('encrypted: ' + encrypted);
console.log('decrypted: ' + decrypted);

Java side

No need of any dependency. Just only use supported features of JDK 8. Ah, except log4j for logging. You could comment it or replace by another logger.

package me.travistran.util.encyption;

import org.apache.log4j.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;

public class TravisAes {

    public enum DataTypeEnum {
        HEX,
        BASE64
    }

    private static final Logger LOGGER = Logger.getLogger(TravisAes.class);
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    private static final String KEY_ALGORITHM = "PBKDF2WithHmacSHA1";
    private static final int IV_SIZE = 128;
    private static final int IV_LENGTH = IV_SIZE / 4;

    private int keySize = 256;
    private int iterationCount = 1989;
    private DataTypeEnum dataType = DataTypeEnum.BASE64;
    private Cipher cipher;
    private int saltLength;

    public TravisAes() {
        try {
            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            saltLength = this.keySize / 4;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            LOGGER.error(e);
        }
    }

    public TravisAes(int keySize, int iterationCount) {
        this.keySize = keySize;
        this.iterationCount = iterationCount;
        try {
            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            saltLength = this.keySize / 4;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            LOGGER.error(e);
        }
    }

    public String encrypt(String salt, String iv, String passPhrase, String plainText) {
        try {
            SecretKey key = generateKey(salt, passPhrase);
            byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plainText.getBytes(StandardCharsets.UTF_8));
            String cipherText;
            if (dataType.equals(DataTypeEnum.HEX)) {
                cipherText = toHex(encrypted);
            } else {
                cipherText = toBase64(encrypted);
            }
            return cipherText;
        } catch (Exception e) {
            LOGGER.error(e);
            return null;
        }
    }

    public String encrypt(String passphrase, String plainText) {
        try {
            String salt = toHex(generateRandom(keySize / 8));
            String iv = toHex(generateRandom(IV_SIZE / 8));
            String cipherText = encrypt(salt, iv, passphrase, plainText);
            return salt + iv + cipherText;
        } catch (Exception e) {
            LOGGER.error(e);
            return null;
        }
    }

    public String decrypt(String salt, String iv, String passPhrase, String cipherText) {
        try {
            SecretKey key = generateKey(salt, passPhrase);
            byte[] encrypted;
            if (dataType.equals(DataTypeEnum.HEX)) {
                encrypted = fromHex(cipherText);
            } else {
                encrypted = fromBase64(cipherText);
            }
            byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv, encrypted);
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            LOGGER.error(e);
            return null;
        }
    }

    public String decrypt(String passPhrase, String cipherText) {
        try {
            String salt = cipherText.substring(0, saltLength);
            String iv = cipherText.substring(saltLength, saltLength + IV_LENGTH);
            String ct = cipherText.substring(saltLength + IV_LENGTH);
            return decrypt(salt, iv, passPhrase, ct);
        } catch (Exception e) {
            LOGGER.error(e);
            return null;
        }
    }

    private static byte[] generateRandom(int length) {
        SecureRandom random = new SecureRandom();
        byte[] randomBytes = new byte[length];
        random.nextBytes(randomBytes);
        return randomBytes;
    }

    private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
        try {
            cipher.init(encryptMode, key, new IvParameterSpec(fromHex(iv)));
            return cipher.doFinal(bytes);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException
                | BadPaddingException e) {
            LOGGER.error(e);
            return null;
        }
    }

    private SecretKey generateKey(String salt, String passphrase) {
        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
            KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), fromHex(salt), iterationCount, keySize);
            return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
        } catch (Exception e) {
            LOGGER.error(e);
            return null;
        }
    }

    private static byte[] fromBase64(String str) {
        return DatatypeConverter.parseBase64Binary(str);
    }

    private static String toBase64(byte[] ba) {
        return DatatypeConverter.printBase64Binary(ba);
    }

    private static byte[] fromHex(String str) {
        return DatatypeConverter.parseHexBinary(str);
    }

    private static String toHex(byte[] ba) {
        return DatatypeConverter.printHexBinary(ba);
    }

    public DataTypeEnum getDataType() {
        return dataType;
    }

    public void setDataType(DataTypeEnum dataType) {
        this.dataType = dataType;
    }

}

In the above example, the class support writing cypher text in Base64 or Hex string format.

Usage:

TravisAes travisAes = new TravisAes();
String encrypted = travisAes.encrypt("secret", "This is plain text.");
String decrypted = travisAes.decrypt("secret", encrypted);
System.out.println("encrypted: " + encrypted);
System.out.println("decrypted: " + decrypted);

And yes, you could try encrypting in Java/Javascript side and then decrypting in another side.

Share this
Send this to a friend