Sign inGet started

加密频道

名称以 private-encrypted- 开头的频道是端到端加密的。你的服务器在发布前加密每条载荷,经授权的浏览器客户端使用从你的授权端点获取的密钥进行解密。Realtime 边缘节点和网络中间方只能看到密文。
生成并存储一个 32 字节的主密钥。主密钥不会出现在 Realtime API 请求中,频道名称前缀即可启用该功能。Bird 无法恢复丢失的密钥,用该密钥加密的载荷在你替换密钥后仍然不可读。
加密频道使用与私有频道相同的端点和签名。授权响应还会以 shared_secret 的形式包含该频道的派生解密密钥。拒绝订阅可以阻止该客户端获取密钥。

生成主密钥

生成 32 个随机字节,将其编码为 base64,并像保存 app secret 一样存储该值:
代码示例
openssl rand -base64 32
将其作为 realtime 配置的一部分传给你的服务器 SDK,与 app key 和 secret 放在一起。

发布加密事件

服务器 SDK 检测到频道前缀后,从主密钥派生频道密钥,并在本地加密 JSON 载荷。发布请求包含加密后的信封。
import { BirdClient } from "@messagebird/sdk";

const bird = new BirdClient({
  apiKey: process.env.BIRD_API_KEY,
  realtime: {
    key: process.env.BIRD_REALTIME_KEY,
    secret: process.env.BIRD_REALTIME_SECRET,
    encryptionMasterKey: process.env.BIRD_REALTIME_MASTER_KEY,
  },
});

await bird.realtime.publish("rap_01krdgeqcxet5s7t44vh8rt9mg", {
  event: "order.updated",
  channels: ["private-encrypted-orders"],
  data: { order_id: "ord_123", status: "shipped" },
});
一次发布中,加密频道必须是唯一的频道。每个加密频道派生不同的密钥,因此其他频道无法解密同一个加密载荷。SDK 会在本地拒绝这种扇出操作,API 在收到此类请求时返回 E23000。要向多个加密频道发布,请使用批量发布,每个事件对应一个频道。

从你的授权端点返回共享密钥

你的授权端点以与审批私有订阅相同的方式审批加密订阅。使用 SDK 的 authorizeChannel 辅助方法,当频道名称带有加密前缀时,响应会自动包含 shared_secret
app.post("/bird/auth", async (req, res) => {
  const { connection_id, channel_name } = req.body;

  const user = getUserFromSession(req);
  if (!user || !mayJoin(user, channel_name)) return res.sendStatus(403);

  res.json(
    await bird.realtime.authorizeChannel({
      connectionId: connection_id,
      channelName: channel_name,
    }),
  );
});
SDK 为每个频道派生单独的 shared_secret。因此,对 private-encrypted-orders 的授权无法解密 private-encrypted-invoices。密钥通过你的授权响应传递,不会包含在发送到边缘节点的订阅帧中。

在浏览器中订阅并解密

加密模块使用单独的 @messagebird/realtime/encrypted 入口点。导入它并将其作为客户端的 encryption 选项传入:
代码示例
import { BirdRealtime } from "@messagebird/realtime";
import { encryption } from "@messagebird/realtime/encrypted";

const bird = new BirdRealtime({
  appKey: "your-app-key",
  region: "us1",
  authEndpoint: "/bird/auth",
  encryption,
});

const orders = bird.subscribe("private-encrypted-orders");
orders.bind("order.updated", (data) => {
  console.log(data); // decrypted: { order_id: "ord_123", status: "shipped" }
});
绑定接收到的是明文。未设置 encryption 选项的订阅会立即抛出异常,授权响应中缺少 shared_secret 会导致订阅失败。
目前只有浏览器客户端支持接收加密频道。Swift 和 Kotlin 客户端会拒绝 private-encrypted- 订阅,因为它们未实现解密功能。

轮换主密钥

将新密钥同时部署到所有发布者和授权端点。轮换期间:
  1. 新发布的消息使用新密钥加密。
  2. 已订阅的浏览器客户端如果无法解密某个事件,会重新授权一次并获取新的 shared_secret
  3. 使用不同主密钥的实例可能短暂地发布某些客户端无法解密的事件,因此请在实例间协调部署。
密钥泄露或丢失时应立即轮换。轮换保护未来的载荷,但无法重新加密之前的事件,也无法撤销旧密钥的副本。

加密频道不支持的功能

  • 官方客户端不支持客户端事件。 浏览器 trigger() 在加密频道上会抛出异常,因为客户端不会对客户端到客户端的载荷进行加密。不要从自定义客户端发送明文客户端事件。
  • Presence 和加密不能组合使用。presence-encrypted- 前缀不受支持。缓存和加密可以一起使用:private-encrypted-cache- 频道以加密形式存储缓存事件,但密钥轮换后,缓存副本仍使用旧密钥加密,直到下一次发布替换它。
  • 频道名称和事件名称不会被加密。 只有载荷会被加密。选择频道名称时,避免泄露你要保护的内容。
  • Realtime 边缘节点无法检查载荷。 频道名称和事件名称仍然可见,但载荷保持加密。

后续步骤

  • 频道授权是本指南所依赖的签名机制。
  • 发布事件涵盖发布和批量发布 API 本身。
  • 缓存频道介绍了 private-encrypted-cache- 所结合使用的最近事件重放功能。