有时候我们需要跨编程语言进行加密加密。比如nodejs里面加密,java里面解密,或者反过来java加密,nodejs解密。node可以使用cryptojs,java可以使用javax.crypto.Cipher包。
网上有很多关于这方面的文章。然而如果node使用了默认的参数进行加密(比如现有业务已经生成了一些已经加密的数据),需要java进行解密,这时候按照默认的方法就无法正常解密了。一般的aes加密使用的是16位的key,而cryptojs默认的key可以任意长度。
cryptoJS.AES默认参数加密代码:
const cryptoJS = require("crypto-js"); const encryptedValue = cryptoJS.AES.encrypt(value, secret).toString()
对应的java解密的示例代码:
try { byte[] cipherData = cn.hutool.core.codec.Base64.decode(encryptedText); byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16); MessageDigest md5 = MessageDigest.getInstance("MD5"); final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5); SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES"); IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]); byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length); Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCBC.init(Cipher.DECRYPT_MODE, key, iv); byte[] decryptedData = aesCBC.doFinal(encrypted); return new String(decryptedData, StandardCharsets.UTF_8); } catch (Exception e) { //log.error("aesDecrypt error.param={}", encryptedText, e); return null; }
以上代码来自参考自:https://www.pianshen.com/question/31931314701/
上面只是演示了如何进行node加密,java解密;而java默认加密的值,node也是无法解密的,需要采用以下写法。
java加密示例代码
try { byte[] input = datastr.getBytes(); // 以下两个变量不可修改,否则java加密的,node无法解密 byte[] saltData = "DFބf$t:".getBytes(); byte[] preData = "Salted__".getBytes(); MessageDigest md5 = MessageDigest.getInstance("MD5"); final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5); SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES"); IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]); Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCBC.init(Cipher.ENCRYPT_MODE, key, iv); byte[] encrypt = aesCBC.doFinal(input); byte[] encryptedData = addBytes(saltData, encrypt); return cn.hutool.core.codec.Base64.encode(addBytes(preData, encryptedData)); } catch (Exception e) { log.error("aesEncrypt error.param={}", datastr, e); return null; }
上述代码亲测有效,使用前可以自己测试测试
对应的node解密示例代码
const cryptoJS = require("crypto-js"); let decryptedValue = cryptoJS.AES.decrypt(value, secret).toString(cryptoJS.enc.Utf8);
完整代码示例
import lombok.extern.slf4j.Slf4j; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.DigestException; import java.security.MessageDigest; import java.util.Arrays; /** * * 兼容node * */ @Slf4j public class AESUtils { public static String aesEncrypt(String datastr, String secret) { try { byte[] input = datastr.getBytes(); // 以下两个变量不可修改,否则java加密的,node无法解密 byte[] saltData = "DFބf$t:".getBytes(); byte[] preData = "Salted__".getBytes(); MessageDigest md5 = MessageDigest.getInstance("MD5"); final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5); SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES"); IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]); Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCBC.init(Cipher.ENCRYPT_MODE, key, iv); byte[] encrypt = aesCBC.doFinal(input); byte[] encryptedData = addBytes(saltData, encrypt); return cn.hutool.core.codec.Base64.encode(addBytes(preData, encryptedData)); } catch (Exception e) { log.error("aesEncrypt error.param={}", datastr, e); return null; } } public static String aesDecrypt(String encryptedText, String secret){ // 参考文章:https://www.pianshen.com/question/31931314701/ try { byte[] cipherData = cn.hutool.core.codec.Base64.decode(encryptedText); byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16); MessageDigest md5 = MessageDigest.getInstance("MD5"); final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5); SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES"); IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]); byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length); Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCBC.init(Cipher.DECRYPT_MODE, key, iv); byte[] decryptedData = aesCBC.doFinal(encrypted); return new String(decryptedData, StandardCharsets.UTF_8); } catch (Exception e) { log.error("aesDecrypt error.param={}", encryptedText, e); return null; } } public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) { int digestLength = md.getDigestLength(); int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength; byte[] generatedData = new byte[requiredLength]; int generatedLength = 0; try { md.reset(); // Repeat process until sufficient data has been generated while (generatedLength < keyLength + ivLength) { // Digest data (last digest if available, password data, salt if available) if (generatedLength > 0) md.update(generatedData, generatedLength - digestLength, digestLength); md.update(password); if (salt != null) md.update(salt, 0, 8); md.digest(generatedData, generatedLength, digestLength); // additional rounds for (int i = 1; i < iterations; i++) { md.update(generatedData, generatedLength, digestLength); md.digest(generatedData, generatedLength, digestLength); } generatedLength += digestLength; } // Copy key and IV into separate byte arrays byte[][] result = new byte[2][]; result[0] = Arrays.copyOfRange(generatedData, 0, keyLength); if (ivLength > 0) result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength); return result; } catch (DigestException e) { throw new RuntimeException(e); } finally { // Clean out temporary data Arrays.fill(generatedData, (byte)0); } } public static byte[] addBytes(byte[] data1, byte[] data2) { byte[] data3 = new byte[data1.length + data2.length]; System.arraycopy(data1, 0, data3, 0, data1.length); System.arraycopy(data2, 0, data3, data1.length, data2.length); return data3; } public static void main(String[] args) { String secret = "aabbccddeeff"; String datastr = "{\"m\":\"images\",\"u\":\"0\",\"t\":\"20230322\",\"f\":\"16794597950146230.png\"}"; String t = aesEncrypt(datastr, secret); System.out.println(t); System.out.println(aesDecrypt(t, secret)); } }