Verifying the authenticity of multiple files by their hashes

  1. Start the Python shell and configure its session.

    The following variables should be available now:

    >>> base_url  # the base URL of the API
    'https://eu2-cloud.acronis.com/api/notary/v2'
    >>> auth  # the 'Authorization' header value with the access token
    {'Authorization': 'Bearer 8770b34b74f9e4d9424eff50c38182bb4ae7f5596582ae61900b1b6a23e3ec58'}
    
  2. Define a variable named file_paths_certificate_ids, and then assign an object with file paths as keys and notarization certificate IDs as values to this variable:

    >>> file_paths_certificate_ids = {
    ...     '<path to file 1>': '7605f73deaee7b071a570b3ac20cc9fe7a3abf337be7c86c55c28af9d3d8435c',
    ...     '<path to file 2>': 'c7be6b0e99c854d1b8fc233a21beabecc1229a6061fe3b1c15a6cbf27222a35e'
    ... }
    

    Important

    If the path contains backslashes, remember to escape them with an additional backslash.

  3. Prepare the request data:

    1. Define a variable named hashes_data and assign an empty list to it:

      >>> hashes_data = []
      
    2. Create a loop that will iterate over the object with file paths and their notarization certificate IDs:

      >>> for file_path, certificate_id in file_paths_certificate_ids.items():
      ...     # Handle "FileNotFoundError" exception and do not add hash data if file was not found
      ...     try:
      ...         # Open the file for reading in binary mode
      ...         file = open(file_path, 'rb')
      ...         # Calculate SHA-256 hash of the file
      ...         sha256 = hashlib.sha256()
      ...         chunk = file.read(128 * sha256.block_size)
      ...         while chunk:
      ...             sha256.update(chunk)
      ...             chunk = file.read(128 * sha256.block_size)
      ...         file_hash = sha256.hexdigest()
      ...         # Close the file
      ...         file.close()
      ...     except FileNotFoundError:
      ...         print(f'{file_path} does not exist.')
      ...         continue
      ...     # Append an object with the file hash and notarization certificate ID corresponding to this file hash
      ...     hashes_data.append(
      ...         {
      ...             'hash': file_hash,
      ...             'certificate_hash': certificate_id
      ...         }
      ...     )
      
  4. Convert the hashes_data object to a JSON text:

    >>> hashes_data = json.dumps(hashes_data, indent=4)
    
  5. Send a POST request with the JSON text to the /hashes/verify_batch endpoint:

    >>> response = requests.post(
    ...     f'{base_url}/hashes/verify_batch',
    ...     headers={'Content-Type': 'application/json', **auth},
    ...     data=hashes_data,
    ... )
    

    Note

    The /hashes/verify_batch endpoint does not require authentication so Authorization header can be safely omitted.

  6. Check the status code of the response:

    >>> response.status_code
    200
    

    Status code 200 means that the notary service has found the notarization certificate for at least one of the files (the file is notarized or is being notarized) and responded to you with this data. If the status code is 404, no certificates are found (the service has never received and notarized provided file hashes).

    A different status code means that an error has occurred. For the details, refer to “Status and error codes”.

    Also, the response body contains the certificates key containing an array of notarization certificate objects formatted as a JSON text. When converted to an object, it will look as follows:

    >>> pprint.pprint(response.json())
    {'certificates': [{'contract': '0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF',
                       'eventtime': '2019-11-11T13:07:07.004366Z',
                       'id': '7605f73deaee7b071a570b3ac20cc9fe7a3abf337be7c86c55c28af9d3d8435c',
                       'merkle_proof': '[{"left":"88c20ca21dd6fa9e0a64c7e981a012812bbca152010195cd4296d959cfa35f1e"}]',
                       'merkle_root': '6d05fb9f0c2cff4942987661a44e71f0f554d435ce494dd3e7a21df6c6ba963c',
                       'notarized_location': 'beta-baas',
                       'object': {'eTag': '2c7c3d5f244f1a40069a32224215e0cf9b42485c99d80f357d76f006359c7a18',
                                  'key': '2c7c3d5f244f1a40069a32224215e0cf9b42485c99d80f357d76f006359c7a18',
                                  'sequencer': '89a8927268f22ee9af',
                                  'size': 0},
                       'blockchain': 'eth',
                       'qr_code': 'data:image/png;base64,iVBORw0KGgoAAAANSUh...',
                       'sender': '0x201354729f8d0f8b64e9a0c353c672c6a66b3857',
                       'signee_details': {'tenant_name': 'John Doe'},
                       'timestamp': 1573572432,
                       'txid': '0x6494a098f6487ebbcfa85b7cbe64c1f9f077f03866477b67be64320ea109fa73',
                       'version': '3.1'},
                      {'contract': '',
                       'eventtime': None,
                       'id': '',
                       'merkle_proof': '',
                       'merkle_root': '',
                       'object': None,
                       'sender': '',
                       'timestamp': 0,
                       'txid': ''}]}
    

    If the id key is empty, this means that the hash was not sent for notarization.

    If the notarization is complete:

    • The txid key contains the hash of the blockchain transaction that can be viewed on https://etherscan.io/tx/{txid}.

    • The contract, sender, merkle_root, and merkle_proof keys contain the blockchain transaction details.

    • The timestamp key contains the Unix time when the hash value of the file contents was written to the blockchain (notarization completion time).

    • The eTag key of the object object contains the actual hash value that was written to the blockchain.

    • The web version of the notarization certificate is available at https://eu2-cloud.acronis.com/notary/certificate/{certificate_id}.

    Empty txid, contract, merkle_proof, merkle_root, and sender keys mean that the notarization is still in progress and the web version of the certificate is not created yet.

Full code example

  1#!/usr/bin/env python3
  2
  3import requests  # Will be used for sending requests to the API.
  4import hashlib   # Will be used for calculating hash values.
  5import os.path   # Will be used for path-related operations.
  6import pprint    # Will be used for formatting the output of JSON objects received in API responses.
  7import json      # Will be used for converting dictionaries into JSON text
  8
  9# Define variables named "LOGIN" and "PASSWORD" and then assign them with your account credentials
 10LOGIN = '<your login>'        # Change login here
 11PASSWORD = '<your password>'  # Change password here
 12
 13# Define a variable named "cloud_url" and then assign it with the URL of the cloud platform
 14cloud_url = 'https://cloud.acronis.com'
 15
 16# Fetch the URL of the data center where your account is located by sending a GET request to the "/api/1/accounts" endpoint
 17response = requests.get(
 18    f'{cloud_url}/api/1/accounts',
 19    params={'login': LOGIN}
 20)
 21response.raise_for_status()
 22
 23# Convert the JSON text that the response body contains to a dictionary and store the data center URL
 24# in a variable that will be used in further requests
 25server_url = response.json()['server_url']
 26
 27# Define a variable named "account_creds", and then assign the username and password to this variable
 28account_creds = {
 29    'username': LOGIN,
 30    'password': PASSWORD
 31}
 32
 33# Generate a token by sending a POST request to the "/api/2/idp/token" with your account credentials to the cloud platform
 34response = requests.post(
 35    f'{server_url}/api/2/idp/token',
 36    headers={'Content-Type': 'application/x-www-form-urlencoded'},
 37    data={'grant_type': 'password', **account_creds}
 38)
 39response.raise_for_status()
 40
 41# Convert the JSON text that the response body contains to a dictionary and then assign it to a variable named "token_info"
 42token_info = response.json()
 43
 44# Define a variable named "auth" and then assign it with a dictionary with "Authorization" key containing
 45# token string formatted as "Bearer <access_token>"
 46auth = {
 47    'Authorization': 'Bearer ' + token_info['access_token']
 48}
 49
 50# Define a variable named "base_url", and then assign the API base URL using the data center URL
 51# to this variable
 52base_url = f'{server_url}/api/notary/v2'
 53
 54# Define a variable named file_paths_certificate_ids, and then assign a dictionary with file paths as keys
 55# and notarization certificate IDs as values to this variable
 56file_paths_certificate_ids = {
 57    '<path to file 1>': '7605f73deaee7b071a570b3ac20cc9fe7a3abf337be7c86c55c28af9d3d8435c',
 58    '<path to file 2>': 'c7be6b0e99c854d1b8fc233a21beabecc1229a6061fe3b1c15a6cbf27222a35e'
 59}
 60
 61# Define multiple_files and files_data variables and then assign an empty list to them
 62hashes_data = []
 63
 64# Create a loop that will iterate over the dictionary with file paths and their notarization certificate IDs
 65for file_path, certificate_id in file_paths_certificate_ids.items():
 66
 67    # Handle "FileNotFoundError" exception and do not add hash data if file was not found
 68    try:
 69        # Open the file for reading in binary mode
 70        file = open(file_path, 'rb')
 71
 72        # Calculate the SHA-256 hash of the file
 73        sha256 = hashlib.sha256()
 74        chunk = file.read(128 * sha256.block_size)
 75        while chunk:
 76            sha256.update(chunk)
 77            chunk = file.read(128 * sha256.block_size)
 78        file_hash = sha256.hexdigest()
 79
 80        # Close the file
 81        file.close()
 82    except FileNotFoundError:
 83        print(f'{file_path} does not exist.')
 84        continue
 85
 86    # Append a dictionary with the file hash and notarization certificate ID corresponding to this file hash
 87    hashes_data.append(
 88        {
 89            'hash': file_hash,
 90            'certificate_hash': certificate_id
 91        }
 92    )
 93
 94# Convert the "hashes_data" dictionary to a JSON text
 95hashes_data = json.dumps(hashes_data, indent=4)
 96
 97# Send the hashes for verification by sending a POST request to the "/hashes/verify_batch" endpoint
 98response = requests.post(
 99    f'{base_url}/hashes/verify_batch',
100    headers={'Content-Type': 'application/json', **auth},
101    data=hashes_data,
102)
103response.raise_for_status()