C# Base64 디코드 가이드

·Game Developer & Unity Engineer·검토자Emma Richardson·게시일

무료 Base64 디코더을 브라우저에서 직접 사용하세요 — 설치 불필요.

Base64 디코더 온라인으로 사용하기 →

제가 작업한 거의 모든 .NET 프로젝트에서 결국 Base64 디코딩이 필요했습니다 — Kubernetes 시크릿에서 연결 문자열을 꺼내거나, 웹훅에서 바이너리 페이로드를 읽거나, 디버깅 중 JWT 토큰을 확인할 때요. C#에서 Base64를 디코딩하는 주요 메서드는 Convert.FromBase64String()으로, 이를 통해 얻은 byte[]Encoding.UTF8.GetString()에 전달하면 읽을 수 있는 텍스트가 됩니다. 코드 작성 없이 빠르게 확인하고 싶다면 ToolDeck의 Base64 디코더 가 브라우저에서 즉시 처리해줍니다. 이 가이드는 Convert.FromBase64String(), .NET 5+를 위한 스팬 기반 TryFromBase64String(), 고성능 System.Buffers.Text.Base64 API, JWT 페이로드 추출, 파일 및 API 응답 디코딩, CryptoStream을 이용한 스트리밍, 그리고 C# 개발자들이 가장 자주 빠지는 네 가지 실수를 다룹니다.

  • Convert.FromBase64String(s) + Encoding.UTF8.GetString(bytes)가 표준 2단계 파이프라인입니다 — 모든 .NET 버전에서 동작합니다.
  • Convert.TryFromBase64String()은 유효하지 않은 입력에서 예외를 피하고 Span<byte>에 결과를 씁니다 — .NET 5+의 핫 패스에 이상적입니다.
  • System.Buffers.Text.Base64.DecodeFromUtf8()은 성능 크리티컬 서비스에서 UTF-8 바이트 버퍼에 대한 제로 할당 디코딩을 제공합니다.
  • JWT 토큰은 Base64url(+ 대신 -, / 대신 _)을 사용합니다 — Convert.FromBase64String() 호출 전에 반드시 입력을 정규화해야 합니다.
  • CryptoStream과 FromBase64Transform은 모든 것을 메모리에 로드하지 않고 대용량 파일의 스트리밍 디코딩을 처리합니다.

Base64 디코딩이란?

Base64 인코딩은 바이너리 데이터를 64개 문자로 구성된 ASCII 알파벳으로 변환하여 텍스트 전용 전송 채널 — JSON 필드, HTTP 헤더, 이메일 본문, XML 속성 — 에서도 안전하게 전달될 수 있게 합니다. 입력 3바이트마다 Base64 문자 4개가 생성되므로 Base64 출력은 원본보다 항상 약 33% 더 큽니다. 디코딩은 이 변환을 반대로 수행합니다. 끝에 붙는 = 패딩은 디코더에게 마지막 그룹에서 몇 바이트를 잘라야 하는지 알려줍니다. = 하나는 마지막 블록에 바이트가 2개였음을, ==는 1개였음을 의미합니다. Base64는 암호화가 아닙니다 — 누구든 되돌릴 수 있습니다. 목적은 기밀성이 아니라 바이너리를 손상시키는 채널을 통한 안전한 전송입니다.

Before · text
After · text
cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ==
redis://cache-prod.internal:6379/session-store

Convert.FromBase64String() — 표준 디코딩 메서드

Convert.FromBase64String() 메서드는 .NET Framework 1.1 시절부터 존재해 왔습니다. NuGet 패키지도 없고 System 외에 추가 임포트도 필요하지 않습니다 — 그냥 호출하면 byte[]를 반환합니다. Base64를 C# 문자열로 디코딩하는 2단계 파이프라인은 항상 동일합니다: Convert.FromBase64String()으로 바이트를 얻고, Encoding.UTF8.GetString()으로 그 바이트를 텍스트로 해석합니다. 주의할 점은 이 메서드가 문자열이 아닌 원시 바이트를 반환한다는 것입니다. 올바른 Encoding을 선택해야 바이트를 텍스트로 변환할 수 있으며, 이 선택은 대부분의 개발자가 생각하는 것보다 훨씬 중요합니다. 잘못된 인코딩을 사용하면 예외 없이 조용히 깨진 문자(모지바케)가 생성됩니다.

최소 동작 예제

C# (.NET 6+)
using System;
using System.Text;

// Kubernetes 시크릿에 Base64로 저장된 연결 문자열
string encoded = "cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ==";

byte[] decodedBytes = Convert.FromBase64String(encoded);
string connectionString = Encoding.UTF8.GetString(decodedBytes);

Console.WriteLine(connectionString);
// redis://cache-prod.internal:6379/session-store

특별한 이유가 없다면 항상 Encoding.UTF8을 사용하세요. .NET 런타임은 문자열을 내부적으로 UTF-16으로 표현하지만, 시스템 경계를 넘어오는 데이터 (API 응답, 설정 파일, 시크릿)는 대부분 UTF-8로 인코딩되어 있습니다. 멀티바이트 문자가 포함된 데이터에 Encoding.ASCII를 사용하면 조용히 ?로 대체됩니다 — 예외 없이, 그냥 손상된 출력만 남습니다.

왕복 검증

C# (.NET 6+)
using System;
using System.Text;

string original = "postgres://db-admin:Kx8!mQ@db-prod.us-east-1.internal:5432/orders";

// 인코딩
string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(original));
Console.WriteLine(encoded);
// cG9zdGdyZXM6Ly9kYi1hZG1pbjpLeDghbVFAZGItcHJvZC51cy1lYXN0LTEuaW50ZXJuYWw6NTQzMi9vcmRlcnM=

// 디코딩
byte[] decoded = Convert.FromBase64String(encoded);
string recovered = Encoding.UTF8.GetString(decoded);

Console.WriteLine(recovered == original); // True

올바른 Encoding 선택

GetString()에 전달하는 Encoding은 원본 데이터를 인코딩할 때 사용한 것과 일치해야 합니다. 잘못된 것을 선택하면 예외 없이 깨진 문자가 생성됩니다 — 디코더는 아무 불평 없이 쓰레기 값을 만들어냅니다. 실용적인 기준은 다음과 같습니다:

  • Encoding.UTF8 — 안전한 기본값. ASCII와 모든 유니코드를 처리합니다. 특별한 이유가 없으면 이것을 사용하세요.
  • Encoding.ASCII — 순수 7비트 ASCII 데이터에만 사용. 멀티바이트 문자는 ?가 됩니다.
  • Encoding.Unicode — .NET에서 UTF-16LE입니다. 일부 Windows 내부 문자열과 PowerShell 내보내기가 이를 사용합니다.
  • Encoding.Latin1 — 레거시 서유럽 인코딩. 오래된 SOAP 서비스나 메인프레임 연동에서 등장합니다.
C# (.NET 6+)
using System;
using System.Text;

// 같은 바이트, 다른 인코딩 — 다른 결과
byte[] decoded = Convert.FromBase64String("w7bDvMOk");

Console.WriteLine(Encoding.UTF8.GetString(decoded));    // oua  (정상)
Console.WriteLine(Encoding.ASCII.GetString(decoded));   // ??????  (손실)
Console.WriteLine(Encoding.Latin1.GetString(decoded));  // öüä  (모지바케)

오류 처리가 포함된 재사용 가능한 디코딩 헬퍼

Convert.FromBase64String()은 잘못된 입력에 예외를 발생시키고 공백 제거 작업이 반복적으로 필요하므로, 저는 대부분의 프로젝트에 작은 헬퍼를 유지합니다:

C# (.NET 6+)
using System;
using System.Text;

static class Base64Helper
{
    public static string? DecodeToString(string encoded)
    {
        if (string.IsNullOrWhiteSpace(encoded))
            return null;

        // .NET 디코더가 거부하는 공백 제거
        string cleaned = encoded
            .Replace("
", "")
            .Replace("
", "")
            .Replace(" ", "")
            .Trim();

        try
        {
            byte[] bytes = Convert.FromBase64String(cleaned);
            return Encoding.UTF8.GetString(bytes);
        }
        catch (FormatException)
        {
            return null; // 또는 도메인별 예외 발생
        }
    }
}

// 사용법
string? decoded = Base64Helper.DecodeToString("  cmVkaXM6Ly9jYWNoZQ==  \n");
Console.WriteLine(decoded); // redis://cache
참고:Convert.FromBase64String()은 공백, 줄바꿈, -_ 같은 URL-safe 문자를 포함하여 Base64 알파벳 외의 문자가 포함된 경우FormatException을 발생시킵니다. 위의 헬퍼는 공백을 자동으로 처리합니다.

비표준 타입으로 Base64 디코딩

디코더는 항상 byte[]를 반환합니다. 그 바이트를 어떻게 처리할지는 원본 데이터에 따라 다릅니다. 때로는 16바이트 원시 데이터로 저장된 GUID이고, 때로는 직렬화된 protobuf 메시지이거나 바이너리 형식의 타임스탬프일 수도 있습니다. 제가 가장 자주 사용하는 변환 방법을 소개합니다.

Base64에서 GUID로

C# (.NET 6+)
using System;

// 일부 API는 36자 16진수 문자열 대신 22자 Base64로 GUID를 전송
string compactGuid = "C0HqetxMckKlZw4CssPUeQ==";
byte[] guidBytes = Convert.FromBase64String(compactGuid);
Guid recovered = new Guid(guidBytes);

Console.WriteLine(recovered);
// 7aea41c0-4cdc-4272-a567-0e02b2c3d479

System.Text.Json으로 Base64를 역직렬화된 JSON으로

C# (.NET 6+)
using System;
using System.Text;
using System.Text.Json;

// 메시지 큐에서 온 Base64 인코딩된 JSON 페이로드
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}

// 레코드로 역직렬화
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에서 16진수 문자열로

C# (.NET 5+)
using System;

// Base64로 저장된 SHA-256 해시
string hashBase64 = "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=";
byte[] hashBytes = Convert.FromBase64String(hashBase64);
string hex = Convert.ToHexString(hashBytes);

Console.WriteLine(hex);
// 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08
경고:신뢰할 수 없는 Base64 데이터를 BinaryFormatter로 역직렬화하지 마세요. 원격 코드 실행 취약점이 알려져 있으며 .NET 8+에서는 사용 중단되었습니다. 인코딩된 콘텐츠가 외부 소스에서 온 경우 .NET 바이너리 직렬화 대신 JSON 또는 protobuf로 파싱하세요.

Base64 디코딩 메서드 레퍼런스

.NET은 두 네임스페이스에 걸쳐 여러 디코딩 메서드를 제공합니다. Convert 클래스는 범용 디코딩을 처리하고, System.Buffers.Text.Base64 이미 원시 UTF-8 바이트 버퍼로 작업 중인 고처리량 시나리오를 대상으로 합니다.

메서드
반환값
입력 타입
설명
Convert.FromBase64String(string)
byte[]
string
표준 Base64 문자열을 바이트 배열로 디코딩; 유효하지 않은 입력에는 FormatException 발생
Convert.TryFromBase64String(string, Span<byte>, out int)
bool
string + Span<byte>
호출자가 할당한 Span에 디코딩을 시도; 실패 시 예외 대신 false 반환 (.NET 5+)
Convert.FromBase64CharArray(char[], int, int)
byte[]
char[] + offset + length
char 배열의 지정 구간을 디코딩; 서브스트링 생성 없이 버퍼를 파싱할 때 유용
Convert.TryFromBase64Chars(ReadOnlySpan<char>, Span<byte>, out int)
bool
ReadOnlySpan<char> + Span<byte>
char 스팬에서 제로 할당 디코딩 (.NET 5+)
System.Buffers.Text.Base64.DecodeFromUtf8(ROSpan<byte>, Span<byte>, out int, out int, bool)
OperationStatus
ReadOnlySpan<byte> + Span<byte>
고성능 UTF-8 바이트 디코딩; OperationStatus 열거형 반환 (.NET Core 2.1+)
System.Buffers.Text.Base64.DecodeFromUtf8InPlace(Span<byte>, out int)
OperationStatus
Span<byte>
입력 버퍼를 덮어쓰는 인플레이스 디코딩; 추가 할당 없음 (.NET Core 2.1+)

Convert.TryFromBase64String() — 예외 없는 디코딩

Try 패턴은 사용자 입력에서 실패할 수 있는 작업에 대한 .NET의 표준 방식입니다. .NET 5부터 사용 가능한 Convert.TryFromBase64String()FormatException을 발생시키는 대신 bool을 반환합니다. 디코딩된 바이트를 호출자가 제공한 Span<byte>에 기록하므로, 소용량 페이로드의 경우 스택 할당 메모리를 사용해 힙을 완전히 건너뛸 수 있습니다.

C# (.NET 5+)
using System;
using System.Text;

string userInput = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=";

// 소용량 페이로드는 스택 할당 (1KB 미만이 안전한 기준)
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");
}

이 방식은 잘못된 입력이 예외적인 상황이 아니라 예상되는 상황인 요청 미들웨어나 유효성 검사 파이프라인에서 빛을 발합니다. 형식이 잘못된 요청마다FormatException을 던지고 잡는 것은 대규모에서 측정 가능한 오버헤드를 추가합니다 — TryFromBase64String()은 이를 완전히 피할 수 있습니다.

디코딩 없이 Base64 입력 유효성 검사

API 컨트롤러에서 자주 사용되는 패턴: 입력을 다운스트림으로 전달하기 전에 유효한 Base64인지 확인합니다. 임시 버퍼를 할당하여 TryFromBase64String()을 유효성 검사기로 사용할 수 있습니다:

C# (.NET 5+)
using System;

static bool IsValidBase64(string input)
{
    // 최대 디코딩 크기 계산
    Span<byte> buffer = stackalloc byte[((input.Length + 3) / 4) * 3];
    return Convert.TryFromBase64String(input, buffer, out _);
}

// API 컨트롤러에서의 사용법
Console.WriteLine(IsValidBase64("eyJob3N0IjoiMTAuMC4xLjUwIn0=")); // True
Console.WriteLine(IsValidBase64("not!!valid!!base64"));              // False
Console.WriteLine(IsValidBase64(""));                                // True (빈 문자열은 유효)
참고:출력 버퍼는 충분히 커야 합니다. 너무 작으면 TryFromBase64String()은 입력이 유효하더라도 false를 반환합니다. 안전하게 (inputLength / 4) * 3으로 필요한 크기를 계산하세요.

파일 및 API 응답에서 Base64 디코딩

디스크의 Base64 인코딩 파일 읽기

인증서, 암호화된 블롭, 데이터 내보내기 파일이 Base64 텍스트로 제공되는 경우가 있습니다. 일반적인 패턴은: 파일을 문자열로 읽고, FormatException을 유발하는 공백이나 줄바꿈을 제거하고, 바이트로 디코딩하고, 바이너리 출력을 씁니다. 오류 처리에 주의하세요 — 파일 I/O 오류와 Base64 형식 오류는 별도로 처리해야 합니다.

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();
    // 줄바꿈 제거 — .NET 디코더가 거부
    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}");
}

HTTP API 응답에서 Base64 필드 디코딩

클라우드 API(Azure Key Vault, AWS Secrets Manager, GitHub Contents API)는 JSON 내부에 Base64 문자열로 임베딩된 바이너리 데이터를 자주 반환합니다. 워크플로는 항상 동일합니다: HTTP 요청을 보내고, JSON 응답을 파싱하고, Base64 필드를 추출하고, 디코딩합니다. 아래 예제는 .NET 6+에 내장된 HttpClient System.Text.Json을 사용합니다.

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 반환값: {"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}");
}
참고:네트워크 오류와 FormatException에 대한 catch 블록을 분리하세요. 합치면 API가 잘못된 데이터를 반환한 것인지 요청 자체가 실패한 것인지 구분하기 어렵습니다. 프로덕션에서 FormatException을 잡을 때는 원시 Base64 값(또는 최소한 길이와 앞 20자)을 로깅하세요 — 디버깅이 훨씬 쉬워집니다.

커맨드 라인에서 Base64 디코딩

항상 컴파일된 프로젝트가 필요한 것은 아닙니다. dotnet-script와 PowerShell 모두 한 줄로 Base64 디코딩이 가능합니다. 디버깅 중 빠른 확인에는 콘솔 앱을 만드는 것보다 이 방법이 훨씬 빠릅니다.

bash
# PowerShell (Windows 내장, Linux/macOS에서도 사용 가능)
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0="))
# {"host":"10.0.1.50"}

# Linux / macOS 기본 base64 명령어
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 --decode
# {"host":"10.0.1.50"}

# macOS는 --decode 대신 -D 사용
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 -D

# dotnet-script (설치: dotnet tool install -g dotnet-script)
echo 'Console.WriteLine(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0=")));' | dotnet-script eval

# Base64 디코딩 후 jq로 JSON 포매팅
echo "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=" | base64 --decode | jq .

인코딩된 문자열을 브라우저에서 바로 붙여넣고 싶다면 ToolDeck의 Base64 디코더 가 별도 설정 없이 표준 및 URL-safe 변형을 모두 처리합니다.

고성능 대안: System.Buffers.Text.Base64

.NET Core 2.1부터 사용 가능한 System.Buffers.Text.Base64 클래스는 .NET 문자열 대신 원시 UTF-8 바이트 스팬에서 동작합니다. 문자열에서 바이트로의 변환 오버헤드를 완전히 건너뜁니다 — 중간 문자열 할당도 없고 UTF-16 인코딩 단계도 없습니다. ASP.NET Core 미들웨어에서 수신 데이터가 이미 요청 본문의ReadOnlySpan<byte>인 경우에 사용합니다.Convert.FromBase64String()에 전달하려고 굳이 string을 만드는 것은 아무 이유 없이 할당을 두 배로 늘리는 것입니다. .NET 8에서 BenchmarkDotNet 테스트 기준으로, 스팬 기반 경로는 1KB 미만 페이로드에서 약 2-3배 빠르며 입력이 커질수록 GC 압력이 일정하게 유지되므로 격차가 더 벌어집니다.

C# (.NET 6+)
using System;
using System.Buffers.Text;
using System.Text;

// HTTP 요청 본문에서 온 원시 UTF-8 바이트 시뮬레이션
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}");
    // 가능한 값: InvalidData, DestinationTooSmall, NeedMoreData
}

반환 타입은 OperationStatus — 네 가지 값을 가진 열거형입니다: Done, InvalidData, DestinationTooSmall,NeedMoreData. 마지막 값은 스트리밍 데이터의 부분 디코딩에 유용합니다. 완전한 제로 할당을 위해 new byte[] 대신 ArrayPool<byte>.Shared.Rent()와 함께 사용하세요.

DecodeFromUtf8InPlace — 입력 버퍼 덮어쓰기

C# (.NET 6+)
using System;
using System.Buffers.Text;
using System.Text;

// 입력 버퍼가 디코딩된 바이트로 덮어쓰여집니다
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는 입력 버퍼를 수정합니다. 이후에 원본 Base64 문자열이 필요하다면 사용하지 마세요 — 배열의 처음 bytesWritten 바이트에는 디코딩된 데이터가 들어 있고 나머지는 쓰레기 값입니다.

한 가지 주의할 점: System.Buffers.Text.Base64는 URL-safe Base64를 직접 처리하지 않습니다. 입력에 -_ 문자가 사용된 경우 (예: JWT 토큰), 이 메서드를 호출하기 전에 여전히 +/로 교체해야 합니다. 스팬 기반 API는 RFC 4648 표준 알파벳만 엄격하게 지원합니다. 현대 API에서 Base64url이 얼마나 흔한지를 고려하면 놀랍게도 DecodeFromUtf8Url 변형은 존재하지 않습니다.

터미널 출력에 구문 강조 추가

Spectre.Console 라이브러리는 JSON 강조를 포함한 풍부한 터미널 출력을 제공합니다 — Base64를 디코딩하고 결과를 표시하는 CLI 도구를 만들 때 유용합니다.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);

// 터미널에서 구문 강조와 함께 출력
AnsiConsole.Write(new JsonText(json));
// 컬러 JSON 출력:
// {
//   "host": "10.0.1.50",
//   "port": 8443,
//   "maxConn": 100
// }

원격 서비스에서 설정을 가져와 디코딩하는 CLI 도구에 특히 편리합니다. Base64 설정을 디코딩하고, JSON으로 역직렬화하고, 강조된 출력을 출력하면 — 몇 줄로 모두 처리됩니다. Spectre.Console은 더 복잡한 디코딩 데이터 구조를 터미널에 표시해야 할 때를 위한 테이블 렌더링, 프로그레스 바, 트리 뷰도 제공합니다.

경고:Spectre.Console 출력에는 ANSI 이스케이프 시퀀스가 포함됩니다. 파일로 파이프하거나 API에서 반환하지 마세요 — 터미널 표시에만 사용하세요.

CryptoStream으로 대용량 Base64 파일 스트리밍

500MB Base64 파일을 File.ReadAllText()로 읽고Convert.FromBase64String()을 호출하면 약 700MB 힙이 할당됩니다: 문자열 자체(UTF-16이므로 파일 크기의 두 배)에 디코딩된 바이트 배열까지요.CryptoStream + FromBase64Transform 조합은 청크 단위로 디코딩하여 메모리 사용량을 일정하게 유지합니다.

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}");

FromBase64TransformMode.IgnoreWhiteSpaces 플래그는 수동 정리 없이 줄바꿈이 포함된 Base64(PEM 파일, 이메일 내보내기)를 처리합니다. 이 플래그 없이는 입력의 줄바꿈이 FormatException을 유발합니다. 이것은 Java의getMimeDecoder()에 가장 가까운 .NET 방식으로 — 디코딩 중 공백 문자를 조용히 건너뜁니다.

CryptoStream이라는 이름은 오해를 불러일으킵니다 — Base64에는 암호화적인 것이 전혀 없습니다. Microsoft가 FromBase64Transform System.Security.Cryptography 네임스페이스에 넣은 것은 AES 및 다른 암호화 변환과 같은 인터페이스인 ICryptoTransform을 구현하기 때문입니다. 스트림 자체는 청크 단위로 데이터를 임의의 변환에 파이프합니다. 잘못된 네임스페이스에 위치한 범용 스트리밍 변환 파이프라인이라고 생각하면 됩니다.

ASP.NET Core 시나리오를 위한 비동기 스트리밍

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);
}

// 엔드포인트에서의 사용법:
// await DecodeStreamAsync(Request.Body, "uploaded-file.bin");
참고:ASP.NET Core 핸들러에서는 CopyTo 대신 CopyToAsync로 전환하세요. Kestrel에서 요청 스레드의 동기 I/O는 기본적으로 차단되며InvalidOperationException을 발생시킵니다.

C#에서 Base64 JWT 토큰 페이로드 디코딩

JWT는 점으로 구분된 세 개의 Base64url 인코딩 세그먼트로 구성됩니다. 중간 세그먼트가 페이로드입니다. JWT 라이브러리 없이 디코딩할 수 있습니다 — .으로 분리하고, Base64url 문자를 정규화하고, 패딩을 수정하고,Convert.FromBase64String()을 호출하면 됩니다. JWT가 +/ 대신 -_를 사용하고 =패딩을 제거하기 때문에 처음에는 거의 모든 개발자가 여기서 막힙니다.

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}");

    // 페이로드(두 번째 세그먼트) 가져오기
    string payload = parts[1];

    // URL-safe 문자를 표준 Base64로 교체
    payload = payload.Replace('-', '+').Replace('_', '/');

    // 4의 배수가 되도록 패딩 추가
    switch (payload.Length % 4)
    {
        case 2: payload += "=="; break;
        case 3: payload += "=";  break;
    }

    byte[] bytes = Convert.FromBase64String(payload);
    return Encoding.UTF8.GetString(bytes);
}

// 실제 형태의 토큰으로 테스트
string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
    + ".eyJzdWIiOiJ1c3ItNjcyIiwiaXNzIjoiYXV0aC5leGFtcGxlLmNvbSIsImV4cCI6MTc0MTk1NjgwMCwicm9sZXMiOlsiYWRtaW4iLCJiaWxsaW5nIl19"
    + ".SIGNATURE_PLACEHOLDER";

Console.WriteLine(DecodeJwtPayload(token));
// {"sub":"usr-672","iss":"auth.example.com","exp":1741956800,"roles":["admin","billing"]}

참고로 이것은 페이로드만 읽습니다. 서명을 검증하지 않습니다. 프로덕션 인증 검증에는 Microsoft.IdentityModel.JsonWebTokens 같은 적절한 라이브러리를 사용하세요. 하지만 디버깅, 로깅, 테스트 어서션에는 이 수동 방식으로 충분합니다.

디코딩된 JWT 페이로드를 타입이 있는 객체로 파싱

JSON 문자열을 얻은 후 System.Text.Json으로 역직렬화하면 문자열 조작 없이 개별 클레임에 접근할 수 있습니다:

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
);

// 위의 DecodeJwtPayload() 사용 후
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

자주 발생하는 실수

저는 이 실수들을 프로덕션 C# 서비스에서 모두 경험했습니다. 처음 두 가지가 코드 리뷰에서 제가 보는 Base64 관련 버그의 대부분을 차지합니다. 각각의 실수는 독립적으로 보면 명백해 보이지만, 더 큰 기능을 구현하는 도중에 Base64 디코딩이 파이프라인의 한 단계일 뿐일 때는 쉽게 놓칩니다.

입력에서 공백 제거를 잊는 경우

문제: 설정 파일, 환경 변수, 또는 사용자 입력에서 읽은 Base64 문자열에는 종종 끝에 줄바꿈이 있습니다. Convert.FromBase64String()은 \r\n을 포함하여 Base64 알파벳 외의 모든 문자를 거부합니다.

해결: .Trim() 또는 .Replace()를 호출하여 디코딩 전에 공백을 제거하세요.

Before · C#
After · C#
// 환경 변수에 끝에 줄바꿈이 있는 경우
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));
URL-safe Base64에 Convert.FromBase64String()을 사용하는 경우

문제: JWT 토큰과 일부 API 페이로드는 - 와 _ (URL-safe 알파벳)를 사용합니다. 표준 디코더는 + 와 / 만 허용하며 첫 번째 - 문자에서 FormatException을 발생시킵니다.

해결: 디코딩 전에 - 를 + 로, _ 를 / 로 교체하세요. 패딩도 수정해야 합니다.

Before · C#
After · C#
// JWT 페이로드 — 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"}
바이너리 데이터를 문자열로 변환하는 경우

문제: 디코딩된 이미지나 protobuf 바이트에 Encoding.UTF8.GetString()을 호출하면 쓰레기 값이 생성됩니다. 더 심각한 것은 그 문자열을 다시 바이트로 변환하면 유효하지 않은 UTF-8 시퀀스가 대체되어 데이터가 조용히 손상된다는 점입니다.

해결: 파이프라인 전체에서 바이너리 데이터를 byte[]로 유지하세요. 콘텐츠가 텍스트임을 아는 경우에만 GetString()을 호출하세요.

Before · C#
After · C#
byte[] decoded = Convert.FromBase64String(pngBase64);
string imageStr = Encoding.UTF8.GetString(decoded); // 바이너리 손상
File.WriteAllText("image.png", imageStr); // 손상된 파일
byte[] decoded = Convert.FromBase64String(pngBase64);
// 바이트를 직접 씁니다 — 문자열 변환 없음
File.WriteAllBytes("image.png", decoded);
인코딩을 지정하지 않는 경우 (플랫폼 기본값에 의존)

문제: 일부 오래된 .NET Framework API는 시스템의 현재 ANSI 코드 페이지를 기본값으로 사용하는데, 이는 머신마다 다릅니다. 코드 페이지 1252의 Windows 서버와 UTF-8의 Linux 컨테이너는 동일한 바이트에서 서로 다른 문자열을 생성합니다.

해결: 항상 Encoding.UTF8을 명시적으로 지정하세요. 절대 플랫폼 기본값에 의존하지 마세요.

Before · C#
After · C#
byte[] decoded = Convert.FromBase64String(encoded);
// Encoding.Default는 플랫폼마다 다릅니다
string result = Encoding.Default.GetString(decoded);
byte[] decoded = Convert.FromBase64String(encoded);
string result = Encoding.UTF8.GetString(decoded);
// Windows, Linux, macOS에서 일관됩니다

메서드 비교

.NET은 대부분의 개발자가 인식하는 것보다 더 많은 Base64 디코딩 메서드를 제공합니다. 아래 표는 모든 내장 옵션과 가장 일반적인 서드파티 대안 두 가지를 다룹니다. "메모리 할당" 열은 고처리량 서비스에서 가장 중요합니다 — 호출마다 새byte[]를 할당하는 메서드는 타이트한 루프에서 GC에 압박을 가합니다.

메서드
메모리 할당
오류 처리
스트리밍
커스텀 타입
설치 필요
Convert.FromBase64String()
새 byte[]
FormatException
불가
불가
불필요 (.NET Framework 1.1+)
Convert.TryFromBase64String()
호출자 Span
false 반환
불가
불가
불필요 (.NET 5+)
Convert.FromBase64CharArray()
새 byte[]
FormatException
불가
불가
불필요 (.NET Framework 1.1+)
Base64.DecodeFromUtf8()
호출자 Span
OperationStatus
부분
불가
불필요 (.NET Core 2.1+)
Base64.DecodeFromUtf8InPlace()
인플레이스
OperationStatus
불가
불가
불필요 (.NET Core 2.1+)
CryptoStream + FromBase64Transform
스트리밍 버퍼
Exception
가능
불가
불필요 (.NET Framework 2.0+)
BouncyCastle Base64
새 byte[]
Exception
가능
불가
필요 (NuGet)

일상적인 작업에는: Convert.FromBase64String(). 사용자 입력을 유효성 검사하는 핫 패스에는: TryFromBase64String(). 원시 요청 바이트에서 동작하는 ASP.NET Core 미들웨어에는: Base64.DecodeFromUtf8(). 대용량 파일에는: CryptoStream + FromBase64Transform. BouncyCastle은 다른 암호화 작업을 위해 이미 의존성 트리에 있는 경우에만 의미가 있습니다.

아무것도 컴파일하지 않고 빠르게 확인하려면 온라인 Base64 디코더 가 일회성 콘솔 앱을 작성하는 것보다 훨씬 빠릅니다.

자주 묻는 질문

C#에서 Base64 문자열을 텍스트로 어떻게 디코딩하나요?

Convert.FromBase64String()을 호출해 바이트 배열을 얻은 후 Encoding.UTF8.GetString()에 전달합니다. 인코딩은 원래 데이터를 인코딩할 때 사용한 것과 일치해야 합니다 — UTF-8은 거의 모든 현대 시스템에서 안전한 기본값입니다. 입력에 공백이나 줄바꿈이 포함될 수 있다면 디코딩 전에 .Trim()을 호출하거나 직접 제거하세요.

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

Convert.FromBase64String()과 Convert.TryFromBase64String()의 차이점은 무엇인가요?

FromBase64String()은 유효하지 않은 입력에 FormatException을 발생시킵니다. TryFromBase64String()은 bool을 반환하고 결과를 호출자가 제공한 Span<byte>에 기록하므로, 예외 오버헤드를 피해야 하는 핫 패스에 적합합니다. TryFromBase64String()은 .NET 5 이상이 필요합니다.

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");

C#에서 Base64url JWT 페이로드를 어떻게 디코딩하나요?

토큰을 점으로 분리하고 두 번째 세그먼트를 가져온 뒤 -를 +로, _를 /로 교체하고 =로 4의 배수가 되도록 패딩을 추가한 다음 Convert.FromBase64String()을 호출합니다. JWT 토큰은 URL-safe Base64 알파벳을 사용하는데, .NET의 표준 디코더는 이를 직접 처리하지 못합니다.

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"}

C#에서 Base64 디코딩 시 올바른 Encoding을 어떻게 선택하나요?

기본값으로 Encoding.UTF8을 사용하세요 — ASCII와 멀티바이트 유니코드 문자를 모두 처리합니다. 데이터가 순수 7비트 ASCII임이 확실한 경우에만 Encoding.ASCII를 사용하세요. .NET에서 UTF-16LE인 Encoding.Unicode는 원본 데이터가 UTF-16으로 인코딩된 경우에만 사용하는데, Windows 내부 문자열이나 PowerShell 내보내기에서 간혹 발생합니다.

Convert.FromBase64String()이 FormatException을 발생시키는 이유는 무엇인가요?

세 가지 일반적인 원인이 있습니다: 입력에 공백이나 줄바꿈이 포함된 경우(디코딩 전에 제거), -와 _ 같은 URL-safe 문자를 사용하는 경우(각각 +와 /로 교체), 패딩이 없거나 올바르지 않은 경우(패딩 후 전체 길이가 4의 배수여야 함). Java와 달리 .NET에는 공백을 허용하는 내장 MIME 디코더가 없으므로 직접 입력을 정리하거나 IgnoreWhiteSpaces 모드의 CryptoStream과 FromBase64Transform을 사용해야 합니다.

C# (.NET 6+)
// 공백 제거
string cleaned = rawInput.Replace("\n", "").Replace("\r", "").Trim();
byte[] decoded = Convert.FromBase64String(cleaned);

C#에서 대용량 Base64 데이터를 스트리밍으로 디코딩할 수 있나요?

가능합니다. System.Security.Cryptography의 FromBase64Transform과 함께 CryptoStream을 사용하세요. 읽는 동안 청크 단위로 디코딩하므로 파일 크기에 관계없이 메모리 사용량이 일정하게 유지됩니다. 입력에 줄바꿈이 포함된 경우 FromBase64TransformMode.IgnoreWhiteSpaces를 전달하세요. .NET 6+에서는 수동 청크 처리를 위해 Base64.DecodeFromUtf8()과 IAsyncEnumerable 패턴을 사용할 수도 있지만, 파일 간 디코딩에는 CryptoStream이 더 간단합니다.

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 — 브라우저에서 텍스트나 바이너리 데이터를 Base64로 인코딩합니다. C# 단위 테스트에 붙여넣을 테스트 픽스처 생성에 유용합니다.
  • JWT Decoder — 세 개의 JWT 세그먼트를 한 번에 디코딩하고 검사합니다. 필드별 페이로드 검사 기능으로 토큰을 읽을 때 C# 헬퍼를 작성하는 것보다 빠릅니다.
  • URL Decoder — URL 인코딩된 문자열을 퍼센트 디코딩합니다. API 응답에 Base64url 데이터와 퍼센트 인코딩된 쿼리 파라미터가 혼재할 때 유용합니다.
  • JSON Formatter — Base64 JWT 페이로드나 API 설정을 디코딩한 후 여기에 JSON을 붙여넣어 구조를 예쁘게 출력하고 유효성을 검사하세요.
다른 언어로도 제공됩니다: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 Richardson기술 검토자

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.