메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

C# 암호화

한빛미디어

|

2002-03-29

|

by HANBIT

15,458

저자: 한동훈

닷넷 프레임워크에서는 암호화와 관련된 많은 API를 제공하고 있다. 데이터를 암호화해서 다른 사람들이 알아볼 수 없게하는 일은 실제로 자주 일어난다.

인트라넷을 구축하거나, 전자상거래 사이트에서 사용자 정보를 보호하거나, 웹 서비스에서 전송되는 XML 데이터의 내용을 암호화하는 것까지 아주 많은 부분에서 이용된다. 실제로 웹 서비스는 순수 텍스트(plain text)로 데이터가 전송되기 때문에 데이터를 가로채서 그 정보를 알아내는 일은 매우 쉬울 것이다. 이러한 이유로 XML Secuirty와 XML Signature에 대한 표준화 작업이 진행되고 있다. 관심있는 분들은 W3C(http://www.w3c.org)에 방문하기 바란다.

XML의 내용을 암호화하는 데에는 SAML(The Security Assertions Markup Language) 표기법을 사용하며, 이에 대한 것도 W3C를 방문하기 바란다.

XML Security & Signature에 대한 실제 구현은 현재 IBM의 XML Security Suite(http://www.alphaworks.ibm.com/tech/xmlsecuritysuite)과 MS의 닷넷 프레임워크가 있다. MS에서는 Web Service Security Language(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsrvspec/html/ws-security.asp)를 참고하기 바란다.

실제로 여기서 소개하는 내용은 위 참고 자료에 대한 한글판 재탕밖에 되지 않을 것이며, SAML에 대해서는 설명하지 않을 것이다. SAML에 대한 것은 『Programming Web Services with SOAP』, Chapter 7(O"Reilly)을 참고하기 바란다.

다음은 간단히 SHA1을 이용해서 해시(Hash)를 생성하는 예제다.
이름 : sha1Hash.cs

using System;
using System.IO;
using System.Text;
using System.Security;
using System.Security.Cryptography;

namespace CryptoFilter
{
  public class sha1Hash
  {
    public static void Main()
    {
      sha1Hash app = new sha1Hash();

      app.DoTest();
    }

    public void DoTest()
    {
      SHA1 sha = new SHA1CryptoServiceProvider();

      byte[] data = new byte[3];
      byte[] result = {};

      data[0] = 97;   // ASCII Code 97 = "a"
      data[1] = 97;
      data[2] = 97;

      result = sha.ComputeHash(data);
      Console.WriteLine("Base64 Encoding : " + Convert.ToBase64String(result));
      Console.WriteLine("ASCII Encoding : \n" + Encoding.ASCII.GetString(result));
    }
  }
}
암호화와 관련된 기능을 이용하려면 System.Security와 System.Security.Cryptography 네임스페이스에 있는 클래스를 이용해야한다. 또한, 암호화는 byte 형태로 되어 있는데 이것을 적절한 문자열(string) 형태로 변환하기 위해 System.Text 네임스페이스를 사용했다. 모든 코드를 입력했으면 다음과 같이 컴파일하여 실행한다.
Csc /out:sha1Hash.exe /target:exe sha1Hash.cs
여기서는 미리 데이터를 입력하고 그에 대한 해시를 생성하는 방법을 사용했다. 아스키(ASCII) 코드 97은 영소문자 "a"이며, 실제로 해시를 생성하기 위해 사용한 입력 데이터는 "aaa"가 된다.

SHA1 알고리즘으로 생성한 해시는 다음과 같다.

Base64 인코딩을 사용하면 화면에 나타낼 수 있는 문자만을 사용하기 때문에 안전하게 표시할 수 있지만, 직접 문자열로 변환하여 출력하는 경우에는 화면 제어문자와 같은 특수 문자로 인해서 화면이 깨져보일 수 있기 때문에 그 다음 라인에 결과를 출력하도록 했다.

System.Text.Encoding 클래스를 사용해서 적절한 형태로 바이트 배열을 인코딩해낼 수 있으며, 파일 입출력 처리시에 데이터를 처리하는데도 유용하다.

간단하게 SHA1 알고리즘을 이용해서 해시를 생성할 수 있는 것을 알 수 있을 것이다. 이것을 VB6에서 작성한다면 대략 3000줄 정도의 코드가 필요하며, 프레임워크가 제공하는 기능에 의해서 단 몇줄만으로 SHA1 암호화 기능을 이용할 수 있으며, 프로그래머는 비즈니스 로직에 더 많이 집중할 수 있을 것이다.

필자의 경험에 따르면, 부족한 실력으로 어마어마한(?) 암호화를 구현할 수는 없었기 때문에 C 언어와 같은 다른 언어로 작성된 라이브러리를 받아서 VB에서 이용하도록 하기 위한 작업을 한 적도 있고, 몇줄 안되는 XOR 연산을 이용해서 데이터를 암호화하고, 다시 데이터를 복원하기 위해 머리를 쥐어짰던 기억도 있다. 가장 최악의 기억은 MSDN의 Crypto Filter Box 예제마저 제대로 작동하지 않아서 머리를 쥐어뜯은 적도 있고, Win32 Crypt API를 사용하고, 제대로 된 결과를 얻기 위해 많은 시간을 보냈던 기억이 있다.

다음은 SHA1과 마찬가지로, 간단히 MD5(Message Digest 5) 알고리즘을 이용하여 해시를 생성하는 예제다.
이름: md5Hash.cs

using System;
using System.IO;
using System.Text;
using System.Security;
using System.Security.Cryptography;

namespace CryptoFilter
{
  public class md5Hash
  {
    public static void Main()
    {
      md5Hash app = new md5Hash();

      app.DoTest();
    }

    public void DoTest()
    {
      MD5 md5 = new MD5CryptoServiceProvider();

      byte[] data = new byte[3];
      byte[] result = {};

      data[0] = 97;   // ASCII Code 97 = "a"
      data[1] = 97;
      data[2] = 97;

      result = md5.ComputeHash(data);
      Console.WriteLine("Base64 Encoding : " + Convert.ToBase64String(result));
      Console.WriteLine("ASCII Encoding : \n" + Encoding.ASCII.GetString(result));
    }
  }
}
SHA1이라는 단어 대신에 MD5를 사용한 것 외에는 큰 차이점이 없을 것이다. 입력 데이터는 같은 데이터를 사용했다. 컴파일은 이전과 같다.
Csc /out:md5Hash.exe /target:exe md5Hash.cs
실행한 결과는 다음과 같을 것이다.

사용자 인증을 웹 서비스로 구현하는 경우에 사용자 ID와 비밀번호를 전송하게 되며, 이 모든 데이터는 일반 텍스트로 전달된다. 즉, 누구나 데이터 패킷을 가로챌 수 있다면 사용자 비밀번호를 쉽게 알아낼 수 있을 것이다.

데이터를 안전하게 전송할 있도록 위 두 예제를 합쳐서 언제나 사용할 수 있는 하나의 라이브러리로 재작성해보자.

CryptoFilter.DLL 라이브러리 만들기

먼저, 이러한 것들을 라이브러리로 편하게 이용할 수 있도록 CryptoFilter.DLL 어셈블리를 작성하도록 하자. 잘 아는 이야기지만, 닷넷에서는 컴포넌트라는 용어 대신에 어셈블리라는 용어를 사용한다.
using System;
using System.IO;
using System.Text;
using System.Security;
using System.Security.Cryptography;
여기서 이용하는데 필요한 각종 네임스페이를 선언하는 부분이다.
namespace CryptoFilter
{
  public class Hash
  {
네임 스페이스와 클래스를 만드는 부분인데, 여기서는 CryptoFilter라는 네임스페이스와 Hash라는 클래스를 작성한다. 코드내에서 이것을 이용하고자 한다면 다음과 같이 사용할 수 있도록 하기 위한 것이다.
CryptoFilter.Hash hash = new CryptoFilter.Hash();
앞에 CryptoFilter를 생략하고 싶다면 코드의 시작에 using CryptoFilter; 를 추가하도록 한다.(하지만, 실제로는 이렇게 추가하지 않으면 원하는 클래스를 찾을 수 없다는 에러가 발생한다)
    public enum HashType
    {
      MD5,
      SHA1
    }

    private string[] InputData = {};
    private string plain = String.Empty;
    private byte[] hashed;
    private byte[] result = new byte[100];
    private bool bSHA1 = false;
    private bool bMD5 = false;
HashType을 선언하고, MD5, SHA1 알고리즘중에 어떤 것을 사용할지를 지정하도록 한다. 이 값에 따라서 bSHA1 또는 bMD5와 같은 불리언(boolean) 타입을 true로 설정하도록 할 것이다. 할당되지 않는 값들에 대해서는 적절한 초기값을 넣어두도록 한다.
private string plain = String.Empty;
문자열 변수에 대해서 값을 할당하지 않는다면 String.Empty과 같은 값을 넣도록 한다.
private string[] InputData = {};
배열에 초기값을 할당하지 않는다는 것을 명시하기 위해 다음과 같이 설정하였다.
    public Hash(HashType hashType, params string[] sInputData)
    {
      if ( hashType == HashType.MD5 )
      {
        bMD5 = true;
      }

      else if( hashType == HashType.SHA1 )
      {
        bSHA1 = true;
      }

      InputData = sInputData;
    } // end of Hash
클래스 이름이 Hash이므로, 여기서 정의한 것은 생성자 Hash를 지정한 부분이다. 생성자 Hash에서는 HashType에 따라서 불리언 값(bMD5, bSHA1)을 true로 설정하도록 한다. 나머지 변수들은 모두 초기값이 false이므로 단 하나의 변수만 true로 설정된다.

다음으로 해시를 생성할 문자열 값을 입력받아서 전역변수 InputData에 저장한다.
public string Encrypt(bool isBase64)
{
  if(bSHA1)
  {
    SHA1 sha1 = new SHA1CryptoServiceProvider();

    for(int loopctr = 0; loopctr < InputData.Length; loopctr++) plain += InputData[loopctr];
    
    byte[] bSha1Hash = Encoding.ASCII.GetBytes(plain);
    
    result = sha1.ComputeHash(bSha1Hash);

    hashed = result;
  }
  else if(bMD5)
  {
    MD5 md5 = new MD5CryptoServiceProvider();

    for (int loopctr = 0; loopctr < InputData.Length; loopctr++) plain += InputData[loopctr];
    
    byte[] bMD5Hash = Encoding.ASCII.GetBytes(plain);
    
    result = md5.ComputeHash(bMD5Hash);
    
    hashed = result;
  }
각각 설정된 불리언 값(bMD5, bSHA1)에 따라서 입력데이터에 대한 해시를 생성한다. 해시는 바이트 단위로 이루어지기 때문에 입력 받은 문자열에 대해서 루프를 돌면서 한 문자(즉, 한 바이트)씩 해시를 생성하고 이것을 바이트 배열 result에 저장한다. (일반적으로 암호화를 하게되면 그 결과는 원본보다 크기가 커진다)
    if(isBase64)
    {
      return Convert.ToBase64String(hashed);
    }
    else
    {
      return Encoding.ASCII.GetString(hashed);
    }
  }
}
}
이 부분은 전달된 값에 따라서 결과를 Base64 인코딩으로 변환할 것인지, ASCII 형식으로 저장할 것인지를 지정하는 것이다. ASCII 인코딩을 하게되면 특수문자등이 들어가기 때문에 처리하기가 어려워진다. 특히, 웹 서비스의 내용을 암호화하는 경우, XML 컨텐트를 암호화하는 경우, 데이터베이스에 암호화한 내용을 저장하는 경우라면 Base64 인코딩을 사용하기를 권한다.(Base64 인코딩이라는 것은 영문 대소문자와 몇몇 기호들을 합쳐서 64개의 문자로 인코딩한다는 의미다)

전체소스는 다음과 같다.
이름: CryptoFilter.cs

using System;
using System.IO;
using System.Text;
using System.Security;
using System.Security.Cryptography;

namespace CryptoFilter
{
  public class Hash
  {
    public enum HashType
    {
      MD5,
      SHA1
    } // end of enum HashType

    private string[] InputData = {};
    private string plain = String.Empty;
    private byte[] hashed;
    private byte[] result = new byte[100];
    private bool bSHA1 = false;
    private bool bMD5 = false;

    public Hash(HashType hashType, params string[] sInputData)
    {
      if ( hashType == HashType.MD5 )
      {
        bMD5 = true;
      }

      else if( hashType == HashType.SHA1 )
      {
        bSHA1 = true;
      }

      InputData = sInputData;
    } // end of Hash Constructor

    public string Encrypt(bool isBase64)
    {
      // SHA1 Hash
      if(bSHA1)
      {
        SHA1 sha1 = new SHA1CryptoServiceProvider();

        for(int loopctr = 0; loopctr < InputData.Length; loopctr++)
        {
          plain += InputData[loopctr];
        }
        
        byte[] bSha1Hash = Encoding.ASCII.GetBytes(plain);
        
        result = sha1.ComputeHash(bSha1Hash);

        hashed = result;
      }
      
      // MD5 Hash
      else if(bMD5)
      {
        MD5 md5 = new MD5CryptoServiceProvider();

        for (int loopctr = 0; loopctr < InputData.Length; loopctr++)
        {
          plain += InputData[loopctr];
        }
        
        byte[] bMD5Hash = Encoding.ASCII.GetBytes(plain);
        
        result = md5.ComputeHash(bMD5Hash);
        
        hashed = result;
      } // end of if

      if(isBase64)
      {
        return Convert.ToBase64String(hashed);
      }
      else
      {
        return Encoding.ASCII.GetString(hashed);
      } // end of if

    } // end of function Encrypt

  } // end of class Hash

} // end of namespace CryptoFilter
코드를 모두 입력했다면 다음과 같은 과정을 거친다.
Csc /out:CryptoFilter.dll /target:library CryptoFilter.cs
컴파일이 끝나면 이제 CryptoFilter.DLL 어셈블리가 생성되었을 것이다. 이제 이 어셈블리를 이용하여 해시를 생성하는 프로그램을 작성해보자.
이름: CryptApp.cs

using System;
using CryptoFilter;

public class CryptApp
{
  public static void Main()
  {
    string result = String.Empty;

    Hash hash = new Hash(hash.HashType.MD5, "password");
    result = hash.Encrypt(true);

    Console.WriteLine(result);
  }
}
입력을 모두 했으면 다음과 같이 컴파일한다.
Csc /out:CryptApp.exe /target:exe CryptApp.cs /reference:CryptoFilter.dll
성공적으로 컴파일이 끝나면 CryptApp.exe가 생성된다. 실행한 결과는 다음과 같다.

입력을 모두 했으면 볼 수 있는 것처럼, MD5 알고리즘(hash.HashType.MD5)을 사용하고, 해시를 생성할 입력데이터는 "password"로 하였다. Hash.Encrypt(true)로 하였으므로 결과는 Base64 인코딩될 것이다.

키를 생성하는 것, XML 컨텐트를 암호화하는 것, 웹 서비스 기반으로 키를 교환하는 것까지 다양한 것들에 대해서 아직 많은 것들이 남아있다. 보다 관심있는 분들은 MSDN이나 관련 문서를 찾아보기바란다.
TAG :
댓글 입력
자료실