我该如何在Android中计算字符串的SHA-256哈希值?

56

我正在尝试在Android中获取一个字符串的SHA256。

这里是我想匹配的PHP代码:

echo bin2hex(mhash(MHASH_SHA256,"asdf"));
//outputs "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"

现在,我正在Java中尝试执行以下操作:

            String password="asdf"
            MessageDigest digest=null;
    try {
        digest = MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
       digest.reset();
       try {
        Log.i("Eamorr",digest.digest(password.getBytes("UTF-8")).toString());
    } catch (UnsupportedEncodingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

但是这会输出:"a42yzk3axdv3k4yh98g8"

我在这里做错了什么?


感谢erickson提供的解决方案:

 Log.i("Eamorr",bin2hex(getHash("asdf")));

 public byte[] getHash(String password) {
       MessageDigest digest=null;
    try {
        digest = MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
       digest.reset();
       return digest.digest(password.getBytes());
 }
static String bin2hex(byte[] data) {
    return String.format("%0" + (data.length*2) + "X", new BigInteger(1, data));
}

1
我认为你的问题可能是getBytes(UTF-8)。尝试使用getBytes()。 - Nicholas
5
为什么有人需要在不指定编码的情况下使用getBytes()方法? - Roland Illig
5
@Eamorr:您能否将解决方案放在答案中而不是问题中? - Paŭlo Ebermann
1
这里有一个干净的答案:https://dev59.com/4mkw5IYBdhLWcg3wwdUT - ThomasW
@ShanXeeshi,你说得对。这些哈希是数据的单向编码,不能(轻易地)反转以重新创建输入。除非你指的是从byte[]返回到String,那么就是new String(inputBytes, StandardCharsets.UTF_8);或类似的方法(你需要提前知道编码)。 - indivisible
显示剩余2条评论
5个回答

34

PHP函数bin2hex的意思是将一串字节编码为十六进制数字。

在Java代码中,您正在尝试使用平台的默认字符编码将一堆随机字节解码为字符串。这不会起作用,即使它起作用,它也不会产生相同的结果。

以下是一个快速粗糙的Java二进制到十六进制转换示例:

static String bin2hex(byte[] data) {
    StringBuilder hex = new StringBuilder(data.length * 2);
    for (byte b : data)
        hex.append(String.format("%02x", b & 0xFF));
    return hex.toString();
}

这很容易 编写,但不一定很快执行。如果你要频繁使用它们,应该用更快的实现重新编写该函数。


实际上,在使用摘要函数生成的数据情况下,new BigInteger(1, data).toString(16) 就足够了,因为它保证没有任何一个半字节是 0。 - class stacker
@ClassStacker 0x35326b9705d136d0d1b5efa92d440f3171f1b711的SHA-1哈希值是多少? - erickson
@erickson 我肯定要为声称没有任何“nibble”会是零而受到责备,但是现在你可以看到;根据Python的说法,它是\x00P\xd5\rv\xc8\x15u\x84*\xf3\x1d\xc3L\x9b\x14{\xedX?我承认我的错误。感谢你指出这一点。 - class stacker
2
@ClassStacker 没问题。SHA-1和SHA-2哈希,以及我假设任何加密摘要,都可以产生零nybbles和字节,包括前导零。我并不意外,但当你说这是不可能的时候,验证我的假设并没有花费太长时间。 - erickson
@erickson 这对你和我可能不是问题,但我认为这可能会在其他地方偶尔引起麻烦。 ;) - class stacker

27

你的想法是正确的,但是转换字节会更加复杂一些。以下代码在我的设备上可以正常运行:

// utility function
    private static String bytesToHexString(byte[] bytes) {
        // https://dev59.com/HXRC5IYBdhLWcg3wVvct
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

// generate a hash

    String password="asdf";
    MessageDigest digest=null;
    String hash;
    try {
        digest = MessageDigest.getInstance("SHA-256");
        digest.update(password.getBytes());

        hash = bytesToHexString(digest.digest());

        Log.i("Eamorr", "result is " + hash);
    } catch (NoSuchAlgorithmException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }

来源:bytesToHexString函数取自IOSched项目。


4
注意:你从IOSched项目中采用的bytesToHexString函数会以小写格式返回字符串。如果通过SHA256(SHA256...(SHA256(input)...)进行链接,则与返回大写格式字符串的实现会得到不同的结果。可以通过将负责的行更改为String hex = Integer.toHexString(0xFF & bytes[i]).toUpperCase();来轻松解决这个问题。 - Anton Kaiser

8

感谢erickson,以下是带使用示例的完整答案。

public static String getSha256Hash(String password) {
    try {
        MessageDigest digest = null;
        try {
            digest = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e1) {
            e1.printStackTrace();
        }
        digest.reset();
        return bin2hex(digest.digest(password.getBytes()));
    } catch (Exception ignored) {
        return null;
    }
}

private static String bin2hex(byte[] data) {
    StringBuilder hex = new StringBuilder(data.length * 2);
    for (byte b : data)
        hex.append(String.format("%02x", b & 0xFF));
    return hex.toString();
}

使用示例:

Toast.makeText(this, Utils.getSha256Hash("123456_MY_PASSWORD"), Toast.LENGTH_SHORT).show();

1
我知道这个问题已经有答案了,但我在android arsenal上找到了一个哈希库,它非常简单易用,只需要一行代码就能实现。可以哈希MD5、SHA-1、SHA-256、SHA-384或SHA-512。
1. 首先将以下内容添加到gradle并同步 implementation 'com.github.1AboveAll:Hasher:1.2' 2. 开始哈希 Hasher.Companion.hash("Hello",HashType.SHA_1);

0
private fun stringToSha256(input: String): String {
    val bytes = input.toByteArray()
    val md = MessageDigest.getInstance("SHA-256")
    val digest = md.digest(bytes)

    // Convert the byte array to a hexadecimal string
    val result = StringBuilder()
    for (byte in digest) {
        result.append(String.format("%02x", byte))
    }

    return result.toString()
}

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