← Go back

鸿蒙开发RSA加解密实现方案

鸿蒙开发入门篇

Table of contents

Open Table of contents

    此方案基于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()
        })
      }
    }
    

    最终结果

    鸿蒙ArkTs API9 RSA加解密