【分享】HarmonyOS 鸿蒙Next对称加密

【分享】HarmonyOS 鸿蒙Next对称加密

1. 介绍

HarmonyOS(鸿蒙)SDK API9支持两种对称加密算法:a. AES(Advanced Encryption Standard) b.3DES(也称为 3DESede 或 TripleDES).

官方也给出了一些样例使用代码,可供开发者参考。

本篇从实践出发,完整的通过代码方式来深入了解HarmonyOS中的对称加密用法。

加密使用的测试数据源有两个,具体见下图

  • “125字节” (即,占用了125个字节的字符串)
  • “128字节” (即,占用了128个字节的字符串)

3.1 MMI入口

cke_4938.png Screenshot_20240407141828163.png Screenshot_20240407141832979.png

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 加密

  1. 生成对动态密钥
//导入加密框架
import cryptoFramework from '@ohos.security.cryptoFramework';

......

//创建密钥生成器,参数为(密钥算法+密文组长度,如:AES256)
let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');

//生成对称密钥
let promiseSymKey = symKeyGenerator.generateSymKey();

//获取密钥
promiseSymKey(key => {
  //动态密钥 
  this.symBinKey = key.getEncoded().data;
})
  1. 初始化Cipher
//创建Cipher
globalCipher = cryptoFramework.createCipher('AES256|ECB|PKCS5');

//初始化Cipher
let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE; 
globalCipher.init(mode, key, null)
  1. 加密
//文字转换为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
})
  1. 结束加密
//结束加密
let promiseFinal = globalCipher.doFinal(null);

//获取剩余结果
promiseFinal(finalResult => {
    
    if(finalResult != null){
        //剩余加密结果
        let authTag = finalResult.data    
    }
    
})

3.2.2 解密

  1. 生成密钥对象
//准备密钥数据,this.symBinKey 是上述过程生成的密钥
let keyMaterialBlob: cryptoFramework.DataBlob = { data: this.symBinKey };

//创建密钥生成器,参数为(密钥算法+密文组长度,如:AES256)
let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');

//密钥生成器使用密钥数据,开始生成密钥对象
symKeyGenerator.convertKey(keyMaterialBlob).then(key => {
   //key 为生成的密钥对象
   
})
  1. 初始化Cipher
//创建Cipher
globalCipher = cryptoFramework.createCipher('AES256|EBC|PKCS5');

//初始化Cipher,参数key由第一步生成
let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
globalCipher.init(mode, key, null)
  1. 解密
//mergeResult 代表密文,本篇文章中,此值来源于上述加密结果
let promiseUpdate = globalCipher.update({ data: mergeResult });
  1. 结束解密
//结束解密
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 源码

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";

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

  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)
      })

  }

  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)
      })

  }

  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;
  }

  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, 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;
  }

  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){
     let event = {
       eventId: 202404063287,
       priority: emitter.EventPriority.HIGH
     };

     let eventData = {
       data: {
         "content": result,
         'status': status,
       }
     };

     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使用概括起来如下

  1. 创建密钥生成器: createSymKeyGenerator
  2. 生成密钥对象:generateSymKey 或 convertKey
  3. 创建Cipher: createCipher
  4. 初始化Cipher: init
  5. 加密/解密:update
  6. 结束加密/解密:doFinal

3.3.1 源码

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";

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


  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)
      })

  }

  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){
    let event = {
      eventId: 202404063287,
      priority: emitter.EventPriority.HIGH
    };

    let eventData = {
      data: {
        "content": result,
        'status': status,
      }
    };

    emitter.emit(event, eventData);
  }

}

export default new TestSymmetric3DESEncryptDecrypt();

实践结果

以256长度分组,“128字节”作为数据源

数据集

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。"

}

组合实验结果

组合 结果
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
组合 结果
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时,其内部已经完成了分组加密,感觉也没有必要单独分段加密。

祝好运!


更多关于【分享】HarmonyOS 鸿蒙Next对称加密的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于【分享】HarmonyOS 鸿蒙Next对称加密的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


鸿蒙Next中的对称加密是一种高效的加密方式,使用相同的密钥进行加密和解密。常见的对称加密算法包括AES、DES和3DES。在鸿蒙Next中,开发者可以通过javax.crypto包实现对称加密,确保数据在传输和存储中的安全性。对称加密适用于大量数据的加密场景,但需妥善管理密钥,防止泄露。

回到顶部