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!