Blockchain Verify provides irrefutable evidence of backup data integrity by storing a cryptographic hash of the backup on a public ledger. Organizations can meet regulatory compliance requirements with a time-stamped audit trail of all modifications and deletions.
Blockchain Verify is a feature that allows customers to verify that their data in their backups has not been changed or deleted. The best practice for integrity verification is to compute a cryptographic hash (e.g., SHA256) of data when it is initially preserved and to store the hash value for future comparison. Blockchain Verify follows this best practice, and writes the hash value, along with the backup ID and the date of the backup, to the blockchain on a public ledger to enable irrefutable and independent confirmation by Own, or any third party with authorized access to the blockchain. This is to allow customers to prove the integrity of their backups as a reliable source to restore normal operations, and specifically meet the SEC17a-4 regulation, intended for fraud prevention. The verification process is performed via an independent hash calculation of the backup data. To verify the data is unchanged, customers or auditors can calculate the backup hash themselves and compare it to the one on the blockchain. If the hash values are identical, the data was not changed and the backup is reliable, complete and accurate.
Generating a Hash
Secure hash algorithms are used to map data to fixed-sized values in a secure manner suited for cryptographic applications. The same content, run through the same hash algorithm, always produces the same hash. Therefore, secure hash functions are widely used to irrefutably and independently confirm the integrity of data.
Recalculating the backup hash, identical to the one stored on the blockchain, is a multi-step procedure. Within the zip file containing the backup are individual object data files (.csv.gz files).
Each object data file within the backup is hashed with the SHA-256 algorithm. Those hashes, in ascending order, are concatenated and hashed with the SHA-256 algorithm, resulting in the final hash.
Verify Unchanged Backups Using Hash Algorithms
Confirm the integrity of your Dynamics backup data using a cryptographic hash stored on a public ledger. Using blockchain verification helps you meet compliance requirements and prevent fraud by maintaining a time-stamped audit trail.
- Navigate to the Backup History.
- Select the backup to verify.
- Click Integrity details.
-
Click Download zip file.
A Backup Export job will begin.
- When the job is complete, click the Click here to download link to download the backup files.
- Sort all files in the backup in ascending order.
-
Hash each file with the SHA-256 algorithm.
Example of the hashed file:
8c4bdaf46e3764ce4b53bdc9e410d5b6d89fa586bfb418fc572f7b4cedf2d93a -
Concatenate the hashes with their object name, separated by a semi-colon (;). Then
concatenate all the hashes, each separated by a line break (ASCII 10), without white spaces.
Make sure the final line ends with a line break. The concatenated string must be encoded with
UTF-8.
Example of the hashed file:
8c4bdaf46e3764ce4b53bdc9e410d5b6d89fa586bfb418fc572f7b4cedf2d93a;accepted_event_relations.csv.gz dd27948844ce32d5510a4a2d7f1f054c58248e3e754f81330a3a7f33c5d2e6c2;account_brand_shares.csv.gz 2cfa4d7c8d830ccf5a1f9c37d5f1a705d4573269aa6acc9468eda0d99af22842;account_brands.csv.gz -
Hash the concatenated hashes with the SHA-256 algorithm.
The final hash should appear similar to the following example:
4c6d3f1f97e7a6001f9764f08b0b5d9e9a8602b2c5386b02a26c4dc1db749bbfThe following is an example hash script in Python.Note The following is meant to provide an example of how the script could be written and should not be used as-is.import hashlib import json import os import argparse LINE_SEP = "\n" BUF_SIZE = 65536 def hash_backup(directory_path): """ Hash all .csv.gz files in the specified directory. Args: directory_path (str): Path to directory containing backup files. Returns: str: JSON string containing the backup_hash """ # Check if directory exists if not os.path.exists(directory_path): raise FileNotFoundError(f"Directory not found: {directory_path}") if not os.path.isdir(directory_path): raise NotADirectoryError(f"Path is not a directory: {directory_path}") hashes = [] # Local variable instead of global try: for filename in sorted( os.listdir(directory_path) ): # loops over each file in directory (backup folder), in ascending order if filename.endswith(".csv.gz"): # only hashes the object (.csv.gz) files file_path = os.path.join(directory_path, filename) sha = hashlib.sha256() try: with open(file_path, "rb") as f: while True: # reads the file in BUF_SIZE buffers data = f.read(BUF_SIZE) if not data: break # hashes the file with the SHA-256 algorithm sha.update(data) hashes.append( "{} {}".format(sha.hexdigest(), filename) ) # adds the hash and object name to the hashes list except (IOError, OSError) as e: print(f"Warning: Could not read file {filename}: {e}") continue if not hashes: raise ValueError(f"No .csv.gz files found in directory: {directory_path}") hashes_string = ( LINE_SEP.join(hashes) + LINE_SEP ) # concatenates all the hashes in the hashes list # Replace all double spaces with semicolons (more robust) encoded = hashes_string.replace(" ", ";").encode("utf-8") final_hash = hashlib.sha256( encoded ).hexdigest() # hashes the concatenated hashes with the SHA-256 algorithm return json.dumps({"backup_hash": final_hash}) except Exception as e: raise RuntimeError(f"Error processing directory {directory_path}: {e}") def main(): parser = argparse.ArgumentParser(description="Hash all .csv.gz files in a directory") parser.add_argument("-f", "--file-directory", required=True, help="Directory containing .csv.gz files to hash") args = parser.parse_args() try: result = hash_backup(args.file_directory) print(f"backup hash: {result}") except Exception as e: print(f"Error: {e}") exit(1) if __name__ == "__main__": main()