C# 및 .NET Framework를 사용한 URL 서명 생성 - Amazon CloudFront

C# 및 .NET Framework를 사용한 URL 서명 생성

이 섹션의 C# 예제는 사용자 지정 및 미리 준비된 정책 설명을 사용하여 CloudFront 프라이빗 배포에 대한 서명을 만드는 방법을 보여주는 예제 애플리케이션을 구현합니다. 이 예제는 .NET 애플리케이션에서 유용할 수 있는 AWS SDK for .NET를 기반으로 한 유틸리티 함수를 포함합니다.

AWS SDK for .NET를 사용하여 서명된 URL과 서명된 쿠키를 생성할 수도 있습니다. AWS SDK for .NET API 참조에서 다음 주제를 참조하세요.

  • 서명된 URL – Amazon.CloudFront > AmazonCloudFrontUrlSigner

  • 서명된 쿠키 – Amazon.CloudFront > AmazonCloudFrontCookieSigner

참고

URL 서명 생성은 서명된 URL을 통해 프라이빗 콘텐츠를 제공하는 프로세스의 한 부분에 불과합니다. 전체 프로세스에 대한 자세한 내용은 서명된 URL 사용을 참조하세요.

코드를 다운로드하려면 C#의 서명 코드를 참조하세요.

.NET 프레임워크에서 RSA 키를 사용하려면 AWS가 제공한 .pem 파일을 .NET 프레임워크에서 사용하는 XML 형식으로 변환해야 합니다.

변환 후, RSA 프라이빗 키 파일의 형식은 다음과 같습니다.

예 XML .NET Framework 형식의 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 PrivateKey.xml, 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 PrivateKey.xml, which you created by converting your // .pem file to the XML format that the .NET framework uses. // Several tools are available. xmlPrivateKey.Load("PrivateKey.xml"); // 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(); }

다음 사항도 참조하세요.