Scope
- Access to private Azure Blob Storage requires authentication.
- Shared Key authentication represents one of the authentication options.
- Scripts are provided for PowerShell releases 5.1, 6.x and 7.x
Example
Download: azure_authentication_shared_key_sample.ps1
# inspired by:
# https://stackoverflow.com/questions/53653473/generating-an-sas-token-in-java-to-download-a-file-in-an-azure-data-storage-cont?rq=1
# https://purple.telstra.com.au/blog/loading-and-querying-data-in-azure-table-storage-using-powershell
# https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
function Create-Signature( [string] $Operation, [string] $Container, [string] $Blob, [string] $Account, [string] $AccessKey, [string] $Version, [DateTime] $Now, [int] $ContentLength=-1 )
{
[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
if ( $ContentLength -gt -1 )
{
$contentLengthValue = $ContentLength
} else {
$contentLengthValue = ''
}
$partToSign = "`n" `
+ "`n" `
+ "`n" `
+ $contentLengthValue + "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "x-ms-blob-type:BlockBlob" + "`n" `
+ "x-ms-date:$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')" + "`n" `
+ "x-ms-version:$Version" + "`n"
switch ( $Operation )
{
'GET' {
$stringToSign = 'GET' `
+ $partToSign `
+ "/$Account/$Container/$Blob"
break
}
'PUT' {
$stringToSign = 'PUT' `
+ $partToSign `
+ "/$Account/$Container/$Blob"
break
}
'LIST' {
$stringToSign = "GET" `
+ $partToSign `
+ "/$Account/$Container" + "`n" `
+ "comp:list" + "`n" `
+ "restype:container"
break
}
default {
throw "invalid operation: $Operation"
}
}
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.key = [Convert]::FromBase64String( $AccessKey )
$signature = $hmac.ComputeHash( [Text.Encoding]::UTF8.GetBytes( $stringToSign ) )
$signature = [Convert]::ToBase64String( $signature )
Write-Debug "container: $Container"
Write-Debug "Accoount: $Account"
Write-Debug "AccessKey: $AccessKey"
Write-Debug "stringToSign: $stringToSign"
$signature
}
function Invoke-BlobRequest( [Uri] $Uri, [string] $Account, [string] $Signature, [DateTime] $Now, [string] $Version, [string] $Method='GET', [string] $FilePath='' )
{
$requestParams = @{}
$requestParams.Add( 'Uri', $Uri )
$requestParams.Add( 'Method', $Method )
if ( $FilePath )
{
$requestParams.Add( 'Infile', $FilePath )
}
$requestParams.Add( 'Headers', @{ `
'x-ms-blob-type' = 'BlockBlob'; `
'x-ms-date' = "$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')"; `
'x-ms-version' = $Version; `
'Authorization' = "SharedKey $($Account):$($Signature)" `
} `
)
Write-Debug ".... Invoke-WebRequest: $Uri"
$requestParams.Keys | % {
if ( $_ -eq 'Headers' )
{
$item = $_
$requestParams.Item($_).Keys | % {
Write-Debug "...... Headers $_ : $($requestParams.Item($item).Item($_))"
}
} else {
Write-Debug "...... $_ $($requestParams.Item($_))"
}
}
# run the web service request
$response = Invoke-WebRequest @requestParams
$response
}
# ---------- ---------- ----------
# Main
# ---------- ---------- ----------
$ownerAccount = '<storage account>'
$ownerAccessKey = '<storage account access key>'
$requesterAccount = '<requester account>'
$version = '2019-10-10'
# consider badly synchronized server time and start 3 minutes in the past
$now = (Get-Date).ToUniversalTime().AddMinutes(-3)
Write-Host "`n"
Write-Host "# ++++++++++++++++++++++++++++++"
Write-Host "# List Blobs in Container"
Write-Host "# ++++++++++++++++++++++++++++++"
# step 1: create signature
$container = 'yade'
$signature = Create-Signature -Operation 'LIST' -Container $container -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now
Write-Debug "Signature: $signature"
# step 2: send web service request
$uri = "https://$($ownerAccount).blob.core.windows.net/$($container)?restype=container&comp=list"
$response = Invoke-BlobRequest -Uri $uri -Account $requesterAccount -Signature $signature -Now $now -Version $version -Method 'GET'
Write-Host "Status Code: $($response.StatusCode)"
Write-Host $response.Content
Write-Host "`n"
Write-Host "# ++++++++++++++++++++++++++++++"
Write-Host "# Get Blob from Container"
Write-Host "# ++++++++++++++++++++++++++++++"
# step 1: create signature
$container = 'yade'
$blob = 'test.txt'
$signature = Create-Signature -Operation 'GET' -Container $container -Blob $blob -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now
Write-Debug "Signature: $signature"
# step 2: send web service request
$uri = "https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)"
$response = Invoke-BlobRequest -Uri $uri -Account $requesterAccount -Signature $signature -Now $now -Version $version -Method 'GET'
Write-Host "Status Code: $($response.StatusCode)"
Write-Host $response.Content
Write-Host "`n"
Write-Host "# ++++++++++++++++++++++++++++++"
Write-Host "# Write Blob to Container"
Write-Host "# ++++++++++++++++++++++++++++++"
# step 1: create signature
$container = 'yade'
$filePath = "c:/tmp/some_blob.txt"
$blob = (Get-Item $filePath).Name
$signature = Create-Signature -Operation 'PUT' -Container $container -Blob $blob -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now -ContentLength (Get-Content $filePath -Raw).length
Write-Debug "Signature: $signature"
# step 2: send web service request
$uri = "https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)"
$response = Invoke-BlobRequest -Uri $uri -Account $requesterAccount -Signature $signature -Now $now -Version $version -Method 'PUT' -FilePath $filePath
Write-Host "Status Code: $($response.StatusCode)"
Write-Host $response.Content
Explanations
The Shared Key authentication enables unlimited access, i.e it does not expire. Refer to the Azure article Authorize with Shared Key for reference. The above PowerShell script creates the Shared Key for three operation LIST, GET, PUT. The three operation support three different processes:
- LIST BLOB
- The LIST BLOB operation retrieves the list of blobs in the container.
- GET BLOB
- The GET BLOB operation is used to read the content of a blob.
- PUT BLOB
- The PUT BLOB operation is used to create a blob and to add the content of a file or to write to an existing blob.
The Shared Key uses different parameters for different operation. The Shared Key requires a signature string that has to be encoded and that is passed with the Authorization Header in the HTTPS request.
The format of the signature string is:
StringToSign = VERB + "\n" +
Content-Encoding + "\n" +
Content-Language + "\n" +
Content-Length + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Date + "\n" +
If-Modified-Since + "\n" +
If-Match + "\n" +
If-None-Match + "\n" +
If-Unmodified-Since + "\n" +
Range + "\n" +
CanonicalizedHeaders +
CanonicalizedResource;
The StringToSIgn is then encoded using the HMAC-SHA256 algorithm for the UTF-8-encoded signature string which is then used with the Authorization Header using the following syntax:
Authorization="SharedKey <AccountName>:<Signature>"
where SharedKey is the authorization scheme, Account name is the name of the storage account by which the HTTPS request is generated, Signature is the encoded StringToSign
Signature String
The StringToSign parameters differ according to the operations:
LIST BLOB:
The LIST BLOB operation lists the blobs from the container. The StringToSign for LIST BLOB will be:
$stringToSign = "GET" `
+ "`n" `
+ "`n" `
+ "`n" `
+ (($ContentLength -gt -1) ? $ContentLength : '') + "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "x-ms-blob-type:BlockBlob" + "`n" `
+ "x-ms-date:$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')" + "`n" `
+ "x-ms-version:$Version" + "`n" `
+ "/$Account/$Container" + "`n" `
+ "comp:list" + "`n" `
+ "restype:container"
where $Account is the storage account by which the HTTPS request is generated. $Container is the name of the container.
GET BLOB:
The GTE BLOB operation retrieves the content of the blob. So, for the get operation it is required to pass the blob name whose content should be read. The StringToSign for GET BLOB will be:
$stringToSign = 'GET' `
+ "`n" `
+ "`n" `
+ "`n" `
+ (($ContentLength -gt -1) ? $ContentLength : '') + "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "x-ms-blob-type:BlockBlob" + "`n" `
+ "x-ms-date:$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')" + "`n" `
+ "x-ms-version:$Version" + "`n" `
+ "/$Account/$Container/$Blob"
where $Account is the storage account by which the HTTPS request is generated. $Container is the name of the container. $Blob is the name of the blob whose content is to be read.
PUT BLOB
The PUT BLOB operation creates a new block blob or updates an existing block blob. The PUT BLOB operation creates a BLOB from the content of a file, therefore it is required to pass the length of the blob so that it can allocate that much space in the Container and can write the file content to the blob. The StringToSign for the PUT BLOB operation will be:
$stringToSign = 'PUT' `
+ "`n" `
+ "`n" `
+ "`n" `
+ (($ContentLength -gt -1) ? $ContentLength : '') + "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "`n" `
+ "x-ms-blob-type:BlockBlob" + "`n" `
+ "x-ms-date:$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')" + "`n" `
+ "x-ms-version:$Version" + "`n" `
+ "/$Account/$Container/$Blob"
where $Account is the storage account by which the HTTPS request is generated. $Container is the name of the container. $Blob is the name of the blob that should be created. The $ContentLength for the LIST BLOB and GET BLOB operation will be empty but for the PUT BLOB operation it required to pass the Content Length for the blob to be created.
Encoding Signature String
After creating the Signature String it is required to encode the string to create a Shared Key which can then be passed to the Authorization header. The algorithm used for encoding is HMAC-SHA256 over the UTF-8-encoded signature string. The PowerShell instructions to encode the StringToSign are:
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.key = [Convert]::FromBase64String( $AccessKey )
$signature = $hmac.ComputeHash( [Text.Encoding]::UTF8.GetBytes( $stringToSign ) )
$signature = [Convert]::ToBase64String( $signature )
where $AccessKey is the access key of the storage account that owns the Azure blob container. $stringToSign is the signature string which depends on the type of operation.
HTTPS Request to Azure
The HTTPS request to Azure is used to invoke the call. The URI depends on the type of operation we want to use in the HTTPS request. The Headers used in the HTTPS request are:
'x-ms-blob-type' = 'BlockBlob'; `
'x-ms-date' = "$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')"; `
'x-ms-version' = $Version; `
'Authorization' = "SharedKey $($Account):$($Signature)" `
where Header
The details about the type of HTTPS request in the operations are:
LIST BLOB
The HTTPS request URI for the LIST BLOB operation just includes the container name. As the LIST operations lists all the blobs in the container. So the HTTPS request URI will be https://$($ownerAccount).blob.core.windows.net/$($container)?restype=container&comp=list where $ownerAccount is the name of the storage account and $container is the name of the container.
The HTTPS request and header syntax for LIST BLOB will be:
Request Syntax
GET https://$($ownerAccount).blob.core.windows.net/$($container)?restype=container&comp=list
Request Headers
x-ms-blob-type: BlockBlob
x-ms-date: <date>
x-ms-version: $Version
Authorization="SharedKey <AccountName>:<Signature>"
GET BLOB
The GET BLOB operation is used to read the blob content so it is required to pass the blob name with the URI. So the request URI for the GET BLOB will be https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob) where $ownerAccount is the name of the storage account and $container is the name of the container and $blob is the name of the blob which is to be read. The HTTPS request and Header syntax for GET BLOB will be:
Request Syntax
GET https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)
Request Headers
x-ms-blob-type: BlockBlob
x-ms-date: <date>
x-ms-version: $Version
Authorization="SharedKey <AccountName>:<Signature>"
- PUT BLOB
The PUT BLOB operation is used to create a blob. So the URI for the PUT BLOB operation includes the name of the blob to be created and the content of the blob is to be passed with the HTTPS request body. The URI will be https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob) where $ownerAccount is the name of the storage account and $container is the name of the container and $blob is the name of the blob which is to be created in the container.
Request Syntax
PUT https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)
Request Headers
x-ms-blob-type: BlockBlob
x-ms-date: <date>
x-ms-version: $Version
Authorization="SharedKey <AccountName>:<Signature>"
Request Body:
<Content of the File>