同时使用Arrays.equals() 和 MessageDigest.isEqual() 来比较两个数组的相等性。在许多情况下,它们可以互换使用。但是,它们确实有一些区别,导致在实际应用中使用场景不同。
一个区别是,传递给MessageDigest.isEqual()的数组不能为null,而Arrays.equals()则可以。
这两种方法之间的一个主要区别是,Arrays.equals()不是恒定时间算法,而MessageDigest.isEqual()是恒定时间算法。这意味着,当比较两个数组时,数组逐字节比较,Arrays.equals()将立即返回第一个不相等的字节。MessageDigest.isEqual()将比较数组中的所有字节,无论第一个不相等的字节在哪里。
Arrays.equals()的实现是:
public static boolean equals(byte[] a, byte[] a2) { if (a==a2) return true; if (a==null || a2==null) return false; int length = a.length; if (a2.length != length) return false; for (int i=0; i<length; i++) if (a[i] != a2[i]) return false; return true; }
从for循环可以看出,Arrays.equals()是一种快速失败方法。如果第一个字节不相等,它将立即返回。这通常意味着效率高,但安全性低。实际上,从实现来看,此方法的设计是为了提高效率。它们进行了一些检查以快速确定两个数组是否相等,而无需实际比较数组的内容。
MessageDigest.isEqual()的实现是:
public static boolean isEqual(byte[] digesta, byte[] digestb) { if (digesta.length != digestb.length) { return false; } int result = 0; // time-constant comparison for (int i = 0; i < digesta.length; i++) { result |= digesta[i] ^ digestb[i]; } return result == 0; }
从for循环可以看出,数组被完全比较。这意味着安全性高,但效率低。
在高安全要求的系统中,Arrays.equals()可能容易受到恒定时间攻击。例如,如果使用Arrays.equals()进行哈希或密钥比较,攻击者可以伪造哈希或密钥并与实际哈希或密钥进行比较。他们可以测量Arrays.equals()返回所需的时间,以了解有多少字节与伪造的哈希或密钥匹配。经过数百或数千次尝试后,他们可能会泄露实际的哈希和密钥。有关此攻击如何工作的详细信息,您可以阅读A Lesson In Timing Attacks (or, Don’t use MessageDigest.isEquals)。
因此,当您决定使用哪种方法来比较两个数组时,请仔细考虑应用程序是关注安全性还是关注效率。如果需要效率,那么使用Arrays.equals();但是,如果您正在开发安全产品,那么请谨慎并继续使用MessageDigest.isDigest()。
记住,黑客是一群“无所不用其极;不择手段”的人。