Publicado por Sergio Giro, engenheiro de software
Se seu aplicativo Android deriva chaves usando o algoritmo SHA1PRNG do provedor Crypto, você deve começar a usar uma função de derivação de chave real e possivelmente criptografar os dados novamente.
A arquitetura de criptografia Java permite que os desenvolvedores criem uma instância de uma classe, como uma criptografia ou um gerador de número pseudoaleatório, usando chamadas como:
SomeClass.getInstance("SomeAlgorithm", "SomeProvider");
Ou simplesmente:
SomeClass.getInstance("SomeAlgorithm");
Por exemplo,
Cipher.getInstance(“AES/CBC/PKCS5PADDING”);
SecureRandom.getInstance(“SHA1PRNG”);
No Android, recomendamos não especificar o provedor. Em geral, toda chamada das Java Cryptography Extension (JCE) APIs que especificam um provedor só deve ser realizada se o provedor está incluso no aplicativo ou se o aplicativo consegue lidar com uma possível ProviderNotFoundException.
Infelizmente, muitos aplicativos dependem do provedor “Crypto”, agora removido, para um antipadrão de derivação de chave.
Este provedor só fornecia uma implementação do algoritmo “SHA1PRNG” para instâncias de SecureRandom. O problema é que o algoritmo SHA1PRNG não é criptograficamente forte. Para leitores interessados em detalhes, Sobre distância estatística baseada em testes de sequências pseudoaleatórias e experimentos com PHP e Debian OpenSSL, seção 8.1, de Yongge Want e Tony Nicol, define que a sequência “aleatória”, considerada na forma binária, tem a tendência a retornar zeros, e que isso piora dependendo da semente.
Como resultado, no Android N, estamos removendo completamente a implementação do algoritmo SHA1PRNG e do provedor Crypto. Abordamos os problemas no uso do SecureRandom para derivação de chave há alguns anos em Como usar criptografia para armazenar credenciais com segurança. No entanto, dado seu uso continuado, falaremos sobre isso novamente aqui.
Um uso comum, mas incorreto, deste provedor era para derivar chaves de criptografia usando uma senha como semente. A implementação de SHA1PRNG tinha um erro que a tornava determinista se setSeed() fosse chamado antes de se obter a saída. Este erro era usado para derivar uma chave fornecendo uma senha como semente e depois usando os bytes da saída “aleatória” da chave (“aleatória” nesta frase significa “previsível e criptograficamente fraca”). Essa chave poderia, então, ser usada para criptografar e decodificar dados.
A seguir, explicamos como derivar chaves de forma correta e como decodificar os dados que foram criptografados usando uma chave sem segurança. Além disso, há um exemplo completo, incluindo uma classe auxiliar para usar a funcionalidade SHA1PRNG removida, com o único objetivo de decodificar dados que, de outra forma, estariam indisponíveis.
As chaves podem ser derivadas da seguinte forma:
/* User types in their password: */
String password = "password";
/* Store these things on disk used to derive key later: */
int iterationCount = 1000;
int saltLength = 32; // bytes; should be the same size
as the output (256 / 8 = 32)
int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
byte[] salt; // Should be of saltLength
/* When first creating the key, obtain a salt with this: */
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);
/* Use this to derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Pronto. Você não deve precisar de mais nada.
Para facilitar o trânsito de dados, abordamos o caso de desenvolvedores que têm dados criptografados com uma chave sem segurança, que é sempre derivada de uma senha. Você pode usar a classe auxiliar InsecureSHA1PRNGKeyDerivator
no exemplo de aplicativo para derivar a chave.
private static SecretKey deriveKeyInsecurely(String password, int
keySizeInBytes) {
byte[] passwordBytes = password.getBytes(StandardCharsets.US_ASCII);
return new SecretKeySpec(
InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(
passwordBytes, keySizeInBytes),
"AES");
}
Em seguida, você pode criptografar os dados novamente com uma chave derivada de forma segura, conforme explicado acima, e ser feliz!
Observação 1: como uma medida temporária para que os aplicativos continuem funcionando, decidimos ainda criar a instância para aplicativos que visam a versão 23 do SDK, a versão para Marshmallow ou anterior. Não fique dependente da presença do provedor Crypto no Android SDK, nosso plano é excluí-lo totalmente no futuro.
Observação 2: já que muitas partes do sistema presumem a existência de um algoritmo SHA1PRNG, quando uma instância de SHA1PRNG é solicitada e o provedor não é especificado, retornamos uma instância de OpenSSLRandom, que é uma fonte segura de números aleatórios derivados do OpenSSL.