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



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



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


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



спасибо!



обновление:

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



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

1000   15  

15 ответов:

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

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

public static bool IsBase64(this string base64String) {
     // Credit: oybek https://stackoverflow.com/users/794764/oybek
     if (base64String== null || base64String.Length == 0 || 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;
}

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

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

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

только соответствие один или два трейлинг ' = ' знаки, а не три.

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

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

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

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

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

EDIT

как было предложено Сэм, вы также можете немного изменить исходный код. Он обеспечивает более эффективный подход для последнего шага тесты. Рутина

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

полный исходный код с аксессуарами от Сэм будет выглядеть так (удалены комментарии для ясности)

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

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

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

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

или в base64 как

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

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

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

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

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

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

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

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

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

или еще лучше проверить этот вопрос stackoverflow

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

    }

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

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

таким образом, нет никакого реального решения, кроме зная, что это в кодировке base 64 строку. Вот что я придумал:

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

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

Comments

    Ничего не найдено.