AES之CryptoJS加密与C#解密
曾经以为ChatGpt 应该是无所不知道,无所不能的,但是就这个C# 解密用了两天时间来搞,gpt给的代码一直有各种bug,最后还是要靠搜索引擎Bing的帮助才找到了答案
AES加密之CryptoJS与Java C#互相加密解密_梁金堂的博客-CSDN博客 这个文章提供了我查找的方向
Port crypto-js AES functions to C# - TruongTX Blog 这个文章给了我解决的方案
下面是完整的代码示例
/// <summary>
/// <para>C# 版本的 cryptojs.AES.decrypt(encryptedString, passphrase).toString(cryptojs.enc.Utf8)</para>
/// <para>使用 AES 加密时,需要传入一个 Key 和一个随机的 IV - 初始化向量(IV 用于为加密过程添加随机性)</para>
/// <para>在 crypto-js 中,如果你将一个口令传递给 "encrypt" 函数,例如 <code>cryptojs.AES.encrypt(message, passphrase).toString()</code>,Key 和 IV 将会自动生成</para>
/// <para>为了从口令中派生出 Key,它使用 OpenSSL 兼容的派生函数 EVP_BytesToKey。该模式生成一个新的 8 字节随机 salt,并与口令一起使用来生成 Key 和 IV。更多信息请参见 <see cref="DeriveKeyAndIv"/>。</para>
/// <para>使用 <code>cryptojs.AES.encrypt(message, passphrase).toString()</code> 加密的结果是一个 Base64 编码的密文,其中包含字符串 "Salted__",后面跟着 8 字节的 salt 和实际的密文。这意味着,前 8 个字节是 "Salted__" 字符串,接下来的 8 个字节是 salt,剩下的字节是实际的密文。</para>
/// <para>以下是解密 cryptojs 生成的 Base64 字符串的步骤:</para>
/// <para>- 将 Base64 字符串转换回一个字节数组</para>
/// <para>- 忽略前 8 个字节(为字符串 "Salted__")</para>
/// <para>- 取接下来的 8 个字节作为 salt</para>
/// <para>- 取剩下的字节作为密文</para>
/// <para>- 使用实用程序函数 <see cref="DeriveKeyAndIv"/> 生成 Key 和 IV</para>
/// <para>- 将密文、Key 和 IV 输入 AES 解密器以获得最终的字符串</para>
/// </summary>
/// <param name="encryptedString">待解密的 Base64 字符串</param>
/// <param name="passphrase">口令,用于生成 Key 和 IV</param>
/// <returns>解密后的字符串</returns>
public static string Cryptojs_AesDecrypt(string encryptedString, string passphrase)
{
// 检查参数是否为 null 或空字符串
if (string.IsNullOrEmpty(encryptedString))
{
throw new ArgumentException("加密字符串不能为空。", nameof(encryptedString));
} if (string.IsNullOrEmpty(passphrase))
{
throw new ArgumentException("口令不能为空。", nameof(passphrase));
} // 将 Base64 编码的字符串转换为字节数组
var encryptedBytes = Convert.FromBase64String(encryptedString); // 获取盐值和密文
var salt = encryptedBytes[8..16];
var cipherText = encryptedBytes[16..]; // 从口令和盐值派生密钥和 IV
const int iterations = 1; // CryptoJS 默认使用 1 次迭代
var passphraseBytes = Encoding.UTF8.GetBytes(passphrase);
DeriveKeyAndIv(passphraseBytes, salt, iterations, out var key, out var iv); // 创建 AES 解密器
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
aes.KeySize = 256;
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC; // 创建解密器
var decryptor = aes.CreateDecryptor(key, iv); // 解密密文
using var msDecrypt = new MemoryStream(cipherText);
using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
using var srDecrypt = new StreamReader(csDecrypt); // 从解密流中读取解密后的字节,并将它们放入一个字符串中。
return srDecrypt.ReadToEnd();
} /// <summary>
/// <para>使用C#编写等效的OpenSSL EVP_BytesToKey方法</para>
/// <para>从输入的口令和盐生成密钥和初始化向量(IV)</para>
/// </summary>
/// <param name="passphrase">口令字节数组</param>
/// <param name="salt">盐字节数组</param>
/// <param name="iterations">哈希迭代次数</param>
/// <param name="key">输出32字节密钥</param>
/// <param name="iv">输出16字节初始化向量(IV)</param>
private static void DeriveKeyAndIv(byte[] passphrase, byte[] salt, int iterations, out byte[] key, out byte[] iv)
{
// 用于保存哈希值
var hashList = new List<byte>(); // 计算第一个哈希值
var preHashLength = passphrase.Length + (salt?.Length ?? 0);
var preHash = new byte[preHashLength];
// 将口令复制到 preHash 数组
Buffer.BlockCopy(passphrase, 0, preHash, 0, passphrase.Length);
if (salt != null)
{
// 将盐值复制到 preHash 数组
Buffer.BlockCopy(salt, 0, preHash, passphrase.Length, salt.Length);
}
// 创建 MD5 哈希对象
var hash = MD5.Create();
// 计算哈希值
var currentHash = hash.ComputeHash(preHash);
// 迭代计算哈希值
for (var i = 1; i < iterations; i++)
{
currentHash = hash.ComputeHash(currentHash);
}
// 将第一个哈希值加入哈希值列表
hashList.AddRange(currentHash); #region 用于32字节密钥和16字节IV
// 如果哈希值不足48字节,则进行更多的哈希计算
while (hashList.Count < 48)
{
preHashLength = currentHash.Length + passphrase.Length + (salt?.Length ?? 0);
preHash = new byte[preHashLength]; // 将上一次的哈希值复制到 preHash 数组
Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
// 将口令复制到 preHash 数组
Buffer.BlockCopy(passphrase, 0, preHash, currentHash.Length, passphrase.Length);
if (salt != null)
{
// 将盐值复制到 preHash 数组
Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + passphrase.Length, salt.Length);
}
// 计算哈希值
currentHash = hash.ComputeHash(preHash);
// 迭代计算哈希值
for (var i = 1; i < iterations; i++)
{
currentHash = hash.ComputeHash(currentHash);
}
// 将计算得到的哈希值加入哈希值列表
hashList.AddRange(currentHash);
}
#endregion 用于32字节密钥和16字节IV #region 从哈希值列表中提取密钥和 IV
// 清除哈希对象
hash.Clear();
// 用于保存密钥的字节数组
key = new byte[32];
// 用于保存 IV 的字节数组
iv = new byte[16];
// 将前32个字节复制到密钥数组
hashList.CopyTo(0, key, 0, 32);
// 将后16个字节复制到 IV 数组
hashList.CopyTo(32, iv, 0, 16);
#endregion 从哈希值列表中提取密钥和 IV }
AES之CryptoJS加密与C#解密的更多相关文章
- 微信小程序aes前后端加密解密交互
aes前后端加密解密交互 小程序端 1. 首先引入aes.js /** * [description] CryptoJS v3.1.2 * [description] zhuangzhudada so ...
- iOS,一行代码进行RSA、DES 、AES、MD5加密、解密
本文为投稿文章,作者:Flying_Einstein(简书) 加密的Demo,欢迎下载 JAVA端的加密解密,读者可以看我同事的这篇文章:http://www.jianshu.com/p/98569e ...
- golang AES/ECB/PKCS5 加密解密 url-safe-base64
因为项目的需要用到golang的一种特殊的加密解密算法AES/ECB/PKCS5,但是算法并没有包含在标准库中,经过多次失败的尝试,终于解码成功,特此分享: /* 描述 : golang AES/EC ...
- python AES 双向对称加密解密
高级加密标准(Advanced Encryption Standard,AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准.这个标准用来替代原先的DES,已经被多方分 ...
- 使用java实现对称加密解密(AES),非对称加密解密(RSA)
对称加密:双方采用同样的秘钥进行加密和解密.特点是速度快,但是安全性没有非对称加密高 非对称加密:接收方生成的公有秘钥公布给发送方,发送方使用该公有秘钥加密之后,发送给接收方,然后接收方使用私有秘钥解 ...
- AES字节数组加密解密流程
AES类时微软MSDN中最常用的加密类,微软官网也有例子,参考链接:https://docs.microsoft.com/zh-cn/dotnet/api/system.security.crypto ...
- Java 前端加密传输后端解密以及验证码功能
目录(?)[-] 加密解密 1 前端js加密概述 2 前后端加密解密 21 引用的js加密库 22 js加密解密 23 Java端加密解密PKCS5Padding与js的Pkcs7一致 验证码 1 概 ...
- AES前后端加密
1.前端代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...
- 我的Android进阶之旅------>Android采用AES+RSA的加密机制对http请求进行加密
前言 未加密的抓包截图 加密之后的抓包截图 基本需求及概念 AES算法 AES基本原理及算法流程 AES算法流程 RSA算法 RSA算法基本原理及流程 RSA算法实现流程 AES与RSA相结合数据加密 ...
- Java使用Cipher类实现加密,包括DES,DES3,AES和RSA加密
一.先看一个简单加密,解密实现 1.1 加密 /** * content: 加密内容 * slatKey: 加密的盐,16位字符串 * vectorKey: 加密的向量,16位字符串 */ publi ...
随机推荐
- CH390使用注意事项
关于CH390使用注意事项 CH390替换DM90xx硬件注意事项 1.CH390L替换DM9000 AVDD33的对地电容建议1uF贴近芯片放置,42脚为主电源AVDD33需10uF并联0.1uF. ...
- RL · Exploration | 使用时序距离构造 intrinsic reward,鼓励 agent 探索
论文标题:Episodic Novelty Through Temporal Distance. ICLR 2025,8 8 6 5 poster. arxiv:https://arxiv.org/a ...
- 判断返回值长度(比如是否为空),执行后续步骤(if..else、len的用法)
爬基金数据,净值因涨跌不同,对应的元素路径也不会一样 比如当天是涨的时候,涨跌元素的class信息为"<span class="fix_dwjz bold ui-color ...
- Go工程选择开源分库分表中间件可用性测试
近期在寻找Go工程可以用的开源分库分表中间件,找了3个:ShardingSphere-Proxy,Kingshard,Gaea,下面给出测试过程和对比结果 ShardingSphere-Proxy h ...
- n8n 快速入门
今天,我将为大家介绍一个当前非常流行的可视化智能体搭建平台--n8n.n8n(发音为 "n-eight-n")是一个强大的自动化工具,它能够帮助您轻松地将任何具有API的应用程序与 ...
- 1.3K star!VisActor团队开源神器,3秒生成商业级图表,程序员直呼真香!
嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 项目速览 VChart 是VisActor团队推出的高性能可视化解决方案,GitHub斩获2 ...
- Java编程--String类和基本数据类型的相互转换
基本数据类型:byte.short.int.long.char.float.double.boolean 基本数据类型->String:利用String类提供的ValueOf(基本类型)方法转换 ...
- java设置权限过滤器--防止用户未登录访问某些页面
话不多说,上代码!!! package com.store.web.filter; import java.io.IOException; import javax.servlet.Filter; i ...
- servlet 作用域
作用域总结 HttpServletRequest:一次请求,请求响应之前有效 HttpSession:一次会话开始,浏览器不关闭或不超时之前有效 ServletContext:服务器启动开始,服务器停 ...
- 哨兵线性搜索算法浅析与Python,C#实践Demo
如题: 在数组A[]中搜索给定值foundNum,其中length是A[]的长度 常规线性搜索 1.令索引i初始值为0,按次序依次赋值到n-1; (a)如果A[i]==foundNum,返回当前i; ...