node中的crypto模块指南
加密操作可能很棘手,以至于付费的加密服务公司的存在只是为了确保在代码库中正确实现加密操作。好消息是,只需学习一些知识,我们就可以使用 Node
的内置加密模块免费进行适当的加密。
在本指南中,我们将探讨如何使用 Node
的内置加密模块正确执行(对称)加密/解密操作,以保护应用程序的数据。
首先,我们需要了解对称加密的概念。
对称加密
当人们谈论“加密”时,他们往往指的是对称加密,这对于将文本加密为随机字符串非常有用。与此相关的一个常见场景是对服务器上的用户数据进行加密,以便将其“静态加密”存储在数据库中。
通俗地说,对称加密就是获取要加密的文本(称为明文),并使用带有加密算法的密钥来输出加密文本(称为密文)。该操作是可逆的,因此解密是我们可以使用与明文相同的密钥。
看起来很容易吧?
但是当真正需要实现对称加密时,开发人员经常会犯错误,因为有很多东西需要理解:
- 编码格式:数据可以通过多种方式进行编码/解码,例如
base64
、hex
等。当从一种格式转换为另一种格式时,这些不同的表示方式常常使开发人员感到困惑。 - 算法和配置:有许多加密算法需要考虑,例如
aes-256-gcm
或aes-256-cbc
,每种算法都有自己的要求。 - 随机性:加密过程中使用的密钥应随机生成,以确保高熵。很多时候,开发人员认为他们正在生成足够随机的密钥,但事实并非如此。
- 复杂性:
Node.js
中的加密涉及几个步骤,这些步骤对开发人员来说并不总是直观的,而且有些概念一开始确实令人费解。以初始化向量(IV
)的概念为例;刚接触密码学的开发人员经常在加密过程中重复使用这些内容,这是一个很大的禁忌。
无论如何,在本文中并不会讨论上述细微差别,但更多地关注我们如何通过示例使用 Node.js
正确执行加密。
解释如何正确执行加密的最佳方法是演示使用aes-256-gcm
算法的正确实现。让我们从加密开始。
加密
const crypto = require('crypto');const encryptSymmetric = (key, plaintext) => {const iv = crypto.randomBytes(12).toString('base64');const cipher = crypto.createCipheriv("aes-256-gcm", Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'));let ciphertext = cipher.update(plaintext, 'utf8', 'base64');ciphertext += cipher.final('base64');const tag = cipher.getAuthTag()return { ciphertext, tag }
}const plaintext = "encrypt me";
const key = crypto.randomBytes(32).toString('base64');const { ciphertext, iv, tag } = encryptSymmetric(key, plaintext);
要执行加密,我们需要两项:
plaintext
:要加密的文本。key
:256 位加密密钥。
我们从加密中得到以下输出:
ciphertext
:加密文本。iv
:一个 96 位初始化向量,用于提供加密的初始状态,并允许在未来的加密操作中以不同iv
重复使用相同key
。tag
:在加密过程中生成的一段数据,用于帮助验证加密文本在稍后的解密过程中没有被篡改。
让我们看一下代码:
const plaintext = "encrypt me";
const key = crypto.randomBytes(32).toString('base64');
这里我们定义执行加密所需的输入变量。除了要加密的文本之外,生成随机数以通过更高的熵key
确保安全性也很重要;我更喜欢使用内置crypto.randomBytes()
来生成。我还将所有内容转换为base64
格式,因为它在概念上易于存储。
接下来我们来剖析一下加密函数:
const encryptSymmetric = (key, plaintext) => {// 创建随机初始化向量const iv = crypto.randomBytes(12).toString('base64');// 创建一个密码对象const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);// 使用明文更新 cipher 对象以进行加密let ciphertext = cipher.update(plaintext, 'utf8', 'base64');// 完成加密过程ciphertext += cipher.final('base64');// 检索加密的身份验证标签const tag = cipher.getAuthTag();return { ciphertext, iv, tag };
}
在这里,加密函数在使用aes-256-gcm
(可以将其视为一种加密算法)算法、key
和iv``crypto.createCipheriv
初始化期间创建一个新的密码。下一部分加载要加密的文本plaintext
并执行加密并检索身份验证标记,可以使用该标记来检查ciphertext
解密过程中文本未被篡改。
关于加密,我们应该了解的最后一件事是如何处理数据。在加密用户数据并静态存储它的情况下,需要将加密的数据ciphertext
、iv
、 和tag
存储在数据库中,并将密钥安全地存储在其他地方,例如服务器上的环境变量。当我们想要检索数据时,可以从数据库中查询并使用密钥对其进行解密。
说到这里,让我们深入解密。
解密
const decryptSymmetric = (key, ciphertext, iv, tag) => {const decipher = crypto.createDecipheriv("aes-256-gcm", Buffer.from(key, 'base64'),Buffer.from(iv, 'base64'));decipher.setAuthTag(Buffer.from(tag, 'base64'));let plaintext = decipher.update(ciphertext, 'base64', 'utf8');plaintext += decipher.final('utf8');return plaintext;
}const plaintext = decryptSymmetric(key, ciphertext, iv, tag);
要执行解密,我们需要四项:
key
:用于加密原始文本的256位加密密钥。ciphertext
:要解密的密文。iv
:加密期间使用的 96 位初始化向量。tag
:加密时生成的标签。
我们从加密中得到以下输出:
plaintext
:我们加密的原始文本。
让我们看一下解密函数:
const decryptSymmetric = (key, ciphertext, iv, tag) => {// 创建一个解密对象const decipher = crypto.createDecipheriv("aes-256-gcm", Buffer.from(key, 'base64'),Buffer.from(iv, 'base64'));// 设置解密对象 decipher 的认证标签decipher.setAuthTag(Buffer.from(tag, 'base64'));// 使用 Base64 编码的密文更新解密对象let plaintext = decipher.update(ciphertext, 'base64', 'utf8');// 完成解密plaintext += decipher.final('utf8');return plaintext;
}
这里,解密函数在用aes-256-gcm
算法进行crypto.createDecipheriv
初始化时创建了一个解密对象,key
、iv
是之前创建的。接下来设置身份验证标签,用于检查是否被ciphertext
篡改,最后我们执行解密以获得原始文本。
最后,我们的数据的安全程度取决于用于加密数据的密钥。因此,强烈建议安全地存储加密密钥,并在应用程序中将它们作为环境变量进行访问。