この記事はサードパーティの sealdさんからの寄稿で 、PubNubに内蔵されている メッセージレベル暗号化の代替案を 紹介しています。 元々は Hackernoonに掲載さ れたものです。
本日は、Pubnubと seald SDKを使って エンドツーエンドの 暗号化 チャットを 構築する方法を紹介します。
👉 PubNubとsealdの使い方の完全な動作例はこちらhttps://github.com/seald/pubnub-example-project。
Pubnubとは?
PubNubはほとんどのアプリケーションに統合できるリアルタイムコミュニケーションサービスです。信頼性と拡張性が高く、最も一般的なフレームワークと簡単に統合できます。
Sealdとは?
Seald.ioは、高度な管理機能を備えたエンドツーエンドの暗号化を、事前の暗号化知識がなくても実行できるSDKを提供しています。 このSDKは、Web、バックエンド、モバイル、デスクトップアプリケーションに統合できます。
コンテンツの概要
なぜエンドツーエンド暗号化を使うのか?
なぜPubNub暗号化フックではなくSeald.ioを使うのか?
目標 🏆.
実装 🧠.
Sealdでエンドツーエンド暗号化を追加する 🔒 💬.
結論
なぜエンドツーエンドの暗号化を使うのか?🔒
エンドツーエンドの暗号化は、最高レベルのプライバシーとセキュリティを提供します。 エンドツーエンドの暗号化によって、機密データを収集したらすぐに暗号化することができます。早期に暗号化することで、アプリの攻撃対象が減ります。もう一つの利点は、データにアクセスできる人を正確に管理できることです。 これは、サードパーティ・サービスのように、あなたの範囲外の場合も保護します。
エンド・ツー・エンドの暗号化により、データが転送されているとき、静止しているとき、そして手元にないときでも、常にコントロールし続けることができる。したがって、他の暗号化技術(TLS、フルディスク暗号化など)よりもはるかに幅広い保護が可能です。
コンプライアンスが 重要な場合(GDPR、HIPAA、SOC-2など)や、機密データ(医療、防衛など)がある場合は、エンドツーエンドの暗号化は必須です。しかし、より一般的なデータであっても、暗号化は必須です。データ漏洩は壊滅的な出来事であり、その頻度はますます高くなっています。
なぜPubNub暗号化フックの代わりにSeald.ioを使うのですか?👀
PubNub SDKは、そのSDKをインスタンス化するときにcipherKey
引数を使用して、単純な暗号化フックを提供しています。しかし、鍵の管理は自分で行う必要があります。
脆弱性が暗号化そのものから生じることはほとんどなく、セキュリティ・モデルの欠陥によって鍵が漏れることがほとんどだ。
Seald.ioは、ANSSIによって認証された堅牢なセキュリティモデルを提案し、リアルタイムのアクセス管理制御、ユーザーの失効と回復、2FAなどを備えています。
ゴール 🏆 この記事では、Seald.ioを統合する方法について説明します。
この記事では、エンドツーエンドの暗号化でチャットを保護するために、PubNubとSealdをステップバイステップで統合する方法を説明します。以下の機能を持つメッセージングアプリの例を構築します:
1対1とグループチャットルーム。
各メンバーは、他のすべてのユーザーと専用のチャットルームを持っています。
誰でも複数のユーザーとグループチャットルームを作成できます。
送信されるすべてのメッセージとファイルにエンドツーエンドの暗号化を使用。
チャットへのリアルタイムアクセス管理。
実装方法 🤎.
PubNubアカウントのセットアップ 👤。
始めるにはPubNubアカウントが必要です。 ここからサインアップできます。ダッシュボードにログインすると、デモキーセット付きのデモアプリが作成されているはずです:
デモキーセットを選択し、設定タブまでスクロールします。デモでは、Filesと
Objectsの
パーミッションを有効にする必要があります。Object
パーミッションでは、User Metadata Events
、Channel Metadata Events
、MembershipEventsを
使用する。
キーセットが作成され、設定されたら、それをフロントエンドにコピーする必要がある。
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チャンネルをサブスクライブして
、新しいメッセージを受け取れるようにします:
/* 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
ルームを確保することです。
新しく見つけたユーザーには、新しいルームを作成して、ハローメッセージを送ります。
そして、ルームのメタデータを設定し、ルームに登録する:
/* 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]
})
}
}
これが完了すると、アプリの初期状態は完全に定義されます。 しかし、アプリの状態を常に最新の状態に保つ必要があります。
メンバーシップイベントの
イベントリスナーを追加するだけだ:
/* 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にアップロードします。ファイルを送信するには、まずファイルを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トークンが表示されます。
以下の要素を取得します:
appId
apiURL
keyStorageURL
これらのキーを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は、secretとsecret IDを使用してバックエンドで生成する必要があります。
ダッシュボードのランディングページから、backend/settings.jsonに
JWTシークレットとそれに関連するIDをコピーします:
{
"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)]
})
}
Seald IDの作成と取得 🔑 Seald SDKをインスタンス化する関数を作成します。
seald.jsには
、IDを作成する関数とIDを取得する関数の2つを追加します。IDを作成するには、アカウント作成時に返されるlicense 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.encryptMessage
、session.encryptFile
、session.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に統合することができました。
チャットがエンドツーエンドで暗号化されたので、データ漏洩の場合でも、ユーザーのデータが機密のままであることを保証できます。
いつものように、追加のガイダンスが必要な場合は、 Sealdに連絡 するか、 PubNub に連絡することを躊躇しないでください。
あなたが作ったものを見るのが楽しみです。
目次
コンテンツ概要なぜエンドツーエンドの暗号化を使うのか?なぜPubNub暗号化フックではなくSeald.ioを使うのですか?👀ゴール🏦実装🏦PubNubアカウントをセットアップする👤PubNubを使用して基本的なチャットを構築する💬初期アプリの状態を取得する🌱チャットルームでメッセージを受信して送信する📩グループチャットを管理する👨👩👦👦So_1F4E9↩Sealdを使用してエンドツーエンド暗号化を追加するSealdを使ったエンドツーエンドの暗号化🔒Sealdアカウントのセットアップ👤Seald SDKの導入💡Seald IDの作成と取得🔑メッセージの暗号化と復号化の開始🔒グループメンバーの管理👨👩👦5 ✅おわりに
PubNubはどのようにお役に立ちますか?
この記事はPubNub.comに掲載されたものです。
PubNubのプラットフォームは、開発者がWebアプリ、モバイルアプリ、IoTデバイス向けにリアルタイムのインタラクティブ機能を構築、提供、管理できるように支援します。
私たちのプラットフォームの基盤は、業界最大かつ最もスケーラブルなリアルタイムエッジメッセージングネットワークです。世界15か所以上で8億人の月間アクティブユーザーをサポートし、99.999%の信頼性を誇るため、停電や同時実行数の制限、トラフィックの急増による遅延の問題を心配する必要はありません。
PubNubを体験
ライブツアーをチェックして、5分以内にすべてのPubNub搭載アプリの背後にある本質的な概念を理解する
セットアップ
PubNubアカウントにサインアップすると、PubNubキーに無料ですぐにアクセスできます。
始める
PubNubのドキュメントは、ユースケースやSDKに関係なく、あなたを立ち上げ、実行することができます。