Base64 Decode C# β€” Convert.FromBase64String() Guide

Β·Game Developer & Unity EngineerΒ·Reviewed byEmma RichardsonΒ·Published

Use the free online Base64 Decode Online directly in your browser β€” no install required.

Try Base64 Decode Online Online β†’

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.

Before Β· text
After Β· text
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

C# (.NET 6+)
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

C# (.NET 6+)
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.
C# (.NET 6+)
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:

C# (.NET 6+)
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://cache
Note:Convert.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

C# (.NET 6+)
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

C# (.NET 6+)
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

C# (.NET 5+)
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
Warning:Never deserialize untrusted Base64 data using 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.

Method
Returns
Input Type
Description
Convert.FromBase64String(string)
byte[]
string
Decodes a standard Base64 string to a byte array; throws FormatException on invalid input
Convert.TryFromBase64String(string, Span<byte>, out int)
bool
string + Span<byte>
Attempts decoding into a caller-allocated Span; returns false on failure instead of throwing (.NET 5+)
Convert.FromBase64CharArray(char[], int, int)
byte[]
char[] + offset + length
Decodes a segment of a char array; useful for parsing buffers without creating substrings
Convert.TryFromBase64Chars(ReadOnlySpan<char>, Span<byte>, out int)
bool
ReadOnlySpan<char> + Span<byte>
Span-based, zero-allocation decoding from a char span (.NET 5+)
System.Buffers.Text.Base64.DecodeFromUtf8(ROSpan<byte>, Span<byte>, out int, out int, bool)
OperationStatus
ReadOnlySpan<byte> + Span<byte>
High-performance UTF-8 byte decoding; returns OperationStatus enum (.NET Core 2.1+)
System.Buffers.Text.Base64.DecodeFromUtf8InPlace(Span<byte>, out int)
OperationStatus
Span<byte>
Decodes in-place, overwriting the input buffer; zero extra allocation (.NET Core 2.1+)

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.

C# (.NET 5+)
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:

C# (.NET 5+)
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)
Note:The output buffer must be large enough. If it's too small, 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.

C# (.NET 6+)
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+.

C# (.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}");
}
Note:Separate your 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.

bash
# 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.

C# (.NET 6+)
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

C# (.NET 6+)
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
}
Note: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.

C# (.NET 6+)
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.

Warning:Spectre.Console output contains ANSI escape sequences. Don't pipe it to a file or return it from an API β€” use it only for terminal display.

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.

C# (.NET 6+)
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

C# (.NET 6+)
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");
Note:Switch from 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.

C# (.NET 6+)
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:

C# (.NET 6+)
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, billing

Common 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.

❌ Forgetting to strip whitespace from input

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.

Before Β· C#
After Β· C#
// 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 string
string 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));
❌ Using Convert.FromBase64String() on URL-safe Base64

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.

Before Β· C#
After Β· C#
// 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"}
❌ Converting binary data to string

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.

Before Β· C#
After Β· C#
byte[] decoded = Convert.FromBase64String(pngBase64);
string imageStr = Encoding.UTF8.GetString(decoded); // corrupts binary
File.WriteAllText("image.png", imageStr); // broken file
byte[] decoded = Convert.FromBase64String(pngBase64);
// Write bytes directly β€” no string conversion
File.WriteAllBytes("image.png", decoded);
❌ Not specifying encoding (defaulting to platform behavior)

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.

Before Β· C#
After Β· C#
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.

Method
Allocation
Error Handling
Streaming
Custom Types
Requires Install
Convert.FromBase64String()
New byte[]
FormatException
No
No
No (.NET Framework 1.1+)
Convert.TryFromBase64String()
Caller Span
Returns false
No
No
No (.NET 5+)
Convert.FromBase64CharArray()
New byte[]
FormatException
No
No
No (.NET Framework 1.1+)
Base64.DecodeFromUtf8()
Caller Span
OperationStatus
Partial
No
No (.NET Core 2.1+)
Base64.DecodeFromUtf8InPlace()
In-place
OperationStatus
No
No
No (.NET Core 2.1+)
CryptoStream + FromBase64Transform
Streaming buffer
Exception
Yes
No
No (.NET Framework 2.0+)
BouncyCastle Base64
New byte[]
Exception
Yes
No
Yes (NuGet)

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.

C# (.NET 6+)
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.

C# (.NET 6+)
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.

C# (.NET 6+)
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.

C# (.NET 6+)
// 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.

C# (.NET 6+)
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);
  • 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.
Also available in:JavaScriptPythonGoJava
AP
Alexei PetrovGame Developer & Unity Engineer

Alexei is a game developer who has shipped multiple titles using Unity and C#. He focuses on gameplay systems, runtime performance, and the serialisation and data-management patterns unique to game development. He writes about Unity scripting, C# async/await in game contexts, asset serialisation, binary data handling, and the intersection of game engineering and general software craftsmanship.

ER
Emma RichardsonTechnical Reviewer

Emma is a .NET developer and cloud engineer who builds production APIs and backend services with ASP.NET Core and Azure. She has worked on everything from microservice migrations to real-time SignalR applications. She writes about C# language features, the System.Text.Json and Newtonsoft.Json ecosystems, Azure integrations, and the architectural patterns that make .NET services scalable and maintainable.