Every .NET project I've worked on eventually needs to decode Base64 β pulling connection strings from Kubernetes secrets, reading binary payloads from webhooks, or inspecting JWT tokens during a debugging session. To decode Base64 in C#, the primary method is Convert.FromBase64String(), which returns a byte[] that you then pass to Encoding.UTF8.GetString() to get readable text. For a quick check without writing code, ToolDeck's Base64 decoder handles it instantly in the browser. This guide covers Convert.FromBase64String(), the span-based TryFromBase64String() for .NET 5+, the high-performance System.Buffers.Text.Base64 API, JWT payload extraction, file and API response decoding, streaming with CryptoStream, and the four mistakes that trip up C# developers most often.
- βConvert.FromBase64String(s) + Encoding.UTF8.GetString(bytes) is the standard two-step pipeline β works across all .NET versions.
- βConvert.TryFromBase64String() avoids exceptions on invalid input and writes to a Span<byte> β ideal for hot paths on .NET 5+.
- βSystem.Buffers.Text.Base64.DecodeFromUtf8() gives zero-allocation decoding for UTF-8 byte buffers in performance-critical services.
- βJWT tokens use Base64url (- and _ instead of + and /) β you must normalize the input before calling Convert.FromBase64String().
- βCryptoStream with FromBase64Transform handles streaming decoding for large files without loading everything into memory.
What is Base64 Decoding?
Base64 encoding converts binary data into a 64-character ASCII alphabet so it survives text-only transport β JSON fields, HTTP headers, email bodies, XML attributes. Every 3 bytes of input become 4 Base64 characters, which is why Base64 output is always about 33% larger than the original. Decoding reverses this transformation. The = padding at the end tells the decoder how many bytes to trim from the final group. A single = means the last block had 2 bytes; == means it had 1 byte. Base64 is not encryption β anyone can reverse it. The purpose is safe transport through channels that mangle raw binary, not confidentiality.
cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ==
redis://cache-prod.internal:6379/session-store
Convert.FromBase64String() β The Standard Decoding Method
The Convert.FromBase64String() method has been in .NET since the Framework 1.1 days. No NuGet packages, no extra imports beyond System β just call it and get back a byte[]. The two-step pipeline to decode Base64 to a C# string is always the same: Convert.FromBase64String() to get bytes, then Encoding.UTF8.GetString() to interpret those bytes as text. The catch is that the method returns raw bytes, not a string. You need to pick the right Encoding to convert those bytes back to text, and the choice matters more than most people expect. A mismatched encoding silently produces mojibake β garbled characters with no exception to warn you.
Minimal working example
using System; using System.Text; // Connection string stored as Base64 in a 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
Always use Encoding.UTF8 unless you have a specific reason not to. The .NET runtime represents strings as UTF-16 internally, but most data crossing system boundaries (API responses, config files, secrets) is encoded as UTF-8. Using Encoding.ASCII on data that contains multi-byte characters silently replaces them with ? β no exception, just corrupted output.
Round-trip verification
using System; using System.Text; string original = "postgres://db-admin:Kx8!mQ@db-prod.us-east-1.internal:5432/orders"; // Encode string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(original)); Console.WriteLine(encoded); // cG9zdGdyZXM6Ly9kYi1hZG1pbjpLeDghbVFAZGItcHJvZC51cy1lYXN0LTEuaW50ZXJuYWw6NTQzMi9vcmRlcnM= // Decode byte[] decoded = Convert.FromBase64String(encoded); string recovered = Encoding.UTF8.GetString(decoded); Console.WriteLine(recovered == original); // True
Choosing the right Encoding
The Encoding you pass to GetString()must match what was used when the data was originally encoded. Pick the wrong one and you get garbage characters with no exception β the decoder happily produces nonsense. Here's the practical breakdown:
Encoding.UTF8β safe default. Handles ASCII and all Unicode. Use this unless you know otherwise.Encoding.ASCIIβ only for pure 7-bit ASCII data. Multi-byte characters become?.Encoding.Unicodeβ this is UTF-16LE in .NET. Some Windows-internal strings and PowerShell exports use this.Encoding.Latin1β legacy Western European encoding. Shows up in old SOAP services and mainframe integrations.
using System;
using System.Text;
// Same bytes, different encodings β different results
byte[] decoded = Convert.FromBase64String("w7bDvMOk");
Console.WriteLine(Encoding.UTF8.GetString(decoded)); // oua (correct)
Console.WriteLine(Encoding.ASCII.GetString(decoded)); // ?????? (lost)
Console.WriteLine(Encoding.Latin1.GetString(decoded)); // ΓΒΆΓΒΌΓΒ€ (mojibake)A reusable decode helper with error handling
Since Convert.FromBase64String() throws on bad input and the whitespace-stripping dance happens constantly, I keep a small helper in most projects:
using System;
using System.Text;
static class Base64Helper
{
public static string? DecodeToString(string encoded)
{
if (string.IsNullOrWhiteSpace(encoded))
return null;
// Strip whitespace that .NET's decoder rejects
string cleaned = encoded
.Replace("
", "")
.Replace("
", "")
.Replace(" ", "")
.Trim();
try
{
byte[] bytes = Convert.FromBase64String(cleaned);
return Encoding.UTF8.GetString(bytes);
}
catch (FormatException)
{
return null; // or throw a domain-specific exception
}
}
}
// Usage
string? decoded = Base64Helper.DecodeToString(" cmVkaXM6Ly9jYWNoZQ== \n");
Console.WriteLine(decoded); // redis://cacheConvert.FromBase64String() throws FormatException if the input contains characters outside the Base64 alphabet β including spaces, newlines, and URL-safe characters like - and _. The helper above handles whitespace automatically.Decoding Base64 into Non-Standard Types
The decoder always gives you byte[]. What you do with those bytes depends on the original data. Sometimes it's a GUID stored as 16 raw bytes, sometimes it's a serialized protobuf message, sometimes it's a timestamp in binary format. Here are the conversions I reach for most.
Base64 to GUID
using System; // Some APIs send GUIDs as 22-char Base64 instead of 36-char hex strings string compactGuid = "C0HqetxMckKlZw4CssPUeQ=="; byte[] guidBytes = Convert.FromBase64String(compactGuid); Guid recovered = new Guid(guidBytes); Console.WriteLine(recovered); // 7aea41c0-4cdc-4272-a567-0e02b2c3d479
Base64 to deserialized JSON with System.Text.Json
using System;
using System.Text;
using System.Text.Json;
// Base64-encoded JSON payload from a 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 into a 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 to hex string
using System; // SHA-256 hash stored as Base64 string hashBase64 = "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="; byte[] hashBytes = Convert.FromBase64String(hashBase64); string hex = Convert.ToHexString(hashBytes); Console.WriteLine(hex); // 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08
BinaryFormatter. It has known remote code execution vulnerabilities and is obsolete in .NET 8+. If the encoded content comes from an external source, parse it as JSON or protobuf instead of using .NET binary serialization.Base64 Decoding Methods Reference
.NET provides multiple decoding methods across two namespaces. The Convert class handles general-purpose decoding, while System.Buffers.Text.Base64 targets high-throughput scenarios where you're already working with raw UTF-8 byte buffers.
Convert.TryFromBase64String() β Exception-Free Decoding
The Try pattern is standard in .NET for operations that might fail on user input. Convert.TryFromBase64String(), available since .NET 5, returns a bool instead of throwing FormatException. It writes the decoded bytes into a caller-provided Span<byte>, which means you can use stack-allocated memory for small payloads and skip the heap entirely.
using System;
using System.Text;
string userInput = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=";
// Stack-allocate for small payloads (< 1KB is a safe rule of thumb)
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");
}This approach shines in request middleware or validation pipelines where bad input is expected, not exceptional. Throwing and catching a FormatException on every malformed request adds measurable overhead at scale β TryFromBase64String() avoids that entirely.
Validating Base64 input without decoding
A common pattern in API controllers: check if the input is valid Base64 before passing it downstream. You can use TryFromBase64String() as a validator by allocating a throwaway buffer:
using System;
static bool IsValidBase64(string input)
{
// Calculate maximum decoded size
Span<byte> buffer = stackalloc byte[((input.Length + 3) / 4) * 3];
return Convert.TryFromBase64String(input, buffer, out _);
}
// Usage in an API controller
Console.WriteLine(IsValidBase64("eyJob3N0IjoiMTAuMC4xLjUwIn0=")); // True
Console.WriteLine(IsValidBase64("not!!valid!!base64")); // False
Console.WriteLine(IsValidBase64("")); // True (empty is valid)TryFromBase64String() returns false even when the input is valid. Calculate the required size as (inputLength / 4) * 3 to be safe.Decode Base64 from a File and API Response
Reading a Base64-encoded file from disk
Certificates, encrypted blobs, and data export files sometimes ship as Base64 text. The typical pattern: read the file as a string, strip any whitespace or line breaks that would cause FormatException, decode to bytes, and write the binary output. Pay attention to the error handling β file I/O errors and Base64 format errors should be caught separately.
using System;
using System.IO;
string inputPath = "tls-cert.pem.b64";
string outputPath = "tls-cert.pem";
try
{
string encoded = File.ReadAllText(inputPath).Trim();
// Remove line breaks β .NET's decoder rejects them
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}");
}Decoding a Base64 field from an HTTP API response
Cloud APIs (Azure Key Vault, AWS Secrets Manager, GitHub Contents API) frequently return binary data as Base64 strings embedded inside JSON. The workflow is always the same: make the HTTP request, parse the JSON response, extract the Base64 field, and decode it. The example below uses HttpClient and System.Text.Json β both built into .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 returns: {"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 blocks for network errors and FormatException. Lumping them together makes it hard to tell whether the API returned bad data or the request itself failed. In production, log the raw Base64 value (or at least its length and first 20 characters) when you catch FormatException β it makes debugging dramatically easier.Base64 Decoding from the Command Line
You don't always need a compiled project. The dotnet-script tool and PowerShell both handle Base64 decoding with one-liners. For quick inspection during debugging, these are faster than creating a console app.
# PowerShell (built into Windows, available on Linux/macOS)
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0="))
# {"host":"10.0.1.50"}
# Linux / macOS native base64 command
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 --decode
# {"host":"10.0.1.50"}
# macOS uses -D instead of --decode
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 -D
# dotnet-script (install: dotnet tool install -g dotnet-script)
echo 'Console.WriteLine(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0=")));' | dotnet-script eval
# Decode and pretty-print JSON with jq
echo "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=" | base64 --decode | jq .For pasting encoded strings directly into a browser, ToolDeck's Base64 decoder handles both standard and URL-safe variants without any setup.
High-Performance Alternative: System.Buffers.Text.Base64
The System.Buffers.Text.Base64 class, available since .NET Core 2.1, operates on raw UTF-8 byte spans instead of .NET strings. This bypasses the string-to-byte conversion overhead entirely β no intermediate string allocation, no UTF-16 encoding step. I reach for it in ASP.NET Core middleware where the incoming data is already a ReadOnlySpan<byte> from the request body. Creating a string just to pass it to Convert.FromBase64String() doubles the allocations for no reason. In BenchmarkDotNet tests on .NET 8, the span-based path is roughly 2-3x faster for payloads under 1 KB and the gap widens with larger inputs because GC pressure stays flat.
using System;
using System.Buffers.Text;
using System.Text;
// Simulate raw UTF-8 bytes from an 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}");
// Possible: InvalidData, DestinationTooSmall, NeedMoreData
}The return type is OperationStatus β an enum with four values: Done, InvalidData, DestinationTooSmall, and NeedMoreData. The last one is useful for partial decoding of streaming data. For absolute zero allocation, pair this with ArrayPool<byte>.Shared.Rent() instead of new byte[].
DecodeFromUtf8InPlace β overwrite the input buffer
using System;
using System.Buffers.Text;
using System.Text;
// The input buffer gets overwritten with decoded bytes
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
}DecodeFromUtf8InPlacemodifies the input buffer. Don't use it if you need the original Base64 string afterward β the first bytesWritten bytes of the array now contain the decoded data, and the rest is garbage.One thing to be aware of: System.Buffers.Text.Base64 does not handle URL-safe Base64 directly. If the input uses - and _ characters (JWT tokens, for example), you still need to replace them with + and /before calling these methods. The span-based APIs are strictly RFC 4648 standard alphabet. There's no DecodeFromUtf8Url variant β a surprising gap given how common Base64url is in modern APIs.
Terminal Output with Syntax Highlighting
The Spectre.Console library gives you rich terminal output including JSON highlighting β useful when building CLI tools that decode Base64 and display the result. Install it with 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 with syntax highlighting in the terminal
AnsiConsole.Write(new JsonText(json));
// Outputs colored JSON:
// {
// "host": "10.0.1.50",
// "port": 8443,
// "maxConn": 100
// }Decode the Base64 config, deserialize to JSON, and print the highlighted output β all in a few lines. Spectre.Console also has table rendering, progress bars, and tree views if you need to display more complex decoded data structures in the terminal.
Streaming Large Base64 Files with CryptoStream
Loading a 500 MB Base64 file with File.ReadAllText() and then calling Convert.FromBase64String() allocates roughly 700 MB of heap: the string itself (UTF-16, so double the file size) plus the decoded byte array. The CryptoStream + FromBase64Transform combination decodes in chunks, keeping memory usage constant.
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}");The FromBase64TransformMode.IgnoreWhiteSpaces flag handles line-wrapped Base64 (PEM files, email exports) without manual cleanup. Without this flag, line breaks in the input cause a FormatException. This is .NET's closest equivalent to Java's getMimeDecoder() β it silently skips whitespace characters during decoding.
The name CryptoStreamis misleading β there's nothing cryptographic about Base64. Microsoft put FromBase64Transform in the System.Security.Cryptography namespace because it implements ICryptoTransform, the same interface used for AES and other cipher transforms. The stream itself just pipes data through any transform in chunks. Think of it as a general-purpose streaming transform pipeline that happens to live in the wrong namespace.
Async streaming for ASP.NET Core scenarios
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);
}
// Usage in an endpoint:
// await DecodeStreamAsync(Request.Body, "uploaded-file.bin");CopyTo to CopyToAsync in ASP.NET Core handlers. Synchronous I/O on the request thread is blocked by default in Kestrel and throws an InvalidOperationException.How to Decode a Base64 JWT Token Payload in C#
A JWT has three Base64url-encoded segments separated by dots. The middle segment is the payload. You can decode it without pulling in a JWT library β split on ., normalize the Base64url characters, fix the padding, and call Convert.FromBase64String(). This trips up almost everyone the first time because JWT uses - and _ instead of + and /, and strips the = 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}");
// Take the payload (second segment)
string payload = parts[1];
// Replace URL-safe characters with standard Base64
payload = payload.Replace('-', '+').Replace('_', '/');
// Pad to a multiple of 4
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] bytes = Convert.FromBase64String(payload);
return Encoding.UTF8.GetString(bytes);
}
// Test with a real-shaped token
string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
+ ".eyJzdWIiOiJ1c3ItNjcyIiwiaXNzIjoiYXV0aC5leGFtcGxlLmNvbSIsImV4cCI6MTc0MTk1NjgwMCwicm9sZXMiOlsiYWRtaW4iLCJiaWxsaW5nIl19"
+ ".SIGNATURE_PLACEHOLDER";
Console.WriteLine(DecodeJwtPayload(token));
// {"sub":"usr-672","iss":"auth.example.com","exp":1741956800,"roles":["admin","billing"]}Quick note: this only reads the payload. It does not verify the signature. For production auth validation, use a proper library like Microsoft.IdentityModel.JsonWebTokens. But for debugging, logging, and test assertions, this manual approach is all you need.
Parsing the decoded JWT payload into a typed object
Once you have the JSON string, deserialize it with System.Text.Json to access individual claims without string manipulation:
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
);
// After DecodeJwtPayload() from above
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, billingCommon Mistakes
I've hit every one of these in production C# services. The first two are responsible for the majority of Base64-related bugs I see in code reviews.
Problem: Base64 strings read from config files, environment variables, or user input often have trailing newlines. Convert.FromBase64String() rejects any character outside the Base64 alphabet, including \r\n.
Fix: Call .Trim() or .Replace() to strip whitespace before decoding.
// Environment variable has a trailing newline
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));Problem: JWT tokens and some API payloads use - and _ (URL-safe alphabet). The standard decoder only accepts + and / β it throws FormatException on the first - character.
Fix: Replace - with + and _ with / before decoding. Also fix the padding.
// JWT payload β uses URL-safe Base64 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"}Problem: Calling Encoding.UTF8.GetString() on decoded image or protobuf bytes produces garbage. Worse, converting that string back to bytes silently corrupts the data because invalid UTF-8 sequences get replaced.
Fix: Keep binary data as byte[] through your entire pipeline. Only call GetString() when you know the content is text.
byte[] decoded = Convert.FromBase64String(pngBase64);
string imageStr = Encoding.UTF8.GetString(decoded); // corrupts binary
File.WriteAllText("image.png", imageStr); // broken filebyte[] decoded = Convert.FromBase64String(pngBase64);
// Write bytes directly β no string conversion
File.WriteAllBytes("image.png", decoded);Problem: Some older .NET Framework APIs default to the system's current ANSI code page, which varies between machines. A Windows server with code page 1252 and a Linux container with UTF-8 produce different strings from the same bytes.
Fix: Always specify Encoding.UTF8 explicitly. Never rely on the platform default.
byte[] decoded = Convert.FromBase64String(encoded); // Encoding.Default varies between platforms string result = Encoding.Default.GetString(decoded);
byte[] decoded = Convert.FromBase64String(encoded); string result = Encoding.UTF8.GetString(decoded); // consistent across Windows, Linux, macOS
Method Comparison
.NET offers more Base64 decoding methods than most developers realize. The table below covers every built-in option plus the two most common third-party alternatives. The "Allocation" column matters most in high-throughput services β a method that allocates a new byte[] on every call puts pressure on the GC in tight loops.
For everyday work: Convert.FromBase64String(). For hot paths where you validate user input: TryFromBase64String(). For ASP.NET Core middleware operating on raw request bytes: Base64.DecodeFromUtf8(). For large files: CryptoStream + FromBase64Transform. BouncyCastle only makes sense if it's already in your dependency tree for other cryptographic operations.
For quick verification without compiling anything, the online Base64 decoder is faster than writing a one-off console app.
Frequently Asked Questions
How do I decode a Base64 string to text in C#?
Call Convert.FromBase64String() to get a byte array, then pass it to Encoding.UTF8.GetString(). The encoding must match whatever was used during encoding β UTF-8 is the safe default for almost all modern systems. If the input might contain whitespace or line breaks, call .Trim() or strip them before decoding.
using System; using System.Text; string encoded = "cG9zdGdyZXM6eGs5bVAycVI="; byte[] bytes = Convert.FromBase64String(encoded); string result = Encoding.UTF8.GetString(bytes); Console.WriteLine(result); // postgres:xk9mP2qR
What is the difference between Convert.FromBase64String() and Convert.TryFromBase64String()?
FromBase64String() throws a FormatException on invalid input. TryFromBase64String() returns a bool and writes the result into a caller-provided Span<byte>, making it suitable for hot paths where you want to avoid exception overhead. TryFromBase64String() requires .NET 5 or later.
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");How do I decode a Base64url JWT payload in C#?
Split the token on dots, take the second segment, replace - with + and _ with /, pad to a multiple of 4 with =, then call Convert.FromBase64String(). JWT tokens use the URL-safe Base64 alphabet, which .NET's standard decoder doesn't handle directly.
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"}How do I choose the right Encoding when decoding Base64 in C#?
Use Encoding.UTF8 as the default β it handles ASCII and multi-byte Unicode characters. Use Encoding.ASCII only when you're certain the data is pure 7-bit ASCII. Use Encoding.Unicode (which is UTF-16LE in .NET) only when the original data was encoded as UTF-16, which happens sometimes with Windows-internal strings and PowerShell exports.
Why does Convert.FromBase64String() throw FormatException?
Three common causes: the input contains whitespace or line breaks (strip them before decoding), the input uses URL-safe characters like - and _ (replace them with + and / respectively), or the padding is missing or incorrect (the total length must be a multiple of 4 after padding). Unlike Java, .NET has no built-in MIME decoder that tolerates whitespace β you must clean the input yourself or use CryptoStream with FromBase64Transform and IgnoreWhiteSpaces mode.
// Fix whitespace
string cleaned = rawInput.Replace("\n", "").Replace("\r", "").Trim();
byte[] decoded = Convert.FromBase64String(cleaned);Can I stream-decode large Base64 data in C#?
Yes. Use a CryptoStream with FromBase64Transform from System.Security.Cryptography. This decodes in chunks as you read, so memory stays flat regardless of file size. Pass FromBase64TransformMode.IgnoreWhiteSpaces if the input contains line breaks. On .NET 6+, you can also use the IAsyncEnumerable pattern with Base64.DecodeFromUtf8() for manual chunked processing, though CryptoStream is simpler for file-to-file decoding.
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);Related Tools
- Base64 Encoder β encode text or binary data to Base64 in the browser, useful for generating test fixtures to paste into your C# unit tests.
- JWT Decoder β decode and inspect all three JWT segments at once, with field-by-field payload inspection β faster than writing a C# helper when you just need to read a token.
- URL Decoder β percent-decode URL-encoded strings, useful when API responses mix Base64url data with percent-encoded query parameters.
- JSON Formatter β after decoding a Base64 JWT payload or API config, paste the JSON here to pretty-print and validate the structure.