Microsoft MVP성태의 닷넷 이야기
.NET Framework: 646. SslStream의 CipherAlgorithm 선택이 가능할까요? [링크 복사], [링크+제목 복사],
조회: 14834
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)

SslStream의 CipherAlgorithm 선택이 가능할까요?

다음과 같은 질문이 있군요. ^^

C# sslstream 사용시 Cipher List 설정
; https://www.sysnet.pe.kr/3/0/4819

결론부터 말하자면, 답이 없습니다. ^^; 그래도 위의 질문에 답하려고 찾아봤던 몇 가지 내용을 정리해 봅니다. ^^




일단, SslStream으로 제 웹사이트에 HTTPS 접속하는 코드를 만들어 볼까요?

using System;
using System.Net.Security;
using System.Net.Sockets;

/*
Connect to Server via SSL with various Cipher strengths and algorithms in C#
; http://stackoverflow.com/questions/4609109/connect-to-server-via-ssl-with-various-cipher-strengths-and-algorithms-in-c-shar
*/

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string host = "www.sysnet.pe.kr";

            TcpClient client = new TcpClient();
            client.Connect(host, 443);

            using (SslStream Ssl = new SslStream(client.GetStream()))
            {
                Ssl.AuthenticateAsClient(host, null, System.Security.Authentication.SslProtocols.Tls12, false);
                Console.WriteLine(Ssl.CipherAlgorithm);
                Console.WriteLine(Ssl.CipherStrength);
                Console.WriteLine(Ssl.SslProtocol);
            }

            client.Close();
        }
    }
}

/*
출력 결과:

Aes256
256
Tls12
*/

보시다시피 SslStream 코드 상으로는 CipherAlgorithm 알고리즘 선택에 대해 SslProtocol 정도만을 선택할 수 있을 뿐 구체적인 CipherAlgorithm의 선택은 할 수 없습니다.

다음의 글을 보면,

TLS/SSL and .NET Framework 4.0
; https://www.simple-talk.com/dotnet/net-framework/tlsssl-and-net-framework-4-0/

"Client Message: ClientHello =>" 단계에서 "Set of Cipher Suites"를 넘겨 주고, "Server Message: <= ServerHello" 단계에서 "Selected Cipher Suite" 가 선택된다고 합니다. 또한 이러한 handshake 수행은 AuthenticateAsClient 메서드가 합니다. (재미있는 것은, AuthenticateAsClient 메서드가 virtual로 되어 있기 때문에 필요하다면 handshake 과정을 자신이 직접 재정의할 수 있습니다. 그런데, 어떻게 해야 할지 막막합니다. ^^;)

일단, SslStream이 선택한 Cipher Suite가 뭔지 한번 유추해 볼까요? "TLS/SSL and .NET Framework 4.0" 글에 보면, 인증서에 따라 프로토콜 별로 허용되는 CipherSuite가 다음과 같다고 합니다.

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 

이 글의 예제 코드에서는 System.Security.Authentication.SslProtocols.Tls12라고 주었고 출력 결과가 다음과 같은 걸로 봐서,

CipherAlgorithm: Aes256
CipherStrength: 256
SslProtocol: Tls12

아마도 "TLS_RSA_WITH_AES_256_CBC_SHA"와 같은 것이 선택된 듯합니다. 이런 이름 규칙은 다음의 문서에 나오는데,

Cipher Suites in TLS/SSL (Schannel SSP)
; https://learn.microsoft.com/en-us/windows/win32/secauthn/cipher-suites-in-schannel

cipher_suites_1.png

실제로 Windows 10 (version 1607)부터 지원되는 TLS Cipher Suites를 보면,

TLS Cipher Suites in Windows 10 v1607
; https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-10-v1607

TLS 1.2의 경우 TLS_RSA_WITH_AES_256_CBC_SHA256가 지원된다고 합니다.



그렇다면, TLS 1.2가 지원하는 또 다른 Cipher Suite인 TLS_RSA_WITH_AES_128_CBC_SHA로도 연결할 수 있을까요? 일단, 제가 아는 바로는 닷넷에서 기본 제공되는 SslStream으로는 방법이 없습니다. 분위기를 보아하니 왠지 마이크로소프트는 SslStream에서 사용하는 Cipher Suite를 윈도우에 기반해서 작성한 것 같습니다. 그렇다면, Win32 API를 이용해 가능할 듯싶은데, 다음의 글에 나오는 방법으로 몇 가지 테스트를 해봤습니다.

Prioritizing Schannel Cipher Suites
; https://learn.microsoft.com/en-us/windows/win32/secauthn/prioritizing-schannel-cipher-suites

우선 Win32 DLL 프로젝트를 만들어 다음과 같이 코딩하면 C#에서 호출할 수 있게 됩니다.

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);
    }

}

만들어진 코드를 C#에서 다음과 같이 호출해 보면,

[DllImport("Win32Project1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static void EnumCipher(string context);

EnumCipher("SSL");

이런 결과를 얻게 됩니다.

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

(참고: 윈도우 10의 경우 (Get-TlsCipherSuite).Name 명령어로 위의 출력 결과를 얻을 수 있습니다.)

그다음 SetPriorityCipher 함수를 정의하고,

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;
        }
    }
}

이렇게 C#에서 호출해 주면,

[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");

TLS_RSA_WITH_AES_128_CBC_SHA의 우선순위가 가장 상단에 올라왔다는 것을 EnumCipher("SSL")를 다시 호출하는 것으로 알 수 있습니다. (주의할 것은 이때 EXE 실행을 반드시 "관리자 권한"으로 해야 합니다. 그렇지 않으면 BCryptAddContextFunction 함수의 반환 값이 0xC0000022로 나옵니다.)

이 호출로 인해 다음의 레지스트리 키가 그 변화를 담게 됩니다.

키 경로: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010002
이름: Functions
값:
TLS_RSA_WITH_AES_128_CBC_SHA
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

이외에, Schannel 관련되는 레지스트리 키를 다음의 글에서 찾을 수 있습니다.

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

그런데, 우선 순위가 바뀌었음에도 불구하고 여전히 C# SslStream 예제 코드는 Aes256으로 나옵니다. 추측건데, 어차피 우선순위가 결정된다고 해도 목록이 서버로 전달될 테고, 서버 측에서는 제공되는 인증서의 능력에 기반해 그중에서 좀 더 높은 걸로 선택하는 듯합니다. (확실한 정보가 아닙니다!!!)




그렇다면, 결국 목록을 작성할 때 특정 항목을 빼는 수밖에 없습니다. 하지만 이 방법은 레지스트리 변경으로 인해 전역적으로 변화가 있기 때문에 좋은 방법이 아닙니다. (참고로, BCryptRemoveContextFunction 함수를 이용해 실제로 빼고 테스트해 본 것은 아닙니다.)

어떤 답변에 보니까, OpenSSL.NET으로 하라는데... 실제로 가능한지는 모르겠습니다.

openssl-net/openssl-net 
; https://github.com/openssl-net/openssl-net

정리 끝!

(첨부 파일은 이 글의 C++, C# 예제 코드를 포함합니다.)





[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/13/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  26  27  28  [29]  30  ...
NoWriterDateCnt.TitleFile(s)
12912정성태1/11/20229007개발 환경 구성: 623. ffmpeg.exe를 사용해 비디오 파일의 이미지를 PGM(Portable Gray Map) 파일 포맷으로 출력하는 방법 [1]
12911정성태1/11/20226281VS.NET IDE: 171. 비주얼 스튜디오 - 더 이상 만들 수 없는 "ASP.NET Core 3.1 Web Application (.NET Framework)" 프로젝트
12910정성태1/10/20226763제니퍼 .NET: 30. 제니퍼 닷넷 적용 사례 (8) - CPU high와 DB 쿼리 성능에 문제가 함께 있는 사이트
12909정성태1/10/20228157오류 유형: 782. Visual Studio 2022 설치 시 "Couldn't install Microsoft.VisualCpp.Redist.14.Latest"
12908정성태1/10/20226001.NET Framework: 1132. C# - ref/out 매개변수의 IL 코드 처리
12907정성태1/9/20226518오류 유형: 781. (youtube-dl.exe) 실행 시 "This app can't run on your PC" / "Access is denied." 오류 발생
12906정성태1/9/20227139.NET Framework: 1131. C# - 네임스페이스까지 동일한 타입을 2개의 DLL에서 제공하는 경우 충돌을 우회하는 방법 [1]파일 다운로드1
12905정성태1/8/20226800오류 유형: 780. Could not load file or assembly 'Microsoft.VisualStudio.TextTemplating.VSHost.15.0, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies.
12904정성태1/8/20228784개발 환경 구성: 623. Visual Studio 2022 빌드 환경을 위한 github Actions 설정 [1]
12903정성태1/7/20227387.NET Framework: 1130. C# - ELEMENT_TYPE_INTERNAL 유형의 사용 예
12902정성태1/7/20227457오류 유형: 779. SQL 서버 로그인 에러 - provider: Shared Memory Provider, error: 0 - No process is on the other end of the pipe.
12901정성태1/5/20227469오류 유형: 778. C# - .NET 5+에서 warning CA1416: This call site is reachable on all platforms. '...' is only supported on: 'windows' 경고 발생
12900정성태1/5/20229166개발 환경 구성: 622. vcpkg로 ffmpeg를 빌드하는 경우 생성될 구성 요소 제어하는 방법
12899정성태1/3/20228671개발 환경 구성: 621. windbg에서 python 스크립트 실행하는 방법 - pykd (2)
12898정성태1/2/20229230.NET Framework: 1129. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 인코딩 예제(encode_video.c) [1]파일 다운로드1
12897정성태1/2/20228067.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 [4]파일 다운로드1
12896정성태1/1/202210980.NET Framework: 1127. C# - FFmpeg.AutoGen 라이브러리를 이용한 기본 프로젝트 구성파일 다운로드1
12895정성태12/31/20219417.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작 [1]파일 다운로드1
12894정성태12/30/20217393.NET Framework: 1125. C# - DefaultObjectPool<T>의 IDisposable 개체에 대한 풀링 문제 [3]파일 다운로드1
12893정성태12/27/20218981.NET Framework: 1124. C# - .NET Platform Extension의 ObjectPool<T> 사용법 소개파일 다운로드1
12892정성태12/26/20216992기타: 83. unsigned 형의 이전 값이 최댓값을 넘어 0을 지난 경우, 값의 차이를 계산하는 방법
12891정성태12/23/20216908스크립트: 38. 파이썬 - uwsgi의 --master 옵션
12890정성태12/23/20217044VC++: 152. Golang - (문자가 아닌) 바이트 위치를 반환하는 strings.IndexRune 함수
12889정성태12/22/20219458.NET Framework: 1123. C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법파일 다운로드1
12888정성태12/21/20217565.NET Framework: 1122. C# - ImageCodecInfo 사용 시 System.Drawing.Image와 System.Drawing.Bitmap에 따른 Save 성능 차이파일 다운로드1
12887정성태12/21/20219689오류 유형: 777. OpenCVSharp4를 사용한 프로그램 실행 시 "The type initializer for 'OpenCvSharp.Internal.NativeMethods' threw an exception." 예외 발생
... 16  17  18  19  20  21  22  23  24  25  26  27  28  [29]  30  ...