Base64 ve URL Kodlaması: Amaç, Tuzaklar, Doğru Kullanım
Özet (TL;DR)
echo -n "Hi" | base64 çalıştırın ve tam olarak dört karakter geri alırsınız: SGk=. İki giriş baytı (16 bit) 6 bitlik parçalara dilimlenir, 4 karakterlik bir çıktıya doldurulur ve aritmetiğin tamamı budur. Bu basit kuralı “güvende olmak için her şeyi sarmak” varsayılanı olarak ele alın, maliyet hızla birikir — üç giriş baytı her zaman dört çıkış karakteri olur, bu yaklaşık %33’lük bir şişmedir ve bir hizmetin Base64 ile bir JSON alanına sardığını izlediğim 1,2 MB’lık bir JPEG tel üstünde kabaca 1,64 MB’a yerleşti. Kodlama şifreleme değil, sıkıştırma da değildir. Bir alfabeyi başka bir alfabenin içinde ifade etmek için kurallardır; veriler aksi halde bozulacak bir kanaldan böylece sağ çıkar. Base64 (RFC 4648 §4), rastgele baytları yalnızca metin anlayan sistemlerden — e-posta gövdeleri, JSON dize alanları, satır içi görsel verisi — taşımak için vardır. Base64URL (aynı RFC, §5), sonucu bir URL yoluna, dosya adına veya JWT’ye bırakmayı güvenli kılmak için iki karakteri (+→-, /→_) değiştirir. Yüzde kodlaması (percent-encoding) (RFC 3986) ayrı bir araçtır: URL’lere bir anlam ifade eden karakterleri (?, &, #, boşluk, Unicode) etmeyen %XX dizilerine dönüştürür. Kanala uyan kodlamayı seçin: yol destekliyorsa ikili, metin kanalları için Base64, URL’e gömülü veri için Base64URL ve zaten URL bağlamında olan her şey için yüzde kodlaması. Açığa çıkan Base64 dizeleri, bir dekodere sahip herkes için okunabilir kalır, bu yüzden kodlamayı asla bir gizlilik mekanizması olarak görmeyin.
Arka plan ve kavramlar
Yalnızca metin olan kanallar, onlardan görsel taşımaları istenmeden çok önce vardı. E-posta geleneksel olarak 7-bit ASCII varsayar ve yüksek biti ayarlı baytları ya ayıklar ya bozardı; HTTP başlıkları hâlâ belirli karakterleri kısıtlar; JSON dize alanları UTF-8’dir ama ham kontrol baytlarını (0x00–0x1F) güvenli biçimde tutamaz. RFC 4648’de standartlaşan Base64, her 3 giriş baytını (24 bit) 6 biter 4 çıkış karakterine eşleyerek sorunu çözer. Alfabe 64 karakterdir: A-Z (indeksler 0–25), a-z (26–51), 0-9 (52–61), + (62), / (63) ve çıktı uzunluğunu dörtün katı tutmak için dolgu olarak =. Bir bayt giriş XX== üretir (8 bit → 6+2 dolgulu), iki bayt XXX= üretir (16 bit → 6+6+4 dolgulu) ve üç bayt dolgusuz XXXX üretir.
Base64’ün URL-güvenli varyantı yalnızca 62 ve 63 alfabe indekslerini değiştirir — + - olur, / _ olur — çünkü + eski form gönderimlerinde boşluk olarak çözülür ve / bir yol ayırıcıdır. Birçok URL-güvenli bağlamda = dolgusu da atılır; bu, kompakt bir başlık.yük.imza üçlüsünün yeniden kaçışa uğramadan Authorization: Bearer ... başlığına sığmak zorunda olduğu JSON Web Token’larda (RFC 7519) kullanılan biçimdir.
Yüzde kodlaması (bazen URL kodlaması olarak da anılır) ayrıdır. RFC 3986, iki ASCII kategorisi tanımlar: aynen görünebilen ayrılmamış karakterler (A-Z, a-z, 0-9, -, ., _, ~) ve yapısal olarak bir anlam taşıyan ve sözdizim yerine veri olarak göründüklerinde kodlanması gereken ayrılmış karakterler (:, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =). Bu kümenin dışındaki herhangi bir bayt — UTF-8 ile kodlanmış ASCII olmayan bir karakterin baytları dahil — onaltılık olarak %XX şeklinde yazılır. JavaScript’teki encodeURIComponent, bir bileşen içinde kullanım için hem ayrılmış hem ayrılmamış karakterleri agresifçe kodlar; encodeURI daha izin vericidir, çünkü bir bütün URL geçirdiğinizi varsayar.
Bu üç kodlamanın ince bir sonucu, ürettikleri baytlar rastgele örtüşse bile birbirinin yerine kullanılamamalarıdır. Yüzde kodlu bir sorgu parametresi hâlâ başka ASCII karakterlerini kodlayan ASCII karakterleridir; bunu bir Base64 dekoderinden geçirmek çöp üretir. Bir URL yoluna bırakılan standart bir Base64 dizesi sessizce bozulur; çünkü içindeki / bir yol ayırıcı olarak okunur — bir iş arkadaşımın böyle tek bir karaktere yarım gün kaybettiğini gördüm. Bu karışıklıklar üretimdeki kodlama hatalarının yarısının kaynağıdır.
Karşılaştırma ve veriler
| Özellik | Base64 (standart) | Base64URL | Yüzde kodlaması |
|---|---|---|---|
| Ne zaman kullanılır | Ham ikiliyi reddeden metin kanalı (e-posta MIME, baytların JSON dizesi) | Aynı, ama URL’e, dosya adına veya JWT’ye gömülü | Ayrılmış veya ASCII olmayan karakter içeren tek bir URL bileşeni |
| Alfabe | A–Z a–z 0–9 + / = | A–Z a–z 0–9 - _ (dolgu isteğe bağlı) | Ayrılmamış küme; başka her şey %XX olur |
| Ek yük | Yaklaşık %33 (3 bayt için 4 karakter), MIME’de 76 karakterlik satır kaydırma | Yaklaşık %33, tipik kullanımda dolgu yok | Değişir — 1 bayt UTF-8, 3 ASCII bayta (%XX) dönüşür |
| Bozar | URL’lerde ham + ve /, katı dekoderlerde dolgusuz uzunluk | +/= gerektiren standart Base64 dekoderleri | Çift kodlama (zaten yüzde kodlu veri tekrar kodlanır) |
| Yaygın kullanımlar | MIME ekleri, data URI, JSON içinde satır içi ikili | JWT, URL-güvenli kimlikler, kısa bağlantılar | Sorgu parametreleri, yol segmentleri, form gövdeleri |
Sayılar bütçeleme için önemlidir. Base64 ile kodlanmış 1 MB’lık bir görsel kabaca 1,37 MB olur; MIME’in 76 karakterlik satır kaydırmasıyla bir %2 daha ekleyin. Bu boyut şişmesi bir HTTP yanıtında hem sunucuyu hem istemci ayrıştırıcısını vurur. Yüzde kodlaması dize düzeyinde genellikle daha küçük bir sorundur ama CJK metin için baytları katlayabilir: UTF-8’deki bir Korece karakter 3 bayt alır ve bunlar kodlamadan sonra 9 ASCII karakterine dönüşür. İki karakterli “안녕” sözcüğü bir URL içinde 18 baytlık %EC%95%88%EB%85%95 dizisine dönüşür.
Gerçek senaryolar
Senaryo 1 — E-posta ekleri. Bir PDF, Content-Transfer-Encoding: base64 olan bir MIME parçasının içinde yolculuk eder. Posta istemcisi dosyayı Base64 ile kodlar, satırları 76 karakterde kaydırır (klasik MIME sınırı) ve gönderir. Alıcı süreci tersine çevirir. SMTP’nin yalnızca metin varsayımı, 8BITMIME veya BINARYMIME gibi modern uzantılar var olmadan çok önce Base64’ü burada varsayılan yaptı ve çoğu posta sunucusu hâlâ güvenlik için Base64 yayar. %33’lük ek yük, güvenilir teslimatın bedeli olarak kabul edilir.
Senaryo 2 — JSON Web Token’lar. Bir JWT (RFC 7519) başlık.yük.imza biçimindedir; her segment dolgusuz Base64URL ile kodlanmış JSON veya bayttır. URL-güvenli alfabe, token’ların yeniden kaçışa uğramadan Authorization başlıklarında, access_token sorgu parametrelerinde ve günlük satırlarında görünmesini sağlar. Dolgusuzluk onları kısa tutar. Bir uyarı: JWT’yi çözen herkes iddiaları okuyabilir — kodlama güvenlik değildir; HMAC veya RSA imzası öyledir. Yüke sır koymayın.
Senaryo 3 — Küçük görseller için data URI’leri. background-image: url("data:image/png;base64,iVBORw0K...") bir PNG’yi doğrudan CSS’e satır içine alır. Bu, simge gibi minicik varlıklar için gidiş-dönüşten kaçınır. Ama Base64’ün %33’lük ek yükü ile tarayıcı önbelleği ve paralel istek kaybı, bunu yalnızca ek HTTP gidiş-dönüşünün daha pahalı olacağı kadar küçük varlıklar için kazanç yapar. Deneyimime göre denge noktası kabaca 1–2 KB’tır; bunun üzerinde ayrı önbelleklenmiş bir dosya veya bir SVG genellikle daha hızlıdır.
Senaryo 4 — Bir kullanıcı avatarını yüklemek. Yaygın bir anti-desen, FileReader.readAsDataURL ile bir dosya okumak — data:image/png;base64,... dizesi döndürür — ve sonra o dizeyi bir JSON alanı olarak POST etmektir. Çalışır, ama neredeyse hiçbir zaman doğru şekil değildir: yük şimdi %33 daha büyüktür, sunucunun diske yazmadan önce onu çözmesi gerekir ve her iki taraf da dize biçiminde bellek yakar. Gözlemlediğim bir durumda 5 MB’lık bir görsel yaklaşık 6,7 MB’lık JSON’a şişti ve mobil ağlarda zaman aşımlarına neden oldu; multipart/form-data’ya geçmek aynı dosyayı 5 MB’ta gönderdi. Bu akışta Base64’e yalnızca taşıma katmanı gerçekten bir JSON dizesi gerektirdiğinde — üçüncü taraf bir API’nin multipart kabul etmemesi gibi — uzanın.
Yaygın yanlış anlamalar
“Base64 şifrelemedir.” Değildir. Eşleme RFC 4648’de yayımlanmıştır, alfabe sabittir ve herhangi bir dekoder orijinal baytları döndürür. Kodlama taşımayı korur, içeriği değil. Yük hassassa önce şifreleyin, sonra taşıma için şifreli metni Base64 ile sarın.
“URL kodlaması yalnızca ASCII olmayan karakterler için gereklidir.” Birçok ayrılmış ASCII karakterinin de veri olarak göründüklerinde kodlanması gerekir. & içeren bir sorgu değeri %26 olmalıdır; aksi halde sunucu onu yeni bir parametre olarak ayrıştırır. #, %23 olmalıdır; aksi halde ondan sonraki her şey parça olarak işlenir. Kural karakter kümesine değil, yapıya dayalıdır.
“HTTP’den geçen her şey güvenlik için Base64 ile kodlanmalı.” HTTP ikili gövdeleri mutlu şekilde taşır — Content-Type: application/octet-stream, parçalı aktarım, herhangi bir bayt değeri. Base64, taşımayanlar için bir geçici çözümdür ve kanal zaten baytları işliyorken %33’lük ek yükü ödemek saf vergidir. Dosya yüklemeleri için multipart/form-data veya ham gövde kullanın; Base64’ü kanal gerçekten metin gerektirdiği durumlara (bir JSON alanı, bir URL parametresi, bir MIME gövdesi) ayırın.
“Base64 ve Base64URL birbirinin yerine kullanılabilir.” Kullanılamaz. +/ bekleyen katı bir Base64 dekoderine URL-güvenli bir dize beslemek başarısız olur veya çöp üretir. Kitaplıklar genellikle ikisini de sağlar; kodlayıcıyı dekoderle baştan sona eşleştirin. Node’un Buffer.from(s, 'base64')’ü her iki alfabeyi de kabul eder ama her standart kitaplık bu kadar hoşgörülü değildir.
Kontrol listesi
- Kanal yalnızca 7-bit mi veya yapılandırılmış metin mi? E-posta gövdesi, baytların JSON dizesi, CSS
data:URI → Base64. - Kodlanan değer bir URL’e, dosya adına veya JWT’ye mi gidiyor? Base64URL kullanın ve dolguya izin verilip verilmeyeceğine karar verin.
- Değer zaten bir URL bileşeni mi? Yüzde kodlayın ve yalnızca ihtiyacı olan kısımları.
- Veri büyük mü? Kanalın gerçekten kodlama gerektirip gerektirmediğini düşünün — ham bir ikili gövde %33’lük ek yükten kaçınır.
- Çözme katı mı yoksa izin verici mi? Standart/URL-güvenli alfabeleri baştan sona eşleştirin; karıştırmayın.
- Gizlilik bir hedef mi? Kodlamadan önce şifreleyin. Kodlama tek başına veriyi gizli yapmaz.
İlgili araç
Patrache Studio Base64 kodlayıcı/dekoder hem standart hem URL-güvenli varyantları yerel olarak işler, böylece giriş baytları tarayıcınızdan hiç ayrılmaz — veri bir token veya küçük bir anahtar olduğunda yararlı. Base64 yükü bir JWT ise, çözülen iddiaları temiz incelemek için JSON Biçimlendirme, Doğrulama ve Şema: Pratikte ile eşleştirin. Kodlanmış blob, UUID’den türetilmiş kompakt bir kimlikse, UUID v1, v4 ve v7 Karşılaştırması: Bir Veritabanı Birincil Anahtarı Seçmek Base64URL ile kodladığınız orijinal baytların sıralama ve dizinleme için neden hâlâ önemli olduğunu açıklar.
Kaynaklar
- IETF RFC 4648, “The Base16, Base32, and Base64 Data Encodings” — https://datatracker.ietf.org/doc/html/rfc4648
- IETF RFC 3986, “Uniform Resource Identifier (URI): Generic Syntax” — https://datatracker.ietf.org/doc/html/rfc3986
- MDN, “Base64” sözlük girdisi — https://developer.mozilla.org/en-US/docs/Glossary/Base64
- MDN, “encodeURIComponent()” — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent