Ce ReadMe vous guide à travers les étapes pour donner vie à l'application Android E2EE NEXMO Messagerie. Il tente également d'expliquer les modifications clés du code NEXMO d'origine sur GitHub.
Tout d'abord, commençons par un rafraîchissement rapide de ce qu'est E2EE (cryptage de bout en bout) et comment cela fonctionne. E2EE est simple: lorsque vous tapez un message de chat, il est chiffré sur votre appareil mobile (ou dans votre navigateur) et est déchiffré uniquement lorsque votre partenaire de chat le reçoit et souhaite l'afficher dans la fenêtre de chat.
Remarque: l'image doit être mise à jour, elle est directement référée du projet Back4App
Le message reste crypté pendant qu'il se déplace sur le Wi-Fi et Internet, via le serveur cloud / web, dans une base de données et sur le chemin du retour à votre partenaire de chat. En d'autres termes, aucun des réseaux ou des serveurs n'a une idée de ce dont vous discutez.
Remarque: l'image doit être mise à jour, elle est directement référée du projet Back4App
Ce qui est difficile dans le cryptage de bout en bout, c'est la tâche de gérer les clés de chiffrement d'une manière que seuls les utilisateurs impliqués dans le chat peuvent y accéder et personne d'autre. Et quand j'écris «personne d'autre», je le pense vraiment: même les initiés de votre fournisseur de cloud ou même vous, le développeur, êtes sortis; [Aucune erreur accidentelle] [_ erreurs] ou un pipe légalement appliqué sont possibles. L'écriture de Crypto, en particulier pour plusieurs plates-formes est difficile: générer de vrais nombres aléatoires, choisir les bons algorithmes et choisir les bons modes de cryptage ne sont que quelques exemples qui font que la plupart des développeurs agitent leurs mains dans l'air et finissent tout simplement à ne pas le faire.
La technologie de cryptage de bout en bout de Virgile permet aux développeurs NEXMO d'ignorer tous ces détails ennuyeux et de crypter rapidement et simplement de bout en bout de bout en bout.
Pour une intro, c'est ainsi que nous allons mettre à niveau l'application Nexmo Android pour être cryptée de bout en bout:
Remarque: l'image doit être mise à jour, elle est directement référée du projet Back4App
Nous publierons les clés publiques des utilisateurs au service de cartes de Virgile afin que les utilisateurs de chat puissent se consulter les uns les autres et se chiffrer les messages les uns pour les autres. Les clés privées resteront sur les appareils utilisateur.
Ok, assez de discussion: commençons à faire!
Le serveur API d'application est déjà installé et disponible par le lien
Ouvrez la classe Virgilfacade et définissez les constantes du tableau ci-dessous
| Nom constant | Description |
|---|---|
| Virgil_access_token | Le jeton d'accès à application Virgile. Vous devez générer ce jeton sur le tableau de bord ou utiliser celui existant |
| Virgil_app_public_key | La clé publique de votre application Virgile comme chaîne codée Base64 |
| Virgil_auth_public_key | Clé publique du serveur d'authentification Virgile comme chaîne codée Base64 |
| Auth_server_url | URL du serveur API d'application |
Deux termes importants ici:
Dans la version E2EE de l'application de messagerie dans l'application, nous générerons une clé privée pour chaque utilisateur à l'heure de l'inscription. Nous générerons ensuite la clé publique de l'utilisateur et la publierons sous une forme de nouvelle carte Virgile pour l'utilisateur, afin que d'autres utilisateurs puissent le trouver et nous crypter des messages.
// 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)Pour créer une carte Virgile, vous aurez besoin de la clé privée de votre application Virgile (sinon, n'importe qui peut publier des cartes pour votre application sans votre contrôle). Étant donné que vous ne devriez pas stocker cette clé sur les appareils mobiles, nous le garderons dans votre application Web et que votre application Web vérifie les utilisateurs avant la création de cartes.
val csr = CSR (virgilCard.export())
val response = NexmoApp .instance.serverClient.signup(csr).execute()
var registrationData = response.body() !! RegistrationData contient également JWT qui doit être utilisé pour se connecter NEXMO avec ConversationClient .
Votre application mobile est le seul endroit où votre clé privée est stockée. Ainsi, vous devez stocker une clé privée pour une utilisation future. Si vous perdez votre clé privée, vous ne pourrez pas décrypter des messages qui vous sont envoyés.
NexmoApp .instance.db.userDao().insert( User (registrationData.user.id,
userName, registrationData.user.href, createdVirgilCard.id,
registrationData.user.virgilCard, virgilKey.privateKey.value)) Initialisez SecureChat et générez des clés ponctuelles pour une utilisation future.
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 )Lors de la connexion, nous obtenons un jeton d'authentification Virgil du serveur. Voir les détails du flux par le lien.
// 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 Connectez-vous Nexmo avec ConversationClient .
val response = NexmoApp .instance.serverClient
.jwt( " Bearer ${virgilToken} " ).execute()
val jwt = response.body() !! .jwtChargez la liste des utilisateurs enregistrés.
val virgilToken = VirgilFacade .instance.getVirgilToken()
val response = NexmoApp .instance.serverClient
.getUsers( " Bearer ${virgilToken} " ).execute()
var users = response.body()Commencer une conversation.
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
.. .
}
})
}
})Vous aurez besoin d'une carte Virgile de l'utilisateur avec lequel vous commencez une conversation.
val userName = NexmoUtils .getConversationPartner(mConversation !! )?.name
mMemberCard = VirgilFacade .instance.virgilApi.cards.find(userName).firstOrNull()?.modelVous pouvez maintenant envoyer et recevoir des messages.
// 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);Vous ne pouvez pas déchiffrer le message que vous avez crypté. Par conséquent, vous devez stocker le message d'origine localement. Pour vous assurer que le message n'est pas falsifié, créez un code de hachage à partir du texte chiffré.
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)
}
})Identifions d'abord l'expéditeur du message.
if (conversationClient.user.userId.equals(textMessage.member.userId)) {
// This message was sent by myself. Find in database
....
} else {
// Message from another conversation member
.. .
}Si c'est votre propre message, obtenez-le simplement de la base de données par le code de hachage texte chiffré.
val hash = textMessage.text.hashCode().toString()
val message = messageDao.getMessage(mConversation !! .conversationId, hash)
val decryptedText = message.textSi le message est envoyé par quelqu'un d'autre, décryptons-le.
// Loadup user session
var secureSession = secureChat !! .loadUpSession(senderCard, encryptedMessage, null )
val decryptedText = secureSession.decrypt(encryptedMessage)