3

I'm implementing an interface for digital payment service called Suomen Verkkomaksut. The information about the payment is sent to them via HTML form. To ensure that no one messes with the information during the transfer a MD5 hash is calculated at both ends with a special key that is not sent to them.

My problem is that for some reason they seem to decide that the incoming data is encoded with ISO-8859-1 and not UTF-8. The hash that I sent to them is calculated with UTF-8 strings so it differs from the hash that they calculate.

I tried this with following code:

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();

Unfortunately both hashes are identical with value C83CF67455AF10913D54252737F30E21. The correct value for this example case is 975816A41B9EB79B18B3B4526569640E according to Suomen Verkkomaksut's documentation.

Is there a way to calculate MD5 hash in Java with ISO-8859-1 strings?

UPDATE: While waiting answer from Suomen Verkkomaksut, I found an alternative way to make the hash. Michael Borgwardt corrected my understanding of String and encodings and I looked for a way to make the hash from byte[].

Apache Commons is an excellent source of libraries and I found their DigestUtils class which has a md5hex function which takes byte[] input and returns a 32 character hex string.

For some reason this still doesn't work. Both of these return the same value:

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

4 回答 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中不能表示的字符。

于 2009-12-03T10:43:55.143 回答
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 课程是无罪的。我特别添加了prehashprehashBytes长度打印输出以验证确实使用了“ISO-8859-1”。在这种情况下,两者都是 328。

当我这样做时presash.getBytes( "utf-8" ),它产生了“9CC2E0D1D41E67BE9C2AB4AABDB6FD3”(字节数组的长度变为332)。同样,不是您正在寻找的结果。

所以,我猜 Suomen Verkkomaksut 对prehash他们没有记录的字符串进行了一些按摩,或者你忽略了。

于 2009-12-03T12:06:08.257 回答
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 类。

于 2011-07-12T09:08:56.580 回答
1

如果您发送他们视为 ISO-8859-1 的 UTF-8 编码数据,那么这可能是您问题的根源。我建议您以 ISO-8859-1 格式发送数据,或者尝试与 Suomen Verkkomaksut 沟通您正在发送 UTF-8。在基于 http 的协议中,您可以通过将 charset=utf-8 添加到 HTTP 标头中的 Content-Type 来执行此操作。

排除某些问题的一种方法是尝试一个 prehash String,它只包含在 UTF-8 和 ISO-8859-1 中编码相同的字符。据我所知,您可以通过删除您使用的字符串中的所有“ä”字符来实现这一点。

于 2009-12-03T10:33:59.007 回答