上传文件
  1. base64hub
  2. 在代码中使用 Base64
  3. C#
代码

C# 中的 Base64

System.Convert 从 .NET Framework 1.0 起就支持 Base64,.NET 9 又新增了处理 URL 安全字符串的 System.Buffers.Text.Base64Url。下面的输出均来自 .NET 9。

编码和解码字符串

Convert 处理的是字节,所以要显式选择一种编码。只要数据会离开你的进程,几乎总是应该选 UTF-8:

using System.Text;

string enc = Convert.ToBase64String(Encoding.UTF8.GetBytes("héllo wörld"));
Console.WriteLine(enc);   // aMOpbGxvIHfDtnJsZA==

string dec = Encoding.UTF8.GetString(Convert.FromBase64String(enc));
Console.WriteLine(dec);   // héllo wörld

Encoding.Unicode 是 UTF-16LE,会让 ASCII 文本的体积翻倍。只有当对方要求 UTF-16 时它才是正确的选择,例如 PowerShell 编码命令 格式。

一个常见的实际用途是 HTTP Basic Auth 请求头:

string token = Convert.ToBase64String(Encoding.UTF8.GetBytes("user:pass"));
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", token);
// Authorization: Basic dXNlcjpwYXNz

Basic Auth 请求头生成器 可以在浏览器中生成相同的值。

URL 安全的 Base64:.NET 9 的 Base64Url

.NET 9 新增了 System.Buffers.Text.Base64Url。它编码时不加填充,解码时有没有填充都行:

using System.Buffers.Text;

byte[] d = Encoding.UTF8.GetBytes("<<???>>");
Console.WriteLine(Base64Url.EncodeToString(d));            // PDw_Pz8-Pg

byte[] a = Base64Url.DecodeFromChars("PDw_Pz8-Pg");        // <<???>>
byte[] b = Base64Url.DecodeFromChars("PDw_Pz8-Pg==");      // <<???>>

它不接受标准字母表:Base64Url.DecodeFromChars("PDw/Pz8+Pg") 会抛出 FormatException。另外还有基于 span 的重载(EncodeToChars、DecodeFromUtf8、TryDecodeFromChars),可以避免内存分配。

在没有 Microsoft.Bcl.Memory 包的旧运行时上,就手动转换:

string url = Convert.ToBase64String(d)
    .TrimEnd('=').Replace('+', '-').Replace('/', '_');    // PDw_Pz8-Pg

解码时需要反向替换,并把长度补齐到 4 的倍数:

static byte[] FromBase64Url(string s)
{
    s = s.Replace('-', '+').Replace('_', '/');
    s = s.PadRight(s.Length + (4 - s.Length % 4) % 4, '=');
    return Convert.FromBase64String(s);
}

Encoding.UTF8.GetString(FromBase64Url("PDw_Pz8-Pg"));   // <<???>>

ASP.NET Core 项目也可以改用 Microsoft.AspNetCore.WebUtilities 中的 WebEncoders.Base64UrlEncode。关于这两种字母表的更多内容,见 Base64 与 Base64URL 的区别。

不抛异常地校验输入

Convert.FromBase64String 遇到无效输入时会抛出 System.FormatException。在 .NET 9 上,几乎所有情况的错误信息都一样:

The input is not a valid Base-64 string as it contains a non-base 64 character,
more than two padding characters, or an illegal character among the padding characters.

对于 PDw_Pz8-Pg==(URL 安全)、aGk=!!(无关字符)、aGk(无填充)、a 和 aGk==,我们得到的都是这条信息。它接受空白(" aGk= " 和 "aGVs\nbG8K" 都能正常解码),也接受不为零的填充位(aGl=)。

要在热点路径上避免异常,可以用带缓冲区的 Try 版本,或者 Base64.IsValid(.NET 8 及以上):

Span<byte> buf = stackalloc byte[16];
Convert.TryFromBase64String("aGk=", buf, out int n);   // True, n = 2
Convert.TryFromBase64String("aGk", buf, out n);        // False, n = 0

System.Buffers.Text.Base64.IsValid("aGk=");      // True
System.Buffers.Text.Base64.IsValid("aGk");       // False
Base64Url.IsValid("PDw_Pz8-Pg");                 // True

缓冲区必须足够容纳解码后的输出,否则即使输入有效,TryFromBase64String 也会返回 false。缓冲区大小取 (s.Length * 3) / 4。对于解不开的字符串,可以借助 Base64 校验工具 和 错误指南。

换行(MIME)

Base64FormattingOptions.InsertLineBreaks 会在 76 个字符处用 \r\n 换行,末尾不带换行符:

string mime = Convert.ToBase64String(
    Encoding.ASCII.GetBytes(new string('x', 60)),
    Base64FormattingOptions.InsertLineBreaks);
// "eHh4eHh4...eHh4" (76 chars) + "\r\n" + "eHh4"

FromBase64String 无需任何改动就能解码这种分行格式。MIME Base64 编码器 生成的布局与此相同。

编码和解码文件

Console.WriteLine(Convert.ToBase64String(File.ReadAllBytes("hello.txt")));  // aGVsbG8K
File.WriteAllBytes("copy.txt", Convert.FromBase64String("aGVsbG8K"));

用 CryptoStream 处理大文件

ToBase64Transform 和 FromBase64Transform 可以接入 CryptoStream,让内存占用保持平稳。别被命名空间误导,这里不涉及任何加密:

using System.Security.Cryptography;

using (var input = File.OpenRead("big.bin"))
using (var output = File.Create("big.b64"))
using (var b64 = new CryptoStream(output, new ToBase64Transform(), CryptoStreamMode.Write))
{
    input.CopyTo(b64);
}

using (var input = new CryptoStream(File.OpenRead("big.b64"),
                                    new FromBase64Transform(), CryptoStreamMode.Read))
using (var output = File.Create("big.out"))
{
    input.CopyTo(output);
}

释放 CryptoStream 时才会写出最后一个块和填充;漏掉这一步,输出就会被截断。在一个 300 KB 的测试文件上,两个方向的结果都与内存中处理的结果一致。想快速手动检查,可以用 Base64 转文件 打开结果。

常见陷阱

  • Data URI 前缀:data:image/png;base64, 包含 : 和 ;,会触发 FormatException。把逗号及其之前的内容全部去掉。
  • 把 URL 安全令牌交给 Convert:JWT 的各部分会报上面那条通用错误。请用 Base64Url 或 JWT 解码器。
  • Encoding.Default:在 .NET Framework 上它是 ANSI 代码页,在 .NET Core 及以后的版本中是 UTF-8。代码在两者之间迁移后,非 ASCII 文本的输出会改变。
  • Base64 不是加密:用 Base64“保护”的连接字符串,对任何人来说都是明文。见 编码与加密的区别。

版本说明

  • .NET Framework 1.0 及以上:Convert.ToBase64String、FromBase64String、ToBase64Transform。
  • .NET Core 2.1 及以上:span 重载 Convert.TryFromBase64String、TryToBase64Chars,以及面向 UTF-8 的 System.Buffers.Text.Base64 类。
  • .NET 8:Base64.IsValid。
  • .NET 9:Base64Url,旧的目标框架也可以通过 Microsoft.Bcl.Memory 使用。

常见问题

如何解决 The input is not a valid Base-64 string 报错?

检查是否有 URL 安全字符(-、_)、缺失的填充以及 Data URI 前缀。Convert.FromBase64String 只接受填充正确的标准字母表。处理令牌时,在 .NET 9 上请用 Base64Url.DecodeFromChars,它带不带填充都能接受。

在 .NET 8 或 .NET Framework 上能用 Base64Url 吗?

Base64Url 内置于 .NET 9 及更高版本。对于更旧的目标框架,微软在 Microsoft.Bcl.Memory 包中提供了同一个类型。如果不用这个包,就对标准输出调用 TrimEnd('=') 和两次 Replace 来转换。

为什么对文本做 Base64 时不该用 Encoding.Unicode?

Encoding.Unicode 是 UTF-16LE,所以 hi 会变成 aABpAA==,而不是 aGk=。除非接收方要求 UTF-16(比如 PowerShell 的 -EncodedCommand),否则请用 Encoding.UTF8。

继续阅读