Introducción: ¿por qué la autenticación "hecha en casa" es un riesgo?
\n\nImagina: estás escribiendo una aplicación en Kotlin, ya sea un cliente Android o un backend con Ktor/Spring Boot. Rápidamente creas un formulario de inicio de sesión, guardas la contraseña en SharedPreferences y te alegras de que todo funciona. Y una semana después recibes un mensaje: "Su aplicación fue hackeada, los datos se filtraron". ¿Te suena? Lamentablemente, es un clásico.
\n\nLa autenticación segura en Kotlin no es solo "verificar la contraseña". Es todo un ecosistema: hash, tokens, protección contra falsificación de solicitudes, almacenamiento seguro de secretos. En este artículo, analizaremos cómo evitar errores típicos y construir un sistema de inicio de sesión robusto. ¡Allá vamos!
\n\n1. Hash de contraseñas: bcrypt y sus amigos
\n\nLa primera regla de seguridad: nunca almacenes contraseñas en texto plano. Incluso si usas HTTPS, la base de datos puede verse comprometida. Tu tarea es almacenar solo el hash.
\n\n¿Por qué MD5 y SHA son malos?
\nEstos algoritmos son rápidos. Y los hashes rápidos son el sueño de un atacante: puede probar miles de millones de combinaciones por segundo. En su lugar, usa bcrypt, scrypt o Argon2. Son intencionalmente lentos y resistentes a ataques con GPU.
\n\nEjemplo en Kotlin usando la librería jbcrypt:
import org.mindrot.jbcrypt.BCrypt\n\nfun hashPassword(password: String): String {\n val salt = BCrypt.gensalt(12) // factor de costo 12 — óptimo\n return BCrypt.hashpw(password, salt)\n}\n\nfun checkPassword(password: String, hashed: String): Boolean {\n return BCrypt.checkpw(password, hashed)\n}\n\nImportante: la sal se genera automáticamente y se incrusta en el hash. No necesitas almacenarla por separado.
\n\n2. JWT: autenticación ligera y segura
\n\nJSON Web Token (JWT) es un estándar para transmitir afirmaciones (claims) entre partes. En Kotlin, puedes usar la librería java-jwt (Auth0) o jsonwebtoken (jjwt).
Generación de JWT en Kotlin
\n\nimport io.jsonwebtoken.Jwts\nimport io.jsonwebtoken.SignatureAlgorithm\nimport io.jsonwebtoken.security.Keys\nimport java.util.Date\n\nfun generateToken(userId: String, secretKey: String): String {\n val key = Keys.hmacShaKeyFor(secretKey.toByteArray())\n return Jwts.builder()\n .setSubject(userId)\n .setIssuedAt(Date())\n .setExpiration(Date(System.currentTimeMillis() + 3600_000)) // 1 hora\n .signWith(key, SignatureAlgorithm.HS256)\n .compact()\n}\n\nValidación del token
\n\nfun validateToken(token: String, secretKey: String): String? {\n return try {\n val key = Keys.hmacShaKeyFor(secretKey.toByteArray())\n val claims = Jwts.parserBuilder()\n .setSigningKey(key)\n .build()\n .parseClaimsJws(token)\n .body\n claims.subject // devolvemos userId\n } catch (e: Exception) {\n null // token inválido o expirado\n }\n}\n\nConsejo: no almacenes la clave secreta en el código. Usa variables de entorno o Vault.
\n\n3. Almacenamiento seguro de tokens en Android
\n\nSi estás escribiendo una aplicación móvil en Kotlin, el token de acceso no debe almacenarse en SharedPreferences: es un archivo abierto que otras aplicaciones o un atacante con acceso root pueden leer.
\n\nUsa EncryptedSharedPreferences (de AndroidX Security) o Keystore:
\n\nimport androidx.security.crypto.EncryptedSharedPreferences\nimport androidx.security.crypto.MasterKey\n\nfun saveToken(context: Context, token: String) {\n val masterKey = MasterKey.Builder(context)\n .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)\n .build()\n\n val sharedPreferences = EncryptedSharedPreferences.create(\n context,\n "secure_prefs",\n masterKey,\n