12/24/2010

한글 필기체 인식 프로그램

 

1. 기본사항

  • 오토마타 및 지능컴퓨팅 수업 과제
  • Windows 7
  • Visual Studio 2010 – .NET framework v3.5 using C#

 

2. 프로그램 스크린샷

image

 

3. 프로그램 소개

  • Artificial Neural Network 알고리즘 사용 (오픈소스 API)
  • 초성, 중성, 종성의 입력을 토대로 유니코드 한글 조합
  • 한글 입력 양식 ▼ (한글 위키 참조)
    image
  • 유니코드 조합 ▼
    유니코드 한글 = [{(초성)×588}+{(중성)×28}+(종성)]+44032
    그림1
  • 오토마타 ▼
    고정된 위치에 초성,중성,종성을 입력 받기 때문에 간단해진 오토마타
    image

 

4. 소스코드

소스코드 보러가기

각 언어별 샘플 수가 많을수록 정확한 인식결과를 가져옵니다.

12/23/2010

Unix 간단한 Shell 구현

유닉스의 Shell 프로그램을 구현

1. 구현기능

  • 좀비프로세스 방지
  • Foreground & Background 기능 구현
  • exit 명령어로 shell 종료
  • cd 명령어 구현
  • Redirection 처리
  • shell 자체는 제어키 무시
  • Pipe 기능 구현

 

2. 소스코드

소스코드 보기

임베디드 계산기 프로그램

1. 프로젝트 소개

  • 한백전자 HBE-EMBOS II, PXA255 Processor
  • Embedded Linux kernel v2.4.19
  • Cross Compile을 위해 Fedora 13 OS 사용
  • TFT-LCD, Touch Screen, 7-Segment, Text-LCD Device 사용
  • 7-Segment, Text-LCD Device Driver 제작

 

2. 스크린샷

스크린샷

 

3. 소스코드

소스코드 보기

  • 디바이스 드라이버

디바이스 드라이버 헤더 (ioctl 명령어)

 

  • 유저 프로그램

Bitmap 파일 읽기

Bitmap 데이터 TFT-LCD에 쓰기

터치스크린 입력 포인트 가져오기

7 Segment, Text-LCD에 값 출력

터치포인트값과 버튼위치 맵핑

Main 함수 내용

12/22/2010

프로그래밍 언어 패러다임

프로그래밍 언어론 수업에서 진행한 최종과제입니다.

각 언어별 패러다임으로 주어진 문제 4개를 작성하는 과제입니다.
전 C(명령형언어), C#(객체지향언어), Scheme(함수형언어), Prolog(논리형언어)를 택했습니다.

  • C - *OS: Fedora 13 *Tool: gedit
  • C# - *OS: Windows 7 *Tool: Visual Studio 2010
  • Scheme - *OS: Windows 7 *Tool: DrRacket(lang: scheme) v5.0.1
  • Prolog - *OS: Windows 7 *Tool: SWI-Prolog v5.10.2

 

1. 프로그램 소스코드

1) C 언어

2) C# 언어

3) Scheme 언어

4) Prolog 언어

 

2. 프로그램 스크린샷

1) 문자열이 올바른 이메일 주소인지를 판단하는 프로그램


2) 문자를 저장하는 두 개의 연결리스트에 리스트 내용이 같은지를 판단하는 프로그램


3) 사람들의 이름, 전화번호, 주소를 저장하고 정보를 검색하는 프로그램


4) 10개의 이름을 입력 받아 정렬하는 프로그램

 

3. 각 프로그래밍 언어의 장단점 비교

  장점 단점
C

· 프로그램이 가볍고 효율적입니다. 따라서 프로그램 실행속도가 빠르고 용량을 절약할 수 있습니다.

· 포인터를 사용해 주소를 직접 제어하기 때문에 메모리를 효율적으로 사용할 수 있습니다.

· 이식성이 좋습니다.

· 포인터의 사용으로 인한 허상포인터 및 분실된 힙-동적 변수 문제점을 야기합니다.

· 형 변환과 같은 타입검사가 너무 유연해 생각하지 못한 오류가 발생하곤 합니다.

· GUI 개발을 하는 것이 어렵습니다.

C#

· C언어와 비교했을 때 가비지 컬렉션의 지원으로 메모리 누수를 걱정하지 않아도 됩니다.

· 타입검사 및 문법을 엄격하게 하므로 신뢰성이 높습니다.

· 닷넷 프레임워크를 설치하면 어디서든 C#으로 작성된 프로그램을 구동할 수 있습니다.

· 다양한 라이브러리를 제공해 손쉽고 신뢰성 높은 프로그램을 작성할 수 있었습니다.

· 포인터가 없기 때문에 직접적으로 메모리에 접근하기가 어렵습니다.

· 닷넷 프레임워크의 설치가 필수적이므로 프로그램 배포시에 문제가 될 수 있습니다.

Scheme

· 매크로라는 기능을 이용해 원하는 작업을 쉽게 추상화 해서 프로그래밍할 수 있습니다.

· 간단한 구문과 의미를 가집니다. 예) operator operand1, 2...

· 동일한 매개변수가 함수에 적용되면 평가 결과는 항상 같습니다.

· 재귀와 조건식으로 프로그램을 제어하므로 부작용이 적습니다.

· 익숙하지 않은 언어였고 그동안 프로그래밍해오던 습관과 달라 과제를 구현하는데 많은 어려움이 있었습니다.

· 함수를 정의할 때 () 괄호를 사용하기 때문에 판독력이 떨어집니다.

· C언어와 같이 대화형 프로그램을 작성하는 것이 매우 어려웠습니다. 변수나 상태를 다루기 힘들어 history sensitive개념적용에 어려움을 겪었습니다.

Prolog

· 의미의 명확성, 높은 프로그래밍 생산성, 메타 프로그래밍의 용이성, 빠른 프로토타이핑, 자동 메모리 관리 및 병렬 처리의 용이성 등 많은 장점이 있습니다.

· 기본적으로 리스트 타입을 지원하기 때문에 리스트 관련 과제를 해결할 때 손쉽게 해결할 수 있었습니다.

· 익숙하지 않은 언어였고 그동안 프로그래밍해오던 습관과 달라 과제를 구현하는데 많은 어려움이 있었습니다.

· C언어와 같이 대화형 프로그램을 작성하는 것이 매우 어려웠습니다. 또한 사용자로부터 데이터를 입력받는것도 어려웠습니다.

· 프로그램이 커지면 디버그하는 것이 매우 어려울 것 같습니다.

 

Language

Intended use

Type strength

Type safety

Expression of types

Compatibility among composite types

Type checking

C

System

weak

unsafe

explicit

name-based

static

C#

Application, Web

strong

safe

explicit

name-based

static

Scheme

General, Education

strong

 

implicit

 

dynamic (latent)

Prolog

Application,
Artificial intelligence

strong

     

dynamic

참조: 위키(http://en.wikipedia.org/wiki/Comparison_of_programming_languages)

 

4. 프로그래밍 언어별 패러다임의 비교

  • 명령형 (Imperative)

    폰 노이만 구조라 불리는 컴퓨터 구조에 기반하여 설계된 언어입니다.
  • 다시 말하면, 언어 구조가 프로그래밍 언어 사용자의 능력과 성향의 관점보다는 기계 구조의 관점에서 모델링되어 있습니다.

    명령형 언어는 계산 문제를 프로그램의 상태와 그 상태를 변화시키는 문장으로 기술합니다. 변수와 배정문 같은 기어 장소를 참조하는 명령을 사용하여, 기억 장소의 내용을 조작/변경하여 계산 과정을 수행하는 언어 패러다임입니다.

    명령형 언어의 주요 특징으로 변수, 배정문, 반복문을 들 수 있습니다.

    명령형 언어로 FORTRAN, COBOL, Pascal, C 등이 있습니다.

    변수, 배정문으로 인해 효율적인 반복문이 가능합니다.

    할당문(= 연산)을 쓰기 때문에 상태가 "알 수 없게" 변한다는 겁니다. 따라서 예상치 못한 오류가 발생할 가능성이 있습니다.

     

  • 객체지향형 (Object-oriented)

    크고 복잡한 프로그램의 생산성 향상을 목적으로 명령형 언어에서 객체지향 방법론을 보다 효율적이고 유연하게 사용할 수 있도록 발전된 형태의 언어 패러다임입니다.
  • 객체 지향의 기본 개념은 클래스, 객체간의 계층화, 상속, 다형성으로 요약됩니다. 프로그램 안의 모든 독립된 개체를 객체로 표현하며, 연관된 모든 연산 및 데이터 구조가 객체에 포함되어 정의됩니다.

    주요 특징으로 추상 데이터 타입, 상속, 동적 타입 바인딩, 다형성이 있습니다.

    객체지향 개념을 잘 지원하는 언어로써 C++, Smalltalk, Java 등이 있습니다.

    동적 타입 바인딩은 보다 유연한 상속을 허용하고 이러한 상속은 기존 추상 데이터 타입을 변경하지 않으므로 소프트웨어의 재사용 가능성을 크게 향상시켜 줍니다.

     

  • 함수형 (Functional)

    프로그램의 기본 단위는 함수가 되며 프로그램의 구성은 함수의 평가와 호출로 이루어집니다. 프로그램을 기억 장소의 상태 변화 및 이에 대한 조작으로 기술하는 것이 아니라, 입력과 출력의 함수만을 사용하여 기술하는 언어입니다.
  • 명령형 언어와 달리 기억 장소의 상태에 대한 개념이 없어 함수적 부작용이 없습니다. 즉, 명시적인 기억장소 할당 명령이나 명시적인 변수 배정문 등이 없습니다. 또한 병렬 실행이 가능합니다. 하지만 상태가 없으므로 history-sensitive와 파일 입출력, 자료구조 처리 같은 것을 다루기가 어렵습니다.

    대표적인 함수형 언어로 Lisp이 있으며 ML, Scheme, Haskell 등의 언어가 함수형 언어에 속합니다.

    문법이 단순해 이해하기 쉽습니다.

    폰 노이만 식 컴퓨터 구조에서는 함수형 언어로 작성된 프로그램의 효율성이 높지 않습니다.

  • 논리형 (Logic)

    논리형 언어는 수학 논리에 기반을 두고 논리 문제를 효과적으로 표현하기 위해 개발되었습니다. 결과를 만드는 절차가 아닌 결과만을 명시합니다. 프로그램은 규칙과 규칙의 활성화 조건으로 구성됩니다. 규칙 활성화 조건이 만족되면, 연관된 규칙이 실행되는 언어입니다. 이러한 규칙의 집합을 Knowledge Base라고도 하며, 지정된 Knowledge에 대해 질의를 할 수 있습니다.
  • 대표적인 논리형 언어로 PROLOG가 있으며 많은 방언이 파생되었습니다.

    큰 프로그램의 경우, 프로그램의 오류 수정이나 실행 결과를 이해하는 것이 어렵고, 많은 back-tracking으로 실행 시간이 오래 걸립니다.

구글 블로그에서 펼치기/접기 Show/Hide 기능 추가하기

1. 대시보드 –> 꾸미기 –> HTML 편집 –> 스킨수정

Ctrl+F를 눌러 </head>를 찾고, </head> 윗줄에 아래의 소스 삽입
&#39; 는 작은따옴표(')를 의미하는데 그냥 삽입해도 상관없습니다.

<script language='JavaScript' type='text/javascript'>
function layer_toggle(obj) {
        if (obj.style.display==&#39;none&#39;) obj.style.display = &#39;block&#39;;
        else if (obj.style.display==&#39;block&#39;) obj.style.display = &#39;none&#39;;
}
</script>



2. 글 작성시 HTML 편집에서 아래와 같이 사용

여러 개의 펼치기/접기를 이용할 땐 밑줄 친 more01a, more01b를 바꾸면서 추가해 줍니다.
<div style="display: block" id="more01a">
<a onclick="layer_toggle(document.getElementById('more01a')); layer_toggle(document.getElementById('more01b')); return false;" href="#">
<p>
접힌 상태</p>
</a></div>
<div style="display: none" id="more01b">
<a onclick="layer_toggle(document.getElementById('more01a')); layer_toggle(document.getElementById('more01b')); return false;" href="#">
<p>
펼친 상태</p>
</a><p>
내용을 적습니다</p>
</div>


3. 사용 예



참고자료 –>
http://blog.naver.com/dionly/120034759268

9/25/2010

VirtualBox (v3.2.8)에 Fedora 13 및 게스트확장(Guest Additions) 설치하기

드림스파크에서 지원해주는 Virtual PC 2007에다가 설치해보려고 했지만
부팅시 화면이 깨져보이는 현상에 이래저래 스트레스를 받고

그냥 오픈소스프로젝트의 VirtualBox에 설치하기로 했다.
또한 VirtualBox 의 편리한 기능인 Seamless Mode를 사용하기 위해서
Guest Additions를 설치하는것이 좋다.

이 모드는 마치 Host와 Guest OS사이의 창 이동없이
Guest OS가 Host PC에서 하나의 프로그램처럼 실행되게끔 느끼게 해준다.
이를 좀더 편리하게 쓰기위해 Host Key 설정을 Right Ctrl 에서 F11키로 바꿨다
(놋북에 오른쪽 Ctrl이 없기에 ㅠㅠ)
Host Key 설정은 VirtualBox 창에서 파일 -> 환경설정의 입력 탭에 가면 설정할 수 있다.

이 모드에서 자주쓰는 Hot Key는 Host Key+L(Seemless Mode On/Off)와
Host Key+Home(Show Context Menu) 이니 유용하고 편리하게 이용하도록 하자.


Fedora 13 및 Guest Additions 설치순서는 아래 사이트에 잘 나와있다.
http://www.sysprobs.com/fedora-13-virtualbox-working-virtualbox-guest-additions



Fedora 13 설치하기
내 노트북 사양이 좋지않기에 램은 512MB로 잡았는데 확실히.. 느리다 -_-!
Fedora 13 설치가 완료되면 Fedora 13 ISO 이미지를 마운트 해제해야 Fedora로 부팅이 된다.




게스트확장(Guest Additions)을 Fedora 13에 설치
부팅이 완료되면 kernel을 업데이트하고 kernel-devel, kernel-headers, dkms, 
그리고 gcc serta gcc-c++ 프로그램을 설치한다.

난 superuser로 로그인해 $쉘이 아닌 #쉘에서 yum 명령어를 수행했다.
-- Superuser로 로그인하는법--
$ su
패스워드 입력

#
-----------------------------

이를 위해 yum 키워드를 쓰는데 인터넷에서 알아서 해당 프로그램을 찾아 설치해주기때문에
인터넷이 연결되었는지 확인한다. 

네트워크설정에서 NAT로 바꿔주면 Host의 설정과 동등한 네트워크환경을 가진다.

설치가 완료되면 재부팅한번 해주고 터미널 띄어서 또 su로 로그인하고
media 폴더의 VBOX어쩌구 폴더로 들어가서
sh VBoxLinuxAdditions-x86.run 명령어를 입력해주면된다.

-- 폴더 쉽게 찾는 방법 --
cd VB (여기까지만 치고 tab키를 눌러보다보면 VB로 시작하는 모든 폴더를 차례대로 입력해준다.)
(리눅스는 대소문자를 명확히 구분한다고 하니 폴더명을 제대로 확인해야한다.)
-----------------------


이로서 모든 설치 끝!
제대로 설치가 되었는데도 불구하고
VirualBox 콘솔에서 머신탭의 두세번째 메뉴가 비활성화된 상태라면
Fedora 재부팅해보자.



참고웹페이지
http://www.virtualbox.org/
http://fedoraproject.org/
http://www.sysprobs.com/fedora-13-virtualbox-working-virtualbox-guest-additions


2011-01-09 추가>>
우분투 10.10 (Ubuntu)에 VirutalBox v3.2.12 Guest Addition 설치하는 방법

우분투 설치가 완료되면 터미널 창 하나 열고
아래 3줄을 차례차례 입력한 후 재부팅하면 설치완료. (게스트 확장 CD 필요없음)

sudo apt-get update
sudo apt-get install build-essential linux-headers-$(uname -r)
sudo apt-get install virtualbox-ose-guest-x11


참고웹페이지
http://www.unixmen.com/linux-tutorials/1157-install-guest-addition-in-ubuntu-1010-maverick-meerkat-fix

6/16/2010

Windows 7. usb로 설치하기

마소에서 대학생 프로모션으로 @*.edu 메일을 가진 사람들에게 $30에 win 7 professional을 판매하기에(예전에도 프로모션이 있었지만 그때 놓쳐서 지금 신청했다 -_ㅠ) 살포시 구매해줬다.

$12을 더 내면 CD로 보내주긴 하지만 내겐 사치라 usb 버전으로 만들어 설치를 하려던 찰나
아주 친절하게도 usb로 win7의 iso 파일을 넘겨주는 프로그램을 마소에서 지원한다는걸 알았다.
그래서 요걸 써주는 센스!!

1. 프로그램 다운로드
2. 프로그램 설치 및 실행
3. ISO 경로 찾기 Next
4. USB연결 USB Device
5. Begin Copying

아 간단하여라~
 이제 설치해 볼까!!!?

관련링크
$30 win 7 professional promotion site : http://www.microsoft.com/student/office/en-us/default.aspx
windows 7 설치 usb 버전 설명 site : http://store.microsoft.com/Help/ISO-Tool



---

CMOS에서 USB 부팅우선순위 잡고 Windows  7을 설치했다.




---
영문 Windows 7 한글판으로 바꾸기
http://liverex.tistory.com/432

설치했는데 한글이 보이긴 했으니
알집, v3등을 설치할때 나타나는 한글깨짐 현상이 여전했다.
이건 '제어판' > '국가 및 언어의 ' > '관리자 옵션' 탭 > '시스템 로캘 변경' 에서
한국으로 바꿔주면 된다.

이제 즐겁게 윈7을 즐겨보자

6/01/2010

C# 형식지정

 String.Format 메소드등에서 쓰이는 형식을 지정할 때마다 헷갈려서 MSDN 링크해 놓는다.

형식항목의 구성요소와 형태
   {index[,alignment][:formatString]}

 

합성 형식 지정
숫자 형식 지정
날짜 및 시간 형식 지정
열거형 형식 지정
사용자 지정 형식 지정

Class 설계 System, Business Logic 그리고 Data

올바른 Class는 System, Business Logic 그리고 Data 순서로 설계되어야 한다는 걸 배웠다.


내가 처음에 작성한 잘못된 클래스 생성자는 아래와 같다.
  public PinPadOperation(SerialPort serialPort, string cardNum, string totalAmount);



클래스를 생성할 때 Serial Port와 Card Number 그리고 Total Amount를 받아온다.

이 클래스의 생성자가 잘못된 이유는 System과 Data를 한꺼번에 받아온다는데 있다.
이를 고쳐보면

  public PinPadOperation(SerialPort serialPort);   //생성자를 통한 System 변수 설정
  public bool CanUseSerialPort();  //System이 사용가능한지 체크
  public void SetPinPadData(string cardNum, string totalAmount);  //Input Data를 가지고 작업수행



이렇게 설계되었어야 정상이다.

즉, Business Logic에 필요한 System들을 먼저 설정하고 그것이 정상인지 체크한 후에
Data를 받아들여 Business Logic을 실행하는것이 올바른 클래스 설계라고 한다.

Thread C#

- 쓰레드에서 간단하게 Invoke 하기 - 
Thread를 사용하다 보면 하위 Thread에서 메인 Thread의 변수에 접근하기 위해 Invoke Method를 쓸 때가 종종 있다.

하지만 Invoke Method를 쓸 때 delegate와 그 delegate와 연결시킬 method가 필요한데, 이를 Event를 사용하면 소스코드가 한결 간단해진다.

그 예는 아래와 같다.


원본소스
private void StartThread()
{
    Thread thread = new Thread(new ThreadStart(SleepThread));
    thread.Start();
}

private void SleepThread()
{
    System.Threading.Thread.Sleep(800);

    this.Invoke(new SleppThreadDelegate(InvokeMethod));
}

public delegate void SleppThreadDelegate();

private void InvokeMethod()
{
    this.timer.Start();
}

이 소스를 아래와 같이 변경가능
private void StartThread()
{
    Thread thread = new Thread(new ThreadStart(SleepThread));
    thread.Start();
}

private void SleepThread()
{
    System.Threading.Thread.Sleep(800);

    this.Invoke(new EventHandler(delegate(object o, EventArgs a)
    {
        this.timer.Start();
    }));
}

http://blog.opennetcf.com/ctacke/2008/12/03/ControlInvokeWithoutExplicitDelegates.aspx




- 다중 매개변수를 갖는 쓰레드 돌리기 - 
// function that running in a thread
void DoSomething(string param1, string param2, string param3)
{
}
// The parameters.
string param1 = "Hey";
string param2 = "You";
stirng param3 = "There";

// The delegate to call.
ThreadStart ts = delegate() { DoSomething(param1, param2, param3) };

// The thread.
Thread t = new Thread(ts);

// Run the thread.
t.Start();

5/12/2010

visual studio settings

Visual Studio를 통해 개발하는 사람은 하루종일 코딩화면을 들여다 봐야하는데 흰색바탕이 여간 눈이 아픈게 아니다.
해서 Style을 바꿔 좀 눈을 편하게 해보려고 이리저리 뒤져 발견한 사이트를 소개한다.

http://studiostyles.info/

가서 마음에 드는 셋팅을 하나 골라잡고 적용시키자.
VS 2005, 2008, 2010  별로 따로 Setting을 제공하니 선택해서 받고나서~

VS 2008을 기준으로 적용법을 살펴보면
  1. Visual Studio 2008의 Tools -> Import and Export Settings... 선택
  2. 열린 창에서 Import selected environment settings를 선택
  3. 현재 설정을 저장할거면 Yes, 그냥 덮어씌울꺼면 No를 하고 Next
  4. Browse 버튼을 눌러 위 사이트에서 다운받은 것 선택하고 Next
  5. Finish
이제 눈을 좀 쉬게해주자~
50분 컴퓨터 10분 휴식 룰은 언제쯤 지켜보려나..ㅠ

4/29/2010

Eazfuscator.NET Obfuscator

Obfuscation(난독화)에 관련된 기본 지식은 위키서광렬님의 블로그를 참조하자.

.NET Framework에서는 C#, VB.NET등과 같은 Managed .NET code로 쓰여진 코드는 .NET 언어 Compiler에 의해  CIL(Common Intermediate Language, = MSIL)로 만들어지고 JIT(Just-In-Time Compiler)에 의해서 Native Code로 변환되고 CLR(Common Language Runtime)위에서 구동된다. 이러한 이유로 .NET Framework가 설치된 Device에서는 CIL 덕분에 어떤 프로그래밍 언어(단, Managed Code)로 작성된 프로그램이든 실행이 된다. 하지만 소스 보안 측면에서 보면 CIL만 있다면 손쉽게 소스코드를 열어볼 수 있음을 의미한다. (맞겠지...??)

Managed Code로 작성된 프로그램은 .NET Reflector(Reflector를 정복하기 위해서는 남정현님의 블로그[또는 데브피아]를 참조하자. - 최신버전 설명서는 아닌듯!?)를 통해 원본 소스와 거의 비슷하게, 심지어 다른 언어로까지 변환이 된다.

소스 보안을 위해 보통 obfuscator를 사용하곤 하는데 최근 알게된 소프트웨어를 소개할까 한다. GNU GPL 라이선스(무료라는 얘기!)를 가진 Eazfuscator.NET 이다.
Eazfuscator.NET를 사용하면 Reflector를 통해서는 원본 소스를 판단할 수 없게 된다.


사용법을 알아보자.

  1.  Eazfuscator.NET을 설치
  2.  Visual Studio 2008의 Tools Menu에 Add-in 된 것 확인.
  3.  Tool > Eazfuscator.NET Assistant 실행.(그림 1, 2)
  4.  Solution Explorer에서 Obfuscation하길 원하는 Project를(Solution이 아님) 끌어다 녹색바탕에 드래그 앤 드랍.
  5.  Obfuscation이 진행. 그리고 완료.
  6.  Obfuscation이 끝나면VS 2008에서 해당 Project가 변경되었으니 다시 로드할꺼냐고 묻는다. Reload버튼을 눌러주자.
  7.  Release Mode로 두고 Project Build. -끝-


 



그림1. Add-in된 Eazfuscator.NET









 그림 2. Eazfuscator.NET가 실행된 모습






Eazfuscator.NET을 사용한 결과는 아래와 같다.

Before Obfuscation

 After Obfuscation

Analyze the ISO 8583 Protocol


Hypercom T4220 Terminal
Hypercom T4220 Terminal
오른쪽에 보이는 Hypercom 단말기에서 생성되고 전송되는 ISO 8583 Message를 분석하는 Project를 하게되었다. Network팀에서 Wireshark프로그램으로  캡쳐한 패킷을 분석하는 일이다.

먼저 ISO 8583에 대해 알아보자.

ISO 8583이란?
ISO 8583은 위키에서 다음과 같이 정의하고 있다.
ISO 8583 Financial transaction card originated messages — Interchange message specifications is the International Organization for Standardization standard for systems that exchange electronic transactions made by cardholders using payment cards.
한국표준정보망에서는 '금융 거래 카드 발원 메시지-교환 메시지 규격'으로 정의하고 있다.


간단하게 말하면, 신용카드등을 소유하고 있는 Cardholder들이 상품구매나 ATM기기에서 돈을 뽑는 등 Card를 사용할 때 발생하는 Card Transaction 정보를 표준으로 만든 것이다.(적어도 난 그렇게 이해했다 -_ㅠ)




ISO 8583의 구성
ISO 8583은 크게 다음과 같은 3가지로 구성된다.
  • Message Type [2byte]
  • Bitmaps [8byte (if secondary bitmap exist, Bitmap length is 16byte)]
  • Fields
위키의 Introduction에서 보듯이 규격 표준이 정의되어 있지만 각 회사마다 Message Format에 맞춰 Message Type 및 각 Field 들을 재정의해 사용한다.

Message Type은 현재 Message의 제목이나 이름과 같다.
Bitmaps는 현재 Message Type에 어떤 Fields들이 포함되었는지 보여준다.
각 Fields는 현재 Message의 정보를 나타낸다.



실제 Message Format의 예를 보며 ISO 8583 의 구성요소를 파헤쳐보자. (아래 설명요소는 Global Payments EAST Spec을 토대로 분석한 것이다.각 Spec에 따라 분석하는데 차이가 있을 수 있으므로 주의하자.)
  1. Message Type:
        항상 2byte로 Sample에서는 1200을 나타낸다.


  2. Bitmaps [8Byte or 16Byte]:
        아래 Sample의 1st Bitmap은 16진수로 '302004032040000a '이다.
        2진수로 바꾸면 '0011 0000 0010 0000 0000 0100 0000 0011 0010 0000 0100 0000 0000 0000 0000 1010' 이다.
        2진수에서 1로 Set되어있는 부분이 어떤 Field가 포함되어있는지를 나타내준다. 위의 2진수를 보면 3, 4, 11, 22, 31, 32, 35, 42, 61, 63번째 Bit가 1로 Set되어있다.
        각 번호는 Spec에 따로 정의되어 있다.


        2진수에서 첫번째 Bit는 2nd Bitmap의 존재여부를 알려주는 Indicator이다.
        이 Bit가 1로 Set되어 있다면 2nd Bitmap이 존재한다. 예를 들어보면 1st Bitmap의 16진수가 'f02404010040000a'라면  2진수는 다음과 같다.
        '1111 0000 0010 0100 0000 0100 0000 0001 0000 0000 0100 0000 0000 0000 0000 1010'

        첫번째 Bit가 1로 Set되어 있기 때문에 뒤에어 나오는 8byte가 2nd Bitmap이 된다.
        2nd Bitmap의 16진수가 '0000000000000004'이라면 2진수는 다음과 같다.
        '0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100'

        따라서 이 16byte Bitmap에서 나타나는 Field는 (1st Bitmap)1, 2, 3, 4, 11, 14, 22, 32, 42, 61, 63 그리고 (2nd Bitmap)126 이다.


  3. Fields
        아래 Sample을 보면 각 Bit에 대응하는 Field들의 이름이 나와있고 각 Field의 길이와 데이터가 나와있다(이러한 정보는 Spec에 정의되어 있다).

        Sample에서 nibble은 1/2 Byte의 숫자를(용어선택이 어려웠다.. BCD로 했어야 했나?) Ascii 는 1Byte의 ASCII 문자를 나타낸다.

        그리고 LL과 LLL은 가변길이를 나타내는데 LL의 경우 첫번째 1Byte가 길이를 지정하고 LLL의 경우 2Byte가 길이를 지정한다. 길이를 결정할 때에는 주의해야한다. 예를 들어 31번 Field acquirer refer data를 보자. 가변길이 LL에서 길이가 2로 지정되어 있다. 현재 ascii 이라서 2Byte가 데이터를 나타내지만 nibble인 경우는 1Byte를 나타내게 된다.

ISO Packet Sample

표 위의 그림은 Wireshark으로 캡쳐해서 얻어온 결과로써, 왼쪽부터 줄버호, ASCII 코드, 일반String 순으로 나열되어 있다.

그림에 나와있는 표는 따로 만든 ISO Packet Analysis 프로그램을 통해 분석한 결과이다.



참고: Wiki - http://en.wikipedia.org/wiki/ISO_8583
    sanjib-swain Blog - http://sanjib-swain.blogspot.com/2009/06/brief-explanation-of-iso-8583-protocol.html






UCharge(Payment Client Module)

1. 프로그램 설명

카드 트랜잭션 패킷을 분석한 내용을 토대로 PC에서 실제로 트랜잭션을 만들어 보내고 승인받는 결제 프로그램을 만들었습니다. 상용 결제 프로그램은 Global Payments와 같은 벤더에게 트랜잭션 메시지가 제대로 생성됨을 인증받는 1차 테스트를 통과해야 합니다. 이 프로그램은 그 1차 테스트를 통과하기 위한 목적으로 만들어졌고, 제가 근무하는 동안 테스트에 통과했습니다.

다음과 같은 카드 트랜잭션을 지원합니다.

  1. Credit - Purchase, Void, Return, Adjustment , Force, Pre & Post Authorization Transaction
  2. Debit - Purchase and Return Transaction
  3. EBT - Purchase, Return, Balance Inquiry, Boucher Clear Transaction
  4. Balance - End of Day Transaction (Host or UCharge Based) 


TCP/IP 방식 또는 Modem을 이용한 Dial-Up방식으로 트랜잭션을 발생시킬 수 있습니다.


프로그램은 MVP(Model, View, Presenter) 디자인 패턴을 이용해 작성되었습니다







2. 프로젝를 통해 배운점


  • 미국 카드 트랜잭션에 관련된 내용
  • Magnetic Stripe, PIN Pad, Modem 사용법 및 제어방법 
  • AT Command 기초 
  • MVP 디자인 패턴 
  • SSL, TripleDES 기초 개념

.






Serial Port를 이용해 단말기(Vx 570)와 통신하기

Verifone Vx 570

System.IO.Ports.SerialPort Class를사용해
Vx 570 단말기에Message를 보냈을때 LRC(Longitudinal Redundancy Check)를 통해
Response Packet의 이상유무를 체크하는 간단한 프로그램을 만들었다.



만들고 보면 간단한 프로그램이었지만 정작 디버깅을 시작했을 땐 SerialPort의 특성을 제대로 이해하고 있지 못했기에 예상치 못한 결과에 당황하곤 했다.

특히 SerialPort.DataReceived 이벤트의 경우에 메인프로그램과 다른 쓰레드에서 돌아간다. 때문에 DataReceived 이벤트가 호출되었다고 해서 전송받은 데이터를 가지고 바로 다른 로직을 처리하려고 하면 모든 데이터가 전송되지 않았기에 예상치 못한 결과를 얻곤 한다.

현재는 Thread.Sleep 메소드를 이용해 모든 데이터를 받아오는데까지 걸리는 시간을 정했지만 나중에 쓰레딩을 좀 더 잘 다루게 되면 이벤트형식이나 Signal로 딜레이 없이 데이터 전송이 완료되면 다음로직을 실행할 수 있게끔 만들어야겠다.



데이터 전송 패킷은 아래와 같다.
"<stx><msg length(2 Bytes)><message><etx><lrc>"

STX(0x02)와 ETX(0x03)으로 패킷의 시작과 끝을 나타내고 LRC로 현재 패킷이 정상인지 확인한다.

LRC를 구하는 소스는 아래와 같다.
private byte getLRC(byte[] msg)
{
    byte lrc = 0;
 
    for (int i = 1; i < msg.Length - 1; i++)
    {
        lrc ^= msg[i];
    }
    return lrc;
}

LRC는  패킷의 각 바이트를 XOR 해서 나온 수이다.
내가 가진 Spec에서는 STX를 제외한 나머지 Byte만을 계산하라 해서 i = 1 부터 루프를 돌렸다.

4/14/2010

Snippet Compiler

프로그래밍하다보면 간단한 코드조각을 테스트해보고 싶을 때가 많다.
그럴때마다 Console Project를 만들고 실행하기엔 귀찮고 시간이 많이 걸리기에
아래와 같은 프로그램을 사용하면 좀 더 빠르게 테스트를 해볼 수 있을 거라 본다.
가볍고 html 로 import도 되니 블로깅할때도 사용할 수 있겠다 +ㅁ+

그치만. 디버깅이 안된다는게 안습 -_-+  너무 많은걸 바라지 말자 ㅎ

다운로드는 아래 링크 클릭
http://www.sliver.com/dotnet/SnippetCompiler/

3/31/2010

.config 파일을 언제 어디서 불러와야 할까?

말 그대로 config 파일을 언제 어디에서 불러와야 할까!?

  1. 어디에서!?
    처음프로젝트를 만들때에는 이파일 저파일에서 config에 저장되어있는 key값을 불러들였다. 이렇게 하면 나중에 유지보수시에 여러 불편함이 생기는걸 알게 되었다.하나의 key이름을 변경하면 config 파일을 불러온 모든 곳에서 수정을 해주는 등의 수고가 생기는 것이다.

    해서 별도의 Settings Class를 제작해 이곳에서 config 파일의 모든 key값을 불러들이고 다른 곳에서 Settings Class의 Properties를 통해서 접근하는것이 가독성과 유지보수면에서 유용할 것이라는 생각이 들었다.



  2. 언제!?
    MVP 패턴을 이용해 프로젝트를 진행하고 있었는데 이때에는 Presenter의 생성자에서 Settings Class 인스턴스를 생성해 설정값을 읽어들이고 그 값들을 바로 Business Logic에 활용하는 것이 이로울 것이라는 생각을 했다.

    Set할때는 Settings UI에서 Settings Class 인스턴스를 생성해 바꿔주거나 직접 config 파일을 열어 변경해주면 되겠다!!


Config file의 수정및 읽어들이는 방법은 App.config 읽고 쓰기 포스팅을 참조하자.


참고:이 지식들을 신뢰하지 말것..내 자신이 의심스러움 :(

3/05/2010

Credit Card Number Validation

Credit Card Number Validation
카드 번호로 카드 회사 구분하기

주어진 Credit Card 번호로 Card Issuer 결정하기.
Credit Card Number로 Card 발행업체를 알아보자.

위키의 내용을 참고해 만들었다.
위키를 보면 대부분의 major 카드 회사들은 유효한 카드 번호인지 확인하기 위해 Luhn algorithm을 쓴다.

아래소스는 Visa, Master, Amex, DinersClub, Discover, JCB, CUP 카드만을 체크할 수 있다.

//Enum
public enum CardIssuer { None, VISA, MASTER, AMEX, DINERSCLUB, DISCOVER, JCB, CUP }

//Card Issuer 결정하기
public static CardIssuer GetCardIssuer(string cardNum)
{
    if (IsValidVisaCard(cardNum))
        return CardIssuer.VISA;

    if (IsValidMasterCard(cardNum))
        return CardIssuer.MASTER;

    if (IsValidDiscoverCard(cardNum))
        return CardIssuer.DISCOVER;

    if (IsValidAmexCard(cardNum))
        return CardIssuer.AMEX;

    if (IsValidDinersClubCard(cardNum))
        return CardIssuer.DINERSCLUB;

    if (IsValidJcbCard(cardNum))
        return CardIssuer.JCB;

    if (IsValidCupCard(cardNum))
        return CardIssuer.CUP;

    return CardIssuer.None;
}

public static bool IsValidVisaCard(string cardNum)
{
    int cardNumLen = cardNum.Length;
    int cardPrefix = Int32.Parse(cardNum.Substring(0, 1));

    if (cardPrefix != 4)
        return false;

    if (cardNumLen != 13 && cardNumLen != 16)
        return false;

    if (!IsValidCardNumber(cardNum))
        return false;
    
    return true;
}

public static bool IsValidMasterCard(string cardNum)
{
    int cardNumLen = cardNum.Length;
    int cardPrefix = Int32.Parse(cardNum.Substring(0, 2));

    if (cardPrefix < 51 || cardPrefix > 55)
        return false;

    if (cardNumLen != 16)
        return false;

    if (!IsValidCardNumber(cardNum))
        return false;

    return true;
}

public static bool IsValidAmexCard(string cardNum)
{
    int cardNumLen = cardNum.Length;
    int cardPrefix = Int32.Parse(cardNum.Substring(0, 2));

    if (cardPrefix != 34 && cardPrefix != 37)
        return false;

    if (cardNumLen != 15)
        return false;

    if (!IsValidCardNumber(cardNum))
        return false;

    return true;
}

public static bool IsValidDinersClubCard(string cardNum)
{
    int cardNumLen = cardNum.Length;
    int cardPrefix = Int32.Parse(cardNum.Substring(0, 2));

    if (cardPrefix != 36)
        return false;

    if (cardNumLen != 14)
        return false;

    if (!IsValidCardNumber(cardNum))
        return false;

    return true;
}

public static bool IsValidDiscoverCard(string cardNum)
{
    int cardNumLen = cardNum.Length;
    int cardPrefix = Int32.Parse(cardNum.Substring(0, 7));

    if (cardPrefix < 6011000 || cardPrefix > 6011090)
        if(cardPrefix < 6011200 || cardPrefix > 6011490)
            if (cardPrefix < 6011740 || cardPrefix > 6011749)
                if (cardPrefix < 6011770 || cardPrefix > 6011790)
                    if (cardPrefix < 6011860 || cardPrefix > 6011990)
                        if (cardPrefix < 6500000 || cardPrefix > 6509990)
                            return false;

    if (cardNumLen != 14)
        return false;

    if (!IsValidCardNumber(cardNum))
        return false;

    return true;
}

//Global Payments processes JCB as a Private Label card type. Currently, JCB is supported in Private Label 0 or Private Label 1. This value is defined by the merchant's bank.
public static bool IsValidJcbCard(string cardNum)
{
    int cardNumLen = cardNum.Length;
    int cardPrefix = Int32.Parse(cardNum.Substring(0, 6));

    if (cardPrefix < 352800 && cardPrefix > 358999)
        return false;

    if (cardNumLen != 14)
        return false;

    if (!IsValidCardNumber(cardNum))
        return false;

    return true;
}

//CUP(china Union Pay)transactions are processed through an agreement with the Discover Network through Global Payments.
public static bool IsValidCupCard(string cardNum)
{
    int cardNumLen = cardNum.Length;
    int cardPrefix = Int32.Parse(cardNum.Substring(0, 8));

    if (cardPrefix < 62212600 || cardPrefix > 62292599)
        return false;

    if (cardNumLen != 14)
        return false;

    if (!IsValidCardNumber(cardNum))
        return false;

    return true;
}

//Luhn algorithm http://en.wikipedia.org/wiki/Luhn_algorithm
public static bool IsValidCardNumber(string cardNumber)
{
   const string allowed = "0123456789";
   int i;

   StringBuilder cleanNumber = new StringBuilder();
   for (i = 0; i < cardNumber.Length; i++)
   {
      if (allowed.IndexOf(cardNumber.Substring(i, 1)) >= 0)
         cleanNumber.Append(cardNumber.Substring(i, 1));
   }
   if (cleanNumber.Length < 13 || cleanNumber.Length > 16)
      return false;

   for (i = cleanNumber.Length + 1; i <= 16; i++)
      cleanNumber.Insert(0, "0");

   int multiplier, digit, sum, total = 0;
   string number = cleanNumber.ToString();

   for (i = 1; i <= 16; i++)
   {
      multiplier = 1 + (i % 2);
      digit = int.Parse(number.Substring(i - 1, 1));
      sum = digit * multiplier;
      if (sum > 9)
         sum -= 9;
      total += sum;
   }
   return (total % 10 == 0);
}

참고문서:
 Credit Card Number 위키 - http://en.wikipedia.org/wiki/Credit_card_number
 Luhn algorithm - http://en.wikipedia.org/wiki/Luhn_algorithm
 알고리즘 소스 - http://www.codeguru.com/csharp/.net/net_security/authentication/article.php/c12283/

2/27/2010

Base 64 Encoding & Decoding with C#

Base 64 Encoding & Decoding

Base 64 Format으로 인코딩하고 디코딩하는 웹페이지를 만들어달라고 하신다 –_-^
C#으로 한다면 라이브러리의 힘을 빌어 아래 메소드를 살짝쿵 이용해주면 된다.

[FromBase64CharArray, FromBase64String 참조]


그런데 이 간편함과는 아랑곳하지 않고 VB로 짜여진 소스를 C#으로 변환해 달라 하신다…
먼저 VB 소스를 분석해야 했기에 위키[http://en.wikipedia.org/wiki/Base64]에서 간략하게 훑어본 후 소스 분석에 들어갔다.
간략하게 Base64 인코딩을 살펴보면 3문자씩 끊어서 4문자로 만든다.

Base64 인코딩 테이블에는 [a-zA-Z0-9+/] 문자만 쓰인다. 물론 자리수가 모자라면 치환을 위해 = 도 쓰인다.

인코딩 테이블

Value Char Value Char Value Char Value Char
0 A 16 Q 32 g 48 w
1 B 17 R 33 h 49 x
2 C 18 S 34 i 50 y
3 D 19 T 35 j 51 z
4 E 20 U 36 k 52 0
5 F 21 V 37 l 53 1
6 G 22 W 38 m 54 2
7 H 23 X 39 n 55 3
8 I 24 Y 40 o 56 4
9 J 25 Z 41 p 57 5
10 K 26 a 42 q 58 6
11 L 27 b 43 r 59 7
12 M 28 c 44 s 60 8
13 N 29 d 45 t 61 9
14 O 30 e 46 u 62 +
15 P 31 f 47 v 63 /


결국 Encoding Data는 무조건 [원본 * 4/3] 이상의 크기가 되어버린다.

위키의 예처럼 Man 을 TWFu로 바꾸려면

    1. 'Man'을 ASCII 코드(10진수)로 변환. 77(M), 97(a), 110(n).
    2. 위 결과를 Binary(2진수)로 변환. 01001101(M), 01100001(a), 01101110(n)
    3. 위 결과를 6자리씩 끊음. 010011 010110 000101 101110
    4. 위 결과를 Base 64 Encode Table에 매칭시킴. 결과 = 'TWFu'

그러면 M은 어떻게 될까? 우선 결론은 TQ== 이다. 위와 같이 해보자.

    1. 'M'을 ASCII 코드로 변환. *3문자씩 끊어야 하므로~ 77(M)0(null)0(null)
    2. Binary로. 01001101(M), 00000000(null), 00000000(null)
    3. 6자리씩 끊음. 010011, 010000, 000000, 000000
    4. Base 64 Encode Table에 매칭시킴 결과 'TQAA' ? 아니다!!!

한 문자를 Base64로 인코딩하면 뒤에는 무조건 ==로 변환되어 'TQ==' 가 되고,
두 문자를 Base64로 인코딩하면 뒤에는 무조건 =로 변환된다.

그런데 소스를 들여다 보니 byte들을 가지고 놀아서 당장에 때려치고 싶었다. -_-a

그.래.서. -_- 꼼수를 썼다… >_<

VB to C# converter…

http://www.tangiblesoftwaresolutions.com/Product_Details/Instant_CSharp.html

그냥 아주그냥 잘 바꿔준다. 변환이 안되거나 주위를 요하는 것은 주석으로 친절하게 설명도 해주니 알랍.
하지만 구매하지 않으면 100줄 이상의 소스는 번환이 안되므로 참고하자.
이것말고도 홈페이지에 가면 다른 언어간의 변환프로그램도 있으니 한번 둘러보시길.. >_<

 

Base 64 En-Decoding Source

   1: using System.Text;
   2:  
   3: namespace Base64
   4: {
   5:     public class Base64
   6:     {
   7:         #region mask variables
   8:         private const int clOneMask = 16515072;     //000000 111111 111111 111111
   9:         private const int clTwoMask = 258048;       //111111 000000 111111 111111
  10:         private const int clThreeMask = 4032;       //111111 111111 000000 111111
  11:         private const int clFourMask = 63;          //111111 111111 111111 000000
  12:  
  13:         private const int clHighMask = 16711680;    //11111111 00000000 00000000
  14:         private const int clMidMask = 65280;        //00000000 11111111 00000000
  15:         private const int clLowMask = 255;          //00000000 00000000 11111111
  16:         #endregion
  17:  
  18:         #region power of 2 variables
  19:         private const int cl2Exp18 = 262144;    //2 to the 18th power
  20:         private const int cl2Exp12 = 4096;      //2 to the 12th
  21:         private const int cl2Exp6 = 64;         //2 to the 6th
  22:         private const int cl2Exp8 = 256;        //2 to the 8th
  23:         private const int cl2Exp16 = 65536;     //2 to the 16th
  24:         #endregion
  25:  
  26:         #region array variables
  27:         private byte[] cbTransTo = new byte[64];
  28:         private byte[] cbTransFrom = new byte[256];
  29:         private long[] clPowers8 = new long[256];
  30:         private long[] clPowers16 = new long[256];
  31:         private long[] clPowers6 = new long[64];
  32:         private long[] clPowers12 = new long[64];
  33:         private long[] clPowers18 = new long[64];
  34:         #endregion
  35:  
  36:         #region .ctor
  37:         public Base64()
  38:         {
  39:             long lTemp = 0;
  40:  
  41:             for (lTemp = 0; lTemp <= 63; lTemp++)   //Fill the translation table.
  42:             {
  43:                 if (lTemp >= 0 && lTemp <= 25)
  44:                 {
  45:                     cbTransTo[lTemp] = (byte)(65 + lTemp);  //A - Z
  46:                 }
  47:                 else if (lTemp >= 26 && lTemp <= 51)
  48:                 {
  49:                     cbTransTo[lTemp] = (byte)(71 + lTemp);  //a - z
  50:                 }
  51:                 else if (lTemp >= 52 && lTemp <= 61)
  52:                 {
  53:                     cbTransTo[lTemp] = (byte)(lTemp - 4);   //1 - 0
  54:                 }
  55:                 else if (lTemp == 62)
  56:                 {
  57:                     cbTransTo[lTemp] = 43;          //Chr(43) = "+"
  58:                 }
  59:                 else if (lTemp == 63)
  60:                 {
  61:                     cbTransTo[lTemp] = 47;          //Chr(47) = "/"
  62:                 }
  63:             }
  64:  
  65:             for (lTemp = 0; lTemp <= 255; lTemp++)  //Fill the lookup tables.
  66:             {
  67:                 clPowers8[lTemp] = lTemp * cl2Exp8;
  68:                 clPowers16[lTemp] = lTemp * cl2Exp16;
  69:             }
  70:  
  71:             for (lTemp = 0; lTemp <= 63; lTemp++)
  72:             {
  73:                 clPowers6[lTemp] = lTemp * cl2Exp6;
  74:                 clPowers12[lTemp] = lTemp * cl2Exp12;
  75:                 clPowers18[lTemp] = lTemp * cl2Exp18;
  76:             }
  77:  
  78:             for (lTemp = 0; lTemp <= 255; lTemp++)  //Fill the translation table.
  79:             {
  80:                 if (lTemp >= 65 && lTemp <= 90)
  81:                 {
  82:                     cbTransFrom[lTemp] = (byte)(lTemp - 65);    //A - Z
  83:                 }
  84:                 else if (lTemp >= 97 && lTemp <= 122)
  85:                 {
  86:                     cbTransFrom[lTemp] = (byte)(lTemp - 71);    //a - z
  87:                 }
  88:                 else if (lTemp >= 48 && lTemp <= 57)
  89:                 {
  90:                     cbTransFrom[lTemp] = (byte)(lTemp + 4);     //1 - 0
  91:                 }
  92:                 else if (lTemp == 43)
  93:                 {
  94:                     cbTransFrom[lTemp] = 62;        //Chr(43) = "+"
  95:                 }
  96:                 else if (lTemp == 47)
  97:                 {
  98:                     cbTransFrom[lTemp] = 63;        //Chr(47) = "/"
  99:                 }
 100:             }
 101:         }
 102:         #endregion
 103:  
 104:         //String --> Base 64 Format String
 105:         public string Encode(string sString)
 106:         {
 107:             byte[] bTrans = new byte[64];
 108:             byte[] bOut, bIn;
 109:             int iPad = 0;
 110:             long lOutSize = 0;
 111:             long lChar = 0;
 112:             long lTrip = 0;
 113:             long lLen = 0;
 114:             long lTemp = 0;
 115:             long lPos = 0;
 116:  
 117:             iPad = sString.Length % 3;          //Check if the length is divisible by 3 for 3 character
 118:             if (iPad != 0)                      //If not, figure out the end pad and resize the input.
 119:             {
 120:                 iPad = 3 - iPad;
 121:                 sString = sString.PadRight(iPad + sString.Length, '\0');
 122:             }
 123:  
 124:             bIn = Encoding.Default.GetBytes(sString);       //Load the input string.
 125:             lLen = ((bIn.GetUpperBound(0) + 1) / 3) * 4;    //Length of resulting string.
 126:             lOutSize = lLen - 1;                            //Calculate the size of the output buffer.
 127:             bOut = new byte[lOutSize + 1];                  //Make the output buffer.
 128:  
 129:             lLen = 0;                                       //Reusing this one, so reset it.
 130:  
 131:             for (lChar = bIn.GetLowerBound(0); lChar <= bIn.GetUpperBound(0); lChar += 3)
 132:             {
 133:                 lTrip = clPowers16[bIn[lChar]]
 134:                         + clPowers8[bIn[lChar + 1]]
 135:                         + bIn[lChar + 2];                       //Combine the 3 bytes
 136:                 lTemp = lTrip & clOneMask;                      //Mask for the first 6 bits
 137:                 bOut[lPos] = cbTransTo[lTemp / cl2Exp18];       //Shift it down to the low 6 bits and get the value
 138:                 lTemp = lTrip & clTwoMask;                      //Mask for the second set.
 139:                 bOut[lPos + 1] = cbTransTo[lTemp / cl2Exp12];   //Shift it down and translate.
 140:                 lTemp = lTrip & clThreeMask;                    //Mask for the third set.
 141:                 bOut[lPos + 2] = cbTransTo[lTemp / cl2Exp6];    //Shift it down and translate.
 142:                 bOut[lPos + 3] = cbTransTo[lTrip & clFourMask]; //Mask for the low set.
 143:                 
 144:                 lLen = lLen + 4;
 145:                 lPos = lPos + 4;
 146:             }
 147:  
 148:             if (iPad == 1)                  //Add the padding chars if any.
 149:             {
 150:                 bOut[lOutSize] = 61;        //Chr(61) = "="
 151:             }
 152:             else if (iPad == 2)
 153:             {
 154:                 bOut[lOutSize] = 61;
 155:                 bOut[lOutSize - 1] = 61;
 156:             }
 157:  
 158:             return Encoding.Default.GetString(bOut);    // Encoding.Unicode.GetString(bOut);
 159:         }
 160:  
 161:         //Base64 Format String --> String
 162:         public string Decode(string sString)
 163:         {
 164:             byte[] bOut = null;
 165:             byte[] bIn = null;
 166:             int iPad = 0;
 167:             long lQuad = 0;
 168:             long lChar = 0;
 169:             long lPos = 0;
 170:             long lTemp = 0;
 171:             string sOut = null;
 172:  
 173:             lTemp = sString.Length % 4; //Test for valid input.
 174:             if (lTemp != 0)
 175:             {
 176:                 return "Error: It's not base64 format string";
 177:             }
 178:  
 179:             if ((sString.LastIndexOf("==") + 1) != 0) //InStrRev is faster when you know it's at the end.
 180:             {
 181:                 iPad = 2;                               //Note: These translate to 0, so you can leave them...
 182:             }
 183:             else if ((sString.LastIndexOf("=") + 1) != 0) //in the string and just resize the output.
 184:             {
 185:                 iPad = 1;
 186:             }
 187:  
 188:             bIn = Encoding.Default.GetBytes(sString);                   //Load the input byte array.
 189:             bOut = new byte[(((bIn.GetUpperBound(0) + 1) / 4) * 3)];    //Prepare the output buffer.
 190:  
 191:             for (lChar = 0; lChar <= bIn.GetUpperBound(0); lChar += 4)
 192:             {
 193:                 lQuad = clPowers18[cbTransFrom[bIn[lChar]]] + clPowers12[cbTransFrom[bIn[lChar + 1]]] + clPowers6[cbTransFrom[bIn[lChar + 2]]] + cbTransFrom[bIn[lChar + 3]]; //Rebuild the bits.
 194:                 lTemp = lQuad & clHighMask;                 //Mask for the first byte
 195:                 bOut[lPos] = (byte)(lTemp / cl2Exp16);      //Shift it down
 196:                 lTemp = lQuad & clMidMask;                  //Mask for the second byte
 197:                 bOut[lPos + 1] = (byte)(lTemp / cl2Exp8);   //Shift it down
 198:                 bOut[lPos + 2] = (byte)(lQuad & clLowMask); //Mask for the third byte
 199:                 lPos = lPos + 3;
 200:             }
 201:  
 202:             sOut = Encoding.Default.GetString(bOut); //Encoding.Unicode.GetString(bOut); //Convert back to a string.
 203:  
 204:             if (iPad != 0)  //Chop off any extra bytes.
 205:             {
 206:                 sOut = sOut.Substring(0, sOut.Length - iPad);
 207:             }
 208:  
 209:             return sOut;
 210:         }
 211:     }
 212: }

관련 아티클: http://naaams.blogspot.com/2009/12/vb-c-c-java.html
참고: 영문 위키 - http://en.wikipedia.org/wiki/Base64
       한글 위키 - http://ko.wikipedia.org/wiki/Base64
       MSDN - http://msdn.microsoft.com/ko-kr/library/system.convert_methods.aspx