如何检查一个有效的Base64编码字符串

176
在C#中,除了尝试转换并查看是否出现错误之外,还有其他方法可以判断一个字符串是否是Base 64编码的吗?我有类似这样的代码:
// Convert base64-encoded hash value into a byte array.
byte[] HashBytes = Convert.FromBase64String(Value);

我想要避免“无效字符在 Base-64 字符串中”的异常,如果值不是有效的 base 64 字符串。我只想要检查并返回 false,而不是处理异常,因为我预期有时候这个值不会是一个 base 64 字符串。有没有一种在使用 Convert.FromBase64String 函数之前进行检查的方法?


1
这取决于您想要多么“彻底”地进行检查。您可以像其他人提供的那样使用一些预验证,使用正则表达式,但这并不是唯一的指标。在某些情况下,base64编码需要使用“=”符号进行填充。如果填充不正确,即使输入与表达式匹配,也会出现错误。 - vcsjones
1
你的条件并不只是满足base64字符串。请考虑字符串\n\fLE16——你的方法会对此产生错误的判断结果。对于任何正在阅读并寻找一个无懈可击的方法的人来说,我建议捕获FormatException或使用适合规范的正则表达式,请参见https://dev59.com/unRB5IYBdhLWcg3w6LN2。 - nullable
4
我认为正则表达式应该是 @"^[a-zA-Z0-9\+/]*={0,2}$" - 4Z4T4R
1
这个解决方案不可靠。如果您添加了4个相同字符的字符串,它会失败。 - Bettimms
正则表达式将匹配几乎任何没有空格的字符串,因此无法用作“IsBase64String”测试。但是,如果您像@4Z4T4R建议的那样调整正则表达式,则可以将其用作IsValidBase64String。 - Dimitri Troncquo
显示剩余2条评论
20个回答

136
使用C# 7.2 (.NET Core 2.1+ 或 .NET Standard 2.0 及更高版本) 中的Convert.TryFromBase64String
public static bool IsBase64String(string base64)
{
   Span<byte> buffer = new Span<byte>(new byte[base64.Length]);
   return Convert.TryFromBase64String(base64, buffer , out int bytesParsed);
}

2
我不知道那是个事情。如果使用C# 7.2,我认为这应该是新答案。 - Chris Mullins
12
仅适用于.NET Core 2.1+或.NET Standard 2.1+。 - Cyrus
4
对于未填充的字符串,这将返回false,以下是修复方法:Convert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span<byte>(new byte[base64.Length]), out _)。谢谢。 - rvnlord
不是完美的验证:在解析Guid.NewGuid().ToString("N")时会出现错误。 - Macko
1
就这么说吧,.FromBase64String 在幕后使用了这个方法,如果底层调用 .TryFromBase64String 返回 false,它会简单地抛出一个异常。唯一的区别是,.FromBase64String 会从输入中移除尾部的空格,所以如果你之前依赖它自动清理输入字符串,可能会遇到一些问题。 - Herohtar
显示剩余2条评论

56

更新: 对于更新版本的C#,有一个更好的替代方法,可以参考Tomas在这里给出的答案: https://dev59.com/Um015IYBdhLWcg3w7wQW#54143400.


很容易识别Base64字符串,因为它只由字符'A'..'Z', 'a'..'z', '0'..'9', '+', '/'组成,通常在末尾用最多三个'='填充,以使长度为4的倍数。但是,与其比较这些字符,如果发生异常,忽略它会更好。


1
我认为你走在了正确的道路上。我进行了一些测试,似乎是4的倍数而不是3。 - Chris Mullins
1
它的长度在编码时需要是3的倍数,以便成功编码!很抱歉...是的,你是对的...编码后的字符串长度是4的倍数。这就是为什么我们要填充3个'='的原因。 - Anirudh Ramanathan
4
因为你首先提到了多重事物,所以被标记为正确。我已经更新了我的问题并实施了解决方案,请告诉我是否发现任何问题。 - Chris Mullins
这个方法不起作用!我在几年后才发现。使用简单值“test”来检查它。 - Homayoun Behzadian
填充字符最多为2个'='。这是因为仅转换一个字节(8位)将导致2个base64字符和2个'='填充。如果你不相信,请尝试找到一个以3个'='结尾的例子。 - Zoltan Tirinda
当你只关心验证而不需要转换时,我认为这种方法会更优越。使用Tomas的答案将不可避免地使处理过程的内存需求超过两倍。背景:我正在考虑一个Azure函数,在这个函数中,我需要接收一个以base64格式编码的文件,而我只需要验证它,而不需要在这个阶段进行转换。如果我允许处理500MB的文件,那么如果我不按照这种方式处理,我就必须将最大内存需求翻倍。 - undefined

49

我知道你说你不想抛出异常。但是,由于捕获异常更可靠,我还是会发布这个答案。

public static bool IsBase64(this string base64String) {
     // Credit: oybek https://stackoverflow.com/users/794764/oybek
     if (string.IsNullOrEmpty(base64String) || base64String.Length % 4 != 0
        || base64String.Contains(" ") || base64String.Contains("\t") || base64String.Contains("\r") || base64String.Contains("\n"))
        return false;

     try{
         Convert.FromBase64String(base64String);
         return true;
     }
     catch(Exception exception){
     // Handle the exception
     }
     return false;
}

更新:感谢oybek的建议,我已经更新了条件以进一步提高可靠性。


2
多次调用 base64String.Contains 可能会导致性能下降,特别是当 base64String 是一个大字符串时。 - NucS
@NucS 您是正确的,我们可以在这里使用已编译的正则表达式。 - harsimranb
1
您可以使用 string.IsNullOrEmpty(base64String) 来检查 base64String== null || base64String.Length == 0 - Daniël Tulp
我认为执行 base64String.Contains("\t") 等操作实际上会转义 \t,因此可能无法被识别,可能需要执行 base64String.Contains("\t"),但我不确定。 - B''H Bi'ezras -- Boruch Hashem
4
现在我们可以访问.NET源代码,因此我们可以看到FromBase64String()函数执行所有这些检查。https://referencesource.microsoft.com/#mscorlib/system/convert.cs,08c34f52087ba624 如果它是有效的base64字符串,那么您将检查两次。只尝试/捕获异常可能更便宜。 - iheartcsharp
显示剩余2条评论

20

我认为正则表达式应该是:

    Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,2}$")

只匹配一个或两个尾随的“=”号,不匹配三个。

s 应该是将被检查的字符串。 RegexSystem.Text.RegularExpressions 命名空间的一部分。


4
不检查字符串长度是否为 4 的倍数 = 0。 - calingasan

10

为了完整起见,我想提供一些实现方案。总的来说,正则表达式是一种昂贵的方法,特别是当字符串很大时(例如传输大文件时)。以下方法首先尝试最快的检测方式。

public static class HelperExtensions {
    // Characters that are used in base64 strings.
    private static Char[] Base64Chars = new[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
    /// <summary>
    /// Extension method to test whether the value is a base64 string
    /// </summary>
    /// <param name="value">Value to test</param>
    /// <returns>Boolean value, true if the string is base64, otherwise false</returns>
    public static Boolean IsBase64String(this String value) {

        // The quickest test. If the value is null or is equal to 0 it is not base64
        // Base64 string's length is always divisible by four, i.e. 8, 16, 20 etc. 
        // If it is not you can return false. Quite effective
        // Further, if it meets the above criterias, then test for spaces.
        // If it contains spaces, it is not base64
        if (value == null || value.Length == 0 || value.Length % 4 != 0
            || value.Contains(' ') || value.Contains('\t') || value.Contains('\r') || value.Contains('\n'))
            return false;

        // 98% of all non base64 values are invalidated by this time.
        var index = value.Length - 1;

        // if there is padding step back
        if (value[index] == '=')
            index--;

        // if there are two padding chars step back a second time
        if (value[index] == '=')
            index--;

        // Now traverse over characters
        // You should note that I'm not creating any copy of the existing strings, 
        // assuming that they may be quite large
        for (var i = 0; i <= index; i++) 
            // If any of the character is not from the allowed list
            if (!Base64Chars.Contains(value[i]))
                // return false
                return false;

        // If we got here, then the value is a valid base64 string
        return true;
    }
}

编辑

根据 Sam 的建议,您还可以稍微改变源代码。他提供了一个更好的测试最后一步的表现方式。这个例程

    private static Boolean IsInvalid(char value) {
        var intValue = (Int32)value;

        // 1 - 9
        if (intValue >= 48 && intValue <= 57) 
            return false;

        // A - Z
        if (intValue >= 65 && intValue <= 90) 
            return false;

        // a - z
        if (intValue >= 97 && intValue <= 122) 
            return false;

        // + or /
        return intValue != 43 && intValue != 47;
    } 

可以使用以下代码将 if (!Base64Chars.Contains(value[i])) 替换为 if (IsInvalid(value[i]))

Sam 改进后的完整源代码如下(为了清晰起见已删除注释)

public static class HelperExtensions {
    public static Boolean IsBase64String(this String value) {
        if (value == null || value.Length == 0 || value.Length % 4 != 0
            || value.Contains(' ') || value.Contains('\t') || value.Contains('\r') || value.Contains('\n'))
            return false;
        var index = value.Length - 1;
        if (value[index] == '=')
            index--;
        if (value[index] == '=')
            index--;
        for (var i = 0; i <= index; i++)
            if (IsInvalid(value[i]))
                return false;
        return true;
    }
    // Make it private as there is the name makes no sense for an outside caller
    private static Boolean IsInvalid(char value) {
        var intValue = (Int32)value;
        if (intValue >= 48 && intValue <= 57)
            return false;
        if (intValue >= 65 && intValue <= 90)
            return false;
        if (intValue >= 97 && intValue <= 122)
            return false;
        return intValue != 43 && intValue != 47;
    }
}

四个 f (ffff) 是一个有效的 base64 字符串吗?你的代码假设它是有效的,但我不确定。 - Just a learner
1
是的,可以。字符串 ffff 可以解码为有效的字节数组。 - Oybek

6
为什么不直接捕获异常并返回False呢?这样可以避免在常见情况下的额外开销。

1
这是一个不寻常的情况,我猜我将使用的值更可能不是base64,所以我宁愿避免异常的开销。在进行转换之前检查要快得多。我正在尝试将我从明文密码继承的旧系统转换为散列值。 - Chris Mullins
2
正则表达式永远不会比Tyler所建议的更快。 - Vincent Koeman
请看我的帖子底部的注释。我认为根据你处理的字符串长度而定,测试优先可能更快,尤其是对于像哈希密码这样的小字符串。该字符串必须是4的倍数才能进入正则表达式,然后在一个小字符串上使用正则表达式比在一个非常大的字符串上更快。 - Chris Mullins
6
在一个完美的世界里,不应该编写那些已知会抛出异常的业务逻辑代码。异常捕获和处理(try/catch)块的性能开销过高,不应当作为一个决策块来使用。 - Ismail Hawayel

6
答案必须取决于字符串的用途。根据几位发布者提出的语法建议,有许多字符串可能是“有效的base64”,但可能无论如何都会正确解码为垃圾。例如:8个字符的字符串Portland是有效的Base64。那么指出这是有效的Base64有什么意义呢?我猜你想知道这个字符串是否应该或不应该进行Base64解码。
在我的情况下,我正在从文件app.config中读取Oracle连接字符串,这些字符串可以是纯文本,比如:
Data source=mydb/DBNAME;User Id=Roland;Password=secret1;

或者使用Base64编码,例如:

VXNlciBJZD1sa.....................................==

(我的前任认为base64是加密方式:-)

为了确定在这个特定的使用情况下是否需要进行base64解码,我应该简单地检查字符串是否以“Data”开头(不区分大小写)。这比仅仅尝试解码并查看是否发生异常要更容易、更快速、更可靠

if (ConnectionString.Substring(0, 4).ToLower() != "data")
{
  //..DecodeBase64..
}

我更新了这个答案;我的结论是:

我只需要检查是否存在分号,因为这证明它不是Base64,当然比上面的任何方法都更快。


1
同意,特定情况也会施加某些额外的快速检查。就像明文连接字符串与Base64编码一样。 - Oybek
1
完全同意。任何偶数长度的ASCII字符串都将根据此处提到的方法注册为base64。 "TEST"将注册为有效的base64,当自动补偿缺少填充时,"TEST1"也是如此。实际上没有一种完全可靠的方法来测试base64编码。这应该是被接受的答案,因为其他答案是误导性的,并会导致许多错误的结果。 - Dimitri Troncquo

5

将字符串解码、重新编码并将结果与原始字符串进行比较。

public static Boolean IsBase64(this String str)
{
    if ((str.Length % 4) != 0)
    {
        return false;
    }

    //decode - encode and compare
    try
    {
        string decoded = System.Text.Encoding.UTF8.GetString(System.Convert.FromBase64String(str));
        string encoded = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(decoded));
        if (str.Equals(encoded, StringComparison.InvariantCultureIgnoreCase))
        {
            return true;
        }
    }
    catch { }
    return false;
}

4
我更喜欢这种用法:
    public static class StringExtensions
    {
        /// <summary>
        /// Check if string is Base64
        /// </summary>
        /// <param name="base64"></param>
        /// <returns></returns>
        public static bool IsBase64String(this string base64)
        {
            //https://dev59.com/Um015IYBdhLWcg3w7wQW
            Span<byte> buffer = new Span<byte>(new byte[base64.Length]);
            return Convert.TryFromBase64String(base64, buffer, out int _);
        }
    }

然后使用

if(myStr.IsBase64String()){

    ...

}

这是最好的方法。人们不会记得扩展名,你给了他们很好的教训。 - Kamil

4

我认为这是不可能的。 所有已发布的解决方案都无法处理像“test”等字符串。如果它们可以被4整除,不为空或null,并且是有效的base64字符,则它们将通过所有测试。这可能是许多字符串...

因此,没有真正的解决方案,除非知道这是一个base64编码的字符串。我想到的是这个:

if (base64DecodedString.StartsWith("<xml>")
{
    // This was really a base64 encoded string I was expecting. Yippie!
}
else
{
    // This is gibberish.
}

我期望解码后的字符串以某种结构开头,因此我会进行检查。


1
这应该是被接受的答案,因为其他答案会误导并导致许多错误的结果。 - Dimitri Troncquo

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