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

127

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

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

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

Спасибо!

Обновление:
спасибо за все ваши ответы. Вот метод расширения, который вы все можете использовать до сих пор, он, кажется, гарантирует, что ваша строка будет передавать 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 итераций для длины символов 100000 - 110000 сначала было в 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 04
Это решение ненадежно. Это не сработает, если вы добавите строку из 4 одинаковых символов.
Bettimms

Ответы:

49

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

Анируд Раманатан
источник
1
Я думаю, вы на правильном пути. Я провел небольшое тестирование, и оказалось, что оно кратно 4, а не 3.
Крис Маллинс
1
Для успешного кодирования его длина должна быть кратна 3 во время кодирования! Извините за это ... и да, вы правы ... Закодированная строка имеет длину, кратную 4. Вот почему мы добавили до 3 '='.
Anirudh Ramanathan
4
Отмечено правильно, потому что вы первым упомянули о множественности. Я обновил свой вопрос реализацией решения, дайте мне знать, если вы заметите какие-либо проблемы с ним.
Крис Маллинс
48

Используйте Convert.TryFromBase64String из C # 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);
}
Томаш Кубес
источник
1
Я не знал, что это так. Я думаю, что это должен быть новый ответ при использовании C # 7.2
Крис Маллинс
4
Работает только в .NET Core 2.1+ или .NET Standard 2.1+
Сайрус,
C # - это компилятор, а TryFromBase64String - это API .NET framework :)
user960567
Это вернет ложь для НЕДОПОЛНЯЮЩИХ строк, здесь ошибки : 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;
}

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

harsimranb
источник
1
base64String.Containsмногократный вызов может привести к снижению производительности base64String, если это большая строка.
NucS
@NucS Вы правы, здесь можно использовать скомпилированное регулярное выражение.
harsimranb
1
вы можете проверить base64String== null || base64String.Length == 0сstring.IsNullOrEmpty(base64String)
Даниэль
Обратите внимание, что Base64 может без проблем содержать пробелы (например, разрывы строк). Парсер игнорирует их.
Тимоти
2
Поскольку теперь у нас есть доступ к исходному коду .NET, мы можем видеть, что функция FromBase64String () выполняет все эти проверки. linksource.microsoft.com/#mscorlib/system/… Если это допустимая строка base64, вы проверяете ее дважды. Возможно, проще просто попробовать / поймать исключение.
iheartcsharp
16

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

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

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

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

jazzdev
источник
2
не проверяет, является ли длина строки по модулю 4 = 0
calingasan
7

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

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

Тайлер Ивз
источник
1
Это необычный случай, я думаю, где я собираюсь использовать значение, скорее всего, не будет базовым 64, поэтому я бы предпочел избежать накладных расходов на исключение. Это намного быстрее проверить раньше. Я пытаюсь преобразовать старую систему, которую я унаследовал от паролей с открытым текстом, в хешированные значения.
Крис Маллинс
2
Регулярные выражения никогда не бывают быстрее, чем предлагает Тайлер.
Винсент Куман
См. Комментарий внизу моего сообщения. Я думаю, что в зависимости от длины строк, с которыми вы работаете, может быть быстрее сначала протестировать, особенно для небольших строк, таких как хешированные пароли. Строка должна быть кратной 4, чтобы даже добраться до регулярного выражения, и тогда регулярное выражение для небольшой строки выполняется быстрее, чем для очень большой строки.
Chris Mullins
2
В идеальном мире не следует писать код, бизнес-логика которого разработана или заведомо генерирует исключения. Блок исключений try / catch слишком дорог для использования в качестве блока принятия решений.
Исмаил Хавайель
7

Просто для полноты картины хочу предложить некоторую реализацию. Вообще говоря, 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'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 (!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;
    }
}
Ойбек
источник
4

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

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

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

или в base64 вроде

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

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

Roland
источник
Согласитесь, специфика дела также требует определенных дополнительных быстрых проверок. Точно так же, как строка подключения открытого текста против закодированной base64.
Ойбек
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;
            }
        }
user3181503
источник
почему вы сначала пытаетесь преобразовать, а затем контролировать другие вещи
Снр,
@Snr, ты прав. Я думаю, что это то, что ему нужно изменить: if (value.EndsWith ("=")) {value = value.Trim (); int mod4 = value.Length% 4; если (mod4! = 0) {вернуть ложь; } Convert.FromBase64String (значение); вернуть истину; } else {вернуть ложь; }
Wajid хан
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, не являются нулевыми или пустыми, и если они являются допустимым символом base64, они пройдут все тесты. Это может быть много строк ...

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

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

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

тестирование
источник
0

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


источник
Если я понял, конечные символы зависят от конечной длины закодированного текста. Таким образом, если длина закодированного текста не равна% 4, то включается знак '='.
Рафаэль Диего Николетти
0

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

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

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

Роб Райш
источник
Это не всегда верный способ сказать. Base64 делает для вас отступы, используя =символ в конце. Если это заполнение недействительно, это неправильная кодировка base64, даже если она соответствует вашему регулярному выражению. Вы можете продемонстрировать это, найдя строку base 64 с 1 или 2 =в конце, удалив их и попытавшись декодировать.
vcsjones
Я считаю, что OP попросил перехватить недопустимые символы, а не если str была законной Base64. В последнем случае вы правы, хотя ошибки заполнения в Base64 легче отловить с помощью исключений.
Rob Raisch
Неправда, по крайней мере, версия .Net парсера base64 полностью игнорирует заполнение.
Джей
0

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

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

сойка
источник
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().

germankiwi
источник
0

Проверить 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;
}

}

Навдип Капил
источник
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

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