Java中ISO-8859-1字符串的MD5哈希值

3
我正在实现数字支付服务的接口,称为Suomen Verkkomaksut。有关付款的信息通过HTML表单发送给他们。为确保在传输过程中没有人擅自更改信息,使用特殊密钥在双方计算MD5哈希值,但该密钥不会被发送给他们。
我的问题是,由于某种原因,他们似乎决定输入的数据采用ISO-8859-1而不是UTF-8进行编码。我向他们发送的哈希值是使用UTF-8字符串计算的,因此与他们计算的哈希值不同。
我尝试了以下代码:
String prehash = "6pKF4jkv97zmqBJ3ZL8gUw5DfT2NMQ|13466|123456||Testitilaus|EUR|http://www.esimerkki.fi/success|http://www.esimerkki.fi/cancel|http://www.esimerkki.fi/notify|5.1|fi_FI|0412345678|0412345678|esimerkki@esimerkki.fi|Matti|Meikäläinen||Testikatu 1|40500|Jyväskylä|FI|1|2|Tuote #101|101|1|10.00|22.00|0|1|Tuote #202|202|2|8.50|22.00|0|1";
String prehashIso = new String(prehash.getBytes("ISO-8859-1"), "ISO-8859-1");

String hash = Crypt.md5sum(prehash).toUpperCase(); 
String hashIso = Crypt.md5sum(prehashIso).toUpperCase();

不幸的是,这两个哈希值都与值C83CF67455AF10913D54252737F30E21相同。根据Suomen Verkkomaksut的文档,此示例案例的正确值为975816A41B9EB79B18B3B4526569640E。

有没有一种方法可以使用ISO-8859-1字符串在Java中计算MD5哈希值?

更新:在等待Suomen Verkkomaksut的答案时,我找到了另一种制作哈希值的方法。Michael Borgwardt纠正了我对字符串和编码的理解,我寻找了一种从byte[]制作哈希值的方法。

Apache Commons是一个极好的库源,我找到了它们的DigestUtils类,该类具有md5hex函数,它接受byte[]输入并返回32个字符的十六进制字符串。

出于某种原因,这仍然无法正常工作。这两个值仍然相同:

DigestUtils.md5Hex(prehash.getBytes());
DigestUtils.md5Hex(prehash.getBytes("ISO-8859-1"));

如果想要获得可预测的结果,永远不要使用getBytes()的第一种形式。它使用你系统的默认编码。由于你来自芬兰,最可能的默认编码是“ISO-8859-1”,因此两个调用将产生相同的结果。 - Alexander Pogrebnyak
4个回答

9
您似乎误解了字符串编码的工作原理,您的 Crypt 类的 API 可疑。
字符串并没有真正的“拥有编码” - 编码是用于在字符串和字节之间进行转换的方式。
Java 字符串在内部存储为 UTF-16,但这并不重要,因为 MD5 作用于字节而不是字符串。您的 Crypt.md5sum() 方法必须先将其传递的字符串转换为字节 - 它使用什么编码来执行此操作?这可能是问题的根源。
您的示例代码非常荒谬,因为它仅产生以下影响:
String prehashIso = new String(prehash.getBytes("ISO-8859-1"), "ISO-8859-1");

将无法在ISO-8859-1中表示的字符替换为问号。


对于Crypt类的可疑性加1。它还表明加密和密码哈希可能存在混淆(但这取决于类的其余部分是否存在这种情况)。 - Romain

2

不确定您是否解决了问题,但我遇到了一个类似的问题,即使用 ISO-8859-1 编码的字符串中包含北欧 ä 和 ö 字符,并计算 SHA-256 哈希以与文档中的内容进行比较。以下代码片段对我有用:

import java.security.MessageDigest;
//imports omitted

@Test
public void test() throws ProcessingException{
String test = "iamastringwithäöchars";           
System.out.println(this.digest(test));      
}

public String digest(String data) throws ProcessingException {
    MessageDigest hash = null;

    try{
        hash = MessageDigest.getInstance("SHA-256");
    }
    catch(Throwable throwable){
        throw new ProcessingException(throwable);
    }
    byte[] digested = null;
    try {
        digested = hash.digest(data.getBytes("ISO-8859-1"));
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    String ret = BinaryUtils.BinToHexString(digested);
    return ret;
}

将字节转换为十六进制字符串有许多选项,包括在此线程中提到的apache commons codec Hex类。


2

Java有一个标准的java.security.MessageDigest类,用于计算不同的哈希值。

以下是示例代码

include java.security.MessageDigest;

// Exception handling not shown

String prehash = ...

final byte[] prehashBytes= prehash.getBytes( "iso-8859-1" );

System.out.println( prehash.length( ) );
System.out.println( prehashBytes.length );

final MessageDigest digester = MessageDigest.getInstance( "MD5" );

digester.update( prehashBytes );

final byte[] digest = digester.digest( );

final StringBuffer hexString = new StringBuffer();

for ( final byte b : digest ) {
    final int intByte = 0xFF & b;

    if ( intByte < 10 )
    {
        hexString.append( "0" );
    }

    hexString.append(
        Integer.toHexString( intByte )
    );
}

System.out.println( hexString.toString( ).toUpperCase( ) );

很遗憾,它生成了相同的“C83CF67455AF10913D54252737F30E21”哈希值。所以,我猜你的Crypto类是无罪的。我特意添加了“prehash”和“prehashBytes”的长度打印输出来验证确实使用了“ISO-8859-1”。在这种情况下,两者都是328。
当我使用“utf-8”对“presash.getBytes()”进行操作时,它生成了“9CC2E0D1D41E67BE9C2AB4AABDB6FD3”(并且字节数组的长度变为332)。同样,这不是你要寻找的结果。
所以,我猜Suomen Verkkomaksut对他们没有记录的“prehash”字符串进行了一些处理,或者你忽略了某些细节。

你的哈希函数在字节小于10时没有用零进行填充。 - BalusC
啊,也许我只能等待他们的答案了。感谢您提供的代码示例。 - Ville Salonen
@BalusC。你说得很对。我已经纠正了我的例子。总是让我感到奇怪的是,Java为什么没有Byte.toHexString和Byte.toUpperHexString来执行正确的操作。 - Alexander Pogrebnyak
只需使用Apache Commons Codec的Hex类即可实现此功能。由于我使用了自己的、有缺陷的byte[]到String转换实现,因此不得不重新构建大量哈希值。 - Malax

1

如果您发送的是UTF-8编码数据,但Suomen Verkkomaksut将其视为ISO-8859-1,则可能是问题的根源。我建议您要么以ISO-8859-1格式发送数据,要么尝试与Suomen Verkkomaksut沟通,告诉他们您正在发送UTF-8编码的数据。在基于http的协议中,您可以通过在HTTP头中添加charset=utf-8来实现这一点。

排除一些问题的方法是尝试使用仅包含在UTF-8和ISO-8859-1中编码相同字符的预哈希字符串。从我所看到的内容来看,您可以通过从使用的字符串中删除所有“ä”字符来实现这一点。


我已经在页面上同时拥有<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />和<?xml version="1.0" encoding="UTF-8"?>。不幸的是,它们似乎没有帮助。但你说得对,也许我应该联系他们。 - Ville Salonen

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