우선 인코딩(Encoding)은 암호화와 유사하지만 데이터를 다른 형태로 변환하는 것이다.
그러나 인코딩은 데이터의 기밀성을 고려하지 않으며, 누구나 디코딩하여 원문을 구할 수 있다.
Base64 Encoding이란 8비트 이진 데이터를 각기 다른 문자인코딩에 영향을 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 일련의 문자열로 바꾸는 것을 말한다. 따라서 Base64 로 Encoding 된 문자열은 총64개의 문자(a-z, A-Z, 0-9, +/) 로 이루어져 있다. ( padding 값으로 = 가 포함될 수도 있다.)
평문을 Base64로 인코딩시키는 방법은 아래와 같다.
Base64 동작 방식
1. 각 문자를 ASCII(Decimal = 10진수)로 변환 후, Binary 로 바꾼다. (각 문자는 1byte 이기 때문에 8bit의 크기를 갖는다.)
만약 who 를 10진수로 바꾸면 아래와 같다.
echo -n 'who' | xxd -b -c 1 | cut -d ' ' -f 2-4
2. 8bit 의 Binary 를 나열한다. (구분하기 쉽게 출력한것이고 완전히 붙이면 된다.)
// 보기 좋게
echo -n 'who' | xxd -b -c 1 | cut -d ' ' -f 2 | tr '\n' ' ' | sed -e '1 s/\s//3'
// 원본
echo -n 'who' | xxd -b -c 1 | cut -d ' ' -f 2 | tr -d '\n'
3. 세글자 who. 즉 24bit(3Byte) 를 6bit씩 끊는다.
echo -n 'who' | xxd -b -c 1 | cut -d ' ' -f 2 | tr -d '\n' | fold -w 6 | tr -d '\n'
4. 이 Binary 를 다시 Decimal (10진수) 로 바꾼다.
for i in $(echo -n 'who' | xxd -b -c 1 | cut -d ' ' -f 2 | tr -d '\n' | fold -w 6 | tr '\n' ' '); do echo 'obase=10; ibase=2;' $i | bc; done | tr '\n' ' ' | sed -e '1 s/\s//4'
5. 바꿔준 10진수를 이제 Base64 색인표와 매핑시킨다. Base64 색인표는 아래와 같다.
4번에서 출력된 Decimal 을 왼쪽의 Base64 색인표와 매핑을 해보면 d2hv 이라는 문자열이 출력된다. 이것이 진짜 맞나 확인해보면된다. 리눅스에서 자동으로 바꿔주는 base64를 사용해보면 직접 매핑한 문자열과 동일함을 확인할 수 있다.
Base64 Padding
Base64 인코딩을 하고 나면 가끔 = 문자가 보일 때가 있다.
이 이유는 원본으로 되돌릴 때 원본에는 없던 비트가 생기는 것을 방지하기 위함이다.
앞서 설명한 Base64 동작방식 또한 맞지만, 정확한 Base64 Enocding의 동작원리는 아래와 같다.
우선, 변환할 문자열을 10진수로 바꾼 후 이것을 나열한다.
echo -n 'privilege_escalation' | xxd -b -c 1 | cut -d ' ' -f 2 | tr -d '\n'
Base64 Encoding 은 24bit 버퍼에 우리가 변환한 Binary 의 MSB(최상위비트 = Most Significant Bit)부터 끼워넣는다. 이렇게되면 아래의 Buffer 7과 같이 마지막 24bit 버퍼가 다 채우지 못할 수 있다. 총 (160bit)(앞서 설명한 문자열 who의 정확히 24bit 여서 인코딩 후 padding 문자 = 가 출력되지 않은 것이다. )
echo -n 'privilege_escalation' | xxd -b -c 1 | cut -d ' ' -f 2 | tr -d '\n' | fold -w 24 | awk '{print "Buffer", NR, ":",$0}'
이럴때 Base64Encoding 에서는 남은 자리를 0 으로 채워준다. (Buffer7 의 빈자리를 0으로 채운다.)
echo -n 'privilege_escalation' | xxd -b -c 1 | cut -d ' ' -f 2 | tr -d '\n' | fold -w 24 | awk '{print "Buffer", NR, ":",$0}' | sed -e '7 s/.$/000000000/1'
결과적으로 원래 문자열의 크기인 160Bit 에서 24bit Buffer 에 맞추기위해 8Bit 를 0 으로 채웠으니 168Bit 가 되었다.
이제 본격적으로 6 Bit 씩 자른다. 마지막줄인 Split LIne 28 을 보면 6bit 모두 0으로 채워져있는 것을 확인할 수 있다. 이것이 결과적으로 Base64 Encoding 후 Padding 문자 = 로 인코딩되는 것이다.
echo -n 'privilege_escalation' | xxd -b -c 1 | cut -d ' ' -f 2 | tr -d '\n' | fold -w 24 | awk '{print $0}' | sed -e '7 s/.$/000000000/1' | tr -d '\n' | fold -w 6 | awk '{print "Split Line", NR, ":",$0}' | tail -4
6bit 씩 자른 Binary 를 다시 Decimal 로 치환하고, BaseEncoding 색인표에 매핑시키면된다. 총 28 글자가 나옴을 확인할 수 있다.
for i in $(echo -n 'privilege_escalation' | xxd -b -c 1 | cut -d ' ' -f 2 | tr -d '\n' | fold -w 24 | awk '{print $0}' | sed -e '7 s/.$/000000000/1' | tr -d '\n' | fold -w 6 | tr '\n' ' '); do echo 'obase=10; ibase=2;' $i | bc; done | tr '\n' ' ' | sed -e '1 s/\s//28'
echo '28 7 9 41 29 38 37 44 25 22 29 37 23 54 21 51 24 54 5 44 24 23 17 41 27 54 56 0' | tr ' ' '\n' | wc -l
최종적으로 Base64 Encoding 색인표과 매핑시킨 문자열과 도구를 사용하여 인코딩한 문자열을 비교하면된다.
Base64 색인표과 매핑시키면 아래와같은 문자열이 나온다. 또한 동일하게 27글자임을 확인할 수 있다.
또한, Base64 자동 도구로 인코딩시킨것과 동일한것을 검증할 수 있다.
정리
1. 문자열의 각 문자들을 10진수로 변환하고 그 10 진수를 다시 2진수(Binary) 로 바꾸어 나열한다.
2. 나열된 Binary 데이터의 MSB부터 24Bit 크기의 버퍼만큼 쪼갬.
마지막 버퍼는 24Bit를 다채우지 못할 수 있음. 이렇게 다채우지 못한 버퍼의 각 bit에 0 을 삽입함.
3. padding에 0 을 채웠기 때문에 24Bit 에 딱 맞음. 여기서부터 6Bit 쪼개어 Decimal로 치환
4. Base64 색인표와 매핑하여 각 문자들을 인코딩.
여기서 최종버퍼의 마지막 혹은 마지막 전의 6Bit가 모두 0 으로 채워져있으면 = 로 인코딩됨.
추가정보
Bash 에서 Base64 인코딩, 디코딩
// Encoding
echo -n 'revi1337' | base64
// Decoding
echo -n cmV2aTEzMzc= | base64 -d
Python2 에서 Base64 인코딩 , 디코딩
// Encoding
python2 -c 'import base64; print(base64.b64encode("revi1337"))'
// Decoding
python2 -c 'import base64; print(base64.b64decode("cmV2aTEzMzc="))'
Python3 에서 Base64 인코딩, 디코딩
// Encoding
python3 -c 'import base64; print(base64.b64encode("revi1337".encode()).decode())'
// Decoding
python3 -c 'import base64; print(base64.b64decode("cmV2aTEzMzc=").decode())'
Java 에서 Base64 인코딩 , 디코딩
// Encoding
import java.util.Base64;
import java.util.Base64.Encoder;
public class Base64Encoder{
public static void main(String[] args){
String text = "revi1337";
byte[] byteText = text.getBytes();
Encoder encoder = Base64.getEncoder();
byte[] encodedBytes = encoder.encode(byteText);
System.out.println("Before Encode : " + text);
System.out.println("After Encode : " + new String(encodedBytes));
}
}
// Decoding
import java.util.Base64;
import java.util.Base64.Decoder;
public class Base64Encoder{
public static void main(String[] args){
String encodedBytes = "cmV2aTEzMzc=";
Decoder decoder = Base64.getDecoder();
byte[] decodedBytes = decoder.decode(encodedBytes);
System.out.println("Before decode : " + encodedBytes);
System.out.println("After decode : " + new String(decodedBytes));
}
}
'Cyrpto > Computer Science' 카테고리의 다른 글
CRYPTO - ROT13 (0) | 2022.08.26 |
---|