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
相关产品推荐
相关产品推荐

