tl; dr:nodejs crypto应提供对
ChaCha20-Poly1305
的访问在可预见的将来足够强大。
密码学很难。 好密码学甚至更难。值得庆幸的是,大多数现代后端服务都为现代开发人员和Nodejs变得更加容易提供了许多手段。
当我开始潜入nodejs时,试图在nodejs中找到良好的加密指南是 hard 。有几个博客,一些帖子,但没有什么具体的,没有什么可以提供有关某些方法比其他方法更好的答案。因此,我决定阅读一些有关密码学的书籍,并阅读一些Nodejs文档。
基本加密使用nodejs
nodejs在其crypto
模块中提供了一些有用的加密实用程序,它允许开发人员使用本地安装的OpenSSL模块中可用的任何算法和方法,如果已安装,则可以在大多数系统上使用以下命令找到,
openssl list -cipher-algorithms
如果安装了OpenSSL,则应为您提供像这样的列表:
AES-128-CBC
AES-128-CBC-HMAC-SHA1
AES-128-CBC-HMAC-SHA256
AES-128-CFB
AES-128-CFB1
AES-128-CFB8
AES-128-CTR
AES-128-ECB
AES-128-OCB
AES-128-OFB
AES-128-XTS
AES-192-CBC
AES-192-CFB
AES-192-CFB1
AES-192-CFB8
AES-192-CTR
AES-192-ECB
AES-192-OCB
AES-192-OFB
AES-256-CBC
AES-256-CBC-HMAC-SHA1
AES-256-CBC-HMAC-SHA256
. . .
对于大多数人而言,不需要任何严重的加密安全性,而只是寻求通过加密某些值来确保应用程序的应用程序,aes-256-cbc
就足够了,对于消息身份验证的某些外观,使用aes-256-cbc-hmac-sha256
可以正常工作。因此,让我们看看Typescript中的外观。
AES-256-CBC
import * as crypto from 'crypto';
function splitEncryptedText( encryptedText: string ) {
return {
ivString: encryptedText.slice( 0, 32 ),
encryptedDataString: encryptedText.slice( 32 ),
}
}
export default class Security {
encoding: BufferEncoding = 'hex';
// process.env.CRYPTO_KEY should be a 32 BYTE key
key: string = process.env.CRYPTO_KEY;
encrypt( plaintext: string ) {
try {
const iv = crypto.randomBytes( 16 );
const cipher = crypto.createCipheriv( 'aes-256-cbc', this.key, iv );
const encrypted = Buffer.concat( [
cipher.update(
plaintext, 'utf-8'
),
cipher.final(),
] );
return iv.toString( this.encoding ) + encrypted.toString( this.encoding );
} catch (e) {
console.error( e );
}
};
decrypt( cipherText: string ) {
const {
encryptedDataString,
ivString,
} = splitEncryptedText( cipherText );
try {
const iv = Buffer.from( ivString, this.encoding );
const encryptedText = Buffer.from( encryptedDataString, this.encoding );
const decipher = crypto.createDecipheriv( 'aes-256-cbc', this.key, iv );
const decrypted = decipher.update( encryptedText );
return Buffer.concat( [ decrypted, decipher.final() ] ).toString();
} catch (e) {
console.error( e );
}
}
}
让我们遍历这里发生的事情。
首先,在encrypt
方法内,我们实例化了iv
初始化向量,它用作链中XOR方法的第一个来源(CBC代表密码加密数据的密码块链)。这应该始终是随机的,最好在密码上安全,但不需要秘密。
const iv = crypto.randomBytes( 16 );
然后,我们创建加密密码,指定算法,键和IV。
接下来,我们需要使用cipher.update()
方法对文本进行加密,然后调用cipher.final()
关闭密码并确保无法进行其他更改(与密码相互作用的任何进一步尝试会导致丢弃错误)。 P>
之后,我们将IV
和加密的字符串组合在一起,将它们都转换为指定的编码,我使用了hex
编码,但这并不重要,只要解密时使用了相同的编码。
说到解密,这只是加密的相反。我们将提供的密文将所提供的密文拆分为加密的文本和IV,将IV与Decipher一起使用并恢复原始文本。
正如我所说,这非常简单,将在大多数情况下工作。现在,应该提到的是,AES-CBC 使用上述aes-256-cbc-hmac-sha256
可能容易受到Padding Oracle Attack,的影响,并且与允许进行身份验证的消息中的下一个方法最相似,这意味着任何尝试的尝试干扰不应工作。
chacha20-poly1305
现在,对于主要事件,一种更健壮,现代且可以说是更安全的加密方法。应该注意的是,这两种方法并非严格可比性,普通的CBC并未对消息进行身份验证,因此与ChaCha20-Poly1305
有很大不同。话虽如此,实施它非常简单,只需要从上面的示例中进行几行更改。
import * as crypto from 'crypto';
function splitEncryptedText( encryptedText: string ) {
return {
encryptedDataString: encryptedText.slice( 56, -32 ),
ivString: encryptedText.slice( 0, 24 ),
assocDataString: encryptedText.slice( 24, 56 ),
tagString: encryptedText.slice( -32 ),
}
}
export default class Security {
encoding: BufferEncoding = 'hex';
// process.env.CRYPTO_KEY should be a 32 BYTE key
key: string = process.env.CRYPTO_KEY;
encrypt( plaintext: string ) {
try {
const iv = crypto.randomBytes( 12 );
const assocData = crypto.randomBytes( 16 );
const cipher = crypto.createCipheriv( 'chacha20-poly1305', this.key, iv, {
authTagLength: 16,
} );
cipher.setAAD( assocData, { plaintextLength: Buffer.byteLength( plaintext ) } );
const encrypted = Buffer.concat( [
cipher.update(
plaintext, 'utf-8'
),
cipher.final(),
] );
const tag = cipher.getAuthTag();
return iv.toString( this.encoding ) + assocData.toString( this.encoding ) + encrypted.toString( this.encoding ) + tag.toString( this.encoding );
} catch (e) {
console.error( e );
}
};
decrypt( cipherText: string ) {
const {
encryptedDataString,
ivString,
assocDataString,
tagString,
} = splitEncryptedText( cipherText );
try {
const iv = Buffer.from( ivString, this.encoding );
const encryptedText = Buffer.from( encryptedDataString, this.encoding );
const tag = Buffer.from( tagString, this.encoding );
const decipher = crypto.createDecipheriv( 'chacha20-poly1305', this.key, iv, { authTagLength: 16 } );
decipher.setAAD( Buffer.from( assocDataString, this.encoding ), { plaintextLength: encryptedDataString.length } );
decipher.setAuthTag( Buffer.from( tag ) );
const decrypted = decipher.update( encryptedText );
return Buffer.concat( [ decrypted, decipher.final() ] ).toString();
} catch (e) {
console.error( e );
}
}
}
CHACHA20-POLY1305的潜在问题
您可能会问,即使在hmac-sha256
的情况下,Chacha20-Poly1305是否比aes-256-cbc
好得多,为什么还要提及它们呢?好吧,这归结为简单的东西。 IV(nonce)大小,96位。
虽然这通常不是问题,但它 小于aes-cbc
方法,这意味着它在这方面不太安全。有一个修复程序,XChaCha20-Poly1305
引入了192位IV(NONCE)。不幸的是,到目前为止(30/04/2022),这还没有进入OpenSSL,因此还没有进入Nodejs Crypto模块。因此,就目前而言,我们一直坚持使用标准ChaCha20-Poly1305
,而无需尝试自己实施。