Visual Basic 中的 Base64 解码:完整指南
设想一下这个场景:一条字符串落进了你的 Visual Basic 项目。它看起来像一副洗乱的字母和数字纸牌,中间偶尔混进一个加号、斜杠或等号,而发它给你的人信誓旦旦地说,它曾经是一句再普通不过的话、一张 JPEG,或是一团配置数据。这条字符串就是 Base64,而这一页是你把它变回原样的野外指南。先说好消息:Visual Basic 从最早的 .NET Framework 时代起就拥有一等公民级的 Base64 解码器,它就装在运行时里,你不需要安装任何包就能使用它。
先来快速温习一下,因为本站首页已经把这种格式完整讲过:Base64 把三个字节写成从 64 符号字母表中挑出的四个字符,末尾一个或两个 = 字符标记着真实数据在哪里结束。所以编码后的文本比原文有点膨胀:每三个输入字节换来四个字符,大约多出三分之一。解码就是把这笔交换反过来做。带着对问题形状的了解,让我们拆开几个信封吧。
解码器家族:一个运行时,四个时代
解码需要的一切都住在 .NET 运行时里。过去二十年它经历了四波生长,而早先几波至今工作得和当年一模一样,所以你在真实项目里会看到它们全部:
| API | 可用版本 | 适用场景 |
|---|---|---|
System.Convert.FromBase64String |
.NET Framework 1.1(2003) | 经典款。进一根字符串,出一块全新的 Byte() 数组。输入不合法就抛异常。 |
System.Convert.FromBase64CharArray |
.NET Framework 1.1(2003) | 同样的解码,只是从你已持有的字符数组里的一段来读。 |
System.Convert.TryFromBase64String、TryFromBase64Chars |
.NET Core 2.1(2018) | 用布尔值代替异常,写入你提供的缓冲区。不可信输入的好帮手。 |
System.Buffers.Text.Base64 |
.NET Core 2.1(2018) | 底层、基于 span 的解码:状态码代替异常、原地解压,还有 IsValid 预检。 |
System.Buffers.Text.Base64Url |
.NET 9(2024) | URL 安全字母表(用 - 和 _ 代替 + 和 /),填充可带可不带。在更老的运行时上,它搭载在 Microsoft.Bcl.Memory NuGet 包里。 |
FromBase64Transform + CryptoStream |
.NET Framework 1.1(2003) | 流式解码:文件到文件、网络到磁盘,一块一块地处理,无需把整个负载装进内存。 |
在我们继续之前,先做一个 Visual Basic 专属的澄清。在 VB 项目里,光秃秃的 Convert 这个名字会解析到 System.Convert,因为标准项目模板已经替你导入了 System 命名空间,而且 Visual Basic 运行时里没有任何东西会遮蔽这个名字。尽管如此,本文大部分时候仍然写完整的 System.Convert 形式:它不花什么代价,却让任何读代码的人都不会误会意图。
说说版本:.NET 10 是当前长期支持版本(2025 年 11 月发布,支持到 2028 年 11 月),.NET 8 和 .NET 9 也都支持到 2026 年 11 月,而 .NET 11 正处于预览阶段,会带来一批新的 Base64 便捷方法。解码 API 在所有这些版本上都很稳定。唯一的版本门槛是 Base64Url:从 .NET 9 起它内置于运行时,在 .NET Framework 4.6.2 或更新的版本上,你可以用 Microsoft.Bcl.Memory 包把它拉进来。本文其他任何地方都不需要装包。
如果你是从零开始搭建,.NET SDK 开箱自带 Visual Basic,所以整个仪式就是这几行:
dotnet new console -lang VB -o EnvelopeOpener
cd EnvelopeOpener
dotnet run
这会给你一个小小的 Program.vb,开头有 Imports System,然后你就可以开始解码了。
一行式:FromBase64String
Visual Basic 解码生涯的百分之九十,就是这一个调用。给它一根字符串,它还给你当初打包进去的确切字节:
Imports System
Imports System.Text
Module EnvelopeOpener
Sub Main()
Dim packed As String = "TWFu"
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
Dim text As String = Encoding.UTF8.GetString(bytes)
Console.WriteLine(text)
' Man
End Sub
End Module
有三件事值得记进脑子里。第一,结果是字节,不是文本:解码器从头到尾都是面向字节的,而这恰恰是你想要的,因为负载可能是一句话、一张 JPEG、一个证书或一个哈希,它们中的任何一位都不该被特殊对待。把这些字节变成可读字符串是一个独立的、刻意分开的步骤,要经过一个 Encoding 对象,而你的字符集决定就住在这一步里(下文细说)。第二,FromBase64String 每次都会分配一块全新的数组,大小恰好等于解码后的长度,所以你永远不会随身携带多余的容量。第三,"TWFu" 解码为单词 "Man",三个字节,毫无意外,这让它成为你写的任何解码代码的完美冒烟测试。
它原谅什么,又拒绝什么
在这里能看到 .NET 解码器的性格,而且是个很有个性的性格:它对恰好一件事大方,对其他一切毫不留情。它大方的对象是空白。解码器无论空白出现在哪里,都会跳过恰好四个字符:空格(U+0020)、制表符(U+0009)、换行符(U+000A)和回车符(U+000D)。这项政策是对邮件的刻意致意,因为 Base64 负载在邮件里总是裹在短行里到达,这也意味着一个 MIME 包裹的附件可以零预处理直接解码。有个好玩的事实:这种宽容是内置家族共享的,包括基于 span 的 System.Buffers.Text.Base64 和 URL 安全的 Base64Url 类,所以无论你伸手去拿哪个 API,都会得到同样的宽容行为。任何落在 64 符号字母表之外的东西、任何违反长度规则的东西、任何放错位置的填充,都会换来一个异常。看看同一个解码器面对几种不同输入时的表现:
| 输入 | 结果 |
|---|---|
"TWFu" |
解码为 Man(3 个字节)。 |
"TWF" + CRLF + "u" |
解码为 Man。中间的换行对解码器是隐形的。 |
"TWFu" + 不间断空格 |
FormatException。只有上面那四个空白字符会被跳过,不间断空格不在其中。 |
"TWE" |
FormatException。忽略空白后,长度必须是 4 的倍数。 |
"TWFu=" |
FormatException。数据结束后不允许还有填充。 |
"====" |
FormatException。超过两个填充字符是非法的。 |
"" 或纯空白 |
一个空字节数组。安静而合法的成功。 |
"TW=u" |
FormatException。中间的填充是非法的。 |
所以这份诚实的契约又小又好记:Nothing 引用抛出 ArgumentNullException,空输入或纯空白输入解码为空数组,合法输入解码为字节,其余一切非法输入都抛出同一个非常具体的异常,FormatException。
从字节到文字:选择字符集
一旦你认定解码出的字节其实是文本,你就必须说出一个字符集,因为字节在你说明怎么读它们之前都还不是文本。Visual Basic 字符串内部是 UTF-16,但从解码器里出来的字节是别人制造的,多半用的是另一套方案,所以你得对上他们的选择。实践菜单如下:
Encoding.UTF8:任何经过网络或 API 旅行的东西的安全默认值。拿不准时,从这里开始。Encoding.Unicode:UTF-16 小端,.NET 的原生风味。当交换双方都是明确选择了 UTF-16 的 .NET 程序时,它很合理。Encoding.ASCII:只有 7 位。非 ASCII 字节会被替换成问号,所以这是一个有损的选择,它会悄悄毁掉重音符号。Encoding.Default:在 .NET Framework 上它是机器的 ANSI 代码页,但在 .NET(Core)上它永远是 UTF-8,与区域设置无关。对你交换的数据仍然要避开它 - 显式说出编码,通常是Encoding.UTF8。
Imports System
Imports System.Text
Module CharsetDemo
Sub Main()
' 单词 "Café" 以 UTF-8 字节存储
Dim bytes() As Byte = System.Convert.FromBase64String("Q2Fmw6k=")
Dim correct As String = Encoding.UTF8.GetString(bytes)
Console.WriteLine(correct)
' Café
End Sub
End Module
改用 Encoding.Unicode 去读同样这五个字节,你会得到一小串乱码 - 几个古怪的 CJK 区字符外加一个替换符号 - 因为解码器把字节两个一对地配对。用 Encoding.ASCII 去读 "Café" 的 UTF-8 字节,重音符号会变成 ??,一对问号,因为重音符号在 UTF-8 里是两个字节。这些选择没有一个会抛异常;它们只是悄悄产出错误的文本,这正是为什么字符集是一个你刻意做的决定,而不是你继承来的默认值。
URL 安全字母表:Base64Url
标准 Base64 使用 + 和 /,而这两个字符在 URL 内部都各带着自己的含义,所以标准字母表一旦落进查询字符串,就可能弄断一条链接。解决办法已在 RFC 4648 第 5 节标准化,就是 URL 安全和文件名安全变体:同样的 64 字符方案,用 - 顶替 +,用 _ 顶替 /,而末尾的填充通常被丢掉,因为长度已经暗示了它。你会在 JWT、API 令牌以及任何 Base64 骑在 URL 里的地方遇见这套字母表。.NET 9 为它添加了一个专用类,System.Buffers.Text.Base64Url,用起来非常痛快:
Imports System.Buffers.Text
Imports System.Text
Module UrlSafeOpener
Sub Main()
' URL 安全输入,末尾无填充
Dim packed As String = "SGVsbG8gd29ybGQ"
Dim bytes() As Byte = Base64Url.DecodeFromChars(packed)
Dim text As String = Encoding.UTF8.GetString(bytes)
Console.WriteLine(text)
' Hello world
End Sub
End Module
有两个行为值得知道。Base64Url 编码器按设计产出不带填充的输出,但它的解码器同时接受带填充和不带填充的输入,所以它对外部生态的数据很友好。而 Base64Url.IsValid 让你在解码之前预检一个候选字符串,当数据来自外部世界时这非常方便。如果你被困在没有这个类的更老运行时上,转换就是两次字符替换加一次填充补齐,恰好是编码器所做事情的倒放:
Imports System
Module CompatOpener
Function FromUrlSafe(ByVal packed As String) As Byte()
Dim standard As String = packed.Replace("-"c, "+"c).Replace("_"c, "/"c)
Select Case standard.Length Mod 4
Case 2
standard &= "=="
Case 3
standard &= "="
End Select
Return System.Convert.FromBase64String(standard)
End Function
End Module
在 .NET Framework 4.6.2 或更新的版本上,你也可以装 Microsoft.Bcl.Memory NuGet 包,直接使用真正的 Base64Url 类。无论如何,规则很简单:从上下文认出字母表(URL、JWT、API 令牌),然后挑匹配的解码器。
打开文件
Base64 最古老的用途之一,就是让二进制偷渡过文本文件:一个装着编码字节的 .b64 或 .txt 文件。在 Visual Basic 里,这段往返是两次文件调用加一次解码。读文本,解码,写字节:
Imports System.IO
Module FileOpener
Sub Main()
Dim packed As String = File.ReadAllText("payload.b64")
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
File.WriteAllBytes("payload.bin", bytes)
End Sub
End Module
对空白的宽容让这件事稳固得令人满足:文件并不在乎负载是被写成一条长行、按 76 字符折行、还是按 64 字符折行,因为解码器反正会跳过换行。把一个尺寸事实揣在兜里:文本文件比它藏着的二进制大约大三分之一,所以一个 10 兆字节的文件会化成大约 13.3 兆字节的字符。没什么惊天动地,但当某个"小"文本文件让你觉得它很大时,这就是该记住的数字。
图片与 Data URI
data URI 方案(RFC 2397)让 URL 能携带自己的内容:data: 后面跟着媒体类型、字面标记 ;base64、一个逗号,然后是编码字节。你在 web 上到处见过它,在 HTML 和 CSS 里,它把小图片和字体直接嵌进标记,而不是指向一个单独的文件。在 Visual Basic 里,拆开一个 data URI 就是字符串一劈加一次解码。下面的例子从 data URI 里拽出一张 PNG,并用它构建一个 WPF 图片:
Imports System.IO
Imports System.Windows.Media.Imaging
Module DataUriOpener
Function ImageFromDataUri(ByVal dataUri As String) As BitmapImage
Dim comma As Integer = dataUri.IndexOf(","c)
Dim header As String = dataUri.Substring(0, comma)
If Not header.EndsWith(";base64") Then
Throw New FormatException("Not a base64 data URI")
End If
Dim packed As String = dataUri.Substring(comma + 1)
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
Dim image As New BitmapImage()
image.BeginInit()
image.CacheOption = BitmapCacheOption.OnLoad
image.StreamSource = New MemoryStream(bytes)
image.EndInit()
Return image
End Function
End Module
注意头部上的防御性检查:没有 ;base64 标记的 data URI 装的是 URL 转义后的数据,把它当 Base64 解码要么失败,要么产出垃圾。RFC 本身就警告 data URI 只对短值有用,而 HTML 还有自己的属性长度限制,所以把它当作图标、头像和缩略图的正确工具,而不是把整个照片库塞进一个属性里运出去。
HTTP、API 与 Basic 认证
Base64 在 web API 里无处不在,最常见的两种出场方式是 HTTP Basic 认证头部,以及携带二进制或预编码数据的 JSON 字段。Basic 认证是最简单的情况:客户端发送 Authorization: Basic,后面跟着 username:password 的 Base64。在 Visual Basic 里解码它,就是前缀检查加一个调用:
Imports System
Imports System.Text
Module BasicAuthOpener
Function ReadCredentials(ByVal header As String) As String
If Not header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase) Then
Throw New FormatException("Not a Basic auth header")
End If
Dim packed As String = header.Substring(6)
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
Return Encoding.UTF8.GetString(bytes)
End Function
End Module
这个函数把 username:password 作为一根字符串还给你,然后你在冒号处劈开它。另一个日常情况是 JSON 响应里某个字段是一团预编码的数据,比如图片或证书。用 HttpClient 和 System.Text.Json(System.Text.Json 从 .NET Core 3.0 起就在盒子里,HttpClient 则早就在了),这个模式直截了当:
Imports System.Net.Http
Imports System.Text.Json
Module ApiOpener
Async Function ReadImageAsync() As Task(Of Byte())
Using client As New HttpClient()
Dim json As String = Await client.GetStringAsync("https://httpbin.org/get?attachment=TWFuIGlzIGhlcmU%3D&name=man.txt")
Dim doc As JsonDocument = JsonDocument.Parse(json)
Dim packed As String = doc.RootElement.GetProperty("args").GetProperty("attachment").GetString()
Return System.Convert.FromBase64String(packed)
End Using
End Function
End Module
两条收尾备注。别因为你能就把解码出来的凭据打印到日志或界面上,也别依赖纯 HTTP 上的 Basic 认证,因为那样你只是换了一套更有意思的字母表来拼写密码而已。
JWT:读取三段
紧凑形式的 JSON Web Token 是三段用点分隔的 Base64Url:头部、载荷和签名。前两段是你能用眼睛读的普通 JSON(或者用一个解码调用),而第三段是密码学签名,必须用正确的密钥去验证,而不是解码。常见演示里的一个样例子长这样:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.W-5gI_FdnMBdpgG4twX96rptvh13gmXQ75kKLRXVaGs。在 Visual Basic 里读取它的载荷,就是在点处劈开、把 URL 安全字母表换回标准字母表、再解码:
Imports System
Imports System.Text
Module JwtOpener
Function ReadPayload(ByVal token As String) As String
Dim parts() As String = token.Split("."c)
If parts.Length <> 3 Then
Throw New FormatException("Not a compact JWT")
End If
' 把 URL 安全字母表换回标准字母表
Dim packed As String = parts(1).Replace("-"c, "+"c).Replace("_"c, "/"c)
Select Case packed.Length Mod 4
Case 2
packed &= "=="
Case 3
packed &= "="
End Select
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
Return Encoding.UTF8.GetString(bytes)
End Function
End Module
拿它跑一遍样例令牌,你会得到 JSON {"sub":"1234567890","name":"John Doe"}。同样方式读头部,你会得到 {"alg":"HS256","typ":"JWT"}。这里要紧的警告是:读取 JWT 不是验证 JWT。任何人都能伪造一个令牌,所以在信任里面的任何声明之前,用签发者的密钥验证签名。干这个活,System.IdentityModel.Tokens.Jwt NuGet 包(来自 Microsoft Entra 团队的 IdentityModel 套件)会替你处理 Base64Url 细节、签名检查和声明解析,而这恰恰是你不想自己造轮子的那一层。
邮件附件:76 字符折行
邮件是 Base64 打下名声的地方。SMTP 是为 7 位 ASCII 设计的,所以二进制附件必须先变成文本才能起飞,而 MIME 标准(RFC 2045)选定了 Base64,配上 76 字符的行宽限制,这是更古老的 PEM 64 字符行的近亲。如果你收到过原始邮件,你就见过它的结果:一坨密集的 Base64,被切成整整齐齐的短行,躺在 Content-Transfer-Encoding: base64 头部下面。对解码器来说美妙的部分是:你什么都不用拆。.NET 解码器会跳过任何位置出现的换行和空格,所以折行后的代码块可以原样解码:
Imports System
Imports System.Text
Module MimeOpener
Sub Main()
' 一个 59 字节的句子,按 76 字符用 CRLF 做了 MIME 折行
Dim wrapped As String = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZywgYW5kIHRoZW4gc29t" & vbCr & vbLf & "ZS4="
Dim bytes() As Byte = System.Convert.FromBase64String(wrapped)
Console.WriteLine(Encoding.UTF8.GetString(bytes))
' The quick brown fox jumps over the lazy dog, and then some.
End Sub
End Module
如果你在打交道的 System.Net.Mail 类,解码就更隐形了:你加到 MailMessage 上的一个 Attachment 自带 TransferEncoding.Base64 的 ContentEncoding,邮件库会替你完成包裹、发送、拆包裹的整套仪式。只有当你从流、测试夹具或遗留邮箱文件里读原始 MIME 时,才需要手动解码。
数据库、配置与环境变量
只存文本的存储场所总是在向 Base64 招手:一个按文本类型声明的数据库列、一个 XML 配置值、一个环境变量。它们全都要普通字符,所以二进制在存入前被编码,取回时再解码。解码侧永远是同一行式,有意思的部分是尺寸算术。SQL Server 里一个普通的 NVARCHAR 列上限是 8,000 字符,也就是说 33% 的税还没把你推过界之前,大约只能装 6,000 字节的二进制;再往上就得找 MAX 变体,或者更诚实地说,找一个真正的二进制列。在 Windows 上,单个用户自定义环境变量上限是 32,767 字符(在 XP 时代的系统上,整个环境块也被卡在那个尺寸上),所以"把整个许可证数据塞进一个环境变量"是有硬上限的。这里有一个值得记在脑角的解码模式:拿存下来的指纹去对文件,用常量时间比较,这样不匹配也不会泄露计时信息:
Imports System.Security.Cryptography
Module FingerprintCheck
Function FingerprintsMatch(ByVal expectedPacked As String, ByVal fileBytes() As Byte) As Boolean
Dim expected() As Byte = System.Convert.FromBase64String(expectedPacked)
Dim actual() As Byte = SHA256.HashData(fileBytes)
Return CryptographicOperations.FixedTimeEquals(expected, actual)
End Function
End Module
同样的形状对任何存下来的哈希都有效:解码存下的值,算出新鲜的哈希,再用固定时间比较。配置文件遵循完全相同的模式,不管值来自 XML app.config 条目、JSON 设置文件,还是注册表字符串。
大数据:不加载就解码流
到目前为止的每个例子都把整个负载读进了内存,这对附件和配置值来说没问题,但对某个被编码进文本文件的 20 亿字节的大文件来说就是错的。到了那个量级,.NET 有一对从 .NET Framework 1.1(2003)就存在的流式搭档:包在 CryptoStream 里的 FromBase64Transform 加密变换。你读一块一块的编码文本,变换即时解码,你把字节写出去,所以无论文件多大,内存都保持平坦:
Imports System.IO
Imports System.Security.Cryptography
Module StreamOpener
Sub DecodeFile(ByVal packedPath As String, ByVal outputPath As String)
Using packedStream As New FileStream(packedPath, FileMode.Open, FileAccess.Read)
Using decodedStream As New CryptoStream(packedStream, New FromBase64Transform(), CryptoStreamMode.Read)
Using outputStream As New FileStream(outputPath, FileMode.Create)
Dim buffer(65535) As Byte
While True
Dim read As Integer = decodedStream.Read(buffer, 0, buffer.Length)
If read = 0 Then Exit While
outputStream.Write(buffer, 0, read)
End While
End Using
End Using
End Using
End Sub
End Module
因为编码后的文件就是纯 ASCII 文本,把它当字节流读完全安全,而且变换会自行应付折行,你什么都不用做。输出文件大约是输入的四分之三个大小,这就是进来时交的 33% 的税,出去时被收回来了。
专咬 Visual Basic 的坑
这一节的大多数陷阱和其他 .NET 语言共享,但有几个戴着很明显的 VB 帽子,所以放在一起:
- Byte 与 Byte()。在 Visual Basic 里,单个字节是
Byte,字节数组是Byte(),空括号干着全部的活。本想说Byte()却写成Dim b As Byte,是经典的第一天错误,而这恰恰是Option Strict On在编译期就能抓住的东西。如果你的项目还没开,就把它打开:dotnet new console -lang VB模板把这个决定留给你,而 Visual Studio 项目模板则已经设好了。 - span 墙。现代的基于 span 的 API 可以从 VB 调用,但只能在调用点:你可以把
Byte()或Char()数组直接递给一个接受 span 的方法,编译器会替你转换。你做不到的是在自己代码里给 span 起名字。声明一个类型为Span或ReadOnlySpan的变量、字段或参数,编译器会回答"此编译器版本不支持包含嵌入式引用的类型"。所以 VB 的惯用法是:用普通数组调用 span API,永远别想把 span 存进变量。 - BitConverter 不是 Base64。
BitConverter.ToString(bytes)把字节渲染成用短横线分隔的十六进制,对任何听过"把字节转成字符串"的人来说,这是一个诱人的错误答案。当 API 期望TWFu时,它会高高兴兴地递给你4D-61-6E。拿不准时,伸手去拿System.Convert。 - MidB 是幽灵。经典 Visual Basic 有字节级字符串函数
MidB、LeftB和RightB,瞄准双字节字符集。现在所有 .NET 字符串都是 Unicode,运行时文档说得很直白:它们不再受支持。如果一段遗留代码用了它们,用字节数组和本文的 API 重写它。 - Encoding.Default 跟着机器走。区域设置分歧的故事是 .NET Framework 的:在现代 .NET 上,
Default永远是 UTF-8,所以同样的字节在任何机器上解码方式都一样。对你共享的数据,显式说出编码,通常是Encoding.UTF8- 这条建议无论如何都成立。 - 往返不是恒等。如果你解码一根字符串再把结果编码回去,新字符串不保证和原来的一致:空白消失了,填充被规范化了。带换行的负载会回来成一条干净的单行。对数据来说没事,但如果你的逻辑比较的是编码文本而不是解码字节,这就危险了。
- 纯空白输入是安静的成功。只有空格和换行的字符串会解码成空字节数组,没有任何错误,所以"用户只粘贴了换行"和"用户粘贴了空负载"看起来一模一样。如果这个区别要紧,在解码之前检查输入长度。
解码最佳实践
从上面所有内容中提炼出来,这些习惯能让解码代码保持无聊(褒义):
- 把 Base64 当运输,而不是保护。它是编码,不是加密:任何人一个函数调用就能读出原文,RFC 4648 甚至提到,对字母表的马虎处理可能打开隐蔽通道。如果要隐藏内容,先加密,再编码。
- 面对不可信输入,比起放任
FormatException乱飞,更推荐Try方法或IsValid预检。把布尔值变成友好的错误消息,比吞掉一个异常更容易。 - 刻意选择字符集,除非你有文档记载的理由支持另一套方案,否则默认用 UTF-8。
- 让字母表匹配来源:MIME、邮件和配置用标准 Base64,JWT 和任何 URL 里的东西用 Base64Url。
- 任何大的东西都让
FromBase64Transform和CryptoStream流过去,而不是加载进字符串。 - 用
CryptographicOperations.FixedTimeEquals而不是=比较指纹和哈希,这样计时不会泄露值匹配了多少。 - 保持
Option Strict On,让Byte/Byte()那一族手滑成为编译错误,而不是生产环境里的悬案。
Visual Basic 是如何得到解码器的
故事始于一个 Visual Basic 完全没有 Base64 的年代。在 Visual Basic 6 和 VBA 的世界里(这套宏语言至今仍在 Excel 和 Office 里面运行),需要 Base64 的开发者从机器上已有的 COM 组件里借用它。那个著名技巧用了一个 XML DOM 元素:MSXML 解析器允许一个节点把 DataType 声明为 bin.base64,于是把 Base64 字符串赋给节点的 text 属性,再读回它的 nodeTypedValue,你就拿到了原始字节,真正的 Base64 数学由 DOM 代劳(ADO Stream 对象自己的 Charset 属性只认识 "utf-8" 或 "iso-8859-1" 这样的真字符集名字,不认识 "base64",所以这个技巧转而伸手去拿 MSXML)。编码则是同一想法倒着跑:把字节写进 nodeTypedValue,再从 text 里读回编码后的字符串:
' 经典的 VB6 / VBA 解码技巧,供参考
Dim node As Object
Set node = CreateObject("MSXML2.DOMDocument").createElement("b64")
node.DataType = "bin.base64"
node.text = packed ' Base64 字符串
Dim bytes() As Byte
bytes = node.nodeTypedValue
它管用,它巧妙,而且三十多年过去,它仍然是 "base64 VBA" 还在搜索引擎上点亮的原因。然后 2002 年改变了一切:Visual Basic 7.0(这门语言第一个 .NET 版本,当时叫 Visual Basic .NET)加入了新的公共语言运行时,.NET Framework 开箱带来了 System.Convert 和 FromBase64String 以及它的伙伴们。从 2003 年的 .NET Framework 1.1 起,每个 VB 程序都拥有一个一等解码器,无需注册任何组件。现代的一波在 2018 年随着 .NET Core 2.1 到来,它加上了无异常的 Try 方法和快速的基于 span 的 System.Buffers.Text.Base64 类;2024 年 .NET 9 到来,终于把 URL 安全字母表标准化为 Base64Url。截至 2026 年,.NET 10 - 2025 年 11 月发布 - 是长期支持版本,预览中的 .NET 11 库正在添加新一代 Base64 便捷方法,所以解码器一直在变好,而 2003 年的原始一行式原样继续服役。
VB 世界的趣味事实
- 官方 Visual Basic 编译器本身就是用 Visual Basic 写的。作为开源 Roslyn 项目的一部分,这门语言编译它自己。
- 最初的 Visual Basic 于 1991 年发布,那时 web 时代还没开始。Base64 的高光时刻随 MIME 在 1993 年到来,而 VB 本身两年后长出了 32 位能力 - 1995 年的 Visual Basic 4 首次让 32 位程序成为可能。1997 年的 VB 5 则走到底:只有 32 位,盒子里一点 16 位都不剩。
- 经典 VB 的字节级函数
MidB、LeftB和RightB在 .NET 里被官方判定"不再受支持",因为所有 VB 字符串从框架第一天起就是 Unicode。一整族 API,因一个编码选择而退休。 - 运行时的 Base64 机械装置不是一个古早的查表:在机器支持的地方,最新实现跑硬件向量化的代码路径(AVX-512、AVX2 和 SSE 变体),所以引擎盖下发生的不是"缓慢的文本编解码"。
- VB6 时代的 MSXML
bin.base64技巧如今仍在生产环境的 Excel 宏里运行,这意味着同一个组织的代码库里,一个 1990 年代末的绕行方案和一个 2003 年的Convert调用正快乐地共处。
在你离开之前
本文覆盖了 Visual Basic 中 Base64 的解码一侧,从一行式到流式,从 JWT 到戴着 VB 帽子的坑。硬币的另一面 - 先把字节和文本变成 Base64 - 有它自己的一组关于填充、换行和尺寸税的决定,姊妹站点上的配套编码文章对它做了完整详细的覆盖。通往它的链接就在这行下方,而首页上的工具依然是手动检查小负载最快的方式。
最后更新: 2026-09-08