Contenidos
Para agregar SSL a tus códigos de servidor y cliente, necesitarás configurar correctamente el contexto SSL en ambas partes. Aquí tienes una guía general sobre cómo hacerlo:
Genera los certificados: Necesitarás generar un par de claves pública y privada para el servidor y el cliente. Puedes utilizar herramientas como OpenSSL o Java Keytool para generar los certificados.
Crear certificado con Keytool para el servidor
1 |
keytool -genkeypair -alias servidor -keyalg RSA -keysize 2048 -validity 365 -keystore servidor_keystore.p12 -storetype PKCS12 -storepass 1234567 |
Exportar certificado .cert con Keytool para el cliente
1 |
keytool -export -alias servidor -keystore servidor_keystore.p12 -file servidor_publico.cer -storepass 1234567 |
Crear servidor seguro
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
import java.io.DataInputStream import java.io.DataOutputStream import java.io.FileInputStream import java.net.SocketException import java.security.KeyStore import javax.net.ssl.* class Servidor { private val puerto = 5556 private val keyStorePath = "/Users/jesusn/IdeaProjects/untitled31/servidor_keystore.p12" private val keyStorePassword = "1234567" private val keyPassword = "1234567" fun iniciarServidor() { try { val sslContext = configurarSSLContext() val sslServerSocketFactory = sslContext.serverSocketFactory val servidorSSL = sslServerSocketFactory.createServerSocket(puerto) as SSLServerSocket servidorSSL.needClientAuth = false // No requerimos autenticación de cliente println("🔐 Servidor SSL iniciado en el puerto $puerto. Esperando conexiones...") while (true) { try { val clienteConectado = servidorSSL.accept() as SSLSocket println("✅ Cliente conectado desde: ${clienteConectado.inetAddress.hostAddress}") // Iniciar handshake antes de manejar al cliente clienteConectado.startHandshake() // Manejar cliente en un hilo separado Thread { manejarCliente(clienteConectado) }.start() } catch (e: SocketException) { println("⚠️ Error en la conexión: ${e.message}") } } } catch (e: Exception) { println("❌ Error al iniciar el servidor: ${e.message}") e.printStackTrace() } } private fun configurarSSLContext(): SSLContext { val keyStore = KeyStore.getInstance("JKS") keyStore.load(FileInputStream(keyStorePath), keyStorePassword.toCharArray()) val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) keyManagerFactory.init(keyStore, keyPassword.toCharArray()) val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustManagerFactory.init(keyStore) val sslContext = SSLContext.getInstance("TLSv1.3") // Compatible con TLS 1.3 y 1.2 sslContext.init(keyManagerFactory.keyManagers, trustManagerFactory.trustManagers, null) return sslContext } private fun manejarCliente(cliente: SSLSocket) { try { println("📡 Comunicación iniciada con el cliente...") val flujoEntrada = DataInputStream(cliente.getInputStream()) val flujoSalida = DataOutputStream(cliente.getOutputStream()) val mensajeRecibido = flujoEntrada.readUTF() println("📩 Mensaje recibido: $mensajeRecibido") // Enviar respuesta al cliente flujoSalida.writeUTF("Saludos del servidor SSL!") flujoSalida.flush() println("📤 Respuesta enviada al cliente. Esperando confirmación...") // Cerrar conexiones de forma segura flujoEntrada.close() flujoSalida.close() if (!cliente.isClosed) { cliente.close() println("🔒 Cliente desconectado correctamente.") } else { println("⚠️ Cliente ya estaba desconectado.") } } catch (e: Exception) { println("❌ Error con el cliente: ${e.message}") e.printStackTrace() } } } fun main() { Servidor().iniciarServidor() } |
Crear cliente seguro en Android
Cliente subido en Github: https://github.com/jesusninoc/ClienteAndroidSSL2025
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
package com.example.myapplication import android.content.Context import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.DataInputStream import java.io.DataOutputStream import java.io.InputStream import java.net.SocketException import java.security.KeyStore import java.security.cert.Certificate import java.security.cert.CertificateFactory import javax.net.ssl.* class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ClientViewModel(this) setContent { SecureClientApp(viewModel) } } } @Composable fun SecureClientApp(viewModel: ClientViewModel) { var response by remember { mutableStateOf("Esperando respuesta del servidor...") } Column( modifier = Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.Center ) { Text(text = response, style = MaterialTheme.typography.headlineMedium) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { viewModel.sendMessage { serverResponse -> response = serverResponse } }) { Text("Enviar Mensaje al Servidor") } } } class ClientViewModel(private val context: Context) : ViewModel() { fun sendMessage(onResponseReceived: (String) -> Unit) { viewModelScope.launch(Dispatchers.IO) { var cliente: SSLSocket? = null var flujoEntrada: DataInputStream? = null var flujoSalida: DataOutputStream? = null var socketCerrado = false // Bandera para evitar intentos de cierre duplicados try { val host = "192.168.0.38" val puerto = 5556 val sslSocketFactory = createSSLSocketFactory(context) cliente = sslSocketFactory.createSocket(host, puerto) as SSLSocket cliente.soTimeout = 10000 try { cliente.startHandshake() Log.d("SSLClient", "Handshake SSL completado correctamente") } catch (e: Exception) { Log.e("SSLClient", "Error en el handshake SSL/TLS: ${e.message}", e) onResponseReceived("Error en el handshake SSL/TLS: ${e.message}") return@launch } flujoSalida = DataOutputStream(cliente.getOutputStream()) flujoEntrada = DataInputStream(cliente.getInputStream()) flujoSalida.writeUTF("Saludos al SERVIDOR DESDE EL CLIENTE") flujoSalida.flush() Log.d("SSLClient", "Mensaje enviado al servidor, esperando respuesta...") try { val serverResponse = flujoEntrada.readUTF() // Esperar la respuesta del servidor Log.d("SSLClient", "Respuesta recibida: $serverResponse") // Actualizar la UI con la respuesta onResponseReceived(serverResponse) } catch (e: Exception) { Log.e("SSLClient", "Error al leer la respuesta: ${e.message}", e) onResponseReceived("Error al leer la respuesta: ${e.message}") } finally { // Cerrar los flujos y el socket solo si aún no están cerrados if (!socketCerrado) { try { flujoEntrada?.close() flujoSalida?.close() if (!cliente.isClosed) { cliente.close() socketCerrado = true // Marcar el socket como cerrado Log.d("SSLClient", "Socket cerrado correctamente.") } } catch (e: SocketException) { // No mostrar advertencia innecesaria } catch (e: Exception) { Log.e( "SSLClient", "Error inesperado al cerrar el socket: ${e.message}", e ) } } } } catch (e: Exception) { Log.e("SSLClient", "Error en la conexión: ${e.message}", e) onResponseReceived("Error en la conexión: ${e.message}") } } } private fun createSSLSocketFactory(context: Context): SSLSocketFactory { try { // Cargar el certificado directamente desde `res/raw/` val certificateFactory = CertificateFactory.getInstance("X.509") val certInputStream: InputStream = context.resources.openRawResource(R.raw.servidor_publico) val certificado: Certificate = certificateFactory.generateCertificate(certInputStream) certInputStream.close() // Crear un KeyStore y agregar el certificado del servidor val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) keyStore.load(null, null) // Inicializar un KeyStore vacío keyStore.setCertificateEntry("servidor", certificado) // Crear el TrustManager con el certificado val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustManagerFactory.init(keyStore) // Configurar SSLContext con TLS 1.3 val sslContext = SSLContext.getInstance("TLSv1.3") sslContext.init(null, trustManagerFactory.trustManagers, null) return sslContext.socketFactory } catch (e: Exception) { throw RuntimeException("❌ Error al cargar el certificado en Android: ${e.message}", e) } } } |