HarmonyOS 鸿蒙Next ArkTs使用CryptoJS开源库的AES加密结果与原生不一致

发布于 1周前 作者 sinazl 来自 鸿蒙OS

HarmonyOS 鸿蒙Next ArkTs使用CryptoJS开源库的AES加密结果与原生不一致 想请教下各位关于加解密的方面,为什么两端加密结果不一致,寻求各位大神指点!Harmony这边的代码逻辑是:

try {
  let key = CryptoJS.enc.Utf8.parse('fxkyVote2023#!05');
  let iv = CryptoJS.enc.Utf8.parse('0000000000000000');
  let encrypted = CryptoJS.AES.encrypt('hello', key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  }).toString()
  // 转换为字符串(Base64编码)
  let encryptedString = encrypted.toString();
  console.log(encryptedString)// 加密结果为:y0JqIt6SSw7stjXc6qw9Rw==
} catch (e) {
  console.log("")
}

Android原生这边加使用的填充模式为AES/CBC/PKCS7Padding

加盐为:private static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

密结果为:gOWnT8QIub2Ej3AhxxEDZw==

代码如下:

public final class AESCrypt {
 private static final String TAG = "AESCrypt";

 //AESCrypt-ObjC uses CBC and PKCS7Padding
 private static final String AES_MODE = "AES/CBC/PKCS7Padding";
 private static final String CHARSET = "UTF-8";

 //AESCrypt-ObjC uses SHA-256 (and so a 256-bit key)
 private static final String HASH_ALGORITHM = "SHA-256";

 //AESCrypt-ObjC uses blank IV (not the best security, but the aim here is compatibility)
 private static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

 //togglable log option (please turn off in live!)
 public static boolean DEBUG_LOG_ENABLED = false;

 /**
  * Generates SHA256 hash of the password which is used as key
  *
  * @param password used to generated key
  * @return SHA256 of the password
  */
 private static SecretKeySpec generateKey(final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
   final MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
   byte[] bytes = password.getBytes("UTF-8");
   digest.update(bytes, 0, bytes.length);
   byte[] key = digest.digest();

   log("SHA-256 key ", key);

   SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
   return secretKeySpec;
 }

 /**
  * Encrypt and encode message using 256-bit AES with key generated from password.
  *
  * @param password used to generated key
  * @param message the thing you want to encrypt assumed String UTF-8
  * @return Base64 encoded CipherText
  * @throws GeneralSecurityException if problems occur during encryption
  */
 public static String encrypt(final String password, String message)
   throws GeneralSecurityException {

   try {
     final SecretKeySpec key = generateKey(password);

     log("message", message);

     byte[] cipherText = encrypt(key, ivBytes, message.getBytes(CHARSET));

     //NO_WRAP is important as was getting \n at the end
     String encoded = Base64.encodeToString(cipherText, Base64.NO_WRAP);
     log("Base64.NO_WRAP", encoded);
     return encoded;
   } catch (UnsupportedEncodingException e) {
     if (DEBUG_LOG_ENABLED)
       Loger.e(TAG, "UnsupportedEncodingException ", e);
     throw new GeneralSecurityException(e);
   }
 }

 /**
  * More flexible AES encrypt that doesn't encode
  *
  * @param key AES key typically 128, 192 or 256 bit
  * @param iv Initiation Vector
  * @param message in bytes (assumed it's already been decoded)
  * @return Encrypted cipher text (not encoded)
  * @throws GeneralSecurityException if something goes wrong during encryption
  */
 public static byte[] encrypt(final SecretKeySpec key, final byte[] iv, final byte[] message)
   throws GeneralSecurityException {
   final Cipher cipher = Cipher.getInstance(AES_MODE);
   IvParameterSpec ivSpec = new IvParameterSpec(iv);
   cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
   byte[] cipherText = cipher.doFinal(message);

   log("cipherText", cipherText);

   return cipherText;
 }

 /**
  * Decrypt and decode ciphertext using 256-bit AES with key generated from password
  *
  * @param password used to generated key
  * @param base64EncodedCipherText the encrpyted message encoded with base64
  * @return message in Plain text (String UTF-8)
  * @throws GeneralSecurityException if there's an issue decrypting
  */
 public static String decrypt(final String password, String base64EncodedCipherText)
   throws GeneralSecurityException {

   try {
     final SecretKeySpec key = generateKey(password);

     log("base64EncodedCipherText", base64EncodedCipherText);
     byte[] decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP);
     log("decodedCipherText", decodedCipherText);

     byte[] decryptedBytes = decrypt(key, ivBytes, decodedCipherText);

     log("decryptedBytes", decryptedBytes);
     String message = new String(decryptedBytes, CHARSET);
     log("message", message);

     return message;
   } catch (UnsupportedEncodingException e) {
     if (DEBUG_LOG_ENABLED)
       Loger.e(TAG, "UnsupportedEncodingException ", e);

     throw new GeneralSecurityException(e);
   }
 }

 /**
  * More flexible AES decrypt that doesn't encode
  *
  * @param key AES key typically 128, 192 or 256 bit
  * @param iv Initiation Vector
  * @param decodedCipherText in bytes (assumed it's already been decoded)
  * @return Decrypted message cipher text (not encoded)
  * @throws GeneralSecurityException if something goes wrong during encryption
  */
 public static byte[] decrypt(final SecretKeySpec key, final byte[] iv, final byte[] decodedCipherText)
   throws GeneralSecurityException {
   final Cipher cipher = Cipher.getInstance(AES_MODE);
   IvParameterSpec ivSpec = new IvParameterSpec(iv);
   cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
   byte[] decryptedBytes = cipher.doFinal(decodedCipherText);

   log("decryptedBytes", decryptedBytes);

   return decryptedBytes;
 }

 private static void log(String what, byte[] bytes) {
   if (DEBUG_LOG_ENABLED)
     Loger.d(TAG, what + "[" + bytes.length + "] [" + bytesToHex(bytes) + "]");
 }

 private static void log(String what, String value) {
   if (DEBUG_LOG_ENABLED)
     Loger.d(TAG, what + "[" + value.length() + "] [" + value + "]");
 }

 /**
  * Converts byte array to hexidecimal useful for logging and fault finding
  *
  * @param bytes
  * @return
  */
 private static String bytesToHex(byte[] bytes) {
   final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
     '9', 'A', 'B', 'C', 'D', 'E', 'F'};
   char[] hexChars = new char[bytes.length * 2];
   int v;
   for (int j = 0; j < bytes.length; j++) {
     v = bytes[j] & 0xFF;
     hexChars[j * 2] = hexArray[v >>> 4];
     hexChars[j * 2 + 1] = hexArray[v & 0x0F];
   }
   return new String(hexChars);
 }

 private AESCrypt() {
 }
}

//////////////////////调用方式:
try {
  String sign = AESCrypt.encrypt("fxkyVote2023#!05", "hello");
  Log.d("", sign);
  // 输出结果为:gOWnT8QIub2Ej3AhxxEDZw==
} catch (Exception e) {
  e.printStackTrace();
}

更多关于HarmonyOS 鸿蒙Next ArkTs使用CryptoJS开源库的AES加密结果与原生不一致的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

7 回复

该问题已解决,并含工具类源码(Harmony原生API实现),帖子为:https://developer.huawei.com/consumer/cn/forum/topic/0202156009282962090?fid=0101587866109860105

更多关于HarmonyOS 鸿蒙Next ArkTs使用CryptoJS开源库的AES加密结果与原生不一致的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


排查发现,JAVA端的代码,生成key的流程是:

  1. 将key转为长度为32位的字节数组
  2. 再将第一步的字节数组update为SHA-256类型的字节数组(32字节,并非64字节)
  3. 最后生成AES的key

目前Harmony这边第一步正常,卡在了第二步,不知Harmony这边如何实现,求大佬指点。

向量不对吧?0在Uint8Array里面是0x30而不是0x00

您好,根据当前代码,能提供下修改建议吗?

let iv = CryptoJS.enc.Utf8.parse(new Array(16).fill(String.fromCharCode(0)).join(’’));

感谢答复。我刚发现Android原生这边的AES加密生成的密钥是32byte的,并非16byte,我感觉和他有关系,正在排查。

在HarmonyOS(鸿蒙)Next ArkTs环境中使用CryptoJS开源库的AES加密结果与原生不一致的问题,可能源于以下几个因素:

  1. 密钥管理:确保在ArkTs和原生环境中使用的密钥完全一致,包括密钥的长度、格式以及任何可能的填充或截断处理。

  2. 加密模式与填充方式:检查AES加密使用的模式(如CBC、ECB等)和填充方式(如PKCS7、NoPadding等)是否一致。不同的模式或填充方式会导致加密结果不同。

  3. 初始化向量(IV):如果使用的是CBC等需要IV的模式,确保IV在ArkTs和原生环境中相同,且正确传递。

  4. 数据编码:加密前的数据编码方式(如Base64、Hex等)应保持一致,编码不一致也会影响最终加密结果。

  5. 库版本差异:CryptoJS开源库的不同版本可能存在算法实现上的差异,确保ArkTs中使用的CryptoJS版本与原生环境中使用的版本一致。

  6. 环境差异:鸿蒙Next ArkTs环境与原生环境可能存在底层实现上的差异,这可能导致即使是相同的输入和配置,加密结果也会有所不同。

如果以上因素均已确认无误,但问题依旧存在,请考虑查看CryptoJS在ArkTs中的具体实现细节,或检查是否有已知的兼容性问题。如果问题依旧没法解决请联系官网客服, 官网地址是 https://www.itying.com/category-93-b0.html

回到顶部