浏览器第1部分中的端到端加密
#javascript #网络开发人员 #typescript #crypto

是一个活着的时间。我们生活在AI上升的时代,每周发生巨大的数据泄露事件,而勒索软件是每日威胁。

每个服务和应用都要求我们提供大量的个人信息,几个月后,这些信息通常可以在Darknet中找到。数据保护是这里的关键。

我想几乎每个人都听说过例如PGP和加密。但是说实话,PGP有点奇怪,而不是普通用户。

因此,我们有RSA,AES(以各种形式)和诸如Chachapoly之类的较新的算法。因此,我们有保护数据的能力。但是为什么使用此应用程序很少?

从一端到另一端

因此,让我们介绍称为端到端加密(E2EE)的奇怪的东西。

如果我们看一下wikipedia,它描述了e2ee:

端到端加密(E2EE)是一个私人通信系统,只有交流用户才能参与。因此,没有人,包括通信系统提供商,电信提供商,互联网提供商或恶意演员,都无法访问Converse

的加密密钥

因此,这是一个广泛的定义。我查看了许多宣传E2EE并相信我的应用程序,公司对E2EE的真实定义有很大的定义。

对于像我这样的某些人来说,如果您对数据进行加密,以便只有对方才能解密并看到它。

对于其他人来说,SSL/TLS也是E2EE。 ðÖ

网络加密API

,但假设你像我一样。并需要真正的加密。那里有很多libs可以为您提供帮助。我们为几乎所有语言都有加密库:PHP,Ruby,Python,C ++等。

如果我们在网络中,我们会面临挑战。如果我们的PHP后端正在加密数据,我们仍然将其发送到服务器。该服务器可能会受到损害(现在或以后)。以及我们如何管理所有钥匙?

要真正安全,我们应该只依靠客户端加密。所有数据都在设备上加密并将加密加密到服务器。没有中间人!

我们甚至为此提供了官方的浏览器API。 Web Crypto API

,不要害怕加密。这并不难!我保证!

对称加密

我们可以使用不同的系统。一个是Symmetric-key Encryption

这很容易理解。您和您的好友知道的共享钥匙。然后您使用该键加密消息。然后,您的好友可以用密钥解密消息并阅读。

AES-GCM

一个非常普遍的标准是AES。它具有不同的模式,并且都有优点和缺点,但是我们不会在这里详细介绍。

在此示例中,我们将重点关注AES-GCM。因为对于我们的用例,它将是最安全的。

我们必须采取的步骤很简单。

  1. 生成AES键
  2. 加密我们的数据
  3. ???
  4. 安全!!

让我们从第一步开始。关键一代:
(为了理智,我们将写一些辅助功能)

async generateAESKey (): Promise<CryptoKey> {
        return await window.crypto.subtle.generateKey(
            {
                name: 'AES-GCM',
                length: 256
            },
            true,
            ['encrypt', 'decrypt']
        )
    }

查看generateKey的文档以获取更多信息。

,但简而言之,我们说的是我们正在使用AES-GCM,我们可以提取键,并且键将用于加密和解密。

然后我们需要加密数据。

export interface EcryptedData {
    iv: Uint8Array
    encrypted: ArrayBuffer
}

async encrypt (data: string, aesKey: CryptoKey): Promise<EcryptedData> {
        const encodedData = new TextEncoder().encode(data)
        const iv = window.crypto.getRandomValues(new Uint8Array(128))
        // Encrypt with AES key
        const encryptedData = await window.crypto.subtle.encrypt(
            { name: 'AES-GCM', iv: iv },
            aesKey,
            encodedData
        )

        return {
            iv: iv,
            encrypted: encryptedData
        }
    }

好吧,我们这里还有更多的事情。
首先,我们假设您的数据是字符串之类的消息。 NE需要将其转换为适当的ArrayBuffer,这是加密所需的。我们可以将TextEncoder使用。

然后,我们需要一个IV(初始化向量)。重要的是要注意,IV应该始终是随机的!

为了帮助我们真正的随机性(在计算机系统中很难),我们具有getRandomValues函数。

然后,我们将我们的aesKeyencodedData传递给函数,而crypto.subtle.encrypt将返回加密的数据。

请注意,我们需要存储加密的数据和IV!

const aesKey = await generateAESKey()
const wallet = await encrypt('Hello World', aesKey)

// wallet.encrypted => 🔒
// wallet.iv => [150, 142, 234, 218, 156, 112,...]

甜!现在我们有military-grade encryption

但是,我们还需要解密我们的信息。
但首先,让我们考虑如何存储加密数据。就像我之前说过的,在网络加密API的世界中,我们将主要使用阵列缓冲区。但是,存储阵列缓冲液可能不是最优雅的解决方案。

有多种方法可以做到这一点,但是为简单起见,我们只是将arraybuffer转换为base64。

要解密我们的秘密消息,我们需要数据,AES密钥和IV。

async decrypt (data: string, key: CryptoKey, iv: string): Promise<string> {
        const ivBytes = base64ToArrayBuffer(iv)
        const dataBytes = base64ToArrayBuffer(data)

        const decryptedData = await window.crypto.subtle.decrypt(
            { name: 'AES-GCM', iv: ivBytes },
            key,
            dataBytes
        )
        return new TextDecoder().decode(decryptedData)
    }

再次,我们将从crypto.subtle.decrypt中获得一个数组缓冲区

const aesKey = await generateAESKey()
const wallet = await encrypt('Hello World', aesKey)

// wallet.encrypted => 🔒
// wallet.iv => [150, 142, 234, 218, 156, 112,...]

const message = await decrypt(wallet.encrypted, aesKey, wallet.iv)

// message => 'Hello World'

我们完成了!我们现在在应用程序中已加密。
但是,在实际应用中,除了加密过程外,我们还有其他疼痛点。

  • 我们如何存储AES密钥?
  • 我们希望用户定义密钥?
  • 使用密码?

PBKDF2

常见用例是使用用户密码加密某些内容。因此,我们需要一种从字符串(密码)生成坚固的加密密钥的方法。 PBKDF2进行救援!

使用基于密码的键推导,我们可以从低熵输入(密码)生成安全的高熵键。

async getKeyFromPassword (password: string): Promise<CryptoKey> {
        const encoder = new TextEncoder()
        return await window.crypto.subtle.importKey(
            'raw',
            encoder.encode(password),
            'PBKDF2',
            false,
            ['deriveBits', 'deriveKey']
        )
    }

因此,我们使用crypto.subtle.importKey来从密码中获取一个密码。重要的是,我们可以deriveBitsderiveKey

我们不能直接将Cryptokey用于AES。为此,我们需要得出密钥。

 async getAESKeyFromPBKDF (
        key: CryptoKey,
        salt: BufferSource
    ): Promise<CryptoKey> {
        return await window.crypto.subtle.deriveKey(
            {
                name: 'PBKDF2',
                salt,
                iterations: 100000,
                hash: 'SHA-256'
            },
            key,
            { name: 'AES-GCM', length: 256 },
            true,
            ['encrypt', 'decrypt']
        )
    }

现在我们可以使用用户密码生成AES密钥。

const password = 'Please-use-proper-high-entropy-passphrases!'
const PBKDFSecret = await getKeyFromPassword(password)
const aesKey = await getAESKeyFromPBKDF(PBKDFSecret, salt)
const wallet = await encrypt('Hello World', aesKey)

// wallet.encrypted => 🔒
// wallet.iv => [150, 142, 234, 218, 156, 112,...]

const message = await decrypt(wallet.encrypted, aesKey, wallet.iv)

// message => 'Hello World'

我们又完成了。恭喜。您有加密!
在第2部分中,我们将查看与RSA的非对称密钥加密以及有关存储秘密,钥匙以及我在与客户端端到端加密建立whistleblowing software时学到的知识的一些最佳实践。