HarmonyOS 鸿蒙Next:Android、IOS端使用的AES加密平移到Harmony使用原生API实现(粘贴即用)
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 联系官网客服。
找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17
点击这个会有惊喜
感谢提醒
期待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 < 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() {
}
}
Algo not support! [Algo]: AES256
HcfMdCreate[149]: Algo name is error!
CreateMd[513]: create c mdObj failed.
{"code":401}
加密失败
贴一下加密key和加密文本