什么是 AES 对称加密?
AES(Advanced Encryption Standard,高级加密标准)是一种对称加密算法,用于加密和解密数据。对称加密意味着加密和解密操作使用相同的密钥。AES 被广泛应用于现代信息安全领域,尤其是在加密通信、文件保护和数据传输中。
AES 的基本工作原理:
-
分组加密:AES 是一个分组加密算法,意味着它一次加密固定大小的数据块。AES 的数据块大小为 128 位(16 字节),因此它每次处理 128 位的输入数据。
-
密钥长度:AES 支持三种密钥长度:
- 128 位密钥:加密和解密过程包含 10 轮(rounds)操作。
- 192 位密钥:加密和解密过程包含 12 轮操作。
- 256 位密钥:加密和解密过程包含 14 轮操作。
-
加密流程:AES 的加密过程包括以下主要步骤:
- 密钥扩展:将原始密钥扩展为一组子密钥。
- 初始轮(Initial Round):将数据块与扩展密钥进行异或操作。
- 主要轮(Main Rounds):包括替代字节(SubBytes)、行移位(ShiftRows)、列混合(MixColumns)和轮密钥加(AddRoundKey)等步骤。
- 最终轮(Final Round):与主要轮相同,但不包含列混合步骤。
AES 的应用场景:
-
数据加密与保护:AES 用于保护静态数据(如硬盘加密、数据库加密)以及传输中的数据(如通过 HTTPS 或 VPN 的加密传输)。
-
无线通信:许多无线通信协议(如 Wi-Fi、LTE 和 5G)使用 AES 进行数据加密,确保通信安全。
-
文件加密:常见的文件加密工具如 Windows 的 BitLocker、macOS 的 FileVault、以及第三方工具如 VeraCrypt 都使用 AES 算法加密存储在硬盘上的文件。
-
虚拟专用网络(VPN):在 VPN 协议中,AES 用于加密传输的数据,确保用户隐私和通信安全。
-
加密货币:许多加密货币(如比特币)的技术栈中使用 AES 作为一种数据加密方案。
-
数字证书和签名:AES 可用于加密传输的数字证书和电子签名,以确保信息的机密性和完整性。
AES 相比其他加密算法的优缺点:
优点:
-
高安全性:
- AES 目前被认为是非常安全的。尤其是 256 位密钥长度,能够提供足够强的安全性,抵抗各种现代的攻击。
- 由于其加密过程复杂且对暴力破解抵抗力强,AES 在加密算法中是业界的标准。
-
高效性:
- AES 具有较高的加密速度,适合在硬件和软件中实现。
- 它是设计为快速、低开销的,尤其适合嵌入式设备和需要高性能加密的环境。
-
广泛支持:
- AES 被多种硬件加速支持(如 AES-NI 指令集),这使得其在处理器上加速执行,尤其适用于现代计算机和移动设备。
- 许多操作系统和安全协议(如 TLS、IPSec)都支持 AES。
-
可配置性:
- AES 提供了三种不同的密钥长度(128、192 和 256 位),允许在安全性和性能之间做出权衡。
缺点:
-
密钥管理问题:
- 对称加密的最大问题之一是密钥管理。加密和解密使用相同的密钥,这就要求在安全的信道上传输和存储密钥。密钥泄露会导致整个加密系统的安全性崩溃。
- 当密钥数量增加时,管理和分发密钥的复杂性也会增加,特别是在大规模系统中。
-
缺乏非对称性:
- 相比于非对称加密(如 RSA 或 ECC),AES 没有直接支持身份验证和密钥交换的能力。在需要密钥交换和数字签名的应用场景下,通常需要与非对称加密结合使用。
-
密钥长度限制:
- 虽然 AES 256 被认为非常安全,但其密钥长度与未来计算能力的发展相比,是否仍然足够安全可能会存在疑虑。对于长期保护非常敏感的数据,有些应用可能会选择更加灵活的算法(如量子安全算法),以应对潜在的量子计算威胁。
与其他加密算法的比较:
-
与 DES(数据加密标准)相比:
- DES 已被认为不安全,因为其密钥长度(56 位)太短,容易受到暴力破解。AES 提供更长的密钥长度(最大 256 位),因此更难以破解。
-
与 RSA(非对称加密)相比:
- RSA 和其他非对称加密算法通常用于加密较小数据量,如加密会话密钥。AES 则是对称加密,适合大数据量的加密。RSA 比较适用于密钥交换和身份验证,而 AES 则用于实际数据的加密。
- RSA 的加密和解密速度较慢,而 AES 速度更快,尤其在处理大数据量时。AES 经常与 RSA 结合使用,在通信协议中通过 RSA 进行密钥交换,再使用 AES 加密实际数据。
-
与 3DES(Triple DES)相比:
- 3DES 是对 DES 算法的扩展,通过三次加密来增加安全性,但其加密速度较慢,而且相比 AES,3DES 的安全性更弱,特别是在处理大规模数据时。AES 比 3DES 更为高效和安全,尤其在现代硬件上支持加速时。
以下是我用ESA256位秘钥进行的对称加密简单代码:
var express = require('express');
var router = express.Router();
let { userModel } = require('../model/model')
let { redis } = require('../model/db')
let bcrypt = require('bcryptjs')
let crypto = require('crypto')
let jwt = require('jsonwebtoken')
const JWT_SECRET = 'wjl001123'
//使用哈希函数(如 SHA-256)将较短的密钥转换为 32 字节
const AES_SECRET = crypto.createHash('sha256').update('wjl20001123').digest(); //AES秘钥
/* GET home page. */
router.get('/', function (req, res, next) {
res.render('index', { title: 'Express' });
});
//AES加密函数
function aesEncrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', AES_SECRET, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted;
}
//AES解密
function aesDecrypt(text) {
const parts = text.split(':');
const iv = Buffer.from(parts.shift(), 'hex'); // 从字符串中提取 IV
const encryptedTextBuffer = Buffer.from(parts.join(':'), 'hex'); // 组合剩余部分并转换为 Buffer
const decipher = crypto.createDecipheriv('aes-256-cbc', AES_SECRET, iv); // 创建解密器
let decrypted = decipher.update(encryptedTextBuffer, 'binary', 'utf8'); // 解密数据
decrypted += decipher.final('utf8'); // 完成解密
return decrypted; // 返回解密后的文本
}
router.post('/adduser', async (req, res) => {
let { username, password } = req.body
const encrypted = aesEncrypt(password)
let obj = {
username,
password: encrypted
}
let userdata = await redis.lRange('user',0,-1)
let users = userdata.map(user=>JSON.parse(user))
if(users.some(user=>user.username===username)){
return res.send({
code: 409
})
}
await userModel.create(obj)
await redis.lPush('user', JSON.stringify(obj))
res.send({
code: 200
})
})
router.post('/login',async(req,res)=>{
let {username,password} = req.body
let user = await userModel.findOne({username})
if (user) {
const decrypted = aesDecrypt(user.password)
console.log(decrypted);
if (password === decrypted) {
const token = jwt.sign({ username }, JWT_SECRET, { expiresIn: '1h' });
await redis.setEx(`token:${username}`,3600,token)
res.send({
code:200
})
}else{
res.send({
code: 401
})
}
}else{
res.send({
code: 401
})
}
})
module.exports = router;
这里基本流程呢,我的理解是用户注册的时候,将密码传到后端,拿到密码之后进行加密,存进mongodb数据库的同时也存一份到redis中,后续一直未改密码的话可以访问redis数据库,提高性能。(对于redis的安装和基本使用,我的上一篇文章中有提到,有兴趣的小伙伴可以动动手阅读一下。这是链接:https://blog.csdn.net/m0_59365887/article/details/143592686?fromshare=blogdetail&sharetype=blogdetail&sharerId=143592686&sharerefer=PC&sharesource=m0_59365887&sharefrom=from_link)
既然有加密就要有解密,在用户登录进行密码验证的时候,同样将密码传到后端,后端将用户信息查出来并且利用解密函数将密码进行解密,同时对比解密之后的密码和前端传过来的密码是否一致,一至则成功登陆,否则失败。
对于加密函数:
//AES加密函数
function aesEncrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', AES_SECRET, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted;
}
1. const iv = crypto.randomBytes(16);
- 这里调用
crypto.randomBytes(16)
生成一个长度为 16 字节的随机字节数组,作为加密过程中的 初始化向量(IV)。在 AES-CBC 模式下,IV 是必需的,用来增强加密的安全性。 - IV(Initialization Vector) 是加密过程中的一个重要参数,它帮助打破加密算法中相同明文产生相同密文的问题,增加加密数据的不可预测性。
- 在此代码中,生成的 IV 是 16 字节(128 位),这符合 AES 的标准。
2. const cipher = crypto.createCipheriv('aes-256-cbc', AES_SECRET, iv);
crypto.createCipheriv
是 Node.js 中crypto
模块的一个方法,用于创建一个加密器(cipher),它接受以下参数:'aes-256-cbc'
:指定使用 AES-256-CBC 加密算法。这里的256
表示 AES 算法使用的是 256 位密钥。AES_SECRET
:这是用于加密的密钥。你需要确保这个密钥是 256 位(32 字节)的,符合 AES-256 的要求。iv
:在前面生成的初始化向量,将用于加密过程中的数据变换。
3. let encrypted = cipher.update(text, 'utf8', 'hex');
cipher.update
方法用于加密明文数据。它接受以下三个参数:text
:要加密的明文数据。'utf8'
:表示输入数据的编码格式是 UTF-8,通常是字符串类型。'hex'
:表示加密后的数据将以十六进制(hex)格式输出,这样可以方便地表示和存储加密后的二进制数据。
- 这行代码将明文数据
text
加密,并将其转换为十六进制表示。
4. encrypted += cipher.final('hex');
cipher.final('hex')
用来获取加密的最终部分。加密操作分为多个步骤,cipher.update
用于加密大部分数据,而cipher.final()
则是最后的填充操作。加密后的数据也以十六进制表示。- 这行代码将加密过程中的剩余数据加密并转换为十六进制格式,最后将结果连接到
encrypted
变量中。
5. return iv.toString('hex') + ':' + encrypted;
- 最终,函数返回一个包含两部分的字符串:
iv.toString('hex')
:将初始化向量(IV)转换为十六进制字符串。encrypted
:加密后的文本。
- 它们用冒号
:
分隔开,这样可以方便地将初始化向量和密文一起传递(在解密时,通常需要分开这两部分)。
为什么这样加密?
-
AES-256-CBC:
- AES-256 是一种对称加密算法,意味着加密和解密使用相同的密钥。在这种算法中,256 位密钥提供了非常高的安全性。
- CBC(Cipher Block Chaining) 是一种加密模式,它通过使用初始化向量(IV)来确保相同的明文每次加密时都会产生不同的密文。这样,即使加密相同的文本,每次加密的结果也是不同的,因此增强了安全性。
- CBC 模式的一个关键特性是它的 串联性,即每个加密块依赖于前一个块的加密结果,因此提高了抵抗某些攻击(如块重放攻击)的能力。
-
初始化向量(IV):
- IV 的引入是为了防止相同的明文在不同的加密过程中产生相同的密文。IV 是随机生成的,确保每次加密的结果不同,即使加密的是相同的文本。
- 将 IV 和密文一起返回,可以确保解密时能够使用相同的 IV 进行正确的解密。
-
十六进制输出:
- 将加密后的数据转换为十六进制字符串可以方便地表示和存储加密数据,因为加密后的二进制数据可能包含不可打印的字符(例如
\x00
)。十六进制编码使得密文以可打印字符的形式存储和传输。
- 将加密后的数据转换为十六进制字符串可以方便地表示和存储加密数据,因为加密后的二进制数据可能包含不可打印的字符(例如
-
为什么返回 IV 和密文一起:
- 在 AES-CBC 模式下,解密时需要知道初始化向量(IV),否则无法正确还原明文。因此,函数将 IV 和密文一起返回。常见的做法是将 IV 和密文通过分隔符(如冒号
:
)连接在一起,便于在解密时提取和使用。
- 在 AES-CBC 模式下,解密时需要知道初始化向量(IV),否则无法正确还原明文。因此,函数将 IV 和密文一起返回。常见的做法是将 IV 和密文通过分隔符(如冒号
总结一下:使用 AES-256-CBC 算法进行加密,通过随机生成一个初始化向量(IV)来增加加密的安全性,并将 IV 和密文以十六进制形式返回,确保解密时可以准确恢复原始明文。这种加密方式适用于需要高安全性保护的场景,特别是当数据需要防止重复加密导致密文相同时。
对于解密函数:
//AES解密
function aesDecrypt(text) {
const parts = text.split(':');
const iv = Buffer.from(parts.shift(), 'hex');
const encryptedTextBuffer = Buffer.from(parts.join(':'), 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', AES_SECRET, iv);
let decrypted = decipher.update(encryptedTextBuffer, 'binary', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
1. const parts = text.split(':');
- 这行代码将输入的加密文本
text
按照冒号:
分隔成两部分。假设输入的text
是由 IV 和密文组成的字符串(例如:"iv:encryptedText"
),这行代码将它分开。parts[0]
是初始化向量(IV)的十六进制表示。parts[1]
是加密后的密文(也是十六进制表示)。
这一步是为了从加密的文本中提取出 IV 和密文,方便后续的解密操作。
2. const iv = Buffer.from(parts.shift(), 'hex');
parts.shift()
会取出parts
数组的第一个元素,即初始化向量(IV)部分。它是一个十六进制字符串。Buffer.from(parts.shift(), 'hex')
将这个十六进制字符串转换成二进制Buffer
对象。Buffer
是 Node.js 中用来处理二进制数据的对象。IV 需要是二进制格式,因为加解密过程中会使用它作为一个二进制数据。- 这里的
iv
就是我们需要用于解密的初始化向量(IV)。
3. const encryptedTextBuffer = Buffer.from(parts.join(':'), 'hex');
parts.join(':')
将数组剩余的部分(密文)重新连接成一个字符串。如果加密过程中原文中有多个冒号(可能是分段的密文),join(':')
会将它们再次连接起来。Buffer.from(parts.join(':'), 'hex')
将这个连接起来的十六进制字符串转换为一个Buffer
,即密文的二进制数据。- 这一步的目的是将密文从十六进制字符串转换为解密过程中需要的二进制格式(
Buffer
)。
4. const decipher = crypto.createDecipheriv('aes-256-cbc', AES_SECRET, iv);
- 这行代码创建一个解密器(
decipher
),用于解密加密文本。它使用了与加密时相同的 AES 算法和模式:'aes-256-cbc'
:选择 AES-256-CBC 加密算法(256 位密钥和 CBC 模式)。AES_SECRET
:加密时使用的密钥。这个密钥必须与加密时的密钥完全一致。iv
:解密时需要使用与加密时相同的初始化向量 IV。
crypto.createDecipheriv
返回一个解密流(decipher),它会根据给定的密钥、IV 和算法来进行解密操作。
5. let decrypted = decipher.update(encryptedTextBuffer, 'binary', 'utf8');
decipher.update(encryptedTextBuffer, 'binary', 'utf8')
负责解密密文。它的参数含义如下:encryptedTextBuffer
:要解密的密文数据(以Buffer
格式提供)。'binary'
:表示输入的加密数据是二进制格式(即Buffer
)。'utf8'
:表示解密后的输出应该是以 UTF-8 字符编码格式的字符串。
- 该方法将密文解密成一部分的明文,并返回一个 UTF-8 编码的字符串。
decrypted
会存储这部分解密后的文本。
6. decrypted += decipher.final('utf8');
decipher.final('utf8')
用来获取解密过程中的剩余数据(如果有填充数据的话)。在 AES-CBC 模式下,密文会有一些填充(padding),final
会处理并返回填充数据的解密结果。- 这个方法必须调用,才能完成解密过程。
- 通过
+=
将这部分解密结果与之前的decrypted
连接起来,确保整个密文都被解密。
7. return decrypted;
- 最后,
decrypted
保存了完整的解密后文本,它被返回作为函数的结果。
为什么这样能解密?
-
AES-256-CBC 算法:
- 这段代码和加密代码使用相同的算法 (
aes-256-cbc
),包括相同的密钥AES_SECRET
和 IV。AES-256-CBC 是一种对称加密算法,意味着加密和解密使用相同的密钥。 - 解密过程是加密过程的逆操作,AES 算法本身是可逆的,给定相同的密钥和 IV,就可以将加密后的密文恢复为原始的明文。
- 这段代码和加密代码使用相同的算法 (
-
初始化向量(IV)的使用:
- 在 CBC 模式下,解密时需要用到加密时所用的相同初始化向量(IV)。由于在加密过程中随机生成并附加在密文前面,因此解密时需要从密文中提取出 IV。
- IV 的作用是确保同样的明文在每次加密时能生成不同的密文(即使使用相同的密钥)。在解密时,IV 必须与加密时使用的一致,才能正确还原明文。
-
填充处理:
- 在 AES-CBC 模式中,明文的长度必须是 16 字节的倍数。若明文长度不满足这一要求,AES 会使用填充来使其长度符合要求。
decipher.final()
负责处理这些填充数据并正确还原出原始的明文。
- 在 AES-CBC 模式中,明文的长度必须是 16 字节的倍数。若明文长度不满足这一要求,AES 会使用填充来使其长度符合要求。
-
十六进制编码:
- 加密时,密文被转换为十六进制字符串存储,以便于传输和存储。解密时需要将十六进制字符串转换回二进制数据(
Buffer
)才能正确解密。Buffer.from(text, 'hex')
将十六进制编码的密文恢复为原始的二进制数据。
- 加密时,密文被转换为十六进制字符串存储,以便于传输和存储。解密时需要将十六进制字符串转换回二进制数据(
总结一下:首先从加密的文本中提取初始化向量(IV)和密文;然后使用与加密时相同的密钥和 IV 创建解密器;最后,调用解密器解密密文并返回原始的明文。这一过程保证了加密和解密的一致性,只要密钥和 IV 相同,就能够正确地还原出明文。
最后呢,再次强调一个注意点:
//使用哈希函数(如 SHA-256)将较短的密钥转换为 32 字节
const AES_SECRET = crypto.createHash('sha256').update('wjl20001123').digest(); //AES秘钥
秘钥要求是32字节的,我们可以用哈希函数将我们自己设置的较短的秘钥自动转化成32字节的秘钥,否则报错 。
digest()
方法是哈希计算的最终步骤。它会返回计算后的哈希值。默认情况下,.digest()
返回一个 Buffer
对象,它包含了 32 字节的哈希值(对应 SHA-256 输出的 256 位数据)