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

openssl의 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




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/14/2021]

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

비밀번호

댓글 작성자
 



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

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

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

(단지, 본문의 예제는 그것들을 별도의 프로젝트로 분리하지 않은 것입니다.)
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13064정성태5/18/202252.NET Framework: 2016. C# - JIT 컴파일러의 인라인 메서드 처리 유무
13063정성태5/18/202287.NET Framework: 2015. C# - 인라인 메서드(inline methods)
13062정성태5/17/2022218.NET Framework: 2014. C# - async/await 그리고 스레드 (4) 비동기 I/O 재현파일 다운로드1
13061정성태5/16/202271.NET Framework: 2013. C# - FILE_FLAG_OVERLAPPED가 적용된 파일의 읽기/쓰기 시 Position 관리파일 다운로드1
13060정성태5/15/2022147.NET Framework: 2012. C# - async/await 그리고 스레드 (3) Task.Delay 재현파일 다운로드1
13059정성태5/14/2022145.NET Framework: 2011. C# - CLR ThreadPool의 I/O 스레드에 작업을 맡기는 방법파일 다운로드1
13058정성태5/13/202289.NET Framework: 2010. C# - ThreadPool.SetMaxThreads 사용법
13057정성태5/12/202254오류 유형: 812. 파이썬 - ImportError: cannot import name ...
13056정성태5/12/2022256.NET Framework: 2009. C# - async/await 그리고 스레드 (2) MyTask의 호출 흐름 [2]파일 다운로드1
13055정성태5/11/2022358.NET Framework: 2008. C# - async/await 그리고 스레드 (1) MyTask로 재현 [4]파일 다운로드1
13054정성태5/11/2022224.NET Framework: 2007. C# - 10진수 숫자를 담은 문자열을 숫자로 변환하는 방법 [9]파일 다운로드1
13053정성태5/10/2022103.NET Framework: 2006. C# - GC.KeepAlive 메서드의 역할
13052정성태5/9/2022174.NET Framework: 2005. C# - 생성한 참조 개체가 언제 GC의 정리 대상이 될까요?
13051정성태5/8/202298.NET Framework: 2004. C# XingAPI - ACF 검색 결과로 구한 CSV 파일을 통해 퀀트 종목 찾기파일 다운로드1
13050정성태5/6/202295.NET Framework: 2003. C# - COM 개체의 이벤트 핸들러에서 발생하는 예외에 대한 CLR의 특별 대우파일 다운로드1
13049정성태5/6/202248오류 유형: 811. GoLand - Error: Cannot find package
13048정성태5/6/202257오류 유형: 810. "ASUS TUF GAMING B550M-PLUS (WI-FI)" 모델에서 블루투스 장치가 인식이 안 되는 문제
13047정성태5/6/202246오류 유형: 809. Speech Recognition could not start
13046정성태5/5/202297.NET Framework: 2002. C# XingAPI - ACF 파일을 이용한 퀀트 종목 찾기(t1857)
13045정성태5/5/202290.NET Framework: 2001. C# XingAPI - 주식 종목에 따른 PBR, PER, ROE 구하는 방법(t3341 예제)
13044정성태5/4/202253오류 유형: 808. error : clang++ exited with code 127
13043정성태5/3/202261오류 유형: 807. C# - 닷넷 응용 프로그램에서 Informix DB 사용 시 오류 메시지 정리
13042정성태5/3/2022142.NET Framework: 2000. C# - 닷넷 응용 프로그램에서 Informix DB 사용 방법파일 다운로드1
13041정성태4/28/202283개발 환경 구성: 642. Informix 데이터베이스 docker 환경 구성
13040정성태4/27/202286VC++: 156. 비주얼 스튜디오 - Linux C/C++ 프로젝트에서 openssl 링크하는 방법
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...