Hashing MD5 no Android

91

Eu tenho um cliente Android simples que precisa 'conversar' com um ouvinte HTTP C # simples. Desejo fornecer um nível básico de autenticação passando nome de usuário / senha em solicitações POST.

O hashing MD5 é trivial em C # e fornece segurança suficiente para minhas necessidades, mas não consigo descobrir como fazer isso no final do Android.

EDIT: Apenas para resolver as preocupações levantadas sobre a fraqueza do MD5 - o servidor C # é executado nos PCs dos usuários do meu cliente Android. Em muitos casos, eles acessarão o servidor usando wi-fi em suas próprias LANs, mas, por sua própria conta e risco, eles podem optar por acessá-lo da Internet. Além disso, o serviço no servidor precisa usar passagem do MD5 para um aplicativo de terceiros sobre o qual não tenho controle.

Squonk
fonte
6
Não use MD5. Use SHA512.
SLaks
1
Por quê? SHA512 não é mais difícil do que MD5. Você não quer ficar preso daqui a cinco anos com clientes legados usando MD5.
SLaks
2
Espero que você esteja usando um nonce em seu protocolo, para que possa jogar fora os ataques de repetição.
sarnold
1
@NickJohnson: Para responder à sua pergunta, por que você selecionaria deliberadamente a opção mais fraca? com outra pergunta ... por que você sentiria a necessidade de comentar sobre uma pergunta que postei há 16 meses? Mas se você realmente quer saber (se você olhar o comentário para SLaks acima do seu), era um código de estágio alfa e o PC (não escrito por mim) usava hashing MD5. O requisito era basicamente um cenário de passagem sem envolver complexidade extra. Na época, eu tinha cerca de 10 testadores em estágio alfa que conheciam os riscos. Uma segurança mais complexa foi incorporada desde que fiz a pergunta.
Squonk
1
...o que? Não, isso não é apenas errado, é perigosamente errado.
Nick Johnson

Respostas:

230

Aqui está uma implementação que você pode usar (atualizada para usar convenções Java mais atualizadas - for:eachloop, em StringBuildervez de StringBuffer):

public static String md5(final String s) {
    final String MD5 = "MD5";
    try {
        // Create MD5 Hash
        MessageDigest digest = java.security.MessageDigest
                .getInstance(MD5);
        digest.update(s.getBytes());
        byte messageDigest[] = digest.digest();

        // Create Hex String
        StringBuilder hexString = new StringBuilder();
        for (byte aMessageDigest : messageDigest) {
            String h = Integer.toHexString(0xFF & aMessageDigest);
            while (h.length() < 2)
                h = "0" + h;
            hexString.append(h);
        }
        return hexString.toString();

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return "";
}

Embora não seja recomendado para sistemas que envolvem até mesmo o nível básico de segurança (MD5 é considerado quebrado e pode ser facilmente explorado ), às vezes é suficiente para tarefas básicas.

Den Delimarsky
fonte
Obrigado, isso resolverá o problema. Veja minha edição para saber por que MD5 será suficiente neste estágio.
Squonk
6
Vote aqui IMO para que as respostas mais corretas abaixo apareçam.
Adam
4
0s não atendidos, conforme respondido abaixo.
SohailAziz
Atualizado para usar os padrões java mais recentes (para: each, StringBuilder)
loeschg
3
Existe algum sistema operacional Android que não tenha o MD5 implementado (causar lançamento de NoSuchAlgorithmException)?
VSB de
49

A resposta aceita não funcionou para mim no Android 2.2. Não sei por que, mas ele estava "comendo" alguns dos meus zeros (0). O Apache commons também não funcionou no Android 2.2, porque ele usa métodos compatíveis apenas a partir do Android 2.3.x. Além disso, se você deseja apenas MD5 uma string, o Apache commons é muito complexo para isso. Por que se deve manter uma biblioteca inteira para usar apenas uma pequena função dela ...

Finalmente eu encontrei o seguinte trecho de código aqui que funcionou perfeitamente para mim. Espero que seja útil para alguém ...

public String MD5(String md5) {
   try {
        java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
        byte[] array = md.digest(md5.getBytes("UTF-8"));
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < array.length; ++i) {
          sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1,3));
       }
        return sb.toString();
    } catch (java.security.NoSuchAlgorithmException e) {
    } catch(UnsupportedEncodingException ex){
    }
    return null;
}
Andranik
fonte
4
Funcionou para mim. Eu mudei md5.getBytes ( "UTF-8") . Eu verifiquei com: q4m'x68n6_YDB4ty8VC4 &} wqBtn ^ 68W com o código acima, o resultado é 0c70bb931f03b75af1591f261eb77d0b , NÃO o c70bb931f03b75af1591f261eb77d0b . 0 está em vigor
Inoy
27

O código androidsnippets.com não funciona de maneira confiável porque os 0s parecem ter sido cortados do hash resultante.

Uma implementação melhor está aqui .

public static String MD5_Hash(String s) {
    MessageDigest m = null;

    try {
            m = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
    }

    m.update(s.getBytes(),0,s.length());
    String hash = new BigInteger(1, m.digest()).toString(16);
    return hash;
}
cristão
fonte
"M" pode ser nulo no Android, neste caso?
desenvolvedor Android
3
@androiddeveloper Você está totalmente certo. O bloco catch é mal implementado aqui, deve haver uma instrução de retorno ou haverá um erro de referência nula quando m.update for chamado. Além disso, não tenho certeza, mas embora isso não possa remover os 0s nos bytes individuais, acho que ainda pode remover os 0s iniciais em todo o hash de 32 caracteres. Em vez de toString (16), acho que String.Format ("% 032X", bigInt) funciona conforme o esperado. Além disso, você pode escolher se deseja o hexadecimal em maiúsculas ou minúsculas ("% 032x" para minúsculas).
rsimp
1
Esta versão é falha. Se seu MD5 começar com "0", o MD5 gerado não terá um 0. Não use esta solução.
elcuco
20

Se usar o Apache Commons Codec for uma opção, esta seria uma implementação mais curta:

String md5Hex = new String(Hex.encodeHex(DigestUtils.md5(data)));

Ou SHA:

String shaHex= new String(Hex.encodeHex(DigestUtils.sha("textToHash")));

Fonte para acima.

Siga o link e vote positivamente na solução para premiar a pessoa correta.


Link do repositório Maven: https://mvnrepository.com/artifact/commons-codec/commons-codec

Dependência atual do Maven (em 6 de julho de 2016):

<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.10</version>
</dependency>
Tbraun
fonte
1
Algum motivo para você usar uma biblioteca externa para uma API que já existe na biblioteca padrão?
m0skit0
12

Uma solução acima usando DigestUtils não funcionou para mim. Na minha versão do Apache commons (a mais recente para 2013) não existe tal classe.

Encontrei outra solução aqui em um blog . Ele funciona perfeitamente e não precisa de recursos comuns do Apache. Parece um pouco mais curto do que o código na resposta aceita acima.

public static String getMd5Hash(String input) {
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] messageDigest = md.digest(input.getBytes());
        BigInteger number = new BigInteger(1, messageDigest);
        String md5 = number.toString(16);

        while (md5.length() < 32)
            md5 = "0" + md5;

        return md5;
    } catch (NoSuchAlgorithmException e) {
        Log.e("MD5", e.getLocalizedMessage());
        return null;
    }
}

Você precisará destas importações:

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
Denis Kutlubaev
fonte
Importante saber que o id do hash MD5 tem 32 caracteres ou menos, obrigado!
Pelanes
3
As últimas linhas provavelmente podem ser substituídas por: return String.Format ("% 032X", número); Caso contrário, eu realmente gosto desta resposta.
rsimp
10

Esta é uma pequena variação das respostas de Andranik e Den Delimarsky acima, mas é um pouco mais conciso e não requer nenhuma lógica bit a bit. Em vez disso, ele usa o String.formatmétodo interno para converter os bytes em cadeias hexadecimais de dois caracteres (não tira 0's). Normalmente, eu apenas comentaria as respostas, mas não tenho reputação para fazer isso.

public static String md5(String input) {
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");

        StringBuilder hexString = new StringBuilder();
        for (byte digestByte : md.digest(input.getBytes()))
            hexString.append(String.format("%02X", digestByte));

        return hexString.toString();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return null;
    }
}

Se você quiser retornar uma string minúscula, basta mudar %02Xpara %02x.

Edit: Usando BigInteger como com a resposta de wzbozon, você pode tornar a resposta ainda mais concisa:

public static String md5(String input) {
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");
        BigInteger md5Data = new BigInteger(1, md.digest(input.getBytes()));
        return String.Format("%032X", md5Data);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return null;
    }
}
rsimp
fonte
4

Eu fiz uma biblioteca simples em Kotlin.

Adicionar na raiz build.gradle

allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

em App build.gradle

implementation 'com.github.1AboveAll:Hasher:-SNAPSHOT'

Uso

Em Kotlin

val ob = Hasher()

Então use o método hash ()

ob.hash("String_You_Want_To_Encode",Hasher.MD5)

ob.hash("String_You_Want_To_Encode",Hasher.SHA_1)

Ele retornará MD5 e SHA-1 respectivamente.

Mais sobre a Biblioteca

https://github.com/ihimanshurawat/Hasher

Himanshu Rawat
fonte
2

Aqui está a versão Kotlin da resposta de @Andranik. Precisamos mudar getBytespara toByteArray(não é necessário adicionar charset UTF-8 porque o charset padrão de toByteArrayé UTF-8) e converter array [i] para inteiro

fun String.md5(): String? {
    try {
        val md = MessageDigest.getInstance("MD5")
        val array = md.digest(this.toByteArray())
        val sb = StringBuffer()
        for (i in array.indices) {
            sb.append(Integer.toHexString(array[i].toInt() and 0xFF or 0x100).substring(1, 3))
        }
        return sb.toString()
    } catch (e: java.security.NoSuchAlgorithmException) {
    } catch (ex: UnsupportedEncodingException) {
    }
    return null
}

Espero que ajude

Phan Van Linh
fonte
1

MD5 é um pouco antigo, SHA-1 é um algoritmo melhor, há um exemplo aqui .

( Além disso, como eles observaram nessa postagem, Java trata disso por conta própria, sem código específico do Android. )

Adão
fonte
4
Não - eu não queria alternativas ao MD5 quando fiz essa pergunta em janeiro de 2011 (19 meses atrás) e não tenho certeza por que você sentiu a necessidade de responder à minha pergunta neste momento.
Squonk de
21
@Squonk eu respondi porque essa é a ideia geral por trás do stackoverflow. Não importa quanto tempo depois do fato, sempre tente obter respostas melhores para as pessoas que podem se deparar com a pergunta mais tarde. Quanto a sugerir SHA-1, não havia como eu saber que você era especificamente contra o SHA-1, mas muitos outros não serão, então, novamente, pode ajudar outras pessoas no futuro que se depararem com isso e direcioná-las para um algoritmo mais moderno.
Adam de
3
Não consigo pensar em uma única situação em que MD5 seja uma escolha ruim, mas SHA1 uma boa escolha. Se você precisa de resistência à colisão, precisa de SHA2, não de SHA1. Se você fizer hash de senhas, precisará de bcrypt ou PBKDF2. Ou, no caso do OP, a solução adequada é provavelmente SSL.
CodesInChaos
3
@Adam, isso não é uma resposta, isso é um comentário.
Antzi
1

Em nosso aplicativo MVC, geramos para long param

using System.Security.Cryptography;
using System.Text;
    ...
    public static string getMD5(long id)
    {
        // convert
        string result = (id ^ long.MaxValue).ToString("X") + "-ANY-TEXT";
        using (MD5 md5Hash = MD5.Create())
        {
            // Convert the input string to a byte array and compute the hash. 
            byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(result));

            // Create a new Stringbuilder to collect the bytes and create a string.
            StringBuilder sBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
                sBuilder.Append(data[i].ToString("x2"));

            // Return the hexadecimal string. 
            result = sBuilder.ToString().ToUpper();
        }

        return result;
    }

e o mesmo no aplicativo Android (thenk ajuda Andranik)

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
...
public String getIdHash(long id){
    String hash = null;
    long intId = id ^ Long.MAX_VALUE;
    String md5 = String.format("%X-ANY-TEXT", intId);
    try {
        MessageDigest md = java.security.MessageDigest.getInstance("MD5");
        byte[] arr = md.digest(md5.getBytes());
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < arr.length; ++i)
            sb.append(Integer.toHexString((arr[i] & 0xFF) | 0x100).substring(1,3));

        hash = sb.toString();
    } catch (NoSuchAlgorithmException e) {
        Log.e("MD5", e.getMessage());
    }

    return hash.toUpperCase();
}
Oleg Grabets
fonte
1

Eu usei o método abaixo para me fornecer MD5 passando a string para a qual você deseja obter MD5

public static String getMd5Key(String password) {

//        String password = "12131123984335";

        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(password.getBytes());

            byte byteData[] = md.digest();

            //convert the byte to hex format method 1
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < byteData.length; i++) {
                sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
            }

            System.out.println("Digest(in hex format):: " + sb.toString());

            //convert the byte to hex format method 2
            StringBuffer hexString = new StringBuffer();
            for (int i = 0; i < byteData.length; i++) {
                String hex = Integer.toHexString(0xff & byteData[i]);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            System.out.println("Digest(in hex format):: " + hexString.toString());

            return hexString.toString();

        } catch (Exception e) {
            // TODO: handle exception
        }

        return "";
}
Ghanshyam Patidar
fonte
1

Use SHA-512, MD5 não é seguro

public static String getSHA512SecurePassword(String passwordToHash) {
    String generatedPassword = null;
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-512");
        md.update("everybreathyoutake".getBytes());
        byte[] bytes = md.digest(passwordToHash.getBytes());
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
        }
        generatedPassword = sb.toString();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return generatedPassword;
}
Carlos Jesus Arancibia Taborga
fonte
0

A conversão toHex () muito desperdiçadora prevalece em outras sugestões.

private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

public static String md5string(String s) {
    return toHex(md5plain(s));
}

public static byte[] md5plain(String s) {
    final String MD5 = "MD5";
    try {
        // Create MD5 Hash
        MessageDigest digest = java.security.MessageDigest.getInstance(MD5);
        digest.update(s.getBytes());
        return digest.digest();
    } catch (NoSuchAlgorithmException e) {
        // never happens
        e.printStackTrace();
        return null;
    }
}

public static String toHex(byte[] buf) {
    char[] hexChars = new char[buf.length * 2];
    int v;
    for (int i = 0; i < buf.length; i++) {
        v = buf[i] & 0xFF;
        hexChars[i * 2] = HEX_ARRAY[v >>> 4];
        hexChars[i * 2 + 1] = HEX_ARRAY[v & 0x0F];
    }
    return new String(hexChars);
}
Gena Batsyan
fonte
A resposta é sobre um toHex "melhor" quando a busca for sobre MD5, como tal, ele não responde. Observação: Donald Knuth O verdadeiro problema é que os programadores gastam muito tempo se preocupando com a eficiência nos lugares e horários errados; a otimização prematura é a raiz de todos os males (ou pelo menos da maior parte deles) na programação.
zaph
0

Isso está funcionando perfeitamente para mim, eu usei isso para obter MD5 no Array LIST (em seguida, convertê-lo em objeto JSON), mas se você só precisa aplicá-lo em seus dados. digite o formato, substitua JsonObject pelo seu.

Especialmente se você tiver uma incompatibilidade com a implementação do python MD5, use isso!

private static String md5(List<AccelerationSensor> sensor) {

    Gson gson= new Gson();
    byte[] JsonObject = new byte[0];
    try {
        JsonObject = gson.toJson(sensor).getBytes("UTF-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    MessageDigest m = null;

    try {
        m = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

    byte[] thedigest = m.digest(JsonObject);
    String hash = String.format("%032x", new BigInteger(1, thedigest));
    return hash;


}
Mehran Sahandi Far
fonte
-1

As soluções fornecidas para a linguagem Scala (um pouco mais curtas):

def getMd5(content: Array[Byte]) =
    try {
        val md = MessageDigest.getInstance("MD5")
        val bytes = md.digest(content)
        bytes.map(b => Integer.toHexString((b + 0x100) % 0x100)).mkString
    } catch {
        case ex: Throwable => null
    }
david.perez
fonte