Introdução: por que a autenticação "feita em casa" é um risco?
\n\nImagine: você está escrevendo um aplicativo em Kotlin — seja um cliente Android ou um backend em Ktor/Spring Boot. Você rapidamente cria um formulário de login, salva a senha em SharedPreferences e fica feliz que tudo funciona. E uma semana depois, recebe uma mensagem: "Seu aplicativo foi hackeado, dados vazaram". Familiar? Infelizmente, isso é clássico.
\n\nAutenticação segura em Kotlin não é apenas "verificar a senha". É todo um ecossistema: hash, tokens, proteção contra falsificação de solicitações, armazenamento seguro de segredos. Neste artigo, vamos abordar como evitar erros típicos e construir um sistema de login robusto. Vamos lá!
\n\n1. Hash de senhas: bcrypt e seus amigos
\n\nA primeira regra de segurança: nunca armazene senhas em texto puro. Mesmo que você use HTTPS, o banco de dados pode ser comprometido. Sua tarefa é armazenar apenas o hash.
\n\nPor que MD5 e SHA são ruins?
\nEsses algoritmos são rápidos. E hashes rápidos são o sonho de um invasor: ele pode testar bilhões de combinações por segundo. Em vez deles, use bcrypt, scrypt ou Argon2. Eles são intencionalmente lentos e resistentes a ataques de GPU.
\n\nExemplo em Kotlin usando a biblioteca jbcrypt:
import org.mindrot.jbcrypt.BCrypt\n\nfun hashPassword(password: String): String {\n val salt = BCrypt.gensalt(12) // fator de custo 12 — ideal\n return BCrypt.hashpw(password, salt)\n}\n\nfun checkPassword(password: String, hashed: String): Boolean {\n return BCrypt.checkpw(password, hashed)\n}\n\nImportante: o salt é gerado automaticamente e incorporado ao hash. Você não precisa armazená-lo separadamente.
\n\n2. JWT: autenticação leve e segura
\n\nJSON Web Token (JWT) é um padrão para transmitir afirmações (claims) entre partes. Em Kotlin, você pode usar a biblioteca java-jwt (Auth0) ou jsonwebtoken (jjwt).
Geração de JWT em 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\nValidação do 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 // retorna o userId\n } catch (e: Exception) {\n null // token inválido ou expirado\n }\n}\n\nDica: não armazene a chave secreta no código. Use variáveis de ambiente ou Vault.
\n\n3. Armazenamento seguro de tokens no Android
\n\nSe você está escrevendo um aplicativo móvel em Kotlin, o token de acesso não pode ser armazenado em SharedPreferences — é um arquivo aberto que pode ser lido por outros aplicativos ou por um invasor com acesso root.
\n\nUse EncryptedSharedPreferences (do AndroidX Security) ou 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