HarmonyOS Next 开发AES加密解密和中文乱码处理

部署运行你感兴趣的模型镜像

 什么是AES对称加密

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

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

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

官方文档

加解密算法库框架开发指导

HarmonyOS Next 开发中如何使用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加密原文

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

 

您可能感兴趣的与本文相关的镜像

Seed-Coder-8B-Base

Seed-Coder-8B-Base

文本生成
Seed-Coder

Seed-Coder是一个功能强大、透明、参数高效的 8B 级开源代码模型系列,包括基础变体、指导变体和推理变体,由字节团队开源

<think>好的,我需要帮助用户解决在Java程序中解密SQL Server中使用对称加密的数据的问题。根据用户提供的引用内容,特别是引用[5]提到的使用对称密钥(如AES_256)加密数据,用户希望用Java解密这些数据。 首先,我需要确认SQL Server中对称加密的具体配置。用户可能使用的是SQL Server的内置对称加密方法,比如通过证书加密对称密钥,然后用对称密钥加密数据。根据引用[5]中的示例,创建对称密钥的语句是使用AES_256算法,并且通过证书进行加密。因此,Java程序需要能够使用相同的对称密钥算法来解密数据。 接下来,用户提到之前的问题是关于Hutool AES加密在SQL Server解密的问题,而当前的询问是如何在Java中解密SQL Server的对称加密数据。需要参考之前的解决方案,可能涉及密钥参数的统一,比如密钥长度、模式、填充方式、IV等。之前的回答中提到了Hutool默认使用CBC模式PKCS5Padding,而SQL Server可能使用不同的设置,需要确保这些参数一致。 现在的问题是解密SQL Server中用对称密钥加密的数据,用户需要在Java中实现。可能的步骤包括: 1. **获取SQL Server中的对称密钥加密数据**:可能需要导出对称密钥或相关证书,或者从数据库中获取加密后的数据以及必要的参数(如IV、密钥等)。但根据SQL Server的加密机制,对称密钥通常由证书加密存储,直接导出可能不可行,可能需要使用相同的密钥材料在Java中生成相同的密钥。 2. **确定加密算法参数**:根据SQL Server中创建对称密钥的语句(如AES_256),确定Java中使用的算法是否为"AES/CBC/PKCS5Padding"或其他模式。需要确认SQL Server使用的模式,例如是ECB还是CBC,是否有IV,填充方式等。例如,如果SQL Server使用的是AES_256的CBC模式,并且自动生成IV,那么Java中需要以同样的方式处理IV。 3. **处理密钥的获取转换**:在SQL Server中,对称密钥可能由证书加密,但实际加密数据时使用的是对称密钥本身。用户可能需要从数据库中获取加密后的对称密钥,并用Java中的证书解密,但这个过程可能比较复杂。或者,如果对称密钥是由密码短语生成的,可能需要通过相同的方式在Java中生成相同的密钥。例如,使用PBKDF2从密码生成密钥。 4. **数据格式处理**:SQL Server中的加密数据可能以varbinary格式存储,Java中需要正确读取并解码这些二进制数据,然后进行解密。例如,使用JDBC获取varbinary字段,转换为字节数组,然后进行解密。 5. **Java中的解密实现**:使用JCE(Java Cryptography Extension)库,根据确定的算法、密钥、IV等参数,编写解密代码。例如,使用Cipher类,初始化解密模式,传入密钥IV,然后处理加密数据。 可能的挑战包括: - **密钥的同步**:如何在Java中获得SQL Server中相同的对称密钥。如果SQL Server的对称密钥是由证书保护的,可能需要导出证书并在Java中使用,或者通过其他方式共享密钥。 - **加密参数的匹配**:确保Java中的算法名称、模式、填充SQL Server一致。例如,SQL Server可能使用AES_256的CBC模式,而Java中对应的算法名称是"AES/CBC/PKCS5Padding"。 - **IV的处理**:如果SQL Server在加密时自动生成IV并前置在密文中,Java解密时需要正确提取IV部分。例如,前16字节是IV,剩余的是密文。 根据用户之前的回答,他们使用了Hutool的AES工具,可能需要类似的步骤,但需要根据SQL Server对称加密的具体参数进行调整。例如,确认密钥长度(256位),模式(CBC),填充(PKCS7),以及IV的来源(是否拼接在密文中)。 此外,引用[4]中提到了使用PassPhrase加密,即通过密码短语生成密钥,可能使用类似ENCRYPTBYPASSPHRASE的函数,这种情况下Java中可能需要使用相同的密码密钥派生函数(如PBKDF2)生成密钥,然后解密。 现在需要将这些思路整理成一个清晰的步骤,帮助用户在Java中实现解密。可能需要示例代码,演示如何从数据库获取加密数据,提取IV(如果有),生成密钥,并使用正确的参数进行解密。</think>根据SQL Server对称加密的实现特点,这里给出Java解密的完整解决方案: ### 一、前置条件确认 1. **加密参数匹配** - 确认SQL Server使用的对称密钥类型(AES_256等)、工作模式(通常为CBC)、填充方式(PKCS7)[^5] - 标准配置示例: $$ \text{Algorithm}=AES/CBC/PKCS5Padding,\ \text{KeySize}=256\text{bit} $$ 2. **密钥获取方式** - 需要从数据库导出或同步密钥材料(需DBA配合)[^5] - 若使用密码生成密钥,需确认密钥生成算法(如PBKDF2) ### 二、核心实现步骤 #### 1. 获取加密数据格式 SQL Server加密后的数据通常包含: ```sql -- 加密示例 CREATE SYMMETRIC KEY MyKey WITH ALGORITHM = AES_256 ENCRYPTION BY CERTIFICATE MyCert; DECLARE @encrypted VARBINARY(MAX) = ENCRYPTBYKEY(KEY_GUID('MyKey'), '敏感数据'); ``` 加密结果包含:头部信息(16字节) + IV(16字节) + 密文数据[^5] #### 2. Java解密实现 ```java import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Arrays; public class SQLServerDecryptor { // 从数据库读取的完整加密数据(包含头部+IV+密文) private static byte[] encryptedData = hexToBytes("01...DB"); public static void main(String[] args) throws Exception { // 步骤1:提取有效部分(跳过SQL Server加密头部) byte[] realCiphertext = Arrays.copyOfRange(encryptedData, 32, encryptedData.length); // 步骤2:分离IV密文(前16字节为IV) byte[] iv = Arrays.copyOfRange(realCiphertext, 0, 16); byte[] ciphertext = Arrays.copyOfRange(realCiphertext, 16, realCiphertext.length); // 步骤3:加载密钥(需SQL Server密钥一致) byte[] keyBytes = "Your256BitKey".getBytes(StandardCharsets.UTF_8); SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); // 步骤4:配置解密器 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); // 步骤5:执行解密 byte[] plaintextBytes = cipher.doFinal(ciphertext); System.out.println("解密结果:" + new String(plaintextBytes, StandardCharsets.UTF_8)); } // 十六进制字符串转字节数组(根据实际存储格式调整) private static byte[] hexToBytes(String hex) { // 实现转换逻辑... } } ``` ### 三、关键配置说明 | 配置项 | SQL Server | Java对应配置 | 示例值 | |--------|------------|--------------|--------| | 算法 | AES_256 | AES/CBC/PKCS5Padding | `Cipher.getInstance("AES/CBC/PKCS5Padding")` | | 密钥 | 证书保护 | SecretKeySpec | `new SecretKeySpec(keyBytes, "AES")` | | IV位置 | 加密数据前16字节 | IvParameterSpec | `new IvParameterSpec(iv)` | | 数据格式 | 包含32字节头部 | 需去除头部 | `Arrays.copyOfRange()` | ### 四、验证流程 1. **数据一致性测试** ```sql -- SQL Server端解密验证 SELECT CONVERT(VARCHAR, DECRYPTBYKEY(@encrypted)) AS plaintext; ``` 2. **边界条件检查** ```java // 验证密钥长度 if (keyBytes.length != 32) { // AES-256需要32字节 throw new IllegalArgumentException("密钥长度错误"); } ``` ### 五、常见问题排查 1. **解密出现乱码** - 检查是否跳过了SQL Server加密头部(前32字节) - 验证IV提取位置是否正确(有效数据的前16字节) 2. **InvalidKeyException异常** - 确认JCE无限制策略文件已安装(AES-256需要) - 检查密钥是否包含不可见字符(建议使用HEX格式传输) 3. **解密结果为空** - 使用Wireshark抓包验证数据库返回数据完整性 - 对比JavaSQL Server的Base64编码结果是否一致 ### 六、高级应用场景 **动态密钥加载方案**(需开启SQL Server CLR集成): ```java // 通过JDBC获取加密密钥 String sql = "SELECT DECRYPTBYCERT(CERT_ID('MyCert'), key_data) FROM sys.symmetric_keys"; try (Statement stmt = conn.createStatement()) { ResultSet rs = stmt.executeQuery(sql); if (rs.next()) { byte[] keyBytes = rs.getBytes(1); // 使用动态密钥初始化解密器... } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值