I am trying to write some Javascript AES encryption/decryption code that is compatible with the defaults of a Java AES/ECB/PKCS5Padding encryption/decryption implementation at my job.
Let me share the implementation code more/less in a junit to show you what I mean:
import static org.junit.Assert.*;
import org.junit.Test;
import com.google.common.base.Charsets;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.io.BaseEncoding.base64;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class EncryptionDecryptionTest {
AES specificAesImpl = new AES("secret");
/* I want javascript code to behave the same way in terms of input/output as shown in the following tests: */
@Test public void encryption() throws Exception {
assertEquals("{ENCRYPT_AES}4XZCA28Y4krd5k/XblolMg==",specificAesImpl.encrypt("password"));
}
@Test public void decryption() throws Exception {
assertEquals("password" , specificAesImpl.decrypt("{ENCRYPT_AES}4XZCA28Y4krd5k/XblolMg=="));
}
}
class AES {
static final String PREFIX = "{ENCRYPT_AES}";
private final String passphrase;
private final Key key;
public AES(String passphrase) {
String formatted = nullToEmpty(passphrase);
if (formatted.getBytes(Charsets.UTF_8).length != 16) {
System.out.format("AES Encryption passphrase was %s to 16 bytes.\n", formatted.length() < 16 ? "padded" : "truncated");
}
this.passphrase = padEnd(formatted, 16, 'X').substring(0, 16);
// Code for SecretKeySpec is in source [4].
this.key = new SecretKeySpec(this.passphrase.getBytes(Charsets.UTF_8), 0, this.passphrase.length(), "AES");
}
public String encrypt(String value) {
try {
Cipher cipher = Cipher.getInstance(this.key.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, this.key);
final byte[] bytes = value.getBytes(Charsets.UTF_8);
final byte[] encrypted = cipher.doFinal(bytes);
return PREFIX + base64().encode(encrypted);
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
System.out.println("Could not encrypt value with provided passphrase.");
throw new RuntimeException();
}
}
public String decrypt(String value) {
try {
Cipher cipher = Cipher.getInstance(this.key.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, this.key);
String encrypted = value.substring(PREFIX.length());
final byte[] decoded = base64().decode(encrypted);
final byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, Charsets.UTF_8);
} catch (GeneralSecurityException e) {
System.out.println("Could not decrypt value with provided passphrase.");
throw new RuntimeException();
}
}
}
According to this source [1] under section "Creating a Cipher Object" , The default transformation settings or modes of operation in java is AES/ECB/PKCS5Padding. So when creating cipher object in encrypt/decrypt methods above it should be set to this default.
Some points I find confusing to get over:
a. Java Strings are represented using unicode and can be converted to a utf 8 byte array with "hi".getBytes(Charsets.UTF_8); however javascript strings are stored as utf 16 strings. Would this function do me any good? I could call it like utf16ToUtf8ByteArr("secret")? Or would I have to call it with Xs to match the AES cstr above? utf16ToUtf8ByteArr("secretXXXXXXXXXX")?
var utf16ToUtf8ByteArr = (utf16Str) => {
var utf8 = unescape(encodeURIComponent(utf16Str));
var arr = [];
for (var i = 0; i < utf8.length; i++) {
arr.push(utf8.charCodeAt(i));
}
return arr;
};
b. I printed out the initialization vector in java System.out.println(Arrays.toString(cipher.getIV())); and it returned null. This contradicts my understanding for doing pkcs5 with your code example here [3]. It almost seems like my jobs implementation doesn't really use or take advantage of using a password for pkcs5? Or a salt? Or number of iterations for that matter?
Here is my attempt at the Javascript implementation. Hope you can understand it as I cannot :P I used [5] as a reference guide.
var utf16ToUtf8ByteArr = (utf16Str) => {
var utf8 = unescape(encodeURIComponent(utf16Str));
var arr = [];
for (var i = 0; i < utf8.length; i++) {
arr.push(utf8.charCodeAt(i));
}
return arr;
};
// Encryption
var cipher = forge.cipher.createCipher('AES-ECB', utf16ToUtf8ByteArr('secretXXXXXXXXXX'));
cipher.start(); // start without iv.
cipher.update(forge.util.createBuffer(utf16ToUtf8ByteArr('password')));
cipher.finish();
var encrypted = cipher.output; // cipher.output has nothing in it. Its like it threw away the input :(
// Decryption
var decipher = forge.cipher.createDecipher('AES-ECB', utf16ToUtf8ByteArr('secretXXXXXXXXXX'));
decipher.start();
decipher.update(encrypted);
decipher.finish(); // true
console.log(decipher.output.toHex()); // This too prints out nothing what was I expecting considering above :(
I assumed something was wrong with key so I tried changing that part as follows and passing it to createCipher/creatDecipher functions but it had no effect.
var salt = forge.random.getBytesSync(128);
var derivedKey = forge.pkcs5.pbkdf2('secretXXXXXXXXXX', salt, 0, 16);
Hi,
I am trying to write some Javascript AES encryption/decryption code that is compatible with the defaults of a Java AES/ECB/PKCS5Padding encryption/decryption implementation at my job.
Let me share the implementation code more/less in a junit to show you what I mean:
According to this source [1] under section "Creating a Cipher Object" , The default transformation settings or modes of operation in java is AES/ECB/PKCS5Padding. So when creating cipher object in encrypt/decrypt methods above it should be set to this default.
Some points I find confusing to get over: a. Java Strings are represented using unicode and can be converted to a utf 8 byte array with
"hi".getBytes(Charsets.UTF_8);
however javascript strings are stored as utf 16 strings. Would this function do me any good? I could call it likeutf16ToUtf8ByteArr("secret")
? Or would I have to call it with Xs to match the AES cstr above?utf16ToUtf8ByteArr("secretXXXXXXXXXX")
?b. I printed out the initialization vector in java
System.out.println(Arrays.toString(cipher.getIV()));
and it returned null. This contradicts my understanding for doing pkcs5 with your code example here [3]. It almost seems like my jobs implementation doesn't really use or take advantage of using a password for pkcs5? Or a salt? Or number of iterations for that matter?Here is my attempt at the Javascript implementation. Hope you can understand it as I cannot :P I used [5] as a reference guide.
I assumed something was wrong with key so I tried changing that part as follows and passing it to createCipher/creatDecipher functions but it had no effect.
Please let me know if you can spot any mistakes.
[1] https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#Cipher [2] http://es5.github.io/#x4.3.16 [3] https://github.com/digitalbazaar/forge#pkcs5 [4] https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/share/classes/javax/crypto/spec/SecretKeySpec.java [5] https://github.com/digitalbazaar/forge#cipher [6] https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/share/classes/javax/crypto/Cipher.java