此方案基于ArkTs API9 实现,解决了分段加解密,解密后中文乱码的问题
背景
随着鸿蒙Next即将发布,纯血鸿蒙将不再兼容Android APK应用的呼声也越来越多,且我司的Android应用因为上架了华为应用市场,最近也一直被华为官方的人员邀请加入鸿蒙开发大家庭。此时作为一个Android开发人员,不得不考虑开始适配鸿蒙系统了。趁着工作的间隙,看了一礼拜的华为鸿蒙开发指南,大概了解下语法就准备开始上手。这第一个难点就来了:我司的网络接口为了数据传输安全,所有请求参数都是组装完一个json串之后,整体用RSA进行加密一次,然后把加密后的字符串传给后台。由于加密内容太长,这就需要开始研究鸿蒙的RSA如何分段加解密了。经过各种探索和失败之后,于是就有了这篇博文。
Android项目已有的基于Java的RSA加解密方案
public static final String KEY_ALGORITHM = "RSA";//加密算法
private static final int MAX_ENCRYPT_BLOCK = 117;// RSA最大加密明文大小
private static final int MAX_DECRYPT_BLOCK = 128;// RSA最大解密明文大小
//公钥 这里只展示部分
public static final String PUBLIC_KEY = "MIGfMA0GCSqG...+wIDAQAB";
//私钥 这里只展示部分
public static final String PRIVATE_KEY = "MIICdwIBADANBg...yhE/3J2I=";
//Base64Utils的decodeFromString方法
public static byte[] decodeFromString(String src) {
if (src.isEmpty()) {
return new byte[0];
}
return Base64.decode(src, NO_WRAP);
}
/**
* 公钥加密 RSA/None/PKCS1Padding 填充方式
*
* @param data 待加密数据字节数组
* @param publicKey 公钥
* @throws Exception
* @returnn 加密密文
*/
public static byte[] encryptByPublicKeyByPKCS1Padding(byte[] data, String publicKey)
throws Exception {
byte[] keyBytes = Base64Utils.decodeFromString(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
/**
* 私钥解密
* @param encryptedData 待解密字节数组
* @param privateKey 私钥
* @return 明文
* @throws Exception
*/
public static byte[] decryptByPrivateKeyByPKCS1Padding(byte[] encryptedData, String privateKey) throws Exception {
byte[] keyBytes = Base64Utils.decodeFromString(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
鸿蒙项目ArkTs(API9)对应的RSA加解密方案
import cryptoFramework from '@ohos.security.cryptoFramework';
import util from '@ohos.util';
import Logger from './Logger'
//公钥 这里只展示部分
export const pubKeyX509Str = "MIGfMA0GCSqG...+wIDAQAB";
//私钥 这里只展示部分
export const priKeyPKCS8Str = "MIICdwIBADANBg...yhE/3J2I=";
export class RSA {
//将字符串转换为Uint8Array
string2Uint8Array(str: string, len:number = null) {
let textEncoder = util.TextEncoder.create('utf-8')
return textEncoder.encodeInto(str)
}
//字节流转成可理解的字符串
uint8ArrayToString(array) {
let textDecoder = util.TextDecoder.create('utf-8')
return textDecoder.decodeWithStream(array)
}
/**
* RSA分段加密
* @param value 待加密字符串
* @returns
*/
rsaEncryption(value: string) {
let base64 = new util.Base64Helper();
let data = base64.decodeSync(pubKeyX509Str)
//RSA每次加解密允许的原文长度大小与密钥位数和填充模式等有关,详细规格内容见overview文档
let plainTextSplitLen = 117;
let globalEncryptionOutput; //加密输出
let arrTest = this.string2Uint8Array(value);
//创建非对称密钥生成器对象
let asyKeyGenerator = cryptoFramework.createAsyKeyGenerator("RSA1024|PRIMES_2");
// 创建加密Cipher对象
let cipherEncryption = cryptoFramework.createCipher("RSA1024|PKCS1");
let pubKeyBlob = { data: data }
return asyKeyGenerator.convertKey(pubKeyBlob, null)
.then(keyPair => {
return cipherEncryption.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null);
})
.then(async () => {
globalEncryptionOutput = [];
// 将原文按64字符进行拆分,循环调用doFinal进行加密,使用1024bit密钥时,每次加密生成128B长度的密文
for (let i = 0; i < (arrTest.length / plainTextSplitLen); i++) {
let tempArr = arrTest.slice(i * plainTextSplitLen, (i + 1) * plainTextSplitLen);
let tempBlob = { data: tempArr };
let tempCipherOutput = await cipherEncryption.doFinal(tempBlob);
globalEncryptionOutput = globalEncryptionOutput.concat(Array.from(tempCipherOutput.data));
}
let enStr = base64.encodeToStringSync(new Uint8Array(globalEncryptionOutput))
Logger.info("加密总长度:" + globalEncryptionOutput.length + "\n生成加密串:\n" + enStr)
return enStr
})
.catch(error => {
Logger.info(`加密异常, ${error.code}, ${error.message}`);
})
}
/**
* RSA分段解密
* @param value 待解密字符串
* @returns
*/
rsaDecryption(value: string) {
let base64 = new util.Base64Helper();
let data = base64.decodeSync(priKeyPKCS8Str)
// RSA密钥每次加密生成的密文数据长度计算方式:密钥位数/8
let cipherTextSplitLen = 128;
//创建非对称密钥生成器对象
let asyKeyGenerator = cryptoFramework.createAsyKeyGenerator("RSA1024|PRIMES_2");
// 创建解密Decoder对象
let cipherDecryption = cryptoFramework.createCipher("RSA1024|PKCS1");
let priKeyBlob = { data: data }
return asyKeyGenerator.convertKey(null, priKeyBlob)
.then(keyPair => {
return cipherDecryption.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null);
}).then(async () => {
let globalCipherOutput = base64.decodeSync(value)
let len = globalCipherOutput.length
//解密输出
let globalDecryptionOutput = new Uint8Array(len);
let globalOffset = 0
// 将密文按128B进行拆分解密,得到原文后进行拼接
for (let i = 0; i < (len / cipherTextSplitLen); i++) {
let tempBlobData = globalCipherOutput.subarray(i * cipherTextSplitLen, (i + 1) * cipherTextSplitLen);
let message = new Uint8Array(tempBlobData);
let tempBlob = { data: message };
let tempDecodeOutput = await cipherDecryption.doFinal(tempBlob);
//存入数组 解决边累加边转中文时 字节错乱出现乱码
globalDecryptionOutput.set(tempDecodeOutput.data, globalOffset)
//偏移量
globalOffset += tempDecodeOutput.data.byteLength
}
let result = this.uint8ArrayToString(globalDecryptionOutput)
Logger.info("解密串:cipherAlgName[RSA1024|PKCS1]\n" + result);
return result
})
.catch(error => {
Logger.info(`解密异常,cipherAlgName[RSA1024|PKCS1] ${error.code}, ${error.message}`);
})
}
}
网络请求,进行验证
import http from '@ohos.net.http'
import {RSA} from '../tools/RSA'
@Entry
@Component
struct Second {
@State message: string = "Hi there"
@State requestStr: string = ''
@State responseStr: string = ''
private rsa: RSA = new RSA()
text = {"brandName":"UPOS","lnglat":"116.192321,39.911187","merDetailedAddress":"中国北京市石景山区古城街道古城南街辅路","channel":"guanwang","sign":"5acea2dc35e7a856db260c6ca6f5c993","sysMark":"1","merAreas":"北京市,北京市,石景山区","rpid":"13476A6FC3370A20","version":"63","token":"13670AF3KlZzvhe9Dzju7uDqnc9JrqBv9Ttfy"}
//此处省略build方法
//网络请求
async requestUpdate() {
let encrypt = await this.rsa.rsaEncryption(JSON.stringify(this.text))
this.requestStr = `------请求json------\n{"data": "${encrypt}"}\n------返回json-----\n`
//每一个httpRequest对应一个Http请求任务,不可复用
let httpRequest = http.createHttp();
httpRequest.request(
"https://www.xxx.com/xxx/xxxx/xxxx.do", {
method: http.RequestMethod.POST,
header: {
'content-type': 'application/json; charset=utf-8',
},
extraData: {
'data': encrypt
}
}, async (err, data) => {
if (!err) {
//请求成功
if (data.responseCode === 200) {
//后台返回json{"ret_code":null,"ret_msg":null,"data":"FGAIlls512...5jplOrXosEIw=="}
let resultStr = JSON.parse(`${data.result}`)
this.responseStr = await this.rsa.rsaDecryption(resultStr.data) || "解密失败"
}
} else {
//请求失败
console.info(`error: ${JSON.stringify(err)}`)
}
//请求完毕时,调用destroy方法主动销毁
httpRequest.destroy()
})
}
}