Browse Source

Dry up Hashing BytesReference (#72443)

Dries up the efficient way to hash a bytes reference and makes use
of it in a few other spots that were needlessly copying all bytes in
the bytes reference for hashing.
Armin Braun 4 years ago
parent
commit
0220dfb3fe

+ 3 - 12
plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageBlobStore.java

@@ -21,8 +21,6 @@ import com.google.cloud.storage.StorageException;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.message.ParameterizedMessage;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.BytesRefIterator;
 import org.elasticsearch.ExceptionsHelper;
 import org.elasticsearch.common.SuppressForbidden;
 import org.elasticsearch.common.blobstore.BlobContainer;
@@ -45,7 +43,6 @@ import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
 import java.nio.channels.WritableByteChannel;
 import java.nio.file.FileAlreadyExistsException;
-import java.security.MessageDigest;
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.Collection;
@@ -231,15 +228,9 @@ class GoogleCloudStorageBlobStore implements BlobStore {
             // Compute md5 here so #writeBlobResumable forces the integrity check on the resumable upload.
             // This is needed since we rely on atomic write behavior when writing BytesReferences in BlobStoreRepository which is not
             // guaranteed for resumable uploads.
-            MessageDigest md5 = MessageDigests.md5();
-            final BytesRefIterator iterator = bytes.iterator();
-            BytesRef ref;
-            while ((ref = iterator.next()) != null) {
-                md5.update(ref.bytes, ref.offset, ref.length);
-            }
-            writeBlobResumable(
-                    BlobInfo.newBuilder(bucketName, blobName).setMd5(Base64.getEncoder().encodeToString(md5.digest())).build(),
-                bytes.streamInput(), bytes.length(), failIfAlreadyExists);
+            final String md5 = Base64.getEncoder().encodeToString(MessageDigests.digest(bytes, MessageDigests.md5()));
+            writeBlobResumable(BlobInfo.newBuilder(bucketName, blobName).setMd5(md5).build(), bytes.streamInput(), bytes.length(),
+                failIfAlreadyExists);
         } else {
             writeBlob(bytes.streamInput(), bytes.length(), failIfAlreadyExists, BlobInfo.newBuilder(bucketName, blobName).build());
         }

+ 25 - 0
server/src/main/java/org/elasticsearch/common/hash/MessageDigests.java

@@ -8,6 +8,11 @@
 
 package org.elasticsearch.common.hash;
 
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefIterator;
+import org.elasticsearch.common.bytes.BytesReference;
+
+import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Objects;
@@ -117,4 +122,24 @@ public final class MessageDigests {
         return result;
     }
 
+    /**
+     * Updates the given digest with the given bytes reference and the returns the result of the digest.
+     *
+     * @param bytesReference bytes to add to digest
+     * @param digest         digest to update and return the result for
+     * @return digest result
+     */
+    public static byte[] digest(BytesReference bytesReference, MessageDigest digest) {
+        final BytesRefIterator iterator = bytesReference.iterator();
+        BytesRef ref;
+        try {
+            while ((ref = iterator.next()) != null) {
+                digest.update(ref.bytes, ref.offset, ref.length);
+            }
+        } catch (IOException e) {
+            throw new AssertionError("no actual IO happens here", e);
+        }
+        return digest.digest();
+    }
+
 }

+ 1 - 14
test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpHandler.java

@@ -10,8 +10,6 @@ package fixture.s3;
 import com.sun.net.httpserver.Headers;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.BytesRefIterator;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.SuppressForbidden;
 import org.elasticsearch.common.UUIDs;
@@ -27,11 +25,9 @@ import org.elasticsearch.rest.RestUtils;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -45,8 +41,6 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.zip.CheckedInputStream;
-import java.util.zip.Checksum;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -341,14 +335,7 @@ public class S3HttpHandler implements HttpHandler {
                             "[bytes read=" + bytesReference.length() + ", expected=" + headerDecodedContentLength + "]");
                 }
             }
-
-            final MessageDigest digest = MessageDigests.md5();
-            BytesRef ref;
-            final BytesRefIterator iterator = bytesReference.iterator();
-            while ((ref = iterator.next()) != null) {
-                digest.update(ref.bytes, ref.offset, ref.length);
-            }
-            return Tuple.tuple(MessageDigests.toHexString(digest.digest()), bytesReference);
+            return Tuple.tuple(MessageDigests.toHexString(MessageDigests.digest(bytesReference, MessageDigests.md5())), bytesReference);
         } catch (Exception e) {
             exchange.sendResponseHeaders(500, 0);
             try (PrintStream printStream = new PrintStream(exchange.getResponseBody())) {

+ 3 - 4
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java

@@ -1218,11 +1218,10 @@ public class ApiKeyService {
 
         public CachedApiKeyDoc toCachedApiKeyDoc() {
             final MessageDigest digest = MessageDigests.sha256();
-            digest.update(BytesReference.toBytes(roleDescriptorsBytes));
-            final String roleDescriptorsHash = MessageDigests.toHexString(digest.digest());
+            final String roleDescriptorsHash = MessageDigests.toHexString(MessageDigests.digest(roleDescriptorsBytes, digest));
             digest.reset();
-            digest.update(BytesReference.toBytes(limitedByRoleDescriptorsBytes));
-            final String limitedByRoleDescriptorsHash = MessageDigests.toHexString(digest.digest());
+            final String limitedByRoleDescriptorsHash =
+                MessageDigests.toHexString(MessageDigests.digest(limitedByRoleDescriptorsBytes, digest));
             return new CachedApiKeyDoc(
                 creationTime,
                 expirationTime,

+ 2 - 2
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java

@@ -345,8 +345,8 @@ public class CompositeRolesStore {
 
     private void buildAndCacheRoleForApiKey(Authentication authentication, boolean limitedBy, ActionListener<Role> roleActionListener) {
         final Tuple<String, BytesReference> apiKeyIdAndBytes = apiKeyService.getApiKeyIdAndRoleBytes(authentication, limitedBy);
-        final String roleDescriptorsHash = MessageDigests.toHexString(
-            MessageDigests.sha256().digest(BytesReference.toBytes(apiKeyIdAndBytes.v2())));
+        final String roleDescriptorsHash =
+                MessageDigests.toHexString(MessageDigests.digest(apiKeyIdAndBytes.v2(), MessageDigests.sha256()));
         final RoleKey roleKey = new RoleKey(Set.of("apikey:" + roleDescriptorsHash), limitedBy ? "apikey_limited_role" : "apikey_role");
         final Role existing = roleCache.get(roleKey);
         if (existing == null) {