이번에는 앞 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 C# 소스  (33) 2014.03.25
AJAX Control Toolkit: NoBot  (0) 2014.03.21
AjaxFileUpload 한글 파일명 관련 버그  (2) 2014.02.28


Posted by 떼르미
,


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