C# 및 .NET Framework를 사용한 URL 서명 생성
이 섹션의 C# 예제는 사용자 지정 및 미리 준비된 정책 설명을 사용하여 CloudFront 프라이빗 배포에 대한 서명을 만드는 방법을 보여주는 예제 애플리케이션을 구현합니다. 이 예제는 .NET 애플리케이션에서 유용할 수 있는 AWS SDK for .NET
AWS SDK for .NET를 사용하여 서명된 URL과 서명된 쿠키를 생성할 수도 있습니다. AWS SDK for .NET API 참조에서 다음 주제를 참조하세요.
-
서명된 URL – AmazonCloudFrontUrlSigner
-
서명된 쿠키 – AmazonCloudFrontCookieSigner
코드를 다운로드하려면 C#의 서명 코드를 참조하세요.
참고
URL 서명 생성은 서명된 URL을 통해 프라이빗 콘텐츠를 제공하는 프로세스의 한 부분에 불과합니다. 전체 프로세스에 대한 자세한 내용은 서명된 URL 사용을 참조합니다. 서명된 쿠키 사용에 대한 자세한 내용은 서명된 쿠키 사용 섹션을 참조하세요.
.NET Framework에서 RSA 키 사용
.NET Framework에서 RSA 키를 사용하려면 AWS가 제공한 .pem 파일을 .NET Framework에서 사용하는 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#의 미리 준비된 정책 서명 메서드
다음 C# 코드는 다음을 수행하여 미리 준비된 정책을 사용하는 서명된 URL을 만듭니다.
-
정책 설명을 만듭니다.
-
SHA1을 사용하여 정책 설명을 해시하고, RSA와 해당 퍼블릭 키가 신뢰할 수 있는 키 그룹에 있는 프라이빗 키를 사용하여 결과에 서명합니다.
-
Base64 방식으로 해시 및 서명된 정책 설명을 인코딩하고 특수 문자를 교체하여 URL 요청 파라미터로 사용하기에 안전한 문자열을 만드세요.
-
값을 연결합니다.
완전한 구현에 대한 내용은 C#의 서명 코드에서 예제를 참조하세요.
참고
CloudFront에 퍼블릭 키를 업로드하면 keyId
가 반환됩니다. 자세한 내용은
&Key-Pair-Id를 참조하세요.
예 : 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 keyId) { // args[] 0-thisMethod, 1-resourceUrl, 2-seconds-minutes-hours-days // to expiration, 3-numberOfPreviousUnits, 4-pathToPolicyStmnt, // 5-pathToPrivateKey, 6-keyId 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=" + keyId; } }
C#의 사용자 지정 정책 서명 메서드
다음 C# 코드는 다음을 수행하여 사용자 지정 정책을 사용하는 서명된 URL을 만듭니다.
-
정책 설명을 만듭니다.
-
Base64 방식으로 정책 설명을 인코딩하고 특수 문자를 교체하여 URL 요청 파라미터로 사용하기에 안전한 문자열을 만드세요.
-
SHA1을 사용하여 정책 설명을 해시하고, RSA와 해당 퍼블릭 키가 신뢰할 수 있는 키 그룹에 있는 프라이빗 키를 사용하여 결과를 암호화합니다.
-
Base64 방식으로 해시된 정책 설명을 인코딩하고 특수 문자를 교체하여 URL 요청 파라미터로 사용하기에 안전한 문자열을 만드세요.
-
값을 연결합니다.
완전한 구현에 대한 내용은 C#의 서명 코드에서 예제를 참조하세요.
참고
CloudFront에 퍼블릭 키를 업로드하면 keyId
가 반환됩니다. 자세한 내용은
&Key-Pair-Id를 참조하세요.
예 : 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 keyId) { // args[] 0-thisMethod, 1-resourceUrl, 2-seconds-minutes-hours-days // to expiration, 3-numberOfPreviousUnits, 4-starttimeFromNow, // 5-ip_address, 6-pathToPolicyStmt, 7-pathToPrivateKey, 8-keyId 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=" + keyId; } }
서명 생성을 위한 유틸리티 메서드
다음 메서드는 파일에서 정책 설명을 가져오고 서명 생성을 위해 시간 간격을 구문 분석합니다.
예 : 서명 생성을 위한 유틸리티 메서드
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(); }
다음 사항도 참조하세요.