使用 C# 和 .NET Framework 创建 URL 签名 - Amazon CloudFront
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

使用 C# 和 .NET Framework 创建 URL 签名

本节中的 C# 示例实现了一个示例应用程序,该程序演示了如何使用标准和自定义策略声明为 CloudFront 私有分配创建签名。此示例包括基于 Amazon SDK for .NET 的实用工具函数,这些函数在 .NET 应用程序中非常有用。

还可以通过使用 Amazon SDK for .NET 创建签名 URL 和签名 Cookie。在 Amazon SDK for .NET API 参考中,请参阅以下主题:

  • 签名 URL – Amazon.CloudFront > AmazonCloudFrontUrlSigner

  • 签名 Cookie – Amazon.CloudFront > AmazonCloudFrontCookieSigner

注意

创建 URL 签名只是使用签名 URL 提供私有内容过程的一部分。有关整个过程的更多信息,请参阅使用签名 URL

要下载代码,请转到 C# 签名代码

要在 .NET 框架中使用 RSA 密钥,您必须将 Amazon 提供的 .pem 文件转换为 .NET 框架使用的 XML 格式。

转换之后,RSA 私有密钥文件是以下格式:

例 XML .NET 框架格式的 RSA 私有密钥
<RSAKeyValue> <Modulus> wO5IvYCP5UcoCKDo1dcspoMehWBZcyfs9QEzGi6Oe5y+ewGr1oW+vB2GPB ANBiVPcUHTFWhwaIBd3oglmF0lGQljP/jOfmXHUK2kUUnLnJp+oOBL2NiuFtqcW6h/L5lIpD8Yq+NRHg Ty4zDsyr2880MvXv88yEFURCkqEXAMPLE= </Modulus> <Exponent>AQAB</Exponent> <P> 5bmKDaTz npENGVqz4Cea8XPH+sxt+2VaAwYnsarVUoSBeVt8WLloVuZGG9IZYmH5KteXEu7fZveYd9UEXAMPLE== </P> <Q> 1v9l/WN1a1N3rOK4VGoCokx7kR2SyTMSbZgF9IWJNOugR/WZw7HTnjipO3c9dy1Ms9pUKwUF4 6d7049EXAMPLE== </Q> <DP> RgrSKuLWXMyBH+/l1Dx/I4tXuAJIrlPyo+VmiOc7b5NzHptkSHEPfR9s1 OK0VqjknclqCJ3Ig86OMEtEXAMPLE== </DP> <DQ> pjPjvSFw+RoaTu0pgCA/jwW/FGyfN6iim1RFbkT4 z49DZb2IM885f3vf35eLTaEYRYUHQgZtChNEV0TEXAMPLE== </DQ> <InverseQ> nkvOJTg5QtGNgWb9i cVtzrL/1pFEOHbJXwEJdU99N+7sMK+1066DL/HSBUCD63qD4USpnf0myc24in0EXAMPLE==</InverseQ> <D> Bc7mp7XYHynuPZxChjWNJZIq+A73gm0ASDv6At7F8Vi9r0xUlQe/v0AQS3ycN8QlyR4XMbzMLYk 3yjxFDXo4ZKQtOGzLGteCU2srANiLv26/imXA8FVidZftTAtLviWQZBVPTeYIA69ATUYPEq0a5u5wjGy UOij9OWyuEXAMPLE= </D> </RSAKeyValue>

以下 C# 代码通过执行以下步骤创建使用标准策略的签名 URL:

  • 创建策略声明。

  • 使用 SHA1 对策略声明进行哈希处理,并使用 RSA 和其相应公有密钥位于可信密钥组中的私有密钥对结果进行签名。

  • 对经过哈希及签署的策略声明进行 Base64 编码,并替换使字符串能够安全地用作 URL 请求参数的特殊字符。

  • 联接值。

有关完整实现,请参阅 C# 签名代码中的示例。

例 C# 中固定策略签名方法
public static string ToUrlSafeBase64String(byte[] bytes) { return System.Convert.ToBase64String(bytes) .Replace('+', '-') .Replace('=', '_') .Replace('/', '~'); } public static string CreateCannedPrivateURL(string urlString, string durationUnits, string durationNumber, string pathToPolicyStmnt, string pathToPrivateKey, string privateKeyId) { // args[] 0-thisMethod, 1-resourceUrl, 2-seconds-minutes-hours-days // to expiration, 3-numberOfPreviousUnits, 4-pathToPolicyStmnt, // 5-pathToPrivateKey, 6-PrivateKeyId TimeSpan timeSpanInterval = GetDuration(durationUnits, durationNumber); // Create the policy statement. string strPolicy = CreatePolicyStatement(pathToPolicyStmnt, urlString, DateTime.Now, DateTime.Now.Add(timeSpanInterval), "0.0.0.0/0"); if ("Error!" == strPolicy) return "Invalid time frame." + "Start time cannot be greater than end time."; // Copy the expiration time defined by policy statement. string strExpiration = CopyExpirationTimeFromPolicy(strPolicy); // Read the policy into a byte buffer. byte[] bufferPolicy = Encoding.ASCII.GetBytes(strPolicy); // Initialize the SHA1CryptoServiceProvider object and hash the policy data. using (SHA1CryptoServiceProvider cryptoSHA1 = new SHA1CryptoServiceProvider()) { bufferPolicy = cryptoSHA1.ComputeHash(bufferPolicy); // Initialize the RSACryptoServiceProvider object. RSACryptoServiceProvider providerRSA = new RSACryptoServiceProvider(); XmlDocument xmlPrivateKey = new XmlDocument(); // Load your private key, which you created by converting your // .pem file to the XML format that the .NET framework uses. // Several tools are available. xmlPrivateKey.Load(pathToPrivateKey); // Format the RSACryptoServiceProvider providerRSA and // create the signature. providerRSA.FromXmlString(xmlPrivateKey.InnerXml); RSAPKCS1SignatureFormatter rsaFormatter = new RSAPKCS1SignatureFormatter(providerRSA); rsaFormatter.SetHashAlgorithm("SHA1"); byte[] signedPolicyHash = rsaFormatter.CreateSignature(bufferPolicy); // Convert the signed policy to URL-safe base64 encoding and // replace unsafe characters + = / with the safe characters - _ ~ string strSignedPolicy = ToUrlSafeBase64String(signedPolicyHash); // Concatenate the URL, the timestamp, the signature, // and the key pair ID to form the signed URL. return urlString + "?Expires=" + strExpiration + "&Signature=" + strSignedPolicy + "&Key-Pair-Id=" + privateKeyId; } }

以下 C# 代码通过执行以下步骤创建使用自定义策略的签名 URL:

  1. 创建策略声明。

  2. 对策略声明进行 Base64 编码,并替换使字符串能够安全地用作 URL 请求参数的特殊字符。

  3. 使用 SHA1 对策略声明进行哈希处理,并使用 RSA 和其相应公有密钥位于可信密钥组中的私有密钥对结果进行加密。

  4. 对经过哈希的策略声明进行 Base64 编码,并替换使字符串能够安全地用作 URL 请求参数的特殊字符。

  5. 联接值。

有关完整实现,请参阅 C# 签名代码中的示例。

例 C# 中自定义策略签名方法
public static string ToUrlSafeBase64String(byte[] bytes) { return System.Convert.ToBase64String(bytes) .Replace('+', '-') .Replace('=', '_') .Replace('/', '~'); } public static string CreateCustomPrivateURL(string urlString, string durationUnits, string durationNumber, string startIntervalFromNow, string ipaddress, string pathToPolicyStmnt, string pathToPrivateKey, string PrivateKeyId) { // args[] 0-thisMethod, 1-resourceUrl, 2-seconds-minutes-hours-days // to expiration, 3-numberOfPreviousUnits, 4-starttimeFromNow, // 5-ip_address, 6-pathToPolicyStmt, 7-pathToPrivateKey, 8-privateKeyId TimeSpan timeSpanInterval = GetDuration(durationUnits, durationNumber); TimeSpan timeSpanToStart = GetDurationByUnits(durationUnits, startIntervalFromNow); if (null == timeSpanToStart) return "Invalid duration units." + "Valid options: seconds, minutes, hours, or days"; string strPolicy = CreatePolicyStatement( pathToPolicyStmnt, urlString, DateTime.Now.Add(timeSpanToStart), DateTime.Now.Add(timeSpanInterval), ipaddress); // Read the policy into a byte buffer. byte[] bufferPolicy = Encoding.ASCII.GetBytes(strPolicy); // Convert the policy statement to URL-safe base64 encoding and // replace unsafe characters + = / with the safe characters - _ ~ string urlSafePolicy = ToUrlSafeBase64String(bufferPolicy); // Initialize the SHA1CryptoServiceProvider object and hash the policy data. byte[] bufferPolicyHash; using (SHA1CryptoServiceProvider cryptoSHA1 = new SHA1CryptoServiceProvider()) { bufferPolicyHash = cryptoSHA1.ComputeHash(bufferPolicy); // Initialize the RSACryptoServiceProvider object. RSACryptoServiceProvider providerRSA = new RSACryptoServiceProvider(); XmlDocument xmlPrivateKey = new XmlDocument(); // Load your private key, which you created by converting your // .pem file to the XML format that the .NET framework uses. // Several tools are available. xmlPrivateKey.Load(pathToPrivateKey); // Format the RSACryptoServiceProvider providerRSA // and create the signature. providerRSA.FromXmlString(xmlPrivateKey.InnerXml); RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(providerRSA); RSAFormatter.SetHashAlgorithm("SHA1"); byte[] signedHash = RSAFormatter.CreateSignature(bufferPolicyHash); // Convert the signed policy to URL-safe base64 encoding and // replace unsafe characters + = / with the safe characters - _ ~ string strSignedPolicy = ToUrlSafeBase64String(signedHash); return urlString + "?Policy=" + urlSafePolicy + "&Signature=" + strSignedPolicy + "&Key-Pair-Id=" + PrivateKeyId; } }
例 签名生成的实用方法

以下方法从文件和签名生成解析时间间隔中获得策略声明。

public static string CreatePolicyStatement(string policyStmnt, string resourceUrl, DateTime startTime, DateTime endTime, string ipAddress) { // Create the policy statement. FileStream streamPolicy = new FileStream(policyStmnt, FileMode.Open, FileAccess.Read); using (StreamReader reader = new StreamReader(streamPolicy)) { string strPolicy = reader.ReadToEnd(); TimeSpan startTimeSpanFromNow = (startTime - DateTime.Now); TimeSpan endTimeSpanFromNow = (endTime - DateTime.Now); TimeSpan intervalStart = (DateTime.UtcNow.Add(startTimeSpanFromNow)) - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); TimeSpan intervalEnd = (DateTime.UtcNow.Add(endTimeSpanFromNow)) - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); int startTimestamp = (int)intervalStart.TotalSeconds; // START_TIME int endTimestamp = (int)intervalEnd.TotalSeconds; // END_TIME if (startTimestamp > endTimestamp) return "Error!"; // Replace variables in the policy statement. strPolicy = strPolicy.Replace("RESOURCE", resourceUrl); strPolicy = strPolicy.Replace("START_TIME", startTimestamp.ToString()); strPolicy = strPolicy.Replace("END_TIME", endTimestamp.ToString()); strPolicy = strPolicy.Replace("IP_ADDRESS", ipAddress); strPolicy = strPolicy.Replace("EXPIRES", endTimestamp.ToString()); return strPolicy; } } public static TimeSpan GetDuration(string units, string numUnits) { TimeSpan timeSpanInterval = new TimeSpan(); switch (units) { case "seconds": timeSpanInterval = new TimeSpan(0, 0, 0, int.Parse(numUnits)); break; case "minutes": timeSpanInterval = new TimeSpan(0, 0, int.Parse(numUnits), 0); break; case "hours": timeSpanInterval = new TimeSpan(0, int.Parse(numUnits), 0 ,0); break; case "days": timeSpanInterval = new TimeSpan(int.Parse(numUnits),0 ,0 ,0); break; default: Console.WriteLine("Invalid time units;" + "use seconds, minutes, hours, or days"); break; } return timeSpanInterval; } private static TimeSpan GetDurationByUnits(string durationUnits, string startIntervalFromNow) { switch (durationUnits) { case "seconds": return new TimeSpan(0, 0, int.Parse(startIntervalFromNow)); case "minutes": return new TimeSpan(0, int.Parse(startIntervalFromNow), 0); case "hours": return new TimeSpan(int.Parse(startIntervalFromNow), 0, 0); case "days": return new TimeSpan(int.Parse(startIntervalFromNow), 0, 0, 0); default: return new TimeSpan(0, 0, 0, 0); } } public static string CopyExpirationTimeFromPolicy(string policyStatement) { int startExpiration = policyStatement.IndexOf("EpochTime"); string strExpirationRough = policyStatement.Substring(startExpiration + "EpochTime".Length); char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; List<char> listDigits = new List<char>(digits); StringBuilder buildExpiration = new StringBuilder(20); foreach (char c in strExpirationRough) { if (listDigits.Contains(c)) buildExpiration.Append(c); } return buildExpiration.ToString(); }

另请参阅