Skip to content

Instantly share code, notes, and snippets.

@ryu1
Last active June 4, 2025 11:00
Show Gist options
  • Save ryu1/44f6b76b52ba3ee5527ad493c54af8e0 to your computer and use it in GitHub Desktop.
Save ryu1/44f6b76b52ba3ee5527ad493c54af8e0 to your computer and use it in GitHub Desktop.
Implementation of AES encryption/decryption in GraalJS
const JavaBase64 = Java.type("java.util.Base64");
const JavaCipher = Java.type("javax.crypto.Cipher");
const JavaIvParameterSpec = Java.type("javax.crypto.spec.IvParameterSpec");
const JavaSecretKeySpec = Java.type("javax.crypto.spec.SecretKeySpec");
const JavaString = Java.type("java.lang.String");
const JavaSystem = Java.type("java.lang.System");
const JavaArrays = Java.type("java.util.Arrays");
const JavaArray= Java.type("java.lang.reflect.Array");
const JavaMethod = Java.type("java.lang.reflect.Method");
const CHARSET_NAME = Object.freeze({
WINDOWS_31J: "Windows-31J",
UTF_8: "UTF-8",
});
const CIPHER_TRANSFORMATION ="AES/CBC/PKCS5Padding";
const ENCRYPTION_ALGORITHM = "AES";
const IV_LENGTH = 16;
const CRYPTOR_IV = getEnvAsByteArrayWithBase64Decoding("IV_WITH_BASE64"),
const CRYPTOR_SECRET_KEY = getEnvAsByteArrayWithBase64Decoding("SECRET_KEY_WITH_BASE64"),
/**
* Converts a string into a byte array using the specified charset.
*
* @param {string} str - The string to convert.
* @param {string} [charsetName=CHARSET_NAME.UTF_8] - The charset name to use for conversion.
*
* @returns {byte[]} - The byte array representation of the input string.
*
* @example
* const bytes = getBytes('Hello, World!', CHARSET_NAME.WINDOWS_31J);
* console.log(bytes); // Output: Uint8Array [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
*/
function getBytes(str, charsetName = CHARSET_NAME.UTF_8) {
const method = JavaString.class.getMethod("getBytes", JavaString.class);
const bytes = method.invoke(str, charsetName);
return bytes;
}
/**
* Retrieves an environment variable value and decodes it from Base64.
*
* @param {string} name - The name of the environment variable to retrieve.
*
* @returns {byte[]} - The decoded Base64 value of the environment variable.
*
* @throws {Error} - If the environment variable is not found or cannot be decoded.
*
* @example
* const decodedValue = getEnvWithBase64Decoding('SECRET_KEY_WITH_BASE64');
* console.log(decodedValue); // Output: Uint8Array [104, 101, 120, ...]
*/
function getEnvAsByteArrayWithBase64Decoding(name) {
return JavaBase64.getDecoder().decode(getEnv(name));
}
/**
* Creates an AES cipher instance for encryption or decryption.
*
* @param {byte[]} secretKey - The secret key used for encryption and decryption.
* @param {byte[]} iv - The initialization vector (IV) used for encryption.
*
* @returns {javax.crypto.Cipher} - The initialized AES cipher instance.
*
* @throws {Error} - If an error occurs during cipher initialization.
*
* @example
* const cipher = createCipher('secretKey', 'ivValue');
*/
function createCipher(mode, secretKey, iv) {
const secretKeySpec = new JavaSecretKeySpec(secretKey, ENCRYPTION_ALGORITHM);
const ivParameterSpec = new JavaIvParameterSpec(iv);
const cipher = JavaCipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(mode, secretKeySpec, ivParameterSpec);
return cipher;
}
/**
* Encrypts a plain text using AES encryption.
*
* @param {string} plainText - The plain text to be encrypted.
* @param {byte[]} secretKey - The secret key used for encryption.
* @param {byte[]} iv - The iv used for encryption.
* @param {string} charsetName - The charset name used for decryption.
*
* @returns {string} - The encrypted text.
*
* @throws {Error} - If an error occurs during encryption.
*
* @example
* const encryptedText = encrypt('exampleText', 'secretKey');
* console.log(encryptedText); // Output: 'encryptedValue'
*/
function encrypt(plainText, secretKey, iv, charsetName = CHARSET_NAME.UTF_8) {
let encryptedText = "";
const cipher = createCipher(JavaCipher.ENCRYPT_MODE, secretKey, iv);
const cipherText = cipher.doFinal(getBytes(plainText, charsetName));
const ByteType = cipherText.getClass().getComponentType();
const joinedArray = JavaArray.newInstance(ByteType, JavaArray.getLength(iv) + JavaArray.getLength(cipherText));
JavaSystem.arraycopy(iv, 0, joinedArray, 0, JavaArray.getLength(iv));
JavaSystem.arraycopy(cipherText, 0, joinedArray, JavaArray.getLength(iv), JavaArray.getLength(cipherText));
const encoder = JavaBase64.getEncoder();
encryptedText = encoder.encodeToString(joinedArray);
return encryptedText;
}
/**
* Decrypts an encrypted value using AES decryption.
*
* @param {string} encryptedText - The encrypted value to be decrypted.
* @param {byte[]} secretKey - The secret key used for decryption.
* @param {string} charsetName - The charset name used for decryption.
*
* @returns {string} - The decrypted value.
*
* @throws {Error} - If an error occurs during decryption.
*
* @example
* const encryptedEmail = 'encryptedEmailValue';
* const decryptedEmail = decrypt(encryptedEmail, SECRET_KEY);
* console.log(decryptedEmail); // Output: '[email protected]'
*/
function decrypt(encryptedText, secretKey, charsetName = CHARSET_NAME.UTF_8) {
const decoder = JavaBase64.getDecoder();
const cipherText = decoder.decode(encryptedText);
const iv = JavaArrays.copyOf(cipherText, IV_LENGTH);
const actualCipherText = JavaArrays.copyOfRange(cipherText, IV_LENGTH, JavaArray.getLength(cipherText));
const cipher = createCipher(JavaCipher.DECRYPT_MODE, secretKey, iv);
const final = cipher.doFinal(actualCipherText);
const decryptedText = new JavaString(final, charsetName);
return decryptedText;
}
@ryu1
Copy link
Author

ryu1 commented Jun 3, 2025

synthisized.io向けに、GraalJSでAES暗号化/復号処理を実装した。
Javascriptから、一部のJavaのAPIが呼び出せないので、リフレクションで強引に呼び出すことで解決した。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment