Visual Basic에서의 Base64 인코딩: 완전한 가이드
Visual Basic 개발자들이 25년째 만나고 있는 문제가 하나 있습니다. 손에는 JPEG, 바이너리 덩어리, 라이선스 파일, 혹은 지극히 평범한 문장이 있고, 바로 앞의 채널은 텍스트만 받습니다. JSON 필드, 환경 변수, 이메일 첨부 파일, URL, 설정 파일, 텍스트 타입의 데이터베이스 컬럼, 이 건물 안의 나머지 모든 문은 한 규칙을 공유하죠: 인쇄 가능한 문자만 허용. Base64는 바이너리를 입장시키는 경비원입니다. 바이트를 글자, 숫자, 더하기, 슬래시, 등호의 흐름으로 다시 쓰기 때문에, 텍스트로 여행하는 무엇도 그것을 실어 나를 수 있습니다. 그리고 좋은 소식: 당신이 필요로 할 수 있는 인코더의 모든 조각은 이미 .NET 런타임 안에 있습니다. 패키지 없이, 구성 요소 없이, 복잡한 절차 없이.
한숨에 정리합니다. 이 사이트의 홈 페이지가 포맷 자체를 깊이 있게 다루고 있으니까요: Base64는 입력 바이트 3개를 받아 64기호 알파벳에서 문자 4개로 쓰며, 바이트 수가 3으로 나누어 떨어지지 않으면 꼬리에 = 한두 개를 추가합니다. 그 4대 3 거래가 바로 인코딩된 데이터가 원본보다 약 3분의 1 더 큰 이유이며, 한 번의 인코딩마다 한 번씩 내는 유명한 크기 세금입니다. 이 글의 나머지는, 당신이 만들어 내는 인코딩이 다음 시스템이 정확히 기대하는 것이 되도록 하는 데 관한 것입니다: 맞는 알파벳, 맞는 패딩, 맞는 줄바꿈, 맞는 문자 집합.
인코더 도구함: 전부 내장되어 있습니다
런타임의 인코딩 쪽도 디코딩 쪽과 같은 네 물결에 걸쳐 자라 왔으므로, 도구함은 여전히 지원되는 옵션의 긴 꼬리를 갖고 있습니다. 전체 패밀리와, 각각이 어떤 일을 위해 만들어졌는지 보여 드릴게요:
| API | 사용 가능 | 용도 |
|---|---|---|
System.Convert.ToBase64String |
.NET Framework 1.1 (2003) | 클래식 그 자체. 배열 하나 들어가고, 문자열 하나 나옵니다. 배열 부분집합, 스판, 선택적 MIME 스타일 줄바꿈용 오버로드를 갖습니다. |
System.Convert.ToBase64CharArray |
.NET Framework 1.1 (2003) | 이미 당신이 할당해 둔 문자 버퍼에 인코딩된 문자를 쓰고, 몇 개의 문자를 썼는지 돌려 줍니다. |
System.Convert.TryToBase64Chars |
.NET Core 2.1 (2018) | 스판 기반, 할당 부담이 적은 인코딩: 당신이 제공한 문자 스판에 쓰며, 예외 대신 부울 값으로 답합니다. |
System.Buffers.Text.Base64 |
.NET Core 2.1 (2018) | 로우레벨 스판 기반 인코딩: 당신의 UTF-8 버퍼에 쓰고, 인플레이스 팽창하며, 버퍼 크기는 GetMaxEncodedToUtf8Length로 잡습니다. |
System.Buffers.Text.Base64Url |
.NET 9 (2024) | URL 안전 알파벳(같은 64문자 체계에 -와 _가 +와 /의 자리를 차지함), 패딩 없이. 오래된 런타임에서는 Microsoft.Bcl.Memory NuGet 패키지로 들어옵니다. |
ToBase64Transform + CryptoStream |
.NET Framework 1.1 (2003) | 스트리밍 인코딩: 파일을 청크 단위로 읽고, 인코딩된 텍스트를 써 내며, 거대한 입력에서도 메모리를 평평하게 유지합니다. |
버전 지형도는 이렇습니다: .NET 10이 지금의 장기 지원 릴리스(2025년 11월, 2028년 11월까지 지원)이며, .NET 8과 .NET 9는 2026년 11월까지 지원되고, .NET 11은 프리뷰 중으로 새 Base64 편의 메서드 한 무더기가 곧 도착합니다. 위 표의 모든 것은 그 모든 버전에서 안정적입니다. 유일한 버전 문턱은 Base64Url입니다: .NET 9부터 내장되어 있고, .NET Framework 4.6.2 이상에서는 Microsoft.Bcl.Memory 패키지로 사용할 수 있으며, 이 글이 당신에게 설치를 부탁하는 단 하나의 패키지입니다. 연습용 프로젝트를 시작하려면, .NET SDK는 Visual Basic을 박스 안에 함께 담고 있습니다:
dotnet new console -lang VB -o Packer
cd Packer
dotnet run
첫 인코딩: 바이트 들어가고, 텍스트 나간다
Visual Basic에서의 인코딩 생활 90퍼센트는 호출 두 개이며, 순서가 중요합니다: 인코더는 텍스트가 아니라 바이트를 받으므로, 문자열에서 시작한다면 먼저 그것을 바이트로 바꿀 인코딩을 고르고, 그때서야 Base64 단계를 밟습니다. 전체 댄스는 이렇습니다:
Imports System
Imports System.Text
Module Encoder
Sub Main()
Dim text As String = "Man"
Dim bytes() As Byte = Encoding.UTF8.GetBytes(text)
Dim packed As String = System.Convert.ToBase64String(bytes)
Console.WriteLine(packed)
' TWFu
End Sub
End Module
그 가운데 3줄이 전체 기술이며, 문자열 "TWFu"은 당신이 쓰는 어떤 인코더든 위한 완벽한 스모크 테스트입니다. 오버로드는 필요해 졌을 때 당신에게 제어력을 줍니다. 부분집합 형태는 먼저 밖으로 복사하지 않고 배열의 한 조각을 인코딩하므로, 진짜 페이로드가 더 큰 버퍼 안에 살고 있을 때 유용합니다:
Imports System
Module SlicePacker
Sub Main()
Dim data() As Byte = {1, 2, 3, 4, 5, 6, 7, 8}
Dim packed As String = System.Convert.ToBase64String(data, 2, 4)
Console.WriteLine(packed)
' AwQFBg== (3부터 6의 바이트만 인코딩됨)
End Sub
End Module
그리고 배열-문자 형태 ToBase64CharArray는 당신이 할당하는 문자 버퍼에 쓰고 몇 개의 문자를 채웠는지 알려 주는데, 도착지가 손으로 만들어 가는 더 큰 텍스트 구조의 일부라면 바로 그 도구입니다. Visual Basic 문법의 집안 규칙에 주목하세요: 바이트 배열은 빈 괄호를 달아 Byte()로 씁니다. 괄호를 빼면 바이트 하나가 되고, Option Strict On(템플릿에서는 기본으로 꺼져 있음 - 모든 프로젝트에서 켜 두면 좋습니다)가 컴파일 시 그 혼동을 잡아 줍니다.
출력 성형하기: 패딩, 줄바꿈, 정확한 크기
같은 바이트를 두 번 인코딩해도 정당한 이유로 서로 다른 문자열 둘이 나올 수 있고, 그 차이는 전부 출력 성형에 달려 있습니다. 첫째, 패딩: 입력 길이가 3의 배수가 아니면, 인코더가 마지막 그룹을 = 한두 개로 채웁니다. RFC는 당신이 따르는 규격이 달리 정하지 않는 한 포함하라고 하며, ToBase64String은 기본으로 포함합니다. 둘째, 줄바꿈: 포맷 오버로드의 두 번째 파라미터 Base64FormattingOptions.InsertLineBreaks는 인코더가 CRLF로 구분된 76자 줄을 출력하게 만들며, 이것이 바로 MIME 규칙입니다. MIME 자체는 76자 한계를 쓰고(오래된 PEM의 64자 줄의 가까운 친척), 두 한계 모두 SMTP 안의 제한에서 유래합니다. 받는 쪽이 이메일 파이프라인이라면 줄바꿈을 켜세요. URL, JSON 필드, 데이터베이스 컬럼이라면 꺼 두세요. 당신의 데이터 안에 숨은 보이지 않는 CRLF가 나중에 반드시 당신을 놀라게 할 길을 찾아내기 때문입니다:
Imports System
Module MimePacker
Sub Main()
Dim data(113) As Byte
For i As Integer = 0 To 113
data(i) = CByte(i)
Next
Dim wrapped As String = System.Convert.ToBase64String(data, Base64FormattingOptions.InsertLineBreaks)
Console.WriteLine(wrapped.Length)
' 154: 76자 두 줄과 그 사이 CRLF 하나
End Sub
End Module
셋째, 정확한 크기: 버퍼와 컬럼 너비를 미리 할당하고 싶을 테니까요. 규칙은 입력 바이트 3개마다 문자 4개, 반올림 올려서입니다: 1000바이트는 1336문자가 됩니다. 손으로 그 산수를 할 것 없이, 런타임에는 주어진 입력 크기에 대한 인코딩된 최대 길이를 돌려 주는 헬퍼가 있고, 그것이 바로 버퍼 할당에 넘길 값입니다:
Imports System.Buffers.Text
Module Sizing
Sub Main()
Dim dataLength As Integer = 1000
Dim textNeeded As Integer = Base64.GetMaxEncodedToUtf8Length(dataLength)
Console.WriteLine(textNeeded)
' 1336
End Sub
End Module
그 4대 3 비율은 크기 세금의 가장 순도 높은 형태입니다: 모든 인코딩된 값은 싣고 있는 바이트보다 약 33퍼센트 더 크므로, 저장과 전송 크기를 계획할 때 그 여유분을 염두에 두세요. 그리고 물리기 전에 알아 둘 가치가 있는 행동 하나: 문자열을 디코딩한 뒤 결과를 다시 인코딩하면, 새 문자열이 원래 것과 일치한다는 보장은 없습니다. 공백은 사라지고, 패딩은 정규화되기 때문이죠. 값을 비교해야 할 때는 디코딩된 바이트를 비교하세요. 인코딩된 텍스트를 비교하는 것이 아닙니다.
인코딩 전에 문자 집합 고르기
텍스트 인코딩의 첫 단계가 "문자열에서 바이트로"이므로, 당신이 고르는 문자 집합이 수신자가 디코딩할 때 무엇을 보게 될지를 결정합니다. Visual Basic 문자열은 런타임 안에서 UTF-16이지만, 당신이 뿜어 내는 바이트는 다른 쪽이 읽을 것을 기대하는 것과 맞춰야 하며, 선택지는 짧습니다:
Encoding.UTF8: 웹, API, 모든 현대적인 것에 맞는 기본값. 이 언어가 담을 수 있는 모든 유니코드 문자를 왕복합니다.Encoding.Unicode: UTF-16 리틀 엔디안. 파이프의 양 끝이 UTF-16을 명시적으로 합의한 .NET 프로그램이라면 합리적인 선택이고, 그 이상은 아닙니다.Encoding.ASCII: 7비트만, 그 외는 조용히 물음표로 바꿉니다. "Café"를 ASCII로 인코딩하면 "Caf?"의 바이트가 나와, 되돌려 디코딩하면 물음표를 포함해 정확히 그것이 나옵니다.Encoding.Default: .NET Framework에서는 머신의 ANSI 코드 페이지였지만, .NET (Core)에서는 로케일에 관계없이 항상 UTF-8입니다. 주고받는 데이터에는 여전히 피하세요 - 인코딩을 명시적으로 적고, 보통 UTF-8입니다.
Imports System
Imports System.Text
Module CharsetPacker
Sub Main()
Dim text As String = "Café"
Dim utf8() As Byte = Encoding.UTF8.GetBytes(text)
Dim ascii() As Byte = Encoding.ASCII.GetBytes(text)
Console.WriteLine(System.Convert.ToBase64String(utf8))
' Q2Fmw6k=
Console.WriteLine(System.Convert.ToBase64String(ascii))
' Q2FmPw== (발음 기호가 물음표가 됨)
End Sub
End Module
서로 다른 Base64 문자열 둘, 단어 하나, 그리고 그중 도착하는 것은 하나뿐입니다. 실무 규칙: 당신이 따르는 프로토콜이 다른 방식을 이름 붙이지 않는 한, 텍스트는 UTF-8로 인코딩하고 그렇게 말해 두세요.
Base64Url: URL을 살아남는 알파벳
표준 알파벳에는 +와 /가 들어 있고, 이 두 문자는 URL 안에서 각자의 역할을 갖고 있으므로, 그 알파벳 위에 만들어진 Base64는 쿼리 문자열이나 경로 세그먼트에 닿는 순간 부러집니다. RFC 4648 5절에서 표준화된 해결책은 문제 문자 둘을 URL 안전한 -와 _로 교체하는 것이고, 데이터 길이가 이미 디코더에게 데이터 끝을 알려 주므로 꼬리 패딩은 보통 생략됩니다. base64url이라 알려진 이 변형은 JWT, API 토큰, 그리고 점점 늘어나는 API들의 알파벳입니다. .NET 9는 이를 위한 전용 클래스를 추가했고, 알아 둘 가치가 있는 하나의 견해를 갖고 만들어졌습니다: 의도적으로 패딩을 생략한다는 것:
Imports System.Buffers.Text
Module UrlSafePacker
Sub Main()
Dim data() As Byte = {219, 255, 0, 63, 16}
Dim packed As String = Base64Url.EncodeToString(data)
Console.WriteLine(packed)
' 2_8APxA (언더스코어, 뒤에 패딩 없음)
End Sub
End Module
위 예제는 좋은 선택입니다: 고른 바이트들이 특수 문자 중 하나를 나타나게 하거든요 - 표준 알파벳에 슬래시가 있는 자리에 언더스코어가 - 그래서 그 교체를 눈으로 볼 수 있습니다. 오래된 런타임에 있다면, 같은 알파벳은 문자 두 개 치환과 자르기에 불과하며, 그대로 교체 가능한 결과를 얻습니다:
Imports System
Module CompatPacker
Function ToUrlSafe(ByVal packed As String) As String
Return packed.Replace("+"c, "-"c).Replace("/"c, "_"c).TrimEnd("="c)
End Function
End Module
.NET Framework 4.6.2 이상에서는 Microsoft.Bcl.Memory 패키지가 대신 진짜 Base64Url 클래스를 줍니다. 어느 쪽이든 패딩의 경계에 주의하세요: .NET의 Base64Url 출력에는 패딩이 없지만, 다른 생태계의 몇몇 라이브러리는 패딩을 추가합니다(엄격한 디코더 몇몇은 그것을 요구하죠). 예를 들어 JWT는 패딩 없는 형태를 요구하므로, .NET 기본값은 바로 거기서 정확합니다. 생태계 경계를 건널 때는, 문자열을 보내기 전에 다른 쪽의 기대를 확인하세요.
파일 포장하기
파일이 원본 사용 사례입니다: 바이너리 파일을 이메일, FTP, 설정 시스템이 모두 기꺼이 실어 나를 텍스트 파일로 바꾸는 것. Visual Basic에서는 전체 작업이 호출 세 개이며, 그중 하나는 읽고 하나는 씁니다:
Imports System.IO
Module FilePacker
Sub Main()
Dim bytes() As Byte = File.ReadAllBytes("photo.png")
Dim packed As String = System.Convert.ToBase64String(bytes)
File.WriteAllText("photo.b64", packed)
End Sub
End Module
크기 비율을 주머니에 넣어 두세요: 10메가바이트 사진은 약 13.4메가바이트의 텍스트 파일이 됩니다. 이 작업은 현대 하드웨어에서 빠릅니다(아래에서 더 다룹니다). 그래서 비용은 거의 언제나 CPU가 아니라 저장과 대역폭이며, 33퍼센트 세금을 쓰다 보면 익숙한 청구서죠. 파일이 자신을 참조하는 텍스트 옆에만 머물 거라면 이 패턴은 완전히 괜찮습니다. 파일이 크고 오래 살 거라면, 그 채널이 진짜로 텍스트 형태를 필요로 하는지 물어 보세요.
이미지: 손으로 데이터 URI 만들기
데이터 URI 스킴(RFC 2397)은 파일 내용을 URL에 직접 임베드합니다: data:, 미디어 타입, 리터럴 마커 ;base64, 쉼표, 인코딩된 바이트. 브라우저는 작은 이미지와 폰트를 인라인으로 넣는 데 씁니다. WPF는 데이터 URI를 직접 소비할 수 없습니다 - data: 스킴을 위한 핸들러가 BitmapImage에 없으므로 - 그래서 관용적인 수법은 접두어를 벗기고 바이트를 MemoryStream에 건네는 것입니다. Visual Basic에서 URI를 만드는 것은 문자열 연결 한 번이고, 소비하는 것은 작은 초기화 블록입니다:
Imports System.IO
Imports System.Windows.Media.Imaging
Module DataUriPacker
Sub Main()
Dim bytes() As Byte = File.ReadAllBytes("logo.png")
Dim dataUri As String = "data:image/png;base64," & System.Convert.ToBase64String(bytes)
Dim image As New BitmapImage()
image.BeginInit()
image.StreamSource = New MemoryStream(System.Convert.FromBase64String(dataUri.Substring(dataUri.IndexOf(","c) + 1)))
image.EndInit()
' 이제 image를 Image 컨트롤에 대입할 수 있다
End Sub
End Module
RFC 자체가 데이터 URI는 짧은 값에만 유용하다고 경고하고, HTML 문서는 자기만의 속성 길이 한계를 두므로, 합리적인 범위는 아이콘, 아바타, 섬네일, 작은 배경 패턴입니다. 미디어 타입은 실제로 인코딩한 바이트와 맞춰야 합니다. 하류에서 그것을 콘텐츠로부터 다시 유추하는 것이 없기 때문이죠.
HTTP: 인증 헤더와 JSON 페이로드
와이어 위에서 당신이 손으로 인코딩하는 두 곳은 HTTP Basic 인증 헤더와 바이너리나 미리 인코딩된 데이터를 싣는 JSON 필드입니다. Basic 인증이 가장 눈에 띄죠: 헤더는 Basic이라는 단어, 공백, 그리고 콜론으로 붙인 username:password의 Base64입니다. 만드는 것은 인코딩 호출 한 번입니다:
Imports System
Imports System.Text
Module AuthPacker
Function MakeBasicHeader(ByVal user As String, ByVal password As String) As String
Dim raw() As Byte = Encoding.UTF8.GetBytes(user & ":" & password)
Return "Basic " & System.Convert.ToBase64String(raw)
End Function
End Module
JSON 경우도 마찬가지로 일상이죠. API가 요청 본문 안에 이미지나 인증서를 원한다면, 바이트를 인코딩해 그 문자열을 페이로드에 넣으면 되고, System.Text.Json(.NET Core 3.0부터 박스에 들어 있음)가 주변의 직렬화를 처리해 줍니다:
Imports System.Text.Json
Module ApiPacker
Function WidgetPayload(ByVal name As String, ByVal imageBytes() As Byte) As String
Dim payload = New With {
.name = name,
.image = System.Convert.ToBase64String(imageBytes)
}
Return JsonSerializer.Serialize(payload)
End Function
End Module
집안 규칙 둘: 자격 증명은 HTTPS로만 보내세요 - 평문 HTTP 위에서는 Base64가 자물쇠가 아니라 가발이니까요 - 그리고 원본 인증 헤더와 그것이 디코딩되는 자격 증명을 절대 로그에 남기지 마세요.
이메일 첨부 파일과 MIME 줄바꿈 포장
이메일은 Base64가 생계를 이룬 곳입니다. SMTP는 7비트 ASCII로 지어졌으므로, 바이너리 첨부 파일은 날아가기 전에 텍스트가 되어야 하고, MIME 표준(RFC 2045)은 선택을 내렸습니다: Base64, 줄당 76자로 줄바꿈, Content-Transfer-Encoding: base64 헤더로 선언. System.Net.Mail 클래스로 일한다면, 전체 의식이 설정 두 줄에 불과합니다. 메일 라이브러리가 전송 시점에 줄바꿈 포장을 대신해 주니까요:
Imports System.IO
Imports System.Net.Mail
Imports System.Net.Mime
Module MailPacker
Sub Main()
Using message As New MailMessage("me@example.com", "you@example.com")
message.Subject = "Quarterly report"
message.Body = "Please find the report attached."
Using stream As New FileStream("report.bin", FileMode.Open, FileAccess.Read)
Dim attachment As New Attachment(stream, "report.bin")
attachment.TransferEncoding = TransferEncoding.Base64
message.Attachments.Add(attachment)
End Using
End Using
End Sub
End Module
당신이 Base64FormattingOptions.InsertLineBreaks로 줄바꿈된 형태를 직접 만들어야 하는 경우는, 원본 MIME 텍스트를 손으로 쓸 때뿐입니다: 메일러 테스트 픽스처, 레거시 게이트웨이, .eml 파일을 뱉어 내는 도구 같은 것이죠. 76자 규칙은 스타일 선호가 아닙니다. 몇몇 수신 시스템은 더 긴 줄을 잘라 버리기에, 그 한계가 표준에서 몇십 년째 살아남은 것이죠.
인코딩된 값 저장하기: 데이터베이스, 설정 파일, 환경 변수
텍스트 전용 저장은 계속 Base64를 요구합니다: 텍스트 타입의 데이터베이스 컬럼, XML 설정 값, 환경 변수. 바이트를 인코딩하고, 문자열을 저장하고, 나갈 때 디코딩합니다. 인코딩 쪽은 언제나 같은 원라인러지만, 저장 쪽에는 크기 세금을 구체적으로 만드는 한계들이 있습니다. SQL Server의 일반 VARCHAR 컬럼은 8,000자에서 멈추고(NVARCHAR 컬럼은 각 유니코드 문자가 두 바이트를 쓰므로 그 절반인 4,000자에서 멈춥니다) - 8,000자는 33퍼센트 오버헤드가 한계를 넘게 하기 전까지 바이너리 약 6,000바이트를 담을 공간 - 그 이상에서는 MAX 타입을 찾거나, 더 솔직하게 말하면 진짜 바이너리 컬럼을 찾게 되죠. Windows에서는 사용자 정의 환경 변수 하나당 32,767자 한계가 있고(XP 시대 시스템에서는 환경 블록 전체도 그 크기로 제한되어 있었으며), "라이선스 덩어리 전체를 환경 변수에 보관하자"는 계획에는 단단한 상한선이 있습니다:
Imports System
Module EnvPacker
Sub Main()
Dim blob() As Byte = {1, 2, 3, 4, 5}
Dim packed As String = System.Convert.ToBase64String(blob)
Environment.SetEnvironmentVariable("APP_BLOB", packed)
Console.WriteLine(packed)
' AQIDBAU=
End Sub
End Module
설정 파일도 같은 모양을 따르며, 값은 XML 또는 JSON 텍스트 안에 살고, 디코딩은 당신의 시작 코드에서 일어납니다. 엔터프라이즈 구석에 대한 레거시 노트 하나: WCF와 XML 데이터 컨트랙트는 바이트 배열을 base64Binary XML 스키마 타입으로 직렬화하므로, 오래된 .NET 서비스의 상당수가 정확히 이 방식으로 바이너리를 저장하고 있고, 그 XML에서 찾을 값은 단순한 ToBase64String 출력입니다.
JWT: 컴팩트 형태 만들기
컴팩트 형태의 JSON Web Token은 점으로 나뉜 base64url 조각 세 개입니다: 헤더, 페이로드, 서명. 첫 둘은 평문 JSON이고, 세 번째는 올바른 키의 소지자가 이 토큰을 만들었다는 암호학적 증명입니다. 서명이 없는 형태를 손으로 만드는 것은 인코딩 두 번과 문자열 연결에 불과하지만, 진짜 JWT에는 서명 단계가 필요하고, 작은 HMAC-SHA256 예제가 전체를 구체적으로 만들어 줍니다:
Imports System
Imports System.Buffers.Text
Imports System.Security.Cryptography
Imports System.Text
Module JwtPacker
Function BuildHs256Jwt(ByVal headerJson As String, ByVal payloadJson As String, ByVal secret() As Byte) As String
Dim header As String = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(headerJson))
Dim body As String = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(payloadJson))
Dim signingInput As String = header & "." & body
Using hmac As New HMACSHA256(secret)
Dim signature() As Byte = hmac.ComputeHash(Encoding.UTF8.GetBytes(signingInput))
Return signingInput & "." & Base64Url.EncodeToString(signature)
End Using
End Function
End Module
그 헤더 {"alg":"HS256","typ":"JWT"}와 당신이 고른 페이로드로 돌려 보면, 결과는 진짜 컴팩트 JWT입니다: 어디에도 패딩이 없고, 세 부분 모두 URL 안전 문자. 서명도 base64url이라는 것에 주목하세요. 토큰 전체가 URL이나 HTTP 헤더를 살아남아야 하기 때문이죠. 프로덕션 시스템에서는 System.IdentityModel.Tokens.Jwt 패키지(Microsoft Entra 팀의 IdentityModel 스위트)가 이 토큰들을 만들어 서명하고 검증해 줍니다. 키 관리, 알고리즘 고정, 만료 검사가 속하는 바로 그 층이죠. 인코딩을 손으로 만드는 것은 이해를 위해, 작은 도구를 위해 괜찮습니다. 접근을 지킨다는 것이 무엇이든, 라이브러리가 그 무게를 지도록 하세요.
함정들: VB 인코더가 미끄러지는 곳
여기 함정들은 언어 습관과 출력 성형의 놀라움의 혼합이며, 대부분은 크래시가 아니라 디버깅 세션 하나를 대가로 합니다:
- Byte와 Byte(). 인코더는 배열을 원합니다. Visual Basic에서 바이트 하나는
Byte이고, 배열은Byte()이며, 차이는 괄호 한 쌍입니다.Option Strict On아래에서는 잘못된 추측이 컴파일 오류가 되고, 꺼져 있으면 대신 런타임의 놀라움을 받을 수 있습니다. 엄격성은 켜 두고, 괄호는 보이게 하세요. - Encoding.Default 함정, 반대로. 로케일 분기 이야기는 .NET Framework의 것입니다: 최신 .NET에서는
Default가 항상 UTF-8이라, 같은 문자열이 모든 머신에서 같은 방식으로 인코딩됩니다. 머신을 건너는 것에는 인코딩을 명시적으로 적고, 보통 UTF-8 - 어떤 경우든 그 조언은 유효합니다. - CRLF는 틈을 타 들어옵니다.
InsertLineBreaks는 MIME에는 훌륭하고, URL, JSON, 데이터베이스 텍스트 컬럼에는 악몽입니다. 거기에서는 아무도 요청하지 않은 캐리지 리턴과 라인 피드를 삽입하니까요. 받는 쪽이 줄바꿈된 줄을 기대할 때만 쓰세요, 그리고 망설여진다면 기본값None을 쓰세요. - 경계에서의 패딩 불일치. .NET의
Base64Url은 패딩을 출력하지 않지만, 다른 생태계의 몇몇 라이브러리는 패딩을 추가합니다(엄격한 디코더 몇몇은 그것을 요구하죠). 인코딩된 값이 생태계를 건널 때는, 문자열을 보내기 전에 다른 쪽의 기대를 확인하세요. JWT는 패딩 없는 형태를 원하고, 그것이 바로 .NET 기본값입니다. - 왕복은 동일성이 아닙니다. 줄바꿈되고 패딩된 문자열을 디코딩한 뒤 다시 인코딩하면, 원래 텍스트가 아니라 새 패딩 달린 깨끗한 한 줄이 나옵니다. 로직이 인코딩된 값의 동등을 비교한다면, 대신 디코딩된 바이트를 비교하세요.
- 크기 상한은 실제로 있습니다. 출력 길이 공식, 3바이트당 4문자 반올림 올림은, 입력 약 1.5기가바이트에서 32비트 카운트를 넘어서고, 인코더는 부분 문자열이 아니라
OutOfMemoryException으로 답합니다. 그 규모 근처의 입력이라면, 대신 스트리밍하세요(아래에서 다룹니다). - 스판 벽. 스판 기반 인코더는 호출 위치에서 VB에서 호출할 수 있습니다:
Byte()나Char()배열을 그대로 넘기면 컴파일러가 변환해 줍니다. 하지만Span또는ReadOnlySpan타입의 변수, 필드, 파라미터를 선언할 수는 없습니다. 컴파일러가 "Types with embedded references are not supported in this version of your compiler"로 거부하니까요. VB 관용 표현은 스판 API를 평범한 배열로 호출하고, 스판을 저장하지 않는 것입니다. - BitConverter는 Base64가 아닙니다.
BitConverter.ToString(bytes)는 페어 사이에 하이픈을 둔 16진수로 렌더링하므로, 유혹하는 잘못된 답입니다. 그것이 만드는 것은4D-61-6E인데, 다른 시스템이 기대하는 것은TWFu입니다. Base64를 위한 클래스는 매번System.Convert입니다.
속도와 크기: 성능 노트
Base64의 "느린 텍스트 코덱" 평판은 현대 런타임과 접촉하면 살아남지 못합니다. .NET 안의 인코더는 머신이 지원할 때 하드웨어 벡터화 코드를 실행하며, AVX-512, AVX2, SSE 명령어 집합을 위한 전용 고속 경로를 갖고, AVX-512 경로는 한 단계마다 48바이트를 씹어 넘깁니다. 보통의 페이로드에서는, 클래식 ToBase64String 호출은 알고리즘이 병목이 될 일이 드물 만큼 빠릅니다. 당신이 느끼는 비용은 33퍼센트 크기 세금과, 핫 패스에서는 중간 할당들이죠. 수백만 개의 작은 값을 인코딩한다면, 스판 기반 API가 그 정교함입니다: TryToBase64Chars는 당신이 제어하는 문자 스판에 쓰며 부울 값으로 성공을 보고하고, System.Buffers.Text.Base64는 더 나아가 당신이 할당하는 UTF-8 버퍼에 직접 인코딩하며, 심지어 데이터를 인플레이스 팽창까지 해 줍니다:
Imports System
Imports System.Buffers
Imports System.Buffers.Text
Imports System.Text
Module BufferPacker
Sub Main()
Dim data() As Byte = {1, 2, 3, 4, 5}
Dim textLength As Integer = Base64.GetMaxEncodedToUtf8Length(data.Length)
Dim buffer(textLength) As Byte
Dim written As Integer
Dim consumed As Integer
Dim status As OperationStatus = Base64.EncodeToUtf8(data, buffer, consumed, written)
Dim packed As String = Encoding.ASCII.GetString(buffer, 0, written)
Console.WriteLine(packed)
' AQIDBAU=
End Sub
End Module
주목할 패턴은 이것입니다: 헬퍼로 버퍼 크기를 잡고, 그 안으로 인코딩하고, 쓰인 접두어만 문자열로 바꾼다는 것. 중간 표면을 가능한 한 작게 유지하죠. 그리고 문자열이 불편할 만큼 큰 파일에는, 스트리밍 쌍이 메모리를 평평하게 유지합니다: ToBase64Transform 변환이 CryptoStream에 감겨 당신의 입력을 청크 단위로 읽고 인코딩된 텍스트를 써 내므로, 2기가바이트 파일이 통째로 2.7기가바이트 문자열이 될 일은 결코 없습니다:
Imports System.IO
Imports System.Security.Cryptography
Module StreamPacker
Sub EncodeFile(ByVal inputPath As String, ByVal packedPath As String)
Using inputStream As New FileStream(inputPath, FileMode.Open, FileAccess.Read)
Using packedStream As New CryptoStream(New FileStream(packedPath, FileMode.Create), New ToBase64Transform(), CryptoStreamMode.Write)
Dim buffer(65535) As Byte
While True
Dim read As Integer = inputStream.Read(buffer, 0, buffer.Length)
If read = 0 Then Exit While
packedStream.Write(buffer, 0, read)
End While
End Using
End Using
End Sub
End Module
앞으로의 노트 하나: 프리뷰 .NET 11 라이브러리는 기존 Base64 타입에 새 편의 메서드와 스판 오버로드를 추가합니다. 그래서 프로젝트가 프리뷰를 따를 수 있다면, 도구함은 계속 커집니다. 그럴 수 없다면, 위 모든 것은 모든 지원 릴리스에서 안정적입니다.
짧은 역사: MSXML에서 스판까지
.NET 훨씬 전에, Base64가 필요한 Visual Basic 프로그램은 COM 세계에서 빌렸습니다. 클래식 VB6와 VBA 트릭(오늘날에도 Excel과 Office 안에서 돌아가는 매크로 언어)은 대신 XML DOM 요소를 썼습니다: MSXML 파서는 노드가 DataType을 bin.base64로 선언하게 해 주므로, 노드의 nodeTypedValue에 바이트를 쓰고 text 프로퍼티를 다시 읽으면 인코딩된 문자열이 손에 오고, 실제 Base64 수학은 DOM이 해 줍니다(ADO Stream 객체 자신의 Charset 프로퍼티는 "utf-8"이나 "iso-8859-1" 같은 진짜 문자 집합 이름만 이해하고 "base64"는 이해하지 못하므로, 변환 자체에는 아무 역할도 하지 못하죠). 영리했고, 어디에나 있었고, 수십 년이 지났는데도 "base64 VBA"가 여전히 검색 엔진을 밝히는 이유입니다. 그 시대는 2002년에 끝났습니다: 이 언어의 첫 .NET 버전인 Visual Basic 7.0이 새로운 Common Language Runtime에 합류했고, .NET Framework가 System.Convert와 ToBase64String을 박스에 함께 들여 놓았죠. 2003년 .NET Framework 1.1부터, 모든 VB 프로그램은 등록할 구성 요소 없이 호출 한 번으로 Base64를 인코딩할 수 있었습니다.
현대 장들은 짧습니다. 2018년, .NET Core 2.1은 할당 부담이 적은 TryToBase64Chars 메서드와 로우레벨 스판 기반 System.Buffers.Text.Base64 클래스를 추가했습니다. 2024년, .NET 9는 URL 안전 알파벳을 Base64Url로 표준화하며, 손수 굴린 Replace 호출의 10년대를 끝냈습니다. 2026년 현재, 2025년 11월에 릴리스된 .NET 10이 이 모든 것을 싣는 장기 지원 릴리스이며, 프리뷰 .NET 11 라이브러리는 새로운 세대의 편의 메서드를 추가하고 있습니다. 2003년 원라인러에서 스판 시대까지의 인코더 이야기는, 처음부터 다시 시작한 이야기가 아니라 같은 클래스가 점점 더 빠르고 정확해진 이야기입니다.
재미있는 사실들, VB판
InsertLineBreaks는 MIME 76자 규칙을 정확히 재현하며, CRLF도 포함합니다. 즉, 2026년에 당신의 인코더가 쓰는 줄바꿈은 1990년에 이메일 표준이 정의한 것과는 바이트 하나하나까지 같은 모양이라는 뜻입니다.- Visual Basic 2005와 함께 추가된
IsNot연산자는, 한때 Microsoft 특허 출원의 주제가 되어 뉴스가 된 적이 있습니다. 그 구분을 주장할 수 있는 언어 연산자는 매우 드뭅니다. - 첫 Visual Basic은 월드 와이드 웹이 존재하기 전인 1991년에 출시되었습니다. 데이터 URI 스킴이 1998년에 모습을 보였을 때, Base64는 이미 5년째 이메일 첨부 파일을 실어 나르고 있었고, VB는 그보다 3년 앞서 1995년 Visual Basic 4에서 32비트 언어로 성장해 있었습니다.
- AVX-512를 갖춘 하드웨어에서, 런타임 인코더는 벡터 단계마다 48바이트를 처리합니다. 박물관의 테이블 룩업과 공장의 컨베이어 벨트의 차이죠.
- 2005년 Visual Basic의 유명한 구문 설탕 레이어인
My네임스페이스는, Base64 헬퍼를 추가할 필요가 한 번도 없었습니다.System.Convert는 언제나 Imports 한 줄 거리였고, 프레임워크가 이미 말해 준 이야기에 VB 런타임이 아무것도 더하지 않은 드문 경우입니다.
반대편 이야기
이 글은 Visual Basic에서 Base64의 인코딩 쪽을 다뤘습니다: 도구함, 출력 성형의 결정들, URL 안전 알파벳, 그리고 파일부터 JWT까지의 사용 사례. 반대 방향, 들어오는 문자열을 그것이 숨기고 있는 바이트로 되돌리는 쪽은 자기만의 행동, 관대함 규칙, 함정들을 갖고 있으며, 자매 사이트의 동반 디코딩 글에서 자세히 다룹니다. 그 링크는 바로 아래 줄에 있고, 홈 페이지의 도구는 여전히 작은 페이로드를 손으로 인코딩하는 가장 빠른 방법입니다.
마지막 업데이트: 2026-09-08