Base64·URL Encoding อธิบาย: ทำไมต้องใช้และใช้ผิดเมื่อใด

เผยแพร่ 2026-04-13 อ่าน 7 นาที

สรุป (TL;DR)

echo -n "Hi" | base64 คืนค่า 4 อักขระพอดี (SGk=) 2 ไบต์ input (16 bit) ถูกตัดเป็นหน่วย 6 bit กลายเป็น output 4 อักขระ และตำแหน่งสุดท้ายถูกเติมด้วย padding = หนึ่งตัว — การคำนวณ bit จบแค่นั้น ทันทีที่ขยายกฎง่ายๆ นี้เป็น “ห่อไว้ก่อนเพื่อความปลอดภัย” ต้นทุนก็ตามมา อัตรา 3 ไบต์ input ต่อ 4 อักขระ output หมายถึง การพองตัวประมาณ 33% พอดี และใน traffic production หนึ่ง JPEG 1.2MB ที่ห่อด้วย Base64 ถูกบันทึกไว้ที่ประมาณ 1.64MB การ encode ไม่ใช่การเข้ารหัสและไม่ใช่การบีบอัด — เป็นเพียงกฎการแทนข้อมูลจาก alphabet หนึ่งด้วยอีก alphabet หนึ่งให้ผ่านช่องที่ไม่รับ raw ได้ Base64 (RFC 4648 §4) สำหรับช่อง text-only เช่นเนื้อหา email, JSON string field, inline image Base64URL (RFC เดียวกัน §5) เปลี่ยนอักขระ 2 ตัว (+-, /_) ให้เข้าไปใน URL·ชื่อไฟล์·JWT อย่างปลอดภัย Percent encoding (RFC 3986) เป็นเครื่องมือคนละตัว ครอบอักขระ ASCII ที่มีความหมายเชิงโครงสร้างใน URL (?, &, #, space, non-ASCII) เป็น %XX เพื่อ ระบุว่านี่คือข้อมูล เลือกตามช่อง ถ้า binary รองรับใช้ raw ถ้าเป็น text channel ใช้ Base64 ถ้าอยู่ในบริบท URL ใช้ Base64URL ถ้าอยู่ใน URL แล้วใช้ percent encoding และเพราะ alphabet เปิดเผย สตริง Base64 ที่เห็นได้ใครก็ decode ได้ — อย่าใช้เป็นวิธีรักษาความลับ

ภูมิหลังและแนวคิด

ช่องที่รับเฉพาะ text มีมาก่อนที่จะต้องส่งรูปภาพนานแล้ว อีเมล ในอดีตสมมติ 7-bit ASCII และทำลาย byte ที่บิตสูงเปิดอยู่ HTTP header ยังห้ามอักขระบางตัวอยู่ และ JSON string field เป็น UTF-8 แต่เก็บ control byte ดิบ (0x00–0x1F) อย่างปลอดภัยไม่ได้ Base64 ที่กำหนดมาตรฐานใน RFC 4648 แก้ปัญหานี้โดยตัด input 3 ไบต์ (24 bit) เป็น 6 bit พอดีแล้ว map เป็น output 4 อักขระ alphabet 64 ตัว: A-Z (0–25), a-z (26–51), 0-9 (52–61), + (62), / (63) และ padding = ที่เติมให้ความยาวเป็นทวีคูณของ 4 input 1 byte กลายเป็น XX== (8 bit → 6+2 bit), 2 byte กลายเป็น XXX= (16 bit → 6+6+4 bit), 3 byte กลายเป็น XXXX (24 bit → 6+6+6+6 bit) และถ้า 3 byte ไม่ขาดจะไม่มี padding

URL-safe variant เปลี่ยนเฉพาะ index 62 และ 63 ใน alphabet — +-, /_ เพราะใน form submission เก่า + ถูกแปลเป็น space และ / เป็น path separator ในบริบท URL-safe มักละ padding = ด้วย — รูปนี้ใช้ใน JWT (RFC 7519) เพราะโครงสร้าง 3 ส่วน header.payload.signature ต้องใส่ใน Authorization: Bearer ... header อย่างกระชับ

Percent encoding (บางครั้งเรียก URL encoding) ต่างกัน RFC 3986 แบ่ง ASCII เป็นสอง อักขระ unreserved ที่ใช้ตรงๆ ได้ (A-Z, a-z, 0-9, -, ., _, ~) และอักขระ reserved ที่มีความหมายเชิงโครงสร้าง จึง ต้อง encode เมื่อใช้เป็นข้อมูล (:, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =) byte นอกชุดนี้ — รวม non-ASCII ใน UTF-8 — เขียนเป็น %XX เลขฐาน 16 encodeURIComponent ของ JavaScript encode อักขระ reserved อย่างรุนแรงเพื่อใส่ลงใน component เดียวอย่างปลอดภัย ขณะที่ encodeURI สมมติว่าจัดการ URL ทั้งเส้นจึงใจดีกว่า

3 encoding นี้ ไม่สามารถใช้แทนกันได้ แม้ output byte จะดูเหมือนซ้อนกัน query parameter ที่ percent-encoded ก็ยังเป็นเพียงอักขระ ASCII ที่แทนอักขระ ASCII อื่น ส่ง Base64 decoder จะได้ byte ที่ไม่มีความหมาย แปะ Base64 มาตรฐานลง URL path ตรงๆ ก็ path จะขาดตรง / ที่อยู่ข้างใน — เพื่อนร่วมงานของผมเสียเวลาครึ่งวันกับอักขระเดียวนี้ ครึ่งหนึ่งของ bug การ encode ใน production มาจากความสับสนแบบนี้

เปรียบเทียบและข้อมูล

รายการBase64 (มาตรฐาน)Base64URLPercent encoding
ใช้เมื่อtext channel ที่ปฏิเสธ binary (MIME body, byte ใน JSON)เหมือนกัน แต่ใส่ใน URL·ชื่อไฟล์·JWTreserved·non-ASCII ใน URL component
AlphabetA–Z a–z 0–9 + / =A–Z a–z 0–9 - _ (padding ทางเลือก)unreserved + ที่เหลือเป็น %XX
Overheadราว 33% (3 byte → 4 char), MIME เพิ่ม line break ที่ 76 charราว 33%, ปกติไม่มี paddingแปรผัน — 1 byte UTF-8 เป็น 3 char %XX
แตกเมื่อ+·/ ใน URL, padding หายใน decoder เข้มงวดใส่กลับใน decoder มาตรฐานที่ใช้ +/=double encoding (encode ค่าที่ encoded แล้วซ้ำ)
การใช้งานทั่วไปMIME attachment, data URI, binary inline ใน JSONJWT, URL-safe ID, link สั้นquery parameter, path segment, form body

ตัวเลขสำคัญ Base64 encode รูป 1MB ได้ประมาณ 1.37MB รวม MIME line break 76 char เพิ่มอีกราว 2% ใน HTTP response การพองนี้กระทบทั้ง bandwidth ของ server และ parser ของ client Percent encoding ระดับ string มักรุนแรงน้อยกว่า แต่ byte เพิ่มมากใน CJK text อักษรเกาหลี 1 ตัวเป็น 3 byte ใน UTF-8 ซึ่ง encode เป็น 9 byte ASCII (%XX × 3) คำว่า “안녕” (2 อักษร) กลายเป็น %EC%95%88%EB%85%95 (18 byte) ใน URL

สถานการณ์จริง

สถานการณ์ 1 — email attachment PDF ใส่ใน MIME part ด้วย Content-Transfer-Encoding: base64 mail client encode ไฟล์เป็น Base64 ตัดบรรทัดที่ 76 char ตามกฎ MIME เดิมแล้วส่ง ผู้รับย้อนกลับ “สมมติ text” ของ SMTP ทำให้ Base64 เป็นค่าเริ่มต้นโดยพฤตินัยในพื้นที่นี้ และแม้ปัจจุบันจะมี extension อย่าง 8BITMIME·BINARYMIME mail server หลายแห่งยังใช้ Base64 เพื่อความปลอดภัย overhead 33% ถูกยอมรับเป็นค่าใช้จ่ายในการส่งที่แน่นอน

สถานการณ์ 2 — JSON Web Token JWT (RFC 7519) คือ header.payload.signature และแต่ละส่วน encode ด้วย Base64URL โดยไม่มี padding alphabet URL-safe ช่วยให้ token ใส่ใน Authorization header, query parameter, log line ได้โดยไม่ต้อง re-escape การละ padding ช่วยให้ token สั้น คำเตือนหนึ่ง — ใครที่ decode JWT ได้อ่าน claims ได้ การ encode ไม่ใช่การรักษาความปลอดภัย ส่วน HMAC/RSA signature ต่างหากที่เป็น อย่าใส่ข้อมูลความลับใน payload

สถานการณ์ 3 — data URI สำหรับรูปเล็ก background-image: url("data:image/png;base64,iVBORw0K...") inline PNG ลงใน CSS ตรงๆ ช่วยลด round-trip สำหรับ icon เล็กมาก แต่เมื่อพิจารณา overhead 33% ของ Base64 กับการเสีย browser cache และ parallel request ข้อได้เปรียบมีเฉพาะ asset ที่เล็กจน HTTP round-trip แพงกว่า จากประสบการณ์ผม จุดคุ้มทุนอยู่ราว 1–2KB เหนือกว่านั้น ไฟล์แยกหรือ SVG มักเร็วกว่า

สถานการณ์ 4 — อัปโหลดรูป avatar ของผู้ใช้ Anti-pattern ที่พบบ่อยคืออ่านไฟล์ด้วย FileReader.readAsDataURL แปลงเป็น string data:image/png;base64,... แล้ว POST เป็น JSON field ใช้งานได้ แต่แทบไม่ใช่ตัวเลือกที่ถูก payload โตขึ้น 33% server ต้อง Base64 decode อีกครั้งก่อนเขียน disk และ memory ของ client และ server ถูกยึดโดยสำเนา string ในกรณีที่ผมเคยเห็น รูป 5MB พองเป็น JSON 6.7MB และ timeout บน mobile line — เปลี่ยนเป็น multipart/form-data ไฟล์เดียวกันอัปโหลด 5MB ตามเดิม พิจารณา Base64 เฉพาะเมื่อ transport layer ต้องการ text จริงๆ เช่น third-party API ที่รับเฉพาะ JSON string

ความเข้าใจผิดที่พบบ่อย

“Base64 คือการเข้ารหัส” ไม่ใช่ การ map เปิดเผยใน RFC 4648, alphabet คงที่ และ decoder ใดๆ คืน byte ต้นฉบับ การ encode ปกป้อง การส่ง ไม่ได้ปกป้อง เนื้อหา ถ้าข้อมูลอ่อนไหวให้ encrypt ก่อน แล้วห่อ ciphertext ด้วย Base64 สำหรับการส่ง

“URL encoding จำเป็นเฉพาะ non-ASCII” อักขระ reserved ของ ASCII ก็ต้อง encode เมื่อใช้เป็นข้อมูล ปล่อย & ไว้ server ตีความเป็น parameter ใหม่ ปล่อย # ไว้ ส่วนที่เหลือกลายเป็น fragment กฎขึ้นกับ โครงสร้าง ไม่ใช่ “ชุดอักขระ”

“Base64 ปลอดภัยตลอดเมื่อส่งทาง HTTP” HTTP รับ binary body ได้อย่างยินดี — Content-Type: application/octet-stream, chunked transfer, ค่า byte ใดก็ได้ Base64 เป็นทางอ้อมสำหรับช่องที่ทำไม่ได้ การใส่ overhead 33% ลงในช่องที่รองรับ byte อยู่แล้วเป็นภาษีล้วนๆ ไฟล์อัปโหลดใช้ multipart/form-data หรือ raw body ใช้ Base64 เฉพาะเมื่อช่องต้องการ text จริงๆ เช่น JSON field, URL parameter, MIME body

“Base64 และ Base64URL ใช้แทนกันได้” ไม่ได้ ใส่ URL-safe string ลง decoder Base64 เข้มงวดที่คาด +/ จะ fail หรือได้ขยะ ไลบรารีมักให้ทั้งสอง จับคู่ encoder กับ decoder ให้ตรงตั้งแต่ต้นจนจบ — Node Buffer.from(s, 'base64') รับทั้งสอง alphabet แต่ไม่ใช่ทุก standard library ของทุกภาษาใจดีเช่นนั้น

เช็กลิสต์

  1. ช่องเป็น 7-bit หรือ text ที่มีโครงสร้างหรือไม่ email body, byte ใน JSON, CSS data: URI → Base64
  2. ค่าอยู่ใน URL·ชื่อไฟล์·JWT ไหม Base64URL, padding อนุญาตหรือไม่ตาม consumer
  3. ตัวค่าเป็น URL component หรือไม่ percent-encode เฉพาะส่วนที่จำเป็น
  4. ข้อมูลใหญ่ไหม ตรวจสอบก่อนว่าช่องต้องการ text จริงๆ ใช้ raw binary body ประหยัด 33% ได้
  5. Decoder เข้มงวดหรือใจดี จับคู่ alphabet มาตรฐาน/URL-safe ตั้งแต่ต้นจนจบ
  6. เป้าหมายคือการรักษาความลับหรือไม่ Encrypt ก่อน การ encode อย่างเดียวไม่มีวันเป็นความลับ

เครื่องมือที่เกี่ยวข้อง

Base64 Encoder·Decoder ของ Patrache Studio จัดการทั้ง standard และ URL-safe ในเครื่อง byte input ไม่ออกจากเบราว์เซอร์ — มีประโยชน์เมื่อดู token หรือ key เล็กๆ ถ้า payload เป็น JWT ดู ความต่างระหว่าง JSON Formatting·Validation·JSON Schema สำหรับวิธีตรวจ claims ที่ decode แล้วอย่างเรียบร้อย ถ้าค่า Base64URL เป็น ID สั้นที่มาจาก UUID ดู เปรียบเทียบ UUID v1·v4·v7 และการออกแบบ Primary Key ของฐานข้อมูล ว่าทำไมการ sort·index ของ byte ต้นฉบับจึงสำคัญต่อเนื่อง

อ้างอิง