此录像机将带您浏览E2EE Nexmo应用程序内消息Android应用程序的步骤。它还试图解释GitHub上原始Nexmo代码的关键更改。
首先,让我们从快速重新审查E2EE(端到端加密)及其工作原理。 E2EE很简单:当您输入聊天消息时,它会在移动设备(或浏览器中)加密,并且仅当您的聊天伙伴收到聊天伙伴并希望在聊天窗口中显示它时才被解密。
注意:需要更新图像,它直接从Back4App项目中引用
该消息在Wi-Fi和Internet,通过云 / Web服务器,数据库中以及返回您的聊天合作伙伴的途中保持加密。换句话说,网络或服务器都没有你们两个正在聊天的线索。
注意:需要更新图像,它直接从Back4App项目中引用
端到端加密困难的是,只有聊天中涉及的用户才能访问它们而没有其他人,以管理加密密钥的任务。当我写“没有其他人”时,我真的是说:即使是您的云提供商的内部人士,甚至您,开发人员也都出去了; [没有意外错误] [_错误]或合法执行的窥视是可能的。编写加密货币,尤其是对于多个平台来说很难:生成真实的随机数,选择正确的算法以及选择正确的加密模式只是几个示例,这些示例使大多数开发人员在空中挥舞着双手,最终不这样做。
Virgil的端到端加密技术使Nexmo开发人员能够忽略所有这些烦人的细节,并迅速而简单地端到端加密用户的应用程序内聊天消息。
对于介绍,这就是我们将如何升级Nexmo Android应用程序进行端到端加密:
注意:需要更新图像,它直接从Back4App项目中引用
我们将发布用户对Virgil卡服务的公共密钥,以便聊天用户能够互相查找并能够相互加密消息。私钥将留在用户设备上。
好的,足够说话:让我们开始做吧!
应用程序API服务器已经安装,链接可用
开放virgilfacade类并从下表定义常数
| 恒定名称 | 描述 |
|---|---|
| virgil_access_token | 您的Virgil应用程序访问令牌。您应该在仪表板上生成这个令牌或使用现有的令牌 |
| virgil_app_public_key | 您的virgil应用程序公钥为base64编码的字符串 |
| virgil_auth_public_key | Virgil身份验证服务器公共密钥作为base64编码的字符串 |
| auth_server_url | 应用程序API服务器URL |
这里有两个重要术语:
在应用程序内消息应用程序的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应用程序的私钥(否则,任何人都可以在不受控制的情况下发布您的应用程序卡)。由于您不应该将此密钥存储在移动设备上,因此我们将其保存在您的Web应用程序中,并使您的Web应用程序在创建卡之前验证用户。
val csr = CSR (virgilCard.export())
val response = NexmoApp .instance.serverClient.signup(csr).execute()
var registrationData = response.body() !! registrationData还包含JWT,该JWT应用于使用ConversationClient登录Nexmo。
您的移动应用程序是唯一存储私钥的地方。因此,您应该存储私钥以备将来使用。如果您丢失了私钥,您将无法解密发送给您的消息。
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使用ConversationClient登录Nexmo。
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)