创建认证加密并使用 AES256 加密您的数据
提供完整可用的示例代码和对创建 AES256 认证加密所需内容的所有解释。
引言
本文的灵感来自于一个简单的事情:希望创建一个允许轻松、安全地存储数据的 WebAPI。
在线试用
在继续之前,您可以尝试一下并在 AuthEncryptViaAES256 - JSFiddle - Code Playground[^] 中查看代码。
我还附带了一个 zip 文件供您下载。或者,您可以在我的 Github 仓库中获取此项目的代码:GitHub - raddevus/Aes256ViaJS: 使用 CryptoJS 库通过 AES256 加密/解密数据(包括认证加密)[^]。
本文的其余部分将解释使用代码时实际发生的情况。
关于通过 JavaScript 实现的 AES256 算法
我的所有代码都只是实现了 AES256 算法,该算法在 https://code.google.com/archive/p/crypto-js/[^] 中有更详细的文档。
注意:我如何知道该库中提供的代码有效?因为我使用 C#、Java(和其他语言)实现了解密端,这些语言有自己的 AES256 加密库,并且这些库可以解密此 JavaScript 库加密的数据。
背景
我最近在 CodeProject 上开始了一系列与此想法相关的文章(为什么是 HTML5?为什么不呢?UI/UX 与存储挑战(第 1 部分,共 N 部分)[^])。
应用程序的轻松、安全、可靠的数据存储
基本观点是,您可以使用基于 Web 的技术来创建完整的应用程序,但仍然没有简单、安全、可靠的方法来真正保存数据,并使其在用户运行您的应用程序的任何地方都可用。
我发现如何使用 AES256 加密您的数据,并使用我简单的 LibreStore WebAPI 进行存储(关于 WebAPI 的更多内容即将推出)。
本文与其他文章的区别
然而,在阅读一本名为《真实世界密码学(Manning Publishers - 通过亚马逊)[^]》时,我偶然发现了一个叫做“认证加密”的东西(本文将详细介绍)。
认证加密是我们加密数据时的真正目标。我从未见过对此的解释。相反,大多数文章只是向您展示使用 AES256 加密您的明文数据的技术方面。但是,我从未见过一篇文章解释 IV(初始化向量)、MAC(消息认证码)的所有细节,以及什么可以是明文,什么必须加密或保密。
加密的两个挑战
对称加密中的两个主要挑战必须得到解释,在我看来,这两个挑战是:
- 知道自己做对了——你是否正确地实现了算法?你是否为每次加密生成了 IV?
- 什么必须保密,什么可以明文?IV 可以公开,还是必须保密?哪些项目必须保密?
一个额外的挑战
实际上还有一个技术挑战,我将在未来的文章中提及。
每种语言的 AES256 加密和解密方式略有不同:Java、JavaScript、C# 等中的 API 不同。
重点是,您可能使用 AES256 正确加密了数据,却发现您的客户端(用 C# 编写)有一个完全不同的加密 API,您必须学习才能解密它。
让我们开始吧。
认证加密
如果您要使用 AES256 等对称加密算法,那么必须确保加密数据没有被篡改。这就是认证加密所保证的。
高层视图
可以通过以下方式实现认证加密:
- 生成一个随机 IV(初始化向量)——
明文
发送给解密方 - 加密您的数据
- 对 IV 和加密数据生成 MAC(消息认证码)(使用密钥)
- 将 MAC 发送给解密方,以便他们可以测试相同的结果
确切过程有点令人困惑
创建认证加密的过程有点令人困惑,所以这里是我在正确加密用户数据时学到的东西。
您可以阅读更多关于认证加密 - 维基百科[^] 的信息。
不难,但信息难以查找
这并不难做到,但很难找到所有信息集中在一个地方,所以我决定写下这篇文章。
我们需要什么来进行认证加密?
认证加密需要一些东西:
- 明文数据 -(要加密的数据)
- 密码 - 在我们的例子中,我们将使用一个密码,它将由用户提供用于加密。关于强密码有很多要说的,所以我们在这里不再深入讨论。重点是解密方也将使用这个秘密。而且,是的,密码在被使用之前就已经经过 SHA256 哈希处理,所以不用担心。
- 随机 IV(初始化向量),将以明文形式传递——AES256 需要 16 字节的随机数据,并且每次加密数据时都应创建此 IV,即使密码或明文数据没有改变。
- MAC 密钥 - 用于生成 MAC(消息认证码)的密钥,这是 16-32 字节的数据,在您和将解密数据的接收者之间保密。
- MAC - 最终的消息认证码 - 使用 MAC 密钥(密钥)对 IV 和加密数据生成 - 此代码将以明文形式传递给接收者(解密方)。必须知道此 MAC,以便接收者可以将其与他们计算的值进行比较,以确保 IV 和加密数据未被篡改。
您将哪些项目提供给解密方?
《真实世界密码学》中主要混淆点之一是因为不知道哪些项目应该提供给解密方。
如果您不确定,您可能会觉得您正在暴露数据,而攻击者可能会利用这些数据来窃取或破坏加密数据。
这是您必须提供给解密方的确切列表,以及他们应该以何种格式(明文或加密)提供。
注意:当我提到某个东西是明文
时,意味着该元素可以暴露给任何人。例如 - 数据元素不必加密即可保护您的数据安全。
- 加密数据——显然,这是您不想暴露的秘密数据。注意:我们的
encryptedData
(加密后)将进行base64
编码(加密后),以便更容易通过 HTTP 传输。 - IV(初始化向量)——这是一个
明文
值,在我们的例子中,我们使用CryptoJS
库生成一个随机值。这将是 16 个随机字节(128 位),并在加密数据时用于随机化输出,使加密数据更具随机性且更安全。您提供给解密方的数据格式将是 2 字节的十六进制值,例如:137b276b3975612498e225657140c5a1
- MAC——这是
明文
消息认证码,它将由解密方用来验证您发送的 IV 和加密数据未被更改或损坏。它看起来像:57bfeffc96a32402266abe2035801e8f65cbd58fc9d88dceb591572acb1dca74
示例数据传输对象 (DTO)
如果您将所有这些数据加载到一个 DTO(JSON 对象)中,它可能看起来像这样:
{MAC:"57bfeffc96a32402266abe2035801e8f65cbd58fc9d88dceb591572acb1dca74",
IV:"137b276b3975612498e225657140c5a1",
enryptedData:"dGhpcyBpc24ndCByZWFsbHkgZW5jcnlwdGVkLCBidXQgaXMganVzdCBhbiBleGFtcGxl"
}
我们首先发送MAC
(为了方便人类阅读),因为解密方将获取IV
和加密数据,应用 MAC 密钥,并尝试再次生成 MAC。如果匹配,解密方就知道encryptedData
和IV
未被更改或损坏。
好的,最后,让我们通过一些代码来看一下。
示例代码
这其中有很多内容,所以我发布了一些示例代码,您可以通过 JSFiddle 尝试。
我们在这里关注的代码是加密代码,所以让我们看看 JavaScript(*main.js*)中的小 Encrypt()
函数。
这是整个 encrypt()
函数,因为它很简单,所以应该完全可以理解。
function encrypt(clearTextData, hashedPwd, useIV){
if (useIV){
let randomBytes = CryptoJS.lib.WordArray.random(128/8).toString();
iv = CryptoJS.enc.Hex.parse(randomBytes);
console.log(`iv : ${iv}`);
// old method (wrong) which used first 16 bytes of key (hashed pwd)
// CryptoJS.enc.Hex.parse(key);
message = CryptoJS.AES.encrypt(clearTextData,
CryptoJS.enc.Hex.parse(hashedPwd),{iv: iv});
console.log(`message.iv : ${message.iv}`);
console.log(`message: ${message}`);
console.log(`message.ciphertext ${message.ciphertext}`);
console.log(`message.salt ${message.salt}`);
}
else{
message = CryptoJS.AES.encrypt(clearTextData, hashedPwd );
}
return message.toString();
}
我将在此处解释一些细节。
encrypt() 参数
clearTextData
- 显然,只是您希望加密以进行保护的明文。hashedPwd
- Web 客户端调用一个方法,该方法接受用户的密码并对其进行哈希处理(SHA256
)。这就是哈希值。useIV
- 我希望能够向用户展示用户决定不生成 IV 时的差异。CryptoJS 算法会为您生成一个 IV。它存储在调用Crypto.AES.encrypt()
后返回的消息对象中。
您可以看到,我还console.log()
了调用 Crypto.AES.encrypt()
方法后返回的message
对象中的一些属性。
加密成功后
成功加密后,message 对象包含加密数据。调用message.toString()
会以base64 编码的字节形式返回加密数据。这就是您在我的示例中尝试时在屏幕上看到的输出。这使得通过 HTTP 传输字节变得非常容易。
解密方会做什么?
了解解密方将如何处理这些数据很重要。
解密方将
- 提供(商定的)秘密 MAC 密钥
- 使用秘密 MAC 密钥对 IV 和
EncryptedData
使用 Hmac 生成 MAC。 - 确定他们生成的 MAC 是否与您发送给他们的 MAC(明文)匹配。
如果 MAC 不匹配?
如果 MAC 与发送给解密方的 MAC 不匹配,那么解密方将知道以下任一情况为真:
- 加密数据已被更改或损坏。
- IV 已被更改或损坏。
- IV 和加密数据都已损坏。
这将允许接收者知道数据可能已被泄露,并且他们应该知道数据可能不安全。
真正的认证加密
在某些情况下,攻击者可能会损坏数据,生成新的 IV 并尝试将其作为解密方发送给您。由于 MAC 只会与原始加密字节和关联的 IV 匹配,因此您将免受此类情况的侵害。这就是认证加密。
由于我们生成了 MAC,因此我们知道我们免受此问题的影响,并且我们的加密数据是安全的。
现在我将总结一下,指出一些有趣的项目。
指出一些有趣的项目
每次加密时都生成 IV
每次用户点击加密按钮时,我们都会生成一个新的随机 IV。这确保了数据的随机性和更高的安全性。但是,这也意味着您必须与解密方共享新的 IV。
这是我们用于此目的的两行代码:
let randomBytes = CryptoJS.lib.WordArray.random(128/8).toString();
iv = CryptoJS.enc.Hex.parse(randomBytes);
这意味着,即使您保持密码和明文数据不变,输出的加密文本(base64 编码)每次都会改变。这都是数据随机化以确保安全的一部分。
观看演示
观看演示:
- 输入密码
- 输入一些明文数据(待加密)
- 只需连续点击加密按钮,您就会看到每次输出的加密数据都会发生变化。
加密数据
我们使用以下代码通过密码加密数据。
CryptoJS
是我们用于实现 AES256 的库。
注意:您可以在上面的 JSFiddle 标签中看到资源(来自 CDN)。
message = CryptoJS.AES.encrypt(clearTextData,
CryptoJS.enc.Hex.parse(hashedPwd),{iv: iv});
我们调用 encrypt()
函数并传入:
clearTextData
- 我们要加密的原始消息hashedPwd
- 我们的密码已进行 SHA256 哈希处理以确保安全,现在我们将其解析为hex
字节iv
对象 - 一个包含名为iv
的参数和我们随机生成的iv
字节的对象
就这样 & RFC
就是这样!请查看并发布您对文章中任何内容的疑问或评论。
我真心希望本文能帮助大家了解 AES256 对称加密,因为它是一个非常有价值的系统,似乎得到的关注比我预期的要少。此外,我还没有见过所有这些信息集中在一个地方,我希望您觉得它很有价值。
我曾用它来加密浏览器客户端、ElectronJS 客户端等数据,并将其发布到我的 WebAPI。之后,我使用我的 Android 应用(Kotlin 应用)和其他 Web 客户端解密了数据。
历史
- 2022 年 6 月 30 日:首次发布