如何在C#中实现Base64 URL安全编码?

165

我想在C#中实现Base64 URL安全编码。在Java中,我们有常见的Codec库,可以给我提供URL安全编码的字符串。如何在C#中实现相同的功能?

byte[] toEncodeAsBytes = System.Text.ASCIIEncoding.ASCII.GetBytes("StringToEncode");
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes);

上面的代码将其转换为Base64,但会填充==。有没有一种方法可以实现URL安全编码?


1
你不能只是在BASE64字符串上使用Url.Encode吗? - user2160375
C# 中 Url 类位于哪个命名空间? - Vishvesh Phadnis
请看这里:http://msdn.microsoft.com/zh-cn/library/system.web.httputility.urlencode(v=vs.110).aspx 您需要引用 System.Web 程序集。 - user2160375
它把 = 转换成了 %3D。我不想要那个。 - Vishvesh Phadnis
3
“url safe”是什么意思?“%3D”是url安全的。 - user2160375
重复的内容:https://dev59.com/13M_5IYBdhLWcg3wymU0 - Neil
10个回答

251

通常可以简单地交换字母表以在 URL 中使用,这样就不需要使用 %-encoding;65个字符中只有3个会引起问题 - +/=。最常见的替换是用-代替+,用_代替/。至于填充:只需删除它(即=); 您可以推断所需的填充量。在另一端:只需反向进行此过程:

string returnValue = System.Convert.ToBase64String(toEncodeAsBytes)
        .TrimEnd(padding).Replace('+', '-').Replace('/', '_');

使用:

static readonly char[] padding = { '=' };

并且要反转:

string incoming = returnValue
    .Replace('_', '/').Replace('-', '+');
switch(returnValue.Length % 4) {
    case 2: incoming += "=="; break;
    case 3: incoming += "="; break;
}
byte[] bytes = Convert.FromBase64String(incoming);
string originalText = Encoding.ASCII.GetString(bytes);

然而,有趣的问题是:这是否是“常用编解码库”所使用的相同方法?这肯定是一个合理的第一步测试 - 这是一个相当常见的方法。


1
在通用编解码器中,它们使用[0-9a-zA-Z_-]字符来进行URL安全模式。 - Vishvesh Phadnis
非常感谢Marc;我一直在尝试解码由Java的encodeBase64URLSafeString创建的字符串,最终问题是字符串末尾缺少填充,你用switch语句解决了这个问题! - Mike Perrenoud
2
为什么我们不需要:case 1: incoming += "==="; break;? - alex da franca
9
由于其长度模4永远不为1,因此不需要使用“===”填充。 3个8位字节变为4个6位字节(每个6位字节是所选字符集中的64个字符之一),0个8位字节编码为0个6位字节而无需填充,“1个8位字节编码为2个6位字节带有“==”填充,2个8位字节被编码为3个6位字节带有“=”填充,3个8位字节被编码为4个6位字节而不使用填充,然后对齐以重复。 永远不会将任何内容编码为1个6位字节。 - George Helyar
显示剩余3条评论

153

您可以使用命名空间为Microsoft.IdentityModel.TokensBase64UrlEncoder类。

const string StringToEncode = "He=llo+Wo/rld";

var encodedStr = Base64UrlEncoder.Encode(StringToEncode);
var decodedStr = Base64UrlEncoder.Decode(encodedStr);

if (decodedStr == StringToEncode)
    Console.WriteLine("It works!");
else
    Console.WriteLine("Dangit!");

Microsoft.IdentityModel.Tokens 是一个NuGet软件包,需要下载。


3
这比被采纳的答案要清晰得多。有什么不利之处吗? - azerty
1
我假设根据名称,这不是跨平台的。是这样吗? - Brandon S.
@BrandonS。可以确认它是跨平台的。生成经过URL编码的Base64字符串编码签名,在Google GKE上可以正确处理。 - Bronumski

51

12

根据这里的回答并进行了一些性能改进,我们已经发布了一个非常易于使用的安全的URL Base64实现到NuGet,源代码可在GitHub上获取(采用MIT许可证)。

使用方法非常简单

var bytes = Encoding.UTF8.GetBytes("Foo");
var encoded = UrlBase64.Encode(bytes);
var decoded = UrlBase64.Decode(encoded);

太棒了,谢谢你。顺便问一下,为什么你选择使用"string".Replace来编写encode方法,但是在decode方法中却使用循环和手动替换呢? - ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
@ᴍᴀᴛᴛʙᴀᴋᴇʀ 我需要重新审视它并运行一些基准测试,但这是因为我们添加到后者,所以它由可增长的字符列表表示,而不是不可变字符串。 - Mahmoud Al-Qudsi
1
另一个返回类型为字符串的类:https://github.com/vndevpro/architecture-common/blob/master/GdNet.Common/Services/Base64SafeEncoder.cs - hazjack

9
要获得一个URL安全的类似于base64的编码,但不是RFC4648中的“base64url”,则需要使用System.Web.HttpServerUtility.UrlTokenEncode(bytes)进行编码,使用System.Web.HttpServerUtility.UrlTokenDecode(bytes)进行解码。请注意,HTML标签应保留。

8
这不符合 RFC4648 标准中 URL 安全的 Base64 编码。请参考 此问答请谨慎使用。 - Artjom B.

2
    public string Decode(string str)
    {
       byte[] decbuff = Convert.FromBase64String(str.Replace(",", "=").Replace("-", "+").Replace("/", "_"));
       return System.Text.Encoding.UTF8.GetString(decbuff);
    }
    
    public string Encode(string input)
    {
        byte[] encbuff = Encoding.UTF8.GetBytes(input ?? "");
        return Convert.ToBase64String(encbuff).Replace("=", ",").Replace("+", "-").Replace("_", "/");
    }

这是与JavaScript对齐的方法!

仅凭其他答案,你确定最后的 Replace 是正确的吗?其他答案中使用了 .Replace("/", "_") - jasonscript

2

最简单的解决方案: (没有填充)

private static string Base64UrlEncode(string input) {
    var inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
    // Special "url-safe" base64 encode.
    return Convert.ToBase64String(inputBytes)
      .Replace('+', '-') // replace URL unsafe characters with safe ones
      .Replace('/', '_') // replace URL unsafe characters with safe ones
      .Replace("=", ""); // no padding
  }

Credit goes to: Tholle(致谢:Tholle)

0

这里有另一种方法可以解码一个url-safe base64,与Marc使用相同的方式进行编码。我只是不明白为什么4-length%4有效(它确实有效)。

如下,只有原始位长度是6和8的公倍数,base64才不会在结果中添加“=”。

1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8 
1 2 3 4 5 6|1 2 3 4 5 6|1 2 3 4 5 6|1 2 3 4 5 6
                "=="            "="

所以我们可以反过来做,如果结果的位数不能被8整除,就会被追加:

base64String = base64String.Replace("-", "+").Replace("_", "/");
var base64 = Encoding.ASCII.GetBytes(base64String);
var padding = base64.Length * 3 % 4;//(base64.Length*6 % 8)/2
if (padding != 0)
{
    base64String = base64String.PadRight(base64String.Length + padding, '=');
}
return Convert.FromBase64String(base64String);

0
Karanvir Kang的回答很好,我投了赞成票。然而,它在字符串末尾留下了一个奇怪的字符(表示删除填充字符的数量)。这是我的解决方案。
var bytesToEncode = System.Text.Encoding.UTF8.GetBytes("StringToEncode"); 
var bytesEncodedPadded = HttpServerUtility.UrlTokenEncode(bytesToEncode);
var objectIdBase64 = bytesEncodedPadded.Substring(0, bytesEncodedPadded.Length - 1);

-1

在 UWP 中使用 Microsoft 加密引擎。

uint length = 32;

IBuffer buffer = CryptographicBuffer.GenerateRandom(length);
string base64Str = CryptographicBuffer.EncodeToBase64String(buffer)
                   // ensure url safe
                   .TrimEnd('=').Replace('+', '-').Replace('/', '_');

return base64Str;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接