Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 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




openssl의 PKCS#1 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법

키 파일의 형식이 참 다양합니다. ^^ 예전 글에서 다뤘던,

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

자바의 키 파일은 완전히 바이너리 형식이었고, openssl의 "genrsa" 옵션으로 생성하는 키 파일은 PEM 형식의 텍스트입니다. 한번 볼까요? ^^ 다음과 같이 명령행에서 "openssl genrsa [keysize]"와 같이 실행하면 화면에 PEM 형식의 개인키가 출력됩니다.

D:\Tools\cert\openssl\bin>openssl genrsa 4096
Loading 'screen' into random state - done
Generating RSA private key, 4096 bit long modulus
......++
.......................++
unable to write 'random state'
e is 65537 (0x10001)
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

그럼, "-----BEGIN RSA PRIVATE KEY-----"과 "-----END RSA PRIVATE KEY-----"을 포함한 그 사이의 출력 결과를 파일로 저장한 후 닷넷의 RSACryptoServiceProvider로 연결하면 됩니다.

문제는, 닷넷의 BCL에서는 PEM 파일을 읽는 기능이 없다는 것입니다. 따라서 이번에도 "(공개키를 담은) 자바의 key 파일을 닷넷의 RSACryptoServiceProvider에서 사용하는 방법" 글에 소개했던 "Bouncy Castle" 라이브러리를 이용해야 합니다.

그래도 ^^ 세상이 많이 좋아져서 Nuget 콘솔 창에서 다음과 같이 입력만 해주시면 다운로드 및 참조까지 다 되어 한방에 사용 준비가 됩니다.

PM> Install-Package BouncyCastle 

이제 PEM 키 파일 변환은 다음과 같이 해주시면 됩니다.

RSACryptoServiceProvider rsa = LoadKey("key.txt");

private static RSACryptoServiceProvider LoadKey(string keyFilePath)
{
    Org.BouncyCastle.OpenSsl.PemReader pem = new Org.BouncyCastle.OpenSsl.PemReader(File.OpenText(keyFilePath));
    Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair rsaParameters = pem.ReadObject() as Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair;
    Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters key = rsaParameters.Private as Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters;

    RSAParameters netrsa = DotNetUtilities.ToRSAParameters(key);
    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.ImportParameters(netrsa);

    return rsa;
}

정상적으로 로드되었다면 다음과 같은 암호화/복호화 테스트가 잘 수행이 됩니다. ^^

// RSACryptoServiceProvider로 암/복호화 테스트
UnicodeEncoding ByteConverter = new UnicodeEncoding();

byte[] dataToEncrypt = ByteConverter.GetBytes("Data to Encrypt");
byte[] encryptedData;
byte[] decryptedData;

encryptedData = RSAEncrypt(dataToEncrypt, rsa.ExportParameters(false), false);
decryptedData = RSADecrypt(encryptedData, rsa.ExportParameters(true), false);

Console.WriteLine("Decrypted plaintext: {0}", ByteConverter.GetString(decryptedData));

static public byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
{
    try
    {
        byte[] encryptedData;
        using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
        {
            RSA.ImportParameters(RSAKeyInfo);
            encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
        }
        return encryptedData;
    }
    catch (CryptographicException e)
    {
        Console.WriteLine(e.Message);
        return null;
    }
}

static public byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
{
    try
    {
        byte[] decryptedData;
        using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
        {
            RSA.ImportParameters(RSAKeyInfo);
            decryptedData = RSA.Decrypt(DataToDecrypt, DoOAEPPadding);
        }
        return decryptedData;
    }
    catch (CryptographicException e)
    {
        Console.WriteLine(e.ToString());
        return null;
    }
}

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

참고로, PEM 파일로부터 .pfx 파일 만드는 것과 Java .keystore 파일에 저장된 개인키가 pem 형식으로 나오는 등의 내용을 다음의 글에서 상세하게 보실 수 있습니다. ^^

.keystore 파일에 저장된 개인키 추출방법과 인증기관으로부터 온 공개키를 합친 pfx 파일 만드는 방법
; https://www.sysnet.pe.kr/2/0/1262

JKS(Java Key Store)에 저장된 인증서를 ActiveX 코드 서명에 사용하는 방법
; https://www.sysnet.pe.kr/2/0/882




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

[연관 글]






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

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

비밀번호

댓글 작성자
 



2016-03-25 01시06분
[spowner] 어떤 일을 하시길래 정리하시는 내용들이 평범하지가 않네요 (사실 거의 대부분의 글들이 그렇습니다 TT). 키 관련 최근 포스팅 잘 보고 있습니다. 감사합니다
[guest]
2016-03-25 12시10분
그냥 이거저거 Q&A 하다 보니... 그렇게 된 것 같습니다. ^^
정성태
2018-08-27 12시50분
정성태
2022-01-11 09시32분
[apple] 일반적으로 RSA 은 암호화 복호화 키가 서로 달라야 하는데, 예제 상의 암, 복호하는 privete.txt 의 키만 가지고 수행하는데,
관련 설명을 요청드려도 될런지요.
[guest]
2022-01-11 09시58분
공개키의 정보는 개인키에 이미 포함되어 있습니다.

본문에서 LoadKey를 이용해 RSACryptoServiceProvider를 반환받은 후 ExportParameters 메서드를 호출하고 있는데요. ExportParameters의 인자로 true를 주면 개인키를 포함한 RSAParameters를 반환하고, false를 주면 개인키를 포함하지 않은, 즉 공개키를 반환합니다.

따라서, RSA 암호화 시에 원래는 ExportParameters(false)로 반환받은 공개키 정보를 상대방에게 전달하고 그쪽에서 공개키로 데이터를 암호화하면, 그 암호 데이터를 이쪽에서 가지고 있는 개인키로 복호화를 하는 것입니다.

(단지, 본문의 예제는 그것들을 별도의 프로젝트로 분리하지 않은 것입니다.)
정성태
2022-06-28 07시17분
[sheoito] 안녕하세요
PEM, PCKS8 format으로 된 key로 동작하는 예제를 요청드려도 될까요?
구글링으로 관련 내용 찾기가 어렵네요.
BouncyCastle은 PCKS1를 Default로 하는 것 같지만, Pkcs8EncryptedPrivateKeyInfo 라는 Class를 가지고 있는 것 보면, 지원을 하는 것 같긴 한데,,
이쪽으로는 지식이 거의 없어서 헤메는 중입니다;;
[guest]
2022-06-28 09시20분
이 글에 있는 내용이 그대로 적용이 될 텐데요. Org.BouncyCastle.OpenSsl.PemReader로 안 읽혀지던가요?
정성태
2022-06-29 09시50분
[sheoito] 네..
Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair rsaParameters = pem.ReadObject() as Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair;
이 구문에서 rsaParameters가 null이 됩니다.

개인키는 아래 구문으로 만들었습니다.
openssl genrsa -out rsaprivkey.pem 2048
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in rsaprivkey.pem -out rsaprivkey8.pem

웹 프로그램(ASP.NET MVC)에서 사용하고 있는데,
서버에서 복호화를 못하니, 급한대로 Client(임시 페이지)에 개인키를 넘겨주고 JSEncrypt library로 복호화 해서 다시 서버로 넘겨주고 있습니다;
임시 페이지가 짧게 지나가고, 복호화된 대칭키로 서버에서 실제 정보를 다시 복호화하는 과정이 있긴 하지만, 그래도 이건 아니지 싶습니다 ㅠㅜ
[guest]
2022-06-29 10시25분
본문의 예제는 openssl genrsa로 생성한 "-----BEGIN RSA PRIVATE KEY-----" / "-----END RSA PRIVATE KEY-----" PEM 파일에 대해서 적용할 수 있습니다.

그 파일을 pkcs8 PEM 형식으로 전환한 경우에는 그냥 다음과 같이 AsymmetricCipherKeyPair 절차 없이 곧바로 RsaPrivateCrtKeyParameters로 읽으면 됩니다.

-------------------------------------------
string keyFilePath = "rsaprivkey8.pem";

Org.BouncyCastle.OpenSsl.PemReader pem = new Org.BouncyCastle.OpenSsl.PemReader(File.OpenText(keyFilePath));
var key = pem.ReadObject() as Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters;
-------------------------------------------

그건 그렇고... (암호화 데이터의 보안 등급에 따라 다르겠지만) 개인키를 클라이언트로 보내 구현한 것은 단순히 급했다는 핑계로 넘어갈 수 있는 문제는 아닙니다.
정성태
2022-07-05 11시57분
[sheoito] 출장 때문에 답변 주신걸 이제야....
덕분에 해결했네요. 정말 감사합니다.

빨리 부끄러운 코드 수정하러 가야겠습니다. ;;
다시 한번 감사드립니다.
[guest]

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13838정성태12/4/2024865오류 유형: 935. Windbg - Breakpoint 0's offset expression evaluation failed.
13837정성태12/3/2024998디버깅 기술: 204. Windbg - 윈도우 핸들 테이블 (3) - Windows 10 이상인 경우
13836정성태12/3/20241058디버깅 기술: 203. Windbg - x64 가상 주소를 물리 주소로 변환 (페이지 크기가 2MB인 경우)
13835정성태12/2/20241019오류 유형: 934. Azure - rm: cannot remove '...': Directory not empty
13834정성태11/29/20241146Windows: 275. C# - CUI 애플리케이션과 Console 윈도우 (Windows 10 미만의 Classic Console 모드인 경우)파일 다운로드1
13833정성태11/29/20241121개발 환경 구성: 737. Azure Web App에서 Scale-out으로 늘어난 리눅스 인스턴스에 SSH 접속하는 방법
13832정성태11/27/20241155Windows: 274. Windows 7부터 도입한 conhost.exe
13831정성태11/27/20241003Linux: 111. eBPF - BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_RINGBUF에 대한 다양한 용어들
13830정성태11/25/20241100개발 환경 구성: 736. 파이썬 웹 앱을 Azure App Service에 배포하기
13829정성태11/25/20241033스크립트: 67. 파이썬 - Windows 버전에서 함께 설치되는 py.exe
13828정성태11/25/20241052개발 환경 구성: 735. Azure - 압축 파일을 이용한 web app 배포 시 디렉터리 구분이 안 되는 문제파일 다운로드1
13827정성태11/25/20241139Windows: 273. Windows 환경의 파일 압축 방법 (tar, Compress-Archive)
13826정성태11/21/20241220닷넷: 2313. C# - (비밀번호 등의) Console로부터 입력받을 때 문자열 출력 숨기기(echo 끄기)파일 다운로드1
13825정성태11/21/20241182Linux: 110. eBPF / bpf2go - BPF_RINGBUF_OUTPUT / BPF_MAP_TYPE_RINGBUF 사용법
13824정성태11/20/20241147Linux: 109. eBPF / bpf2go - BPF_PERF_OUTPUT / BPF_MAP_TYPE_PERF_EVENT_ARRAY 사용법
13823정성태11/20/20241110개발 환경 구성: 734. Ubuntu에 docker, kubernetes (k3s) 설치
13822정성태11/20/20241079개발 환경 구성: 733. Windbg - VirtualBox VM의 커널 디버거 연결 시 COM 포트가 없는 경우
13821정성태11/18/20241238Linux: 108. Linux와 Windows의 프로세스/스레드 ID 관리 방식
13820정성태11/18/20241237VS.NET IDE: 195. Visual C++ - C# 프로젝트처럼 CopyToOutputDirectory 항목을 추가하는 방법
13819정성태11/15/20241270Linux: 107. eBPF - libbpf CO-RE의 CONFIG_DEBUG_INFO_BTF 빌드 여부에 대한 의존성
13818정성태11/15/20241382Windows: 272. Windows 11 24H2 - sudo 추가
13817정성태11/14/20241227Linux: 106. eBPF / bpf2go - (BPF_MAP_TYPE_HASH) Map을 이용한 전역 변수 구현
13816정성태11/14/20241269닷넷: 2312. C#, C++ - Windows / Linux 환경의 Thread Name 설정파일 다운로드1
13815정성태11/13/20241157Linux: 105. eBPF - bpf2go에서 전역 변수 설정 방법
13814정성태11/13/20241234닷넷: 2311. C# - Windows / Linux 환경에서 Native Thread ID 가져오기파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...