paint-brush
Seald と PubNub を使用してエンドツーエンドの暗号化チャットを構築する方法@seald
557 測定値
557 測定値

Seald と PubNub を使用してエンドツーエンドの暗号化チャットを構築する方法

Seald16m2023/09/20
Read on Terminal Reader

長すぎる; 読むには

この記事では、PubNub と Seald SDK を使用してエンドツーエンドの暗号化チャットを構築する方法を説明します。プライバシーとセキュリティのためのエンドツーエンド暗号化の重要性を強調し、キー管理には PubNub のネイティブ暗号化よりも Seald.io が好ましい理由を説明します。この記事では、PubNub を使用した 1 対 1 およびグループ チャット ルームの実装、ユーザー認証、リアルタイム アクセス管理について説明します。また、チャット アプリケーション内のメッセージとファイルの堅牢な暗号化と復号化のために Seald を統合するプロセスについても概説します。全体として、安全なチャット アプリケーションを作成するための包括的なガイドを提供します。
featured image - Seald と PubNub を使用してエンドツーエンドの暗号化チャットを構築する方法
Seald HackerNoon profile picture
0-item
1-item

こんにちは👋


今日は、 Pubnubと sealed SDK を使用してエンドツーエンドの暗号化チャットを構築する方法を見てみましょう


👉 PubNub と Seald の使用方法の完全に機能する例は、ここにあります。 https://github.com/seald/pubnub-example-project


パブナブって何?

PubNub は、ほとんどのアプリケーションに統合できるリアルタイム通信サービスです。信頼性と拡張性に優れており、最も一般的なフレームワークと簡単に統合できます。


シールって何?

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


コンテンツの概要

  • エンドツーエンド暗号化を使用する理由は何ですか?
  • PubNub 暗号化フックの代わりに Seald.io を使用する理由?👀
  • 目標 🏆
  • 実装🧠
  • Seald によるエンドツーエンド暗号化の追加 🔒💬
  • 結論✅


エンドツーエンド暗号化を使用する理由は何ですか? 🔒

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


エンドツーエンドの暗号化により、データが転送中、保管中、さらにはデータが手元にないときでも、常に制御を維持できます。したがって、他の暗号化テクノロジ (TLS、フルディスク暗号化など) よりもはるかに幅広い保護を提供します。


コンプライアンスが重要なケース (GDPR、HIPAA、SOC-2 など) や機密データ(医療、防衛など) が存在するケースに直面した場合、エンドツーエンドの暗号化は必須です。ただし、より一般的なデータであっても、これを用意することをお勧めします。データ侵害は、ますます頻繁になっている壊滅的なイベントです。


PubNub 暗号化フックの代わりに Seald.io を使用する理由?👀

PubNub SDK は、SDK をインスタンス化するときにcipherKey引数を使用して、単純な暗号化フックを提供します。そうすることで、アップロードされたすべてのメッセージが送信前に確実に暗号化されます。ただし、鍵管理は自分で行う必要があり、これがエンドツーエンド暗号化システムの最も難しい部分です。


脆弱性が暗号化自体から発生することはほとんどありませんが、ほとんどの場合、セキュリティ モデルの欠陥を通じてキーが漏洩することが原因です


セキュリティのためにサードパーティのサービスを使用すると、単一障害点をなくすことができます。 Seald.io は、リアルタイムのアクセス管理制御、ユーザーの取り消しと回復、2FA などを備えた、 ANSSI によって認定された堅牢なセキュリティ モデルを提案しています。


目標 🏆

この記事では、エンドツーエンドの暗号化でチャットを保護するために、Seald を PubNub と統合する方法を段階的に説明します。次の機能を備えたサンプル メッセージング アプリを構築します。


  • 1対1およびグループチャットルーム。

  • 各メンバーには、他のすべてのユーザーと専用のチャット ルームがあります。

  • 誰でも他の複数のユーザーとのグループ チャット ルームを作成できます。

  • 送信されるすべてのメッセージとファイルにエンドツーエンドの暗号化を使用します。

  • チャットへのリアルタイムのアクセス管理を許可します。


実装🧠

PubNub アカウントをセットアップする 👤

始めるには、 PubNub アカウントが必要です。サインアップできますここ。ダッシュボードにログインすると、デモ キーセットを含むデモ アプリが作成されたことがわかります。


デモ キーセットを選択し、設定タブまでスクロールします。このデモでは、 FilesObjectsアクセス許可を有効にする必要があります。 Object権限には、 User Metadata EventsChannel Metadata Events 、およびMembership Eventsのイベントを使用します。


キーセットを作成して構成したら、それをフロントエンドにコピーする必要があります。

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

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


PubNub を使用して基本的なチャットを構築する 💬

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


フロント側には、小さな認証インターフェイスが必要です。


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

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


物事を簡単にするために、バックエンドと同じ ID を使用します。

 /* 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関数を呼び出すだけです。

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


アプリの初期状態を取得する 🌱

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

 /* 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 チャネルにsubscribe 、新しいメッセージを受信できるようになります。

 /* 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) })


これで、現在いるすべてのルームを取得しました。アプリの初期化を完了するには、最後に 1 つ必要があります。それは、新しく登録したメンバーを含む他のすべてのメンバーとone2oneルームを確保することです。


新しく見つかったユーザーごとに、新しいルームを作成し、Hello メッセージを送信します。


次に、ルームのメタデータを設定し、サブスクライブします。

 /* 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] }) } }


すべての作業が完了すると、アプリの初期状態が完全に定義されます。ただし、常に最新の状態に保つ必要があります。


これは、 membershipイベントのイベント リスナーを追加するだけで実行できます。

 /* 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 を知るだけで実行できます。

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


チャンネルに登録して新しいメッセージを取得し、リスナーを追加してリアルタイムで表示できます。

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


メッセージを送信するには、それをチャネルで公開するだけです。

 /* 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 にアップロードします。次に、アップロードされたファイル URI を取得し、チャット ルームでメッセージとして公開します。

 /* 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) } })


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

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



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

完全なチャット アプリが完成しました。すべてのメッセージにエンドツーエンドの暗号化を追加しましょう。


Seald によるエンドツーエンド暗号化の追加 🔒💬

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


Seald を始めるには、無料トライアルアカウントを作成してくださいここ


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


次の要素を取得します。

  • アプリID
  • apiURL
  • キーストレージURL


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

 { "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 は、シークレットとシークレット ID を使用してバックエンドで生成する必要があります。

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

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


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

 /* 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 に関するドキュメント記事


次に、 Seald SDK をインストールする必要があります。


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


このプラグインにより、ユーザーの簡単なパスワード認証が可能になります。

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

Seald SDK をインスタンス化する 💡

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

 /* 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)] }) }


Seal ID の作成と取得 🔑

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

 /* 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 が動作し、暗号化と復号化が可能になります。


警告:適切なセキュリティを確保するには、パスワードを認証サーバーに送信する前に事前ハッシュする必要があります。詳細については、を参照してください。 ドキュメントのパスワード認証に関する段落

メッセージの暗号化と復号化を開始します 🔒🔓

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

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

 /* 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.encryptMessagesession.encryptFilesession.decryptMessage 、およびsession.decryptFile関数を単純に使用できます。


まずはメッセージから始めましょう。メッセージを送信するには:

 /* 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... */ }


そしてメッセージを受け取ったとき:

 /* 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関数を使用して、ルームを開くときにセッション内の既存のすべてのメッセージを復号化します。

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


次にファイルについてです。ファイルをアップロードするには:

 /* 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 })


ファイルを復号化するには:

 /* 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があります。グループが作成されるたびに、グループを作成する必要があります。

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


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

 /* 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 に統合することができました。


チャットがエンドツーエンドで暗号化されるようになったので、データ侵害が発生した場合でもデータの機密性が保たれることをユーザーに保証できます。


いつものように、遠慮せずにお問い合わせ追加のガイダンスが必要な場合。


あなたが構築したものを見るのが待ちきれません 🥳。