使用字符串哈希码作为 etag 是否安全

is it safe to use string hashcode as etag

给定某个输入参数,休息一下api我想使用哈希码作为 etag。 json 响应发生变化且哈希码相同的概率是多少?

或者有更好的方法吗?

@GET
    public Response getConfigurationForView(@PathParam("in1") String in1, @Context Request request) throws Exception {
        String jsonResponse = getJsonResponse(in1);
        EntityTag etag = new EntityTag(Integer.toString(in1.hashCode()) + "-" + Integer.toString(jsonResponse.hashCode()));
        ResponseBuilder builder = request.evaluatePreconditions(etag);


         if(builder == null){
             builder = Response.ok(jsonResponse, MediaType.APPLICATION_JSON);
             builder.tag(etag);
         }

        return builder.build();
    }

考虑到对于所有字符串,您只有 40 亿个可能的哈希码,因此您最终会遇到 ETag 冲突的可能性很大。

查看 String.hashCode() 的实现方式:

        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;

-- 你甚至可以自己想出可能发生的碰撞。例如,""(空字符串)和 "[=15=]"(仅包含 [=16=] 字符的字符串)将为您提供相同的 hashCode 0.

我建议您使用 SHA1 哈希(或 MD5,但请先参阅 security and CPU running time 上的这些说明)。

假设您继续使用 SHA1 哈希,您的代码可能如下所示:

public static String calculateEtag(final String s) throws java.security.NoSuchAlgorithmException {
    final java.nio.ByteBuffer buf = java.nio.charset.StandardCharsets.UTF_8.encode(s);
    final java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA1");
    buf.mark();
    digest.update(buf);
    buf.reset();
    return String.format("W/\"%s\"", javax.xml.bind.DatatypeConverter.printHexBinary(digest.digest()));
}

这将产生与 sha1sum 实用程序相同的输出。 您也可以使用 BigInteger 将字节缓冲区转换为十六进制字符串:

new BigInteger(1, digest.digest()).toString(16)

-- 但是 javax.xml.bind.DatatypeConverter.printHexBinary() 快了好几倍。

如果你会用jdk8+和GoogleGuava,你可以试试

final String myETag = ( (Set<Object>) this.setOfChangeableProperties ).stream()
  .filter( Objects::nonNull )
  .map( Objects::toString )
  .reduce( (a,b) -> a.concat(b) )
  .map( s -> Hashing.md5().hashUnencodedChars( s ).toString() )
  .orElse( "nothing of interest to hash!" )