Этот Readme проходит через шаги, чтобы воплотить в жизнь приложение Android обмена E2EE Nexmo. Он также пытается объяснить ключевые изменения в исходном коде Nexmo на GitHub.
Во-первых, давайте начнем с быстрого освежения того, что такое E2EE (сквозное шифрование) и как оно работает. E2EE прост: когда вы вводите сообщение в чате, оно зашифруется на вашем мобильном устройстве (или в вашем браузере) и дешифрует только тогда, когда ваш партнер по чату получает его и хочет отобразить его в окне чата.
Примечание. Изображение должно быть обновлено, оно напрямую упоминается из проекта Back4App
Сообщение остается зашифрованным, когда оно путешествует по Wi-Fi и Интернету через облачный / веб-сервер, в базу данных и на обратном пути к вашему партнеру по чату. Другими словами, ни одна из сетей или серверов не имеет представления о том, о чем вы оба болтаете.
Примечание. Изображение должно быть обновлено, оно напрямую упоминается из проекта Back4App
Что трудно в сквозном шифровании, так это задача управления клавишами шифрования таким образом, чтобы только пользователи, участвующие в чате, могли получить к ним доступ, и никто больше. И когда я пишу «никто другой», я действительно имею в виду это: даже инсайдеры вашего облачного провайдера или даже вас, разработчика, вышли; [Нет случайных ошибок] [_ ошибки] или юридически принудительное мозоль. Написание крипто, особенно для нескольких платформ, сложно: генерирование истинных случайных чисел, выбор правильных алгоритмов и выбор правильных режимов шифрования - это всего лишь несколько примеров, которые заставляют большинство разработчиков махать руками в воздухе и в конечном итоге просто не делают этого.
ТЕХНИКА СЛОВНОГО ТЕХНИИ ВИРГИЛА позволяет разработчикам NEXMO игнорировать все эти раздражающие детали и быстро и просто сквозные сообщения в чате своих пользователей.
Для вступления, вот как мы обновим приложение Nexmo Android, чтобы быть зашифрованным:
Примечание. Изображение должно быть обновлено, оно напрямую упоминается из проекта Back4App
Мы опубликуем общедоступные ключи пользователей в службу карт Вирджила, чтобы пользователи чата могли искать друг друга и могли шифровать сообщения друг для друга. Частные ключи останутся на пользовательских устройствах.
Хорошо, достаточно разговоров: давайте начнем делать!
API -сервер приложения уже установлен и доступен по ссылке
Открыть класс Virgilfacade и определить константы из таблицы ниже
| Постоянное имя | Описание |
|---|---|
| Virgil_access_token | Ваше токен доступа к приложению вашего приложения. Вы должны генерировать этот токен на панели инструментов или использовать существующий |
| Virgil_app_public_key | Общедоступный ключ вашего приложения Virgil Application в качестве строки Base64-кодировки |
| Virgil_auth_public_key | Virgil Authentication Server Public Key в качестве строки Base64-кодировки |
| Auth_server_url | URL -адрес сервера API приложения |
Два важных термина здесь:
В версии E2EE в приложении In-App Messing Message мы генерируем закрытый ключ для каждого пользователя во время регистрации. Затем мы создадим открытый ключ пользователя и опубликуем его в форме новой карты 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
.. .
}
})
}
})Вам понадобится карта Вирджила пользователя, с которой вы начинаете разговор.
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)