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