Mọi dự án .NET tôi từng làm đều cần giải mã Base64 ở một thời điểm nào đó — kéo chuỗi kết nối từ Kubernetes secrets, đọc binary payload từ webhook, hoặc kiểm tra JWT token khi debug. Để giải mã Base64 trong C#, phương thức chính là Convert.FromBase64String(), trả về một byte[] rồi bạn truyền vào Encoding.UTF8.GetString() để có văn bản đọc được. Để kiểm tra nhanh mà không cần viết code, công cụ giải mã Base64 của ToolDeck xử lý ngay trong trình duyệt. Hướng dẫn này bao gồm Convert.FromBase64String(), phiên bản span-based TryFromBase64String() cho .NET 5+, API hiệu năng cao System.Buffers.Text.Base64, trích xuất payload JWT, giải mã file và API response, streaming với CryptoStream, và bốn lỗi thường gặp nhất của lập trình viên C#.
- ✓Convert.FromBase64String(s) + Encoding.UTF8.GetString(bytes) là pipeline hai bước chuẩn — hoạt động trên mọi phiên bản .NET.
- ✓Convert.TryFromBase64String() tránh ngoại lệ với đầu vào không hợp lệ và ghi vào Span<byte> — lý tưởng cho hot path trên .NET 5+.
- ✓System.Buffers.Text.Base64.DecodeFromUtf8() cho giải mã không cấp phát bộ nhớ với UTF-8 byte buffer trong các service yêu cầu hiệu năng cao.
- ✓JWT token dùng Base64url (- và _ thay cho + và /) — bạn phải chuẩn hóa đầu vào trước khi gọi Convert.FromBase64String().
- ✓CryptoStream với FromBase64Transform xử lý giải mã streaming cho file lớn mà không cần nạp toàn bộ vào bộ nhớ.
Giải mã Base64 là gì?
Mã hóa Base64 chuyển đổi dữ liệu nhị phân thành bảng chữ cái ASCII 64 ký tự để dữ liệu có thể truyền qua các kênh chỉ hỗ trợ văn bản — trường JSON, HTTP header, nội dung email, thuộc tính XML. Mỗi 3 byte đầu vào trở thành 4 ký tự Base64, đó là lý do đầu ra Base64 luôn lớn hơn khoảng 33% so với bản gốc. Giải mã đảo ngược quá trình này. Ký tự = padding ở cuối cho bộ giải mã biết cần cắt bớt bao nhiêu byte từ nhóm cuối. Một = nghĩa là block cuối có 2 byte; == nghĩa là có 1 byte. Base64 không phải mã hóa — bất kỳ ai cũng có thể đảo ngược nó. Mục đích là truyền tải an toàn qua các kênh làm hỏng dữ liệu nhị phân, không phải bảo mật.
cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ==
redis://cache-prod.internal:6379/session-store
Convert.FromBase64String() — Phương thức giải mã chuẩn
Phương thức Convert.FromBase64String() đã có trong .NET từ thời Framework 1.1. Không cần NuGet, không cần import thêm ngoài System — chỉ cần gọi là có byte[] ngay. Pipeline hai bước để giải mã Base64 thành chuỗi C# luôn như vậy: Convert.FromBase64String() để lấy byte, sau đó Encoding.UTF8.GetString() để diễn giải các byte đó thành văn bản. Điểm cần lưu ý là phương thức trả về byte thô, không phải string. Bạn cần chọn đúngEncoding để chuyển các byte trở lại văn bản, và lựa chọn này quan trọng hơn nhiều người nghĩ. Encoding sai sẽ âm thầm tạo ra mojibake — ký tự rác mà không có ngoại lệ nào cảnh báo.
Ví dụ tối giản
using System; using System.Text; // Chuỗi kết nối được lưu dưới dạng Base64 trong Kubernetes secret string encoded = "cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ=="; byte[] decodedBytes = Convert.FromBase64String(encoded); string connectionString = Encoding.UTF8.GetString(decodedBytes); Console.WriteLine(connectionString); // redis://cache-prod.internal:6379/session-store
Luôn dùng Encoding.UTF8 trừ khi có lý do cụ thể. Runtime .NET biểu diễn string nội bộ theo UTF-16, nhưng hầu hết dữ liệu qua ranh giới hệ thống (API response, config file, secret) đều được mã hóa UTF-8. Dùng Encoding.ASCII trên dữ liệu có ký tự đa byte sẽ âm thầm thay chúng bằng ? — không có ngoại lệ, chỉ có đầu ra bị hỏng.
Kiểm tra vòng lặp
using System; using System.Text; string original = "postgres://db-admin:Kx8!mQ@db-prod.us-east-1.internal:5432/orders"; // Mã hóa string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(original)); Console.WriteLine(encoded); // cG9zdGdyZXM6Ly9kYi1hZG1pbjpLeDghbVFAZGItcHJvZC51cy1lYXN0LTEuaW50ZXJuYWw6NTQzMi9vcmRlcnM= // Giải mã byte[] decoded = Convert.FromBase64String(encoded); string recovered = Encoding.UTF8.GetString(decoded); Console.WriteLine(recovered == original); // True
Chọn đúng Encoding
Encoding bạn truyền vào GetString() phải khớp với encoding được dùng khi mã hóa dữ liệu ban đầu. Chọn sai sẽ ra ký tự rác mà không có ngoại lệ — bộ giải mã vui vẻ tạo ra kết quả vô nghĩa. Dưới đây là tóm tắt thực tế:
Encoding.UTF8— mặc định an toàn. Xử lý được ASCII và toàn bộ Unicode. Dùng cái này trừ khi biết rõ hơn.Encoding.ASCII— chỉ cho dữ liệu ASCII 7-bit thuần túy. Ký tự đa byte thành?.Encoding.Unicode— đây là UTF-16LE trong .NET. Một số chuỗi nội bộ Windows và export PowerShell dùng cái này.Encoding.Latin1— encoding Western European cũ. Xuất hiện trong service SOAP cũ và tích hợp mainframe.
using System;
using System.Text;
// Cùng byte, encoding khác nhau — kết quả khác nhau
byte[] decoded = Convert.FromBase64String("w7bDvMOk");
Console.WriteLine(Encoding.UTF8.GetString(decoded)); // oua (đúng)
Console.WriteLine(Encoding.ASCII.GetString(decoded)); // ?????? (mất dữ liệu)
Console.WriteLine(Encoding.Latin1.GetString(decoded)); // öüä (mojibake)Helper giải mã tái sử dụng với xử lý lỗi
Vì Convert.FromBase64String() ném ngoại lệ với đầu vào xấu và việc loại bỏ khoảng trắng xảy ra liên tục, tôi thường giữ một helper nhỏ trong hầu hết các dự án:
using System;
using System.Text;
static class Base64Helper
{
public static string? DecodeToString(string encoded)
{
if (string.IsNullOrWhiteSpace(encoded))
return null;
// Loại bỏ khoảng trắng mà bộ giải mã .NET từ chối
string cleaned = encoded
.Replace("
", "")
.Replace("
", "")
.Replace(" ", "")
.Trim();
try
{
byte[] bytes = Convert.FromBase64String(cleaned);
return Encoding.UTF8.GetString(bytes);
}
catch (FormatException)
{
return null; // hoặc ném ngoại lệ domain-specific
}
}
}
// Sử dụng
string? decoded = Base64Helper.DecodeToString(" cmVkaXM6Ly9jYWNoZQ== \n");
Console.WriteLine(decoded); // redis://cacheConvert.FromBase64String() ném FormatException nếu đầu vào chứa ký tự ngoài bảng chữ cái Base64 — bao gồm khoảng trắng, dấu xuống dòng, và ký tự URL-safe như - và _. Helper trên tự động xử lý khoảng trắng.Giải mã Base64 thành các kiểu dữ liệu không chuẩn
Bộ giải mã luôn cho bạn byte[]. Bạn làm gì với các byte đó phụ thuộc vào dữ liệu gốc. Đôi khi là GUID lưu dưới dạng 16 byte thô, đôi khi là message protobuf đã serialize, đôi khi là timestamp ở định dạng nhị phân. Dưới đây là các chuyển đổi tôi hay dùng nhất.
Base64 sang GUID
using System; // Một số API gửi GUID dưới dạng Base64 22 ký tự thay vì chuỗi hex 36 ký tự string compactGuid = "C0HqetxMckKlZw4CssPUeQ=="; byte[] guidBytes = Convert.FromBase64String(compactGuid); Guid recovered = new Guid(guidBytes); Console.WriteLine(recovered); // 7aea41c0-4cdc-4272-a567-0e02b2c3d479
Base64 sang JSON deserialize với System.Text.Json
using System;
using System.Text;
using System.Text.Json;
// JSON payload mã hóa Base64 từ message queue
string encoded = "eyJzZXJ2aWNlIjoicGF5bWVudC1nYXRld2F5IiwicmVnaW9uIjoiZXUtd2VzdC0xIiwicmVwbGljYXMiOjR9";
byte[] jsonBytes = Convert.FromBase64String(encoded);
string json = Encoding.UTF8.GetString(jsonBytes);
Console.WriteLine(json);
// {"service":"payment-gateway","region":"eu-west-1","replicas":4}
// Deserialize vào record
var deployEvent = JsonSerializer.Deserialize<DeployEvent>(jsonBytes);
Console.WriteLine($"{deployEvent!.Service} in {deployEvent.Region}");
// payment-gateway in eu-west-1
record DeployEvent(string Service, string Region, int Replicas);Base64 sang chuỗi hex
using System; // Hash SHA-256 lưu dưới dạng Base64 string hashBase64 = "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="; byte[] hashBytes = Convert.FromBase64String(hashBase64); string hex = Convert.ToHexString(hashBytes); Console.WriteLine(hex); // 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08
BinaryFormatter. Nó có lỗ hổng thực thi mã từ xa đã biết và bị loại bỏ trong .NET 8+. Nếu nội dung mã hóa đến từ nguồn bên ngoài, hãy parse dưới dạng JSON hoặc protobuf thay vì dùng .NET binary serialization.Tài liệu tham khảo các phương thức giải mã Base64
.NET cung cấp nhiều phương thức giải mã trên hai namespace. Class Convert xử lý giải mã đa năng, trong khi System.Buffers.Text.Base64 nhắm đến các kịch bản throughput cao khi bạn đang làm việc trực tiếp với buffer byte UTF-8 thô.
Convert.TryFromBase64String() — Giải mã không ném ngoại lệ
Pattern Try là chuẩn trong .NET cho các thao tác có thể thất bại với đầu vào người dùng. Convert.TryFromBase64String(), có từ .NET 5, trả về bool thay vì ném FormatException. Nó ghi các byte đã giải mã vào Span<byte> do người gọi cung cấp, nghĩa là bạn có thể dùng bộ nhớ stack-allocated cho payload nhỏ và bỏ qua hoàn toàn heap.
using System;
using System.Text;
string userInput = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=";
// Stack-allocate cho payload nhỏ (< 1KB là rule of thumb an toàn)
Span<byte> buffer = stackalloc byte[256];
if (Convert.TryFromBase64String(userInput, buffer, out int bytesWritten))
{
string result = Encoding.UTF8.GetString(buffer[..bytesWritten]);
Console.WriteLine(result);
// {"host":"10.0.1.50","port":8443}
}
else
{
Console.WriteLine("Invalid Base64 input — skipping");
}Cách tiếp cận này tỏa sáng trong request middleware hoặc validation pipeline nơi đầu vào xấu là điều bình thường, không phải ngoại lệ. Ném và bắtFormatException với mỗi request không hợp lệ tạo ra overhead đáng kể ở quy mô lớn — TryFromBase64String() tránh hoàn toàn điều đó.
Kiểm tra đầu vào Base64 mà không giải mã
Pattern phổ biến trong API controller: kiểm tra đầu vào có phải Base64 hợp lệ không trước khi chuyển xuống downstream. Bạn có thể dùng TryFromBase64String() như một validator bằng cách cấp phát buffer bỏ đi:
using System;
static bool IsValidBase64(string input)
{
// Tính kích thước giải mã tối đa
Span<byte> buffer = stackalloc byte[((input.Length + 3) / 4) * 3];
return Convert.TryFromBase64String(input, buffer, out _);
}
// Sử dụng trong API controller
Console.WriteLine(IsValidBase64("eyJob3N0IjoiMTAuMC4xLjUwIn0=")); // True
Console.WriteLine(IsValidBase64("not!!valid!!base64")); // False
Console.WriteLine(IsValidBase64("")); // True (chuỗi rỗng là hợp lệ)TryFromBase64String() trả về false ngay cả khi đầu vào hợp lệ. Tính kích thước cần thiết là (inputLength / 4) * 3 để an toàn.Giải mã Base64 từ File và API Response
Đọc file mã hóa Base64 từ đĩa
Certificate, encrypted blob, và file export dữ liệu đôi khi được phân phối dưới dạng văn bản Base64. Pattern thông thường: đọc file dưới dạng string, loại bỏ khoảng trắng hoặc dấu xuống dòng vì chúng gây FormatException, giải mã thành byte, và ghi đầu ra nhị phân. Chú ý xử lý lỗi — lỗi I/O file và lỗi định dạng Base64 nên được bắt riêng.
using System;
using System.IO;
string inputPath = "tls-cert.pem.b64";
string outputPath = "tls-cert.pem";
try
{
string encoded = File.ReadAllText(inputPath).Trim();
// Xóa dấu xuống dòng — bộ giải mã .NET từ chối chúng
encoded = encoded.Replace("
", "").Replace("
", "");
byte[] decoded = Convert.FromBase64String(encoded);
File.WriteAllBytes(outputPath, decoded);
Console.WriteLine($"Decoded {decoded.Length} bytes -> {outputPath}");
}
catch (IOException ex)
{
Console.Error.WriteLine($"File error: {ex.Message}");
}
catch (FormatException ex)
{
Console.Error.WriteLine($"Invalid Base64: {ex.Message}");
}Giải mã trường Base64 từ HTTP API response
Các Cloud API (Azure Key Vault, AWS Secrets Manager, GitHub Contents API) thường trả về dữ liệu nhị phân dưới dạng chuỗi Base64 nhúng trong JSON. Quy trình luôn như nhau: thực hiện HTTP request, parse JSON response, trích xuất trường Base64, và giải mã. Ví dụ dưới đây dùng HttpClient và System.Text.Json — đều tích hợp sẵn trong .NET 6+.
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer sk-prod-9f8e7d6c");
try
{
var response = await client.GetAsync("https://api.example.com/secrets/db-password");
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
// API trả về: {"name":"db-password","value":"cG9zdGdyZXM6eGs5bVAycVI=","version":3}
using var doc = JsonDocument.Parse(body);
string encodedValue = doc.RootElement.GetProperty("value").GetString()!;
byte[] decoded = Convert.FromBase64String(encodedValue);
string secret = Encoding.UTF8.GetString(decoded);
Console.WriteLine($"Secret: {secret}");
// Secret: postgres:xk9mP2qR
}
catch (HttpRequestException ex)
{
Console.Error.WriteLine($"HTTP error: {ex.Message}");
}
catch (FormatException ex)
{
Console.Error.WriteLine($"Base64 decode failed: {ex.Message}");
}catch cho lỗi mạng và FormatException. Gộp chung sẽ khó phân biệt liệu API trả về dữ liệu xấu hay bản thân request thất bại. Trong môi trường production, ghi log giá trị Base64 thô (hoặc ít nhất độ dài và 20 ký tự đầu) khi bắtFormatException — điều đó giúp debug dễ hơn rất nhiều.Giải mã Base64 từ dòng lệnh
Không phải lúc nào bạn cũng cần một project đã compile. Công cụ dotnet-script và PowerShell đều xử lý giải mã Base64 chỉ với một dòng. Để kiểm tra nhanh khi debug, những cách này nhanh hơn tạo console app.
# PowerShell (tích hợp sẵn trên Windows, có trên Linux/macOS)
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0="))
# {"host":"10.0.1.50"}
# Lệnh base64 native trên Linux / macOS
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 --decode
# {"host":"10.0.1.50"}
# macOS dùng -D thay vì --decode
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 -D
# dotnet-script (cài đặt: dotnet tool install -g dotnet-script)
echo 'Console.WriteLine(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0=")));' | dotnet-script eval
# Giải mã và pretty-print JSON với jq
echo "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=" | base64 --decode | jq .Để dán chuỗi mã hóa trực tiếp vào trình duyệt, công cụ giải mã Base64 của ToolDeck xử lý cả dạng chuẩn lẫn URL-safe mà không cần cài đặt gì.
Phương án hiệu năng cao: System.Buffers.Text.Base64
Class System.Buffers.Text.Base64, có từ .NET Core 2.1, hoạt động trên raw UTF-8 byte span thay vì .NET string. Điều này bỏ qua hoàn toàn overhead chuyển đổi string sang byte — không có cấp phát string trung gian, không có bước encoding UTF-16. Tôi dùng nó trong ASP.NET Core middleware khi dữ liệu đến đã là ReadOnlySpan<byte> từ request body. Tạo string chỉ để truyền vào Convert.FromBase64String() nhân đôi số cấp phát vô ích. Trong các bài test BenchmarkDotNet trên .NET 8, phương án span-based nhanh hơn khoảng 2-3 lần với payload dưới 1 KB và khoảng cách tăng lên với đầu vào lớn hơn vì GC pressure không tăng.
using System;
using System.Buffers.Text;
using System.Text;
// Giả lập byte UTF-8 thô từ HTTP request body
byte[] utf8Input = Encoding.UTF8.GetBytes("eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=");
byte[] output = new byte[Base64.GetMaxDecodedFromUtf8Length(utf8Input.Length)];
OperationStatus status = Base64.DecodeFromUtf8(
utf8Input, output, out int bytesConsumed, out int bytesWritten);
if (status == OperationStatus.Done)
{
string result = Encoding.UTF8.GetString(output.AsSpan(0, bytesWritten));
Console.WriteLine(result);
// {"host":"10.0.1.50","port":8443}
Console.WriteLine($"Consumed: {bytesConsumed}, Written: {bytesWritten}");
// Consumed: 44, Written: 31
}
else
{
Console.WriteLine($"Decode failed: {status}");
// Có thể là: InvalidData, DestinationTooSmall, NeedMoreData
}Kiểu trả về là OperationStatus — một enum với bốn giá trị: Done, InvalidData, DestinationTooSmall, và NeedMoreData. Giá trị cuối hữu ích cho giải mã từng phần của dữ liệu streaming. Để đạt zero allocation tuyệt đối, kết hợp với ArrayPool<byte>.Shared.Rent() thay vì new byte[].
DecodeFromUtf8InPlace — ghi đè buffer đầu vào
using System;
using System.Buffers.Text;
using System.Text;
// Buffer đầu vào sẽ bị ghi đè bằng byte đã giải mã
byte[] data = Encoding.UTF8.GetBytes("c2VydmVyLWNvbmZpZw==");
OperationStatus status = Base64.DecodeFromUtf8InPlace(data, out int bytesWritten);
if (status == OperationStatus.Done)
{
Console.WriteLine(Encoding.UTF8.GetString(data.AsSpan(0, bytesWritten)));
// server-config
}DecodeFromUtf8InPlace sửa đổi buffer đầu vào. Đừng dùng nó nếu bạn cần chuỗi Base64 gốc sau đó — bytesWritten byte đầu tiên của mảng giờ chứa dữ liệu đã giải mã, và phần còn lại là rác.Một điều cần lưu ý: System.Buffers.Text.Base64 không xử lý trực tiếp Base64 URL-safe. Nếu đầu vào dùng ký tự - và _ (ví dụ JWT token), bạn vẫn cần thay chúng bằng + và / trước khi gọi các phương thức này. Các API span-based tuân thủ nghiêm ngặt bảng chữ cái chuẩn RFC 4648. Không có phiên bản DecodeFromUtf8Url — một khoảng trống đáng ngạc nhiên khi Base64url rất phổ biến trong các API hiện đại.
Đầu ra terminal với syntax highlighting
Thư viện Spectre.Console cung cấp đầu ra terminal phong phú bao gồm JSON highlighting — hữu ích khi xây dựng CLI tool giải mã Base64 và hiển thị kết quả. Cài đặt với dotnet add package Spectre.Console.
using System;
using System.Text;
using Spectre.Console;
using Spectre.Console.Json;
string encoded = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0MywibWF4Q29ubiI6MTAwfQ==";
byte[] decoded = Convert.FromBase64String(encoded);
string json = Encoding.UTF8.GetString(decoded);
// Pretty-print với syntax highlighting trong terminal
AnsiConsole.Write(new JsonText(json));
// Xuất ra JSON có màu:
// {
// "host": "10.0.1.50",
// "port": 8443,
// "maxConn": 100
// }Điều này đặc biệt tiện cho CLI tool lấy và giải mã configuration từ remote service. Giải mã Base64 config, deserialize sang JSON, và in đầu ra có highlight — chỉ vài dòng code. Spectre.Console còn có table rendering, progress bar, và tree view nếu bạn cần hiển thị cấu trúc dữ liệu đã giải mã phức tạp hơn trên terminal.
Streaming file Base64 lớn với CryptoStream
Nạp file Base64 500 MB bằng File.ReadAllText() và sau đó gọi Convert.FromBase64String() cấp phát khoảng 700 MB heap: bản thân string (UTF-16, tức gấp đôi kích thước file) cộng với mảng byte đã giải mã. Kết hợp CryptoStream + FromBase64Transform giải mã theo từng chunk, giữ bộ nhớ ổn định.
using System.IO;
using System.Security.Cryptography;
string inputPath = "database-export.sql.b64";
string outputPath = "database-export.sql";
using var inputStream = new FileStream(inputPath, FileMode.Open, FileAccess.Read);
using var transform = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
using var base64Stream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read);
using var outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
base64Stream.CopyTo(outputStream);
Console.WriteLine($"Decoded {new FileInfo(outputPath).Length} bytes -> {outputPath}");Flag FromBase64TransformMode.IgnoreWhiteSpaces xử lý Base64 ngắt dòng (PEM file, email export) mà không cần làm sạch thủ công. Không có flag này, dấu xuống dòng trong đầu vào gây ra FormatException. Đây là tương đương gần nhất của .NET với getMimeDecoder() của Java — nó lặng lẽ bỏ qua khoảng trắng trong quá trình giải mã.
Tên CryptoStream có thể gây hiểu lầm — Base64 không có gì liên quan đến mật mã. Microsoft đặt FromBase64Transform trong namespace System.Security.Cryptography vì nó implement ICryptoTransform, interface tương tự dùng cho AES và các cipher transform khác. Bản thân stream chỉ đưa dữ liệu qua bất kỳ transform nào theo từng chunk. Hãy nghĩ nó như một pipeline transform streaming đa năng tình cờ nằm sai namespace.
Async streaming cho các kịch bản ASP.NET Core
using System.IO;
using System.Security.Cryptography;
async Task DecodeStreamAsync(Stream inputStream, string outputPath)
{
using var transform = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
using var base64Stream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read);
await using var outputStream = new FileStream(
outputPath, FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 81920, useAsync: true);
await base64Stream.CopyToAsync(outputStream);
}
// Sử dụng trong endpoint:
// await DecodeStreamAsync(Request.Body, "uploaded-file.bin");CopyTo sang CopyToAsync trong ASP.NET Core handler. I/O đồng bộ trên request thread mặc định bị chặn trong Kestrel và ném InvalidOperationException.Cách giải mã payload JWT Base64 trong C#
JWT có ba đoạn mã hóa Base64url cách nhau bằng dấu chấm. Đoạn giữa là payload. Bạn có thể giải mã mà không cần kéo vào thư viện JWT — tách theo ., chuẩn hóa ký tự Base64url, sửa padding, và gọi Convert.FromBase64String(). Điều này làm hầu như tất cả mọi người vấp ngã lần đầu vì JWT dùng - và _ thay cho + và /, và bỏ padding =.
using System;
using System.Text;
static string DecodeJwtPayload(string token)
{
string[] parts = token.Split('.');
if (parts.Length != 3)
throw new ArgumentException($"Invalid JWT: expected 3 segments, got {parts.Length}");
// Lấy payload (đoạn thứ hai)
string payload = parts[1];
// Thay ký tự URL-safe bằng Base64 chuẩn
payload = payload.Replace('-', '+').Replace('_', '/');
// Thêm padding cho bội số của 4
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] bytes = Convert.FromBase64String(payload);
return Encoding.UTF8.GetString(bytes);
}
// Kiểm tra với token có hình dạng thực
string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
+ ".eyJzdWIiOiJ1c3ItNjcyIiwiaXNzIjoiYXV0aC5leGFtcGxlLmNvbSIsImV4cCI6MTc0MTk1NjgwMCwicm9sZXMiOlsiYWRtaW4iLCJiaWxsaW5nIl19"
+ ".SIGNATURE_PLACEHOLDER";
Console.WriteLine(DecodeJwtPayload(token));
// {"sub":"usr-672","iss":"auth.example.com","exp":1741956800,"roles":["admin","billing"]}Lưu ý nhanh: cách này chỉ đọc payload. Nó không xác minh chữ ký. Để xác thực auth trong production, dùng thư viện như Microsoft.IdentityModel.JsonWebTokens. Nhưng để debug, logging, và test assertion, cách thủ công này là đủ.
Parse payload JWT đã giải mã thành object có kiểu
Sau khi có chuỗi JSON, deserialize với System.Text.Json để truy cập từng claim mà không cần thao tác chuỗi:
using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
record JwtPayload(
[property: JsonPropertyName("sub")] string Subject,
[property: JsonPropertyName("iss")] string Issuer,
[property: JsonPropertyName("exp")] long Expiration,
[property: JsonPropertyName("roles")] string[] Roles
);
// Sau DecodeJwtPayload() ở trên
string json = DecodeJwtPayload(token);
var claims = JsonSerializer.Deserialize<JwtPayload>(json)!;
Console.WriteLine($"User: {claims.Subject}");
Console.WriteLine($"Issuer: {claims.Issuer}");
Console.WriteLine($"Expires: {DateTimeOffset.FromUnixTimeSeconds(claims.Expiration):u}");
Console.WriteLine($"Roles: {string.Join(", ", claims.Roles)}");
// User: usr-672
// Issuer: auth.example.com
// Expires: 2025-03-14 12:00:00Z
// Roles: admin, billingLỗi thường gặp
Tôi đã gặp tất cả những lỗi này trong các service C# production. Hai lỗi đầu chiếm đa số các bug liên quan đến Base64 tôi thấy trong code review. Mỗi lỗi trông rõ ràng khi tách riêng ra, nhưng dễ bỏ qua khi bạn đang chìm đắm trong tính năng lớn hơn và giải mã Base64 chỉ là một bước trong pipeline.
Vấn đề: Chuỗi Base64 đọc từ config file, biến môi trường, hoặc user input thường có dấu xuống dòng ở cuối. Convert.FromBase64String() từ chối bất kỳ ký tự nào ngoài bảng chữ cái Base64, bao gồm \r\n.
Giải pháp: Gọi .Trim() hoặc .Replace() để loại bỏ khoảng trắng trước khi giải mã.
// Biến môi trường có dấu xuống dòng ở cuối
string encoded = Environment.GetEnvironmentVariable("DB_PASS_B64")!;
byte[] decoded = Convert.FromBase64String(encoded);
// FormatException: The input is not a valid Base-64 stringstring encoded = Environment.GetEnvironmentVariable("DB_PASS_B64")!;
string cleaned = encoded.Replace("\n", "").Replace("\r", "").Trim();
byte[] decoded = Convert.FromBase64String(cleaned);
Console.WriteLine(Encoding.UTF8.GetString(decoded));Vấn đề: JWT token và một số API payload dùng - và _ (bảng chữ cái URL-safe). Bộ giải mã chuẩn chỉ chấp nhận + và / — nó ném FormatException ngay ký tự - đầu tiên.
Giải pháp: Thay - bằng + và _ bằng / trước khi giải mã. Cũng cần sửa padding.
// JWT payload — dùng Base64 URL-safe string payload = "eyJzdWIiOiJ1c3ItNjcyIn0"; byte[] decoded = Convert.FromBase64String(payload); // FormatException
string payload = "eyJzdWIiOiJ1c3ItNjcyIn0";
payload = payload.Replace('-', '+').Replace('_', '/');
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] decoded = Convert.FromBase64String(payload);
Console.WriteLine(Encoding.UTF8.GetString(decoded));
// {"sub":"usr-672"}Vấn đề: Gọi Encoding.UTF8.GetString() trên byte ảnh hoặc protobuf đã giải mã tạo ra ký tự rác. Tệ hơn, chuyển string đó trở lại thành byte âm thầm làm hỏng dữ liệu vì các chuỗi UTF-8 không hợp lệ bị thay thế.
Giải pháp: Giữ dữ liệu nhị phân dạng byte[] trong toàn bộ pipeline. Chỉ gọi GetString() khi biết chắc nội dung là văn bản.
byte[] decoded = Convert.FromBase64String(pngBase64);
string imageStr = Encoding.UTF8.GetString(decoded); // làm hỏng nhị phân
File.WriteAllText("image.png", imageStr); // file bị hỏngbyte[] decoded = Convert.FromBase64String(pngBase64);
// Ghi trực tiếp byte — không chuyển đổi sang string
File.WriteAllBytes("image.png", decoded);Vấn đề: Một số API .NET Framework cũ mặc định theo code page ANSI hiện tại của hệ thống, khác nhau giữa các máy. Server Windows với code page 1252 và Linux container với UTF-8 tạo ra string khác nhau từ cùng byte.
Giải pháp: Luôn chỉ định Encoding.UTF8 rõ ràng. Không bao giờ dựa vào mặc định của nền tảng.
byte[] decoded = Convert.FromBase64String(encoded); // Encoding.Default khác nhau giữa các nền tảng string result = Encoding.Default.GetString(decoded);
byte[] decoded = Convert.FromBase64String(encoded); string result = Encoding.UTF8.GetString(decoded); // nhất quán trên Windows, Linux, macOS
So sánh các phương thức
.NET cung cấp nhiều phương thức giải mã Base64 hơn nhiều lập trình viên nhận ra. Bảng dưới đây bao gồm tất cả các tùy chọn tích hợp sẵn cộng với hai phương án bên thứ ba phổ biến nhất. Cột "Cấp phát" quan trọng nhất trong các service throughput cao — phương thức cấp phát byte[] mới mỗi lần gọi tạo áp lực lên GC trong vòng lặp chặt.
Cho công việc thông thường: Convert.FromBase64String(). Cho hot path khi bạn validate user input: TryFromBase64String(). Cho ASP.NET Core middleware xử lý raw request byte: Base64.DecodeFromUtf8(). Cho file lớn: CryptoStream + FromBase64Transform. BouncyCastle chỉ hợp lý nếu đã có trong dependency tree của bạn cho các thao tác mật mã khác.
Để kiểm tra nhanh mà không cần compile gì, công cụ giải mã Base64 online nhanh hơn viết console app một lần dùng.
Câu hỏi thường gặp
Làm thế nào để giải mã chuỗi Base64 thành văn bản trong C#?
Gọi Convert.FromBase64String() để lấy mảng byte, sau đó truyền vào Encoding.UTF8.GetString(). Encoding phải khớp với encoding đã dùng khi mã hóa — UTF-8 là lựa chọn an toàn mặc định cho hầu hết các hệ thống hiện đại. Nếu đầu vào có thể chứa khoảng trắng hoặc dấu xuống dòng, hãy gọi .Trim() hoặc loại bỏ chúng trước khi giải mã.
using System; using System.Text; string encoded = "cG9zdGdyZXM6eGs5bVAycVI="; byte[] bytes = Convert.FromBase64String(encoded); string result = Encoding.UTF8.GetString(bytes); Console.WriteLine(result); // postgres:xk9mP2qR
Sự khác biệt giữa Convert.FromBase64String() và Convert.TryFromBase64String() là gì?
FromBase64String() ném FormatException khi đầu vào không hợp lệ. TryFromBase64String() trả về bool và ghi kết quả vào Span<byte> do người gọi cung cấp, phù hợp cho các hot path muốn tránh overhead của ngoại lệ. TryFromBase64String() yêu cầu .NET 5 trở lên.
using System;
string input = "maybe-not-valid-base64!!";
Span<byte> buffer = stackalloc byte[256];
if (Convert.TryFromBase64String(input, buffer, out int written))
Console.WriteLine($"Decoded {written} bytes");
else
Console.WriteLine("Invalid Base64 input");Làm thế nào để giải mã payload JWT Base64url trong C#?
Tách token theo dấu chấm, lấy đoạn thứ hai, thay - bằng + và _ bằng /, thêm padding = cho đến khi độ dài là bội số của 4, sau đó gọi Convert.FromBase64String(). JWT dùng bảng chữ cái Base64 URL-safe mà bộ giải mã chuẩn của .NET không xử lý trực tiếp.
string token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3ItNjcyIn0.SIG";
string payload = token.Split('.')[1];
payload = payload.Replace('-', '+').Replace('_', '/');
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] bytes = Convert.FromBase64String(payload);
Console.WriteLine(Encoding.UTF8.GetString(bytes));
// {"sub":"usr-672"}Làm thế nào để chọn đúng Encoding khi giải mã Base64 trong C#?
Dùng Encoding.UTF8 làm mặc định — nó xử lý được ASCII và các ký tự Unicode đa byte. Chỉ dùng Encoding.ASCII khi chắc chắn dữ liệu là ASCII 7-bit thuần túy. Dùng Encoding.Unicode (là UTF-16LE trong .NET) chỉ khi dữ liệu gốc được mã hóa theo UTF-16, điều này đôi khi xảy ra với chuỗi nội bộ Windows và các export từ PowerShell.
Tại sao Convert.FromBase64String() ném FormatException?
Ba nguyên nhân thường gặp: đầu vào chứa khoảng trắng hoặc dấu xuống dòng (loại bỏ trước khi giải mã), đầu vào dùng ký tự URL-safe như - và _ (thay bằng + và / tương ứng), hoặc padding thiếu hoặc sai (độ dài tổng phải là bội số của 4 sau khi thêm padding). Khác với Java, .NET không có bộ giải mã MIME tích hợp chấp nhận khoảng trắng — bạn phải tự làm sạch đầu vào hoặc dùng CryptoStream với FromBase64Transform và chế độ IgnoreWhiteSpaces.
// Xử lý khoảng trắng
string cleaned = rawInput.Replace("\n", "").Replace("\r", "").Trim();
byte[] decoded = Convert.FromBase64String(cleaned);Tôi có thể giải mã streaming dữ liệu Base64 lớn trong C# không?
Có. Dùng CryptoStream với FromBase64Transform từ System.Security.Cryptography. Cách này giải mã theo từng chunk khi đọc, nên bộ nhớ không tăng bất kể kích thước file. Truyền FromBase64TransformMode.IgnoreWhiteSpaces nếu đầu vào có dấu xuống dòng. Trên .NET 6+, bạn cũng có thể dùng pattern IAsyncEnumerable với Base64.DecodeFromUtf8() để xử lý chunk thủ công, tuy nhiên CryptoStream đơn giản hơn cho việc giải mã file sang file.
using System.IO;
using System.Security.Cryptography;
using var input = File.OpenRead("payload.b64");
using var transform = new FromBase64Transform();
using var cryptoStream = new CryptoStream(input, transform, CryptoStreamMode.Read);
using var output = File.Create("payload.bin");
cryptoStream.CopyTo(output);Công cụ liên quan
- Base64 Encoder — mã hóa văn bản hoặc dữ liệu nhị phân thành Base64 ngay trên trình duyệt, hữu ích để tạo test fixture dán vào unit test C# của bạn.
- JWT Decoder — giải mã và kiểm tra cả ba đoạn JWT cùng lúc, với kiểm tra payload từng trường — nhanh hơn viết C# helper khi bạn chỉ cần đọc token.
- URL Decoder — percent-decode chuỗi URL-encoded, hữu ích khi API response kết hợp dữ liệu Base64url với query parameter percent-encoded.
- JSON Formatter — sau khi giải mã Base64 JWT payload hoặc API config, dán JSON vào đây để pretty-print và kiểm tra cấu trúc.