You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity/C#中如何用AWS4-HMAC-SHA256签名PUT请求传图至S3

在Unity中使用AWS4-HMAC-SHA256签名的PUT请求上传图片到S3

要实现这个功能,你需要手动构造符合AWS4-HMAC-SHA256规范的请求签名,再通过Unity的网络API发送PUT请求。以下是具体步骤和代码实现:

1. 准备必要的AWS信息

确保你拥有以下信息(绝对不要在代码中硬编码密钥,建议用加密存储或环境变量加载):

  • AWS Access Key ID
  • AWS Secret Access Key
  • S3存储桶名称
  • 存储桶所在区域(例如us-east-1、ap-southeast-1)
  • 图片在S3中的目标路径(例如images/user_avatar.png)

2. 核心签名逻辑实现

AWS4签名需要多步哈希计算,核心是生成签名密钥,再构造签名字符串并计算最终签名。下面是关键方法的C#实现:

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

public class S3Uploader : MonoBehaviour
{
    private const string AwsAlgorithm = "AWS4-HMAC-SHA256";
    private const string AwsService = "s3";
    private const string AwsRequestType = "aws4_request";

    // 替换为你的AWS信息
    private string _accessKey = "YOUR_ACCESS_KEY";
    private string _secretKey = "YOUR_SECRET_KEY";
    private string _bucketName = "YOUR_BUCKET_NAME";
    private string _region = "YOUR_REGION";

    // 计算HMAC-SHA256哈希
    private byte[] HmacSha256(byte[] key, string data)
    {
        using (var hmac = new HMACSHA256(key))
        {
            return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
        }
    }

    // 生成AWS4签名密钥
    private byte[] GetSignatureKey(string secretKey, string dateStamp, string regionName, string serviceName)
    {
        byte[] kSecret = Encoding.UTF8.GetBytes($"AWS4{secretKey}");
        byte[] kDate = HmacSha256(kSecret, dateStamp);
        byte[] kRegion = HmacSha256(kDate, regionName);
        byte[] kService = HmacSha256(kRegion, serviceName);
        byte[] kSigning = HmacSha256(kService, AwsRequestType);
        return kSigning;
    }

    // 计算字符串的SHA256哈希(用于请求体哈希)
    private string ComputeSha256Hash(string input)
    {
        using (SHA256 sha256 = SHA256.Create())
        {
            byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
            StringBuilder builder = new StringBuilder();
            foreach (byte b in bytes)
            {
                builder.Append(b.ToString("x2"));
            }
            return builder.ToString();
        }
    }

    // 计算字节数组的SHA256哈希
    private string ComputeSha256Hash(byte[] inputBytes)
    {
        using (SHA256 sha256 = SHA256.Create())
        {
            byte[] bytes = sha256.ComputeHash(inputBytes);
            StringBuilder builder = new StringBuilder();
            foreach (byte b in bytes)
            {
                builder.Append(b.ToString("x2"));
            }
            return builder.ToString();
        }
    }

3. 构造PUT请求并上传图片

接下来实现上传方法,将Texture2D转换为字节数组,构造完整的请求头和签名,最后用UnityWebRequest发送请求:

// 上传图片到S3
    public IEnumerator UploadImageToS3(Texture2D imageTexture, string s3FilePath)
    {
        // 将Texture转为PNG字节数组(也可以用JPG,调整quality参数)
        byte[] imageBytes = imageTexture.EncodeToPNG();
        string imageHash = ComputeSha256Hash(imageBytes);

        // 获取当前UTC时间,格式要严格符合AWS要求
        DateTime utcNow = DateTime.UtcNow;
        string amzDate = utcNow.ToString("yyyyMMddTHHmmssZ");
        string dateStamp = utcNow.ToString("yyyyMMdd");

        // 构造S3请求URL
        string s3Url = $"https://{_bucketName}.s3.{_region}.amazonaws.com/{s3FilePath}";

        // 构造规范请求的各个部分
        string method = "PUT";
        string canonicalUri = $"/{s3FilePath}";
        string canonicalQueryString = ""; // PUT请求无查询参数时留空

        // 构造规范头部(注意头部名称要小写,排序后拼接)
        Dictionary<string, string> headers = new Dictionary<string, string>
        {
            { "host", $"{_bucketName}.s3.{_region}.amazonaws.com" },
            { "x-amz-content-sha256", imageHash },
            { "x-amz-date", amzDate },
            { "x-amz-acl", "public-read" } // 可选,设置文件访问权限,也可以用private
        };

        // 排序头部并拼接成规范头部字符串
        List<string> sortedHeaderKeys = new List<string>(headers.Keys);
        sortedHeaderKeys.Sort(StringComparer.Ordinal);
        StringBuilder canonicalHeadersSb = new StringBuilder();
        StringBuilder signedHeadersSb = new StringBuilder();
        foreach (string key in sortedHeaderKeys)
        {
            canonicalHeadersSb.Append($"{key}:{headers[key].Trim()}\n");
            signedHeadersSb.Append($"{key};");
        }
        string canonicalHeaders = canonicalHeadersSb.ToString();
        string signedHeaders = signedHeadersSb.ToString().TrimEnd(';');

        // 构造规范请求
        string canonicalRequest = $"{method}\n{canonicalUri}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{imageHash}";

        // 构造签名字符串
        string credentialScope = $"{dateStamp}/{_region}/{AwsService}/{AwsRequestType}";
        string stringToSign = $"{AwsAlgorithm}\n{amzDate}\n{credentialScope}\n{ComputeSha256Hash(canonicalRequest)}";

        // 生成签名密钥并计算最终签名
        byte[] signingKey = GetSignatureKey(_secretKey, dateStamp, _region, AwsService);
        string signature = BitConverter.ToString(HmacSha256(signingKey, stringToSign)).Replace("-", "").ToLower();

        // 构造Authorization头部
        string authorizationHeader = $"{AwsAlgorithm} Credential={_accessKey}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}";

        // 创建UnityWebRequest并添加所有头部
        using (UnityWebRequest request = UnityWebRequest.Put(s3Url, imageBytes))
        {
            request.SetRequestHeader("Host", headers["host"]);
            request.SetRequestHeader("X-Amz-Content-Sha256", headers["x-amz-content-sha256"]);
            request.SetRequestHeader("X-Amz-Date", headers["x-amz-date"]);
            request.SetRequestHeader("X-Amz-Acl", headers["x-amz-acl"]);
            request.SetRequestHeader("Authorization", authorizationHeader);

            // 发送请求并等待结果
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.Success)
            {
                Debug.Log("图片上传成功!");
                Debug.Log("S3文件地址: " + s3Url);
            }
            else
            {
                Debug.LogError("上传失败: " + request.error);
                Debug.LogError("响应内容: " + request.downloadHandler.text);
            }
        }
    }
}

4. 关键注意事项

  • CORS配置:必须在S3存储桶的CORS规则中添加允许PUT请求的配置,示例规则:

    [
    {
    "AllowedHeaders": [""],
    "AllowedMethods": ["PUT"],
    "AllowedOrigins": ["
    "], // 测试用,生产环境建议限制具体域名
    "ExposeHeaders": []
    }
    ]

  • 时间同步:Unity客户端的系统时间必须和UTC时间同步,否则AWS会因为时间差拒绝请求。
  • 密钥安全:绝对不要把Access Key和Secret Key打包进发布包,建议通过后端接口临时获取签名凭证(STS),或者用加密存储方案。
  • 文件权限:根据业务需求调整x-amz-acl的值,比如private(默认)、public-read、authenticated-read等。

内容的提问来源于stack exchange,提问作者Csabi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 21:04:56