Giải Mã Base64 trong JavaScript: atob() & Buffer
Sử dụng Giải mã Base64 Trực tuyến miễn phí trực tiếp trên trình duyệt — không cần cài đặt.
Dùng thử Giải mã Base64 Trực tuyến trực tuyến →Khi tôi debug một vấn đề xác thực trên môi trường production, thứ đầu tiên tôi dùng là bộ giải mã Base64 — JWT payload, webhook signature, và các giá trị config được mã hóa đều ẩn bên trong chuỗi Base64. JavaScript cung cấp hai cách tích hợp sẵn để base64 decode: atob() (browser + Node.js 16+) và Buffer.from(encoded, 'base64').toString() (Node.js) — và chúng hoạt động rất khác nhau khi dữ liệu gốc chứa ký tự Unicode. Để giải mã nhanh mà không cần viết code, Bộ giải mã Base64 của ToolDeck xử lý ngay lập tức trong trình duyệt của bạn. Hướng dẫn này bao gồm cả hai môi trường — nhắm đến Node.js 16+ và các trình duyệt hiện đại (Chrome 80+, Firefox 75+, Safari 14+) — với các ví dụ sẵn sàng cho production: khôi phục UTF-8, các biến thể URL-safe, giải mã JWT, file, phản hồi API, Node.js streams, và bốn lỗi thường xuyên tạo ra kết quả sai trong các codebase thực tế.
- ✓atob(encoded) có sẵn natively trong trình duyệt và trong Node.js 16+ toàn cục, nhưng trả về một binary string — dùng TextDecoder để khôi phục văn bản UTF-8 từ bất kỳ nội dung nào trên ASCII.
- ✓Buffer.from(encoded, "base64").toString("utf8") là cách tiếp cận chuẩn của Node.js và xử lý UTF-8 tự động mà không cần thêm bước nào.
- ✓URL-safe Base64 (dùng trong JWT) thay + bằng -, / bằng _, và bỏ padding =. Hãy khôi phục các ký tự này trước khi gọi atob(), hoặc dùng Buffer.from(encoded, "base64url").toString() trong Node.js 18+.
- ✓Loại bỏ khoảng trắng và ký tự xuống dòng trước khi giải mã — GitHub Contents API và nhiều MIME encoder ngắt dòng Base64 output tại 60–76 ký tự mỗi dòng.
- ✓Uint8Array.prototype.fromBase64() (TC39 Stage 3) đã có sẵn trong Node.js 22+ và Chrome 130+ và cuối cùng sẽ thống nhất cả hai môi trường.
Base64 Decoding là gì?
Base64 decoding là phép nghịch đảo của encoding — nó chuyển đổi biểu diễn ASCII 64 ký tự trở về dữ liệu nhị phân hoặc văn bản gốc. Mỗi 4 ký tự Base64 ánh xạ ngược lại chính xác 3 byte. Các ký tự padding = ở cuối chuỗi được mã hóa cho decoder biết bao nhiêu byte thêm đã được chèn vào để hoàn thành nhóm 3 byte cuối cùng.
Base64 không phải là mã hóa — thao tác này hoàn toàn có thể đảo ngược bởi bất kỳ ai có chuỗi đã mã hóa. Mục đích của nó là an toàn truyền tải: các giao thức và định dạng lưu trữ được thiết kế cho văn bản ASCII 7 bit không thể xử lý các byte nhị phân tùy ý, và Base64 lấp đầy khoảng trống đó. Các tình huống giải mã JavaScript phổ biến bao gồm kiểm tra JWT payload, giải nén cấu hình JSON được mã hóa Base64 từ biến môi trường, trích xuất nội dung file nhị phân từ REST API, và giải mã data URI trong trình duyệt.
ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=
deploy-bot:sk-prod-a7f2c91e4b3d8
atob() — Hàm Giải Mã Native của Trình Duyệt
atob() (ASCII-to-binary) đã có trong trình duyệt từ IE10 và trở thành biến toàn cục trong Node.js 16.0 như một phần của sáng kiến tương thích WinterCG. Nó cũng hoạt động natively trong Deno, Bun, và Cloudflare Workers — không cần import.
Hàm trả về một binary string: một chuỗi JavaScript trong đó mỗi ký tự có code point bằng một giá trị byte thô (0–255). Điều này quan trọng: nếu dữ liệu gốc là văn bản UTF-8 chứa các ký tự trên U+007F (chữ có dấu, Cyrillic, CJK, emoji), chuỗi được trả về là chuỗi byte thô, không phải văn bản có thể đọc được. Dùng TextDecoder để khôi phục nó (được đề cập ở phần tiếp theo).
Ví dụ tối thiểu hoạt động được
// Decoding an HTTP Basic Auth credential pair received in a request header
// Authorization: Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=
function parseBasicAuth(header: string): { serviceId: string; apiKey: string } {
const base64Part = header.replace(/^Basics+/i, '')
const decoded = atob(base64Part)
const [serviceId, apiKey] = decoded.split(':')
return { serviceId, apiKey }
}
const auth = parseBasicAuth('Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=')
console.log(auth.serviceId) // deploy-bot
console.log(auth.apiKey) // sk-prod-a7f2c91e4b3d8Xác minh round-trip
// Verify lossless recovery for ASCII-only content const original = 'service:payments region:eu-west-1 env:production' const encoded = btoa(original) const decoded = atob(encoded) console.log(encoded) // c2VydmljZTpwYXltZW50cyByZWdpb246ZXUtd2VzdC0xIGVudjpwcm9kdWN0aW9u console.log(decoded === original) // true
atob() và btoa() là một phần của WinterCG Minimum Common API — cùng một spec điều chỉnh Fetch, URL, và crypto trong các runtime không phải trình duyệt. Chúng hoạt động giống hệt nhau trong Node.js 16+, Bun, Deno, và Cloudflare Workers.Khôi Phục Văn Bản UTF-8 Sau Khi Giải Mã
Lỗi phổ biến nhất với atob() là hiểu nhầm kiểu trả về của nó. Khi văn bản gốc được mã hóa UTF-8 trước Base64, atob() trả về binary string Latin-1, không phải văn bản có thể đọc:
// 'Алексей Иванов' was UTF-8 encoded then Base64 encoded before transmission const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy' // ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string — garbled output console.log(atob(encoded)) // "Алексей Р˜РІР°РЅРѕРІ" ← byte values misread as Latin-1
Cách tiếp cận đúng là dùng TextDecoder để diễn giải các byte thô đó dưới dạng UTF-8:
Phương pháp TextDecoder — an toàn với mọi output Unicode
// Unicode-safe Base64 decode utilities
function fromBase64(encoded: string): string {
const binary = atob(encoded)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
function toBase64(text: string): string {
const bytes = new TextEncoder().encode(text)
const chars = Array.from(bytes, byte => String.fromCharCode(byte))
return btoa(chars.join(''))
}
// Works with any language or script
const orderNote = 'Confirmed: 田中太郎 — São Paulo warehouse, qty: 250'
const encoded = toBase64(orderNote)
const decoded = fromBase64(encoded)
console.log(decoded === orderNote) // true
console.log(decoded)
// Confirmed: 田中太郎 — São Paulo warehouse, qty: 250Buffer.from(encoded, 'base64').toString('utf8'). Hàm này tự động diễn giải các byte đã giải mã dưới dạng UTF-8 và nhanh hơn với input lớn.Buffer.from() trong Node.js — Hướng Dẫn Giải Mã Toàn Diện
Trong Node.js, Buffer là API chuẩn cho mọi thao tác nhị phân bao gồm giải mã Base64. Nó xử lý UTF-8 natively, trả về một Buffer thực sự (an toàn nhị phân), và từ Node.js 18 hỗ trợ phím tắt encoding 'base64url' cho các biến thể URL-safe.
Giải mã config từ biến môi trường
// Server config stored as Base64 in an env variable (avoids JSON escaping in shell)
// DB_CONFIG=eyJob3N0IjoiZGItcHJpbWFyeS5pbnRlcm5hbCIsInBvcnQiOjU0MzIsImRhdGFiYXNlIjoiYW5hbHl0aWNzX3Byb2QiLCJtYXhDb25uZWN0aW9ucyI6MTAwfQ==
const raw = Buffer.from(process.env.DB_CONFIG!, 'base64').toString('utf8')
const dbConfig = JSON.parse(raw)
console.log(dbConfig.host) // db-primary.internal
console.log(dbConfig.port) // 5432
console.log(dbConfig.maxConnections) // 100Khôi phục file nhị phân từ file .b64
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
// Read the Base64-encoded certificate and restore the original binary
const encoded = readFileSync(join(process.cwd(), 'dist', 'cert.b64'), 'utf8').trim()
const certBuf = Buffer.from(encoded, 'base64')
writeFileSync('./ssl/server.crt', certBuf)
console.log(`Restored ${certBuf.length} bytes`)
// Restored 2142 bytesGiải mã bất đồng bộ với xử lý lỗi
import { readFile, writeFile } from 'node:fs/promises'
async function decodeBase64File(
encodedPath: string,
outputPath: string,
): Promise<number> {
try {
const encoded = await readFile(encodedPath, 'utf8')
const binary = Buffer.from(encoded.trim(), 'base64')
await writeFile(outputPath, binary)
return binary.length
} catch (err) {
const code = (err as NodeJS.ErrnoException).code
if (code === 'ENOENT') throw new Error(`File not found: ${encodedPath}`)
if (code === 'EACCES') throw new Error(`Permission denied: ${encodedPath}`)
throw err
}
}
// Restore a PDF stored as Base64
const bytes = await decodeBase64File('./uploads/invoice.b64', './out/invoice.pdf')
console.log(`Decoded ${bytes} bytes — PDF restored`)Hàm Giải Mã Base64 — Tham Chiếu Tham Số
Tham chiếu nhanh cho các tham số của hai API giải mã native chính, được định dạng để dùng tra cứu khi viết hoặc review code.
atob(encodedData)
| Tham số | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| encodedData | string | Có | Chuỗi Base64 chuẩn sử dụng các ký tự +, /, =. Các biến thể URL-safe (-, _) ném InvalidCharacterError. Khoảng trắng không được phép. |
Buffer.from(input, inputEncoding) / .toString(outputEncoding)
| Tham số | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
| input | string | Buffer | TypedArray | ArrayBuffer | bắt buộc | Chuỗi được mã hóa Base64 để giải mã, hoặc buffer chứa các byte được mã hóa. |
| inputEncoding | BufferEncoding | "utf8" | Đặt thành "base64" cho Base64 chuẩn (RFC 4648 §4), hoặc "base64url" cho URL-safe Base64 (RFC 4648 §5, Node.js 18+). |
| outputEncoding | string | "utf8" | Encoding cho output .toString(). Dùng "utf8" cho văn bản có thể đọc, "binary" cho binary string Latin-1 tương thích với output atob(). |
| start | integer | 0 | Byte offset trong Buffer đã giải mã để bắt đầu đọc. Truyền vào .toString() làm tham số thứ hai. |
| end | integer | buf.length | Byte offset để dừng đọc (exclusive). Truyền vào .toString() làm tham số thứ ba. |
URL-safe Base64 — Giải Mã JWT và Tham Số URL
JWT sử dụng URL-safe Base64 (RFC 4648 §5) cho cả ba phân đoạn. URL-safe Base64 thay + bằng - và / bằng _, và bỏ padding = ở cuối. Truyền trực tiếp vào atob() mà không khôi phục sẽ tạo ra kết quả sai hoặc ném lỗi.
Browser — khôi phục ký tự và padding trước khi giải mã
function decodeBase64Url(input: string): string {
const base64 = input.replace(/-/g, '+').replace(/_/g, '/')
const padded = base64 + '==='.slice(0, (4 - base64.length % 4) % 4)
const binary = atob(padded)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
// Inspect a JWT payload segment (the middle part between the two dots)
const jwtToken = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9'
const payload = decodeBase64Url(jwtToken)
const claims = JSON.parse(payload)
console.log(claims.userId) // usr_9f2a1c3e8b4d
console.log(claims.role) // editor
console.log(claims.workspaceId) // ws_3a7f91c2Node.js 18+ — encoding 'base64url' native
// Node.js 18 added 'base64url' as a first-class Buffer encoding — no manual replace needed
function decodeJwtSegment(segment: string): Record<string, unknown> {
const json = Buffer.from(segment, 'base64url').toString('utf8')
return JSON.parse(json)
}
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9.SIGNATURE'
const [headerB64, payloadB64] = token.split('.')
const header = decodeJwtSegment(headerB64)
const payload = decodeJwtSegment(payloadB64)
console.log(header.alg) // HS256
console.log(payload.role) // editor
console.log(payload.workspaceId) // ws_3a7f91c2Giải Mã Base64 từ File và Phản Hồi API
Trong code production, giải mã Base64 thường xảy ra nhất khi sử dụng các API bên ngoài trả về nội dung dưới dạng mã hóa. Cả hai tình huống đều có những gotcha quan trọng liên quan đến khoảng trắng và output nhị phân so với văn bản. Nếu bạn chỉ cần kiểm tra phản hồi được mã hóa trong khi debug, hãy dán trực tiếp vào Bộ giải mã Base64 — xử lý ngay lập tức cả chế độ chuẩn và URL-safe.
Giải mã nội dung từ GitHub Contents API
// GitHub Contents API returns file content as Base64, wrapped at 60 chars per line
async function fetchDecodedFile(
owner: string,
repo: string,
path: string,
token: string,
): Promise<string> {
const res = await fetch(
`https://api.github.com/repos/${owner}/${repo}/contents/${path}`,
{ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github.v3+json' } }
)
if (!res.ok) throw new Error(`GitHub API ${res.status}: ${res.statusText}`)
const data = await res.json() as { content: string; encoding: string }
if (data.encoding !== 'base64') throw new Error(`Unexpected encoding: ${data.encoding}`)
// ⚠️ GitHub wraps at 60 chars — strip newlines before decoding
const clean = data.content.replace(/\n/g, '')
return Buffer.from(clean, 'base64').toString('utf8')
}
const openApiSpec = await fetchDecodedFile('acme-corp', 'platform-api', 'openapi.json', process.env.GITHUB_TOKEN!)
const spec = JSON.parse(openApiSpec)
console.log(`API version: ${spec.info.version}`)Giải mã dữ liệu nhị phân Base64 từ API (browser)
// Some APIs return binary content (images, PDFs) as Base64 JSON fields
async function downloadDecodedFile(endpoint: string, authToken: string): Promise<void> {
const res = await fetch(endpoint, { headers: { Authorization: `Bearer ${authToken}` } })
if (!res.ok) throw new Error(`Download failed: ${res.status}`)
const { filename, content, mimeType } = await res.json() as {
filename: string; content: string; mimeType: string
}
// Decode Base64 → binary bytes → Blob
const binary = atob(content)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
const blob = new Blob([bytes], { type: mimeType })
// Trigger browser download
const url = URL.createObjectURL(blob)
const a = Object.assign(document.createElement('a'), { href: url, download: filename })
a.click()
URL.revokeObjectURL(url)
}
await downloadDecodedFile('/api/reports/latest', sessionStorage.getItem('auth_token')!)Giải Mã Base64 qua Command-Line Node.js và Shell
Đối với các script CI/CD, phiên debug, hoặc tác vụ giải mã một lần, công cụ shell và one-liner Node.js nhanh hơn một script đầy đủ. Lưu ý rằng tên flag khác nhau giữa macOS và Linux.
# ── macOS / Linux system base64 ───────────────────────────────────────
# Standard decoding (macOS uses -D, Linux uses -d)
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -d # Linux
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -D # macOS
# Decode a .b64 file to its original binary
base64 -d ./dist/cert.b64 > ./ssl/server.crt # Linux
base64 -D -i ./dist/cert.b64 -o ./ssl/server.crt # macOS
# URL-safe Base64 — restore + and / before decoding
echo "eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ" | tr '-_' '+/' | base64 -d
# ── Node.js one-liner — works on Windows too ───────────────────────────
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64').toString())" "ZGVwbG95LWJvdA=="
# deploy-bot
# URL-safe (Node.js 18+)
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64url').toString())" "eyJhbGciOiJIUzI1NiJ9"
# {"alg":"HS256"}base64 dùng -D để giải mã (D hoa), trong khi Linux dùng -d (chữ thường). Điều này phá vỡ script CI một cách im lặng — hãy dùng one-liner Node.js khi không đảm bảo nền tảng đích là Linux.Thư Viện Hiệu Năng Cao: js-base64
Lý do chính để dùng thư viện là tính nhất quán đa môi trường. Nếu bạn phát hành một package chạy trong cả browser và Node.js mà không cần cấu hình bundler, Buffer cần phát hiện môi trường và atob() cần workaround TextDecoder. js-base64 (100M+ lượt tải npm hàng tuần) xử lý cả hai một cách trong suốt.
npm install js-base64 # or pnpm add js-base64
import { fromBase64, fromBase64Url, isValid } from 'js-base64'
// Standard decoding — Unicode-safe, works in browser and Node.js
const raw = fromBase64('eyJldmVudElkIjoiZXZ0XzdjM2E5ZjFiMmQiLCJ0eXBlIjoiY2hlY2tvdXRfY29tcGxldGVkIiwiY3VycmVuY3kiOiJFVVIiLCJhbW91bnQiOjE0OTAwfQ==')
const event = JSON.parse(raw)
console.log(event.type) // checkout_completed
console.log(event.currency) // EUR
// URL-safe decoding — no manual character replacement needed
const jwtPayload = fromBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciJ9')
const claims = JSON.parse(jwtPayload)
console.log(claims.role) // editor
// Validate before decoding untrusted input
const untrusted = 'not!valid@base64#'
if (!isValid(untrusted)) {
console.error('Rejected: invalid Base64 input')
}
// Binary output — second argument true returns Uint8Array
const pngBytes = fromBase64('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', true)
console.log(pngBytes instanceof Uint8Array) // trueKết Quả Terminal với Tô Màu Cú Pháp
Khi viết công cụ debug CLI hoặc script kiểm tra, output console.log thông thường khó đọc với JSON payload lớn. chalk (package npm được tải nhiều nhất để tô màu terminal) kết hợp với giải mã Base64 tạo ra output terminal dễ đọc, dễ quét — hữu ích cho kiểm tra JWT, debug phản hồi API, và kiểm toán cấu hình.
npm install chalk # chalk v5+ is ESM-only — use import, not require
import chalk from 'chalk'
// Decode and display any Base64 value with smart type detection
function inspectBase64(encoded: string, label = 'Decoded value'): void {
let decoded: string
try {
decoded = Buffer.from(encoded.trim(), 'base64').toString('utf8')
} catch {
console.error(chalk.red('✗ Invalid Base64 input'))
return
}
console.log(chalk.bold.cyan(`\n── ${label} ──`))
// Attempt JSON pretty-print
try {
const parsed = JSON.parse(decoded)
console.log(chalk.green('Type:'), chalk.yellow('JSON'))
for (const [key, value] of Object.entries(parsed)) {
const display = typeof value === 'object' ? JSON.stringify(value) : String(value)
console.log(chalk.green(` ${key}:`), chalk.white(display))
}
return
} catch { /* not JSON */ }
// Plain text fallback
console.log(chalk.green('Type:'), chalk.yellow('text'))
console.log(chalk.white(decoded))
}
// Inspect a Base64-encoded JWT payload
const tokenParts = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsImV4cCI6MTcxNzIwMzYwMH0.SIGNATURE'.split('.')
inspectBase64(tokenParts[0], 'JWT Header')
inspectBase64(tokenParts[1], 'JWT Payload')
// ── JWT Header ──
// Type: JSON
// alg: HS256
// typ: JWT
//
// ── JWT Payload ──
// Type: JSON
// userId: usr_9f2a1c3e8b4d
// role: editor
// exp: 1717203600Giải Mã File Base64 Lớn với Node.js Streams
Khi file được mã hóa Base64 vượt quá ~50 MB, việc tải toàn bộ vào bộ nhớ bằng readFileSync() trở thành vấn đề. Node.js streams cho phép bạn giải mã dữ liệu theo từng phần — nhưng Base64 yêu cầu bội số của 4 ký tự mỗi phần (mỗi nhóm 4 ký tự giải mã đúng 3 byte) để tránh lỗi padding ở ranh giới chunk.
import { createReadStream, createWriteStream } from 'node:fs'
import { pipeline } from 'node:stream/promises'
async function streamDecodeBase64(inputPath: string, outputPath: string): Promise<void> {
const readStream = createReadStream(inputPath, { encoding: 'utf8', highWaterMark: 4 * 1024 * 192 })
const writeStream = createWriteStream(outputPath)
let buffer = ''
await pipeline(
readStream,
async function* (source) {
for await (const chunk of source) {
buffer += (chunk as string).replace(/\s/g, '') // strip any whitespace/newlines
// Decode only complete 4-char groups to avoid mid-stream padding issues
const remainder = buffer.length % 4
const safe = buffer.slice(0, buffer.length - remainder)
buffer = buffer.slice(buffer.length - remainder)
if (safe.length > 0) yield Buffer.from(safe, 'base64')
}
if (buffer.length > 0) yield Buffer.from(buffer, 'base64')
},
writeStream,
)
}
// Decode a 200 MB video that was stored as Base64
await streamDecodeBase64('./uploads/product-demo.b64', './dist/product-demo.mp4')
console.log('Stream decode complete')4 × 1024 × 192 = 786.432 ký tự (768 KB). Với các file dưới 50 MB, readFile() + Buffer.from(content.trim(), 'base64') đơn giản hơn và đủ nhanh.Lỗi Thường Gặp
Tôi đã thấy bốn lỗi này trong các codebase JavaScript nhiều lần — chúng thường ẩn khuất cho đến khi một ký tự không phải ASCII hoặc phản hồi API bị ngắt dòng đến được đường dẫn giải mã trong production.
Lỗi 1 — Dùng atob() không có TextDecoder cho nội dung UTF-8
Vấn đề: atob() trả về binary string trong đó mỗi ký tự là một giá trị byte thô. Chuỗi đa byte UTF-8 (Cyrillic, CJK, ký tự có dấu) xuất hiện dưới dạng ký tự Latin-1 bị lộn xộn. Sửa: bọc output trong TextDecoder.
// ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy' const decoded = atob(encoded) console.log(decoded) // "Алексей Р˜РІР°РЅРѕРІ" ← wrong
// ✅ Use TextDecoder to correctly interpret the UTF-8 bytes const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy' const binary = atob(encoded) const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0)) const decoded = new TextDecoder().decode(bytes) console.log(decoded) // Алексей Иванов ✓
Lỗi 2 — Truyền URL-safe Base64 trực tiếp vào atob()
Vấn đề: Các phân đoạn JWT dùng - và _ thay vì + và /, không có padding. atob() có thể trả về dữ liệu sai hoặc ném lỗi. Sửa: khôi phục ký tự chuẩn và thêm lại padding trước.
// ❌ URL-safe JWT segment passed directly — unreliable const jwtPayload = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ' const decoded = atob(jwtPayload) // May produce wrong result or throw
// ✅ Restore standard Base64 chars and padding first
function decodeBase64Url(input: string): string {
const b64 = input.replace(/-/g, '+').replace(/_/g, '/')
const pad = b64 + '==='.slice(0, (4 - b64.length % 4) % 4)
const bin = atob(pad)
const bytes = Uint8Array.from(bin, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
const decoded = decodeBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ')
// {"userId":"usr_9f2a1c3e"} ✓Lỗi 3 — Không loại bỏ newline từ Base64 bị ngắt dòng
Vấn đề: GitHub Contents API và MIME encoder ngắt dòng Base64 output tại 60–76 ký tự mỗi dòng. atob() ném InvalidCharacterError trên các ký tự \n. Sửa: loại bỏ tất cả khoảng trắng trước khi giải mã.
// ❌ GitHub API content field contains embedded newlines const data = await res.json() const decoded = atob(data.content) // ❌ throws InvalidCharacterError
// ✅ Strip newlines (and any other whitespace) before decoding const data = await res.json() const clean = data.content.replace(/\s/g, '') const decoded = atob(clean) // ✓
Lỗi 4 — Gọi .toString() trên nội dung nhị phân đã giải mã
Vấn đề: Khi dữ liệu gốc là nhị phân (ảnh, PDF, audio), gọi .toString('utf8') thay thế các chuỗi byte không nhận ra bằng U+FFFD, làm hỏng output một cách im lặng. Sửa: giữ kết quả dưới dạng Buffer — đừng chuyển sang string.
// ❌ .toString('utf8') corrupts binary content
import { readFileSync, writeFileSync } from 'node:fs'
const encoded = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const corrupted = Buffer.from(encoded, 'base64').toString('utf8') // ❌
writeFileSync('./out/invoice.pdf', corrupted) // ❌ unreadable PDF// ✅ Keep the Buffer as binary — do not convert to a string
import { readFileSync, writeFileSync } from 'node:fs'
const encoded = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const binary = Buffer.from(encoded, 'base64') // ✓ raw bytes preserved
writeFileSync('./out/invoice.pdf', binary) // ✓ valid PDFPhương Pháp Giải Mã Base64 JavaScript — So Sánh Nhanh
| Phương pháp | Output UTF-8 | Output nhị phân | URL-safe | Môi trường | Cần cài đặt |
|---|---|---|---|---|---|
| atob() | ❌ cần TextDecoder | ✅ binary string | ❌ khôi phục thủ công | Browser, Node 16+, Bun, Deno | Không |
| TextDecoder + atob() | ✅ UTF-8 | ✅ qua Uint8Array | ❌ khôi phục thủ công | Browser, Node 16+, Deno | Không |
| Buffer.from().toString() | ✅ utf8 | ✅ giữ nguyên Buffer | ✅ base64url (Node 18+) | Node.js, Bun | Không |
| Uint8Array.fromBase64() (TC39) | ✅ qua TextDecoder | ✅ native | ✅ tùy chọn alphabet | Chrome 130+, Node 22+ | Không |
| js-base64 | ✅ luôn luôn | ✅ Uint8Array | ✅ tích hợp sẵn | Universal | npm install |
Chọn atob() chỉ khi nội dung đã giải mã được đảm bảo là văn bản ASCII. Với bất kỳ văn bản do người dùng cung cấp hoặc đa ngôn ngữ trong trình duyệt, hãy dùng TextDecoder + atob(). Với code server-side Node.js, Buffer là lựa chọn mặc định đúng — nó xử lý UTF-8 tự động và giữ nguyên dữ liệu nhị phân. Với các thư viện đa môi trường, js-base64 loại bỏ tất cả các trường hợp ngoại lệ.
Câu Hỏi Thường Gặp
Công Cụ Liên Quan
Để giải mã một cú nhấp chuột mà không cần viết code, hãy dán chuỗi Base64 của bạn trực tiếp vào Bộ giải mã Base64 — xử lý cả chế độ chuẩn và URL-safe với kết quả ngay lập tức trong trình duyệt của bạn.
Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.
Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.