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

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

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

我想避免“ Base-64字符串中的无效字符”异常,如果该值不是有效的 Base 64字符串,就会发生这种情况。我只想检查并返回 false,而不是处理异常,因为我预计有时这个值不会是一个基数为64的字符串。在使用 Convert 之前是否有一些方法可以检查。从 Base64String 函数? ?

谢谢!

更新:
谢谢你的回答。这里有一个扩展方法,你可以使用到目前为止,似乎可以确保您的字符串将通过转换。无异常的 FromBase64String。.NET 似乎忽略所有的尾随和结束空格时,转换为基数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倍。

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

197344 次浏览

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

或者最好看看 这个堆栈溢出的问题

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

更新 : 对于 C # 的更新版本,有一个更好的选择,请参考 Tomas 的答案: https://stackoverflow.com/a/54143400/125981


识别 Base64字符串非常容易,因为它只由字符 'A'..'Z', 'a'..'z', '0'..'9', '+', '/'组成,并且通常在字符串的末尾填充多达3个“ =”,使长度为4的倍数。但是,与其比较这些异常,不如忽略异常(如果发生的话)。

是的,因为 基地64使用一组有限的字符将二进制数据编码成 ASCII 字符串,所以您可以简单地用下面的正则表达式检查它:

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

它将确保字符串只包含 A-Z、 A-Z、0-9、’+’、’/’、’=’和空格。

为什么不直接捕获异常并返回 False 呢?

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

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

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

更新: 为了进一步提高可靠性,我已经通过 奥贝克更新了状态。

答案必须取决于字符串的用法。有许多字符串可能是“有效的 base64”根据一些海报建议的语法,但这可能“正确”解码,毫无例外,垃圾。例如: 8char 字符串 Portland是有效的 Base64。说明这是有效的 Base64有什么意义呢?我猜在某些时候您会想知道这个字符串应该或不应该被 Base64解码。

在我的例子中,我从 app.config 文件中读取 Oracle 连接字符串,这些字符串可以是纯文本,比如:

Data source=mydb/DBNAME;User Id=Roland;Password=secret1;

或者像64号基地那样

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

(我的前任将 base64视为加密: -)

为了决定是否需要 base64解码,在这个特定的用例中,我应该简单地检查字符串是否以“ Data”开头(不区分大小写)。这比仅仅尝试解码并查看是否发生异常要容易得多、快得多,而且 更可靠也要快得多:

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

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

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

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

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

剪辑

根据 山姆的建议,您还可以稍微更改源代码。他为测试的最后一步提供了一种性能更好的方法。例行公事

    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 (IsInvalid(value[i]))代替 if (!Base64Chars.Contains(value[i]))线

山姆增强的完整源代码如下所示(为清晰起见,删除了注释)

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

克尼布高中橄榄球队的规则!

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

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

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

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

只匹配一个或两个’=’标志,而不是三个。

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

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

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

我刚刚有一个非常类似的需求,我让用户在一个 <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 类型的字符串,这是使用 .toDataURL()<canvas>返回的默认值。

这是不可能的。所有提交的解决方案对于字符串(如 “测试”等)都失败。如果它们可以被除以4,不为 null 或空,并且如果它们是有效的 base64字符,则它们将通过所有测试。可能有很多条线。

因此,除了 知道这是一个64进制编码的字符串,没有其他真正的解决方案。我想到的是:

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

我希望解码的字符串以特定的结构开始,所以我检查这一点。

使用 C # 7.2中的 转换.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);
}

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

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

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


}

所有的答案都被分解为一个函数,以确保其结果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;
}
}

我更喜欢这个用法:

    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()){


...


}

我只是想指出,到目前为止,没有一个答案是非常有用的(取决于您的用例,但与我无关)。

对于长度可以被4整除但不包含空格的字符串,所有这些函数都会返回假阳性。 如果调整缺少的填充,那么[ aA-zZ0-9] + 范围内的所有字符串都将注册为 base64编码。

无论您是检查有效的字符和长度,还是使用 Exception 或 TryConvert 方法 所有这些方法都返回假阳性,都没有关系。

一些简单的例子:

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

我并不是说这里描述的方法是无用的,但是您应该使用 在生产环境中使用之前,请注意这些限制