성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Reordering on an Alpha processor ;...
[정성태] 공유 감사합니다. ^^ 참고로, WPF에서 WindowsF...
[Tom Lee] 답변 감사합니다. 나름의 해결책 연구해보고 여기에도 공유해봅니다...
[정성태] 아래의 글을 보면, MoveWindow 하면 될 듯한데요. ^^...
[Tom Lee] 안녕하세요 올려주신 글 참고하여 WPF 어플리케이션 안에 Uni...
[정성태] A graphical depiction of the steps ...
[정성태] 질문을 주셔서 출판사 측에 문의를 했습니다. 약 한 달 정도 후...
[Thorondor
] @정성태 개인 블로그인데도 거의 커뮤니티 급 인 것 같아요. 요...
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
글쓰기
제목
이름
암호
전자우편
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'>openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 (2)</h1> <p> 예전에 소개한 글에서 BouncyCastle을 이용한 방법을 다뤘는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/10926'>https://www.sysnet.pe.kr/2/0/10926</a> </pre> <br /> 순수하게 key file을 분석한 소스 코드가 있어 소개합니다. ^^<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 load the RSA public key from file in C# ; <a target='tab' href='https://stackoverflow.com/questions/11506891/how-to-load-the-rsa-public-key-from-file-in-c-sharp/32243171#32243171'>https://stackoverflow.com/questions/11506891/how-to-load-the-rsa-public-key-from-file-in-c-sharp/32243171#32243171</a> </pre> <br /> 위의 덧글에 보면, BEGIN/END PUBLIC KEY, BEGIN/END PRIVATE KEY, BEGIN/END ENCRYPTED PRIVATE KEY에 대한 RSACryptoServiceProvider 변환을 해주는 소스 코드를 담고 있습니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Decoding an SSH Key from PEM to BASE64 to HEX to ASN.1 to prime decimal numbers // ; <a target='tab' href='http://feeds.hanselman.com/~/566255904/0/scotthanselman~Decoding-an-SSH-Key-from-PEM-to-BASE-to-HEX-to-ASN-to-prime-decimal-numbers.aspx'>http://feeds.hanselman.com/~/566255904/0/scotthanselman~Decoding-an-SSH-Key-from-PEM-to-BASE-to-HEX-to-ASN-to-prime-decimal-numbers.aspx</a> using System; using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography; using System.Text; public class PemKeyUtils { const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----"; const String pemprivfooter = "-----END RSA PRIVATE KEY-----"; const String pempubheader = "-----BEGIN PUBLIC KEY-----"; const String pempubfooter = "-----END PUBLIC KEY-----"; const String pemp8header = "-----BEGIN PRIVATE KEY-----"; const String pemp8footer = "-----END PRIVATE KEY-----"; const String pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; const String pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----"; static bool verbose = false; public static RSACryptoServiceProvider GetRSAProviderFromPemFile(String pemfile) { bool isPrivateKeyFile = true; string pemstr = File.ReadAllText(pemfile).Trim(); if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter)) { isPrivateKeyFile = false; } return GetRSAProviderFromPem(pemfile, isPrivateKeyFile); } public static RSACryptoServiceProvider GetRSAProviderFromPem(String pemText, bool isPrivate) { byte[] pemkey; if (isPrivate) pemkey = DecodeOpenSSLPrivateKey(pemText); else pemkey = DecodeOpenSSLPublicKey(pemText); if (pemkey == null) return null; if (isPrivate) return DecodeRSAPrivateKey(pemkey); else return DecodeX509PublicKey(pemkey); } //------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider --- static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey) { byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; // --------- Set up stream to decode the asn.1 encoded RSA private key ------ MemoryStream mem = new MemoryStream(privkey); BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading byte bt = 0; ushort twobytes = 0; int elems = 0; try { twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; twobytes = binr.ReadUInt16(); if (twobytes != 0x0102) //version number return null; bt = binr.ReadByte(); if (bt != 0x00) return null; //------ all private key components are Integer sequences ---- elems = GetIntegerSize(binr); MODULUS = binr.ReadBytes(elems); elems = GetIntegerSize(binr); E = binr.ReadBytes(elems); elems = GetIntegerSize(binr); D = binr.ReadBytes(elems); elems = GetIntegerSize(binr); P = binr.ReadBytes(elems); elems = GetIntegerSize(binr); Q = binr.ReadBytes(elems); elems = GetIntegerSize(binr); DP = binr.ReadBytes(elems); elems = GetIntegerSize(binr); DQ = binr.ReadBytes(elems); elems = GetIntegerSize(binr); IQ = binr.ReadBytes(elems); if (verbose) { Console.WriteLine("showing components .."); showBytes("\nModulus", MODULUS); showBytes("\nExponent", E); showBytes("\nD", D); showBytes("\nP", P); showBytes("\nQ", Q); showBytes("\nDP", DP); showBytes("\nDQ", DQ); showBytes("\nIQ", IQ); } // ------- create RSACryptoServiceProvider instance and initialize with public key ----- RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); RSAParameters RSAparams = new RSAParameters(); RSAparams.Modulus = MODULUS; RSAparams.Exponent = E; RSAparams.D = D; RSAparams.P = P; RSAparams.Q = Q; RSAparams.DP = DP; RSAparams.DQ = DQ; RSAparams.InverseQ = IQ; RSA.ImportParameters(RSAparams); return RSA; } catch (Exception) { return null; } finally { binr.Close(); } } //-------- Get the binary RSA PUBLIC key -------- static byte[] DecodeOpenSSLPublicKey(String instr) { const String pempubheader = "-----BEGIN PUBLIC KEY-----"; const String pempubfooter = "-----END PUBLIC KEY-----"; String pemstr = instr.Trim(); byte[] binkey; if (!pemstr.StartsWith(pempubheader) || !pemstr.EndsWith(pempubfooter)) return null; StringBuilder sb = new StringBuilder(pemstr); sb.Replace(pempubheader, ""); //remove headers/footers, if present sb.Replace(pempubfooter, ""); String pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace try { binkey = Convert.FromBase64String(pubstr); } catch (System.FormatException) { //if can't b64 decode, data is not valid return null; } return binkey; } //----- Get the binary RSA PRIVATE key, decrypting if necessary ---- static byte[] DecodeOpenSSLPrivateKey(String instr) { const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----"; const String pemprivfooter = "-----END RSA PRIVATE KEY-----"; String pemstr = instr.Trim(); byte[] binkey; if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter)) return null; StringBuilder sb = new StringBuilder(pemstr); sb.Replace(pemprivheader, ""); //remove headers/footers, if present sb.Replace(pemprivfooter, ""); String pvkstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace try { // if there are no PEM encryption info lines, this is an UNencrypted PEM private key binkey = Convert.FromBase64String(pvkstr); return binkey; } catch (System.FormatException) { //if can't b64 decode, it must be an encrypted private key //Console.WriteLine("Not an unencrypted OpenSSL PEM private key"); } StringReader str = new StringReader(pvkstr); //-------- read PEM encryption info. lines and extract salt ----- if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED")) return null; String saltline = str.ReadLine(); if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,")) return null; String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim(); byte[] salt = new byte[saltstr.Length / 2]; for (int i = 0; i < salt.Length; i++) salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16); if (!(str.ReadLine() == "")) return null; //------ remaining b64 data is encrypted RSA key ---- String encryptedstr = str.ReadToEnd(); try { //should have b64 encrypted RSA key now binkey = Convert.FromBase64String(encryptedstr); } catch (System.FormatException) { // bad b64 data. return null; } //------ Get the 3DES 24 byte key using PDK used by OpenSSL ---- SecureString despswd = GetSecPswd("Enter password to derive 3DES key==>"); //Console.Write("\nEnter password to derive 3DES key: "); //String pswd = Console.ReadLine(); byte[] deskey = GetOpenSSL3deskey(salt, despswd, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes if (deskey == null) return null; //showBytes("3DES key", deskey) ; //------ Decrypt the encrypted 3des-encrypted RSA private key ------ byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV if (rsakey != null) return rsakey; //we have a decrypted RSA private key else { Console.WriteLine("Failed to decrypt RSA private key; probably wrong password."); return null; } } static SecureString GetSecPswd(String prompt) { SecureString password = new SecureString(); Console.ForegroundColor = ConsoleColor.Gray; Console.Write(prompt); Console.ForegroundColor = ConsoleColor.Magenta; while (true) { ConsoleKeyInfo cki = Console.ReadKey(true); if (cki.Key == ConsoleKey.Enter) { Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine(); return password; } else if (cki.Key == ConsoleKey.Backspace) { // remove the last asterisk from the screen... if (password.Length > 0) { Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); Console.Write(" "); Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); password.RemoveAt(password.Length - 1); } } else if (cki.Key == ConsoleKey.Escape) { Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine(); return password; } else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar)) { if (password.Length < 20) { password.AppendChar(cki.KeyChar); Console.Write("*"); } else { Console.Beep(); } } else { Console.Beep(); } } } static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV) { MemoryStream memst = new MemoryStream(); TripleDES alg = TripleDES.Create(); alg.Key = desKey; alg.IV = IV; try { CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write); cs.Write(cipherData, 0, cipherData.Length); cs.Close(); } catch (Exception exc) { Console.WriteLine(exc.Message); return null; } byte[] decryptedData = memst.ToArray(); return decryptedData; } //----- OpenSSL PBKD uses only one hash cycle (count); miter is number of iterations required to build sufficient bytes --- static byte[] GetOpenSSL3deskey(byte[] salt, SecureString secpswd, int count, int miter) { IntPtr unmanagedPswd = IntPtr.Zero; int HASHLENGTH = 16; //MD5 bytes byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store contatenated Mi hashed results byte[] psbytes = new byte[secpswd.Length]; unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd); Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length); Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd); //UTF8Encoding utf8 = new UTF8Encoding(); //byte[] psbytes = utf8.GetBytes(pswd); // --- contatenate salt and pswd bytes into fixed data array --- byte[] data00 = new byte[psbytes.Length + salt.Length]; Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes // ---- do multi-hashing and contatenate results D1, D2 ... into keymaterial bytes ---- MD5 md5 = new MD5CryptoServiceProvider(); byte[] result = null; byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget for (int j = 0; j < miter; j++) { // ---- Now hash consecutively for count times ------ if (j == 0) result = data00; //initialize else { Array.Copy(result, hashtarget, result.Length); Array.Copy(data00, 0, hashtarget, result.Length, data00.Length); result = hashtarget; //Console.WriteLine("Updated new initial hash target:") ; //showBytes(result) ; } for (int i = 0; i < count; i++) result = md5.ComputeHash(result); Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //contatenate to keymaterial } //showBytes("Final key material", keymaterial); byte[] deskey = new byte[24]; Array.Copy(keymaterial, deskey, deskey.Length); Array.Clear(psbytes, 0, psbytes.Length); Array.Clear(data00, 0, data00.Length); Array.Clear(result, 0, result.Length); Array.Clear(hashtarget, 0, hashtarget.Length); Array.Clear(keymaterial, 0, keymaterial.Length); return deskey; } public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key) { // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1" byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; byte[] seq = new byte[15]; // --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------ MemoryStream mem = new MemoryStream(x509key); BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading byte bt = 0; ushort twobytes = 0; try { twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; seq = binr.ReadBytes(15); //read the Sequence OID if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct return null; twobytes = binr.ReadUInt16(); if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8203) binr.ReadInt16(); //advance 2 bytes else return null; bt = binr.ReadByte(); if (bt != 0x00) //expect null byte next return null; twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; twobytes = binr.ReadUInt16(); byte lowbyte = 0x00; byte highbyte = 0x00; if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81) lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus else if (twobytes == 0x8202) { highbyte = binr.ReadByte(); //advance 2 bytes lowbyte = binr.ReadByte(); } else return null; byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order int modsize = BitConverter.ToInt32(modint, 0); byte firstbyte = binr.ReadByte(); binr.BaseStream.Seek(-1, SeekOrigin.Current); if (firstbyte == 0x00) { //if first byte (highest order) of modulus is zero, don't include it binr.ReadByte(); //skip this null byte modsize -= 1; //reduce modulus buffer size by 1 } byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data return null; int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values) byte[] exponent = binr.ReadBytes(expbytes); // ------- create RSACryptoServiceProvider instance and initialize with public key ----- RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); RSAParameters RSAKeyInfo = new RSAParameters(); RSAKeyInfo.Modulus = modulus; RSAKeyInfo.Exponent = exponent; RSA.ImportParameters(RSAKeyInfo); return RSA; } catch (Exception) { return null; } finally { binr.Close(); } } static void showBytes(String info, byte[] data) { Console.WriteLine("{0} [{1} bytes]", info, data.Length); for (int i = 1; i <= data.Length; i++) { Console.Write("{0:X2} ", data[i - 1]); if (i % 16 == 0) Console.WriteLine(); } Console.WriteLine("\n\n"); } private static int GetIntegerSize(BinaryReader binr) { byte bt = 0; byte lowbyte = 0x00; byte highbyte = 0x00; int count = 0; bt = binr.ReadByte(); if (bt != 0x02) //expect integer return 0; bt = binr.ReadByte(); if (bt == 0x81) count = binr.ReadByte(); // data size in next byte else if (bt == 0x82) { highbyte = binr.ReadByte(); // data size in next 2 bytes lowbyte = binr.ReadByte(); byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; count = BitConverter.ToInt32(modint, 0); } else { count = bt; // we already have the data size } while (binr.ReadByte() == 0x00) { //remove high order zeros in data count -= 1; } binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte return count; } static bool CompareBytearrays(byte[] a, byte[] b) { if (a.Length != b.Length) return false; int i = 0; foreach (byte c in a) { if (c != b[i]) return false; i++; } return true; } } </pre> <br /> 위의 소스 코드를 활용하면 BouncyCastle 의존성 없이 이런 식으로 RSACryptoServiceProvider를 사용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Program { static void Main(string[] args) { string userKey = @"-----BEGIN RSA PRIVATE KEY----- MIIJKgIBAAKCAgEAz849b+b4pKd2Gk34NOCPF+zuO9jG6E+pL+ZXzUFAXJfh1fGB ...[생략]... 1JzXJK0bWJ4p81eTEq6SFrmZVX0ZR/5M6wQ/x2WsKszZQ/OIoLO9mCKrDnsd/w== -----END RSA PRIVATE KEY-----"; <span style='color: blue; font-weight: bold'>RSACryptoServiceProvider rsa = PemKeyUtils.GetRSAProviderFromPem(userKey, isPrivate: true);</span> // 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)); /* 출력 결과 Decrypted plaintext: Data to Encrypt */ } static public byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding) { // ...[생략]... (참고: <a target='tab' href='https://www.sysnet.pe.kr/2/0/10926#rsa_enc_dec'>https://www.sysnet.pe.kr/2/0/10926#rsa_enc_dec</a>) } static public byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding) { // ...[생략]... (참고: <a target='tab' href='https://www.sysnet.pe.kr/2/0/10926#rsa_enc_dec'>https://www.sysnet.pe.kr/2/0/10926#rsa_enc_dec</a>) } } </pre> <br /> 해본 김에, Kubernetes의 kubeconfig 파일과도 이야기를 엮어볼까요? ^^<br /> <br /> 즉, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12584#kubeconfig_path'>k8s 설치 시 kubeconfig 파일에 담긴 client-certificate-data 개인키</a> 문자열로부터 RSACryptoServiceProvider를 초기화하는 코드를 다음과 같이 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using k8s; // Install-Package KubernetesClient string path = Environment.ExpandEnvironmentVariables(@"%USERPROFILE%\.kube\config"); KubernetesClientConfiguration config = <a target='tab' href='https://www.sysnet.pe.kr/2/0/12594'>KubernetesClientConfiguration.BuildConfigFromConfigFile</a>(path); string userKey = Encoding.UTF8.GetString(Convert.FromBase64String(config.ClientCertificateKeyData)); RSACryptoServiceProvider rsa = PemKeyUtils.GetRSAProviderFromPem(userKey, true); </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1743&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
9236
(왼쪽의 숫자를 입력해야 합니다.)