성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>SslStream의 CipherAlgorithm 선택이 가능할까요?</h1> <p> 다음과 같은 질문이 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# sslstream 사용시 Cipher List 설정 ; <a target='tab' href='http://www.sysnet.pe.kr/3/0/4819'>http://www.sysnet.pe.kr/3/0/4819</a> </pre> <br /> 결론부터 말하자면, 답이 없습니다. ^^; 그래도 위의 질문에 답하려고 찾아봤던 몇 가지 내용을 정리해 봅니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 일단, SslStream으로 제 웹사이트에 HTTPS 접속하는 코드를 만들어 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Net.Security; using System.Net.Sockets; /* Connect to Server via SSL with various Cipher strengths and algorithms in C# ; <a target='tab' href='http://stackoverflow.com/questions/4609109/connect-to-server-via-ssl-with-various-cipher-strengths-and-algorithms-in-c-shar'>http://stackoverflow.com/questions/4609109/connect-to-server-via-ssl-with-various-cipher-strengths-and-algorithms-in-c-shar</a> */ namespace ConsoleApp1 { class Program { static void Main(string[] args) { string host = "www.sysnet.pe.kr"; TcpClient client = new TcpClient(); <span style='color: blue; font-weight: bold'>client.Connect(host, 443);</span> using (SslStream Ssl = new SslStream(client.GetStream())) { <span style='color: blue; font-weight: bold'>Ssl.AuthenticateAsClient(host, null, System.Security.Authentication.SslProtocols.Tls12, false);</span> Console.WriteLine(Ssl.CipherAlgorithm); Console.WriteLine(Ssl.CipherStrength); Console.WriteLine(Ssl.SslProtocol); } client.Close(); } } } <span style='color: blue; font-weight: bold'>/* 출력 결과: Aes256 256 Tls12 */</span> </pre> <br /> 보시다시피 SslStream 코드 상으로는 CipherAlgorithm 알고리즘 선택에 대해 SslProtocol 정도만을 선택할 수 있을 뿐 구체적인 CipherAlgorithm의 선택은 할 수 없습니다.<br /> <br /> 다음의 글을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TLS/SSL and .NET Framework 4.0 ; <a target='tab' href='https://www.simple-talk.com/dotnet/net-framework/tlsssl-and-net-framework-4-0/'>https://www.simple-talk.com/dotnet/net-framework/tlsssl-and-net-framework-4-0/</a> </pre> <br /> "Client Message: ClientHello =>" 단계에서 "Set of Cipher Suites"를 넘겨 주고, "Server Message: <= ServerHello" 단계에서 "Selected Cipher Suite" 가 선택된다고 합니다. 또한 이러한 handshake 수행은 AuthenticateAsClient 메서드가 합니다. (재미있는 것은, AuthenticateAsClient 메서드가 virtual로 되어 있기 때문에 필요하다면 handshake 과정을 자신이 직접 재정의할 수 있습니다. 그런데, 어떻게 해야 할지 막막합니다. ^^;)<br /> <br /> 일단, SslStream이 선택한 Cipher Suite가 뭔지 한번 유추해 볼까요? "<a target='tab' href='https://www.simple-talk.com/dotnet/net-framework/tlsssl-and-net-framework-4-0/'>TLS/SSL and .NET Framework 4.0</a>" 글에 보면, 인증서에 따라 프로토콜 별로 허용되는 CipherSuite가 다음과 같다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Protocol KEA SYM (bit) HSH (bit) CipherSuite TLS1.0 RSAKeyX AES (128) SHA1 (160) TLS_RSA_WITH_AES_128_CBC_SHA SSL3.0 RSAKeyX RC4 (128) SHA1 (160) SSL_RSA_WITH_RC4_128_SHA SSL2.0 RSAKeyX RC4 (128) MD5 (128) SSL_CK_RC4_128_WITH_MD5 </pre> <br /> 이 글의 예제 코드에서는 System.Security.Authentication.SslProtocols.Tls12라고 주었고 출력 결과가 다음과 같은 걸로 봐서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CipherAlgorithm: Aes256 CipherStrength: 256 SslProtocol: Tls12 </pre> <a name='naming'></a> <br /> 아마도 "TLS_RSA_WITH_AES_256_CBC_SHA"와 같은 것이 선택된 듯합니다. 이런 이름 규칙은 다음의 문서에 나오는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Cipher Suites in TLS/SSL (Schannel SSP) ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/secauthn/cipher-suites-in-schannel'>https://learn.microsoft.com/en-us/windows/win32/secauthn/cipher-suites-in-schannel</a> </pre> <br /> <img alt='cipher_suites_1.png' src='/SysWebRes/bbs/cipher_suites_1.png' /><br /> <br /> 실제로 Windows 10 (version 1607)부터 지원되는 TLS Cipher Suites를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TLS Cipher Suites in Windows 10 v1607 ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-10-v1607'>https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-10-v1607</a> </pre> <br /> TLS 1.2의 경우 TLS_RSA_WITH_AES_256_CBC_SHA256가 지원된다고 합니다.<br /> <br /> <hr style='width: 50%' /> <br /> 그렇다면, TLS 1.2가 지원하는 또 다른 Cipher Suite인 TLS_RSA_WITH_AES_128_CBC_SHA로도 연결할 수 있을까요? 일단, 제가 아는 바로는 닷넷에서 기본 제공되는 SslStream으로는 방법이 없습니다. 분위기를 보아하니 왠지 마이크로소프트는 SslStream에서 사용하는 Cipher Suite를 윈도우에 기반해서 작성한 것 같습니다. 그렇다면, Win32 API를 이용해 가능할 듯싶은데, 다음의 글에 나오는 방법으로 몇 가지 테스트를 해봤습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Prioritizing Schannel Cipher Suites ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/secauthn/prioritizing-schannel-cipher-suites'>https://learn.microsoft.com/en-us/windows/win32/secauthn/prioritizing-schannel-cipher-suites</a> </pre> <br /> 우선 Win32 DLL 프로젝트를 만들어 다음과 같이 코딩하면 C#에서 호출할 수 있게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WIN32PROJECT1_API void EnumCipher(wchar_t *pszContext) { HRESULT Status = ERROR_SUCCESS; DWORD cbBuffer = 0; PCRYPT_CONTEXT_FUNCTIONS pBuffer = NULL; Status = BCryptEnumContextFunctions( CRYPT_LOCAL, pszContext, NCRYPT_SCHANNEL_INTERFACE, &cbBuffer, &pBuffer); if (FAILED(Status)) { printf_s("\n**** Error 0x%x returned by BCryptEnumContextFunctions\n", Status); goto Cleanup; } if (pBuffer == NULL) { printf_s("\n**** Error pBuffer returned from BCryptEnumContextFunctions is null"); goto Cleanup; } printf_s("\n\n Listing Cipher Suites "); for (UINT index = 0; index < pBuffer->cFunctions; ++index) { printf_s("\n%S", pBuffer->rgpszFunctions[index]); } Cleanup: if (pBuffer != NULL) { BCryptFreeBuffer(pBuffer); } } </pre> <br /> 만들어진 코드를 C#에서 다음과 같이 호출해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("Win32Project1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] public extern static void EnumCipher(string context); EnumCipher("SSL"); </pre> <br /> 이런 결과를 얻게 됩니다.<br /> <br /> <pre style='height: 200px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_RSA_WITH_AES_256_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_CBC_SHA256 TLS_RSA_WITH_AES_128_CBC_SHA256 TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_3DES_EDE_CBC_SHA TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 TLS_DHE_DSS_WITH_AES_256_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA TLS_RSA_WITH_RC4_128_SHA TLS_RSA_WITH_RC4_128_MD5 TLS_RSA_WITH_NULL_SHA256 TLS_RSA_WITH_NULL_SHA TLS_PSK_WITH_AES_256_GCM_SHA384 TLS_PSK_WITH_AES_128_GCM_SHA256 TLS_PSK_WITH_AES_256_CBC_SHA384 TLS_PSK_WITH_AES_128_CBC_SHA256 TLS_PSK_WITH_NULL_SHA384 TLS_PSK_WITH_NULL_SHA256 </pre> <br /> (참고: 윈도우 10의 경우 (Get-TlsCipherSuite).Name 명령어로 위의 출력 결과를 얻을 수 있습니다.)<br /> <br /> 그다음 SetPriorityCipher 함수를 정의하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WIN32PROJECT1_API void SetPriorityCipher(wchar_t *pszContext, wchar_t *pszFunction) { SECURITY_STATUS Status = ERROR_SUCCESS; Status = BCryptAddContextFunction(CRYPT_LOCAL, pszContext, NCRYPT_SCHANNEL_INTERFACE, pszFunction, CRYPT_PRIORITY_TOP); if (Status == STATUS_SUCCESS) { printf_s("Success\n"); } else { switch (Status) { case STATUS_INVALID_PARAMETER: // 0xC0000030L printf_s("Failed: STATUS_INVALID_PARAMETER\n"); break; case STATUS_NO_MEMORY: // 0xC0000017L printf_s("Failed: STATUS_NO_MEMORY\n"); break; case STATUS_NOT_FOUND: // 0xC0000225L printf_s("Failed: STATUS_NOT_FOUND\n"); break; case STATUS_ACCESS_DENIED: // 0xC0000022L printf_s("Failed: STATUS_ACCESS_DENIED\n"); // 관리자 권한 필요. break; default: printf_s("Failed: %d\n", Status); break; } } } </pre> <br /> 이렇게 C#에서 호출해 주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("Win32Project1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] public extern static void SetPriorityCipher(string context, string cipher); SetPriorityCipher("SSL", "TLS_RSA_WITH_AES_128_CBC_SHA"); </pre> <br /> TLS_RSA_WITH_AES_128_CBC_SHA의 우선순위가 가장 상단에 올라왔다는 것을 EnumCipher("SSL")를 다시 호출하는 것으로 알 수 있습니다. (주의할 것은 이때 EXE 실행을 반드시 "관리자 권한"으로 해야 합니다. 그렇지 않으면 BCryptAddContextFunction 함수의 반환 값이 0xC0000022로 나옵니다.)<br /> <br /> 이 호출로 인해 다음의 레지스트리 키가 그 변화를 담게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 키 경로: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010002 이름: Functions 값: <span style='color: blue; font-weight: bold'>TLS_RSA_WITH_AES_128_CBC_SHA</span> TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ...[생략]... TLS_PSK_WITH_AES_128_GCM_SHA256 TLS_PSK_WITH_AES_256_CBC_SHA384 TLS_PSK_WITH_AES_128_CBC_SHA256 TLS_PSK_WITH_NULL_SHA384 TLS_PSK_WITH_NULL_SHA256 </pre> <br /> 이외에, Schannel 관련되는 레지스트리 키를 다음의 글에서 찾을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > How to restrict the use of certain cryptographic algorithms and protocols in Schannel.dll ; <a target='tab' href='https://support.microsoft.com/ko-kr/help/245030/how-to-restrict-the-use-of-certain-cryptographic-algorithms-and-protocols-in-schannel.dll'>https://support.microsoft.com/ko-kr/help/245030/how-to-restrict-the-use-of-certain-cryptographic-algorithms-and-protocols-in-schannel.dll</a> </pre> <br /> 그런데, 우선 순위가 바뀌었음에도 불구하고 여전히 C# SslStream 예제 코드는 Aes256으로 나옵니다. 추측건데, 어차피 우선순위가 결정된다고 해도 목록이 서버로 전달될 테고, 서버 측에서는 제공되는 인증서의 능력에 기반해 그중에서 좀 더 높은 걸로 선택하는 듯합니다. (확실한 정보가 아닙니다!!!)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그렇다면, 결국 목록을 작성할 때 특정 항목을 빼는 수밖에 없습니다. 하지만 이 방법은 레지스트리 변경으로 인해 전역적으로 변화가 있기 때문에 좋은 방법이 아닙니다. (참고로, BCryptRemoveContextFunction 함수를 이용해 실제로 빼고 테스트해 본 것은 아닙니다.)<br /> <br /> 어떤 답변에 보니까, OpenSSL.NET으로 하라는데... 실제로 가능한지는 모르겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > openssl-net/openssl-net ; <a target='tab' href='https://github.com/openssl-net/openssl-net'>https://github.com/openssl-net/openssl-net</a> </pre> <br /> 정리 끝!<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1114&boardid=331301885'>첨부 파일은 이 글의 C++, C# 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1525
(왼쪽의 숫자를 입력해야 합니다.)