본문 바로가기

알고리즘

Base64 를 정리해본다면...

Base64 도 내겐 알아두면 항상 까먹고 쓸 때 헷갈리는 것 중에 하나이다.
그제 개발할 때 Base64로 또 삽질했어서 정리해본다.
이제 그만 삽질해야지. 진짜 삽질할 것도 아닌데 신기하게 매번 그런다. 


Base64 인코딩 디코딩은 보통 http 통신으로 바이너리 데이터나 url을 주고받을 때 등 주로 사용된다.

Base64는

RFC 4686에 따르면 Base64 인코딩은 임의의 바이트들을 대소문자 영문 등으로 이루어진 형태로 나타낼 수 있도록 고안되었다.
US-ASCII에서 사용하는 65 글자가 사용되었으며 6비트당 한 글자를 표현한다.
a-z, A-Z, 0-9 , +, / 로 64글자이며, 65번째 = 는 패딩용이다(= 는 아래 추가 서술).

 

3 바이트 = 24비트 = Base64인코딩된 4 글자

인코딩하려는 바이너리를 왼쪽부터 3바이트(24 비트) 단위로 끊어서 인코딩하는 데,
6비트당 Base64 Table에 따라 한 글자씩 매핑되어, 결론적으로 4 글자로 나온다.

아래는 Base64 Table

                      Table 1: The Base 64 Alphabet

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

 

 

 패딩용 =

만약 인코딩 대상이 24비트보다 작다면 남은 비트 자릿수를 0으로 채워 6비트의 형태가 되도록 만들고 = 로 매핑한다. 
이때 아래와 같은 케이스들이 있는 데;, 

- 8 비트일 때, 16비트를 0으로 채우며, 끝 12비트를 == 로 표현한다.
- 16 비트일 때, 8비트를 0으로 채우며, 끝 6비트를 =  로 표현한다.
- 24 비트일 때, = 를 사용하지 않는다.


Base64 Encoding & Decoding 예시

코드는 여기에도 올려두었다

abc라는 문자열을 UTF_8 디코딩해 바이트 어레이를 만들어 바이너리를 확인해보면
01100001 01100010 01100011인데 이걸 앞에서부터 6자리씩 끊어주면
011000 010110 001001 100011이고 이걸 10진수로 바꾸면
24 22 9 35이며 각각을 위의 Base64 Table에서 찾으면 YWJj이다. 
디코딩할 때는 정확히 반대의 과정을 거친다.
그냥 인코딩 디코딩만 하면 되긴 하지만 위의 내용을 눈으로 볼 겸 굳이 굳이 과정들을 넣어두었다.

java16 & mac os 12.2.1 

아래는 자바 코드와 실행했을 때 프린트 문이다.

package testBase64;

import java.nio.charset.StandardCharsets;
import java.util.Base64;


public class Main {
    public static void main(String[] args) {
        System.out.println("============== Start Base 64 Encoding & Decoding ! ==================");

        System.out.print("Target String is ");
        String aString = "abc";

        System.out.print("Byte length of abc is ");
        System.out.println(aString.length());

        byte[] aStringBytes = aString.getBytes(StandardCharsets.UTF_8);

        StringBuilder sb = new StringBuilder();

        for (byte aStringByte : aStringBytes) {
            sb.append(String.format("%8s", Integer.toBinaryString(aStringByte))
                            .replaceAll(" ", "0"));
        }
        System.out.println("abc in Binary is ");
        System.out.println(sb);

        System.out.print("binary length of abc is ");
        System.out.println(sb.length());

        Base64.Encoder encoder = Base64.getEncoder();
        String base64EncodedAString = encoder.encodeToString(aStringBytes);
        byte[] base64EncodedAStringBytes = encoder.encode(aStringBytes);

        System.out.print("Base64 Encoded abc is ");
        System.out.println(base64EncodedAString);

        System.out.print("Base64 Encoded abc length is ");
        System.out.println(base64EncodedAString.length());
        
        Base64.Decoder decoder = Base64.getDecoder();
        
        byte[] decodedAStringBytes = decoder.decode(base64EncodedAStringBytes);

        StringBuilder sb2 = new StringBuilder();

        for (byte aStringByte : decodedAStringBytes) {
            sb2.append(String.format("%8s", Integer.toBinaryString(aStringByte))
                            .replaceAll(" ", "0"));
        }

        System.out.println("Base64 Decoded abc in Binary is ");
        System.out.println(sb2);

        String decodedAString = new String(decodedAStringBytes, StandardCharsets.UTF_8);
        System.out.print("Base64 Decoded abc is ");
        System.out.println(decodedAString);

        System.out.println("============== End Base 64 Encoding & Decoding ! ==================");
    }
}

============== Start Base 64 Encoding & Decoding! ==================
Target String is Byte length of abc is 3
abc in Binary is 
011000010110001001100011
binary length of abc is 24
decimal index per 6 bit is 24 22 9 35 
Base64 Encoded abc is YWJj
Base64 Encoded abc length is 4
Base64 Decoded abc in Binary is 
011000010110001001100011
Base64 Decoded abc is abc
============== End Base 64 Encoding & Decoding  ! ==================

node v16.13.1 & mac os 12.2.1 

같은 내용을 javascript로 구현한 버전이다.
웹에 특화된 언어이다 보니 내장 메서드가 정말 간단하다..!
(자바 코드에서 디코딩된 바이너리를 보여주는 부분을 생략했다)

console.log("============== Start Base 64 Encoding & Decoding ! ==================")

const aString = "abc"
console.log("Byte length of abc is " + aString.length)

let aStringInBinary = ""
const format8bit = (bitInString) => {
    return "0"*(8-bitInString.length) + bitInString
}

for (let i = 0; i < aString.length ; i ++) {
    aStringInBinary += format8bit(aString.charCodeAt(i).toString(2))
}

console.log("abc in binary is " + aStringInBinary)
console.log("binary length of abc is " + aStringInBinary.length)

let aStringIndexPer6Bit = ""
for (let i = 0; i < aStringInBinary.length; i=i+6) {
    aStringIndexPer6Bit += parseInt(aStringInBinary.substr(i, 6), 2).toString() + " "
}
console.log("decimal index per 6 bit is " + aStringIndexPer6Bit)

const encodedAString = btoa(aString)
console.log("Base64 Encoded abc is " + encodedAString)
console.log("Base64 Encoded abc length is " + encodedAString.length)

const decodedAString = atob(encodedAString)
console.log("Base64 Decoded abc is " + decodedAString)

console.log("============== End Base 64 Encoding & Decoding ! ==================")


아래는 같은 내용의 python 버전!
역시 가장 심플..

import base64 

print("============== Start Base 64 Encoding & Decoding ! ==================")
a_string = "abc"
print("Byte length of abc is "+ str(len(a_string)))
a_string_in_bytes = a_string.encode(encoding="utf-8")
a_string_in_binary = ''.join('{:08b}'.format(ord(x)) for x in a_string)

print("abc in binary is " + a_string_in_binary)
print("binary length of abc is " + str(len(a_string_in_binary)))

a_string_index_per_6_bit = ""
for i in range(0, len(a_string_in_binary), 6):
    a_string_index_per_6_bit += str(int(a_string_in_binary[i:i+6], 2))
print("decimal index per 6 bit is " + a_string_index_per_6_bit)

encoded_a_string = base64.b64encode(a_string_in_bytes)
encoded_a_string_in_string = encoded_a_string.decode(encoding="utf-8")
print("Base64 Encoded abc is " + encoded_a_string_in_string)
print("Base64 Encoded abc length is " + str(len(encoded_a_string_in_string)))

decoded_a_string = base64.b64decode(encoded_a_string)
decoded_a_string_in_string = decoded_a_string.decode(encoding="utf-8")
print("Base64 Encoded abc is " + decoded_a_string_in_string )
print("============== End Base 64 Encoding & Decoding ! ==================")

 


코드를 넣다 보니 생각보다 글이 길어졌다. 쨌든 끝! 
아래는 참고한 RFC 4686!

 

 

Information on RFC 4686 » RFC Editor

 

www.rfc-editor.org

 

'알고리즘' 카테고리의 다른 글

UUID  (0) 2021.06.28