readme นี้จะนำคุณผ่านขั้นตอนเพื่อนำแอพ Android ของ E2ee Nexmo มาส่งข้อความถึงชีวิต นอกจากนี้ยังพยายามอธิบายการเปลี่ยนแปลงที่สำคัญของรหัส NEXMO ดั้งเดิมบน GitHub
ก่อนอื่นมาเริ่มต้นด้วยการทบทวนอย่างรวดเร็วของสิ่งที่ E2EE (การเข้ารหัสแบบ end-to-end) คือและวิธีการทำงาน E2EE นั้นง่าย: เมื่อคุณพิมพ์ข้อความแชทจะได้รับการเข้ารหัสบนอุปกรณ์มือถือของคุณ (หรือในเบราว์เซอร์ของคุณ) และได้รับการถอดรหัสเฉพาะเมื่อคู่แชทของคุณได้รับและต้องการแสดงในหน้าต่างแชท
หมายเหตุ: ต้องอัปเดตรูปภาพจะอ้างอิงโดยตรงจากโครงการ back4app
ข้อความยังคงเข้ารหัสขณะเดินทางผ่าน Wi-Fi และอินเทอร์เน็ตผ่านคลาวด์ / เว็บเซิร์ฟเวอร์ลงในฐานข้อมูลและระหว่างทางกลับไปที่คู่แชทของคุณ กล่าวอีกนัยหนึ่งไม่มีเครือข่ายหรือเซิร์ฟเวอร์ใดที่มีเงื่อนงำว่าคุณสองคนกำลังสนทนากันอย่างไร
หมายเหตุ: ต้องอัปเดตรูปภาพจะอ้างอิงโดยตรงจากโครงการ back4app
สิ่งที่ยากในการเข้ารหัสแบบ end-to-end คือหน้าที่ของการจัดการคีย์การเข้ารหัสในลักษณะที่เฉพาะผู้ใช้ที่เกี่ยวข้องในการแชทเท่านั้นที่สามารถเข้าถึงได้และไม่มีใครอื่น และเมื่อฉันเขียน“ ไม่มีใครอื่น” ฉันหมายถึงมันจริงๆ: แม้แต่คนวงในของผู้ให้บริการคลาวด์ของคุณหรือแม้แต่คุณผู้พัฒนาก็ยังออกมา [ไม่มีข้อผิดพลาดโดยบังเอิญ] [_ ผิดพลาด] หรือการจ้องมองที่ถูกต้องตามกฎหมายเป็นไปได้ การเขียน crypto โดยเฉพาะอย่างยิ่งสำหรับหลายแพลตฟอร์มนั้นยาก: การสร้างตัวเลขสุ่มที่แท้จริงการเลือกอัลกอริทึมที่เหมาะสมและการเลือกโหมดการเข้ารหัสที่ถูกต้องเป็นเพียงตัวอย่างเล็ก ๆ น้อย ๆ ที่ทำให้นักพัฒนาส่วนใหญ่โบกมือในอากาศและจบลงด้วยการไม่ทำ
เทคโนโลยีการเข้ารหัสแบบ end-to-end ของ Virgil ช่วยให้นักพัฒนา NEXMO ไม่สนใจรายละเอียดที่น่ารำคาญเหล่านี้และเข้ารหัสข้อความแชทในแอพของผู้ใช้อย่างรวดเร็วและง่ายดาย
สำหรับอินโทรนี่คือวิธีที่เราจะอัพเกรดแอพ Nexmo Android ให้เข้ารหัสแบบ end-to-end:
หมายเหตุ: ต้องอัปเดตรูปภาพจะอ้างอิงโดยตรงจากโครงการ back4app
เราจะเผยแพร่กุญแจสาธารณะของผู้ใช้ไปยังบริการบัตรของ Virgil เพื่อให้ผู้ใช้แชทสามารถค้นหาซึ่งกันและกันและสามารถเข้ารหัสข้อความซึ่งกันและกัน คีย์ส่วนตัวจะอยู่บนอุปกรณ์ผู้ใช้
โอเคพูดมากพอ: เริ่มทำกันเถอะ!
แอปพลิเคชันเซิร์ฟเวอร์ Appi ได้รับการติดตั้งแล้วและพร้อมใช้งานโดยลิงก์
เปิดคลาส Virgilfacade และกำหนดค่าคงที่จากตารางด้านล่าง
| ชื่อคงที่ | คำอธิบาย |
|---|---|
| virgil_access_token | โทเค็นการเข้าถึงแอปพลิเคชัน Virgil ของคุณ คุณควรสร้างโทเค็นนี้บนแผงควบคุมหรือใช้อันที่มีอยู่ |
| virgil_app_public_key | คีย์สาธารณะแอปพลิเคชัน Virgil ของคุณเป็นสตริงที่เข้ารหัส Base64 |
| virgil_auth_public_key | Virgil Authentication Server Public Key เป็นสตริงที่เข้ารหัส Base64 |
| auth_server_url | URL Application API Server |
สองคำสำคัญที่นี่:
ในแอพส่งข้อความในแอปเวอร์ชัน E2EE เราจะสร้างคีย์ส่วนตัวสำหรับผู้ใช้ทุกคนเมื่อเวลาลงทะเบียน จากนั้นเราจะสร้างคีย์สาธารณะของผู้ใช้และเผยแพร่ในรูปแบบของการ์ด Virgil ใหม่สำหรับผู้ใช้เพื่อให้ผู้ใช้รายอื่นสามารถค้นหาและเข้ารหัสข้อความสำหรับเรา
// Generate private key
val virgilKey = virgilApi.keys.generate()
// Create Virgil Card
val customFields = HashMap < String , String >()
customFields.put( " deviceId " , Settings . Secure . ANDROID_ID )
val virgilCard = virgilApi.getCards().create(userName, virgilKey,
" name " , customFields)ในการสร้างการ์ด Virgil คุณจะต้องใช้คีย์ส่วนตัวของแอปพลิเคชัน Virgil (มิฉะนั้นทุกคนสามารถเผยแพร่การ์ดสำหรับแอปของคุณโดยไม่ต้องควบคุม) เนื่องจากคุณไม่ควรเก็บคีย์นี้ไว้บนอุปกรณ์มือถือเราจะเก็บไว้ในเว็บแอปของคุณและทำให้เว็บแอปของคุณยืนยันผู้ใช้ก่อนสร้างการ์ด
val csr = CSR (virgilCard.export())
val response = NexmoApp .instance.serverClient.signup(csr).execute()
var registrationData = response.body() !! registrationData ยังมี JWT ซึ่งควรใช้ในการเข้าสู่ระบบ NEXMO ด้วย ConversationClient
แอพมือถือของคุณเป็นสถานที่เดียวที่จัดเก็บคีย์ส่วนตัวของคุณ ดังนั้นคุณควรจัดเก็บคีย์ส่วนตัวเพื่อใช้ในอนาคต หากคุณสูญเสียคีย์ส่วนตัวของคุณคุณจะไม่สามารถถอดรหัสข้อความที่ส่งถึงคุณได้
NexmoApp .instance.db.userDao().insert( User (registrationData.user.id,
userName, registrationData.user.href, createdVirgilCard.id,
registrationData.user.virgilCard, virgilKey.privateKey.value)) เริ่มต้น SecureChat และสร้างคีย์แบบครั้งเดียวสำหรับการใช้งานในอนาคต
crypto = VirgilCrypto ()
keyStorage = JsonFileKeyStorage (
context.getFilesDir().getAbsolutePath(), userName + " .ks " )
userDataStorage = JsonFileUserDataStorage (
context.getFilesDir().getAbsolutePath(), userName + " .ds " )
// Configure PFS
var chatContext = SecureChatContext (virgilCard, privateKey,
crypto, VIRGIL_ACCESS_TOKEN )
chatContext.keyStorage = keyStorage
chatContext.deviceManager = DefaultDeviceManager ()
chatContext.userDataStorage = userDataStorage
secureChat = SecureChat (chatContext)
secureChat?.rotateKeys( 10 )เมื่อเข้าสู่ระบบเราจะได้รับโทเค็นการตรวจสอบความถูกต้องของ Virgil จากเซิร์ฟเวอร์ ดูรายละเอียดการไหลตามลิงค์
// Get challenge message
val challengeMessage = this .authClient.getChallengeMessage(cardId)
// Decode encrypted message
val decodedMessage = this .crypto.decrypt(
ConvertionUtils .base64ToBytes(challengeMessage.encryptedMessage),
this .privateKey)
// Encrypt decoded message with application public key
val appPublicKey = this .crypto.importPublicKey(
ConvertionUtils .base64ToBytes( VIRGIL_AUTH_PUBLIC_KEY ))
val newEncryptedMessage =
this .crypto.encrypt(decodedMessage, appPublicKey)
val message = ConvertionUtils .toBase64String(newEncryptedMessage)
// Send acknowledge to auth server
val code = this .authClient.acknowledge(
challengeMessage.authorizationGrantId, message)
// Obtain access token
val accessTokenResponse = this .authClient.obtainAccessToken(code)
val virgilToken = accessTokenResponse.accessToken เข้าสู่ระบบ Nexmo ด้วย ConversationClient
val response = NexmoApp .instance.serverClient
.jwt( " Bearer ${virgilToken} " ).execute()
val jwt = response.body() !! .jwtมาโหลดรายการผู้ใช้ที่ลงทะเบียนกันเถอะ
val virgilToken = VirgilFacade .instance.getVirgilToken()
val response = NexmoApp .instance.serverClient
.getUsers( " Bearer ${virgilToken} " ).execute()
var users = response.body()เริ่มการสนทนา
conversationClient.newConversation( true , userName,
object : RequestHandler < Conversation > {
override fun onError ( apiError : NexmoAPIError ? ) {
closeWithError( " Conversation is not created " , apiError)
}
override fun onSuccess ( result : Conversation ? ) {
Log .d( TAG , " Created conversation ${result?.conversationId} for user ${userName} " )
mConversation = result
mConversation?.invite(userName, object : RequestHandler < Member > {
override fun onError ( apiError : NexmoAPIError ? ) {
closeWithError( " Can't invite user ${userName} into conversation " , apiError)
}
override fun onSuccess ( result : Member ? ) {
Log .d( TAG , " User ${result?.name} invited into conversation " )
mMemberCard = VirgilFacade .instance.virgilApi.cards.find(result?.name).firstOrNull()?.model
// initizlize conversation
.. .
}
})
}
})คุณจะต้องใช้การ์ด Virgil ของผู้ใช้ที่คุณกำลังเริ่มการสนทนาด้วย
val userName = NexmoUtils .getConversationPartner(mConversation !! )?.name
mMemberCard = VirgilFacade .instance.virgilApi.cards.find(userName).firstOrNull()?.modelตอนนี้คุณสามารถส่งและรับข้อความ
// Get active session
var secureSession = secureChat !! .activeSession(recipientCard.getId());
// If no session, start a new one
if (secureSession == null ) {
secureSession = secureChat !! .startNewSession(recipientCard, null );
}
// Encrypt message text
val encryptedText = secureSession.encrypt(text);คุณไม่สามารถถอดรหัสข้อความที่คุณเข้ารหัสได้ ดังนั้นคุณควรจัดเก็บข้อความต้นฉบับในพื้นที่ เพื่อให้แน่ใจว่าข้อความไม่ได้ถูกดัดแปลงให้สร้างรหัสแฮชจากข้อความที่เข้ารหัส
mConversation?.sendText(encryptedMessage,
object : RequestHandler < Event > {
override fun onSuccess ( result : Event ? ) {
// Save message in database
val hash = encryptedMessage.hashCode().toString()
val msg = Message (hash, mConversation !! .conversationId, result !! .member.userId, text)
messageDao.insert(msg)
}
override fun onError ( apiError : NexmoAPIError ? ) {
Log .e( TAG , " Send message error " , apiError)
}
})มาระบุผู้ส่งข้อความก่อน
if (conversationClient.user.userId.equals(textMessage.member.userId)) {
// This message was sent by myself. Find in database
....
} else {
// Message from another conversation member
.. .
}หากเป็นข้อความของคุณเองเพียงแค่รับจากฐานข้อมูลด้วยรหัสแฮชข้อความที่เข้ารหัส
val hash = textMessage.text.hashCode().toString()
val message = messageDao.getMessage(mConversation !! .conversationId, hash)
val decryptedText = message.textหากข้อความถูกส่งโดยคนอื่นเราจะถอดรหัสมันกันเถอะ
// Loadup user session
var secureSession = secureChat !! .loadUpSession(senderCard, encryptedMessage, null )
val decryptedText = secureSession.decrypt(encryptedMessage)