powershell to list contents azure storage blob containter using key1 or key2

Jan Vávra 316 Reputation points
2024-09-26T10:19:50.93+00:00

I have following pwsh7 script and have no other idea, why the Azure Blob REST API refuse to authenticate. I know there is an Entra ID Oauth2 or SAS scheme but I need to make a prove that an integrated third party software that supports only keys will be able to authenticate.

I checked the value of $StringToSign and returned xml error

<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:9300092d-b01e-0070-71fb-0f5492000000
Time:2024-09-26T10:01:29.7869247Z</Message><AuthenticationErrorDetail>The MAC signature found in the HTTP request '72Lz/EdHuzzi57gC4JAKW1oFWz8SnU1JW4lq5+LLajA=' is not the same as any computed signature. Server used following string to sign: 'GET











x-ms-date:Thu, 26 Sep 2024 09:56:58 GMT
x-ms-version:2020-10-02
/presofalogs/pre-sofa-logs
comp:list
restype:container'.</AuthenticationErrorDetail

The script I checked againts the docs [https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key] and didn't find any discrepancies.

# Variables
$StorageAccountName = "mysa"
$StorageAccountKey = "mykey"
$ContainerName = "mycontainer"
$StorageUrl = "https://${StorageAccountName}.blob.core.windows.net/${ContainerName}?restype=container&comp=list"
$DateUtc = (Get-Date).ToUniversalTime().ToString("R")
$Verb = "GET"
$Version = "2020-10-02"

#$OpenSSLPath = "c:\Program Files\OpenSSL-Win64\bin"
$OpenSSLPath = "c:\Program Files\OpenSSL-Win64-1.1\bin" 
$env:PATH = "$OpenSSLPath;$env:PATH"

# Build the String-to-Sign with LF (`\n`) line endings explicitly
# https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
$StringToSign = $Verb + "`n" +
"`n" +  # Content-Encoding (empty)
"`n" +  # Content-Language (empty)
"`n" +  # Content-Length (empty)
"`n" +  # Content-MD5 (empty)
"`n" +  # Content-Type (empty)
"`n" +  # Date (empty, because we're using x-ms-date)
"`n" +  # If-Modified-Since (empty)
"`n" +  # If-Match (empty)
"`n" +  # If-None-Match (empty)
"`n" +  # If-Unmodified-Since (empty)
"`n" +  # Range (empty)
"x-ms-date:$DateUtc`n" +  # Custom header
"x-ms-version:$Version`n" +  # Custom header
"/$StorageAccountName/$ContainerName`n" +  # Canonicalized resource
"comp:list`n" +  # Query parameter in alphabetical order
"restype:container"  # Query parameter in alphabetical order

$StringToSign | Out-File -FilePath sts.txt -NoNewline

# Create a temporary file for the binary storage account key
$TempKeyFile = [System.IO.Path]::GetTempFileName()

# Decode the base64-encoded storage account key and save it as binary to the temporary file
[IO.File]::WriteAllBytes($TempKeyFile, [Convert]::FromBase64String($StorageAccountKey))

# Create the HMAC signature using OpenSSL with the binary key stored in the temporary file
# Use a here-string to pass $StringToSign as input via stdin to openssl
$StringToSignBytes = [System.Text.Encoding]::UTF8.GetBytes($StringToSign)
$Signature = $StringToSignBytes | & openssl dgst -sha256 -binary -mac HMAC -macopt "key:file:$TempKeyFile" | base64

# Remove the temporary file securely
Remove-Item $TempKeyFile

# Output the signature for debugging
Write-Host "Signature: $Signature"

# Prepare the curl request
$Headers = [Ordered]@{
    "x-ms-date"    = $DateUtc
    "x-ms-version" = $Version
    "Authorization" = "SharedKey ${StorageAccountName}:${Signature}"
}

# Execute the request to list the blobs in the container
# Used -Proxy 'http://localhost:8888' for inspecting via a Fiddler
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
Invoke-RestMethod -Method $Verb -Uri $StorageUrl -Headers $Headers -Proxy 'http://localhost:8888'

Fiddler showed other request headers that shouldn't break the signature.

GET https://mysa.blob.core.windows.net/mycontainer?restype=container&comp=list HTTP/1.1
Host: mysa.blob.core.windows.net
x-ms-date: Thu, 26 Sep 2024 09:56:58 GMT
x-ms-version: 2020-10-02
Authorization: SharedKey mysa:72Lz/EdHuzzi57gC4JAKW1oFWz8SnU1JW4lq5+LLajA=
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.19045; cs-CZ) PowerShell/7.4.5
Accept-Encoding: gzip, deflate, br

Azure Blob Storage
Azure Blob Storage
An Azure service that stores unstructured data in the cloud as blobs.
2,851 questions
0 comments No comments
{count} votes

Accepted answer
  1. Vinod Kumar Reddy Chilupuri 315 Reputation points Microsoft Vendor
    2024-09-27T09:46:03.9566667+00:00

    Hi Jan Vávra,
    Welcome to Microsoft Q&A, thanks for posting your query.

     

    Based on the error provided the issue you are facing with Azure Blob Rest API authentication is due to the incorrect signature being generated. The error message shows that the MAC signature is found in the HTTP request is not the same as any computed signature.

     

    The signature string depends on which service and version you are authorizing against and which authorization scheme you are using. Make sure that the StringToSign is correctly formatted. The string must include all the required parameters and the headers in the correct order.
    Ensure that the storage account key is correctly decoded and is use to generate the HMAC signature. The key should be based64-encoded and decoded before the use. Make sure that the signature in the PowerShell script is being generated correctly using the HMAC-SHA256 algorithm.

     

    Double check the authorization header that is correct or not and  Authorization header in your PowerShell script is formed correct using the signature and the storage account name including the Sharedkey scheme.
    You can go through the similar kind of query:
    https://stackoverflow.com/questions/77822427/azure-blob-storage-rest-api-403-server-failed-to-authenticate-the-request

    https://learn.microsoft.com/en-us/answers/questions/1486776/subject-azure-blob-storage-issue-with-mac-signatur

    Please let us know if you have any further queries. I’m happy to assist you further. 


    Please do not forget to "Accept the answer” and “up-vote” wherever the information provided helps you, this can be beneficial to other community members.

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Jan Vávra 316 Reputation points
    2024-09-27T11:04:07.0933333+00:00

    The problem was in the mechanism of computing the hash, evidently the openssl should be called some other way. The $signatureText was constructed well, if not, the error message returned contains the expected form of signature string. There are intentionally used `n because of I am running on Windows and the Blob auth scheme requires LF for \n

    This works:

    # Variables
    $StorageAccountName = "mysa"
    $StorageAccountKey = "mykey"
    $ContainerName = "mycontainer"
    
    $StorageUrl = "https://${StorageAccountName}.blob.core.windows.net/${ContainerName}?restype=container&comp=list&maxresults=3"
    $DateUtc = (Get-Date).ToUniversalTime().ToString("R")
    $Verb = "GET"
    $Version = "2020-10-02"
    
    # Build the String-to-Sign with LF (`\n`) line endings explicitly
    # https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
    $StringToSign = $Verb + "`n" +
    "`n" +  # Content-Encoding (empty)
    "`n" +  # Content-Language (empty)
    "`n" +  # Content-Length (empty)
    "`n" +  # Content-MD5 (empty)
    "`n" +  # Content-Type (empty)
    "`n" +  # Date (empty, because we're using x-ms-date)
    "`n" +  # If-Modified-Since (empty)
    "`n" +  # If-Match (empty)
    "`n" +  # If-None-Match (empty)
    "`n" +  # If-Unmodified-Since (empty)
    "`n" +  # Range (empty)
    "x-ms-date:$DateUtc`n" +  # Custom header
    "x-ms-version:$Version`n" +  # Custom header
    "/$StorageAccountName/$ContainerName`n" +  # Canonicalized resource
    "comp:list`n" +  # Query parameter in alphabetical order
    "maxresults:3`n" +
    "restype:container"  # Query parameter in alphabetical order
    
    # Create the HMAC signature using OpenSSL with the binary key stored in the temporary file
    # Use a here-string to pass $StringToSign as input via stdin to openssl
    $StringToSignBytes = [System.Text.Encoding]::UTF8.GetBytes($StringToSign)
    # Decode the Base64-encoded shared key
    $keyBytes = [Convert]::FromBase64String($StorageAccountKey)
    # Compute the HMAC-SHA256 hash
    $hmacsha256 = New-Object System.Security.Cryptography.HMACSHA256
    $hmacsha256.Key = $keyBytes
    $signatureBytes = $hmacsha256.ComputeHash($StringToSignBytes)
    # Convert the hash to a Base64 string
    $Signature = [Convert]::ToBase64String($signatureBytes)
    
    # Prepare the web request
    $Headers = [Ordered]@{
        "x-ms-date"    = $DateUtc
        "x-ms-version" = $Version
        "Authorization" = "SharedKey ${StorageAccountName}:${Signature}"
    }
    
    #Debug output the Invoke-RestMethod command
    $HeadersString = $Headers.GetEnumerator() 
     | ForEach-Object { "`"$($_.Key)`" = `"$($_.Value)`";" } | Out-String
    Write-Host "Invoke-RestMethod -Method $Verb -Uri `"$StorageUrl`" -Headers ([Ordered]@{$HeadersString})"
    
    
    # Execute the web request to list the blobs in the container
    $useFiddlerToDebug = $false
    if ($useFiddlerToDebug) {
        [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }    
        Invoke-RestMethod -Method $Verb -Uri $StorageUrl -Headers $Headers -Proxy 'http://localhost:8888'
    }
    else {
        Invoke-RestMethod -Method $Verb -Uri $StorageUrl -Headers $Headers
    }
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.