이번에는 앞 SEED 소스를 이용해서 실제 공인 인증서를 읽는 부분과 기타 암호화/복호화에 사용하는 방법을 예제로 올려본다. 아래에 사용된 기본 코드는 모노 프로젝트의 PKCS12 코드를 기반으로 했지만 모노에서 사용하는 비밀키, 초기화 벡터 생성 방식이 기존 다른 알고리즘들과는 좀 다른, 이상한 부분이 있어 SEED 알고리즘을 기존 코드 내에 추가, 확장하는 데는 실패했고, 결국 그냥 따로 만들었다.



모노 프로젝트는 아래에서 받을 수 있다.


>> 참고: http://www.go-mono.com/mono-downloads/download.html



아마도 아래 소스 코드에서 모노의 PKCS12를 확장하려고 했던, PKCS12 상속 설정된 부분을 제거해도 동작하는 데는 크게 상관 없을 듯. 물론 관련 의존 코드들은 다 삭제해야 하겠지만.



[nPKCS12.cs]

using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; namespace PnPeople.Security { public class nPKCS12 : PKCS12 { public const string pbeWithSHAAndSEEDCBC = "1.2.410.200004.1.15"; public nPKCS12() { } /// <summary> /// SEED/CBC에서 Password는 입력 문자열을 그대로 사용한다 /// </summary> public string Password { set { if (!string.IsNullOrEmpty(value)) { int size = value.Length; if (size > MaximumPasswordLength) size = MaximumPasswordLength; _password = new byte[size]; Encoding.Default.GetBytes(value, 0, size, _password, 0); } else { // no password _password = null; } } } // Key, IV를 각각 64바이트로 만들어서 합친다.... 이상한 로직: 사용하면 안될 듯 private SEED GetSymmetricAlgorithm(string algorithmOid, byte[] salt, int iterationCount) { int keyLength = 16; // 128 bits (default) int ivLength = 16; // 128 bits (default) DeriveBytes pd = new DeriveBytes(); pd.Password = base._password; pd.Salt = salt; pd.IterationCount = iterationCount; switch (algorithmOid) { case pbeWithSHAAndSEEDCBC: // no unit test available pd.HashName = "SHA"; break; } SEED seed = new SEED(); seed.KeyBytes = pd.DeriveKey(keyLength); // IV required only for block ciphers (not stream ciphers) if (ivLength > 0) { seed.IV = pd.DeriveIV(ivLength); seed.ModType = SEED.MODE.AI_CBC; // CBC } return seed; } private SEED GetSymmetricAlgorithm(byte[] salt, int iterationCount) { // Rfc2898DeriveBytes 사용: 에러남 //Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(_password, salt, iterationCount); // PBKDF2 //byte[] derivedKey = pdb.GetBytes(20);


PasswordDeriveBytes pdb = new PasswordDeriveBytes(_password, salt, "SHA1", iterationCount); // PBKDF1 byte[] derivedKey = pdb.GetBytes(20); SEED seed = new SEED(); seed.KeyBytes = getKey(derivedKey); seed.IV = getIV(derivedKey); seed.ModType = SEED.MODE.AI_CBC; // CBC return seed; } private byte[] getKey(byte[] derivedKey) { byte[] key = new byte[16]; Buffer.BlockCopy(derivedKey, 0, key, 0, 16); return key; } private byte[] getIV(byte[] derivedKey) { byte[] iv = new byte[16]; byte[] ivTemp = new byte[4]; SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); Buffer.BlockCopy(derivedKey, 16, ivTemp, 0, 4); byte[] derivedIV = sha1.ComputeHash(ivTemp); Buffer.BlockCopy(derivedIV, 0, iv, 0, 16); return iv; } public new byte[] Decrypt(string algorithmOid, byte[] salt, int iterationCount, byte[] encryptedData) { if (algorithmOid != pbeWithSHAAndSEEDCBC) return null; // Only for SHA1/SEED/CBC // Mono DeriveBytes 사용: 에러남 //SEED seed1 = GetSymmetricAlgorithm(algorithmOid, salt, iterationCount); //byte[] result1 = seed1.Decrypt(encryptedData); SEED seed = GetSymmetricAlgorithm(salt, iterationCount); byte[] result = seed.Decrypt(encryptedData); // CFB 테스트 seed.ModType = SEED.MODE.AI_CFB; byte[] enc = seed.Encrypt(result2); byte[] dec = seed.Decrypt(enc); // ECB 테스트 seed.ModType = SEED.MODE.AI_ECB; enc = seed.Encrypt(result2); dec = seed.Decrypt(enc); // OFB 테스트 seed.ModType = SEED.MODE.AI_OFB; enc = seed.Encrypt(result2); dec = seed.Decrypt(enc); return result; } } }



마지막으로 앞의 SEED 코드와 위 nPKCS12 코드를 이용해서 실제 공인 인증서를 읽는 예제는 아래와 같다.

아래 예제를 정상 실행하기 위해서는 PKCS8 클래스가 필요하기 때문에 모노 프로젝트가 설치되어 있어야 한다.

또는,

모노 프로젝트에 포함된 소스 파일들 중에서 아래 클래스들 몇 개만 따로 복사해서 사용해도 되긴 한다.

  • ASN1.cs
  • ASN1Convert.cs
  • BitConverterLE.cs
  • PKCS12.cs
  • PKCS7.cs
  • PKCS8.cs
  • X501Name.cs
  • X520.cs


아무튼


[CertTest.cs]

using System; using System.Globalization; using System.IO; using System.Security; using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; using System.Text; namespace PnPeople.Security { /// <summary> /// test에 대한 요약 설명입니다. /// </summary> class CertTest { /// <summary> /// 공인인증서 읽기 /// </summary> [STAThread] static void Main() { string folder = Environment.GetEnvironmentVariable("ProgramFiles") + "\\NPKI\\yessign"; // XP, yessign 인증서인 경우 if (Environment.OSVersion.Version.Major >= 6) folder = Environment.GetEnvironmentVariable("LOCALAPPDATA") + "Low\\NPKI\\KICA"; // > Windows 7, KICA 인증서인 경우 // 인증기관 인증서(공개키) --> 이 부분은 그냥 아무 의미 없는 테스트... // 자신의 PC에 있는 인증기관 인증서 파일명을 쓰면 된다 string certFile = folder + "\\B909F2B621489A2ABA025980862793166A77F559_10081.der"; X509Certificate cert = X509Certificate.CreateFromCertFile(certFile); X509Certificate2 token = new X509Certificate2(cert); Console.WriteLine("IssuerName: " + token.Issuer); Console.WriteLine("KeyAlgorithm: " + token.GetKeyAlgorithm()); Console.WriteLine("KeyAlgorithmParameters: " + token.GetKeyAlgorithmParametersString()); Console.WriteLine("Name: " + token.Subject); Console.WriteLine("PublicKey: " + token.GetPublicKeyString()); Console.WriteLine("SerialNumber: " + token.GetSerialNumberString()); // 실제 개인 인증서 접근 부분 DirectoryInfo keyDir = new DirectoryInfo(folder + "\\USER"); byte[] bytes = null; foreach (DirectoryInfo dir in keyDir.GetDirectories()) { FileInfo[] files = dir.GetFiles("s*.key"); // 개인키 파일 FileStream stream = files[0].OpenRead(); stream.Position = 0; bytes = new byte[stream.Length]; stream.Read(bytes, 0, (int)stream.Length); stream.Close(); } Console.WriteLine("KeyType: " + PKCS8.GetType(bytes)); PKCS8.EncryptedPrivateKeyInfo encInfo = new PKCS8.EncryptedPrivateKeyInfo(bytes); Console.WriteLine("Algorithm: " + encInfo.Algorithm); nPKCS12 p12 = new nPKCS12(); p12.Password = "********"; // 실제 개인키 암호 byte[] decrypted = p12.Decrypt(encInfo.Algorithm, encInfo.Salt, encInfo.IterationCount, encInfo.EncryptedData); if (decrypted != null) { PKCS8.PrivateKeyInfo keyInfo = new PKCS8.PrivateKeyInfo(decrypted); RSA rsa2 = PKCS8.PrivateKeyInfo.DecodeRSA(keyInfo.PrivateKey); RSACryptoServiceProvider provider = (RSACryptoServiceProvider)rsa2; // 개인키를 이용한 전자서명 테스트 byte[] buffer = Encoding.Default.GetBytes("1234567890"); byte[] signed = provider.SignData(buffer, "SHA1"); //provider.VerifyData(signed, "SHA1", signed); } } } }



보다시피 공인 인증서라 해 봐야 별 건 없다. 개인키 파일은 개인키 암호를 이용해서 SEED 알고리즘으로 암호화되어 있기 때문에 개인 인증서(개인키)를 얻으려면 개인키 암호를 이용해서 복호화하면 된다.


순서대로 보자면,

  1. 개인키 파일을 읽어서 PKCS8 클래스로 읽으면 암호화된 개인키 정보를 얻을 수 있는데
  2. 여기서 암호화 알고리즘과 Salt, 반복횟수 정보를 바로 읽을 수 있고
  3. 이걸 이용해서 SEED 알고리즘의 암호화 키와 초기화 벡터를 얻는다.
    (암호화 키와 초기화 벡터를 얻었으면 끝난 거지 뭐.)
  4. 개인키 파일로부터 개인 인증서(개인키)를 제대로 얻었으면 이걸로 전자서명이든 뭐든 하고 싶은 대로 할 수 있다.

위 예제는 1234567890 이라는 데이터에 전자서명을 한 번 해 본 것이다. 서명된 데이터는 역시 개인키로 검증할 수 있다. 또, 실제로는 위 예제처럼 바이너리 바이트 배열로 사용할 수도 있지만 보통은 XML 디지털 서명을 할 때 더 많이 쓴다. 그 예제는 시간 나면 따로 올려 볼 기회가 있을라나... 뭐 이미 관련 글들은 넘쳐나니 내가 또 쓸 이유가 별로 없긴 하다.


끝.


'Tech:  > .NET·C#' 카테고리의 다른 글

ASP.NET 웹 서비스와 일반 Javascript Ajax  (0) 2014.04.11
C# 4.5 버전과 C# 4.5.1 버전의 차이  (0) 2014.04.04
SEED 활용 - 전자서명  (5) 2014.03.26
SEED C# 소스  (23) 2014.03.25
AJAX Control Toolkit: NoBot  (0) 2014.03.21
AjaxFileUpload 한글 파일명 관련 버그  (2) 2014.02.28


Posted by 🐮Thermidor™ 떼르미

댓글을 달아 주세요

  1. 123456 2017.11.14 12:55  댓글주소  수정/삭제  댓글쓰기

    아직 답변을 주실지는 모르겠지만..

    혹시 모노프로젝트를 참조하는 방법은 어떻게 하는건지 알수있을까요??

    참고: http://www.go-mono.com/mono-downloads/download.html

    위 주소에서 mono 프로젝트 설치는 했는데 설치후에 비주얼 스튜디오에서 적용하려면

    참조 추가같은것을 해줘야 하는게아닌지해서요..

    제가 초보라 모르는부분 많습니다.. 도움부탁드려요

    • 🐮Thermidor™ 떼르미 2017.11.14 14:12 신고  댓글주소  수정/삭제

      음... 모노 설치 폴더 아래에 lib\\mono 폴더가 있습니다. 그 안에 닷넷 프레임워크 버전 폴더가 각각 따로 있는데 사용하시는 버전 폴더 안에 있는 Mono.Security.dll을 참조하시고 using Mono.Security.X509; 코드를 추가하시면 될 것 같습니다.

  2. 123456 2017.11.14 14:50  댓글주소  수정/삭제  댓글쓰기

    댓글 감사합니다.. 알려주신데로 프로젝트에 사용하고있는 닷넷 프레임워크 버전 폴더에들어가서
    Mono.Security.dll 을 참조하여 컴파일하였더니 아래와같은 오류메세지가 나옵니다.

    <오류내용>
    System.BadImageFormatException: 파일이나 어셈블리 'Mono.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756' 또는 여기에 종속되어 있는 파일이나 어셈블리 중 하나를 로드할 수 없습니다. 참조 어셈블리를 로드하여 실행하지 않아야 합니다. 참조 어셈블리는 리플렉션 전용 로더 컨텍스트에서만 로드할 수 있습니다. (예외가 발생한 HRESULT: 0x80131058)

    혹시 어떤문제인지 알려주실수있을까요?

    • 🐮Thermidor™ 떼르미 2017.11.16 17:42 신고  댓글주소  수정/삭제

      해당 오류는 원인이 천차만별이라 딱히 뭐라 짚어내기가 어렵네요. 대표적인 원인은 보통 target platform이 다를 경우(x86, x64, 또는 AnyCPU)이고, 그 다음으로는 bindingRedirect 설정 버전이 잘못 명시된 경우 정도가 떠오르네요.
      두 가지 모두 해당되지 않는다면 그밖엔... 직접 당해보지 않고서는 뭐라 말하기 어렵습니다.^^;;

  3. story 2018.11.11 17:04  댓글주소  수정/삭제  댓글쓰기

    감사합니다. 따로 컨버팅 할 필요가 없이 시간이 단축되었네요.



자바스크립트를 허용해주세요!
Please Enable JavaScript![ Enable JavaScript ]