public void run() {
    decryptJobMetricsTracker.onJobProcessingStart();
    Exception exception = null;
    ByteBuf decryptedBlobContent = null;
    ByteBuffer decryptedUserMetadata = null;
    try {
      Object containerKey =
          kms.getKey(getBlobOptions == null ? null : getBlobOptions.getRestRequest(), blobId.getAccountId(),
              blobId.getContainerId());
      Object perBlobKey = cryptoService.decryptKey(encryptedPerBlobKey, containerKey);
      if (encryptedBlobContent != null) {
        decryptedBlobContent = cryptoService.decrypt(encryptedBlobContent, perBlobKey);
      }
      if (encryptedUserMetadata != null) {
        decryptedUserMetadata = cryptoService.decrypt(encryptedUserMetadata, perBlobKey);
      }
    } catch (Exception e) {
      exception = e;
      if (decryptedBlobContent != null) {
        decryptedBlobContent.release();
        decryptedBlobContent = null;
      }
    } finally {
      // After decryption, we release the ByteBuf;
      if (encryptedBlobContent != null) {
        encryptedBlobContent.release();
      }
      decryptJobMetricsTracker.onJobProcessingComplete();
      callback.onCompletion(
          exception == null ? new DecryptJobResult(blobId, decryptedBlobContent, decryptedUserMetadata) : null,
          exception);
    }
  }
Steps to be performed on decryption
1. Fetch ContainerKey from kms for the given blob
2. Decrypt encryptedPerBlobKey using containerKey to obtain perBlobKey
3. Decrypt encryptedContent using perBlobKey if not null
4. Decrypt encryptedUserMeta using perBlobKey if not null
5. Invoke callback with the decryptedBlobContent
ambry-router/src/main/java/com/github/ambry/router/DecryptJob.java