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


127

除了只是尝试转换字符串并查看是否存在错误,C#中是否有其他方法可以查看字符串是否为Base 64编码?我有这样的代码:

// Convert base64-encoded hash value into a byte array.
byte[] HashBytes = Convert.FromBase64String(Value);

我想避免如果值不是有效的基数64字符串,则会发生“基数64字符串中的无效字符”异常。我只想检查并返回false而不是处理异常,因为我希望有时此值不会是基数为64的字符串。在使用Convert.FromBase64String函数之前,有什么方法可以检查吗?

谢谢!

更新:
感谢您的所有回答。这是迄今为止您可以全部使用的扩展方法,似乎可以确保您的字符串毫无例外地通过Convert.FromBase64String。转换为基数64时,.NET似乎会忽略所有尾随空格,因此“ 1234”有效,“ 1234”有效

public static bool IsBase64String(this string s)
{
    s = s.Trim();
    return (s.Length % 4 == 0) && Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None);

}

对于那些对测试性能,捕获性能和异常性能感到疑惑的人来说,在大多数情况下,对于这种基于64位的东西,检查它要比捕获异常更快,直到达到一定长度为止。长度越小越快

在我非常不科学的测试中:对于字符长度100,000-110000的10000次迭代,第一次进行测试的速度提高了2.7倍。

对于1000个字符长度为1到16个字符的迭代,总共进行了16,000次测试,速度提高了10.9倍。

我敢肯定,使用基于异常的方法进行测试会变得更好。我只是不知道那是什么时候。


1
这取决于您希望支票的“彻底”程度。您可以使用正则表达式进行一些预验证,就像其他人回答的那样,但这不是唯一的指标。base64编码在某些情况下需要使用=符号进行填充。如果填充错误,即使输入与表达式匹配,也会产生错误。
vcsjones

1
您的条件不只满足base64字符串。考虑字符串\n\fLE16-您的方法将对此产生误报。对于阅读和寻找万无一失的方法的任何人;我建议捕获FormatException或使用符合规范的RegEx,请参阅stackoverflow.com/questions/475074/…

如果上述方法返回false,如何将字符串填充到正确的长度?
Paul Alexander

3
我认为RegEx应该是@"^[a-zA-Z0-9\+/]*={0,2}$"
azatar

该解决方案不可靠。如果添加4个相同的字符串,它将失败。
Bettimms

Answers:


49

识别Base64字符串非常容易,因为它仅由字符组成,'A'..'Z', 'a'..'z', '0'..'9', '+', '/'并且通常在结尾处最多填充三个'=',以使长度为4的倍数。 d最好忽略异常(如果发生)。


1
我认为您在正确的轨道上。我做了一些测试,似乎它是3。4,而不是倍数
克里斯·穆林

1
在编码时,其长度必须是3的倍数才能成功编码!很抱歉...是的,您是对的...编码的字符串的长度是4的倍数。这就是为什么我们要填充3'='的原因。
Anirudh Ramanathan

4
标记为正确,因为您首先提到了多个问题。我通过解决方案的实现更新了我的问题,如果您发现任何问题,请告诉我。
克里斯·穆林斯

47

从C#7.2使用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);
}

1
我不知道那是一回事。我认为这应该是新的答案,如果使用c#7.2
Chris Mullins

4
只能在.NET的核心2.1+或.NET标准2.1+
赛勒斯

C#是编译器,而TryFromBase64String是.NET Framework的API :)
user960567

对于未填充的字符串,它将返回false,这是一个解决方法:Convert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span<byte>(new byte[base64.Length]), out _)。谢谢。
rvnlord

44

我知道您说过您不想捕获异常。但是,因为捕获异常更可靠,所以我将继续发布此答案。

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更新了条件,以进一步提高可靠性。


1
如果字符串很大,base64String.Contains多次调用可能会导致性能下降base64String
NucS 2015年

@NucS是的,我们可以在这里使用编译后的正则表达式。
harsimranb

1
您可以base64String== null || base64String.Length == 0通过string.IsNullOrEmpty(base64String)
-DaniëlTulp

请注意,Base64可以包含空格(例如换行符),而不会出现问题。解析器将忽略它们。
蒂莫西

2
由于我们现在可以访问.NET源代码,因此我们可以看到FromBase64String()函数执行所有这些检查。referencesource.microsoft.com/#mscorlib/system/…如果它是有效的base64字符串,则需要检查两次。尝试/捕获异常可能更有益。
iheartcsharp

16

我相信正则表达式应该是:

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

仅匹配一个或两个结尾的“ =”符号,而不匹配三个。

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


1
不检查字符串长度是否为4 = 0的mod
calingasan

7

为什么不只捕获异常并返回False?

在通常情况下,这避免了额外的开销。


1
这是一种不寻常的情况,我想我将在哪里使用该值,而不是不是以64为底,所以我宁愿避免该异常的开销。之前检查要快得多。我正在尝试将继承自明文密码的旧系统转换为哈希值。
克里斯·穆林斯

2
正则表达式永远不会比Tyler所建议的快。
文森特·科曼

请参阅我的文章底部的评论。我认为,根据所使用的字符串的长度,首先进行测试可能会更快,尤其是对于哈希密码这样的小字符串而言。字符串必须是4的倍数才能到达正则表达式,然后小字符串的正则表达式比大字符串的正则表达式快。
克里斯·穆林斯

2
在一个完美的世界中,不应编写业务逻辑经过设计或已知会引发异常的代码。异常try / catch块太昂贵,无法用作决策块。
Ismail Hawayel '18

7

为了完整起见,我想提供一些实现。通常来说,正则表达式是一种昂贵的方法,尤其是当字符串很大时(在传输大文件时会发生这种情况)。以下方法首先尝试最快的检测方法。

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;
    }
}

4

答案必须取决于字符串的用法。根据一些发布者建议的语法,有很多字符串可能是“有效的base64”,但是可以毫无例外地“正确”解码为垃圾。示例:8char字符串Portland是有效的Base64。指出这是有效的Base64有什么意义?我猜想在某个时候您可能想知道该字符串是否应该被Base64解码。

就我而言,我有可能是纯文本格式的Oracle连接字符串,例如:

Data source=mydb/DBNAME;User Id=Roland;Password=.....`

或在base64之类的

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

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


同意,具体情况还强加了某些额外的快速检查。就像纯文本connectionstring vs base64编码一样。
奥伊贝克2014年

2

Knibb高高的足球规则!

这应该是相对快速和准确的,但是我承认我并没有通过全面的测试。

它避免了昂贵的异常,正则表达式,还避免了循环遍历字符集,而使用了ascii范围进行验证。

public static bool IsBase64String(string s)
    {
        s = s.Trim();
        int mod4 = s.Length % 4;
        if(mod4!=0){
            return false;
        }
        int i=0;
        bool checkPadding = false;
        int paddingCount = 1;//only applies when the first is encountered.
        for(i=0;i<s.Length;i++){
            char c = s[i];
            if (checkPadding)
            {
                if (c != '=')
                {
                    return false;
                }
                paddingCount++;
                if (paddingCount > 3)
                {
                    return false;
                }
                continue;
            }
            if(c>='A' && c<='z' || c>='0' && c<='9'){
                continue;
            }
            switch(c){ 
              case '+':
              case '/':
                 continue;
              case '=': 
                 checkPadding = true;
                 continue;
            }
            return false;
        }
        //if here
        //, length was correct
        //, there were no invalid characters
        //, padding was correct
        return true;
    }

2
public static bool IsBase64String1(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return false;
            }
            try
            {
                Convert.FromBase64String(value);
                if (value.EndsWith("="))
                {
                    value = value.Trim();
                    int mod4 = value.Length % 4;
                    if (mod4 != 0)
                    {
                        return false;
                    }
                    return true;
                }
                else
                {

                    return false;
                }
            }
            catch (FormatException)
            {
                return false;
            }
        }

为什么您首先尝试转换然后控制其他东西
Snr

@Snr,你是对的。我认为这是他需要更改的内容:if(value.EndsWith(“ =”)){value = value.Trim(); int mod4 = value.Length%4; 如果(mod4!= 0){返回false; } Convert.FromBase64String(value); 返回true;} else {返回false;}
Wajid khan

2

我将这样使用,这样我就不需要再次调用convert方法

   public static bool IsBase64(this string base64String,out byte[] bytes)
    {
        bytes = null;
        // Credit: oybek http://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
        {
             bytes=Convert.FromBase64String(base64String);
            return true;
        }
        catch (Exception)
        {
            // Handle the exception
        }

        return false;
    }

2

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

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;
}

1

恕我直言,这实际上是不可能的。对于诸如“ test”之类的字符串,所有发布的解决方案均失败。如果它们可以除以4,则不能为null或为空,并且它们是有效的base64字符,则它们将通过所有测试。那可能是很多字符串...

因此,除了知道这是一个基数为64的编码字符串之外没有其他解决方案。我想出的是:

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

我希望解码后的字符串以某种结构开头,所以我检查一下。


0

当然。只要确保每个字符内a-zA-Z0-9/,或+,并以字符串结束==。(至少,这是最常见的Base64编码实现,你可能会发现一些实现从使用不同的字符/+最后两个字符)。


据我了解,结尾字符取决于编码文本的最终长度。因此,如果编码文本的长度不是%4,则包含'='。
拉斐尔·迭戈·尼科莱蒂

0

是的,由于Base64使用一组有限的字符将二进制数据编码为ASCII字符串,因此您可以使用以下正则表达式对其进行简单检查:

/ ^ [A-Za-z0-9 \ = \ + \ / \ s \ n] + $ / s

这将确保字符串仅包含AZ,az,0-9,'+','/','='和空白。


这并非总是可以肯定的方式。Base64 =在结尾使用字符为您做一些填充。如果该填充无效,则即使它与您的正则表达式匹配,也不是正确的base64编码。您可以通过查找结尾为1或2的基本64字符串=,删除它们并尝试对其进行解码来进行演示。
vcsjones

我认为OP要求捕获非法字符,而不是str是否合法的Base64。如果是后者,那是正确的,尽管使用例外更容易捕获Base64中的填充错误。
Rob Raisch 2011年

不是真的,至少.Net版本的base64解析器会完全忽略填充。
周杰伦

0

我建议创建一个正则表达式来完成这项工作。您必须检查类似以下内容:[a-zA-Z0-9 + / =]您还必须检查字符串的长度。我不确定这是什么,但是我很确定如果修剪(填充“ =”除外)它会炸掉。

或者更好地检查这个stackoverflow问题


0

我刚刚有一个非常相似的要求,即让用户在<canvas>元素中进行一些图像处理,然后将检索.toDataURL()到的结果图像发送到后端。我想在保存图像之前进行一些服务器验证,并ValidationAttribute使用其他答案中的一些代码实现了:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class Bae64PngImageAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value == null || string.IsNullOrWhiteSpace(value as string))
            return true; // not concerned with whether or not this field is required
        var base64string = (value as string).Trim();

        // we are expecting a URL type string
        if (!base64string.StartsWith("data:image/png;base64,"))
            return false;

        base64string = base64string.Substring("data:image/png;base64,".Length);

        // match length and regular expression
        if (base64string.Length % 4 != 0 || !Regex.IsMatch(base64string, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None))
            return false;

        // finally, try to convert it to a byte array and catch exceptions
        try
        {
            byte[] converted = Convert.FromBase64String(base64string);
            return true;
        }
        catch(Exception)
        {
            return false;
        }
    }
}

如您所见,我期望使用image / png类型的字符串,这是<canvas>使用时返回的默认值.toDataURL()


0

检查Base64或普通字符串

公共布尔IsBase64Encoded(String str)

{

try

{
    // If no exception is caught, then it is possibly a base64 encoded string
    byte[] data = Convert.FromBase64String(str);
    // The part that checks if the string was properly padded to the
    // correct length was borrowed from d@anish's solution
    return (str.Replace(" ","").Length % 4 == 0);
}
catch
{
    // If exception is caught, then it is not a base64 encoded string
   return false;
}

}


0

所有答案均被汇总为1个函数,该函数可确保100%保证其结果准确无误。


1)使用如下功能:

    string encoded = "WW91ckJhc2U2NHN0cmluZw==";
    msgbox("Is string base64=" + IsBase64(encoded));

2)下面是功能:

  public bool IsBase64(string base64String)
    {
        try
        {
            if (!base64String.Length < 1)
            {
                if (!base64String.Equals(Convert.ToBase64String(Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(Convert.FromBase64String(base64String)))), StringComparison.InvariantCultureIgnoreCase) & !System.Text.RegularExpressions.Regex.IsMatch(base64String, @"^[a-zA-Z0-9\+/]*={0,2}$"))
                {
                    return false;
                    return;
                }
                if ((base64String.Length % 4) != 0 || string.IsNullOrEmpty(base64String) || base64String.Length % 4 != 0 || base64String.Contains(" ") || base64String.Contains(Constants.vbTab) || base64String.Contains(Constants.vbCr) || base64String.Contains(Constants.vbLf))
                {
                    return false;
                    return;
                }
            }
            else
            {
                return false;
                return;
            }

            return true;
            return;
        }
        catch (FormatException ex)
        {
            return false;
            return;
        }
    }

-1

我喜欢正则表达式检查的想法。正则表达式可以很快,并且有时可以节省编码开销。原始查询已进行了更新。但是,我发现我永远不能假设字符串不会为null。我将扩展扩展功能以检查源字符串是否为null或仅包含空格的字符。

    public static bool IsBase64String(this string s)
    {
        if (string.IsNullOrWhiteSpace(s))
            return false;

        s = s.Trim();
        return (s.Length % 4 == 0) && Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None);

    }

这失败了。尝试传递一个包含4个字符的字符串,例如'aaaa'。
Bettimms
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.