레이블이 UMS Intern인 게시물을 표시합니다. 모든 게시물 표시
레이블이 UMS Intern인 게시물을 표시합니다. 모든 게시물 표시

4/29/2010

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 부터 루프를 돌렸다.

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