头图

什么是AES对称加密

    对称/分组密码一般分为流加密(如OFB、CFB等)和块加密(如ECB、CBC等)。对于流加密,需要将分组密码转化为流模式工作。对于块加密(或称分组加密),如果要加密超过块大小的数据,就需要涉及填充和链加密模式。

ECB(Electronic Code Book电子密码本)模式

ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密

官方文档
加解密算法库框架开发指导

鸿蒙开发中如何使用AES加密
官方文档中使用方法:


// AES GCM模式示例,自动生成密钥(promise写法)
function testAesGcm() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('testAesGcm');
    }, 10)
  }).then(() => {
    // 生成对称密钥生成器
    let symAlgName = 'AES128';
    let symKeyGenerator = cryptoFramework.createSymKeyGenerator(symAlgName);
    if (symKeyGenerator == null) {
      console.error('createSymKeyGenerator failed');
      return;
    }
    console.info(`symKeyGenerator algName: ${symKeyGenerator.algName}`);
    // 通过密钥生成器随机生成128位长度的对称密钥
    let promiseSymKey = symKeyGenerator.generateSymKey();
    // 构造参数
    globalGcmParams = genGcmParamsSpec();
 
    // 生成加解密生成器
    let cipherAlgName = 'AES128|GCM|PKCS7';
    try {
      globalCipher = cryptoFramework.createCipher(cipherAlgName);
      console.info(`cipher algName: ${globalCipher.algName}`);
    } catch (error) {
      console.error(`createCipher failed, ${error.code}, ${error.message}`);
      return;
    }
    return promiseSymKey;
  }).then(key => {
      let encodedKey = key.getEncoded();
      console.info('key hex:' + uint8ArrayToShowStr(encodedKey.data));
      globalKey = key;
      return key;
  }).then(key => {
      // 初始化加解密操作环境:开始加密
      let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE;
      let promiseInit = globalCipher.init(mode, key, globalGcmParams);    // init
      return promiseInit;
  }).then(() => {
      let plainText = {data : stringToUint8Array('this is test!')};
      let promiseUpdate = globalCipher.update(plainText);   // update
      return promiseUpdate;
  }).then(updateOutput => {
      globalCipherText = updateOutput;
      let promiseFinal = globalCipher.doFinal(null);    // doFinal
      return promiseFinal;
  }).then(authTag => {
      // GCM模式需要从doFinal的输出中取出加密后的认证信息并填入globalGcmParams,在解密时传入init()
      globalGcmParams.authTag = authTag;
      return;
  }).then(() => {
      // 初始化加解密操作环境:开始解密
      let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
      let promiseInit = globalCipher.init(mode, globalKey, globalGcmParams);    // init
      return promiseInit;
  }).then(() => {
      let promiseUpdate = globalCipher.update(globalCipherText);    // update
      return promiseUpdate;
  }).then(updateOutput => {
      console.info('decrypt plainText: ' + uint8ArrayToString(updateOutput.data));
      let promiseFinal = globalCipher.doFinal(null);    // doFinal
      return promiseFinal;
  }).then(finalOutput => {
      if (finalOutput == null) {  // 使用finalOutput.data前,先判断结果是否为null
          console.info('GCM finalOutput is null');
      }
  }).catch(error => {
      console.error(`catch error, ${error.code}, ${error.message}`);
  })
}

AES加密:

//AES加密
function aesECBEncrypt(plaintext) {
  let key = '12345678901234==';
  let ivKey = '12345678901234==';
  let cipherAlgName = 'AES128|ECB|PKCS7';
  let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES128')
  let ivParam: cryptoFramework.IvParamsSpec = {
    algName: 'IvParamsSpec',
    iv: {
      data: stringToUint8Array(ivKey, 16)
    }
  }
  var cipher;
 
  // 生成密钥
  return symKeyGenerator.convertKey({
    data: stringToUint8Array(key)
  }).then(symKey => {
    // 创建加密器
    try {
      cipher = cryptoFramework.createCipher(cipherAlgName);
      console.info(`xx cipher algName: ${cipher.algName}`);
    } catch (error) {
      console.error(`xx createCipher failed, ${error.code}, ${error.message}`);
      return null
    }
    // 初始化加密器
    return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, ivParam)
      .then(() => {
        // 开始加密
        return cipher.doFinal({
          data: stringToUint8Array(plaintext)
        })
      })
      .then(output => {
        let base64 = new util.Base64Helper();
        let result = base64.encodeToStringSync(output.data);
        return new Promise((resolve) => {
          resolve(result)
        })
      }).catch(err => {
        return new Promise((_, reject) => {
          reject(err)
        })
      })
  }).catch(err => {
    return new Promise((_, reject) => {
      reject(err)
    })
  })
}

AES解密:

//AES解密
function aesECBDecrypt(encrypttext) {
  let key = '12345678901234==';
  let ivKey = '12345678901234==';
  let cipherAlgName = 'AES128|ECB|PKCS7';
  let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES128')
  let ivParam: cryptoFramework.IvParamsSpec = {
    algName: 'IvParamsSpec',
    iv: {
      data: stringToUint8Array(ivKey, 16)
    }
  }
  var cipher;
 
  return symKeyGenerator.convertKey({
    data: stringToUint8Array(key)
  }).then(symKey => {
    try {
      cipher = cryptoFramework.createCipher(cipherAlgName);
      console.info(`xx cipher algName: ${cipher.algName}`);
    } catch (error) {
      console.error(`xx createCipher failed, ${error.code}, ${error.message}`);
      return null
    }
    return cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, ivParam)
      .then(() => {
        let base64 = new util.Base64Helper();
        let result = base64.decodeSync(encrypttext);
        return cipher.doFinal({
          data: result
        })
      })
      .then(output => {
        let result = uint8ArrayToString(output.data)
        return new Promise((resolve) => {
          resolve(result)
        })
      }).catch(err => {
        return new Promise((_, reject) => {
          reject(err)
        })
      })
  }).catch(err => {
    return new Promise((_, reject) => {
      reject(err)
    })
  })
}

使用加密观察结果
调用:


aboutToAppear() {
    let xxx;
    let globalPlainText = "This is a long plainTest! This is a long plainTest! This is a long plainTest!";
     console.log('Aes加密===原文is :' + globalPlainText)
    aesECBEncrypt(globalPlainText).then(res=>{
      console.log('Aes加密===加密后的密文is :' + res)
      xxx = res;
    }).catch(err => {
      console.log('Aes加密===catch :' + err)
    })
    setTimeout(function() {
      console.log('Aes加密=================================== ' )
      aesECBDecrypt(xxx).then(res=>{
        console.log('Aes加密===解密后的原文is: ' + res)
      }).catch(err => {
        console.log('Aes加密===解密:catch: ' + err)
      })
    }, 3000);
  
  }

结果:


I 0FEFE/JsApp: Aes加密===原文is :This is a long plainTest! This is a long plainTest! This is a long plainTest!
I 0FEFE/JsApp: Aes加密===加密后的密文is :zbusam9rOBPmJ072ENhEO9DCPD/DeSJOYvFGHOmYazQ7pFjUXUMW7aUfhyYtqVLQuflFtPW5Y8MG06n1C0stQJybjSA68it6TNVWP6GBkk8=
I 0FEFE/JsApp: Aes加密===================================
I 0FEFE/JsApp: Aes加密===解密后的原文is: This is a long plainTest! This is a long plainTest! This is a long plainTest!
可以看到打印出来是没有任何问题的,但是当我在加密原文中加入中文后出现了意想不到的结果,将 globalPlainText改为:This is a long plainTest! AES加密原文 ;观察打印结果

I 0FEFE/JsApp: Aes加密===原文is :This is a long plainTest! AES加密原文
I 0FEFE/JsApp: Aes加密===加密后的密文is :zbusam9rOBPmJ072ENhEO+8AvpCHJdNUxFsgzFxHMM55off1zCchwYL/C/3DHvPm
I 0FEFE/JsApp: Aes加密===================================
I 0FEFE/JsApp: Aes加密===解密后的原文is: This is a long plainTest! AES Æ

what ? 英文没问题,为啥换成中文就不行了?

解密过程出现中文乱码如何处理
百思不得其解,官方文档明明就是这么写的,难道是官方文档有问题?


uint8ArrayToString、 stringToUint8Array在文档中出现过多次,也有实例,文档中的使用:

// 字节流以16进制输出
function uint8ArrayToShowStr(uint8Array) {
  return Array.prototype.map
    .call(uint8Array, (x) => ('00' + x.toString(16)).slice(-2))
    .join('');
}
 
// 字节流转成可理解的字符串
function uint8ArrayToString(array) {
  let arrayString = '';
  for (let i = 0; i < array.length; i++) {
    arrayString += String.fromCharCode(array[i]);
  }
  return arrayString;
}

思考:会不会在字符串转字节流的时候出现了问题
String转16进制的原理是将字符串中每个字符转换为16进制的形式,

UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节。

使用Java进行16进制转换的时候我们都知道会将内容先统一编码,再使用StringBuilder转16进制


bytel] bytes = chinese.getBytes(Charset.forName("UTF-8"));
StringBuilder hex= new StringBuilder();

但是刚刚的AES加密uint8ArrayToShowStr函数中没有统一指定编码格式,那如果一个中文占用了3个字符,岂不是无法解码,果然问题出在这,在翻阅鸿蒙util文档中我发现了TextEncoder:
文档中对TextEncoder是这么介绍的:

​ 该模块主要提供常用的工具函数,实现字符串编解码(TextEncoder,TextDecoder)这不就是我们想要的嘛 ​

完美解决中文加密解密乱码问题
修改字符串转成字节流 互相转换的函数如下:


// 字符串转成字节流
function stringToUint8Array(str, len = null) {
  let textEncoder = new util.TextEncoder();
  //获取点流并发出 UTF-8 字节流 TextEncoder 的所有实例仅支持 UTF-8 编码
  return textEncoder.encodeInto(str);
}
 
// 字节流转成可理解的字符串
function uint8ArrayToString(array) {
  let textDecoder = util.TextDecoder.create("utf-8", { ignoreBOM: true })
  return textDecoder.decodeWithStream(new Uint8Array(array), { stream: false });
}

继续执行代码观察日志

I 0FEFE/JsApp: Aes加密===原文is :This is a long plainTest! AES加密原文
I 0FEFE/JsApp: Aes加密===加密后的密文is :zbusam9rOBPmJ072ENhEO5JErEY5X7HP2L/WPwARmlMV0PoGgXX+6ZhohYdJLhKt
I 0FEFE/JsApp: Aes加密===================================
I 0FEFE/JsApp: Aes加密===解密后的原文is: This is a long plainTest! 

AES加密原文
日志中可以看出,中文加密的密文也能被完整的解密出来,至此这篇文章页到了尾声,希望我的文章能够帮到你。


kirk
1 声望0 粉丝