Keeping your important files safe and secure

We all have important files of some sort or another but do we all look after them correctly? Ensuring their security using NTFS ACLs might be the process used by many. Some will add a Read Only marker on the files that are for reference only (ISO's, backups etc) but do you do anything else to ensure the data is unchanged from the time it is written to the storage to the time that you choose to access it?

Let's see how we can use a simple PowerShell cmdlet called Get-FileHash to help us here.

Get-FileHash accepts 4 parameters:

  • Path – this is the l ocation of the file that we want to work with
  • Literalpath – this is used in the place of Path if we need to work with special characters (like wildcards) in our path string. No interpretation of the wildcards is done when passed in via the LiteralPath parameter
  • Algorithm – this is the type of hash we want to create. For our purposes we are not trying to use this for encryption purposes so MD5 and SHA1 are as valid as SHA256 or any other available options. By default SHA256 is used.
  • InputStream – this is used if the file is opened as a stream rather than directly from the file using the -Path parameter

We will only be using the Path parameter for the purposes of this blog but the other parameters are well explained in the Get-FileHash help content. It should be noted that the SHA1 default is a slower process than using an MD5 algorithm value. On the iso shown in the examples here the SHA256 hash regularly took 8-9s while the MD5 took 3-4s.

In its simplest form Get-FileHash returns the SHA1 hash of the file along with the filename and confirmation of the hash algorithm used

 Get-FileHash -Path D:\ISO\en_sql_server_2016_service_pack_1_x64_dvd_9542248.iso | Format-List

Algorithm: SHA256
Hash: 801D596FA10E0619F63AD97413DCCC0AEAA8BD24C6894F5790547397059BBCFB
Path: D:\ISO\en_sql_server_2016_service_pack_1_x64_dvd_9542248.iso

We can pipe a result set from a previous cmdlet to Get-FileHash if we want to create hashes for a set of files

 Get-ChildItem D:\HashTest | Get-FileHash

Algorithm    Hash     Path
---------        ----     ----
SHA256     A48208897580355DBFD87FF55D3AFA5153F359F1B70F9FC01770F5CEBB7BB814    D:\HashTest\Database1.accdb
SHA256     6689D1E139E8AA69BCD646E2A0FC457FE1DC6EB710EBB37330AF33369EB5FE0E     D:\HashTest\Document1.docx
SHA256     801D596FA10E0619F63AD97413DCCC0AEAA8BD24C6894F5790547397059BBCFB     D:\HashTest\en_sql_server_2016_service_pack_1_x64_dvd_9542248.iso
SHA256     63EA7EC057B089A0CFCBA568B40FC5C5016A33865BE77D3822321B369142E60A     D:\HashTest\Hash_Database1_accdb.csvh
SHA256     B403128B954520D356A9BD0C0B474FF9D09B08BD3494C6BE091ECC1107390067     D:\HashTest\Hash_Document1_docx.csvh
SHA256     ECA46DBC21EC3D18CC6065A7D33547E296BFAAEAFE9E587FCD142D2391D8CD91     D:\HashTest\Hash_Presentation1_pptx.csvh
SHA256     4861C11FEC24C3CDE663641AF92C2069EFA10F51A79E24AB92F7B1F684B7CEC0     D:\HashTest\ISOArchive.csv
SHA256     B9100952467E7403F19CF450B150447B4D1B1107FB1D1A6A6D09446EEE6CB778     D:\HashTest\Presentation1.pptx

The output from the cmdlet is an object so we can reference the results from the above by setting the output to a variable and referencing the array index for the value we need.

 $IsoHash = Get-ChildItem D:\HashTest | Get-FileHash -Algorithm MD5
$IsoHash[2].Hash
33B324D311FDFC68A9A3E3405B8CD222
$IsoHash[2].Path
D:\ISO\en_sql_server_2016_service_pack_1_x64_dvd_9542248.iso
$IsoHash[2].Algorithm
MD5

We can also use the whole object with the -match regex comparison to search for files that we have already calculated a hash.

 $FileToConfirm = "en_sql_server_2016"
$r = $IsoHash.path -match $FileToConfirm
if ($r) {
"$r found" | Write-Output
}
else {
"No details found for $FileToConfirm" | Write-Output
}
D:\HashTest\en_sql_server_2016_service_pack_1_x64_dvd_9542248.iso found

Let’s now work on placing all the Get-FileHash output into a file so that we can reference it in months to come to check our iso's etc are guaranteed to be safe to use.

 # export to a file
$IsoHash | Export-csv -Path "D:\HashTest\ISOArchive.csvh"

Opening this file in Notepad we see the information that we need, safely stored for us.

#TYPE Microsoft.Powershell.Utility.FileHash
"Algorithm","Hash","Path"
"MD5","27C2D3E6786CE78F77F45F8B6AEEC97C","D:\HashTest\Database1.accdb"
"MD5","4C3661354D2C104963A8F80749818D6A","D:\HashTest\Document1.docx"
"MD5","33B324D311FDFC68A9A3E3405B8CD222","D:\HashTest\en_sql_server_2016_service_pack_1_x64_dvd_9542248.iso"
"MD5","2141E775DBC0328775CDFED0E7B28058","D:\HashTest\Hash_Database1_accdb.csvh"
"MD5","8DC877E71CE2575B8E34C933D9D2384F","D:\HashTest\Hash_Document1_docx.csvh"
"MD5","BADBCBF3D8ACB5B705A32A9697EBB8A4","D:\HashTest\Hash_Presentation1_pptx.csvh"
"MD5","488890AE63C647350CCCC9E3C47C3D8D","D:\HashTest\ISOArchive.csv"
"MD5","AF6233A16AAEBFD23373EA987BA0D497","D:\HashTest\Presentation1.pptx"

Now, imagine many months pass and we want to use an iso to install some software or we want to ensure that script content hasn’t changed since it was placed into the store location. We are able to generate a hash of the content just before we use it and compare that hash to the value generated when the store was created. Any changes to the file contents will result in a different hash value and we will therefore know that the content has changed and should not be trusted.

 # read hash from file and compare with newly calculated hash
$ISOArchive = @()
$ISOArchive = Import-csv -path "D:\hashtest\ISOArchive.csvh"

# choose the file we want to verify and get the hash of it right now
$FileToCheck = "D:\hashtest\en_sql_server_2016_service_pack_1_x64_dvd_9542248.iso"
$NewFileHash = Get-FileHash $FileToCheck -Algorithm MD5

# check if the new hash is found in the archive
$match = ($ISOArchive.hash) -match [regex]::Escape($NewFileHash.Hash)
if ($match) {
    "The hash for $($match.path) matches the archive value. It is safe to use." | Write-Output
}
else { # check if the file is found in the archive
    $exists = (($ISOArchive.path) -match [regex]::Escape($NewFileHash.Path)) -and (($ISOArchive.algorithm) -match [regex]::Escape($NewFileHash.Algorithm))
    if ($exists) { # if the file exists and the hash wasn’t found then the file has changed
        "The hash for $($match.path) does not match the archive value. It is NOT safe." | Write-Warning
    }
    else { # if the file isn’t found then we don’t have a hash that we can compare with
        if (!((($ISOArchive.path) -match [regex]::Escape($NewFileHash.Path)))) {
            "It doesnt look like there is a hash value for $($Newfilehash.path) in the archive." | Write-Output
        }
        else { # we have to compare hashes generated with the same algorithm
            "Please re-test $($newfilehash.path) using the correct hash algorithm" | Write-Output
        }
    }
}

I hope you can see how useful this is to ensure confidence in important files that you place on your network.