1. 介绍
HarmonyOS(鸿蒙)SDK API9支持两种对称加密算法:a. AES(Advanced Encryption Standard) b.3DES(也称为 3DESede 或 TripleDES).
官方也给出了一些样例使用代码,可供开发者参考。
本篇从实践出发,完整的通过代码方式来深入了解HarmonyOS中的对称加密用法。
2. 基础概念
字符串样例:AES256|EBC|PKCS5
- 密钥算法 : AES
- 密文组长度 : 256
- 分组模式 : EBC
- 填充模式 : PCKCS5
关于"AES算法","分组模式","填充模式"的具体概念,可自行搜索。
3. 实践
加密使用的测试数据源有两个,具体见下图
- "125字节" (即,占用了125个字节的字符串)
- "128字节" (即,占用了128个字节的字符串)
3.1 MMI入口
3.2 AES加解密
3DES加密算法密文分组长度有三种:128,192,256;支持7种分组模式:ECB,CBC,OFB,CFB,CTR,GCM,CCM;支持3种填充模式:NoPadding, PKCS5, PKCS7
本篇仅以256密文组长度做为实践验证,默认选择"128字节"数据集
片段代码以 "AES256|ECB|PKCS5" 为例
3.2.1 加密
- 生成对动态密钥
ini
//导入加密框架
import cryptoFramework from '@ohos.security.cryptoFramework';
......
//创建密钥生成器,参数为(密钥算法+密文组长度,如:AES256)
let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');
//生成对称密钥
let promiseSymKey = symKeyGenerator.generateSymKey();
//获取密钥
promiseSymKey( key => {
//动态密钥
this.symBinKey = key.getEncoded().data;
})
- 初始化Cipher
ini
//创建Cipher
globalCipher = cryptoFramework.createCipher('AES256|ECB|PKCS5');
//初始化Cipher
let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE;
globalCipher.init(mode, key, null)
- 加密
ini
//文字转换为Uint8Array
const encoder = new util.TextEncoder()
let u8a_encoder = encoder.encodeInto('测试')
//封装Uint8Array 数据,必须是一个带data属性的对象
let plainText = { data: u8a_encoder };
//开始加密
let promiseUpdate = globalCipher.update(plainText);
//获取加密结果
promiseUpdate( result => {
//密文
let this.cipherText = result.data
})
- 结束加密
上述虽然已经完成了加密,但是需要加密的文字字节长度不一定是128的整数倍,所以使用填充模式会弥补不足位的数据,并且在update之后,采用doFinal的方式结束最终加密
javascript
//结束加密
let promiseFinal = globalCipher.doFinal(null);
//获取剩余结果
promiseFinal( finalResult => {
if(finalResult != null){
//剩余加密结果
let authTag = finalResult.data
}
})
3.2.2 解密
- 生成密钥对象
我们这里使用加密过程中产生的密钥数据。注意此密钥数据不能直接用来构建Key对象(即 密钥对象),正确的方法是通过API来生成密钥对象
ini
//准备密钥数据,this.symBinKey 是上述过程生成的密钥
let keyMaterialBlob: cryptoFramework.DataBlob = { data: this.symBinKey };
//创建密钥生成器,参数为(密钥算法+密文组长度,如:AES256)
let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');
//密钥生成器使用密钥数据,开始生成密钥对象
symKeyGenerator.convertKey(keyMaterialBlob).then( key => {
//key 为生成的密钥对象
})
- 初始化Cipher
ini
//创建Cipher
globalCipher = cryptoFramework.createCipher('AES256|EBC|PKCS5');
//初始化Cipher,参数key由第一步生成
let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
globalCipher.init(mode, key, null)
- 解密
ini
//mergeResult 代表密文,本篇文章中,此值来源于上述加密结果
let promiseUpdate = globalCipher.update({ data: mergeResult });
- 结束解密
javascript
//结束解密
let promiseFinal = globalCipher.doFinal(null);
//获取剩余结果
promiseFinal( finalResult => {
if(finalResult != null){
//有剩余解密结果
} else {
//无剩余解密结果
}
})
3.2.3 注意点
- 采用GCM分组模式时,需要设置 'GcmParamsSpec' 参数
- 采用CCM分组模式时,需要设置 'CcmParamsSpec' 参数
- 采用CBC,OFB,CFB,CTR模式时,可以使用 'IvParamsSpec' 参数
- 如果选择了"NoPadding"填充模式,需要明文的字节长度如果不是128的整数倍,则会出现截断现象,这种情况算做正常。
- 不要并发加密
- 加密/解密行为之间需要有时间间隔
3.2.4 源码
kotlin
import cryptoFramework from '@ohos.security.cryptoFramework';
import util from '@ohos.util';
import Logger from '../../common/Logger';
import OriginData from './OriginData';
import emitter from "@ohos.events.emitter";
/**
* 对称密钥
* 密钥算法:AES
* 密钥规格格式:密钥算法名称 + 密钥长度
* 密钥长度:128,192,256
* 密钥规格列表:AES128, AES192, AES256
*
* 对称加密
* 加密算法:AES
* 加密规格格式:密钥算法名称 + 密钥长度 + 分组模式 + 填充模式
* 密钥长度:128,192,256
* 分组模式:ECB、CBC、OFB、CFB、CTR、GCM和CCM
* 填充模式:NoPadding,PKCS5,PKCS7
* 加密规格样例:AES256|GCM|PKCS5
*
*
*
*/
class TestSymmetricAESEncryptDecrypt {
private symBinKey: Uint8Array;
private cipherText: Uint8Array;
private authTag: Uint8Array;
private algorithmWithLength: string = 'AES256'
private blockCipherMode: string = 'ECB' //ECB、CBC、OFB、CFB、CTR、GCM和CCM
private paddingMode: string = 'NoPadding' //NoPadding,PKCS5,PKCS7
//对称加密:AES256|GCM|PKCS5
testSymAESEncryptWith256GCMPKCS5(blockCipherMode: string, paddingMode: string, dataSource128:boolean) {
this.authTag = null
let originData: string = OriginData.CONTENT_128
if(dataSource128){
originData = OriginData.CONTENT_128
} else {
originData = OriginData.CONTENT_125
}
this.blockCipherMode = blockCipherMode
this.paddingMode = paddingMode
let symKeyGenerator = cryptoFramework.createSymKeyGenerator(this.algorithmWithLength);
let promiseSymKey = symKeyGenerator.generateSymKey();
let globalCipher: cryptoFramework.Cipher
promiseSymKey.then(key => {
console.log('密钥已生成',key.format, key.algName, key.getEncoded().data.toString())
Logger.d(this.algorithmWithLength, this.blockCipherMode, this.paddingMode)
this.symBinKey = key.getEncoded().data;
return key;
}).then( key => {
globalCipher = cryptoFramework.createCipher(this.algorithmWithLength + '|'
+ this.blockCipherMode + '|'
+ this.paddingMode);
let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE;
if (this.blockCipherMode == 'GCM') {
return globalCipher.init(mode, key, this.genGcmParamsSpec());
} else if(this.blockCipherMode == 'CCM') {
return globalCipher.init(mode, key, this.genCcmParamsSpec());
} else if(this.blockCipherMode == 'CBC' || this.blockCipherMode == 'CTR' || this.blockCipherMode == 'OFB'|| this.blockCipherMode == 'CFB'){
return globalCipher.init(mode, key, this.genIvParamsSpec());
} else {
return globalCipher.init(mode, key, null);
}
}).then(() => {
const encoder = new util.TextEncoder()
let u8a_encoder = encoder.encodeInto(originData)
Logger.d('开始加密')
let plainText = { data: u8a_encoder };
let promiseUpdate = globalCipher.update(plainText);
return promiseUpdate;
}).then(updateOutput => {
if(updateOutput != null && updateOutput.data != null){
this.cipherText = updateOutput.data
}
if (this.paddingMode != 'NoPadding') {
Logger.d('加密分组之后的剩余数据')
let promiseFinal = globalCipher.doFinal(null);
return promiseFinal;
} else {
return null
}
}).then( authTag => {
if(authTag != null && authTag.data != null){
console.log('authTag:', authTag.data.toString())
this.authTag = authTag.data
} else {
this.authTag = null
console.log('authTag == null')
}
console.log('加密结束')
return
}).then(() => {
this.testSymAESDecrypt()
return
}).catch( error => {
console.error(`catch error, ${error.code}, ${error.message}`);
this.notificationStatus('加密中-error', error.code + '-' + error.message)
})
}
//对称解密: AES256|GCM|PKCS5
testSymAESDecrypt() {
// 二进制密钥数据
let keyEncode = [242, 202, 181, 197, 174, 191, 60, 94, 138, 7, 53, 123, 64, 30, 32, 236, 93, 165, 234, 21, 136, 142, 12, 161, 238, 9, 56, 211,
192, 134, 39, 236];
let keyMaterialBlob: cryptoFramework.DataBlob = { data: this.symBinKey };
//即将被解密的二进制数据,
let willDecryptData = [209, 124, 163, 117, 73, 39, 230, 52, 162, 77, 46, 28, 39, 82, 32, 123, 177, 15, 218, 22, 206, 49, 167, 61]
// GCM auth参数
let authOriginData = [125, 81, 34, 43, 37, 200, 200, 251, 207, 183, 121, 185, 59, 143, 212, 128];
let authBlob = { data: this.authTag };
let gcmParamsSpec = null
if(this.blockCipherMode == 'GCM'){
console.log('配置'+this.blockCipherMode+'解密参数')
gcmParamsSpec = this.genGcmParamsSpec()
if(this.authTag != null){
gcmParamsSpec.authTag = authBlob
}
} else if(this.blockCipherMode == 'CCM'){
console.log('配置'+this.blockCipherMode+'解密参数')
gcmParamsSpec = this.genCcmParamsSpec()
if(this.authTag != null){
gcmParamsSpec.authTag = authBlob
}
} else if(this.blockCipherMode == 'CBC' || this.blockCipherMode == 'CTR' || this.blockCipherMode == 'OFB'|| this.blockCipherMode == 'CFB'){
console.log('配置'+this.blockCipherMode+'解密参数')
gcmParamsSpec = this.genIvParamsSpec()
}
let globalCipher: cryptoFramework.Cipher
let symKeyGenerator = cryptoFramework.createSymKeyGenerator(this.algorithmWithLength);
let first: Uint8Array = null
// 根据指定的二进制密钥数据,生成对称密钥对象
symKeyGenerator.convertKey(keyMaterialBlob)
.then(key => {
console.log('解密-初始化Cipher')
globalCipher = cryptoFramework.createCipher(this.algorithmWithLength + '|'
+ this.blockCipherMode + '|'
+ this.paddingMode);
let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
if (this.blockCipherMode == 'GCM' || this.blockCipherMode == 'CCM'
|| this.blockCipherMode == 'CBC' || this.blockCipherMode == 'CTR'
|| this.blockCipherMode == 'OFB'|| this.blockCipherMode == 'CFB') {
globalCipher.init(mode, key, gcmParamsSpec)
} else {
globalCipher.init(mode, key, null);
}
return
})
.then(() => {
let mergeResult: Uint8Array
if ((this.authTag != null)
&& ((this.blockCipherMode == 'ECB') || (this.blockCipherMode == 'CBC'))) {
mergeResult = new Uint8Array([...this.cipherText, ...this.authTag])
console.log('开始解密', '密文+authTag')
} else {
mergeResult = this.cipherText
console.log('开始解密', '密文')
}
let promiseUpdate = globalCipher.update({ data: mergeResult });
return promiseUpdate;
})
.then(updateOutput => {
Logger.d('解密分组之后的剩余数据')
first = updateOutput.data
if(this.blockCipherMode == 'GCM'){
if(this.paddingMode == 'NoPadding'){
return
}
}
let promiseFinal = globalCipher.doFinal(null);
return promiseFinal;
})
.then( finalOutput => {
if (finalOutput == null) { // 使用finalOutput.data前,先判断结果是否为null
let textDecoder = util.TextDecoder.create()
let key = textDecoder.decodeWithStream(first);
Logger.d('解密完成1: ', key);
this.notificationStatus('解密完成', key)
} else {
let textDecoder = util.TextDecoder.create()
let key = textDecoder.decodeWithStream(new Uint8Array([...first, ...finalOutput.data]));
Logger.d('解密完成2: ', key);
this.notificationStatus('解密完成', key)
}
console.log('解密结束')
})
.catch(error => {
console.error(`catch error, ${error.code}, ${error.message}`);
this.notificationStatus('解密中-error', error.code + '-' + error.message)
})
}
//https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/js-apis-cryptoframework-0000001477981409-V2#ZH-CN_TOPIC_0000001523488570__gcmparamsspec
genGcmParamsSpec() {
let arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 12 bytes
let dataIv = new Uint8Array(arr);
let ivBlob = { data: dataIv };
arr = [0, 0, 0, 0, 0, 0, 0, 0]; // 8 bytes
let dataAad = new Uint8Array(arr);
let aadBlob = { data: dataAad };
arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 16 bytes
let dataTag = new Uint8Array(arr);
let tagBlob = { data: dataTag };
let gcmParamsSpec = { iv: ivBlob, aad: aadBlob, authTag: tagBlob, algName: "GcmParamsSpec" };
return gcmParamsSpec;
}
//https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/js-apis-cryptoframework-0000001477981409-V2#ZH-CN_TOPIC_0000001523488570__ccmparamsspec
genCcmParamsSpec() {
let arr = [0, 0, 0, 0, 0, 0, 0]; // 7 bytes
let dataIv = new Uint8Array(arr);
let ivBlob = { data: dataIv };
arr = [0, 0, 0, 0, 0, 0, 0, 0]; // 8 bytes
let dataAad = new Uint8Array(arr);
let aadBlob = { data: dataAad };
arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 12 bytes
let dataTag = new Uint8Array(arr);
let tagBlob = { data: dataTag };
let ccmParamsSpec = { iv: ivBlob, aad: aadBlob, authTag: tagBlob, algName: "CcmParamsSpec" };
return ccmParamsSpec;
}
//https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/js-apis-cryptoframework-0000001477981409-V2#ZH-CN_TOPIC_0000001523488570__ivparamsspec
genIvParamsSpec() {
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 = { data: dataIv };
let gcmParamsSpec = { iv: ivBlob, algName: "IvParamsSpec" };
return gcmParamsSpec;
}
notificationStatus(status:string, result: string){
// 定义一个eventId为1的事件,事件优先级为Low
let event = {
eventId: 202404063287,
priority: emitter.EventPriority.HIGH
};
let eventData = {
data: {
"content": result,
'status': status,
}
};
// 发送eventId为1的事件,事件内容为eventData
emitter.emit(event, eventData);
}
}
export default new TestSymmetricAESEncryptDecrypt();
3.3 3DES加解密
3DES加密算法密文分组长度仅有一种:192; 支持3种分组模式:ECB,CBC,OFB; 支持3种填充模式:NoPadding, PKCS5, PKCS7
3DES加解密的流程与 "3.2 AES加解密"流程是一致的。
API使用概括起来如下
- 创建密钥生成器: createSymKeyGenerator
- 生成密钥对象:generateSymKey 或 convertKey
- 创建Cipher: createCipher
- 初始化Cipher: init
- 加密/解密:update
- 结束加密/解密:doFinal
3.3.1 源码
kotlin
import cryptoFramework from '@ohos.security.cryptoFramework';
import util from '@ohos.util';
import Logger from '../../common/Logger';
import OriginData from './OriginData';
import emitter from "@ohos.events.emitter";
/**
* 对称密钥
* 密钥算法:3DES
* 密钥规格格式:密钥算法名称 + 密钥长度
* 密钥长度:192
* 密钥规格列表:3DES
*
* 对称加密
* 加密算法:3DES
* 加密规格格式:密钥算法名称 + 密钥长度 + 分组模式 + 填充模式
* 密钥长度:192
* 分组模式:ECB、CBC、OFB
* 填充模式:NoPadding,PKCS5,PKCS7
* 加密规格样例:3DES192|ECB|NoPadding
*
*
*
*/
class TestSymmetric3DESEncryptDecrypt {
private symBinKey: Uint8Array = null;
private cipherText: Uint8Array = null;
private authTag: Uint8Array = null;
private algorithmWithLength: string = '3DES192'
private blockCipherMode: string = 'ECB' //ECB、CBC、OFB
private paddingMode: string = 'NoPadding' //NoPadding,PKCS5,PKCS7
//对称加密:3DES192|ECB|PKCS5
testSym3DESEncryptWith192ECBPKCS5(blockCipherMode: string, paddingMode: string, dataSource128:boolean) {
let originData: string = OriginData.CONTENT_128
if(dataSource128){
originData = OriginData.CONTENT_128
} else {
originData = OriginData.CONTENT_125
}
this.blockCipherMode = blockCipherMode
this.paddingMode = paddingMode
let symKeyGenerator = cryptoFramework.createSymKeyGenerator(this.algorithmWithLength);
let promiseSymKey = symKeyGenerator.generateSymKey();
let globalCipher: cryptoFramework.Cipher
promiseSymKey.then( key => {
console.log(key.format, key.algName, key.getEncoded().data.toString())
Logger.d(this.algorithmWithLength, this.blockCipherMode, this.paddingMode)
this.symBinKey = key.getEncoded().data;
globalCipher = cryptoFramework.createCipher(this.algorithmWithLength + '|'
+ this.blockCipherMode + '|'
+ this.paddingMode);
let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE;
let initResult = globalCipher.init(mode, key, null);
return initResult;
}).then(async () => {
const encoder = new util.TextEncoder()
let u8a_encoder = encoder.encodeInto(originData)
Logger.d('原文:' + u8a_encoder.toString())
let plainText = { data: u8a_encoder };
let promiseUpdate = globalCipher.update(plainText);
return promiseUpdate;
}).then( updateOutput => {
if(updateOutput.data != null){
Logger.d('编码后: ' + updateOutput.data.toString())
}
this.cipherText = updateOutput.data
if (this.paddingMode != 'NoPadding') {
let promiseFinal = globalCipher.doFinal(null);
return promiseFinal;
} else {
return null
}
}).then(authTag => {
if ((authTag != null) && (authTag.data != null)) {
console.log('authTag:', authTag.data.toString())
this.authTag = authTag.data
} else {
this.authTag = null
}
return
}) .then(() => {
this.testSym3DESDecrypt()
return
}).catch( error => {
console.error(`catch error, ${error.code}, ${error.message}`);
this.notificationStatus('加密中-error', error.code + '-' + error.message)
})
}
//对称解密: 3DES192|ECB|PKCS5
testSym3DESDecrypt() {
// 二进制密钥数据
let keyMaterialBlob: cryptoFramework.DataBlob = { data: this.symBinKey };
let globalCipher: cryptoFramework.Cipher
let symKeyGenerator = cryptoFramework.createSymKeyGenerator(this.algorithmWithLength);
let first: Uint8Array = null
// 根据指定的二进制密钥数据,生成对称密钥对象
symKeyGenerator.convertKey(keyMaterialBlob)
.then(key => {
globalCipher = cryptoFramework.createCipher(this.algorithmWithLength + '|'
+ this.blockCipherMode + '|'
+ this.paddingMode);
let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
globalCipher.init(mode, key, null)
return
}).then(() => {
let mergeResult: Uint8Array
if ((this.authTag != undefined) && (this.authTag != null)) {
mergeResult = new Uint8Array([...this.cipherText, ...this.authTag])
} else {
mergeResult = this.cipherText
}
let promiseUpdate = globalCipher.update({ data: mergeResult });
return promiseUpdate;
})
.then( updateOutput => {
first = updateOutput.data
let promiseFinal = globalCipher.doFinal(null);
return promiseFinal;
})
.then( finalOutput => {
if (finalOutput == null) {
let textDecoder = util.TextDecoder.create()
let key = textDecoder.decodeWithStream(first);
Logger.d('解密完成1: ', key);
this.notificationStatus('解密完成', key)
} else {
let textDecoder = util.TextDecoder.create()
let key = textDecoder.decodeWithStream(new Uint8Array([...first, ...finalOutput.data]));
Logger.d('解密完成2: ', key);
this.notificationStatus('解密完成', key)
}
console.log('解密结束')
})
.catch(error => {
console.error(`catch error, ${error.code}, ${error.message}`);
this.notificationStatus('解密中-error', error.code + '-' + error.message)
})
}
notificationStatus(status:string, result: string){
// 定义一个eventId为1的事件,事件优先级为Low
let event = {
eventId: 202404063287,
priority: emitter.EventPriority.HIGH
};
let eventData = {
data: {
"content": result,
'status': status,
}
};
// 发送eventId为1的事件,事件内容为eventData
emitter.emit(event, eventData);
}
}
export default new TestSymmetric3DESEncryptDecrypt();
实践结果
以256长度分组,"128字节"作为数据源
数据集
typescript
export default class OriginData {
//125字节 * 8
public static readonly CONTENT_125: string =
"hello...iot...modbus,"
+ "加解密算法库框架是,全随机数等相关功能测开发者测可以通过调用加解密1024。"
//128字节 * 8
public static readonly CONTENT_128: string =
"hello...iot...modbus,"
+ "加解密算法库框架是,安全随机数等相关功能测开发者测可以通过调用加解密1024。"
}
组合实验结果
T:成功,F:失败
AES256
组合 | 结果 | 组合 | 结果 | 组合 | 结果 |
---|---|---|---|---|---|
ECB-NoPadding | T | ECB-PKCS5 | T | ECB-PKCS7 | T |
CBC-NoPadding | T | CBC-PKCS5 | T | CBC-PKCS7 | T |
OFB-NoPadding | T | OFC-PKCS5 | T | OFB-PKCS7 | T |
CFB-NoPadding | T | CFB-PKCS5 | T | CFB-PKCS7 | T |
CTR-NoPadding | T | CTR-PKCS5 | T | CTR-PKCS7 | T |
GCM-NoPadding | T | GCM-PKCS5 | T | GCM-PKCS7 | T |
CCM-NoPadding | F |
CCM-PKCS5 | T | CCM-PKCS7 | T |
3DES
组合 | 结果 | 组合 | 结果 | 组合 | 结果 |
---|---|---|---|---|---|
ECB-NoPadding | T | ECB-PKCS5 | T | ECB-PKCS7 | T |
CBC-NoPadding | T | CBC-PKCS5 | T | CBC-PKCS7 | T |
OFB-NoPadding | T | OFC-PKCS5 | T | OFB-PKCS7 | T |
结束
本篇没有演示分段加密和分段解密,原始:代码没有实验成功
既然已经有了密文分组模式和长度,理论上在执行update时,其内部已经完成了分组加密,感觉也没有必要单独分段加密。
祝好运!