# 加密频道

名称以 `private-encrypted-` 开头的频道是端到端加密的。你的服务器在发布前加密每条载荷，经授权的浏览器客户端使用从你的授权端点获取的密钥进行解密。Realtime 边缘节点和网络中间方只能看到密文。

生成并存储一个 32 字节的主密钥。主密钥不会出现在 Realtime API 请求中，频道名称前缀即可启用该功能。Bird 无法恢复丢失的密钥，用该密钥加密的载荷在你替换密钥后仍然不可读。

加密频道使用与[私有频道](/docs/guides/realtime/private-channels)相同的端点和签名。授权响应还会以 `shared_secret` 的形式包含该频道的派生解密密钥。拒绝订阅可以阻止该客户端获取密钥。

## 生成主密钥

生成 32 个随机字节，将其编码为 base64，并像保存 app secret 一样存储该值：

```bash
openssl rand -base64 32
```

将其作为 realtime 配置的一部分传给你的服务器 SDK，与 app key 和 secret 放在一起。

## 发布加密事件

服务器 SDK 检测到频道前缀后，从主密钥派生频道密钥，并在本地加密 JSON 载荷。发布请求包含加密后的信封。

**TypeScript**

```typescript
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" },
});
```

Examples: [TypeScript](/zh-sg/wendang/guides/realtime/encrypted-channels.ts.md) · [Python](/zh-sg/wendang/guides/realtime/encrypted-channels.py.md) · [Go](/zh-sg/wendang/guides/realtime/encrypted-channels.go.md) · [PHP](/zh-sg/wendang/guides/realtime/encrypted-channels.php.md)

一次发布中，加密频道必须是唯一的频道。每个加密频道派生不同的密钥，因此其他频道无法解密同一个加密载荷。SDK 会在本地拒绝这种扇出操作，API 在收到此类请求时返回 `E23000`。要向多个加密频道发布，请使用批量发布，每个事件对应一个频道。

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

你的授权端点以与审批私有订阅相同的方式审批加密订阅。使用 SDK 的 `authorizeChannel` 辅助方法，当频道名称带有加密前缀时，响应会自动包含 `shared_secret`：

**TypeScript**

```typescript
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,
    }),
  );
});
```

Examples: [TypeScript](/zh-sg/wendang/guides/realtime/encrypted-channels.ts.md) · [Python](/zh-sg/wendang/guides/realtime/encrypted-channels.py.md) · [Go](/zh-sg/wendang/guides/realtime/encrypted-channels.go.md) · [PHP](/zh-sg/wendang/guides/realtime/encrypted-channels.php.md)

SDK 为每个频道派生单独的 `shared_secret`。因此，对 `private-encrypted-orders` 的授权无法解密 `private-encrypted-invoices`。密钥通过你的授权响应传递，不会包含在发送到边缘节点的订阅帧中。

## 在浏览器中订阅并解密

加密模块使用单独的 `@messagebird/realtime/encrypted` 入口点。导入它并将其作为客户端的 `encryption` 选项传入：

```typescript
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 边缘节点无法检查载荷。** 频道名称和事件名称仍然可见，但载荷保持加密。

## 后续步骤

- [频道授权](/docs/guides/realtime/authorizing-channels)是本指南所依赖的签名机制。
- [发布事件](/docs/guides/realtime/publishing-events)涵盖发布和批量发布 API 本身。
- [缓存频道](/docs/guides/realtime/cache-channels)介绍了 `private-encrypted-cache-` 所结合使用的最近事件重放功能。

## Related resources

- [Realtime](/products/realtime) (product)
- [Build your first integration](/learn/paths/integration) (course)
- [Send your first realtime event](/docs/get-started/send-your-first-realtime-event) (docs)

[Get an implementation brief](/learn/workspace?topic=realtime)
