HarmonyOS 鸿蒙Next:Android、IOS端使用的AES加密平移到Harmony使用原生API实现(粘贴即用)

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

HarmonyOS 鸿蒙Next:Android、IOS端使用的AES加密平移到Harmony使用原生API实现(粘贴即用)

  Prerequisites

  • DevEco Studio NEXT Developer Beta1
  • Build Version: 5.0.3.403, built on June 20, 2024
  • Compatible SDK:12

        经过一段时间的分析和摸索发现,APP原生端的AES加解密操作中,前置存在SHA-256摘要计算之后,对比生成特定规格的SecretSpec,然后通过字节转换、加盐、base64编码等等操作完成了一套加密过程;解密过程也是基本同理,先通过base64.decodeSync解编再按照前置规则生成Key进行解密操作。 

        我把加解密过程写了一个简易工具类,后续可以根据自己业务需求进行深度封装,粘贴可用:

/*

  • author:shj
  • 基于Harmony API 11+原生实现————平移原生(JAVA、IOS)端的AES256加密/解密+CBC模式+默认加盐+前置SHA-256摘要计算.
  • remark:其他模式按照使用需求,待陆续添加.
  • 2024.07.10
  • */

export class AesUtils {

// TODO AES-CBC-加密 /*

  • @param message: 要加密的明文

  • @param pk: 加密使用的key

  • */ static async encryptCBC(message: string, pk: string, callBack: Function) { try { let dataUnit = AesUtils.doMdBySync(pk)

    let symKey = await AesUtils.genSymKeyByData(dataUnit); let newBuf = buffer.from(buffer.from(message, ‘utf-8’).buffer); let plainText: cryptoFramework.DataBlob = { data: new Uint8Array(newBuf.buffer) }; let encryptText = await AesUtils.encryptMessagePromise(symKey, plainText); let encrypt = AesUtils.Base64().encodeToStringSync(encryptText.data) if (callBack) { callBack(encrypt) }

    let decryptText = await AesUtils.decryptMessagePromise(symKey, encryptText); let decrypt = buffer.from(decryptText.data).toString(‘utf-8’) if (decrypt) { console.error('decrypt plainText: ’ + decrypt); } else { console.error(‘decrypt failed’); }

} catch (error) { console.error(JSON.stringify(error)); if (callBack) { let str = JSON.stringify(error) callBack(str) } } }

// TODO AES-CBC-解密 /*

  • @param message: 要解密的密文(base64后的字符串)

  • @param pk: 解密使用的key

  • */ static async decryptCBC(message: string, pk: string, callBack: Function) { try { let dataUnit = AesUtils.doMdBySync(pk) let symKey = await AesUtils.genSymKeyByData(dataUnit); let base64 = AesUtils.Base64() let result = base64.decodeSync(message); let encryptText: cryptoFramework.DataBlob = { data: result }; let decryptText = await AesUtils.decryptMessagePromise(symKey, encryptText); if (decryptText.data.toString()) { let decrypt = buffer.from(decryptText.data).toString(‘utf-8’) if (callBack) { callBack(decrypt) } } else { if (callBack) { callBack(‘decrypt failed’) } }

    } catch (error) { if (callBack) { let str = JSON.stringify(error) callBack(‘decrypt failed’ + str) } } }

// 加密消息 private static async encryptMessagePromise(symKey: cryptoFramework.SymKey, plainText: cryptoFramework.DataBlob) { let cipher = cryptoFramework.createCipher(‘AES256|CBC|PKCS7’); let iv = AesUtils.genIvParamsSpec(); await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, iv); let cipherData = await cipher.doFinal(plainText); return cipherData; }

// 解密消息 private static async decryptMessagePromise(symKey: cryptoFramework.SymKey, cipherText: cryptoFramework.DataBlob) { let decoder = cryptoFramework.createCipher(‘AES256|CBC|PKCS7’); let iv = AesUtils.genIvParamsSpec(); await decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, iv); let decryptData = await decoder.doFinal(cipherText); return decryptData; }

private static doMdBySync(key: string): Uint8Array { let mdAlgName = ‘SHA256’; // 摘要算法名 let md = cryptoFramework.createMd(mdAlgName); let firstArray = new Uint8Array(buffer.from(key, ‘utf-8’).buffer) md.updateSync({ data: firstArray }); let mdResult = md.digestSync(); let data = AesUtils.Base64().encodeToStringSync(mdResult.data) console.info(’[Sync]:Md result base64:’ + data); let hex = buffer.from(mdResult.data).toString(‘hex’) console.info(‘Md result hex:’ + hex); let mdLen = md.getMdLength(); console.info("md len: " + mdLen);

<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> mdResult.data

}

// key生成 private static async genSymKeyByData(symKeyData: Uint8Array) { let symKeyBlob: cryptoFramework.DataBlob = { data: symKeyData }; let aesGenerator = cryptoFramework.createSymKeyGenerator(‘AES256’); let symKey = await aesGenerator.convertKey(symKeyBlob); console.info(‘convertKey success:’); return symKey; }

// iv生成 private static genIvParamsSpec(): cryptoFramework.IvParamsSpec { let arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 16 bytes let dataIv = new Uint8Array(arr); let ivBlob: cryptoFramework.DataBlob = { data: dataIv }; let ivParamsSpec: cryptoFramework.IvParamsSpec = { algName: “IvParamsSpec”, iv: ivBlob }; return ivParamsSpec; }

private static Base64() { return new util.Base64Helper() } }<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>



关于HarmonyOS 鸿蒙Next:Android、IOS端使用的AES加密平移到Harmony使用原生API实现(粘贴即用)的问题,您也可以访问:https://www.itying.com/category-93-b0.html 联系官网客服。

15 回复

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

cke_273.png

点击这个会有惊喜

期待HarmonyOS能继续优化多屏协同功能,让跨设备体验更加完美。

不知为何,服务端解密出来的是乱码

Harmony加密之后传到服务端,解密乱码吗?可以先在Harmony端上加密后手动解密试一下。

HarmonyOS的动画效果细腻流畅,每次滑动屏幕都是享受。

public static byte[] encodeByCBC(byte[] key, byte[] keyIv, byte[] data) throws Exception { //获取SecretKey对象,也可以使用getSecretKey()方法 Key secretKey = new SecretKeySpec(key, ALGORITHM); //获取指定转换的密码对象Cipher(参数:算法/工作模式/填充模式) Cipher cipher = Cipher.getInstance(AES_CBC_PADDING); //创建向量参数规范也就是初始化向量 IvParameterSpec ips = new IvParameterSpec(keyIv); //用密钥和一组算法参数规范初始化此Cipher对象(加密模式) cipher.init(Cipher.ENCRYPT_MODE, secretKey, ips); //执行加密操作 return cipher.doFinal(data); }

public static void main(String[] args) throws Exception { System.out.println("------------------AES加密之CBC加密转base64------------------"); //key的长度至少为24位并且是8的倍数(比如32,40等等) String aesKey = “0123456789abcdef0123456789abcdef”; String plainText = “123456789”; byte[] key = aesKey.getBytes(StandardCharsets.UTF_8); byte[] data = plainText.getBytes(StandardCharsets.UTF_8); byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; byte[] result = encodeByCBC(key, iv, data); String st = byte2hex(result); System.out.println(Base64.getEncoder().encodeToString(result)) output: xtFKgQ6kP5WbXGo8azGwiA==; }

main() { let text: string = “123456789” let key: string = “0123456789abcdef0123456789abcdef”

AesUtils.encryptCBC(text, key, (encode: string) => { console.log(“login”, “CBC:” + encode) //LFxdInGzWOwrs20+pThzAQ== 和服务端的base64结果不一致 }) AesUtils.decryptCBC(“qxcnYyAO+p54Q2DaL7+9Qg==”, key, (mk: string)=>{ console.log(“login”, “CBC mk:” + mk) //123456789 鸿蒙是能够正常解析自己加密结果的 }) }

两个端的base64不一致,难道是服务端的base64方式不同?感谢大佬

我个人认为啊,服务端的加解密有问题的可能性更大,给你个Android端的加解密工具,你试试。

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 &lt; bytes.length; j++) {
        v = bytes[j] &amp; 0xFF;
        hexChars[j * 2] = hexArray[v &gt;&gt;&gt; 4];
        hexChars[j * 2 + 1] = hexArray[v &amp; 0x0F];
    }
    return new String(hexChars);
}

private AESCrypt() {
}

}

Algo not support! [Algo]: AES256

HcfMdCreate[149]: Algo name is error!

  CreateMd[513]: create c mdObj failed.

{"code":401}

加密失败

贴一下加密key和加密文本

服务器端格式是Cipher 'AES128|CBC|PKCS5' 的。 是不是我直接在代码里的“createCipher('AES256|CBC|PKCS7')” 改成AES128就可以了吧?
如何使用?我加密出来用不了
回到顶部