성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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'>C# - PKCS#8 PEM 파일을 이용한 RSA 개인키/공개키 설정 방법</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;' > openssl의 PKCS#1 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/10926'>https://www.sysnet.pe.kr/2/0/10926</a> C# - BouncyCastle을 사용한 암호화/복호화 예제 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12992'>https://www.sysnet.pe.kr/2/0/12992</a> </pre> <br /> 이제는 openssl의 내용이 달라져서 헤매는 분들이 계시는군요. ^^ 이참에 업데이트 차원에서 글을 남깁니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> openssl 도구를 이용하면 개인키를 담은 RSA PEM 파일을 다음과 같이 쉽게 생성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp> <span style='color: blue; font-weight: bold'>openssl genrsa 2048</span> -----<span style='color: blue; font-weight: bold'>BEGIN PRIVATE KEY</span>----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1IWcgsx34cMS8 K17VxHTQzpGRpIx4KTgrrJl2C14gh9PrbHCye9HipPwXhysLJ+PQLtWm9mx8jZC2 ...[생략]... Mon/3wDEyLMOftFNgaaAOOkMefhPABwQ1TG1lApa1mk3zGaTvbnksYbvHUohtedm 9MKZgdIakI4h9a74xwQv0+CE -----<span style='color: blue; font-weight: bold'>END PRIVATE KEY</span>----- </pre> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/12992'>예전 글</a>에서는 저 명령어의 결과로 "BEGIN RSA PRIVATE KEY" / "END RSA PRIVATE KEY" 쌍으로 나왔는데 이제는 "RSA"라는 문자열이 빠져 있습니다. 왜냐하면, 생성한 (base64 인코딩된) 데이터에 Key의 종류까지 포함하고 있기 때문입니다.<br /> <br /> 위와 같이 생성한 개인키는 이제 BouncyCastle을 이용해 다음과 같이 읽을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Install-Package <a target='tab' href='https://www.nuget.org/packages/BouncyCastle.Cryptography'>BouncyCastle.Cryptography</a> string privateKeyFilePath = "prvkey.pem"; Org.BouncyCastle.OpenSsl.PemReader pem = new Org.BouncyCastle.OpenSsl.PemReader(File.OpenText(privateKeyFilePath)); var key = pem.ReadObject() as Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters; </pre> <br /> RSA는 서로의 키로 암호화한 데이터를 복호화하는 것이 가능합니다. 가령 개인키로 암호화하면 공개키로 복호화할 수 있고, 공개키로 암호화하면 개인키로 복호화할 수 있습니다. 하지만 키의 배포 측면에서 현실적인 이유로 인해, 보통은 공개키로 암호화하고 개인키로 복호화합니다. (아울러, 개인키로는 서명하고, 공개키로는 서명을 검증합니다.)<br /> <br /> 또한, 개인키 자체에는 공개키 데이터도 포함돼 있기 때문에 어찌 보면 위에서 구한 RsaPrivateCrtKeyParameters로 암호화를 하면 될 것도 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string plainText = "Hello World"; byte[] encBuffer = EncryptData(key, plainText); private static byte[] EncryptData(ICipherParameters keyParam, string data) { var cipher = CipherUtilities.GetCipher("RSA/NONE/OAEPWITHSHA256ANDMGF1PADDING"); cipher.Init(<span style='color: blue; font-weight: bold'>true</span>, keyParam); // true == 암호화 byte[] plain = Encoding.UTF8.GetBytes(data); return cipher.DoFinal(plain); } </pre> <br /> 그런데, 저렇게 암호화한 데이터를 복호화하려고 하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Console.WriteLine(DecryptData(key, encBuffer)); private static string DecryptData(ICipherParameters keyParam, byte[] encrypted) { var cipher = CipherUtilities.GetCipher("RSA/NONE/OAEPWITHSHA256ANDMGF1PADDING"); cipher.Init(<span style='color: blue; font-weight: bold'>false</span>, keyParam); // false == 복호화 return Encoding.UTF8.GetString(cipher.DoFinal(encrypted)); } </pre> <br /> "Org.BouncyCastle.Crypto.InvalidCipherTextException: data wrong" 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unhandled exception. Org.BouncyCastle.Crypto.InvalidCipherTextException: data wrong at Org.BouncyCastle.Crypto.Encodings.OaepEncoding.DecodeBlock(Byte[] inBytes, Int32 inOff, Int32 inLen) in C:\Users\Peter\code\bc-csharp-release\crypto\src\crypto\encodings\OaepEncoding.cs:line 272 at Org.BouncyCastle.Crypto.Encodings.OaepEncoding.ProcessBlock(Byte[] inBytes, Int32 inOff, Int32 inLen) in C:\Users\Peter\code\bc-csharp-release\crypto\src\crypto\encodings\OaepEncoding.cs:line 124 at Org.BouncyCastle.Crypto.BufferedAsymmetricBlockCipher.DoFinal() in C:\Users\Peter\code\bc-csharp-release\crypto\src\crypto\BufferedAsymmetricBlockCipher.cs:line 152 at Org.BouncyCastle.Crypto.BufferedAsymmetricBlockCipher.DoFinal(Byte[] input, Int32 inOff, Int32 length) in C:\Users\Peter\code\bc-csharp-release\crypto\src\crypto\BufferedAsymmetricBlockCipher.cs:line 167 at Org.BouncyCastle.Crypto.BufferedCipherBase.DoFinal(Byte[] input) at ConsoleApp1.Program.DecryptData(ICipherParameters keyParam, Byte[] encrypted) at ConsoleApp1.Program.Main(String[] args) </pre> <br /> 아마도 키 자체가 RsaPrivateCrtKeyParameters 타입이기 때문에 암호화를 할 때 (공개키를 사용하지 않고) 개인키를 이용해 암호화를 한 것으로 보입니다. 그래서 복호화 할 때도 마찬가지로 개인키를 사용하기 때문에 "data wrong" 오류가 발생하는 것인데요, 실제로 이에 대한 확인을 복호화할 때 공개키를 명시적으로 지정해 보면 됩니다.<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;' > c:\temp> <span style='color: blue; font-weight: bold'>openssl rsa -pubout -in prvkey.pem -out pubkey.pem</span> writing RSA key </pre> <br /> 다음과 같이 테스트할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { string privateKeyFilePath = "prvkey.pem"; string publicKeyFilePath = "pubkey.pem"; Org.BouncyCastle.OpenSsl.PemReader pem = new Org.BouncyCastle.OpenSsl.PemReader(File.OpenText(privateKeyFilePath)); var key = pem.ReadObject() as Org.BouncyCastle.Crypto.Parameters.<span style='color: blue; font-weight: bold'>RsaPrivateCrtKeyParameters</span>; if (key == null) { Console.WriteLine("Private Key is null"); return; } string plainText = "Hello World"; byte[] encBuffer = EncryptData(key, plainText); Console.WriteLine(Convert.ToBase64String(encBuffer)); pem = new Org.BouncyCastle.OpenSsl.PemReader(File.OpenText(publicKeyFilePath)); var pubkey = pem.ReadObject() as Org.BouncyCastle.Crypto.Parameters.<span style='color: blue; font-weight: bold'>RsaKeyParameters</span>; if (pubkey == null) { Console.WriteLine("Public Key is null"); return; } Console.WriteLine(DecryptData(pubkey, encBuffer)); } </pre> <br /> 코드는 보는 바와 같이 개인키로 암호화가 된 것을 공개키로 복호화하기 때문에 잘 동작합니다. 물론, 원래대로 아래와 같이 공개키를 이용해 암호화하고 개인키를 이용해 복호화하도록 코딩해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { string privateKeyFilePath = "prvkey.pem"; string publicKeyFilePath = "pubkey.pem"; Org.BouncyCastle.OpenSsl.PemReader pem = new Org.BouncyCastle.OpenSsl.PemReader(File.OpenText(<span style='color: blue; font-weight: bold'>publicKeyFilePath</span>)); var key = pem.ReadObject() as Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters; if (key == null) { Console.WriteLine("Public Key is null"); return; } string plainText = "Hello World"; byte[] encBuffer = <span style='color: blue; font-weight: bold'>EncryptData</span>(key, plainText); Console.WriteLine(Convert.ToBase64String(encBuffer)); pem = new Org.BouncyCastle.OpenSsl.PemReader(File.OpenText(<span style='color: blue; font-weight: bold'>privateKeyFilePath</span>)); var pubkey = pem.ReadObject() as Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters; if (pubkey == null) { Console.WriteLine("Private Key is null"); return; } Console.WriteLine(<span style='color: blue; font-weight: bold'>DecryptData</span>(pubkey, encBuffer)); } </pre> <br /> 참고로, 여전히 "RSA" 문자열이 있는 PKCS#1 포맷으로 생성하고 싶다면 -traditional 옵션을 적용하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp> <span style='color: blue; font-weight: bold'>openssl genrsa -traditional 2048</span> -----BEGIN <span style='color: blue; font-weight: bold'>RSA</span> PRIVATE KEY----- MIIEowIBAAKCAQEAv0t1fMeb6iIcgDAPXI/HD4XruSkCMAoNMSEGVN4OElF0oU4i CIKpf91/ukoIC73AZwOFyx6gDTx6KErIYR1aN3JwcXkBTlk9r/K+8jME21c5SQXf ...[생략]... ZsZ6SRYcsXZO9Lw08lnQuNbJaxYyVIORxZf/rzVmFuPiGTbjqwdD6FF5ks0zHYtI 5iClwv97z+UVmw/mzHkM0gqhinMYhHvY25Fr5i/hT9SNoLTwK4PO -----END <span style='color: blue; font-weight: bold'>RSA</span> PRIVATE KEY----- </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 사실 이제 더 이상 BouncyCastle을 사용할 필요가 없습니다. 왜냐하면, .NET 5부터 PEM 파일에 대한 지원이 추가되었기 때문입니다.<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 read a PEM RSA private key from .NET ; <a target='tab' href='https://stackoverflow.com/questions/243646/how-to-read-a-pem-rsa-private-key-from-net'>https://stackoverflow.com/questions/243646/how-to-read-a-pem-rsa-private-key-from-net</a> </pre> <br /> 그래서 다음과 같이 바로 처리할 수 있습니다.<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.IO; using System.Security.Cryptography; using System.Text; internal class Program { static void Main(string[] args) { string privateKeyFilePath = "prvkey.pem"; string publicKeyFilePath = "pubkey.pem"; byte[] encBuffer = null; { var rsa = RSA.Create(); string text = File.ReadAllText(publicKeyFilePath); rsa.<span style='color: blue; font-weight: bold'>ImportFromPem</span>(text.ToCharArray()); string plainText = "Hello World"; byte[] buffer = Encoding.UTF8.GetBytes(plainText); encBuffer = rsa.Encrypt(buffer, RSAEncryptionPadding.OaepSHA256); } { var rsa = RSA.Create(); string text = File.ReadAllText(privateKeyFilePath); rsa.<span style='color: blue; font-weight: bold'>ImportFromPem</span>(text.ToCharArray()); byte[] buffer = rsa.Decrypt(encBuffer, RSAEncryptionPadding.OaepSHA256); Console.WriteLine(Encoding.UTF8.GetString(buffer)); } } } </pre> <br /> 개인키/공개키 상관없이 일괄적으로 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsa.importfrompem'>ImportFromPem</a> 메서드를 호출하면 끝입니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2016&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6461
(왼쪽의 숫자를 입력해야 합니다.)