我正在实施名为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"));
答案 0 :(得分:9)
您似乎误解了字符串编码的工作原理,并且您的Crypt
类的API值得怀疑。
字符串实际上并没有“编码” - 编码就是用来在字符串和字节之间进行转换的。
Java Strings在内部存储为UTF-16,但这并不重要,因为MD5适用于字节,而不是字符串。你的Crypt.md5sum()
方法必须首先将它传递的字符串转换为字节 - 它使用什么编码来做到这一点?这可能是你问题的根源。
您的示例代码非常荒谬,因为此行的唯一效果是:
String prehashIso = new String(prehash.getBytes("ISO-8859-1"), "ISO-8859-1");
是用问号替换ISO-8859-1中无法表示的字符。
答案 1 :(得分: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。
当我presash.getBytes( "utf-8" )
时,它产生了“9CC2E0D1D41E67BE9C2AB4AABDB6FD3”(并且字节数组的长度变为332)。再一次,不是你要找的结果。
所以,我猜Suomen Verkkomaksut对他们没有记录的prehash
字符串做了一些按摩,或者你忽略了。
答案 2 :(得分:2)
不确定你是否解决了问题,但我对ISO-8859-1编码的字符串有一个类似的问题,有nordicä&amp; ö字符并计算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编解码器Hex类。
答案 3 :(得分:1)
如果您发送他们视为ISO-8859-1的UTF-8编码数据,那么这可能是您的问题的根源。我建议您发送ISO-8859-1中的数据或尝试与Suomen Verkkomaksut沟通您发送的UTF-8。在基于http的协议中,您可以通过在HTTP标头中向Content-Type添加charset = utf-8来实现此目的。
排除某些问题的方法是尝试preshsh字符串,该字符串仅包含在UTF-8和ISO-8859-1中编码相同的字符。从我所看到的,您可以通过删除所用字符串中的所有“ä”字符来实现此目的。