Este readme lo guía a través de los pasos para dar vida a la aplicación de mensajería E2EE NEXMO en la aplicación. También intenta explicar los cambios clave en el código NEXMO original en GitHub.
Primero, comencemos con un repaso rápido de lo que es E2EE (cifrado de extremo a extremo) y cómo funciona. E2EE es simple: cuando escribe un mensaje de chat, se encripta en su dispositivo móvil (o en su navegador) y se descifran solo cuando su compañero de chat lo recibe y quiere mostrarlo en la ventana de chat.
Nota: La imagen debe actualizarse, se refiere directamente desde el proyecto Back4App
El mensaje permanece encriptado mientras viaja a través de Wi-Fi e Internet, a través del servidor en la nube / web, en una base de datos y en el camino de regreso a su socio de chat. En otras palabras, ninguna de las redes o servidores tiene una pista de lo que ustedes dos están conversando.
Nota: La imagen debe actualizarse, se refiere directamente desde el proyecto Back4App
Lo que es difícil en el cifrado de extremo a extremo es la tarea de administrar las claves de cifrado de una manera que solo los usuarios involucrados en el chat pueden acceder a ellos y a nadie más. Y cuando escribo "nadie más", realmente lo digo en serio: incluso los expertos de su proveedor de nubes o incluso usted, el desarrollador, están fuera; [No hay errores accidentales] [_ errores] o es posible hacer cumplir legalmente. Escribir criptografía, especialmente para múltiples plataformas, es difícil: generar números aleatorios verdaderos, elegir los algoritmos correctos y elegir los modos de cifrado correctos son solo algunos ejemplos que hacen que la mayoría de los desarrolladores agiten sus manos en el aire y terminen sin hacerlo.
La tecnología de cifrado de extremo a extremo de Virgil permite a los desarrolladores de NEXMO ignorar todos estos detalles molestos y simplemente cifrar los mensajes de chat en la aplicación de sus usuarios.
Para una introducción, así es como actualizaremos la aplicación NEXMO Android para estar encriptada de extremo a extremo:
Nota: La imagen debe actualizarse, se refiere directamente desde el proyecto Back4App
Publicaremos las claves públicas de los usuarios para el servicio de tarjetas de Virgil para que los usuarios de chat puedan buscar entre sí y encriptar mensajes entre sí. Las claves privadas permanecerán en los dispositivos de usuario.
Ok, suficiente hablando: ¡Empecemos a hacer!
El servidor API de la aplicación ya está instalado y disponible por el enlace
Abra la clase Virgilfacade y defina constantes de la tabla a continuación
| Nombre constante | Descripción |
|---|---|
| Virgil_access_token | El token de acceso a la aplicación Virgil. Debe generar este token en el tablero o usar el existente |
| Virgil_app_public_key | La aplicación pública de la aplicación Virgil como cadena codificada por Base64 |
| Virgil_auth_public_key | Clave pública del servidor de autenticación de Virgil como cadena codificada por base64 |
| Auth_server_url | URL del servidor API de la aplicación |
Dos términos importantes aquí:
En la versión E2EE de la aplicación de mensajería en la aplicación, generaremos una clave privada para cada usuario en la hora de registro. Luego generaremos la clave pública del usuario y la publicaremos en forma de una nueva tarjeta Virgil para el usuario, para que otros usuarios puedan encontrarla y cifrar mensajes para nosotros.
// 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)Para crear una tarjeta Virgil, necesitará la clave privada de su aplicación Virgil (de lo contrario, cualquiera puede publicar tarjetas para su aplicación sin su control). Dado que no debe almacenar esta clave en dispositivos móviles, la mantendremos en su aplicación web y haremos que su aplicación web verifique los usuarios antes de la creación de tarjetas.
val csr = CSR (virgilCard.export())
val response = NexmoApp .instance.serverClient.signup(csr).execute()
var registrationData = response.body() !! RegistrationData también contiene JWT que debe usarse para iniciar sesión en NEXMO con ConversationClient .
Su aplicación móvil es el único lugar donde se almacena su clave privada. Por lo tanto, debe almacenar la clave privada para uso futuro. Si pierde su clave privada, no podrá descifrar los mensajes enviados a usted.
NexmoApp .instance.db.userDao().insert( User (registrationData.user.id,
userName, registrationData.user.href, createdVirgilCard.id,
registrationData.user.virgilCard, virgilKey.privateKey.value)) Inicialice SecureChat y genere claves únicas para uso futuro.
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 )Al iniciar sesión, obtenemos un token de autenticación Virgil del servidor. Vea los detalles del flujo por el enlace.
// 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 Iniciar sesión en Nexmo con ConversationClient .
val response = NexmoApp .instance.serverClient
.jwt( " Bearer ${virgilToken} " ).execute()
val jwt = response.body() !! .jwtCargamos la lista de usuarios registrados.
val virgilToken = VirgilFacade .instance.getVirgilToken()
val response = NexmoApp .instance.serverClient
.getUsers( " Bearer ${virgilToken} " ).execute()
var users = response.body()Comience una conversación.
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
.. .
}
})
}
})Necesitará una tarjeta Virgil del usuario con el que está comenzando a conversar.
val userName = NexmoUtils .getConversationPartner(mConversation !! )?.name
mMemberCard = VirgilFacade .instance.virgilApi.cards.find(userName).firstOrNull()?.modelAhora puede enviar y recibir mensajes.
// 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);No puedes descifrar el mensaje que cifraste. Por lo tanto, debe almacenar el mensaje original localmente. Para asegurarse de que el mensaje no esté alterado, cree un código hash desde el texto cifrado.
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)
}
})Identionamos primero el remitente del mensaje.
if (conversationClient.user.userId.equals(textMessage.member.userId)) {
// This message was sent by myself. Find in database
....
} else {
// Message from another conversation member
.. .
}Si es su propio mensaje, simplemente obténlo de la base de datos mediante el código hash de texto cifrado.
val hash = textMessage.text.hashCode().toString()
val message = messageDao.getMessage(mConversation !! .conversationId, hash)
val decryptedText = message.textSi el mensaje es enviado por otra persona, descifrarlo.
// Loadup user session
var secureSession = secureChat !! .loadUpSession(senderCard, encryptedMessage, null )
val decryptedText = secureSession.decrypt(encryptedMessage)