PowerShell에서의 Base64 디코딩: 완전한 가이드
로그의 한 줄, 설정 파일, 에러 메시지 어딘가에서 그것과 마주칩니다. 문자와 숫자가 길게 이어진 가운데 가끔 섞이는 더하기나 슬래시, 그리고 끝에는 의심쩍게 서 있는 등호 하나, 혹은 두 개. 잡음처럼 보입니다. 하지만 잡음이 아닙니다. 그것은 Base64이고, 당신이 원하는 것이 무엇인지 이미 아실 겁니다. 바로 그것이 숨기고 있는 내용입니다.
Base64는 번역입니다. 압축도 아니고, 잠금도 아닙니다. 어떤 바이트 시퀀스든 표시 가능한 텍스트로 다시 씁니다. 입력 바이트 3개마다 문자 4개를 씁니다(그래서 인코딩된 데이터는 원본보다 약 33% 커지고), 64자 알파벳에 끝 패딩으로서의 등호를 쓰는 방식입니다. 이 사이트의 홈 페이지가 알파벳과 비트 계산, 변형들을 충분히 다루므로, 이 글은 PowerShell이 차이를 만드는 곳에 시간을 씁니다. 당신이 부를 .NET 메서드 하나, 그것이 강제하는 규칙, 그리고 실제 작업에서 PowerShell 디코딩이 흥미로워지는 약 열두 개의 구석입니다.
메서드와 그 계약
PowerShell에는 자체 Base64 cmdlet이 없습니다. 일을 해 주는 것은 .NET 클래스의 메서드입니다. 이 클래스가 포함된 .NET Framework 1.1은 2003년 나왔고, PowerShell 자체가 출시되기 3년 전의 일입니다:
$bytes = [System.Convert]::FromBase64String("SGVsbG8sIFdvcmxkIQ==")
[System.Text.Encoding]::UTF8.GetString($bytes)
# Hello, World!
전체 API가 이것입니다: 문자열 하나가 들어가 바이트 배열 하나가 나옵니다. Windows PowerShell 5.1이든, Windows, Linux, macOS의 PowerShell 7이든, 모든 운영체제의 모든 PowerShell에서 동작합니다. 결국 .NET이기 때문이죠. 계약은 외울 만큼 짧으니, 표로 정리했습니다:
| 입력 | 돌아오는 것 |
|---|---|
$null |
빈 배열, 에러 없음. 호출 전에 PowerShell이 $null을 조용히 빈 문자열로 바꿉니다 |
| 빈 문자열 | 빈 배열, 에러 없음 |
| 유효한 페이로드 | byte[]이며, 데이터가 텍스트여도 문자열이 되는 일은 절대 없습니다 |
| 유효하지 않은 페이로드 | FormatException, MethodInvocationException에 싸여 전달됩니다 |
에러 핸들링을 쓰기 전에 한 가지 경고: 그 FormatException에는 세 가지 다른 죄를 한 문장으로 덮는 메시지 하나가 있습니다. 알파벳 밖의 문자, 패딩 기호 두 개를 넘는 것, 패딩 사이에 숨은 비공백 문자, 셋 다 정확히 같은 문장을 만들어냅니다. 보게 되면 메시지가 당신이 어떤 짓을 저질렀는지를 알려 주지 않으므로, 돌아가서 입력을 읽어야 합니다:
try {
[System.Convert]::FromBase64String("SGV!G8s=")
}
catch {
$real = $_.Exception.InnerException
$real.GetType().Name
# FormatException
$real.Message
}
그리고 4의 배수 규칙에는, 처음 부딪힌 사람들이 놀라는 한 가지 끝이 있습니다. 패딩 없는 문자 4개는 완전히 유효합니다. 마지막 문자의 잉여 비트가 버려진다는 뜻이죠. 문자 3개는 4의 배수가 아니라서 거부됩니다:
[System.Convert]::FromBase64String("SGVs").Count
# 3: 패딩 없이 4자면 문제없음
[System.Convert]::FromBase64String("SGV")
# FormatException: 3자는 4의 배수가 아님
디코더가 받아들이고, 받아들이지 않는 것
디코더는 알파벳에 대해서는 엄격하고, 특정한 한 가지에 대해서는 관대합니다. 유효한 문자는 64개의 Base64 숫자(A부터 Z까지, a부터 z까지, 0부터 9까지, 더하기와 슬래시)와 끝 패딩으로서의 등호입니다. 딱 네 가지 공백 문자가 어디에서든, 아무리 자주 나와도 무시됩니다: 탭, 줄바꿈 문자, 캐리지 리턴, 공백. 공식 .NET 문서가 이들의 유니코드 이름을 나열한다는 것은, 이것이 문서화된 보장이지 운 좋은 사고가 아니라는 뜻입니다.
실전에서 이것이 초능력입니다. Base64를 유명하게 만든 메일 인코딩 MIME은 인코딩된 줄을 76자에서 줄바꿈하므로, 메일, 티켓, 로그 파일을 거쳐온 페이로드는 보통 여러 줄로 깨진 채 도착합니다. 디코더는 신경 쓰지 않습니다. 있는 그대로 붙여 넣으세요:
$wrapped = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gQmFzZTY0IHRleHQg`r`n" +
"YXJyaXZlcyB3cmFwcGVkIGF0IHNldmVudHktc2l4IGNvbHVtbnMgaW4gbWFpbCwgc28gdGhlIGRl`r`n" +
"Y29kZXIgbXVzdCBub3QgY2FyZS4="
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($wrapped))
# The quick brown fox jumps over the lazy dog. Base64 텍스트가
# 메일에서 76열로 줄바꿈되어 오므로, 디코더는 신경 쓸 필요가 없다.
알파벳 문자가 아닌 나머지는 전부 정지 신호입니다. 현장에서 가장 흔한 범인은 고정 공백(웹 페이지에서 복사해 온 텍스트의 단골)과 바이트 순서 마크(BOM)(틀린 인코딩으로 파일을 읽으면 따라다니는 보이지 않는 표식)입니다. 이 메서드에게는 둘 다 공백이 아니므로, 둘 다 예외를 던집니다:
try {
[System.Convert]::FromBase64String("SGVs`u{00A0}G8=")
}
catch {
$_.Exception.InnerException.GetType().Name
# FormatException
}
이 엄격함은 의도적인 것이지 까탈이 아닙니다. 2006년 Base64를 표준으로 정리한 RFC 4648은, 프로토콜이 명확히 관용을 허용하는 경우를 제외하고 구현은 알파벳 밖의 문자를 거부해야 한다고 말합니다. 조용히 이질적 문자를 삼키는 디코더는, 알파벳만 살펴보는 모든 것을 끼고 지나가 데이터를 밀수하는 은밀한 채널로 뒤바뀔 수 있기 때문입니다. .NET 디코더는 엄격한 규칙을 따르며, 보통 당신은 그렇게 되기를 원합니다.
바이트 배열은 문자열이 아니다
이 메서드는 바이트 배열에서 의도적으로 멈춥니다. 그 바이트가 의미하는 바는 당신만이 내릴 수 있는 두 번째 판단이고, PowerShell Base64 작업에서 가장 유명한 실수는 바로 이 판단을 틀리는 것입니다. 기본 가정인 UTF-8은 인터넷 위의 거의 모든 것에 대해 정확하며, 왕복은 2회 호출입니다:
$bytes = [System.Convert]::FromBase64String("SGVsbG8sIFdvcmxkIQ==")
[System.Text.Encoding]::UTF8.GetString($bytes)
# Hello, World!
실제로 손이 갈 인코딩과, 각각을 잘못 고르면 일어나는 일:
| 인코딩 | 언제 쓰나 | 잘못 고르면 |
|---|---|---|
UTF8 |
웹 API, JSON, JWT, 현대의 모든 것. 안전한 기본값 | Latin-1이나 UTF-16 텍스트가 모지바케로 돌아온다 |
Unicode (UTF-16LE) |
페이로드가 Windows 도구, 레지스트리 값, 혹은 출하 전에 인코딩된 .NET 문자열에서 왔을 때 | 두 바이트가 의도된 자리에서 한 바이트를 읽었으니, 각 문자 주위에 간격이 생긴다 |
ASCII |
고전적인 HTTP Basic 인증 자격 증명과 기타 7비트 보증 프로토콜 | 값이 127을 넘는 것은 전부 물음표가 된다 |
Latin1 |
UTF-8 이전의 레거시 유럽 텍스트 | 멀티바이트 UTF-8 시퀀스가 여러 개의 잘못된 문자로 쪼개진다 |
Default |
거의 절대. 이것은 이 머신의 시스템 코드 페이지입니다 | Windows 지역 설정마다 스크립트 동작이 달라진다 |
클래식한 실패는 UTF-8 텍스트를 UTF-16으로 디코딩하는 것입니다. 바이트는 진짜이고, 메서드는 만족하며, 결과는 여전히 쓰레기입니다:
# "SGk="는 "Hi"의 UTF-8 바이트
[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("SGk="))
# 읽을 수 없는 문자 하나: UTF-8 두 바이트가 UTF-16 2바이트 유닛 하나로 읽힘
실용적인 규칙: 디코딩된 텍스트가 각 문자 주위에 보이지 않는 간격이 있는 것처럼 보이거나, 다른 알파벳인 것처럼 보인다면, 당신은 인코딩을 한 단계 벗어난 것입니다. 데이터가 어디서 만들어졌는지 물어보고, 확신이 없다면 UTF-8을 신뢰하되, 눈으로 처음 몇 글자를 확인하세요. 그리고 인코딩은 디코딩 전에, 모지바케가 로그에 나타난 후에가 아니라 결정하세요.
base64url: URL에서 사이가 좋은 알파벳
당신이 손대게 될 모든 API 토큰, JWT, URL에 내장된 식별자에서 Base64의 사촌을 만나게 됩니다. 표준 Base64의 더하기와 슬래시는 퍼센트 인코딩 후에만 URL 안에서 합법이고, 등호 패딩은 필드 구분자처럼 보입니다. 그래서 RFC 4648이 URL과 파일명에서 안전한 알파벳을 정의했습니다: 같은 64자이지만, 더하기는 하이픈으로, 슬래시는 밑줄로 바뀌어 있습니다. 패딩은 보통 완전히 생략됩니다. 데이터의 길이가 그것을 불필요하게 만들 테니깐요. RFC는 이 변형을 "base64"가 아니라 base64url이라고 불러야 한다고 조심스럽게 명시하고, 이 섹션의 나머지도 그렇게 따릅니다.
.NET에는 이 용도의 전용 클래스가 실제로 들어 있습니다: .NET 9에서 추가된 System.Buffers.Text.Base64Url로, ReadOnlySpan<T> 매개변수를 중심으로 완전히 만들어진 빠른 인코딩/디코딩 메서드를 가지고 있습니다. 현재의 PowerShell(7.4 이상, 이 클래스를 담은 .NET 버전 위에서 돌아가는 것)은 실제로 오늘 바로 이 스팬을 받는 오버로드를 직접 부를 수 있습니다. 메서드 바인더가 이제 배열/문자열에서 스팬으로의 암시적 변환을 수행하기 때문이죠. 그래서 [System.Buffers.Text.Base64Url]::DecodeFromChars("--__AQI")가 형식적인 절차 없이 동작합니다. 항상 그런 것은 아니었습니다: Windows PowerShell 5.1과 오래된 PowerShell 7.x 릴리스는 스팬 매개변수에 바인딩조차 할 수 없었고, 이 클래스는 애초에 .NET 9 이전에는 존재하지도 않았습니다. 그래서 5.1, 오래된 7.x, .NET 9 이전 호스트에서 돌아가야 하는 스크립트는 여전히 포터블 버전이 필요합니다: 두 문자를 바꾸고, 텍스트를 표준 디코더에 넘기기 전에 패딩을 복원하는 것입니다. 추가할 패딩은 길이를 4의 배수로 만드는 만큼이면 됩니다:
$token = "--__AQI" # base64url, 패딩 없음
$standard = $token.Replace("-", "+").Replace("_", "/")
$pad = 4 - ($standard.Length % 4)
if ($pad -eq 4) { $pad = 0 }
$standard = $standard.PadRight($standard.Length + $pad, "=")
$bytes = [System.Convert]::FromBase64String($standard)
$bytes -join ","
# 251,239,255,1,2
그 작은 블록 안에는 두 개의 함정이 있습니다. 첫째, 패딩 계산입니다: 길이가 이미 4의 배수인 페이로드는 패딩이 필요 없고, -eq 4 가드가 식을 정직하게 지켜 줍니다. 둘째, 방향입니다: 디코딩만 하는 경우에는 패딩을 더하고 문자를 바꿉니다. 표준 디코더는 패딩이 거기 있다고 기대하므로, 표준 Base64 입력에서 패딩을 제거하는 일은 절대 하지 않습니다. 출처가 JWT나 API 토큰이라면, 패딩 없는 base64url일 테고, 위 레시피가 바로 당신이 원하는 형태입니다.
키 없이 JWT 열기
JSON Web Token은 마침표로 연결된 base64url 세그먼트 세 개입니다: 헤더, 페이로드, 서명. 앞의 둘은 평범한 JSON이고, Base64는 암호화가 아니므로, 토큰을 가진 사람은 누구나 둘 다 읽을 수 있습니다. 이것은 결함이 아니라 기능입니다: 토큰은 검사되도록 설계되어 있고, 위조할 수 없게 만드는 것은 서명입니다. PowerShell에서 살짝 들여다보는 것은 3줄 작업입니다:
$jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
$parts = $jwt.Split(".")
function Decode-UrlSegment([string]$segment) {
$standard = $segment.Replace("-", "+").Replace("_", "/")
$pad = 4 - ($standard.Length % 4)
if ($pad -eq 4) { $pad = 0 }
$standard = $standard.PadRight($standard.Length + $pad, "=")
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($standard))
}
Decode-UrlSegment $parts[0] | ConvertFrom-Json | ConvertTo-Json -Compress
Decode-UrlSegment $parts[1] | ConvertFrom-Json
# name 프로퍼티:
(Decode-UrlSegment $parts[1] | ConvertFrom-Json).name
# John Doe
세 가지를 마음에 새겨 두세요. 세 번째 세그먼트인 서명도 base64url이지만, 디코딩되면 텍스트가 아니라 바이너리 서명 바이트입니다. 거기서 잘 정리된 JSON을 기대하지 마세요. 헤더는 보통 토큰에 서명한 알고리즘이 무엇인지(HS256, RS256, ...) 알려 줄 뿐이고, none라고 적힌 헤더는 편의가 아니라 경고 신호입니다. 그리고 페이로드를 읽는 것은 그것을 신뢰하는 것이 아닙니다: base64는 클레임들을 보이게 해 줄 뿐, 진정성 있는 것으로 만드는 것은 서명뿐입니다. 당신의 일이 토큰을 받아들이는 것이라면, 발행자의 키로 서명을 검증하세요. 하나의 토큰을 디버깅하는 것이라면, 위 코드가 필요한 전부입니다.
파일, PEM, 그리고 바이트로 가는 긴 길
가장 흔한 파일 형태는, 더 큰 것의 Base64를 담은 텍스트 파일입니다: 백업 블롭, 내려받은 바이너리, 직렬화된 오브젝트. 왕복은 4줄이고, 현대적인 출력 읽기 방식은 텍스트 추측이 아니라 진짜 바이트 배열입니다:
$encoded = Get-Content -Path ./payload.b64 -Raw
$encoded = $encoded.Trim()
$bytes = [System.Convert]::FromBase64String($encoded)
[System.IO.File]::WriteAllBytes("./payload.bin", $bytes)
$bytes.Length
# 텍스트가 싣고 있던 바이트 수
원래 바이너리를 다시 읽는 것에서 PowerShell 6과 이후 버전이 자신의 가치를 증명합니다. -AsByteStream 매개변수는 원시 바이트를 읽고, -Raw와 함께 사용하면 진정한 byte[]을 한 번에 건네 줍니다:
$bytes = Get-Content -Path ./photo.png -AsByteStream -Raw
$encoded = [System.Convert]::ToBase64String($bytes)
Set-Content -Path ./photo.b64 -Value $encoded -NoNewline
$bytes.Length
# 33퍼센트 텍스트 세금이 붙기 전의 원래 크기
-Raw를 빼면 개별 바이트 오브젝트의 스트림을 얻습니다(캡처하면 Object[]). 점검에는 괜찮지만, 배열을 기대하는 .NET 메서드에 넘길 때는 아닙니다. 그리고 Windows PowerShell 5.1에는 애초에 -AsByteStream이 없으므로, 5.1에서 신뢰할 수 있는 읽기는 어디에나 존재하는 [System.IO.File]::ReadAllBytes()입니다.
PEM은 모든 인증서와 개인 키에서 아시는 갑옷 입은 사촌입니다: 표준 Base64 본문, 보통 64자에서 줄바꿈되며, -----BEGIN ...와 -----END ... 줄 사이에 끼어 있습니다. 갑옷은 텍스트이고, 본문은 페이로드입니다. 갑옷을 벗기고, 줄을 합치고, 디코딩하세요:
$pem = Get-Content -Path ./certificate.pem -Raw
$body = ($pem -split "`n") | Where-Object { $_ -notmatch "^-----" } | ForEach-Object { $_.Trim() }
$der = [System.Convert]::FromBase64String(($body -join ""))
$der.Length
# 인증서의 바이너리 DER 크기
표준 디코더는 어차피 공백을 무시하므로, -join ""는 요구사항이 아니라 이중 안전장치입니다. 하지만 스크립트가 무엇을 제거하는지 명시적으로 하면, 모든 머신과 모든 줄 끝 관례에서 같은 동작을 합니다. 반대 방향, DER 바이트를 PEM으로 감는 일은 Base64 인코더에 텍스트 2줄을 더한 것에 불과하고, 자매 사이트의 인코딩 글이 64열 줄바꿈을 온전히 보여 줍니다.
인증서와 Windows 도구함
인증서는 일상 작업에서 가장 무거운 Base64 시민이며, PowerShell은 그 일족 전체를 감당할 수 있습니다. PFX 파일은 인증서와 개인 키의 바이너리 번들인데, 설정 파일과 배포 스크립트에서 Base64 텍스트로 방치되어 있는 것을 가장 자주 만나는 형식입니다. 그것을 살아 있는 인증서로 되돌리는 디코딩은 .NET 타입으로 한 줄이고, PowerShell 7에서는 크로스 플랫폼으로 동작합니다:
$bytes = [System.Convert]::FromBase64String($pfxText)
$password = ConvertTo-SecureString "secret" -AsPlainText -Force
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes, $password)
$cert.Subject
# CN=example.org
$cert.NotAfter
# 더 이상 참이 아니게 되는 시점
PowerShell 7에는 Get-PfxCertificate도 함께 들어 있습니다. -Password 매개변수로 PFX 파일을 디스크에서 바로 읽으므로, 디스크에 있는 파일은 수동 디코딩을 완전히 건너뛸 수 있습니다. 맨 인증서(키 없음)는 더 간단합니다: DER 바이트가 비밀번호 없이 같은 X509Certificate2 타입에 바로 들어갑니다.
언어 바깥에서는 알 가치가 있는 네이티브 도구가 두 개 있습니다. Windows에서는 certutil -decode infile.b64 outfile가 파일-입력/파일-출력 의미론으로 Base64 파일을 디코딩하며(덮어쓰려면 -f 추가), 그것이 일반 명령 프롬프트에서 빠른 수리의 단골 도구가 되는 이유입니다. 그 동생 certutil -encode에는 기억할 가치가 있는 플래그가 있습니다: -unicodetext는 Base64 인코딩 이전에 입력 텍스트를 UTF-16으로 변환하여, 인코딩 결정 전체를 하나의 스위치 안에 숨깁니다. Linux와 macOS에서는 클래식한 유틸리티가 base64 -d로, 파일이나 표준 입력을 디코딩하고 기본적으로 줄바꿈을 건너뜁니다. GNU coreutils에서는 페이로드에 Windows 메일에서 온 공백, 탭, CRLF도 섞여 있다면 -i를 추가하세요.
Base64 봉투 속의 명령
PowerShell은 1.0 버전부터 Base64를 말해야 할 내장된 이유가 있었습니다. 바로 호스트 자체의 -EncodedCommand 매개변수입니다. pwsh에 Base64 문자열을 건네면, 바이트를 UTF-16LE로 디코딩하고, 그 결과가 명령으로 실행됩니다. 문서에서 가져온 공식적인 목적은, 복잡한 따옴표나 중괄호가 필요한 명령을 바깥 셸의 따옴표 규칙과 싸우지 않고 제출하는 것입니다:
$command = "Write-Host encoded-hello"
$encoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
# VwByAGkAdABlAC0ASABvAHMAdAAgAGUAbgBjAG8AZABlAGQALQBoAGUAbABsAG8A
pwsh -NoProfile -EncodedCommand $encoded
# encoded-hello
두 번째 줄을 잘 읽어 보세요. 모두가 여기서 넘어지는 곳이니까요. 페이로드는 UTF-16LE여야 하며, 그것은 [System.Text.Encoding]::Unicode입니다. 대신 UTF-8로 명령을 인코딩하면, PowerShell은 마냥 좋아서 UTF-16LE로 디코딩하고 모지바케로 이루어진 명령을 실행하며, 그것이 만들어내는 에러 메시지는 그 실수의 완벽한 초상화입니다:
$wrong = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($command))
pwsh -NoProfile -EncodedCommand $wrong
# 에러: 깨진 문자의 벽, "The term ... is not recognized..."
같은 메커니즘 때문에 보안 팀들이 PowerShell의 Base64를 주시합니다. -EncodedCommand에 전달되는 긴 불투명한 토큰은 자동화 툴링의 흔한 형태이며, 바로 그래서 엔드포인트 보호 제품들이 실행 전에 이러한 페이로드를 디코딩합니다. Base64에 명령을 디코더로부터 숨기는 것은 아무것도 없습니다. 그것은 프로세스 목록을 읽는 사람에게는 숨길 뿐입니다. 자신의 자동화를 위해 인코딩된 명령을 생성한다면, 원본 명령을 토큰 옆에 두세요. 토큰 자체가 새벽 3시에 당신에게 자신을 설명해 주지는 않으니까요.
입력이 거대할 때의 디코딩
일상적인 크기에 대해서는 단일 메서드 방식이 빠른 방법입니다. 5메가바이트 바이너리는 약 690만 자의 문자열이 되며, 그 문자열의 디코딩은 현대 머신에서 1자리 밀리초를 듭니다. .NET 문서 자체의 주석은, FromBase64String이 모든 데이터를 담는 단일 문자열을 처리하도록 설계되어 있다는 것인데, 사실입니다. 그리고 아주 큰 한계까지도 괜찮습니다. 이 메서드는 의미 있는 추가 복사 없이 문자열을 그 자리에서 다루니까요.
페이로드가 단일 문자열에 편하게 담기기보다 크거나, 스트림(다운로드, 소켓, 거대한 로그)으로 도착하는 경우, 문서화된 도구는 CryptoStream에 감긴 System.Security.Cryptography.FromBase64Transform입니다. Base64 텍스트를 넣으면 디코딩된 바이트가 나가고, 어느 순간에도 살아 있는 것은 작은 버퍼 하나뿐입니다. 참고로, 이것의 C# 헬퍼인 TransformStream은 확장 메서드이고, PowerShell은 확장 메서드를 보지 못하므로, CryptoStream을 직접 인스턴스화합니다:
$inputStream = [System.IO.File]::OpenRead("./payload.b64")
$transform = [System.Security.Cryptography.FromBase64Transform]::new()
$stream = [System.Security.Cryptography.CryptoStream]::new(
$inputStream, $transform, [System.Security.Cryptography.CryptoStreamMode]::Read)
$destination = [System.IO.File]::Create("./payload.bin")
$buffer = New-Object byte[] 65536
while (($read = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
$destination.Write($buffer, 0, $read)
}
$destination.Dispose()
$stream.Dispose()
$inputStream.Dispose()
업무의 90퍼센트에 대해, 간단한 경로가 여전히 정답입니다. 텍스트 파일 전체를 Get-Content -Raw로 읽고, 자르고, 디코딩하고, 바이트를 쓰세요. 파일이 메모리에 편하게 담기기보다 크거나, 데이터가 조각 조각 도착할 때 스트림 버전을 꺼내세요. 그리고 줄을 돌면서 각 줄을 각각 디코딩하려는 시도를 하지 마세요. Base64의 4자 그룹은 줄바꿈을 존중하지 않으므로, 그룹을 중간에 가르는 줄은 혼자서 디코딩되지 않습니다. 텍스트 전체를 읽고, 나서야 한 번 디코딩하세요.
오후를 태워 버리는 함정들
- 인코딩 추측. UTF-8을 UTF-16으로, Latin-1을 UTF-8으로 읽으면, 자신 넘치는 모지바케가 만들어집니다. 데이터의 출처에서 인코딩을 결정하고, 기본값은 UTF-8으로 두며, 나머지를 신뢰하기 전에 디코딩된 처음 몇 글자를 보세요.
- 웹에서 온 보이지 않는 문자. 페이지나 리치 텍스트 이메일에서 붙여 넣은 고정 공백이나 바이트 순서 마크(BOM)는 디코더에게 이질적 문자이며, 일반적
FormatException을 던집니다. 디코딩 전에 입력을.Trim()과 비출력 문자 점검을 통과시키세요. - 패딩 혼동. 표준 Base64는 끝에
=또는==를 달고 도착하고, 토큰의 base64url은 패딩 없이 도착합니다. 하나를 다른 쪽을 위해 만들어진 레시피에 넣는 것이 API 업무에서 가장 흔한 조용한 파괴이며, base64url 섹션의 길이 체크가 바로 그 가드입니다. - 한 문장, 세 가지 죄.
FormatException메시지가 잘못된 문자, 과도한 패딩, 더러운 패딩을 한 번에 덮어 주므로, 메시지만 기록하는 catch 블록은 당신을 빙글빙글 돌게 만듭니다. 입력의 길이와 첫 문제 지역도 함께 기록하세요. - 문자열이 돌아올 거라 기대하기. 결과는 항상 바이트 배열입니다. 그것을 바로 문자열 포맷팅하기 시작하는 순간, 얻는 것은 텍스트가 아니라 숫자 목록입니다. 명시적인 인코딩으로 변환하세요. 한 번만, 마지막에.
- 5.1 파일 기본값. Windows PowerShell 5.1은 BOM 없는 파일을 시스템 ANSI 코드 페이지로 읽는 반면, PowerShell 7은 UTF-8을 가정합니다. 5.1에서 스크립트가 Base64 텍스트 파일을 읽는 상황에서 파일이 페이로드 주변에 비-ASCII가 섞인 UTF-8이면, 파괴는 디코더가 보기 전에 이미 일어납니다.
- Base64를 잠금으로 취급하기. 이것은 번역입니다. Base64로 쓴 비밀번호, 토큰, 시크릿은 의상을 입은 평문이며, 이 행성의 모든 디코더(이 글도 포함)가 한 줄로 그것을 엽니다.
스크립트를 정직하게 유지하는 습관들
- 디코딩 전에 외부 입력을 자르세요.
.Trim()하나만으로도 어떤 에러 핸들러보다 더 많은 프로덕션 인시던트를 없애 줍니다. - 출처가 신뢰할 수 없다면, 디코딩 전에 검증하세요: 허용되는 네 가지 공백 문자를 제거한 뒤, 문자열은 알파벳 문자와 끝에 최대 두 개의 등호만 일치해야 합니다. 간단한 정규식 체크가 수수께끼 같은 예외를 깔끔한 입력 거부 메시지로 바꿔 줍니다.
- 바이트는 마지막 단계까지 바이트로 두세요. 한 번만 디코딩하고,
byte[]을 필요로 하는 파일 API나 인코더에 건넨 다음, 그때서야 의도적인 인코딩으로 텍스트를 변환하세요. - 페이로드가 아니라 길이를 기록하세요. 입력의 크기와 디코딩된 출력의 크기는, 잠재적으로 민감한 데이터를 로그에 붙여 넣지 않고도 디코딩 실패에 대해 거의 모든 것을 알려 줍니다.
- 무엇이든 네트워크를 건너게 되는 것이라면, 그것을 디코딩하는 같은 줄의 코드에서 그것이 어떤 알파벳(표준 또는 base64url)이고 어떤 패딩 관례인지 기록하세요. 그 주석의 소비자는 미래의 당신입니다.
PowerShell이 디코더를 물려받은 방식
PowerShell의 Base64에 대한 가장 짧은 진짜 역사는, PowerShell이 그것을 쓴 적이 없다는 것입니다. 당신이 쓰는 메서드, Convert.FromBase64String은 2003년 .NET Framework 1.1과 함께 출시되었고, 2006년 11월 버전 1.0 이후의 모든 PowerShell은 단순히 자신이 돌아가는 .NET을 노출했을 뿐입니다. 프로젝트는 개발 중에는 Monad라고 불렸고, 2003년 10월 Professional Developers Conference에서 처음 공개되었으며, 출시할 때는 그것이 감싸고 있는 .NET 인코더/디코더 쌍이 이미 세 살이 되어 일상에서 쓰이고 있었습니다.
포맷 자체는 셸이 출시된 그 해에 표준화되었습니다. 2006년 10월에 발행된 RFC 4648은 알파벳, 패딩 규칙, 엄격한 디코딩에 대한 기대, base64url 변형을 고정시킨 문서이며, 여전히 오늘날 FromBase64String이 구현하는 동작을 정확히 기술하고 있습니다. 2016년 8월 PowerShell이 PowerShell Core라는 이름으로 오픈 소스이고 크로스 플랫폼이 되었을 때, 디코더는 아무 변경 없이 Linux와 macOS에 따라 왔습니다. 바꿀 것이 없었기 때문입니다.
유일한 진정한 추가물은 PowerShell Gallery의 커뮤니티 관리 모듈 Microsoft.PowerShell.TextUtility입니다. 그 ConvertFrom-Base64 cmdlet은 같은 .NET 메서드를 감싸, -AsByteArray 스위치와 UTF-8로 디코딩하는 텍스트 기본값을 추가합니다. cmdlet 형태를 선호한다면 Install-Module -Name Microsoft.PowerShell.TextUtility로 설치하세요. 단 한 가지 주의할 점: 이 모듈은 이제 아카이브되었고 더 이상 적극적으로 유지되지 않습니다. 그것이 새로운 스크립트에 내장 메서드가 여전히 추천인 또 다른 이유입니다.
기억할 가치가 있는 사실들
- 디코더는 입력의 어디에 있든 탭, 줄바꿈 문자, 캐리지 리턴, 공백을 무시합니다. 100줄로 줄바꿈된 텍스트는 긴 한 줄과 정확히 같은 방식으로 디코딩됩니다.
$null과 빈 문자열은 둘 다 불평 없이 빈 배열로 디코딩되며,FromBase64String은 끝에서 유례없이 관대합니다.- 단 하나의
FormatException메시지가 세 가지 다른 실패 모드를 덮고 있습니다. 그것이 발화하면, 답은 메시지가 아니라 입력에 있습니다. "SABpAA=="는 PowerShell 자체의 내부 인코딩, UTF-16LE로 된 문자열Hi입니다. 같은 두 글자의 UTF-8 인코딩보다 두 배 길며, 그 비률은 당신이 읽는 모든 Base64에서 Windows 네이티브 텍스트의 지문입니다.-EncodedCommand은 첫 PowerShell 릴리스부터 존재해 왔고, 그 페이로드는 UTF-8이 아니라 UTF-16LE여야 한다는 것이 규정되어 있습니다. 틀린 인코딩으로 인코딩하면, 셸은 마냥 좋아서 당신의 모지바케를 실행합니다.- .NET의 새로운 스팬 기반 Base64 헬퍼(
Base64Url클래스를 포함)는 스팬이 메서드 바인더가 바인딩할 수 없는 byref형 타입이기 때문에, 오래된 PowerShell 릴리스에서는 닿을 수 없었습니다. 그것은 바뀌었습니다: 현재 PowerShell(7.4 이상, 그 클래스를 담기에 충분히 새로운 .NET 버전 위에서)은 배열 또는 문자열 인수를ReadOnlySpan<T>매개변수에 불평 없이 해석하므로, 직접 호출이 오늘 동작합니다. 두 문자 스왑은 남은 유일한 경로가 아니라, Windows PowerShell 5.1과 오래된 호스트에서도 동작한다는 점에서 자신의 값을 버는 버전입니다. -Raw없는Get-Content -AsByteStream은 바이트 배열이 아니라 바이트 오브젝트의 스트림을 줍니다.-Raw를 더하면, 타입은 정확히 .NET 메서드가 기대하는 것입니다.
긴 우회로
이 글의 모든 것은 Base64 문자열을 받아 데이터를 되찾는 것에 관한 것입니다. 거울상의 연산, 데이터를 Base64로 바꾸는 일은, PowerShell의 문자열은 바이트가 아니고, UTF-16은 크기를 두 배로 만들며, 줄바꿈에는 관례적 폭이 두 개 있고, base64url 출력은 자체적인 두 문자 수술이 필요하다는 사실에 부딪히기 전까지는 원라이너처럼 보입니다. 그 방향은 자매 사이트의 관련 글인 PowerShell에서의 Base64 인코딩에서 온전히 다뤄지며, 자신만의 함정과 자신만의 역사를 가지고 있습니다. 이 페이지는 아래에서 그 글로 링크합니다.
마지막 업데이트: 2026-09-07