publicado

Categories iOS
Stack SwiftUI, Keychain, iCloud, CryptoKit

visão geral

O Lockat é um aplicativo de autenticação de dois fatores criado nativamente para iOS. Ele nasceu de um desafio na Apple Developer Academy: pegar uma ideia do zero e transformá-la num produto real, publicado na App Store, com features monetizáveis. Chegamos no tema de 2FA depois de uma rodada de pesquisa sobre os concorrentes, que revelou algo curioso: a maioria dos apps existentes parecia inacabada, sem coisas simples como ordenação A–Z ou organização por pastas. Esse vazio nos pareceu um bom lugar para fincar a bandeira.

A estratégia de produto saiu naturalmente daí. Mantivemos tudo o que importa para manter as contas seguras (geração de códigos, importação, exportação) completamente gratuito e plenamente funcional. Reservamos os recursos mais avançados de organização e personalização para uma assinatura. O raciocínio era direto: ninguém deveria ter que pagar para se proteger online, mas quem quisesse uma ferramenta mais bem cuidada teria algo que valesse a pena pagar.

Por baixo do capô, os segredos ficam no próprio dispositivo, guardados no Keychain do iOS e protegidos pela criptografia em hardware da Apple, enquanto os metadados não sensíveis sincronizam via iCloud. Assim os códigos te acompanham entre iPhone, iPad e Mac. A versão para Mac roda pelo modo "Designed for iPad", o que permitiu a um time pequeno cobrir três plataformas a partir de uma única base de código em SwiftUI, sem abrir mão do feel nativo. Face ID, Touch ID e bloqueio por senha são integrados via LocalAuthentication, e o gerador de TOTP em si é uma implementação limpa, em conformidade com a RFC 6238, sobre o CryptoKit. Também existe uma migração com um toque a partir do Google Authenticator, que acabou sendo o recurso que mais pesou no onboarding.

Comercialmente, o Lockat não decolou. A categoria de 2FA é dominada por apps gratuitos de empresas em que os usuários já confiam. Furar essa barreira é difícil, e foi mais complicado do que prevíamos. Mas o que eu levo do projeto é concreto: um entendimento prático da stack de segurança da Apple, a disciplina de reduzir um produto ao escopo que um time pequeno consegue de fato finalizar e publicar, e uma percepção muito mais aguçada do abismo entre uma boa ideia no papel e algo que as pessoas vão instalar no lugar do app que já usam. Essas lições rodam silenciosamente por trás de tudo que construí desde então, e acho que isso vale mais do que os números do lançamento.

trechos de código

O contador é derivado do tempo Unix e passa por HMAC com o segredo do usuário. É então truncado em um código numérico conforme a especificação. As entradas são limitadas para evitar que problemas no tempo do sistema travem o app.

static func generateCode(
    secret: Data,
    algorithm: TOTPAlgorithm,
    digits: Int,
    period: Int,
    date: Date = .now
) -> String {
    let safePeriod = max(1, period)
    let safeDigits = max(1, min(9, digits))
    let timestamp = max(0, date.timeIntervalSince1970)
    let counter = UInt64(timestamp) / UInt64(safePeriod)
    var bigEndianCounter = counter.bigEndian
    let counterData = withUnsafeBytes(of: &bigEndianCounter) { Data($0) }
    let key = SymmetricKey(data: secret)

    let hmacBytes: [UInt8]
    switch algorithm {
    case .sha1:
        hmacBytes = Array(HMAC<Insecure.SHA1>.authenticationCode(for: counterData, using: key))
    case .sha256:
        hmacBytes = Array(HMAC<SHA256>.authenticationCode(for: counterData, using: key))
    case .sha512:
        hmacBytes = Array(HMAC<SHA512>.authenticationCode(for: counterData, using: key))
    }

    guard hmacBytes.count >= 4, let last = hmacBytes.last else {
        return String(repeating: "0", count: safeDigits)
    }

    let offset = Int(last & 0x0F)
    guard offset + 3 < hmacBytes.count else {
        return String(repeating: "0", count: safeDigits)
    }

    let truncated: UInt32 =
        (UInt32(hmacBytes[offset] & 0x7F) << 24)
        | (UInt32(hmacBytes[offset + 1]) << 16)
        | (UInt32(hmacBytes[offset + 2]) << 8)
        | UInt32(hmacBytes[offset + 3])

    var modulo: UInt32 = 1
    for _ in 0..<safeDigits {
        modulo *= 10
    }

    let code = truncated % modulo
    return String(format: "%0\(safeDigits)d", code)
}

escolhas de design

cores

preto do sistema #000000
azul do sistema #0A84FF
branco do sistema #FFFFFF

tipografia

SF Pro / interface
Privado, seguro e simples.
SF Mono / códigos TOTP
482 913

algumas fontes utilizadas neste projeto são proprietárias e podem não ser exibidas corretamente caso não estejam instaladas em seu sistema.

fundamentação

O design do Lockat segue à risca as Human Interface Guidelines da Apple. Foi uma decisão de produto deliberada, não um atalho. Boa parte de quem busca um app de 2FA de terceiros nem sabe que o iOS já traz um autenticador embutido, então eu queria que o Lockat parecesse nativo. Trocar da solução da própria Apple ou conviver com ela não deveria exigir esforço cognitivo. Padrões de navegação do sistema, materiais nativos, SF Pro e um azul de destaque do sistema sobre fundos totalmente nativos, tanto no modo claro quanto no escuro, fazem com que ele pareça um app de primeira-parte já na primeira abertura. A aparência acompanha o sistema perfeitamente, evitando aquela sensação de "tema pela metade" comum em outros apps.

Dentro dessa casca nativa, o design se justifica oferecendo o que falta nos concorrentes. Cada entrada pode carregar uma nota livre, para que um código de recuperação, um e-mail de backup ou um lembrete sobre qual número está vinculado à conta fique ao lado do próprio TOTP. Os códigos podem ser agrupados em pastas para quem precisa cuidar de dezenas de contas, e a cor de destaque da interface é configurável pelo usuário, o que ajuda o app a parecer pessoal em vez de ser apenas mais um utilitário cinza. O resultado é uma interface que se dissolve no iOS até você precisar dela, e que, calada, faz mais do que os apps ao redor.

créditos

pessoas

design · desenvolvimento
pedro wiezelluiz rosa