Android中的MD5哈希

98

我有一个简单的Android客户端需要与一个简单的C# HTTP监听器进行通信。 我想通过在POST请求中传递用户名/密码来提供基本级别的身份验证。

在C#中进行MD5哈希是微不足道的,并为我的需求提供了足够的安全性,但我似乎找不到如何在Android端执行此操作。

编辑:为了解决关于MD5弱点的担忧- C#服务器在我的Android客户端用户的PC上运行。 在许多情况下,他们将使用自己的LAN上的Wi-Fi访问服务器,但是,在自己的风险下,他们可能选择从互联网访问它。 此外,服务器上的服务需要将MD5直通到我无法控制的第三方应用程序。


6
不要使用MD5,应使用SHA512。 - SLaks
1
为什么?SHA512并不比MD5更难。你不想在五年后被困在使用MD5的遗留客户端中。 - SLaks
2
希望你在你的协议中使用随机数(nonce),这样你就可以防范重放攻击。 - sarnold
1
@NickJohnson:回答你的问题“为什么你会故意选择更弱的选项?”用另一个问题...你为什么觉得有必要评论我16个月前发布的问题?但是如果你真的想知道(如果你看一下在你上面的SLaks的评论),那是alpha阶段的代码,PC端(不是我写的)使用了MD5哈希。要求基本上是通过场景而不涉及额外的复杂性。当时我有大约10个alpha阶段的测试人员知道风险。自从我提出这个问题以来,更复杂的安全性已经被纳入。 - Squonk
1
...什么?不,这不仅是错误的,而且是非常危险的错误。 - Nick Johnson
显示剩余8条评论
16个回答

243

这里是一个你可以使用的实现(更新为使用更加现代化的Java约定-for:each循环,StringBuilder代替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 "";
}

虽然不推荐在涉及基本安全级别的系统中使用(MD5被认为已经破解并且容易受到攻击),但对于基本任务来说,有时候这已经足够了。


9
请在我的看法中投反对票,这样下面更正确的答案就会浮现出来。 - Adam
4
不提供0的翻译,如下所答。 - SohailAziz
已更新为使用较新的Java标准(for:each,StringBuilder) - loeschg
3
有没有任何未实现MD5算法的Android操作系统(导致抛出“NoSuchAlgorithmException”)? - VSB
你不应该使用这个,因为它会删除一些“0”,Andranik的答案对我有用。 - Bastien
显示剩余2条评论

56

在Android 2.2中,被接受的答案对我不起作用。我不知道为什么,但它“吃掉”了我的一些零(0)。 Apache commons 在Android 2.2上也无法使用,因为它使用的方法仅从Android 2.3.x开始支持。此外,如果您只想MD5一个字符串,那么Apache commons对此来说太复杂了。为什么要保留整个库只是为了使用其中的一个小函数呢...

最后,我在这里找到了以下代码片段,它完美地解决了我的问题。希望它对某人有用...

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;
}

5
对我来说有效。我已更改 md5.getBytes("UTF-8")。我使用以下代码进行了检查:q4m'x68n6_YDB4ty8VC4&}wqBtn^68W,结果为0c70bb931f03b75af1591f261eb77d0b,而不是c70bb931f03b75af1591f261eb77d0b。0出现在原位。 - Inoy
还有一点需要知道的是,如果你在模拟器上运行,可以使用AdRequest.DEVICE_ID_EMULATOR来进行测试,并且你也可以通过日志来检查ID(如此处所述:https://www.howtodroid.com/how-to-find-hashed-device-id-for-admob.html),只需在日志中按"addTestDevice"进行过滤即可。 - android developer

31

androidsnippets.com提供的代码不可靠,因为生成的哈希值中的0可能会被切除。

更好的实现方法请参考此处

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;
}

在Android中,这种情况下“m”可能会为空吗? - android developer
3
您说得对。该捕获块实现不佳,需要有一个返回语句,否则在调用m.update时会出现空引用错误。此外,我不确定这是否会剥离每个字节上的0,但我认为它仍可能会剥离整个32个字符哈希中的前导0。我认为,与其使用toString(16),使用String.Format(“%032X”,bigInt)可以达到预期的效果。此外,您还可以选择是否要使用大写或小写十六进制(“%032x”表示小写)。 - rsimp
1
这个版本有缺陷。如果您的MD5以“0”开头,则生成的MD5将没有前导0。请不要使用此解决方案。 - elcuco

22
如果可以使用Apache Commons Codec,那么这将是更简短的实现方式:
String md5Hex = new String(Hex.encodeHex(DigestUtils.md5(data)));

或 SHA:

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

上述内容的来源

请点击链接并给他的解决方案点赞以奖励正确的人。


Maven仓库链接: https://mvnrepository.com/artifact/commons-codec/commons-codec

当前Maven依赖项(截至2016年7月6日):

<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.10</version>
</dependency>

1
你为什么要在标准库已经存在API的情况下使用外部库呢? - m0skit0
这是唯一一个让我感觉不像在90年代的答案;-) 一个漂亮的一行代码,而其他解决方案看起来像是hack。 - Pat Lee

12

使用DigestUtils的上面的解决方案对我没用。在我的Apache commons版本中(2013年的最新版本),没有这样的类。

我在这个博客中找到了另一个解决方案。它工作得很完美,不需要Apache commons。它看起来比上面被接受的答案中的代码要短一些。

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;
    }
}

您需要这些导入:

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

需要注意的是MD5哈希值的长度为32个字符或更少,谢谢! - Pelanes
3
最后几行可以用以下代码替换:return String.Format("%032X", number); 除此之外,我非常喜欢这个答案。 - rsimp

10

这是对Andranik和Den Delimarsky以上回答的微小变化,但它更加简洁且不需要任何位运算逻辑。相反,它使用内置的String.format方法将字节转换为两个字符的十六进制字符串(不会去掉0)。通常我会在他们的答案上进行评论,但我没有足够的声望这样做。

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;
    }
}

如果您想返回小写字符串,只需将%02X更改为%02x即可。

编辑: 像wzbozon的答案一样,使用BigInteger可以使答案更加简洁:

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;
    }
}

5

这里是@Andranik答案的Kotlin版本。

我们需要将getBytes改成toByteArray(不需要添加字符集UTF-8,因为toByteArray的默认字符集是UTF-8),并将array[i]强制转换为整数。

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
}

希望这能有所帮助


4
我已经用 Kotlin 制作了一个简单的库。
在根 build.gradle 中添加以下内容:
allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

在应用程序的 build.gradle 文件中。
implementation 'com.github.1AboveAll:Hasher:-SNAPSHOT'

用法

在Kotlin中

val ob = Hasher()

然后使用 hash() 方法。
ob.hash("String_You_Want_To_Encode",Hasher.MD5)

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

它将分别返回MD5和SHA-1。
关于该库的更多信息。

https://github.com/ihimanshurawat/Hasher


2

请使用SHA-512,MD5是不安全的

。"最初的回答"
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;
}

2

有用的 Kotlin 扩展函数示例

fun String.toMD5(): String {
    val bytes = MessageDigest.getInstance("MD5").digest(this.toByteArray())
    return bytes.toHex()
}

fun ByteArray.toHex(): String {
    return joinToString("") { "%02x".format(it) }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接