【问题标题】:How to check for a valid Base64 encoded string如何检查有效的 Base64 编码字符串
【发布时间】:2023-03-23 00:05:01
【问题描述】:

除了尝试转换字符串并查看是否有错误之外,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 函数之前有什么方法可以检查吗?

谢谢!

更新:
感谢您的所有回答。这是迄今为止您都可以使用的扩展方法,它似乎确保您的字符串将毫无例外地通过 Convert.FromBase64String。 .NET 在转换为 base 64 时似乎忽略了所有尾随和结束空格,因此“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 倍。

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

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

【问题讨论】:

  • 这取决于您希望检查的“彻底”程度。您可以使用其他人回答的正则表达式进行一些预验证,但这不是唯一的指标。在某些情况下,base64 编码需要使用 = 符号进行填充。如果填充错误,即使输入匹配表达式也会出错。
  • 您的条件不完全满足 base64 字符串。考虑字符串\n\fLE16 - 您的方法会为此产生误报。对于任何阅读和寻找万无一失的方法的人;我建议捕获 FormatException 或使用符合规范的 RegEx,请参阅 stackoverflow.com/questions/475074/…
  • 如果上面的方法返回false,如何将字符串填充到正确的长度?
  • 我认为正则表达式应该是@"^[a-zA-Z0-9\+/]*={0,2}$"
  • 此解决方案不可靠。如果添加 4 个相同的字符串,它会失败。

标签: c# validation base64


【解决方案1】:

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

或者更好的是查看this stackoverflow question

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 如果我理解的话,结束字符取决于编码文本的最终长度。因此,如果编码文本的长度不是 % 4,则包含 '='。
    【解决方案3】:

    更新:对于较新版本的 C#,有更好的选择,请参阅下面 Tomas 的回答。


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

    【讨论】:

    • 我认为你是在正确的轨道上。我做了一些测试,它似乎是 4 而不是 3 的倍数。
    • 它的长度需要在编码时是3的倍数,才能编码成功!抱歉...是的,你是对的...编码字符串的长度是 4 的倍数。这就是为什么我们最多填充 3 个 '=' 。
    • 标记为正确,因为您首先提到了多个事物。我用解决方案的实施更新了我的问题,如果您发现任何问题,请告诉我。
    • 这个方法不行!几年后我找到了。用简单的值检查它test
    • 内边距最多为 2 '='。那是因为只转换一个字节(8 位)会以 2 个 base64 字符和 2 个 '=' 填充结束。如果您不相信我,请尝试在末尾找到一个带有 3 '=' 的示例。
    【解决方案4】:

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

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

    这将确保字符串仅包含 A-Z、a-z、0-9、'+'、'/'、'=' 和空格。

    【讨论】:

    • 这并不总是一种可靠的判断方式。 Base64 在末尾使用 = 字符为您做一些填充。如果该填充无效,则它不是正确的 base64 编码,即使它与您的正则表达式匹配。您可以通过找到一个结尾为 1 或 2 = 的 base 64 字符串、删除它们并尝试对其进行解码来进行演示。
    • 我相信 OP 要求捕获非法字符,而不是 str 是否合法 Base64。如果是后者,那么您是正确的,尽管 Base64 中的填充错误更容易使用异常来捕获。
    • 不正确,至少 .Net 版本的 base64 解析器完全忽略了填充。
    【解决方案5】:

    为什么不直接捕获异常,然后返回 False?

    这避免了常见情况下的额外开销。

    【讨论】:

    • 这是一个不寻常的情况,我想我将在哪里使用该值更有可能不是基数 64,所以我宁愿避免异常的开销。之前检查要快得多。我正在尝试将从明文密码继承的旧系统转换为哈希值。
    • 正则表达式永远不会比 Tyler 建议的更快。
    • 查看我帖子底部的评论。我认为根据您正在使用的字符串的长度,首先测试它可能会更快,特别是对于像散列密码这样的小字符串。字符串必须是 4 的倍数才能到达正则表达式,然后小字符串上的正则表达式比非常大的字符串上更快。
    • 在一个完美的世界中,不应编写业务逻辑被设计或已知会引发异常的代码。异常 try/catch 块太昂贵,不能用作决策块。
    【解决方案6】:

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

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

    【讨论】:

    • 多次调用base64String.Contains 可能会导致性能不佳base64String 是一个大字符串。
    • @NucS 你说得对,我们可以在这里使用已编译的正则表达式。
    • 你可以用string.IsNullOrEmpty(base64String)检查base64String== null || base64String.Length == 0
    • 请注意,Base64 可以包含空格(例如换行符)而不会出现问题。解析器会忽略它们。
    • 由于我们现在可以访问 .NET 源代码,我们可以看到 FromBase64String() 函数完成了所有这些检查。 referencesource.microsoft.com/#mscorlib/system/… 如果它是有效的 base64 字符串,那么您将检查它两次。尝试/捕获异常可能更便宜。
    【解决方案7】:

    答案必须取决于字符串的用法。根据几位海报建议的语法,有许多字符串可能是“有效的 base64”,但可能“正确”解码为垃圾,无一例外。示例:8char 字符串 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,这当然比上述任何方法都快。

    【讨论】:

    • 同意,案例细节还强制执行某些额外的快速检查。就像纯文本连接字符串与 base64 编码一样。
    • 完全同意。根据此处提到的方法,任何偶数长度的 ASCII 字符串都将注册为 base64。 “TEST”将注册为有效的 base64,并且在自动补偿缺少的填充时,“TEST1”也将如此。对于 base64 编码,确实没有一种全面的测试方法。这应该是公认的答案,因为其他答案具有误导性,会导致许多误报。
    【解决方案8】:

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

    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 的增强功能的完整源代码将如下所示(为清楚起见,删除了 cmets)

    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 字符串吗?您的代码假定它是有效的。但我不确定。
    • 是的。可以将字符串ffff 解码为有效的字节数组。
    【解决方案9】:

    Knibb High 足球规则!

    这应该是相对快速和准确的,但我承认我没有通过彻底的测试,只是一些。

    它避免了昂贵的异常、正则表达式,还避免了循环遍历字符集,而是使用 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;
        }
    

    【讨论】:

      【解决方案10】:
      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 你是对的。我认为这是他需要改变的: if (value.EndsWith("=")) { value = value.Trim(); int mod4 = value.Length % 4; if (mod4 != 0) { return false; } Convert.FromBase64String(value);返回真; } 否则 { 返回假; }
      【解决方案11】:

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

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

      只匹配一个或两个尾随 '=' 符号,而不是三个。

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

      【讨论】:

      • 不检查字符串长度是否为 mod of 4 = 0
      【解决方案12】:

      我会这样使用,这样我就不需要再调用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;
          }
      

      【讨论】:

        【解决方案13】:

        我刚刚有一个非常相似的要求,我让用户在&lt;canvas&gt; 元素中进行一些图像处理,然后将使用.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;
                }
            }
        }
        

        如您所见,我期待一个图像/png 类型的字符串,这是使用.toDataURL()&lt;canvas&gt; 返回的默认值。

        【讨论】:

          【解决方案14】:

          恕我直言,这不太可能。所有发布的解决方案对于 "test" 等字符串都失败了。如果它们可以被 4 整除,不为 null 或为空,并且是有效的 base64 字符,则它们将通过所有测试。那可以是很多字符串...

          所以除了知道这是一个 base 64 编码的字符串之外没有真正的解决方案。我想出的是这样的:

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

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

          【讨论】:

          • 这应该是公认的答案,因为其他答案具有误导性,会导致许多误报。
          【解决方案15】:

          使用 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);
          }
          

          【讨论】:

          • 我不知道那是一回事。我认为这应该是新的答案,如果使用 c# 7.2
          • 仅适用于 .NET Core 2.1+ 或 .NET Standard 2.1+
          • C# 是编译器,TryFromBase64String 是 .NET 框架的 API :)
          • 这将为非填充字符串返回 false,这是一个修复:Convert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span&lt;byte&gt;(new byte[base64.Length]), out _)。谢谢。
          • 答案对每个用例都有用。您应该以正确的格式发送参数。
          【解决方案16】:

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

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

          【讨论】:

            【解决方案17】:

            检查 Base64 或普通字符串

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

            【讨论】:

              【解决方案18】:

              所有答案都被消化成 1 个函数,确保其结果 100% 准确。

              1) 使用功能如下:

              string encoded = "WW91ckJhc2U2NHN0cmluZw==";
              Console.WriteLine("Is string base64=" + IsBase64(encoded));
              

              2) 下面是函数:

              public bool IsBase64(string base64String)
              {
                  try
                  {
                      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;
                      }
                      else 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;
                      }
                      else return true;
                  }
                  catch (FormatException ex)
                  {
                      return false;
                  }
              }
              

              【讨论】:

              • @Pipe 那时我是菜鸟。请立即查看。
              • 那更好;)
              【解决方案19】:

              我更喜欢这种用法:

                  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://stackoverflow.com/questions/6309379/how-to-check-for-a-valid-base64-encoded-string
                          Span<byte> buffer = new Span<byte>(new byte[base64.Length]);
                          return Convert.TryFromBase64String(base64, buffer, out int _);
                      }
                  }
              

              然后使用

              if(myStr.IsBase64String()){
              
                  ...
              
              }
              

              【讨论】:

              • 这是最好的方法。人们不记得扩展,你给了他们很好的教训。
              【解决方案20】:

              我只是想指出,迄今为止没有一个答案非常有用(取决于您的用例,但对我来说是裸露的)。

              对于长度可被 4 整除且不包含空格的字符串,它们都会返回误报。 如果您调整缺少的填充,则 [aA-zZ0-9]+ 范围内的所有字符串都将注册为 base64 编码。

              无论您检查有效字符和长度,还是使用 Exception 或 TryConvert 方法,所有这些方法都会返回误报

              一些简单的例子:

              • "test" 将注册为 base64 编码
              • "test1" 将注册为 base64 编码,如果您调整缺少的填充(尾随 '=')
              • "test test"永远注册为 base64 编码
              • "tést"永远注册为 base64 编码

              我并不是说这里描述的方法没有用,但是您应该在生产环境中使用这些方法之前了解这些限制

              【讨论】:

                猜你喜欢
                • 2012-01-24
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-02-19
                • 2011-10-31
                • 2011-05-15
                相关资源
                最近更新 更多