Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일

(시리즈 글이 14개 있습니다.)
.NET Framework: 292. RSACryptoServiceProvider의 공개키와 개인키 구분
; https://www.sysnet.pe.kr/2/0/1218

.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기
; https://www.sysnet.pe.kr/2/0/1295

.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자
; https://www.sysnet.pe.kr/2/0/1300

.NET Framework: 356. (공개키를 담은) 자바의 key 파일을 닷넷의 RSACryptoServiceProvider에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1401

.NET Framework: 383. RSAParameters의 ToXmlString과 ExportParameters의 결과 비교
; https://www.sysnet.pe.kr/2/0/1491

.NET Framework: 565. C# - Rabin-Miller 소수 생성 방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/10925

.NET Framework: 566. openssl의 PKCS#1 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/10926

.NET Framework: 638. RSAParameters와 RSA
; https://www.sysnet.pe.kr/2/0/11140

.NET Framework: 1037. openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 (2)
; https://www.sysnet.pe.kr/2/0/12598

.NET Framework: 2093. C# - PKCS#8 PEM 파일을 이용한 RSA 개인키/공개키 설정 방법
; https://www.sysnet.pe.kr/2/0/13245

닷넷: 2297. C# - ssh-keygen으로 생성한 Public Key 파일 해석과 fingerprint 값(md5, sha256) 생성
; https://www.sysnet.pe.kr/2/0/13739

닷넷: 2297. C# - ssh-keygen으로 생성한 ecdsa 유형의 Public Key 파일 해석
; https://www.sysnet.pe.kr/2/0/13742

닷넷: 2300. C# - OpenSSH의 공개키 파일에 대한 "BEGIN OPENSSH PUBLIC KEY" / "END OPENSSH PUBLIC KEY" PEM 포맷
; https://www.sysnet.pe.kr/2/0/13747

닷넷: 2302. C# - ssh-keygen으로 생성한 Private Key와 Public Key 연동
; https://www.sysnet.pe.kr/2/0/13749




C# - ssh-keygen으로 생성한 ecdsa 유형의 Public Key 파일 해석

지난 글에서 ssh-keygen으로 생성한 공개키 파일을 해석해 봤는데요,

C# - ssh-keygen으로 생성한 Public Key 파일 해석과 fingerprint 값(md5, sha256) 생성
; https://www.sysnet.pe.kr/2/0/13739

근래에는 RSA보다는 (키 길이도 짧아 연산 속도에 부담이 없는) Elliptic Curve 암호화 방식을 더 선호합니다.

Visual Studio - Cross Platform / "Authentication Type: Private Key"로 접속하는 방법
; https://www.sysnet.pe.kr/2/0/13733

테스트를 위해 하나 만들면,

// 여기서는 테스트를 쉽게 하기 위해 암호를 생략하도록 (-N 옵션의 빈 문자열) 지정했습니다. (현업에서는 암호 사용을 권장합니다.)

C:\temp> ssh-keygen -N "" -m pem -t ecdsa -f test_ecdsa
...[생략]...

C:\temp> type test_ecdsa
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEYJmh+sBdW+5ZjpqsHFJRiHwXuJ3IJFUhyEndpG1BVdoAoGCCqGSM49
AwEHoUQDQgAEtLUV5PkGQHC1pIDe35fjtJn6zYWC/63FE6/vFwTKS8knZMf17x5K
jAW98bl1T6nJMiPGYPPr/1dfyL09AQUvnA==
-----END EC PRIVATE KEY-----

C:\temp> type test_ecdsa.pub
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLS1FeT5BkBwtaSA3t+X47SZ+s2Fgv+txROv7xcEykvJJ2TH9e8eSowFvfG5dU+pyTIjxmDz6/9XX8i9PQEFL5w= kevin@THE12

보는 바와 같이 이런 경우 공개키가 "ssh-rsa"가 아닌 "ecdsa-sha2-nistp256"로 시작합니다. 그렇긴 한데, 구조 자체는 이전의 해석 방식을 그대로 따르는데요, 단지 필드의 의미가 좀 변경됩니다.

이에 대한 정보를 다음의 경로에서 찾아냈는데요,

SSH public key: C# parsing library
; https://formats.kaitai.io/ssh_public_key/csharp.html

SSH public key: format specification
; https://formats.kaitai.io/ssh_public_key/

openssh-portable/sshkey.c (위의 글을 작성한 이가 참조했다는 openssh 소스코드)
; https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshkey.c#L1970

대충 다음과 같이 해석할 수 있습니다.

string        "algorithm_name"
string        "curve_name"
int(byte[])   Q[length(Q.X) + length(Q.Y) + 1]

실제로 아래는 지난 글의 코드를 조금 수정해 "ecdsa-sha2-nistp256"로 시작하는 공개키를 읽도록 한 것입니다.

using System.Security.Cryptography;
using System.Text;

namespace ConsoleApp1;

internal class Program
{
    static void Main(string[] args)
    {
        string text = File.ReadAllText("test_ecdsa.pub");

        {
            string[] sshKeys = text.Split(' ');
            if (sshKeys.Length != 3)
            {
                throw new Exception("Invalid SSH Key");
            }

            string keyType = sshKeys[0];
            string comment = sshKeys[2];
            byte[] bytesEncoded = Convert.FromBase64String(sshKeys[1]);

            if (keyType != "ecdsa-sha2-nistp256")
            {
                throw new NotSupportedException($"Unsupported key type: {keyType}");
            }

            (string algorithmName, string curveName, byte[] ecKey) = DecodeSSHPublicKey(bytesEncoded);
        }
    }

    private static (string algorithmName, string curveName, byte[] eccKey) DecodeSSHPublicKey(byte[] bytesEncoded)
    {
        string algorithmName;
        string curveName;
        byte[] eccKey;

        using (var stream = new MemoryStream(bytesEncoded))
        using (var reader = new BinaryReader(stream))
        {
            int algorithmLength = reader.ReadInt32BE();
            algorithmName = Encoding.ASCII.GetString(reader.ReadBytes(algorithmLength));

            int curveNameLength = reader.ReadInt32BE();
            curveName = Encoding.ASCII.GetString(reader.ReadBytes(curveNameLength));

            int publicKeyLength = reader.ReadInt32BE();
            eccKey = reader.ReadBytes(publicKeyLength);
        }

        return (algorithmName, curveName, eccKey);
    }
}

public static class BinaryReaderExtension
{
    public static Int32 ReadInt32BE(this BinaryReader reader)
    {
        byte[] bytes = new byte[4];
        bytes[3] = (byte)reader.ReadByte();
        bytes[2] = (byte)reader.ReadByte();
        bytes[1] = (byte)reader.ReadByte();
        bytes[0] = (byte)reader.ReadByte();
        return BitConverter.ToInt32(bytes);
    }
}

자, 이렇게 해서 Elliptic Curve 방식의 공개키를 구했는데요, 이제 이것을 관련 타입에서 어떻게 초기화할 수 있을까요? 닷넷에서 Elliptic Curve 키는 ECParameters를 통해 초기화하는데요,

ECParameters ecparams = new ECParameters
{
    Curve = ECCurve.NamedCurves.nistP256,
    Q = new ECPoint
    {
        X = [...x_coord_byte_buffer...],
        Y = [...y_coord_byte_buffer...],
    }
};

위와 같이 Q.X, Q.Y에만 초기화를 하면, 마치 RSA에서 공개키를 초기화하는 것과 같습니다. 그런데, 여기서 재미있는 건 "ecdsa-sha2-nistp256"로 구한 ecKey 버퍼의 크기가 65바이트라는 점입니다.

보통 Q.X, Q.Y가 동일한 크기를 가지므로 정확히 2로 나눠떨어져야 하는데, 생뚱맞게 1바이트가 더 큽니다. 이것의 원인은, Q.X, Q.Y를 합한 바이트 배열을 압축 유형으로 표현하는 것까지 지원하기 위해 선행 바이트에 그 여부를 표시하기 때문입니다.

압축 유형인 경우에는 0x02, 0x03이 되는데요, 이것은 다음의 공식으로 구해집니다.

// Algorithm for elliptic curve point compression
// AlexHag/ECC-DSA-DH
byte prefix = (byte)((yCoord[yCoord.Length - 1] & 1) == 0 ? 0x02: 0x03);

반면, Uncompressed 유형인 경우에는 고정값 0x04가 선행됩니다. (여기서는 compressed 유형은 생략할 텐데, Algorithm for elliptic curve point compression 글을 참고하시면 decompressed 하는 방법을 알 수 있으니 참고하시고.)

따라서, ecKey 바이트 배열을 이용해 ECParameters를 다음과 같이 초기화할 수 있고,

(string algorithmName, string curveName, byte[] ecKey) = DecodeSSHPublicKey(bytesEncoded);

int keyLengthInBytes= (ecKey.Length - 1) / 2;
int keyLength = keyLengthInBytes * 8;

{
    if (ecKey[0] != 0x04)
    {
        throw new NotSupportedException("Only uncompressed keys are supported");
    }

    byte[] xCoord = ecKey.Skip(1).Take(keyLengthInBytes).ToArray();
    byte[] yCoord = ecKey.Skip(keyLengthInBytes + 1).ToArray();

    ECParameters ecparams = new ECParameters
    {
        Curve = ECCurve.CreateFromFriendlyName(curveName),
        Q = new ECPoint
        {
            X = xCoord,
            Y = yCoord,
        }
    };
}

이후 키가 정상적인지 테스트 여부를 ECDiffieHellman.ImportParameters 등의 메서드를 사용해 확인할 수 있습니다.

ECDiffieHellman ecdh = ECDiffieHellman.Create(ecParam); // ecdh.ImportParameters(ecParam);

// 또는 서명을 검증하기 위해,
ECDsa ecdsa = ECDsa.Create(ecParam); // ecdsa.ImportParameters(ecParam);
// bool verified = ecdsa.VerifyData(plainTextBuffer, signedBuffer, HashAlgorithmName.SHA256);

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





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







[최초 등록일: ]
[최종 수정일: 9/30/2024]

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

비밀번호

댓글 작성자
 



2024-09-30 09시52분
본문에서 Elliptic Curve를 이용한 서명을 다뤘는데요, 아래의 repo에는,

AlexHag/ECC-DSA-DH
; https://github.com/AlexHag/ECC-DSA-DH

키를 갖는 양 측에서 키 교환 및 그 키로 대칭키 암호화를 하는 예제를 보여주고 있습니다.
정성태

... 106  107  108  109  110  111  112  113  114  115  116  117  118  [119]  120  ...
NoWriterDateCnt.TitleFile(s)
10949정성태4/28/201619948.NET Framework: 575. SharedDomain과 JIT 컴파일파일 다운로드1
10948정성태4/28/201623910.NET Framework: 574. .NET - 눈으로 확인하는 SharedDomain의 동작 방식 [3]파일 다운로드1
10947정성태4/27/201621793.NET Framework: 573. .NET CLR4 보안 모델 - 4. CLR4 보안 모델에서의 조건부 APTCA 역할파일 다운로드1
10946정성태4/26/201624575VS.NET IDE: 106. Visual Studio 2015 확장 - INI 파일을 위한 사용자 정의 포맷 기능 (Syntax Highlighting)파일 다운로드1
10945정성태4/26/201618342오류 유형: 327. VSIX 프로젝트 빌드 시 The "VsTemplatePaths" task could not be loaded from the assembly 오류 발생
10944정성태4/22/201619565디버깅 기술: 80. windbg - 풀 덤프 파일로부터 텍스트 파일의 내용을 찾는 방법
10943정성태4/22/201624420디버깅 기술: 79. windbg - 풀 덤프 파일로부터 .NET DLL을 추출/저장하는 방법 [1]
10942정성태4/19/201619734디버깅 기술: 78. windbg 사례 - .NET 예외가 발생한 시점의 오류 분석 [1]
10941정성태4/19/201619646오류 유형: 326. Error MSB8020 - The build tools for v120_xp (Platform Toolset = 'v120_xp') cannot be found.
10940정성태4/18/201622941Windows: 116. 프로세스 풀 덤프 시간을 줄여 주는 Process Reflection [3]
10939정성태4/18/201623953.NET Framework: 572. .NET APM 비동기 호출의 Begin...과 End... 조합 [3]파일 다운로드1
10938정성태4/13/201623512오류 유형: 325. 파일 삭제 시 오류 - Error 0x80070091: The directory is not empty.
10937정성태4/13/201631698Windows: 115. UEFI 모드로 윈도우 10 설치 가능한 USB 디스크 만드는 방법
10936정성태4/8/201642449Windows: 114. 삼성 센스 크로노스 7 노트북의 운영체제를 USB 디스크로 새로 설치하는 방법 [3]
10935정성태4/7/201626718웹: 32. Edge에서 Google Docs 문서 편집 시 한영 전환키가 동작 안하는 문제
10934정성태4/5/201625410디버깅 기술: 77. windbg의 콜스택 함수 인자를 쉽게 확인하는 방법 [1]
10933정성태4/5/201631043.NET Framework: 571. C# - 스레드 선호도(Thread Affinity) 지정하는 방법 [8]파일 다운로드1
10932정성태4/4/201623327VC++: 96. C/C++ 식 평가 - printf("%d %d %d\n", a, a++, a);
10931정성태3/31/201623617개발 환경 구성: 283. Hyper-V 내에 구성한 Active Directory 환경의 시간 구성 방법 [3]
10930정성태3/30/201621580.NET Framework: 570. .NET 4.5부터 추가된 CLR Profiler의 실행 시 Rejit 기능
10929정성태3/29/201631681.NET Framework: 569. ServicePointManager.DefaultConnectionLimit의 역할파일 다운로드1
10928정성태3/28/201637384.NET Framework: 568. ODP.NET의 완전한 닷넷 버전 Oracle ODP.NET, Managed Driver [2]파일 다운로드1
10927정성태3/25/201626581.NET Framework: 567. System.Net.ServicePointManager의 DefaultConnectionLimit 속성 설명
10926정성태3/24/201626151.NET Framework: 566. openssl의 PKCS#1 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 [10]파일 다운로드1
10925정성태3/24/201620439.NET Framework: 565. C# - Rabin-Miller 소수 생성 방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 - 두 번째 이야기파일 다운로드1
10924정성태3/22/201621123오류 유형: 324. Visual Studio에서 Azure 클라우드 서비스 생성 시 Failed to initialize the PowerShell host 에러 발생
... 106  107  108  109  110  111  112  113  114  115  116  117  118  [119]  120  ...