最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Unable to generate a valid Azure Storage SAS token in Cpp - Stack Overflow

programmeradmin2浏览0评论

I have tried several methods, but they all give the same error when creating a SAS token programatically.

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:32b24722-801e-0024-1e9c-3b3c1d000000 Time:2024-11-20T22:36:21.0299082Z</Message><AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail></Error>

Of course, this all works when using a SAS token generated in the portal. I have a (working) storage account and container and the containers key (I assume this is base64 encoded and needs to be decoded prior to signing).

bool downloadFile(const std::string& blobName, const std::string& localFilePath) {
        if (!m_curl) {
            std::cerr << "Failed to initialize cURL" << std::endl;
            return false;
        }

        // Generate SAS token just before the file download
        //IotHubHelpers iotHubHelper;
        std::string resourceUri = m_storage_account + ".blob.core.windows/" + m_container_name + "/" + blobName;
        std::string account_key = "Only Half the eyJBuYU2S2G99MDFfb5K6aGRrZJRdAlonKchD+AStQfq0Ig==";  // Your container access key
        std::string start_time = "2024-11-19T19:21:22Z";
        std::string expiry_time = "2028-11-20T03:21:22Z";
        std::string permissions = "rwdlacupiyx";


         std::string sasToken = generate_blob_sas_token(
            //m_storage_account,
            //m_container_name,
            //blobName,
            account_key //,
            //permissions,
            //start_time,
            //expiry_time
        );

        if (sasToken.empty()) {
            std::cerr << "Error: Failed to generate SAS token." << std::endl;
            return false;
        }

        std::cout << "SAS Token: " << sasToken << std::endl;

        // Construct URL with SAS token
        std::string url = "https://" + resourceUri + "?" + sasToken;
        std::cout << "Downloading from URL: " << url << std::endl;

        // Open the file stream in binary mode
        std::ofstream outFile(localFilePath, std::ios::binary);
        if (!outFile) {
            std::cerr << "Failed to open file for writing: " << localFilePath << std::endl;
            return false;
        }

        // Set cURL options
        curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeData);
        curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &outFile);
        curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L);
        curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 2L);

        CURLcode res = curl_easy_perform(m_curl);
        if (res != CURLE_OK) {
            std::cerr << "cURL error: " << curl_easy_strerror(res) << std::endl;
            outFile.close();
            return false;
        }

        outFile.close();
        return true;
    }

This is the Token generator, where I cant replicate a token from Azure Portal.

std::string generate_blob_sas_token(const std::string& key) {
        const std::string canonicalizedResource = "/blob/fotacontainer/fota/testfile.txt";

        std::vector<std::pair<std::string, std::string>> sas_token_properties = {
            {"sp", "r"},
            {"st", "2024-11-19T19:21:22Z"}, //get_utc_time(-120)},
            {"se", "2025-11-19T19:21:22Z"}, //get_utc_time(1440)},
            {"canonicalizedResource", canonicalizedResource},
            {"si", ""},
            {"sip", ""},
            {"spr", "https"},
            {"sv", "2023-01-03"},
            {"sr", "b"},
            {"sst", ""},
            {"ses", ""},
            {"rscc", ""},
            {"rscd", ""},
            {"rsce", ""},
            {"rscl", ""},
            {"rsct", ""}
        };

        std::vector<std::string> values;
        for (const auto& entry : sas_token_properties) {
            values.push_back(entry.second);
        }

        std::string string_to_sign = join_with_newline(values);
        std::cout << string_to_sign << std::endl;

        // The keys we get from the storage account are base64 encoded, so we need to decode them first
        std::string decoded_key = base64_decode(key);
        unsigned char* digest = HMAC(EVP_sha256(), decoded_key.data(), decoded_key.size(),
                                     reinterpret_cast<const unsigned char*>(string_to_sign.data()), string_to_sign.size(), nullptr, nullptr);

        std::string signature = base64_encode(digest, SHA256_DIGEST_LENGTH);

        std::vector<std::string> parameters;
        for (const auto& entry : sas_token_properties) {
            if (!entry.second.empty() && entry.first != "canonicalizedResource") {
                parameters.push_back(entry.first + "=" + url_encode(entry.second));
            }
        }
        parameters.push_back("sig=" + url_encode(signature));

        return join(parameters, "&");
}
    

Using the code above (needs cleanup, yes), I get the error message

<?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:32b24722-801e-0024-1e9c-3b3c1d000000 Time:2024-11-20T22:36:21.0299082Z</Message><AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail></Error> I must be missing something basic in the algorithm to assemble and generate the SAS Token. I've read the docs, and looking online. still no solution I can find.

I have tried several methods, but they all give the same error when creating a SAS token programatically.

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:32b24722-801e-0024-1e9c-3b3c1d000000 Time:2024-11-20T22:36:21.0299082Z</Message><AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail></Error>

Of course, this all works when using a SAS token generated in the portal. I have a (working) storage account and container and the containers key (I assume this is base64 encoded and needs to be decoded prior to signing).

bool downloadFile(const std::string& blobName, const std::string& localFilePath) {
        if (!m_curl) {
            std::cerr << "Failed to initialize cURL" << std::endl;
            return false;
        }

        // Generate SAS token just before the file download
        //IotHubHelpers iotHubHelper;
        std::string resourceUri = m_storage_account + ".blob.core.windows/" + m_container_name + "/" + blobName;
        std::string account_key = "Only Half the eyJBuYU2S2G99MDFfb5K6aGRrZJRdAlonKchD+AStQfq0Ig==";  // Your container access key
        std::string start_time = "2024-11-19T19:21:22Z";
        std::string expiry_time = "2028-11-20T03:21:22Z";
        std::string permissions = "rwdlacupiyx";


         std::string sasToken = generate_blob_sas_token(
            //m_storage_account,
            //m_container_name,
            //blobName,
            account_key //,
            //permissions,
            //start_time,
            //expiry_time
        );

        if (sasToken.empty()) {
            std::cerr << "Error: Failed to generate SAS token." << std::endl;
            return false;
        }

        std::cout << "SAS Token: " << sasToken << std::endl;

        // Construct URL with SAS token
        std::string url = "https://" + resourceUri + "?" + sasToken;
        std::cout << "Downloading from URL: " << url << std::endl;

        // Open the file stream in binary mode
        std::ofstream outFile(localFilePath, std::ios::binary);
        if (!outFile) {
            std::cerr << "Failed to open file for writing: " << localFilePath << std::endl;
            return false;
        }

        // Set cURL options
        curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeData);
        curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &outFile);
        curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L);
        curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 2L);

        CURLcode res = curl_easy_perform(m_curl);
        if (res != CURLE_OK) {
            std::cerr << "cURL error: " << curl_easy_strerror(res) << std::endl;
            outFile.close();
            return false;
        }

        outFile.close();
        return true;
    }

This is the Token generator, where I cant replicate a token from Azure Portal.

std::string generate_blob_sas_token(const std::string& key) {
        const std::string canonicalizedResource = "/blob/fotacontainer/fota/testfile.txt";

        std::vector<std::pair<std::string, std::string>> sas_token_properties = {
            {"sp", "r"},
            {"st", "2024-11-19T19:21:22Z"}, //get_utc_time(-120)},
            {"se", "2025-11-19T19:21:22Z"}, //get_utc_time(1440)},
            {"canonicalizedResource", canonicalizedResource},
            {"si", ""},
            {"sip", ""},
            {"spr", "https"},
            {"sv", "2023-01-03"},
            {"sr", "b"},
            {"sst", ""},
            {"ses", ""},
            {"rscc", ""},
            {"rscd", ""},
            {"rsce", ""},
            {"rscl", ""},
            {"rsct", ""}
        };

        std::vector<std::string> values;
        for (const auto& entry : sas_token_properties) {
            values.push_back(entry.second);
        }

        std::string string_to_sign = join_with_newline(values);
        std::cout << string_to_sign << std::endl;

        // The keys we get from the storage account are base64 encoded, so we need to decode them first
        std::string decoded_key = base64_decode(key);
        unsigned char* digest = HMAC(EVP_sha256(), decoded_key.data(), decoded_key.size(),
                                     reinterpret_cast<const unsigned char*>(string_to_sign.data()), string_to_sign.size(), nullptr, nullptr);

        std::string signature = base64_encode(digest, SHA256_DIGEST_LENGTH);

        std::vector<std::string> parameters;
        for (const auto& entry : sas_token_properties) {
            if (!entry.second.empty() && entry.first != "canonicalizedResource") {
                parameters.push_back(entry.first + "=" + url_encode(entry.second));
            }
        }
        parameters.push_back("sig=" + url_encode(signature));

        return join(parameters, "&");
}
    

Using the code above (needs cleanup, yes), I get the error message

<?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:32b24722-801e-0024-1e9c-3b3c1d000000 Time:2024-11-20T22:36:21.0299082Z</Message><AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail></Error> I must be missing something basic in the algorithm to assemble and generate the SAS Token. I've read the docs, and looking online. still no solution I can find.

Share Improve this question asked Nov 20, 2024 at 22:57 Stefan GudmundssonStefan Gudmundsson 32 bronze badges 2
  • You need to generate sas token for particular blob right? – Venkatesan Commented Nov 21, 2024 at 3:31
  • Check the below answer. – Venkatesan Commented Nov 21, 2024 at 9:30
Add a comment  | 

1 Answer 1

Reset to default 0

Unable to generate a valid Azure Storage SAS token in Cpp

You can use the below code to generate Azure blob sas token using cpp with crypto library.

Code:

#include <iostream>
#include <iomanip>
#include <sstream>
#include <ctime>
#include <string>
#include <cryptopp/cryptlib.h>
#include <cryptopp/hex.h>
#include <cryptopp/hmac.h>
#include <cryptopp/sha.h>
#include <cryptopp/base64.h>
#include <cryptopp/filters.h>
#include <cctype>
#include <iomanip>

using namespace CryptoPP;

std::string GenerateSas(const std::string& storageAccountKey, const std::string& input) {
    std::string decodedKey;
    StringSource(storageAccountKey, true, new Base64Decoder(new StringSink(decodedKey)));

    // Create HMAC-SHA256 signature
    std::string digest;
    HMAC<SHA256> hmac((const byte*)decodedKey.data(), decodedKey.size());
    StringSource(input, true,
        new HashFilter(hmac, new StringSink(digest))); // Compute HMAC digest

    // Encode the digest to Base64
    std::string encodedDigest;
    StringSource(digest, true, new Base64Encoder(new StringSink(encodedDigest), false));
    encodedDigest.erase(std::remove(encodedDigest.begin(), encodedDigest.end(), '\n'), encodedDigest.end());
    return encodedDigest; 
}

std::string TimePointToString(const std::chrono::seconds& timePoint) {
    std::time_t t = timePoint.count();
    std::tm tm{};
    gmtime_s(&tm, &t);
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
    return oss.str();
}

// URL encode function
std::string UrlEncode(const std::string& str) {
    std::ostringstream escaped;
    for (char c : str) {
        if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
        }
        else {
            escaped << '%' << std::setw(2) << std::setfill('0') << std::hex << (int)(unsigned char)c;
        }
    }
    return escaped.str();
}

// Function to generate SAS token
void GenerateSasToken(const std::string& blobName, const std::string& accessKey, const std::string& containerName) {
    // Define SAS token parameters
    auto currentTime = std::chrono::system_clock::now();
    auto currentTimeSeconds = std::chrono::duration_cast<std::chrono::seconds>(currentTime.time_since_epoch());
    auto expirationTime = currentTimeSeconds + std::chrono::minutes(120);

    // Convert time to ISO 8601 format
    std::string signedStart = TimePointToString(currentTimeSeconds);
    std::string signedExpiry = TimePointToString(expirationTime);

    // Define other SAS parameters
    std::string signedPermissions = "rwl";
    std::string signedService = "b"; // 'b' for blob
    std::string signedProtocol = "https";
    std::string signedVersion = "2022-11-02";
    std::string accountName = "venkat326123";

    // Create canonicalized resource
    std::string canonicalizedResource = "/blob/" + accountName + "/" + containerName + "/" + blobName;

    // Construct the string to sign
    std::ostringstream stringToSign;
    stringToSign << signedPermissions << "\n"
        << signedStart << "\n"
        << signedExpiry << "\n"
        << canonicalizedResource << "\n"
        << "\n\n"
        << signedProtocol << "\n"
        << signedVersion << "\n"
        << signedService << "\n"
        << "\n\n\n\n\n\n";

    // Generate the signature
    std::string signature = GenerateSas(accessKey, stringToSign.str());

    // URL encode the signature
    std::string urlEncodedSignature = UrlEncode(signature);

    // Construct the SAS token
    std::ostringstream sasToken;
    sasToken << "sv=" << signedVersion
        << "&sr=" << signedService
        << "&sp=" << signedPermissions
        << "&st=" << signedStart
        << "&se=" << signedExpiry
        << "&spr=" << signedProtocol
        << "&sig=" << urlEncodedSignature;
    std::cout << "Blob URL: https://" << accountName << ".blob.core.windows/"
        << containerName << "/" << blobName << "?" << sasToken.str() << std::endl;
}

int main() {
    // Define blob details
    std::string blobName = "scenery.jpg";
    std::string containerName = "test";
    std::string accessKey = "<storage account key>";

    // Generate SAS token
    GenerateSasToken(blobName, accessKey, containerName);

    return 0;
}

Output:

Blob URL: https://venkat326123.blob.core.windows/test/scenery.jpg?sv=2022-11-02&sr=b&sp=rwl&st=2024-11-21T09:20:49Z&se=2024-11-21T11:20:49Z&spr=https&sig=erc8cD0Jxxxxxxx1hxxxGR%2btK9muys%3d

I verified the blob URL in the browser.

Browser:

发布评论

评论列表(0)

  1. 暂无评论