인디노트

공인인증서에서 개인키 획득하기 (using C#) 본문

인증기술

공인인증서에서 개인키 획득하기 (using C#)

인디개발자 2018. 6. 18. 17:28

최근 전자세금계산서 관련하여 프로젝트를 진행하면서 모르는 것이 너무 많아 매일 구글과 함께 지내고 있습니다.
(곧, 이 프로젝트에서 손을 떼게 되겠지만,, ^^;;)
오늘은 이 프로젝트를 진행하던 중에 새롭게 알게 된 지식을 포스팅 해볼까 합니다.

전자세금계산서 프로젝트 중 전자서명 모듈을 개발함에 있어서 가장 힘들었던 부분이 공인인증서에서 개인키를 가져오는 부분이었습니다.

"전자서명이 무엇인가?" 에 대한 답은 아래의 링크를 참조하시기 바랍니다.
http://blog.naver.com/citsoft?Redirect=Log&logNo=150000376240

위 주소와 연결 된 포스트를 읽어보시면 알겠지만, 전자서명을 하기 위해서는 송신자의 개인키(private key) 가 있어야 합니다.
즉, 전자세금계산서를 보낼 때는 전자서명을 해야되고, 이때 전자세금계산서를 보내는 사업자의 공인인증서에 있는 개인키가 필요하다는 말입니다.
그런데, 이 개인키를 획득하기가 쉽지 않다는거죠 ^^;;

대한민국에서 쓰는 공인인증서 파일은 다음과 같이 세가지 형식이 있습니다.
*.der, *.pfx, *.pem

여기서 "pfx" 확장자를 가지는 공인인증서 파일은 내부에 개인키 정보를 함께 가지고 있기 때문에, 이 파일의 경우엔
C#의 X509Certificate2 클래스를 사용하면 별 무리없이 개인키를 획득할 수 있습니다.

하지만, "der" 확장자를 가진 공인인증서는 "*.key" 형태의 개인키 파일이 따로 존재합니다.
("pem" 확장자를 가진 공인인증서는 아직 보질 못해서 확실하지 않습니다 ^^;;)

그래서, 만약, "der" 확장자를 가진 공인인증서를 사용하기 위해선 "*.key" 파일에서 개인키를 얻어와야 되는 것입니다.
그리고, 이 파일은 암호화된 형태로 개인키를 저장하고 있기 때문에 복호화해서 구해와야 합니다. (약간 복잡하죠,,ㅎ)
서론이 길었습니다. 지금부터, 이 개인키 파일에서 전자서명에 필요한 개인키를 획득하도록 해 보겠습니다.

국내에서 개인키 파일을 저장할 때 PKCS #8 표준을 따르고 있으며, 그 중에서 Encrypted private-key information syntax 를 따르고 있습니다.
(다음 url을 참조하세요~ http://tools.ietf.org/html/draft-kaliski-pkcs8-00)
그래서 개인키 파일("*.key")을 이 형태대로 읽어야 하는데, 다행히 오픈소스 프로젝트인 Mono에서 제공하는 라이브러리를 사용하면 쉽게 읽을 수 있습니다. (Mono 프로젝트 다운로드 : http://www.go-mono.com/mono-downloads/download.html)

using System;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Mono.Security.Cryptography;                             // Mono 프로젝트 라이브러리

class CertSelect
{
    static void Main()
    {
        byte[] byteKey = File.ReadAllBytes("개인키 파일명");
        PKCS8.EncryptedPrivateKeyInfo encriptedPKInfo = new PKCS8.EncryptedPrivateKeyInfo(byteKey);
 

개인키 파일은 "Key Generation with SHA1 and Encryption with SEED CBC mode" 알고리즘에 의해 생성되며, 자세한 사항은 한국정보보호진흥원(http://www.rootca.or.kr/)의 암호 알고리즘 규격(KCAC.TS.ENC)에서 확인할 수 있습니다.

이러한 개인키는 패스워드 기반의 키 암호화 기법을 사용하여 암호화해서 저장되는데 이때 사용되는 키 암호화 기법이 PBES(Password-Based Encryption Scheme)이며, 개인키를 암호화 할때 사용되는 키를 가공하는 함수는 PBKDF (Password-Based Key Derivation Function) 입니다.

따라서, 우선, 이 PBKDF 함수를 사용하여 개인키를 암호화 할때 사용했던 키와 초기벡터를 구해야 합니다.
C#의 PasswordDeriveBytes 클래스를 사용하면 암호에서 키를 파생시킬 수 있으며, 키를 파생시킬 때 해쉬 알고리즘은 SHA1을 사용합니다.

byte[] byteDK = new byte[20];
// 암호는 각 공인인증서의 암호를 사용하여야 합니다.
PasswordDeriveBytes PBKDF1 = new PasswordDeriveBytes("암호", encriptedPKInfo.Salt, "SHA1", encriptedPKInfo.IterationCount);
byteDK = PBKDF1.GetBytes(20);


이렇게 파생 된 값을 이용하여 키와 초기 벡터를 구합니다.
키와 초기 벡터를 구하기 위해선 위에서 언급했던 "Key Generation with SHA1 and Encryption with SEED CBC mode" 알고리즘 규격을 알고 있어야 하지만 사실 이해하기 어렵죠 ^^;;
간단히 얘기를 하면 위에서 구했던 byteDK의 값에서 앞의 16바이트는 키 값이 되고, 
초기 벡터는 뒤의 4바이트를 해쉬 알고리즘을 사용해 나온 20바이트 중 앞 16바이트가 됩니다. 약간 말이 이상하지만 다음 코드를 보면 이해하실 수 있을겁니다. ㅎ

byte[] byteK = new byte[16];                    // K 값
byte[] byteIV = new byte[16];                   // 초기 벡터 값
byte[] byteDIV = new byte[20];                  // SHA1으로 해쉬한 값
byte[] byte4Temp = new byte[4];                 // IV 값
        
System.Array.Copy(byteDK, 0, byteK, 0, 16);        // 앞 16자리는 K값
System.Array.Copy(byteDK, 16, byte4Temp, 0, 4);    // 뒷 4자리는 초기 벡터를 구하기 위한 임시 값

SHA1 sha1 = SHA1.Create();
byteDIV = sha1.ComputeHash(byte4Temp);
System.Array.Copy(byteDIV, 0, byteIV, 0, 16);     
 // SHA1으로 해쉬한 값의 앞 16자리 값을 초기 벡터(IV)로 한다.

이렇게 해서 개인키를 암호화할 때 사용되었던 키값과 초기벡터를 구하였습니다. 이제 암호화 되어있는 개인키를 복호화 하는 일만 남았습니다.
대한민국에서는 개인키를 암호화할 때 SEED 알고리즘을 사용하는데, 이 알고리즘은 국내에서 개발한 블록 암호화 알고리즘입니다.
국내에서 개발한 알고리즘이라,, 닷넷에는 관련된 라이브러리가 없습니다 ㅡㅠ

하지만, 누군가가 친절하게도 C#으로 이 알고리즘을 구현해놓은 소스가 데브피아에 있습니다. 정말 고마운 일이죠,, ^^
이 소스를 이용해서 암호화 된 개인키를 복호화 합니다.
복호화 하여 나온 byte 배열을 Mono.Security.Cryptography.PKCS8.PrivateKeyInfo 클래스를 사용하여 불러오고
이 클래스의 DecodeRSA 메서드를 사용하면 닷넷에서 전자서명에 사용할 수 있는 RSA 타입으로 변환할 수 있습니다.

byte[] decryptedKey = SeedCs.SEED.Decrypt(encriptedPKInfo.EncryptedData, byteK, true, byteIV);   // SEED 알고리즘을 이용하여 복호화한다.
PKCS8.PrivateKeyInfo pKey = new PKCS8.PrivateKeyInfo(decryptedKey);
RSA rsa = PKCS8.PrivateKeyInfo.DecodeRSA(pKey.PrivateKey);                       
// RSA 타입으로 변환

자~! 이제는 이 RSA 타입의 인스턴스를 이용하여 자유롭게 전자서명 또는 암호화를 수행할 수 있습니다.

개인키를 획득하는 것이 너무 복잡하여 몇일을 이것 때문에 삽질했던 기억이 나네요.
혹시, 저와 같이 고생하시는 분이 계실 것 같아 이 내용을 포스팅하게 되었습니다.
전자세금계산서 관련 개발을 하는 개발자분들 또는 공인인증서를 이용하는 프로젝트 개발자분들에게 많은 도움이 되었으면 합니다~ ^^

참조 : FLY32.NET (http://fly32.net/447)


반응형
Comments