Seald와 PubNub로 엔드투엔드 암호화 채팅 구축하기
Published: 2024-03-18 08:02:57

> **이 글은 타사인** [**seald에서**](https://www.seald.io/)**제공** 한 것으로 **PubNub에 내장된** [**메시지 수준 암호화에**](https://www.pubnub.com/docs/general/setup/data-security#message-encryption) **대한 대안을 제시합니다** **.** **원래는** [**해커눈에**](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은 대부분의 애플리케이션에 통합할 수 있는 [실시간 커뮤니케이션](https://www.pubnub.com/solutions/data-streaming/) 서비스입니다. 안정적이고 확장성이 뛰어나며 가장 일반적인 프레임워크와 쉽게 통합할 수 있습니다.

**Seald란 무엇인가요?**

Seald.io는 **사전 암호화 지식 없이도** 고급 관리 기능과 함께 **엔드투엔드 암호화를** 수행할 수 있는 SDK를 제공합니다. 이 SDK는 웹, 백엔드, 모바일 또는 데스크톱 애플리케이션에 통합할 수 있습니다.

콘텐츠 개요
------

-   왜 엔드투엔드 암호화를 사용해야 하나요?
    
-   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는 실시간 액세스 관리 제어, 사용자 해지 및 복구, 2FA 등을 통해 **ANSSI의 인증을** 받은 강력한 보안 모델을 제안합니다.

**목표 🏆**
---------

이 문서에서는 엔드투엔드 암호화로 채팅을 보호하기 위해 Seald와 PubNub을 **단계별로** 통합하는 방법을 설명합니다. 다음 기능을 갖춘 **메시징 앱 예시를** 구축하겠습니다:

-   일대일 및 그룹 채팅방.
    
-   각 구성원은 다른 모든 사용자와 전용 채팅방을 갖게 됩니다.
    
-   누구나 다른 여러 사용자와 함께 그룹 채팅방을 만들 수 있습니다.
    
-   모든 메시지와 파일 전송 시 엔드투엔드 암호화를 사용합니다.
    
-   채팅에 대한 실시간 액세스 관리를 허용합니다.
    

**구현 🧠**
---------

### **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)

**데모 키셋을 선택하고** 구성 탭으로 스크롤합니다. 데모의 경우 `파일` 및 `개체` 권한을 활성화해야 합니다. `개체` 권한의 경우 `사용자 메타데이터 이벤트`, `채널 메타데이터 이벤트` 및 멤버십`이벤트를` 사용합니다.

키 세트가 생성되고 구성되면 **이를 프런트엔드에 복사해야** 합니다.

`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`: 삭제된 그룹방을 숨기기 위한 부울입니다.
    

`소유자 ID` 메타데이터는 나중에 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) })
```

이제 우리가 있는 모든 방을 가져왔습니다. 앱 초기화를 완료하기 위해 마지막으로 한 가지 더 필요한 것은 새로 등록된 회원을 포함한 모든 회원과 `일대일` 방이 있는지 확인하는 것입니다.

새로 찾은 모든 사용자에 대해 새 방을 **생성하고** 인사 메시지를 보냅니다.

그런 다음, 방의 메타데이터를 설정하고 구독을 신청합니다:

```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
```

이제 `일대일` 채팅방 자체를 살펴볼 수 있습니다. 그런 다음 그룹 대화방을 처리하겠습니다.

### **채팅방에서 메시지 받기 및 보내기 📩**

`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에 파일을 업로드합니다. 그런 다음 업로드된 파일 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 채널을 생성한 다음 채널의 메타데이터와 멤버십을 설정할 수 있습니다. 이 코드는 원투원 룸에서 수행되는 것과 매우 유사하므로 여기서는 반복하지 않겠습니다.

이제 전체 채팅 앱이 완성되었습니다. **모든 메시지에 대해 엔드투엔드 암호화를 추가해 보겠습니다!**

**Seald로 엔드투엔드 암호화 추가하기 🔒💬**
------------------------------

### **Seald 계정 설정하기 👤**

Seald를 시작하려면 [여기에서](https://www.seald.io/create-sdk?ref=hackernoon.com) **무료 평가판 계정을 만드세요**.

Seald 대시보드에 도착하면 몇 개의 URL과 API 토큰이 표시됩니다.

다음 요소를 가져옵니다:

-   앱아이디
    
-   apiURL
    
-   keyStorageURL
    

이 키를 `설정.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는 백엔드에서 비밀 번호와 비밀 ID를 사용하여 생성해야 합니다.

대시보드 랜딩 페이지에서 `백엔드/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.js에는` ID를 생성하는 함수와 검색하는 함수 두 개를 추가합니다. **ID를 생성하려면** 계정 생성 시 반환되는 라이선스 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의 `암호화 세션과` 연결됩니다.

대화방을 만들 때마다 **한 줄만 추가하여 암호화 세션을 생성한** 다음 사용하면 됩니다:

```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을` 가져와야 합니다. 이는 암호화된 메시지나 파일에서 검색할 수 있습니다. 일단 확보하면 컴포넌트 참조에 보관합니다.

그런 다음 `세션.encryptMessage`, 세션.`encryptFile`, `세션.decryptMessage` 및 `세션.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 */
  }
}
```

### **그룹 구성원 관리하기 👨‍👩‍👦**

그룹 채팅에도 `암호화 세션이` 있습니다. 그룹이 생성될 때마다 그룹을 만들어야 합니다:

```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)( )로 문의해 주세요.

여러분이 구축한 것을 보고 싶어요 🥳.

#### 콘텐츠

[콘텐츠](#h-0)[개요엔드투엔드 암호화를 사용하는](#h-1)[이유는](#h-0) 무엇인가요?[PubNub 암호화 후크 대신 Seald.io를 사용하는](#h-2)[이유는](#h-1) 무엇인가요?[👀목표](#h-2)[🏆구현하기](#h-3)[🧠PubNub 계정 설정하기](#h-5)[👤PubNub을 사용하여 기본 채팅](#h-6)[구축하기](#h-5)[💬초기 앱 상태](#h-7)[가져오기](#h-6)[🌱대화방에서 메시지](#h-8)[수신](#h-7)[및 보내기](#h-8)[📩그룹 채팅](#h-9)[관리하기](#h-8)[👨‍👩‍👦‍👦Adding](#h-10) 종단간[엔드 암호화 🔒💬Seal](#h-10)[계정](#h-11)[설정하기](#h-10)[👤Seal](#h-11)[SDK](#h-12)[인스턴스화하기](#h-11)[💡Seal ID](#h-13)[생성](#h-12)[및 검색하기](#h-13)[🔑메시지 암호화 및 복호화](#h-14)[시작하기](#h-13)[🔒🔓그룹 멤버](#h-15)[관리하기](#h-14)[👨‍👩‍👦Conclusion](#h-16) ✅

펍넙이 어떤 도움을 줄 수 있나요?
===================

이 기사는 원래 [PubNub.com에](https://www.pubnub.com/blog/build-an-end-to-end-encrypted-chat-with-seald-and-pubnub/) 게시되었습니다.

저희 플랫폼은 개발자가 웹 앱, 모바일 앱, IoT 디바이스를 위한 실시간 상호작용을 구축, 제공, 관리할 수 있도록 지원합니다.

저희 플랫폼의 기반은 업계에서 가장 크고 확장성이 뛰어난 실시간 에지 메시징 네트워크입니다. 전 세계 15개 이상의 PoP가 월간 8억 명의 활성 사용자를 지원하고 99.999%의 안정성을 제공하므로 중단, 동시 접속자 수 제한 또는 트래픽 폭증으로 인한 지연 문제를 걱정할 필요가 없습니다.

PubNub 체험하기
-----------

[라이브 투어를](https://www.pubnub.com/tour/introduction/) 통해 5분 이내에 모든 PubNub 기반 앱의 필수 개념을 이해하세요.

설정하기
----

PubNub [계정에](https://admin.pubnub.com/signup/) 가입하여 PubNub 키에 무료로 즉시 액세스하세요.

시작하기
----

사용 사례나 [SDK에](https://www.pubnub.com/docs) 관계없이 [PubNub 문서를](https://www.pubnub.com/docs) 통해 바로 시작하고 실행할 수 있습니다.