SealdとPubNubでエンドツーエンドの暗号化チャットを構築する
Published: 2024-03-18 07:57:54

> **この記事はサードパーティの** [**seald**](https://www.seald.io/)さん**からの寄稿で** 、**PubNubに内蔵されている** [**メッセージレベル暗号**](https://www.pubnub.com/docs/general/setup/data-security#message-encryption)化の**代替案を** 紹介しています**。 元々は** [**Hackernoonに**](https://hackernoon.com/how-to-build-an-end-to-end-encrypted-chat-with-seald-and-pubnub)**掲載さ** れたものです**。**

* * *

本日は、**Pubnubと** **seald SDKを使って** [エンドツーエンドの](https://hackernoon.com/a-cryptographers-guide-to-end-to-end-encryption?ref=hackernoon.com) [**暗号化**](https://hackernoon.com/a-cryptographers-guide-to-end-to-end-encryption?ref=hackernoon.com) **チャットを** **構築**する方法を紹介します。

👉 PubNubとsealdの使い方の完全な動作例はこちら[https://github.com/seald/pubnub-example-project](https://github.com/seald/pubnub-example-project?ref=hackernoon.com)。

**Pubnubとは？**

PubNubはほとんどのアプリケーションに統合できる[リアルタイムコミュニケーション](https://www.pubnub.com/solutions/data-streaming/)サービスです。信頼性と拡張性が高く、最も一般的なフレームワークと簡単に統合できます。

**Sealdとは？**

Seald.ioは、高度な管理機能を備えた**エンドツーエンドの暗号**化を、**事前の暗号化知識がなくても**実行できるSDKを提供しています。 このSDKは、Web、バックエンド、モバイル、デスクトップアプリケーションに統合できます。

コンテンツの概要
--------

-   なぜエンドツーエンド暗号化を使うのか？
    
-   なぜPubNub暗号化フックではなくSeald.ioを使うのか？
    
-   目標 🏆.
    
-   実装 🧠.
    
-   Sealdでエンドツーエンド暗号化を追加する 🔒 💬.
    
-   結論
    

**なぜエンドツーエンドの暗号化を使うのか？🔒**
--------------------------

エンドツーエンドの暗号化は、**最高レベルのプライバシーと**セキュリティを提供**します。** エンドツーエンドの暗号化によって、機密データを収集したらすぐに暗号化することができます。早期に暗号化することで、アプリの攻撃対象が減ります。もう一つの利点は、**データにアクセスできる人を**正確に**管理**できることです。 これは、**サードパーティ・**サービスのように、あなたの範囲外の場合も保護します。

エンド・ツー・エンドの暗号化により、データが転送されているとき、静止しているとき、そして手元にないときでも、常に**コントロールし続けることが**できる。したがって、他の暗号化技術（TLS、フルディスク暗号化など）よりもはるかに幅広い保護が可能です。

[**コンプライアンスが**](https://hackernoon.com/60-stories-to-learn-about-compliance?ref=hackernoon.com) **重要な**場合（GDPR、HIPAA、SOC-2など）や、**機密データ**（医療、防衛など）がある場合は、エンドツーエンドの暗号化は必須です。しかし、より一般的なデータであっても、暗号化は必須です。データ漏洩は壊滅的な出来事であり、その**頻度は**ますます高くなっています。

**なぜPubNub暗号化フックの代わりにSeald.ioを使うのですか？👀**
-----------------------------------------

PubNub SDKは、そのSDKをインスタンス化するときに`cipherKey`引数を使用して、単純な暗号化フックを提供しています。**しかし**、鍵の管理は自分で行う必要があります。

脆弱性が暗号化そのものから生じることはほとんどなく、セキュリティ・モデルの欠陥によって**鍵が漏れる**ことがほとんどだ。

Seald.ioは、**ANSSIによって認証さ**れた堅牢なセキュリティモデルを提案し、リアルタイムのアクセス管理制御、ユーザーの失効と回復、2FAなどを備えています**。**

**ゴール 🏆 この記事では、Seald.ioを統合する方法について説明します。**
-------------------------------------------

この記事では、エンドツーエンドの暗号化でチャットを保護するために、PubNubとSealdを**ステップバイステップで**統合する方法を説明します。以下の機能を持つ**メッセージングアプリの例を**構築します：

-   1対1とグループチャットルーム。
    
-   各メンバーは、他のすべてのユーザーと専用のチャットルームを持っています。
    
-   誰でも複数のユーザーとグループチャットルームを作成できます。
    
-   送信されるすべてのメッセージとファイルにエンドツーエンドの暗号化を使用。
    
-   チャットへのリアルタイムアクセス管理。
    

**実装方法 🤎.**
------------

### **PubNubアカウントのセットアップ 👤。**

始めるには**PubNubアカウントが**必要です**。** [ここから](https://admin.pubnub.com/?ref=hackernoon.com#/login)サインアップできます。ダッシュボードにログインすると、デモキーセット付きの**デモアプリが**作成されているはずです：

![](https://www.pubnub.com/cdn/3prze68gbwl1/7dlHV6HrK8ycTndYN8kN60/b8c5c4ed358737006494e485ae750867/YIMSJwDGhsOOhi9zHncV7Nc4OJG2-2023-09-20T13_43_30.768Z-sc4346be8h3z6uph34s3t8gi.jpg)

**デモキーセットを選択**し、設定タブまでスクロールします。デモでは、`Filesと` `Objectsの`パーミッションを有効にする必要があります。`Object`パーミッションでは、`User Metadata Events`、`Channel Metadata Events`、Membership`Eventsを`使用する。

キーセットが作成され、設定されたら、**それをフロントエンドにコピーする必要がある**。

`src/`フォルダに`settings.jsonという`JSONファイルを作りましょう。このファイルを必要なすべてのAPIキーに使用します。PubNubキーセットから始める：

```js
{
  "PUBNUB_PUB_KEY": "pub-c-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "PUBNUB_SUB_KEY": "sub-c-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
}
```

### **PubNubを使って基本的なチャットを構築する**

PubNubを**バックエンドの**ほぼ**すべてのタスクに**使用します**。** バックエンドはユーザのサインアップ/サインインのみを処理し、ID、名前、メールアドレスのみの最小限のユーザモデルを使用します。

フロントサイドでは小さな認証インターフェースが必要です：

![](https://www.pubnub.com/cdn/3prze68gbwl1/4QeRZBwCwvCKNd643LRd80/51d262b0ec0bc0af901139b02788cc13/YIMSJwDGhsOOhi9zHncV7Nc4OJG2-2023-09-20T13_43_30.784Z-gzbwgy4u8frc04p3yn3tmb87.jpg)

ユーザーがアカウントを取得したら、最初に必要なのはPubNub SDKのインスタンスです。

**Pubnub上でユーザーを識別するために、**UUIDを提供する必要があります。

シンプルにするために、バックエンドと同じIDを使用します：

```js
/* frontend/src/App.js */
import settings from './settings.json' // our settings file for API keys
/*
...
*/
const pubnub = new PubNub({
    publishKey: settings.PUBNUB_PUB_KEY,
    subscribeKey: settings.PUBNUB_SUB_KEY,
    uuid: currentUser.id
})
```

バックエンドをできるだけシンプルに保つために、PubNubのユーザーメタデータを使用してユーザー情報を交換します。

**SDKのインスタンス化の**直後にPubNubの`setUUIDMetadata`関数を呼び出すだけです：

```js
/* frontend/src/App.js */
await pubnub.objects.setUUIDMetadata({
  uuid: currentUser.id,
  data: {
    email: currentUser.emailAddress,
    name: currentUser.name
  }
})
```

### **アプリの初期状態の取得**

PubNubで**最初に**行うことは、すべての既存メンバーを取得してローカルデータストアに保存することです：

```js
/* frontend/src/App.js */
const existingMembers = await pubnub.objects.getAllUUIDMetadata()
dispatch({
  type: SET_USERS,
  payload: {
    users: existingMembers.data.map(u => new User({ id: u.id, name: u.name, emailAddress: u.email }))
  }
})
```

各チャットルームはPubNubチャンネルに対応します。また、各チャンネルにいくつかのメタデータを追加します：

-   `ownerId`：部屋を作成したユーザのID。
    
-   `one2one`：ダイレクトメッセージルームとグループルームを区別するためのブール値。
    
-   `archived`：削除されたグループルームを隠すためのブール値。
    

`ownerId`メタデータは後でSeald SDKを追加する際に使用されます。PubNubには所有権の概念がありませんが、Sealdにはあります。これはチャンネルにユーザーを追加したり削除したりできる人を定義します。基本的にはグループ管理者を定義します。

まずは**既存のチャット・ルームを取得**することから始める。 ルーム・メタデータも必要なので、カスタム・フィールドを含める必要がある。次に、アーカイブされたルームをフィルタリングし、すべてをデータストアに送信する必要があります。

最後に、ルームに関連付けられたPubNubチャンネルを`サブスクライブして`、新しいメッセージを受け取れるようにします：

```js
/* frontend/src/App.js */
// Retrieve rooms of which we are members
const memberships = await pubnub.objects.getMemberships({
  include: {
    customChannelFields: true
  }
})
const knownRooms = []
// For each room, retrieve room members
for (const room of memberships.data.filter(r => !r.channel.custom.archived)) {
  const roomMembers = await pubnub.objects.getChannelMembers({ channel: room.channel.id })
  knownRooms.push(new Room({
    id: room.channel.id,
    name: room.channel.name,
    users: roomMembers.data.map(u => u.uuid.id),
    ownerId: room.channel.custom.ownerId,
    one2one: room.channel.custom.one2one
  }))
}
// Store rooms in our data store
dispatch({
  type: SET_ROOMS,
  payload: {
    rooms: knownRooms
  }
})
// Subscribe to channels to get new messages
pubnub.subscribe({ channels: knownRooms.map(r => r.id) })
```

これで今いる部屋はすべて取得できた。アプリの初期化を完了させるために、最後にもう一つ必要なことがあります。それは、新しく登録されたユーザーを含め、他のメンバー全員と`one2one`ルームを確保することです。

新しく見つけたユーザーには、新しいルームを**作成して**、ハローメッセージを送ります。

そして、ルームのメタデータを設定し、ルームに登録する：

```js
/* frontend/src/App.js */
// Ensure that we have a one2one room with everyone
const one2oneRooms = knownRooms.filter(r => r.one2one)
for (const m of existingMembers.data.filter(u => u.id!== currentUser.id)) {
    if (!one2oneRooms.find(r => r.users.includes(m.id))) {
      // New user found: generating a new one2one room
      const newRoomId = PubNub.generateUUID()
      const newRoom = new Room({
            id: newRoomId,
            users: [currentUser.id, m.id],
            one2one: true,
            name: m.name,
            ownerId: currentUser.id
          })
      // Add the new room to our local list
      dispatch({
        type: EDIT_OR_ADD_ROOM,
        payload: {
          room: new Room({
            id: newRoomId,
            users: [currentUser.id, m.id],
            one2one: true,
            name: m.name, ownerId: currentUser.id
          })
        }
      })
      // Publish a "Hello" message in the room
      await pubnub.publish({
        channel: newRoomId,
        message: {
          type: 'message',
          data: (await sealdSession.encryptMessage('Hello 👋'))
        }
      })
      // Subscribe to the new room
      pubnub.subscribe({ channels: [newRoomId] })
      await pubnub.objects.setChannelMetadata({
        channel: newRoomId,
        data: {
          name: 'one2one',
          custom: {
            one2one: true,
            ownerId: currentUser.id,
          },
        }
      })
      await pubnub.objects.setChannelMembers({
        channel: newRoomId,
        uuids: [currentUser.id, m.id]
      })
    }
}
```

これが完了すると、アプリの初期状態は**完全に定義されます。** しかし、アプリの状態を常に最新の状態に保つ必要があります。

`メンバーシップイベントの`イベントリスナーを追加するだけだ：

```js
/* frontend/src/App.js */
pubnub.addListener({
  objects: async function(objectEvent) {
    if (objectEvent.message.type === 'membership') {
      if (objectEvent.message.event === 'delete') { // User is removed from a room
        /*
        Removing the room from store...
        */
      }
      if (objectEvent.message.event === 'set') { // User is added to a room
        const metadata = await pubnub.objects.getChannelMetadata({ channel: objectEvent.message.data.channel.id })
        const roomMembers = (await pubnub.objects.getChannelMembers({ channel: objectEvent.message.data.channel.id })).data.map(u => u.uuid.id)
        /*
        Adding new room to store + subscribing to new room channel...
        */
      }
    }
  }
})
pubnub.subscribe({ channels: [currentUser.id] }) // channel on which events concerning the current user are published
```

それでは、`one2one`チャットルームを見てみましょう。次にグループルームを扱います。

### **チャットルーム📩でのメッセージの受信と送信**

`chat.js`ファイルには、チャットルームのメッセージを表示するためのすべてのロジックがあります。

チャット**ルームを初期化するために**必要なことは、既存のメッセージをすべて取得することです。

これは、ルームIDを知るだけでできます：

```js
/* frontend/src/components/Chat.jsx */
const fetchedMessages = (await pubnub.fetchMessages({ channels: [currentRoomId] })).channels[currentRoomId]
```

新しいメッセージを受け取るためにチャンネルを購読し、リアルタイムで表示するためにリスナーを追加することができる：

```js
/* frontend/src/components/Chat.jsx */
pubnub.addListener({ message: handleReceiveMessage })
pubnub.subscribe({ channels: [currentRoomId] })
```

メッセージを送信するには、チャンネルに公開すればいい：

```js
/* frontend/src/components/Chat.jsx */
const handleSubmitMessage = async e => {
  /* Some checks that the room is in a correct state... */
  await pubnub.publish({
    channel: state.room.id,
    message: {
      type: 'message',
      data: state.message
    }
  })
}
```

**ファイルを送信するには、**まずPubNubにアップロードします。ファイルを送信するには、まずファイルをPubNubにアップロードします。次にアップロードされたファイルのURIを取得し、チャットルームにメッセージとして公開します：

```js
/* frontend/src/components/UploadButton.jsx */
// Upload Encrypted file
const uploadData = await pubnub.sendFile({
  channel: room.id,
  file: myFile,
  storeInHistory: false
})
const fileURL = await pubnub.getFileUrl({ id: uploadData.id, name: uploadData.name, channel: room.id })
await pubnub.publish({
  channel: state.room.id,
  message: {
    type: 'file',
    url: fileURL,
    fileName: await sealdSession.encryptMessage(selectedFiles[0].name)
  }
})
```

#### **グループチャットの管理 👨‍👩‍👦‍👦**

グループの作成と管理には、ユーザを選択するためのインターフェースが必要です：

![](https://www.pubnub.com/cdn/3prze68gbwl1/5eUvBiTJXw901zb2ItefN0/2189f12071c272cc34192ca26ce2339f/YIMSJwDGhsOOhi9zHncV7Nc4OJG2-2023-09-20T13_43_30.789Z-qpr0csdfsonvkr3haucsetki.jpg)

**グループメンバーを選択**したら、ルーム用のPubNubチャンネルを作成し、チャンネルのメタデータとメンバーシップを設定します。このコードはone2oneルームで行われるものと非常に似ているので、ここでは繰り返しません。

これで完全なチャットアプリの完成だ。**メッセージごとにエンドツーエンドの暗号**化を追加しよう！

**Sealdでエンドツーエンドの暗号化を追加する 🔒 💬.**
----------------------------------

### **Sealdアカウントをセットアップする👤。**

Sealdを始めるには、[ここで](https://www.seald.io/create-sdk?ref=hackernoon.com) **無料トライアルアカウントを作成**します。

Sealdのダッシュボードにアクセスすると、いくつかのURLとAPIトークンが表示されます。

以下の要素を取得します：

-   appId
    
-   apiURL
    
-   keyStorageURL
    

これらのキーを`settings.jsonに`追加します：

```js
{
  "PUBNUB_PUB_KEY": "pub-c-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "PUBNUB_SUB_KEY": "sub-c-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "SEALD_APP_ID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "SEALD_API_URL": "https://api.staging-0.seald.io",
  "SEALD_KEYSTORAGE_URL": "https://ssks.staging-0.seald.io"
}
```

**Seald SDKを使用できるように**するには、サインアップ時にすべてのユーザーがライセンスJWTを必要とします。

これらのJWTは、secretとsecret IDを使用してバックエンドで生成する必要があります。

ダッシュボードのランディングページから、`backend/settings.jsonに`JWTシークレットとそれに関連するIDをコピーします：

```js
{
  "SEALD_JWT_SECRET_ID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "SEALD_JWT_SECRET": "XXXXXXXXXXXXXXXXXXXXXXXX"
}
```

サインアップAPI呼び出しの間に、SealdライセンスJWTを生成し、それを返します：

```js
/* backend/routes/account.js */
const token = new SignJWT({
  iss: settings.SEALD_JWT_SECRET_ID,
  jti: uuidv4(), /// Random string with enough entropy to never repeat.
  iat: Math.floor(Date.now() / 1000), // JWT valid only for 10 minutes. `Date.now()` returns the  in milliseconds, this needs it in seconds.
  scopes: [3], // PERMISSION_JOIN_TEAM
  join_team: true
})
  .setProtectedHeader({ alg: 'HS256' })
const signupJWT = await token.sign(Buffer.from(settings.SEALD_JWT_SECRET, 'ascii'))
```

これに関する詳細は、[JWTに関するドキュメントの記事を](https://docs.seald.io/en/sdk/guides/jwt.html?ref=hackernoon.com#creating-a-json-web-token)参照してください。

次に、**Seald SDKをインストール**します。

また、Sealdサーバーのユーザーを識別するための**プラグインもインストールする必要が**あります。そのために、`sdk-plugin-ssks-password`パッケージを使用します。

このプラグインは、ユーザーの簡単なパスワード認証を可能にします：

```js
npm i -S @seald-io/sdk @seald-io/sdk-plugin-ssks-password
```

### **Seald SDKのインスタンス化 💡。**

次に、`seald.js`ファイルを作成します。このファイルで、Seald SDKをインスタンス化する関数を作成します：

```js
/* frontend/src/services/seald.js */
import SealdSDK from '@seald-io/sdk-web'
import SealdSDKPluginSSKSPassword from '@seald-io/sdk-plugin-ssks-password'
import settings from './settings.json'
let sealdSDKInstance = null
const instantiateSealdSDK = async () => {
  sealdSDKInstance = SealdSDK({
    appId: settings.SEALD_APP_ID,
    apiURL: settings.SEALD_API_URL,
    plugins: [SealdSDKPluginSSKSPassword(settings.SEALD_KEYSTORAGE_URL)]
  })
}
```

### **Seald IDの作成と取得 🔑 Seald SDKをインスタンス化する関数を作成します。**

`seald.jsには`、IDを作成する関数とIDを取得する関数の2つを追加します。**IDを作成**するには、アカウント作成時に返されるlicense JWTも必要です：

```js
/* frontend/src/services/seald.js */
export const createIdentity = async ({ userId, password, signupJWT }) => {
  await instantiateSealdSDK()
  await sealdSDKInstance.initiateIdentity({ signupJWT })
  await sealdSDKInstance.ssksPassword.saveIdentity({ userId, password })
}
export const retrieveIdentity = async ({ userId, password }) => {
  await instantiateSealdSDK()
  await sealdSDKInstance.ssksPassword.retrieveIdentity({ userId, password })
}
```

サインアップとサインインのフローでは、ユーザーがログインした後にこれらの関数を呼び出すだけでよい。

この時点で、ユーザーが接続するたびに、Seald SDKが動作し、**暗号化と復号化の準備が**整います！

**警告：**適切なセキュリティを確保するため、パスワードは認証サーバーに送信する前にプリハッシュ化する必要があります。詳細については、[ドキュメントのパスワード認証の項を](https://docs.seald.io/en/sdk/guides/4-identities.html?ref=hackernoon.com#password-also-used-for-authentication)参照してください。

### **メッセージの暗号化と復号化を開始する**

各チャットルームはSeald SDKの`encryptionSessionと`関連付けられます。

チャットルームを作成するたびに、**暗号化セッションを作成する行を1行追加**し、それを使用するだけです：

```js
/* frontend/src/App.js */
// Create a Seald session
const sealdSession = await getSealdSDKInstance().createEncryptionSession(
  { userIds: [currentUser.id, m.id] },
  { metadata: newRoomId }
)
// Publish a "Hello" message in the room
await pubnub.publish({
  channel: newRoomId,
  message: {
    type: 'message',
    data: (await sealdSession.encryptMessage('Hello 👋'))
  }
})
```

`encryptionSessionの`受信者として、デフォルトで私たちのユーザーが含まれていることに注意してください。

ルームにアクセスするときは、対応する`encryptionSessionを`取得する必要がある。これは暗号化されたメッセージやファイルから取得できる。それを手に入れたら、コンポーネントのリファレンスに置いておく。

そして、`session.encryptMessage``、session.encryptFile`、`session.decryptMessage`、`session.decryptFile`関数を使うだけです。

メッセージから始めましょう。メッセージを送信する：

```js
/* frontend/src/components/Chat.js */
const handleSubmitMessage = async m => {
  /* Some validation that we are in a valid room... */
  // if there is no encryption session set in cache yet, create one
  // (should never happen, as a "Hello" is sent on room creation)
  if (!sealdSessionRef.current) {
    sealdSessionRef.current = await getSealdSDKInstance().createEncryptionSession(
      { userIds: state.room.users },
      { metadata: state.room.id }
    )
  }
  // use the session to encrypt the message we are trying to send
  const encryptedMessage = await sealdSessionRef.current.encryptMessage(state.message)
  // publish the encrypted message to pubnub
  await pubnub.publish({
    channel: state.room.id,
    message: {
      type: 'message',
      data: encryptedMessage
    }
  })
  /* Some cleanup... */
}
```

そして、メッセージを受信する：

```js
/* frontend/src/components/Chat.js */
const decryptMessage = async m => {
  /* Filter out files... */
  let encryptedData = m.message.data
  if (!sealdSessionRef.current) { // no encryption session set in cache yet
    // we try to get it by parsing the current message
    sealdSessionRef.current = await getSealdSDKInstance().retrieveEncryptionSession({ encryptedMessage: encryptedData })
    // now that we have a session loaded, let's decrypt
  }
  const decryptedData = await sealdSessionRef.current.decryptMessage(encryptedData)
  // we have successfully decrypted the message
  return {
    ...m,
    uuid: m.uuid || m.publisher,
    value: decryptedData
  }
  /* Some error handling... */
}
/* Other stuff... */
const handleReceiveMessage = async m => {
  const decryptedMessage = await decryptMessage(m)
  setState(draft => {
    draft.messages = [...draft.messages, decryptedMessage]
  })
}
```

また、この`decryptMessage`関数は、ルームを開くときに、すでにセッションにあるすべてのメッセージを復号化するために使います：

```js
/* frontend/src/components/Chat.js */
const fetchedMessages = (await pubnub.fetchMessages({ channels: [currentRoomId] })).channels[currentRoomId]
const clearMessages = fetchedMessages ? await Promise.all(fetchedMessages.map(decryptMessage)) : []
```

次はファイルです。ファイルをアップロードする：

```js
/* frontend/src/components/UploadButton.js */
// Encrypt file
const encryptedBlob = await sealdSession.encryptFile(
  selectedFiles[0],
  selectedFiles[0].name,
  { fileSize: selectedFiles[0].size }
)
const encryptedFile = new File([encryptedBlob], selectedFiles[0].name)
// Upload Encrypted file
const uploadData = await pubnub.sendFile({
  channel: room.id,
  file: encryptedFile,
  storeInHistory: false
})
```

ファイルの復号化

```js
/* frontend/src/components/Message.js */
const onClick = async () => {
  if (state.data.type === 'file') {
    const response = await fetch(state.data.url)
    const encryptedBlob = await response.blob()
    const { data: clearBlob, filename } = await sealdSession.decryptFile(encryptedBlob)
    const href = window.URL.createObjectURL(clearBlob)
    /* Create an <a> element and simulate a click on it to download the created objectURL */
  }
}
```

### **グループメンバーの管理 👨‍👩‍👦**

グループチャットにも`encryptionSessionが`あります。グループが作成されるたびに、グループを作成する必要があります：

```js
/* frontend/src/components/ManageDialogRoom.js.js */
// To create the encryptionSession
const sealdSession = await getSealdSDKInstance().createEncryptionSession(
  { userIds: dialogRoom.selectedUsersId },
  { metadata: newRoomId }
)
```

そして、**グループメンバーを変更する**たびに、メンバーを追加または削除する必要があります：

```js
/* frontend/src/components/ManageDialogRoom.js.js */
// we compare old and new members to figure out which ones were just added or removed
const usersToRemove = dialogRoom.room.users.filter(id => !dialogRoom.selectedUsersId.includes(id))
const usersToAdd = dialogRoom.selectedUsersId.filter(id => !dialogRoom.room.users.includes(id))
if (usersToAdd.length > 0) {
  // for every added user, add them to the Seald session
  await dialogRoom.sealdSession.addRecipients({ userIds: usersToAdd })
  // then add them to the pubnub channel
  await pubnub.objects.setChannelMembers({
    channel: dialogRoom.room.id,
    uuids: usersToAdd
  })
}
if (usersToRemove.length > 0) {
  // for every removed user, revoke them from the Seald session
  await dialogRoom.sealdSession.revokeRecipients({ userIds: usersToRemove })
  // then remove them from the pubnub channel
  for (const u of usersToRemove) {
    await pubnub.objects.removeMemberships({
      channels: [dialogRoom.room.id],
      uuid: u
    })
  }
}
```

**結論✅。**
--------

これが終われば完成です！

たった数行のコードで**SealdをPubNubに統合**することができました。

**チャットがエンドツーエンドで暗号化されたので**、**データ漏洩の**場合でも、ユーザーのデータが機密のままであることを保証できます。

いつものように、追加のガイダンスが必要な場合は、 [**Sealdに連絡**](https://www.seald.io/discover?ref=hackernoon.com) **するか、** [**PubNub**](https://support.pubnub.com/hc/en-us) に[**連絡する**](https://support.pubnub.com/hc/en-us)ことを**躊躇しないで**ください。

あなたが作ったものを見るのが楽しみです。

#### 目次

[コンテンツ概要なぜ](#h-0)[エンドツーエンドの暗号化を使うのか？なぜ](#h-1)[PubNub暗号化フックではなくSeald.ioを使うのですか？👀](#h-2)ゴール[🏦実装](#h-4)[🏦PubNubアカウントを](#h-5)[セット](#h-4)[アップする](#h-5)[👤PubNubを使用して基本的なチャットを](#h-6)[構築する](#h-5)[💬初期アプリの状態を](#h-7)[取得する](#h-6)[🌱](#h-7)[チャットルームでメッセージを受信して送信する](#h-8)[📩グループチャットを](#h-9)[管理する](#h-8)[👨‍👩‍👦‍👦So\_1F4E9↩Sealdを](#h-9)使用して[エンドツーエンド](#h-10)暗号化を[追加する](#h-9)[Sealdを使ったエンドツーエンドの暗号化](#h-10)[🔒Sealdアカウントのセットアップ](#h-11)[👤Seald SDKの](#h-12)[導入](#h-11)[💡Seald IDの作成と取得](#h-13)[🔑メッセージの暗号化と復号化の](#h-14)[開始](#h-13)[🔒グループメンバーの](#h-15)[管理](#h-14)[👨‍👩‍👦5 ✅おわりに](#h-16)

PubNubはどのようにお役に立ちますか？
=====================

この記事は[PubNub.com](https://www.pubnub.com/blog/build-an-end-to-end-encrypted-chat-with-seald-and-pubnub/)に掲載されたものです。

PubNubのプラットフォームは、開発者がWebアプリ、モバイルアプリ、IoTデバイス向けにリアルタイムのインタラクティブ機能を構築、提供、管理できるように支援します。

私たちのプラットフォームの基盤は、業界最大かつ最もスケーラブルなリアルタイムエッジメッセージングネットワークです。世界15か所以上で8億人の月間アクティブユーザーをサポートし、99.999%の信頼性を誇るため、停電や同時実行数の制限、トラフィックの急増による遅延の問題を心配する必要はありません。

PubNubを体験
---------

[ライブツアーを](https://www.pubnub.com/tour/introduction/)チェックして、5分以内にすべてのPubNub搭載アプリの背後にある本質的な概念を理解する

セットアップ
------

[PubNubアカウントに](https://admin.pubnub.com/signup/)サインアップすると、PubNubキーに無料ですぐにアクセスできます。

始める
---

[PubNubのドキュメントは](https://www.pubnub.com/docs)、ユースケースや[SDKに](https://www.pubnub.com/docs)関係なく、あなたを立ち上げ、実行することができます。