最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 使用Web Crypto API的端到端加密聊天

    正文概述 掘金(杭州程序员张张)   2021-01-30   494

    在传输或存储用户数据(尤其是私人对话)时,必须考虑采用加密技术来确保隐私。

    使用Web Crypto API的端到端加密聊天

    通过阅读本教程,您将了解如何仅使用JavaScript和Web Crypto API(一种本地浏览器API)在Web应用程序中对数据进行端到端加密。

    请注意,本教程非常基础,并且具有严格的教育意义,可能包含一些简化,不建议使用您自己的加密协议,如果没有在安全专家的帮助下正确使用,所使用的算法可能包含某些“陷阱”

    如果您碰巧迷路了,也可以在此GitHub仓库中找到完整的项目。

    什么是端到端加密?

    端到端加密是一种通信系统,其中唯一能够读取消息的人就是进行通信的人。没有任何窃听者可以访问解密对话所需的加密密钥,甚至是运行消息传递服务的公司也无法访问。

    什么是Web Crypto API?

    Web Cryptography API定义了一个低级接口,用于与用户代理管理或暴露的加密密钥材料进行交互。API本身对密钥存储的底层实现是不可知的,但提供了一组通用的接口,允许富Web应用执行诸如签名生成和验证、散列和验证、加密和解密等操作,而不需要访问原始密钥材料。

    基础知识

    在以下步骤中,我们将声明端到端加密所涉及的基本功能。您可以将每个文件复制到 lib 文件夹下的专用 .js 文件中。请注意,由于Web Crypto API的异步特性,它们都是异步函数。

    生成密钥对

    加密密钥对对于端到端加密至关重要。密钥对由公共密钥私有密钥组成。应用程序中的每个用户都应具有一个密钥对来保护其数据,其他用户可以使用公共组件,而密钥对的所有者只能访问私有组件。您将在下一部分中了解这些功能的作用。

    要生成密钥对,我们将使用 window.crypto.subtle.generateKey 方法,并使用具有 JWK格式的 window.crypto.subtle.exportKey 导出私钥和公钥。可以将其视为序列化密钥以在JavaScript之外使用的一种方法。

    generateKeyPair.js

    export default async () => {
      const keyPair = await window.crypto.subtle.generateKey(
        {
          name: "ECDH",
          namedCurve: "P-256",
        },
        true,
        ["deriveKey", "deriveBits"]
      );
    
      const publicKeyJwk = await window.crypto.subtle.exportKey(
        "jwk",
        keyPair.publicKey
      );
    
      const privateKeyJwk = await window.crypto.subtle.exportKey(
        "jwk",
        keyPair.privateKey
      );
    
      return { publicKeyJwk, privateKeyJwk };
    };
    

    此外,我选择了具有P-256椭圆曲线的ECDH算法,因为它得到了很好的支持,并且在安全性和性能之间达到了适当的平衡。随着新算法的推出,这种偏好会随着时间而改变。

    注意:导出私钥可能会导致安全问题,因此必须谨慎处理。本教程集成部分将介绍的让用户复制粘贴的做法,并不是一个很好的做法,只是出于教育目的。

    派生密钥

    我们将使用在最后一步中生成的密钥对来派生对称加密密钥,该密钥对数据进行加密和解密,并且对于任何两个通信用户都是唯一的。例如,用户A使用他们的私钥和用户B的公钥派生密钥,用户B使用他们的私钥和用户A的公钥派生相同的密钥。没有人可以在不访问至少一个用户私钥的情况下生成派生密匙,因此保证它们的安全非常重要。

    在上一步中,我们以JWK格式导出了密钥对。在推导出密钥之前,我们需要使用 window.crypto.subtle.importKey 将这些导入到原始状态。为了导出密钥,我们将使用 window.crypto.subtle.deriveKey

    deriveKey.js

    export default async (publicKeyJwk, privateKeyJwk) => {
      const publicKey = await window.crypto.subtle.importKey(
        "jwk",
        publicKeyJwk,
        {
          name: "ECDH",
          namedCurve: "P-256",
        },
        true,
        []
      );
    
      const privateKey = await window.crypto.subtle.importKey(
        "jwk",
        privateKeyJwk,
        {
          name: "ECDH",
          namedCurve: "P-256",
        },
        true,
        ["deriveKey", "deriveBits"]
      );
    
      return await window.crypto.subtle.deriveKey(
        { name: "ECDH", public: publicKey },
        privateKey,
        { name: "AES-GCM", length: 256 },
        true,
        ["encrypt", "decrypt"]
      );
    };
    

    在这种情况下,我选择AES-GCM算法是因为它具有已知的安全性/性能平衡和浏览器可用性。

    加密文本

    现在,我们可以使用派生密钥对文本进行加密,因此可以安全地传输文本。

    在加密之前,我们将文本编码为 Uint8Array,因为这就是加密功能所需要的。我们使用 window.crypto.subtle.encrypt 对该数组进行加密,然后将其 ArrayBuffer 输出返回给 Uint8Array,然后将其转换为字符串并将其编码为Base64。JavaScript使它有点复杂,但这只是将我们的加密数据转换为可传输文本的一种方式。

    encrypt.js

    export default async (messageJSON, derivedKey) => {
      try {
        const message = JSON.parse(messageJSON);
        const text = message.base64Data;
        const initializationVector = new Uint8Array(message.initializationVector).buffer;
    
        const string = atob(text);
        const uintArray = new Uint8Array(
          [...string].map((char) => char.charCodeAt(0))
        );
        const algorithm = {
          name: "AES-GCM",
          iv: initializationVector,
        };
        const decryptedData = await window.crypto.subtle.decrypt(
          algorithm,
          derivedKey,
          uintArray
        );
    
        return new TextDecoder().decode(decryptedData);
      } catch (e) {
        return `error decrypting message: ${e}`;
      }
    };
    

    如您所见,AES-GCM算法参数包括一个初始化向量(iv)。对于每一个加密操作,可以是随机的,但绝对必须是唯一的,以保证加密的强度。它包含在信息中,所以它可以用于解密过程,这是下一步。另外,虽然不太可能达到这个数字,但你应该在2³²次使用后丢弃钥匙,因为此时随机IV会重复。

    解密文字

    现在我们可以使用派生密钥来解密我们收到的任何加密文本,做的事情与加密步骤正好相反。

    在解密之前,我们检索初始化向量,将字符串从Base64转换回来,变成一个 Uint8Array,并使用相同的算法定义进行解密。之后,我们对 ArrayBuffer 进行解码,并返回人类可读的字符串。

    decrypt.js

    export default async (messageJSON, derivedKey) => {
      try {
        const message = JSON.parse(messageJSON);
        const text = message.base64Data;
        const initializationVector = new Uint8Array(message.initializationVector).buffer;
    
        const string = atob(text);
        const uintArray = new Uint8Array(
          [...string].map((char) => char.charCodeAt(0))
        );
        const algorithm = {
          name: "AES-GCM",
          iv: initializationVector,
        };
        const decryptedData = await window.crypto.subtle.decrypt(
          algorithm,
          derivedKey,
          uintArray
        );
    
        return new TextDecoder().decode(decryptedData);
      } catch (e) {
        return `error decrypting message: ${e}`;
      }
    };
    

    也有可能由于使用了错误的派生密钥或初始化向量,导致这个解密过程失败,这意味着用户没有正确的密钥对来解密他们收到的文本。在这种情况下,我们会返回一个错误信息。

    集成到您的聊天应用程序中

    而这就是所有需要的加密工作!在下面的章节中,我将解释我是如何使用我们在上面实现的方法来对一个使用Stream Chat强大的React聊天组件构建的聊天应用程序进行端到端加密的。

    克隆项目

    将encrypted-web-chat仓库克隆到本地文件夹中,安装依赖项并运行它。

    $ git clone https://github.com/getstream/encrypted-web-chat
    $ cd encrypted-web-chat/
    $ yarn install
    $ yarn start
    

    之后,应打开浏览器选项卡。但是首先,我们需要使用我们自己的Stream Chat API密钥配置项目。

    配置Stream Chat Dashboard

    在GetStream.io上创建帐户,创建一个应用程序,然后选择开发而不是生产。

    使用Web Crypto API的端到端加密聊天

    为简化起见,让我们同时禁用身份验证检查和权限检查。确保点击保存。当您的应用程序在生产中,您应该保持这些启用,并有一个后端为用户提供令牌。

    使用Web Crypto API的端到端加密聊天

    请注意Stream凭据,因为下一步将使用它们在应用程序中初始化聊天客户端。由于我们禁用了身份验证和权限,因此我们现在仅真正需要密钥。不过,在未来,你还是会在你的后台使用密钥来实现认证,为Stream Chat发行用户令牌,这样你的聊天应用就可以有适当的访问控制。

    使用Web Crypto API的端到端加密聊天

    如您所见,我已编辑密钥。最好保留这些凭据的安全性。

    更改凭证

    src/lib/chatClient.js 中,用您的密钥更改密钥。我们将使用此对象进行API调用并配置聊天组件。

    chatClient.js

    import { StreamChat } from "stream-chat";
    
    export default new StreamChat("[api_key]");
    

    在此之后,您应该能够测试应用程序。在以下步骤中,您将了解我们定义的函数适用于何处。

    设置用户

    src/lib/setUser.js 中,我们定义了设置聊天客户端的用户并使用给定的公钥对更新的函数。发送公共密钥对于其他用户来说是必要的,以便获得与我们的用户进行加密和解密通信所需的密钥。

    setUser.js

    import chatClient from "./chatClient";
    
    export default async (id, keyPair) => {
      const response = await chatClient.setUser(
        {
          id,
          name: id,
          image: `https://getstream.io/random_png/?id=cool-recipe-9&name=${id}`,
        },
        chatClient.devToken(id)
      );
    
      if (
        response.me?.publicKeyJwk &&
        response.me.publicKeyJwk != JSON.stringify(keyPair.publicKeyJwk)
      ) {
        await chatClient.disconnect();
        throw "This user id already exists with a different key pair. Choose a new user id or paste the correct key pair.";
      }
    
      await chatClient.upsertUsers([
        { id, publicKeyJwk: JSON.stringify(keyPair.publicKeyJwk) },
      ]);
    };
    

    在此函数中,我们导入上一版中定义的 chatClient。它需要一个用户ID和一个密钥对,然后调用 chatClient.setUser 来设置用户。此后,它将检查该用户是否已经具有公共密钥,并且是否与给定密钥对中的公共密钥匹配。如果公钥匹配或不存在,我们将使用给定的公钥更新该用户;如果不是,我们断开连接并显示错误。

    发件人组件

    src/components/Sender.js 中,我们定义了第一屏,在这里选择我们的用户id,可以使用我们在 generateKey.js 中描述的函数生成一个密钥对,如果这是一个现有的用户,则可以粘贴用户创建时生成的密钥对。

    使用Web Crypto API的端到端加密聊天

    收件人组成

    src/components/Recipient.js 中,我们定义了第二个屏幕,在这里我们选择要与之通信的用户的id。该组件将使用 chatClient.queryUsers 获取该用户。该调用的结果将包含用户的公钥,我们将用它来导出加密/解密密钥。

    使用Web Crypto API的端到端加密聊天

    KeyDeriver组件

    src/components/KeyDeriver.js 中,我们定义了第三个屏幕,其中密钥是使用我们在 deriveKey.js 中实现的方法派生的,该方法使用发送方(us)的私钥和接收方的公钥。该组件只是一个被动加载屏幕,因为所需的信息已在前两个屏幕中收集。但是如果密钥有问题,它会显示一个错误。

    EncryptedMessage组件

    src/components/EncryptedMessage.js 中,我们自定义Stream Chat的Message组件,使用我们在 decrypt.js 中定义的方法对消息进行解密,同时提供加密数据和派生密钥。

    使用Web Crypto API的端到端加密聊天

    如果不对Message组件进行此自定义,它将显示如下:

    使用Web Crypto API的端到端加密聊天

    通过包装Stream Chat的 MessageSimple 组件并使用 useEffect 钩子来使用DEcrypt方法修改消息属性来进行自定义。

    EncryptedMessageInput组件

    src/components/EncryptedMessageInput.js 中,我们自定义Stream Chat的MessageInput组件,以便在发送之前使用我们在 encrypt.js 中定义的方法将写好的消息与原始文本一起加密。

    定制是通过包装Stream Chat的 MessageInputLarge 组件并将 overrideSubmitHandler prop设置为一个函数来完成的,该函数在发送到通道之前对文本进行加密。

    Chat组件

    最后,在 src/components/Chat.js 中,我们使用Stream Chat的组件和我们自定义的Message和EncryptedMessageInput组件构建整个聊天屏幕。

    Web Crypto API的后续步骤

    恭喜你!您刚刚学习了如何在Web应用程序中实现基本的端到端加密,重要的是要知道这是端对端加密的最基本形式。它缺乏一些额外的调整,可以让它在现实世界中更加弹性,比如随机化填充、数字签名和前向保密等等。此外,对于实际使用而言,获得应用程序安全专业人员的帮助也至关重要。


    原文:levelup.gitconnected.com
    作者:Matheus Cardoso


    起源地下载网 » 使用Web Crypto API的端到端加密聊天

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元