Skip to main content
X에서 종단 간 암호화된 다이렉트 메시지를 주고받기: 키를 설정하고, 대화를 초기화하며, 메시지를 보내고, 수신 트래픽을 복호화합니다. X Chat 앱은 두 가지 요소를 함께 사용합니다:
전제 조건
  • 개발자 계정과 OAuth 2.0용으로 구성된 앱
  • dm.read, dm.write, tweet.read, users.read 권한이 있는 사용자 액세스 토큰

1. 의존성 설치

PyPI 패키지는 chatxdk이며 chat_xdk로 import합니다. Python 3.10+이 필요합니다.
사용자 OAuth 2.0 액세스 토큰으로 API 클라이언트를 생성합니다:

2. 기존 키로 Chat XDK 초기화

이 단계는 이미 가지고 있는 키를 로드합니다—이 아이덴티티가 이전에 최초 설정을 완료한 경우 사용하세요:
  • 보안 키 백업: 공개 키 레코드의 juicebox_config로 SDK를 구성한 다음 패스코드로 unlock하여 개인 키를 복구합니다(예: 새 기기에서).
  • 키 blob: 이전에 export_keys로 내보낸 blob과 함께 등록된 키 버전을 전달하여 import_keys를 호출합니다(Rust와 Go에서는 이 변형을 import_keys_with_version / ImportKeysWithVersion이라고 부릅니다).
그런 다음 **set_identity(user_id, signing_key_version)**을 사용자 ID와 레코드의 public_key_version으로 한 번 호출합니다. 이는 세션 아이덴티티를 저장합니다: 이후 모든 encrypt와 prepare 호출은 이 아이덴티티로 서명되므로, 호출마다 발신자 ID나 서명 키 버전을 전달할 필요가 없습니다. 처음 설정 중인가요? 동일한 방식으로 SDK를 생성하되 unlock/import_keys를 건너뛰고, 키를 생성하고 백업 및 등록하려면 3단계로 계속 진행하세요.
서버와 봇 샘플은 종종 키 blob(export_keys / import_keys)을 사용합니다. 클라이언트 앱은 종종 보안 키 백업(패스코드를 사용하는 setup / unlock)을 사용합니다. 두 경로 모두에 대해서는 Chat XDK 레퍼런스를 참고하세요.
자체 키를 가져오시나요? import_keys는 Chat XDK의 export_keys가 생성한 불투명 blob만 받습니다—이는 원시나 PEM 인코딩된 P-256 키가 아니라, 전체 키 상태의 버전 관리된 비공개 직렬화입니다. 이 blob을 직접 만들 수는 없습니다: generate_keypairs(3단계)로 키를 생성하고, blob을 한 번 내보내어 base64로 인코딩하여 저장하세요. 수작업으로 만들거나 수정된 blob은 import에 실패합니다.

3. 키 생성 및 등록(최초 설정)

2단계에서 기존 키를 로드한 경우 이 단계를 건너뛰세요. 그렇지 않은 경우, 새 아이덴티티에 대한 일회성 설정은 세 가지를 수행합니다:
  1. 키쌍 생성generate_keypairs가 아이덴티티 및 서명 키쌍을 생성합니다.
  2. 개인 키 저장 — 패스코드로 setup을 호출하면 보안 키 백업에 저장하고(클라이언트), export_keys는 안전하게 저장할 키 blob을 반환합니다(서버 및 봇).
  3. 공개 키 등록 — add-public-key 엔드포인트에 등록 페이로드를 POST하여 다른 사람이 당신에게 암호화하고 당신의 서명을 검증할 수 있게 합니다.
등록의 키 버전으로 set_identity를 호출하여 마무리하면, 이 세션은 새 아이덴티티로 서명합니다.
모든 바인딩(Python, TypeScript, Go, Rust, C#, Java)에 대한 바로 실행 가능한 일회성 등록 스크립트가 chat-xdk/examples에 있습니다. 새 아이덴티티를 온보딩하기만 하면 될 때는 아래 흐름을 손수 구현하는 대신 이를 사용하세요.
보안 키 백업에는 강력한 패스코드를 사용하세요. 패스코드를 잃거나 보호되지 않은 키 blob을 잃으면 과거 메시지를 복호화하지 못할 수 있습니다.

4. 대화 키 설정

**prepare_conversation_key_change**를 모든 참여자의 아이덴티티 공개 키와 함께 호출합니다. 발신자 아이덴티티는 2단계에서 설정한 세션에서 옵니다. 한 번의 호출로 새 대화 키가 생성되고, 각 참여자에 대해 암호화되며, 변경에 서명이 이루어집니다. 결과를 add conversation keys 엔드포인트(POST /2/chat/conversations/{id}/keys)에 POST하세요—본문은 conversation_key_version, conversation_participant_keys(SDK encrypted_key → API encrypted_conversation_key), 그리고 **action_signatures**가 필요합니다(필수이며, 이것이 없으면 API가 호출을 거부합니다). 전송에 사용할 원시 대화 키는 보관하세요. 응답은 정규 대화 ID(data.conversation_id—1:1의 경우 하이픈으로 연결된 쌍, 그룹의 경우 g가 접두된 ID)와 키 변경의 data.sequence_id를 반환합니다. 이후 요청에서는 클라이언트에서 다시 구성하는 대신 반환된 이 ID를 사용하세요. 나중에 동일한 호출로 키를 순환시킬 수도 있습니다: 기존 대화 ID를 prepare_conversation_key_change에 전달하고 새 키 버전으로 POST하세요. 대화 키가 노출된 것으로 의심되면 순환하세요—순환은 미래 메시지만 보호합니다. 이전 키 버전으로 암호화된 메시지는 그 버전을 가진 누구에게나 여전히 읽을 수 있습니다.
감싸기 전에 가져온 키를 검증하세요. prepare_conversation_key_change는 전달하는 모든 공개 키에 대해 새 대화 키를 암호화합니다. 각 가져온 레코드를 먼저 verify_key_binding(identity, signing, signature)로 확인하세요—공개 키 API에서 얻은 레코드의 public_key, signing_public_key, identity_public_key_signature 필드를 전달하세요—대체된 아이덴티티 키가 대화 키를 받지 못하도록 합니다.

5. 메시지 보내기

4단계의 원시 대화 키로 암호화합니다. SDK는 메시지 ID(UUID)를 생성하여 서명된 이벤트에 포함시키고 페이로드에 반환합니다—절대 직접 만들지 마세요. 전송 요청에서는 다음을 매핑하세요: API가 요구하는 경우 URL 경로에는 하이픈이 있는 대화 ID를 사용하세요(:-). SDK 자체는 유연합니다: encrypt_messageencrypt_reply는 당신이 보유한 어떤 형태의 ID든 받아들입니다—이벤트의 A:B, 목록이나 URL 경로의 A-B(순서 무관), 혹은 그저 수신자의 사용자 ID까지—그리고 서명 전에 정규화합니다. 그룹 ID(접두사 g)는 변경 없이 통과합니다.
스니펫은 이 흐름에서 방금 4단계에서 대화 키를 생성했기 때문에 명시적으로 전달합니다. 키 캐시가 켜져 있고 decrypt_events 실행이 대화의 키를 검증한 후에는(6단계), encrypt_message(conversation_id, text)만으로 충분합니다—SDK가 최신 검증된 키를 채웁니다. 재시도는 같은 암호화된 페이로드를 다시 보내야 하므로 ID가 두 번 생성되지 않습니다.

6. 수신 및 복호화

실시간 트래픽에는 웹훅 또는 활동 스트림을 사용하고, 히스토리에는 대화 events를 페이지 조회하세요.
  • 실시간 페이로드 필드: encoded_event, 선택적 conversation_key_change_event
  • 히스토리: GET /2/chat/conversations/{id}/events — 모든 이벤트에 **decrypt_events**와 meta.conversation_key_events를 함께 사용하는 것을 권장
  • 복호화하려면 발신자의 서명 키가 필요하므로 SDK가 각 메시지의 작성자를 검증할 수 있습니다. 이는 다른 참여자의 공개 키입니다—4단계에서 사용한 동일한 공개 키 엔드포인트에서 가져와 SigningKeyEntry로 필드를 매핑하세요(아래 스니펫에 매핑이 포함되어 있습니다).
  • 모든 호출에 서명 키를(그리고 decrypt_event의 경우 대화 키를) 전달하거나, 두 개의 선택적 세션 저장소를 한 번 설정한 후 짧은 호출 형태를 사용할 수 있습니다. 아래 스니펫은 저장소를 사용합니다: set_signing_keys(entries)는 참여자의 키를 보관하고, set_cache_keys(true)(기본은 꺼짐)는 각 대화의 최신 서명 검증된 키를 보관하여 이후 호출이 키 인자를 생략할 수 있게 합니다. 두 스타일 모두 동일하게 검증합니다.
  • JavaScript는 카멜케이스 이벤트 타입(message)을 사용하고, 다른 언어는 JSON에서 "Message"와 스네이크케이스 필드를 사용합니다.
서버리스나 멀티 인스턴스인가요? 서명 키 저장소와 키 캐시는 SDK 인스턴스의 메모리에 있습니다. 이 방식이 맞지 않는 경우—한 호출이 복호화하고 다른 호출이 전송하는—키를 명시적으로 전달하세요: decrypt_events(events, signing_keys), decrypt_event(event_b64, conversation_keys, signing_keys), 그리고 encrypt 메서드의 conversation_key/conversation_key_version 오버라이드를 사용하세요. decrypt_events가 반환하는 conversation_keys는 직접 저장하여 다시 전달하세요.
모든 언어에 대한 완전한 폴링 및 답장 봇: chat-xdk/examples.

모범 사례

  • 서명 키 저장소를 최신 상태로 유지하세요: 발신자가 새 키 버전을 등록할 때 전체 참여자 세트로 set_signing_keys를 다시 호출하고, 서명 검증 실패 시 새로 고치세요
  • 실시간 전달은 event_uuid로 중복 제거하세요