가. 현상 - 쿠키값 이중 인코딩 로직 발견
1) 기존 방법 - Base64 Encoder + URL Encoder 활용
•
이중 인코딩 및 디코딩 로직을 발견했습니다. base64 인코딩 후 일부 특수문자 처리를 위해 URL 인코딩을 한 번 더 거칩니다. 디코딩의 경우, URL 디코딩 후에 base64 디코딩 과정을 추가적으로 거칩니다.
이중 인코딩 코드 참고
2) 이중 인코딩 원인 - 특수문자 처리 실패
•
base64로 단일 인코딩한 쿠키 값이 브라우저에 저장되지 않습니다.
•
이하 Set-Cookie 필드 참고
HTTP/1.1 200
Vary: origin,access-control-request-method,access-control-request-headers,accept-encoding
Set-Cookie: cookie=Sq09f43ep4dxl2gfmsKvfQ==; Domain=.x.me; SameSite=Lax
Plain Text
복사
•
위의 쿠키값에는 '/' 및 '='이 포함되어 있습니다. 참고로 쿠키값에는 앞의 두 개의 특수문자를 포함하여 @, 쉼표, 마침표 등을 허용하지 않습니다.
•
base64 Index Table을 보면 Index 63에 '/'가 맵핑되고, '='는 인코딩의 끝을 알리는 문자로 사용됩니다.
나. 해결 - 단일 인코딩 대안 및 성능 비교
두 가지 대안 모두 90% 이상의 성능 상 이점이 있었지만, RFC6265을 참고하여 첫번째 대안을 최종적으로 채택했습니다.
1) 대안 1 - Base64 단일 인코딩
•
Base64 라이브러리에 정의된 Base64.getUrlEncoder().withoutPadding() 로 인코더를 생성하면, 문제가 되는 특수문자를 다른 문자로 대체되어 인코딩됩니다.
•
예를 들어, getUrlEncoder()는 '/'을 _로 대체하고, withoutPadding()은 '='를 제거하는 옵션이 추가됩니다. 이를 사용하면 쿠키값에 문제가 되는 특수문자가 모두 대체되거나 제거됩니다.
•
이하의 쿠키값을 보면 문제가 되는 특수문자가 모두 대체되거나 제거된 것을 확인할 수 있습니다.
HTTP/1.1 200
Vary: origin,access-control-request-method,access-control-request-headers,accept-encoding
Set-Cookie: cookie=ZM3VpJqGcJVzWp_hPOgclA; Domain=.x.me; SameSite=Lax
Kotlin
복사
Base64 단일 인코딩 코드 참고
2) 대안 2 - URLEncoder 단일 인코딩
•
URLEncoder만 활용하여 인코딩하는 것이 두번재 대안입니다. URL 인코딩 방식은 unsafe 문자로 분류되는 것에 대해 ‘%xx’의 형태로 인코딩하는 것입니다.
URLEncoder 단일 인코딩 코드 참고
다. Base64 파헤치기
1) Base64란
•
Base64 인코딩은 binary data를 기본적인 문자만으로 이루어진 문자열로 표현하기 위해 설계되었습니다. 여기서 말하는 기본적인 문자란 대소문자 알파벳, 숫자 0-9, 그리고 세 개의 특수문자(+, /, =)를 의미합니다.
•
Base64 인코딩의 목적은 binary data가 시스템에 따라 다르게 해석되는 문자로 표현될 경우, 의도하지 않은 동작을 방지하는 것입니다. 시스템별로 다르게 해석되는 문자의 예로 개행문자를 들 수 있습니다. Unix, MacOS, Windows의 개행문자는 각각 \n, \r, \r\n입니다. 다시 말해, ASCII 코드로 플랫폼 간 통신에 사용하는 것은 부작용을 야기하므로 Base64 방식으로 추가적인 인코딩이 필요합니다.
•
Base64 인코딩 방식은 이메일 등 텍스트 기반 전송 프로토콜을 사용하는 시스템에서 binary data를 추가할 때 사용됩니다. 예를 들어, 이메일에 이미지를 첨부할 때, 이미지는 binary data로 구성되어 있으므로 이를 문자열로 변환한 후에 이메일에 첨부할 수 있습니다. 이 때, Base64 인코딩 방식으로 binary data를 변환한다면 OS별로 다르게 처리될 가능성을 제거할 수 있습니다. 참고로 binary data 전용의 프로토콜에서는 인코딩 과정이 필요 없습니다.
•
HTTP의 일부 기능에서도 Base64 인코딩을 권장합니다. RFC6265에 따르면 웹 클라이언트의 호환성을 고려하여 서버에서 HTTP Cookie의 값에 대해 Base64로 인코딩하여 전달할 것을 권합니다. 브라우저와 같은 웹 클라이언트에 따라 일부 기능에서 특수문자에 대한 허용정책이 다를 수 있기 때문입니다.
•
이하 Base64 Index Table
image from https://en.wikipedia.org/wiki/Base64
2) 인코딩 절차
•
Base64 인코딩의 과정은 결과적으로 ASCII 문자를 Base64 기반의 문자로 변경하는 방향으로 동작합니다. 구체적으로 8비트(1바이트)의 데이터를 6비트 단위로 쪼개고, 이를 네 단위(24비트)로 묶는 방식으로 진행됩니다.
•
인코딩 과정은 다음 네 단계를 거칩니다. 텍스트 콘텐츠 → ASCII Index → 비트 패턴 → Base64 Index → Base64 인코딩
•
24비트를 모두 채울 수 없는 경우 0과 패딩(=)으로 채워 넣습니다.
•
이하 Encoding Process 예시 참고
image from https://en.wikipedia.org/wiki/Base64
image from https://en.wikipedia.org/wiki/Base64
image from https://en.wikipedia.org/wiki/Base64