MASTG-KNOW-0069: Gerenciamento de Chaves

Existem vários métodos de como armazenar a chave no dispositivo. Não armazenar nenhuma chave garantirá que nenhum material de chave possa ser extraído. Isso pode ser alcançado usando uma função de Derivação de Chave por Senha (Password Key Derivation Function), como a PBKDF-2. Veja o exemplo abaixo:

func pbkdf2SHA1(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
}

func pbkdf2SHA256(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
}

func pbkdf2SHA512(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
}

func pbkdf2(hash: CCPBKDFAlgorithm, password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    let passwordData = password.data(using: String.Encoding.utf8)!
    var derivedKeyData = Data(repeating: 0, count: keyByteCount)
    let derivedKeyDataLength = derivedKeyData.count
    let derivationStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
        salt.withUnsafeBytes { saltBytes in

            CCKeyDerivationPBKDF(
                CCPBKDFAlgorithm(kCCPBKDF2),
                password, passwordData.count,
                saltBytes, salt.count,
                hash,
                UInt32(rounds),
                derivedKeyBytes, derivedKeyDataLength
            )
        }
    }
    if derivationStatus != 0 {
        // Error
        return nil
    }

    return derivedKeyData
}

func testKeyDerivation() {
    let password = "password"
    let salt = Data([0x73, 0x61, 0x6C, 0x74, 0x44, 0x61, 0x74, 0x61])
    let keyByteCount = 16
    let rounds = 100_000

    let derivedKey = pbkdf2SHA1(password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
}

Quando você precisa armazenar a chave, é recomendado usar o Keychain, desde que a classe de proteção escolhida não seja kSecAttrAccessibleAlways. Armazenar chaves em qualquer outro local, como NSUserDefaults, arquivos de lista de propriedades (property list) ou por qualquer outro meio do Core Data ou Realm, geralmente é menos seguro do que usar o Keychain. Mesmo quando a sincronização do Core Data ou Realm é protegida usando a classe de proteção de dados NSFileProtectionComplete, ainda recomendamos usar o Keychain. Veja o capítulo "Armazenamento de Dados no iOS" para mais detalhes.

O Keychain suporta dois tipos de mecanismos de armazenamento: uma chave é protegida por uma chave de criptografia armazenada no Secure Enclave, ou a própria chave está dentro do Secure Enclave. O último caso só é válido quando você usa uma chave de assinatura ECDH. Consulte a Documentação da Apple para mais detalhes sobre sua implementação.

As últimas três opções consistem em usar chaves de criptografia embutidas no código-fonte (hardcoded), ter uma função de derivação de chave previsível baseada em atributos estáveis e armazenar chaves geradas em locais compartilhados com outros aplicativos. Usar chaves de criptografia embutidas obviamente não é o caminho, pois isso significaria que todas as instâncias do aplicativo usam a mesma chave de criptografia. Um atacante precisa fazer o trabalho apenas uma vez para extrair a chave do código-fonte (seja armazenado nativamente ou em Objective-C/Swift). Consequentemente, o atacante pode descriptografar quaisquer outros dados que foram criptografados pelo aplicativo. Em seguida, quando você tem uma função de derivação de chave previsível baseada em identificadores acessíveis a outros aplicativos, o atacante só precisa encontrar a KDF e aplicá-la ao dispositivo para encontrar a chave. Por último, armazenar chaves de criptografia simétrica publicamente também é altamente desencorajado.

Mais duas noções que você nunca deve esquecer quando se trata de criptografia:

  1. Sempre criptografe/verifique com a chave pública e sempre descriptografe/assine com a chave privada.
  2. Nunca reuse o par de chaves para outro propósito: isso pode permitir vazar informações sobre a chave: tenha um par de chaves separado para assinatura e um par de chaves separado para criptografia.