所有内容均为测试可用,真实
当前位置:绿茶加糖-郭保升 > SSL证书 > 正文

sm4分段加解密

07-11 SSL证书

先来看看文档中怎么介绍SM4:SM4为分组加密算法,分组长度128位,当前算法库提供SM4常用的7种加密模式:ECB、CBC、CTR、OFB、CFB、CFB128和GCM。

SM4算法规格:

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/crypto-sym-encrypt-decrypt-spec#sm4

华为的官方文档只给了SM4的GCM模式的分段加密解密,先把官网给的GCM模式分段加密解密实现下:

首先是加密过程:

1.生成加密密钥:Api:createSymKeyGenerator
https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-cryptoframework#cryptoframeworkcreatesymkeygenerator
  •  
cryptoFramework.createSymKeyGenerator("SM4_128");

2.创建加密过程的Cipher实例:Api:createCipher

https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-cryptoframework#cryptoframeworkcreatecipher

,createCipher需要传入一个参数,参数的官网解释为:待生成的Cipher的算法名称(含密钥长度),加密模式,以及填充方式的组合。这么说可能不直观,换个说法:

transformation为:算法名称|加密模式|填充方式。算法名称如:SM4_128,表示国密SM4算法,128位密钥;

加密模式如:ECB,CBC这种;填充方式如:PKCS5,PKCS7。不同的组合应对不同的场景。如下示例常见SM4,128位密钥的GCM,填充模式位PKCS7的cipher实例。

  •  
cryptoFramework.createCipher("SM4_128|GCM|PKCS7");
3.创建好cipher后,进行cipher的初始化,Api:init
https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-cryptoframework#init-1
,注意这个初始化Api是实例cipher调用的,需要参数opMode,key,params,参数解释为:加密/解密模式,指定加密/解密的密钥,指定加密/解密参数。
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
initSM4() {  this.sm4Key = this.generateSM4Key();//调用密钥生成函数  this.cipher = cryptoFramework.createCipher("SM4_128|GCM|PKCS7");//创建cipher示实例  this.gcmParams = {    iv: { data: new Uint8Array(12) },    aad: { data: new Uint8Array(8) },    authTag: { data: new Uint8Array(8) },    algName: 'GcmParamsSpec'  }  this.cipher.initSync(cryptoFramework.CryptoMode.ENCRYPT_MODE, this.sm4Key, this.gcmParams)
4.分段加密过程:循环调用update

https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-cryptoframework#update-1

接口更新数据,更新结束必须要调用doFinal
https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-cryptoframework#dofinal-1
终止update过程,并获取最终加密数据,将数据作为解密的认证信息。
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
encryptData(cipher: cryptoFramework.Cipher, data: Uint8Array, gcmParams: cryptoFramework.GcmParamsSpec) {  const blockSize = 256;//分段加密的块区大小  let outPut = new Uint8Array(0);  try {    for (let i = 0; i < data.length; i += blockSize) {//分段过程      const chunk = data.slice(i, i + blockSize);      const updateBlob = cipher.updateSync({        data: chunk      });      outPut = this.concatUint8Arrays(outPut,updateBlob.data);    }    const finalBlob = cipher.doFinalSync(null);//获取加密后的数据    gcmParams.authTag = finalBlob;//作为解密的认证信息    hilog.info(0x0000,"ccTest",outPut.toString())  }catch (e) {    hilog.info(0x0000,"ccTest",JSON.stringify(e))  }  return outPut;}
解密过程和加密过程没什么差别,需要注意的是,1.加密过程的cipher实例并不能作为解密过程的cipher实例使用,这两个过程的cipher并不是同一个实例;2.解密的cipher参数要和加密的cipher完全一样,同样包含初始化,update更新,doFinal过程。代码如下:
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  decryData(decoder:cryptoFramework.Cipher,encryptedData:Uint8Array){    const blockSize = 256;    let output = new Uint8Array(0);    try {      for (let i = 0; i < encryptedData.length; i += blockSize) {        const chunk = encryptedData.slice(i, i + blockSize);        const updateBlob =  decoder.updateSync({ data: chunk });        output = this.concatUint8Arrays(output, updateBlob.data);      }      decoder.doFinalSync(null);     }catch (e) {      hilog.info(0x0000,"ccTest",JSON.stringify(e))    }    return output;  }
以加密解密字符串为例,还需要对应的字符串拼接函数:
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  concatUint8Arrays(arr1Uint8Arrayarr2Uint8Array): Uint8Array {    // 创建新数组(长度 = arr1长度 + arr2长度)    const result = new Uint8Array(arr1.length + arr2.length);    // 手动复制数据    for (let i = 0; i < arr1.length; i++) {      result[i] = arr1[i];    }    for (let i = 0; i < arr2.length; i++) {      result[arr1.length + i] = arr2[i];    }    return result;  }  u8ArrToStr(u8Arr:Uint8Array):string{    let str = "";    for (let i = 0; i < u8Arr.length; i++) {      str += String.fromCharCode(u8Arr[i]);    }    return str;  }
以上就是SM4在GCM模式下的分段加解密,那其他模式呢?
1.ECB的分段加解密,ECB模式容易被模式分析攻击,在ECB模式下,相同的明文块会输出相同的密文,如果是加密大量数据的话,能通过这些特征破解。直接看分段的代码:
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  encryptECBData(data: Uint8Array,key:cryptoFramework.SymKey) {    const cipher = cryptoFramework.createCipher('SM4_128|ECB|PKCS5');    cipher.initSync(cryptoFramework.CryptoMode.ENCRYPT_MODE, key, null)    const blockSize = 256;    let outPut = new Uint8Array(0);    for (let i = 0; i < data.length; i += blockSize) {      const chunk = data.subarray(i, i + blockSize);      const updateBlob = cipher.updateSync({        data: chunk      })      if(updateBlob){        outPut = this.concatUint8Arrays(outPut, updateBlob.data);      }    }    let final = cipher.doFinalSync(null);     if(final){      outPut = this.concatUint8Arrays(outPut,final.data)    };    return outPut;  }      decryECBData(encryptedData: Uint8Array,key:cryptoFramework.SymKey) {    const cipher = cryptoFramework.createCipher('SM4_128|ECB|PKCS5');    try {      cipher.initSync(cryptoFramework.CryptoMode.DECRYPT_MODE, key, null)    }catch (e) {      hilog.info(0x0000"ccTest", JSON.stringify(e))    }    const blockSize = 256;    let output = new Uint8Array(0);    try {      for (let i = 0; i < encryptedData.length; i += blockSize) {        const chunk = encryptedData.subarray(i, i + blockSize);        const updateBlob = cipher.updateSync({ data: chunk });        if(updateBlob){          output = this.concatUint8Arrays(output, updateBlob.data);        }      }      let final = cipher.doFinalSync(null);       if(final){        output = this.concatUint8Arrays(output,final.data)      }    } catch (e) {      hilog.info(0x0000"ccTest", JSON.stringify(e))    }    return output;  }

这个加解密过程需要严格注意时序,不然会出现401,17360001,正常交替出现。

再看看CTR模式的分段加解密:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  encryptCTRData(data: Uint8Array, key: cryptoFramework.SymKey){    const cipher = cryptoFramework.createCipher('SM4_128|CTR|NoPadding');    cipher.initSync(cryptoFramework.CryptoMode.ENCRYPT_MODE, key, null);    const blockSize = 256;    let outPut = new Uint8Array(0);    try {      for (let i = 0; i < data.length; i += blockSize) {        const chunk = data.subarray(i, i + blockSize);        const updateBlob = cipher.updateSync({          data: chunk        })        if (updateBlob) {          outPut = this.concatUint8Arrays(outPut, updateBlob.data);        }      }      let final = cipher.doFinalSync(null);       if (final) {        outPut = this.concatUint8Arrays(outPut, final.data)      }      ;    } catch (e) {      hilog.info(0x0000"ccTest"JSON.stringify(e))    }    return outPut;  }  decryCTRData(encryptedData: Uint8Array, key: cryptoFramework.SymKey){    const cipher = cryptoFramework.createCipher('SM4_128|CTR|NoPadding');    try {      cipher.initSync(cryptoFramework.CryptoMode.DECRYPT_MODE, key, null)    } catch (e) {      hilog.info(0x0000"ccTest"JSON.stringify(e))    }    const blockSize = 256;    let output = new Uint8Array(0);    try {      for (let i = 0; i < encryptedData.length; i += blockSize) {        const chunk = encryptedData.subarray(i, i + blockSize);        const updateBlob = cipher.updateSync({ data: chunk });        if (updateBlob) {          output = this.concatUint8Arrays(output, updateBlob.data);        }      }      let final = cipher.doFinalSync(null);       if (final) {        output = this.concatUint8Arrays(output, final.data)      }    } catch (e) {      hilog.info(0x0000"ccTest"JSON.stringify(e))    }    return output;  }

观察GCM,ECB,CTR这三种模式的分段加解密代码,可以发现,SM4的分段加密解密的代码相似度非常高。木有错,这几种模式主要的区别在createCipher的参数上,比如

ECB需要填充PCKS7/PCKS7,GCM,CTR不需要;GCM需要解密时设置authTag用于强制验证。

但是我在用CTR进行测试的时候(就行上述代码示例),发现CTR模式下,不指定iv参数,也是能够正常加解密的,但是CTR在init的时候需要传入iv参数,猜测传入null值时,Api内部做了适配?至于为什么Api允许CTR模式的iv参数可以为null,这需要问一问api设计者了。所以实际上CTR的初始化要这么做:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  encryptCTRData(data: Uint8Array, key: cryptoFramework.SymKey,ctrParams:cryptoFramework.IvParamsSpec){    const cipher = cryptoFramework.createCipher('SM4_128|CTR|NoPadding');    cipher.initSync(cryptoFramework.CryptoMode.ENCRYPT_MODE, key, ctrParams);    const blockSize = 256;    let outPut = new Uint8Array(0);    try {      for (let i = 0; i < data.length; i += blockSize) {        const chunk = data.subarray(i, i + blockSize);        const updateBlob = cipher.updateSync({          data: chunk        })        if (updateBlob) {          outPut = this.concatUint8Arrays(outPut, updateBlob.data);        }      }      let final = cipher.doFinalSync(null);       if (final) {        outPut = this.concatUint8Arrays(outPut, final.data)      }      ;    } catch (e) {      hilog.info(0x0000"ccTest"JSON.stringify(e))    }    return outPut;  }  decryCTRData(encryptedData: Uint8Array, key: cryptoFramework.SymKey,ctrParams:cryptoFramework.IvParamsSpec){    const cipher = cryptoFramework.createCipher('SM4_128|CTR|NoPadding');    try {      cipher.initSync(cryptoFramework.CryptoMode.DECRYPT_MODE, key, ctrParams)    } catch (e) {      hilog.info(0x0000"ccTest"JSON.stringify(e))    }    const blockSize = 256;    let output = new Uint8Array(0);    try {      for (let i = 0; i < encryptedData.length; i += blockSize) {        const chunk = encryptedData.subarray(i, i + blockSize);        const updateBlob = cipher.updateSync({ data: chunk });        if (updateBlob) {          output = this.concatUint8Arrays(output, updateBlob.data);        }      }      let final = cipher.doFinalSync(null);      if (final) {        output = this.concatUint8Arrays(output, final.data)      }    } catch (e) {      hilog.info(0x0000"ccTest"JSON.stringify(e))    }    return output;  }

其中ctrParams为,这一块内容可以看文档的安全随机数生成:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/crypto-generate-random-number,另外建议官方在SM4文档那块增加说明,CTR在init的时候要传入iv参数

(ps:鸿蒙的开发文档真的是东一块西一块)

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
let randomKey =  cryptoFramework.createRandom();let seed = new Uint8Array([123]);randomKey.setSeed({data:seed});let counter = randomKey.generateRandomSync(16);let ctrParams :cryptoFramework.IvParamsSpec = {  iv: counter,  algName'IvParamsSpec'}

以下是SM4的7种模式的差异(ds给的表,仅供参考)

模式 加密原理 分段处理差异 IV 要求 填充要求 错误传播 并行性 典型场景
ECB

(电子密码本)

每个明文块独立加密,相同输入产生相同输出
直接分块独立处理,无链式依赖
❌ 不需要
✅ 必须填充

(如 PKCS7)

❌ 单块错误不影响其他块
✅ 加密/解密均可并行
简单加密、硬件加速场景17
CBC

(密码块链)

前一块密文与当前明文异或后加密
需依赖前一块密文,串行处理
✅ 需固定长度 IV

(通常 16 字节)

✅ 必须填充
✅ 单块错误影响后续所有块
❌ 仅解密可并行
文件加密、SSL/TLS19
CFB

(密文反馈)

将分组密码转为流密码,加密反馈寄存器后与明文异或
支持按字节/比特流处理,无需填充
✅ 需 IV(长度=分组大小)
❌ 无需填充
✅ 单字节错误影响后续字节
❌ 串行处理
实时流数据传输17
OFB

(输出反馈)

加密 IV 生成密钥流,与明文异或
密钥流独立于明文,可预生成
✅ 需 IV(长度=分组大小)
❌ 无需填充
❌ 仅影响当前字节
❌ 串行生成密钥流
卫星通信、高容错场景14
CTR

(计数器)

加密计数器生成密钥流,与明文异或
计数器可并行递增,支持随机访问
✅ 需唯一计数器

(Nonce + Counter)

❌ 无需填充
❌ 仅影响当前块
✅ 完全并行
大数据加密、GPU 加速79
CTS

(密文窃取)

CBC 变体,处理末尾不完整块时“窃取”密文填充
末尾块特殊处理,避免填充
✅ 需 IV
❌ 无需填充
✅ 错误传播类似 CBC
❌ 串行处理
固定长度报文加密1
GCM

(伽罗瓦计数)

CTR 模式 + GMAC 认证
CTR 模式加密,GMAC 计算认证标签
✅ 需 IV(通常 12 字节)
❌ 无需填充
❌ 不影响认证
 
 

版权保护: 本文由 绿茶加糖-郭保升 原创,转载请保留链接: https://www.guobaosheng.com/tuijian/2025/0711/367.html