There are plenty of extensions and methods for encrypting/decrypting data/values using AES on AI2, and plenty of methods for doing the same on the web with javascript (Crypto-JS), but you need to align the the method used in each setting, so that they can work effectively together. This was the issue that I found when setting up encryption on AI2 and in a Google Apps Script. So here I show where the encryption methods are interoperable between the two.
In these examples we are using AES/CBC.
The Crypto-JS (GAS) example comes from Tanaike
The android/AI2 example found in many places on StackOverflow
Setting the cipher instance is the "key" - AES/CBC/PKCS5Padding, this aligns with the default settings used in Crypto JS
Extension: aesjajs.aix
package uk.co.metricrat.aesjajs;
import android.util.Base64;
import com.google.appinventor.components.annotations.*;
import com.google.appinventor.components.runtime.*;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
@DesignerComponent(
version = 2,
versionName = "2.0",
description = "Developed by TIMAI2 using Fast.",
iconName = "icon.png"
)
public class AESJAJS extends AndroidNonvisibleComponent {
private String k1;
private String k2;
public AESJAJS(ComponentContainer container) {
super(container.$form());
}
@SimpleFunction(description = "Encrypt the message.")
public void Encrypt(String message) throws Exception {
if (k1.length() != 16 || k2.length() != 16) {
AfterEncrypting("Check values for key and IV");
} else {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(k1.getBytes("UTF-8"), "AES");
IvParameterSpec iv = new IvParameterSpec(k2.getBytes("UTF-8"));
cipher.init(1, key, iv);
byte[] encrypted = cipher.doFinal(message.getBytes("UTF-8"));
AfterEncrypting(Base64.encodeToString(encrypted, Base64.NO_WRAP));
}
}
@SimpleFunction(description = "Decrypt the message.")
public void Decrypt(String encrypted) throws Exception {
if (k1.length() != 16 || k2.length() != 16) {
AfterDecrypting( "Check values for key and IV");
} else {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(k1.getBytes("UTF-8"), "AES");
IvParameterSpec iv = new IvParameterSpec(k2.getBytes("UTF-8"));
cipher.init(2, key, iv);
byte[] decrypted = cipher.doFinal(Base64.decode(encrypted, 0));
AfterDecrypting( new String(decrypted, "UTF-8"));
}
}
@SimpleFunction(description = "Set the key value - 16 bytes).")
public void Key(String value) {
if (value.length() != 16) {
throw new IllegalArgumentException("Key value length must be 16 bytes");
} else {
k1 = value;
}
}
@SimpleFunction(description = "Set the IV value - 16 bytes.")
public void IV(String value) {
if (value.length() != 16) {
throw new IllegalArgumentException("IV value length must be 16 bytes");
} else {
k2 = value;
}
}
@SimpleEvent(description = "returns encrypted text")
public void AfterEncrypting(String encryptedText) {
EventDispatcher.dispatchEvent(this, "AfterEncrypting", encryptedText);
}
@SimpleEvent(description = "returns decrypted text")
public void AfterDecrypting(String decryptedText) {
EventDispatcher.dispatchEvent(this, "AfterDecrypting", decryptedText);
}
}
This is just the example function with everything laid out in one place, in production you would do things differently, and use a web app with parameters.
I copied the crypto-js min js contents to a new script file in the gas project, so that I could avoid having to call in the file from cdn.
function myFunction() {
const k1 = "0123456789012345"; // Sample key
const k2 = "5432109876543210"; // Sample key
const str = "sample text"; // Sample text
const key = CryptoJS.enc.Utf8.parse(k1);
const iv = CryptoJS.enc.Utf8.parse(k2);
// Encrypting
const res1 = CryptoJS.AES.encrypt(str, key, { iv }).toString();
console.log(res1); // <--- wyMY7zKADagvk1Z98gjIbw==
// Decrypting
var res2 = CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(res1, key, { iv }));
console.log(res2); // <--- sample text
}