Как проверить действительную строку с кодировкой Base64

Есть ли способ в С#, чтобы узнать, не закодирована ли строка Base 64, кроме как просто ее конвертировать и посмотреть, есть ли ошибка? У меня есть код такой:

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

Я хочу исключить исключение "Недопустимый символ в строке Base-64", если это значение недопустимо. Я хочу просто проверить и вернуть false вместо обработки исключения, потому что я ожидаю, что иногда это значение не будет базой 64. Есть ли способ проверить перед использованием функции Convert.FromBase64String?

Спасибо!

Update:
Спасибо за все ваши ответы. Вот метод расширения, который вы все можете использовать до сих пор, кажется, что ваша строка будет передавать Convert.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 быстрее проверять, чем перехватывать исключение, пока вы не достигнете определенной длины. Чем меньше длина, тем быстрее

В моем очень ненаучном тестировании: Для 10000 итераций для длины символа 100 000 - 110000 вначале это было в 2,7 раза быстрее.

Для 1000 итераций для символов длиной 1 - 16 символов для общей сложности 16 000 тестов это было в 10,9 раза быстрее.

Я уверен, что есть точка, где лучше тестировать метод, основанный на исключении. Я просто не знаю, в какой момент.

Ответ 1

Довольно легко распознать строку Base64, так как она будет состоять только из символов 'A'..'Z', 'a'..'z', '0'..'9', '+', '/', и она в конце концов дополняется до двух "=", чтобы сделать длину кратной 4. Но вместо этого сравнивая их, вы бы лучше проигнорировали исключение, если это произойдет.

Ответ 2

Я знаю, ты сказал, что не хотел ловить исключение. Но, поскольку ловить исключение более надежно, я отправлю этот ответ.

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

Обновление: Я обновил состояние благодаря ойбеку для дальнейшего повышения надежности.

Ответ 3

Я считаю, что регулярное выражение должно быть:

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

Соответствует только одному или двум знакам "=", а не трем.

s должна быть строкой, которая будет проверяться. Regex является частью пространства имен System.Text.RegularExpressions.

Ответ 4

Почему бы просто не поймать исключение и не вернуть False?

Это позволяет избежать дополнительных накладных расходов в общем случае.

Ответ 5

Просто для полноты я хочу обеспечить некоторую реализацию. Вообще говоря, Regex - дорогостоящий подход, особенно если строка большая (что происходит при передаче больших файлов). Следующий подход сначала использует самые быстрые способы обнаружения.

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

Ответ 6

Ответ должен зависеть от использования строки. Существует много строк, которые могут быть "действительными base64" в соответствии с синтаксисом, предложенным несколькими плакатами, но которые могут "правильно" декодировать без исключения нежелательные сообщения. Пример: строка 8char Portland действительна Base64. Какой смысл заявлять, что это действительно Base64? Я предполагаю, что в какой-то момент вы захотите узнать, что эта строка должна или не должна быть декодирована Base64.

В моем случае у меня есть строки соединения Oracle, которые могут быть в виде обычного текста, например:

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

или в base64, например

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

Мне просто нужно проверить наличие точки с запятой, потому что это доказывает, что это НЕ base64, что, конечно, быстрее, чем любой вышеописанный метод.

Ответ 7

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

Ответ 8

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

Ответ 9

Я буду использовать это так, чтобы мне не нужно снова вызвать метод 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;
    }

Ответ 10

Представленное решение не работает, см. изображение ниже.

Значение "9230" не является строкой base64, а регулярное выражение возвращает TRUE.

enter image description here

Ответ 11

Я бы предложил создать регулярное выражение для выполнения задания. Вам нужно будет проверить что-то вроде этого: [a-zA-Z0-9 +/=] Вам также нужно будет проверить длину строки. Я не уверен в этом, но я уверен, что если что-то обрезается (кроме прокладки "=" ), он взорвется.

Или еще лучше проверить fooobar.com/questions/82598/...

Ответ 12

Конечно. Просто убедитесь, что каждый символ находится в пределах a-z, a-z, 0-9, / или +, а строка заканчивается на ==. (По крайней мере, это самая распространенная реализация Base64. Вы можете найти некоторые реализации, которые используют символы, отличные от / или + для двух последних символов.)

Ответ 13

Да, поскольку Base64 кодирует двоичные данные в строки ASCII с использованием ограниченного набора символов, вы можете просто проверить его с помощью этого регулярного выражения

/^ [A-Za-z0-9\=\+ \/\ с\п] + $/с

который гарантирует, что строка содержит только A-Z, a-z, 0-9, '+', '/', '=' и пробелы.

Ответ 14

Мне нравится идея регулярного выражения. Регулярные выражения могут быть быстрыми, и время от времени сохраняйте накладные расходы на кодирование. Первоначальный запрос, имел обновление, которое сделало именно это. Я считаю, что я никогда не могу предположить, что строки не будут равны нулю. Я бы расширил функцию Extension, чтобы проверить исходную строку для нулевых или простых пробелов.

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

    }

Ответ 15

У меня только что было очень похожее требование, когда я разрешаю пользователю делать некоторые манипуляции с изображениями в элементе <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().

Ответ 16

Имхо это не реально. Все опубликованные решения не работают для строк, таких как "тест" и т.д. Если их можно разделить на 4, они не являются нулевыми или пустыми, и если они являются допустимым символом base64, они пройдут все тесты. Это может быть много строк...

Таким образом, нет реального решения, кроме , зная, что это строковая кодировка с базой 64. Я придумал следующее:

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

Я ожидаю, что декодированная строка начнется с определенной структуры, поэтому я проверю это.

Ответ 17

Используйте Convert.TryFromBase64String из С# 7.2

public static bool IsBase64String(string base64)
{
   Span<byte> buffer = new Span<byte>(new byte[base64.Length]);
   return Convert.TryFromBase64String(base64, buffer , out int bytesParsed);
}