Por que faço esta pergunta:
Eu sei que tem havido muitas perguntas sobre a criptografia AES, mesmo para Android. E há muitos trechos de código se você pesquisar na web. Mas em cada página, em cada pergunta do Stack Overflow, encontro outra implementação com grandes diferenças.
Portanto, criei esta questão para encontrar uma "prática recomendada". Espero que possamos coletar uma lista dos requisitos mais importantes e configurar uma implementação que seja realmente segura!
Eu li sobre vetores de inicialização e sais. Nem todas as implementações que encontrei tinham esses recursos. Então você precisa disso? Aumenta muito a segurança? Como você o implementa? O algoritmo deve gerar exceções se os dados criptografados não puderem ser descriptografados? Ou isso é inseguro e deve apenas retornar uma string ilegível? O algoritmo pode usar Bcrypt em vez de SHA?
E quanto a essas duas implementações que encontrei? Eles estão bem? Perfeito ou faltando algumas coisas importantes? Qual destes é seguro?
O algoritmo deve pegar uma string e uma "senha" para criptografar e, em seguida, criptografar a string com essa senha. A saída deve ser uma string (hex ou base64?) Novamente. A descriptografia também deve ser possível, é claro.
Qual é a implementação AES perfeita para Android?
Implementação # 1:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AdvancedCrypto implements ICrypto {
public static final String PROVIDER = "BC";
public static final int SALT_LENGTH = 20;
public static final int IV_LENGTH = 16;
public static final int PBE_ITERATION_COUNT = 100;
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
private static final String HASH_ALGORITHM = "SHA-512";
private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY_ALGORITHM = "AES";
public String encrypt(SecretKey secret, String cleartext) throws CryptoException {
try {
byte[] iv = generateIv();
String ivHex = HexEncoder.toHex(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
Cipher encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
encryptionCipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
byte[] encryptedText = encryptionCipher.doFinal(cleartext.getBytes("UTF-8"));
String encryptedHex = HexEncoder.toHex(encryptedText);
return ivHex + encryptedHex;
} catch (Exception e) {
throw new CryptoException("Unable to encrypt", e);
}
}
public String decrypt(SecretKey secret, String encrypted) throws CryptoException {
try {
Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
String ivHex = encrypted.substring(0, IV_LENGTH * 2);
String encryptedHex = encrypted.substring(IV_LENGTH * 2);
IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
String decrypted = new String(decryptedText, "UTF-8");
return decrypted;
} catch (Exception e) {
throw new CryptoException("Unable to decrypt", e);
}
}
public SecretKey getSecretKey(String password, String salt) throws CryptoException {
try {
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), HexEncoder.toByte(salt), PBE_ITERATION_COUNT, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
SecretKey tmp = factory.generateSecret(pbeKeySpec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
return secret;
} catch (Exception e) {
throw new CryptoException("Unable to get secret key", e);
}
}
public String getHash(String password, String salt) throws CryptoException {
try {
String input = password + salt;
MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM, PROVIDER);
byte[] out = md.digest(input.getBytes("UTF-8"));
return HexEncoder.toHex(out);
} catch (Exception e) {
throw new CryptoException("Unable to get hash", e);
}
}
public String generateSalt() throws CryptoException {
try {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] salt = new byte[SALT_LENGTH];
random.nextBytes(salt);
String saltHex = HexEncoder.toHex(salt);
return saltHex;
} catch (Exception e) {
throw new CryptoException("Unable to generate salt", e);
}
}
private byte[] generateIv() throws NoSuchAlgorithmException, NoSuchProviderException {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return iv;
}
}
Implementação # 2:
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Usage:
* <pre>
* String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
* ...
* String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
* </pre>
* @author ferenc.hechler
*/
public class SimpleCrypto {
public static String encrypt(String seed, String cleartext) throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] result = encrypt(rawKey, cleartext.getBytes());
return toHex(result);
}
public static String decrypt(String seed, String encrypted) throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] enc = toByte(encrypted);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
public static byte[] toByte(String hexString) {
int len = hexString.length()/2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
return result;
}
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2*buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private final static String HEX = "0123456789ABCDEF";
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
}
}
Fonte: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml
fonte
implements ICrypto
e alterandothrows CryptoException
parathrows Exception
e assim por diante. Então você não precisará mais dessas aulas.getSecretKey
,getHash
,generateSalt
na primeira implementação que não são utilizados. Talvez eu esteja errado, mas como essa classe poderia ser usada para criptografar uma string na prática?Respostas:
Nenhuma das implementações fornecidas em sua pergunta está totalmente correta e nenhuma das implementações fornecidas deve ser usada como está. A seguir, discutirei aspectos da criptografia baseada em senha no Android.
Chaves e Hashes
Vou começar a discutir o sistema baseado em senha com sais. O sal é um número gerado aleatoriamente. Não é "deduzido". A implementação 1 inclui um
generateSalt()
método que gera um número aleatório criptograficamente forte. Como o sal é importante para a segurança, ele deve ser mantido em segredo assim que for gerado, embora precise ser gerado apenas uma vez. Se este for um site da Web, é relativamente fácil manter o segredo do sal, mas para aplicativos instalados (para desktops e dispositivos móveis), isso será muito mais difícil.O método
getHash()
retorna um hash da senha e do salt fornecidos, concatenado em uma única string. O algoritmo usado é SHA-512, que retorna um hash de 512 bits. Este método retorna um hash que é útil para verificar a integridade de uma string, então também pode ser usado chamandogetHash()
apenas com uma senha ou apenas um salt, já que simplesmente concatena ambos os parâmetros. Como esse método não será usado no sistema de criptografia baseado em senha, não o discutirei mais.O método
getSecretKey()
deriva uma chave de umachar
matriz da senha e um sal codificado em hexadecimal, conforme retornado degenerateSalt()
. O algoritmo usado é PBKDF1 (eu acho) de PKCS5 com SHA-256 como a função hash e retorna uma chave de 256 bits.getSecretKey()
gera uma chave gerando repetidamente hashes da senha, salt e um contador (até a contagem de iteração fornecidaPBE_ITERATION_COUNT
, aqui 100) para aumentar o tempo necessário para montar um ataque de força bruta. O comprimento do salt deve ser pelo menos tão longo quanto a chave que está sendo gerada, neste caso, pelo menos 256 bits. A contagem de iterações deve ser definida o maior tempo possível, sem causar atrasos excessivos. Para obter mais informações sobre sais e contagens de iteração na derivação principal, consulte a seção 4 em RFC2898 .A implementação no PBE do Java, entretanto, é falha se a senha contiver caracteres Unicode, ou seja, aqueles que requerem mais de 8 bits para serem representados. Conforme declarado em
PBEKeySpec
, "o mecanismo PBE definido em PKCS # 5 olha apenas para os 8 bits de ordem inferior de cada caractere". Para contornar esse problema, você pode tentar gerar uma seqüência hexadecimal (que conterá apenas caracteres de 8 bits) de todos os caracteres de 16 bits da senha antes de passá-la paraPBEKeySpec
. Por exemplo, "ABC" torna-se "004100420043". Observe também que PBEKeySpec "solicita a senha como uma matriz de caracteres, para que possa ser substituída [comclearPassword()
] quando terminar". (Com relação à "proteção de strings na memória", consulte esta pergunta .) Não vejo nenhum problema, no entanto,Encriptação
Depois que uma chave é gerada, podemos usá-la para criptografar e descriptografar o texto.
Na implementação 1, o algoritmo de cifra utilizado é
AES/CBC/PKCS5Padding
, ou seja, AES no modo de cifra Cipher Block Chaining (CBC), com preenchimento definido em PKCS # 5. (Outros modos de cifra AES incluem modo contador (CTR), modo livro de código eletrônico (ECB) e modo contador Galois (GCM). Outra pergunta no Stack Overflow contém respostas que discutem em detalhes os vários modos de cifra AES e os recomendados para uso. Esteja ciente também de que existem vários ataques à criptografia do modo CBC, alguns dos quais são mencionados no RFC 7457.)Observe que você deve usar um modo de criptografia que também verifique a integridade dos dados criptografados (por exemplo, criptografia autenticada com dados associados , AEAD, descrita em RFC 5116). No entanto,
AES/CBC/PKCS5Padding
não fornece verificação de integridade, portanto, sozinho não é recomendado . Para fins de AEAD, o uso de um segredo com pelo menos o dobro do comprimento de uma chave de criptografia normal é recomendado, para evitar ataques de chave relacionados: a primeira metade serve como chave de criptografia e a segunda metade serve como chave para a verificação de integridade. (Ou seja, neste caso, gere um único segredo a partir de uma senha e sal e divida esse segredo em dois.)Implementação Java
As várias funções na implementação 1 usam um provedor específico, a saber "BC", para seus algoritmos. Em geral, porém, não é recomendado solicitar provedores específicos, uma vez que nem todos os provedores estão disponíveis em todas as implementações Java, seja por falta de suporte, para evitar duplicação de código, ou por outros motivos. Esse conselho se tornou especialmente importante desde o lançamento da visualização do Android P no início de 2018, porque algumas funcionalidades do provedor "BC" foram descontinuadas lá - consulte o artigo "Mudanças na criptografia no Android P" no Android Developers Blog. Consulte também a introdução aos provedores Oracle .
Portanto,
PROVIDER
não deve existir e a string-BC
deve ser removidaPBE_ALGORITHM
. A implementação 2 está correta a este respeito.É inadequado para um método capturar todas as exceções, mas sim tratar apenas as exceções que pode. As implementações fornecidas em sua pergunta podem lançar uma variedade de exceções verificadas. Um método pode escolher envolver apenas essas exceções verificadas com CryptoException ou especificar essas exceções verificadas na
throws
cláusula. Por conveniência, agrupar a exceção original com CryptoException pode ser apropriado aqui, uma vez que existem potencialmente muitas exceções verificadas que as classes podem lançar.SecureRandom
no AndroidConforme detalhado no artigo "Some SecureRandom Thoughts", no Android Developers Blog, a implementação do
java.security.SecureRandom
Android nas versões anteriores a 2013 tem uma falha que reduz a força dos números aleatórios que fornece. Essa falha pode ser mitigada conforme descrito nesse artigo.fonte
getInstance
tem uma sobrecarga que leva apenas o nome do algoritmo. Exemplo: Cipher.getInstance () Vários provedores, incluindo Bouncy Castle, podem ser registrados na implementação Java e esse tipo de sobrecarga busca na lista de provedores um deles que implementa o algoritmo fornecido. Você deveria experimentar e ver.# 2 nunca deve ser usado, pois usa apenas "AES" (que significa criptografia de modo ECB em texto, um grande não-não) para a cifra. Vou apenas falar sobre o # 1.
A primeira implementação parece aderir às melhores práticas de criptografia. As constantes são geralmente OK, embora o tamanho do sal e o número de iterações para realizar PBE estejam no lado curto. Além disso, parece ser para AES-256, já que a geração da chave PBE usa 256 como um valor codificado (uma pena depois de todas essas constantes). Ele usa CBC e PKCS5Padding, que é pelo menos o que você esperaria.
Falta completamente qualquer proteção de autenticação / integridade, então um invasor pode alterar o texto cifrado. Isso significa que ataques oracle de preenchimento são possíveis em um modelo cliente / servidor. Isso também significa que um invasor pode tentar alterar os dados criptografados. Isso provavelmente resultará em algum erro em algum lugar porque o preenchimento ou conteúdo não é aceito pelo aplicativo, mas essa não é uma situação na qual você deseja estar.
O tratamento de exceções e a validação de entrada podem ser aprimorados, detectar a exceção está sempre errado em meu livro. Além disso, a classe implementa ICrypt, que eu não sei. Eu sei que ter apenas métodos sem efeitos colaterais em uma aula é um pouco estranho. Normalmente, você faria isso estático. Não há armazenamento em buffer de instâncias de Cipher, etc., portanto, todos os objetos necessários são criados ad-nauseum. No entanto, você pode remover com segurança ICrypto da definição ao que parece; nesse caso, você também pode refatorar o código para métodos estáticos (ou reescrevê-lo para ser mais orientado a objetos, sua escolha).
O problema é que qualquer wrapper sempre faz suposições sobre o caso de uso. Dizer que um invólucro está certo ou errado é, portanto, besteira. É por isso que sempre tento evitar a geração de classes de wrapper. Mas pelo menos não parece explicitamente errado.
fonte
Você fez uma pergunta muito interessante. Como acontece com todos os algoritmos, a chave de cifra é o "molho secreto", pois uma vez que é conhecida do público, todo o resto também é. Então, você procura maneiras de este documento do Google
segurança
Além do Google In-App Billing, também oferece ideias sobre segurança, o que também é perspicaz
billing_best_practices
fonte
Use a API BouncyCastle Lightweight. Ele fornece 256 AES com PBE e sal.
Aqui está um exemplo de código, que pode criptografar / descriptografar arquivos.
fonte
buf
(eu realmente espero que não seja umstatic
campo). Também se parece com ambosencrypt()
edecrypt()
não conseguirá processar o bloco final corretamente se a entrada for um múltiplo de 1024 bytes.Encontrei uma boa implementação aqui: http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html e https://github.com/nelenkov/android-pbe Isso também foi útil em minha busca por uma implementação de AES boa o suficiente para Android
fonte