我有一组对象,我希望从这个对象集合中生成一个哈希值(使用SHA256)。
散列这些对象的过程是零知识证明系统的一部分,其中证明者生成证明,该证明稍后由验证者验证。这个证明实际上是哈希函数的输出。
这些对象都包含3或4个大的BigInteger值(大小为2048位)。对象的数量是可变的,但它将在4到10之间。
我编写了以下方法,用于从可变数量的对象生成哈希值:
public byte[] buildHashFromHashcodes(final Object... listOfObjects) {
for (Object obj : listOfObjects) {
if (obj == null) {
throw new Exception(
"Input objects cannot be null");
}
_md.update(intToBytes(obj.hashCode()));
}
return _md.digest();
}
private byte[] intToBytes(final int intValue) {
return new byte[] {(byte) (intValue >> 24),
(byte) (intValue >> 16), (byte) (intValue >> 8),
(byte) intValue };
}
我的问题与此代码中hashCode方法的使用有关。具体来说,我试图确定hashCode方法的使用是否会削弱系统的安全性,因为它只生成一个32位数,因此只在每次迭代期间使用32位信息更新散列。因此,我不确定此过程中此信息的丢失是否真的会削弱系统。
这些对象的hashCode方法的实现使用大的BigInteger值来生成它们的哈希码,但是在返回之前该数字被截断为int。
我担心的部分原因是某些对象的哈希码之间可能存在冲突。但话说回来,哈希在循环内多次更新,因此单个冲突不会是一个大问题。
让我们假设对象集合中有4个对象。在循环的第一次迭代中,将使用32位来更新散列,在第二次迭代中,将使用另外32位来更新散列等。
据我所知,在调用update方法后执行散列算法。不是128位(4个对象)将存储在缓冲区中,然后使用这128位作为输入执行散列算法。
因此,我们可以说在最终更新之后哈希所处的状态总数将是(2 ^ 32)*(2 ^ 32)*(2 ^ 32)*(2 ^ 32) ? (在实践中,这当然不会发生,因为它会在某些时候被截断)。
我认为使用hashCode是一种安全的方法,因为在每次迭代期间都会调用update方法。
为了避免对象之间发生冲突的风险,另一种方法是使用每个对象的toString()方法,该方法返回一个String,其中包含每个对象的全部熵(BigInteger数字的大值)包含在字符串中)。这意味着在循环的每次迭代期间使用更多信息更新散列,但我不确定是否有必要。
所以,我的问题是,在这段代码中使用hashCode方法会削弱系统的强度吗?
答案 0 :(得分:7)
这是一个可怕的想法。加密散列函数的目的是彻底混合输入数据,以便每个输入位都会影响每个输出位。
通过引入中间hashCode
,您可以使每个输入BigInteger
仅有机会影响一个32位hashCode。因此,单个32位hashCode中的冲突会导致最终哈希值完全冲突。
因此,为了攻击您的方案,攻击者只需要找到一个与输入BigInteger对象的一个具有相同hashCode的BigInteger。这完全不安全。
答案 1 :(得分:1)
你应该做什么而不是调用hashCode
- 它本身就有冲突 - 是以字节为单位散列规范编码。但是BigInteger
,每个数字都有自己独特的编码作为字节数组。并非所有对象都具有这样的规范编码,因此您无法为其创建通用方法。
此外,您还需要一些保持值分开的方法(例如,11可以是1和1或数字11的串联)。最简单的方法可能是在值前加上值的大小。
public static byte[] buildHashFromSeparatedCanonicalValues(final BigInteger ... numbers) {
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-256 should always be available", e);
}
final ByteBuffer lengthBuffer = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE);
for (BigInteger number : numbers) {
if (number == null) {
throw new IllegalArgumentException(
"Input objects cannot be null");
}
final byte[] encodedNumber = number.toByteArray();
lengthBuffer.putInt(encodedNumber.length);
lengthBuffer.flip();
md.update(lengthBuffer);
lengthBuffer.clear();
md.update(encodedNumber);
}
return md.digest();
}
这特定于BigInteger
值。您可以通过序列化对象(实现Serializable
)来使其更通用,但要注意序列化陷阱。