Як перевірити правильну кодовану рядок Base64


127

Чи є спосіб в C # перевірити, чи є рядок закодованою Base 64, крім того, щоб просто перетворити її і побачити, чи є помилка? У мене такий код коду:

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

Я хочу уникати винятку "Недійсний символ у рядку Base-64", який трапляється, якщо значення не є дійсним базовим 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 ітерацій довжиною символу 100 000 - 110000 було тестувати в 2,7 рази швидше.

За 1000 ітерацій символів довжиною 1 - 16 символів для загальної кількості 16000 тестів це було в 10,9 разів швидше.

Я впевнений, що є момент, коли краще перевірити метод, заснований на винятках. Я просто не знаю, в якому моменті це.


1
Це залежить від того, наскільки "ретельним" ви хочете перевірити. Ви можете використовувати деяку попередню перевірку, використовуючи регулярний вираз, як відповіли інші, але це не єдиний показник. Кодування base64 вимагає в деяких випадках прокладки за допомогою =знака. Якщо підкладка помилкова, це призведе до помилки, навіть якщо вхід відповідає виразу.
vcsjones

1
Ваш стан не задовольняє виключно базових 64 рядків. Розгляньте рядок \n\fLE16- ваш метод дасть помилковий позитив для цього. Для всіх, хто читає та шукає бездоганний метод; Я рекомендую вловлювати FormatException або використовувати відповідний RegEx, див. Stackoverflow.com/questions/475074/… .
зведена

якщо вищевказаний метод повертає значення false, як я можу прокладати рядок до потрібної довжини?
Пол Олександр

3
Я вважаю, що RegEx повинен бути@"^[a-zA-Z0-9\+/]*={0,2}$"
азатар

Це рішення не є надійним. Він не вдається, якщо ви додасте 4 однакових символьних рядка.
Bettimms

Відповіді:


49

Розпізнати рядок Base64 досить просто, оскільки він буде складатися лише з символів, 'A'..'Z', 'a'..'z', '0'..'9', '+', '/'і він часто вкладається в кінці до трьох '=', щоб довжина була кратною 4. Але замість того, щоб порівнювати їх, ви ' Буде краще ігнорувати виняток, якщо він стався.


1
Я думаю, ви на правильному шляху. Я зробив кілька тестувань, і, здається, це кратні 4, а не 3.
Кріс Маллінз

1
Для успішного кодування його довжина повинна бути кратною 3, на момент кодування! Вибачте з цього приводу ... і так, ви маєте рацію ... Зашифрована рядок має довжину, кратну 4. Ось чому ми б додали до 3 '='.
Аніруд Рамананат

4
Позначено правильним, оскільки ви вперше згадали про кілька речей. Я оновив своє запитання реалізацією рішення, повідомте мені, чи бачите ви з цим проблеми.
Кріс Маллінз

47

Використовуйте 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;
}

Оновлення: я оновив умову завдяки oybek для подальшого підвищення надійності.


1
base64String.Containsбагаторазовий виклик може призвести до низької продуктивності, якщо base64Stringбуде велика струна.
NucS

@NucS Ви маєте рацію, тут ми можемо використовувати компільований регулярний вираз.
harsimranb

1
ви можете перевіритись base64String== null || base64String.Length == 0уstring.IsNullOrEmpty(base64String)
Даніель Тулп

Зауважте, що Base64 може містити пробіли (наприклад, розриви рядків) без проблем. Вони ігноруються парсером.
Тимофій

2
Оскільки ми маємо доступ до вихідного коду .NET, тепер ми можемо бачити, що функція FromBase64String () робить усі ці перевірки. referenceource.microsoft.com/#mscorlib/system/… Якщо це дійсна строка base64, ви перевіряєте її двічі. Можливо, шепає просто спробувати виловити виняток.
iheartcsharp

16

Я вважаю, що регулярний вираз повинен бути:

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

Порівнюється лише один або два знаки "=", а не три.

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


1
не перевіряє, чи довжина рядка мода 4 = 0
calingasan

7

Чому б просто не зловити виняток і повернути False?

Це дозволяє уникнути додаткових накладних витрат у загальній справі.


1
Це незвичайний випадок, я думаю, де я буду використовувати значення, швидше за все, це не буде база 64, тому я б краще уникнути накладних винятків. Це набагато швидше перевірити раніше. Я намагаюся перетворити стару систему, яку я успадкував з ясних текстових паролів, у хешовані значення.
Кріс Маллінз

2
Регулярні вирази ніколи не бувають швидшими за те, що пропонує Тайлер.
Вінсент Коман

Дивіться коментар внизу мого допису. Я думаю, що залежно від довжини рядків, з якими ви працюєте, можна швидше перевірити спочатку, особливо для невеликих рядків, таких як хешовані паролі. Рядок повинен бути кратним 4, щоб навіть дістатися до регулярного вираження, і тоді регулярний вираз на невеликій струні швидше, ніж на дуже великій.
Кріс Маллінз

2
У ідеальному світі не слід писати код, бізнес-логіка якого розроблена або, як відомо, кидає винятки. Блок спробу / лову винятку є надто дорогим, щоб використовуватись як блок прийняття рішення.
Ісмаїл Говейел

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

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

Повний вихідний код із вдосконаленнями від 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;
    }
}

4

Відповідь повинна залежати від використання рядка. Є багато рядків, які можуть бути "дійсними base64" відповідно до синтаксису, запропонованого декількома плакатами, але вони можуть "правильно" розшифрувати, без винятку, непотріб. Приклад: рядок 8char Portlandє дійсним Base64. Який сенс стверджувати, що це дійсно Base64? Я думаю, що в якийсь момент ви хочете знати, що цей рядок повинен або не повинен бути розшифрований Base64.

У моєму випадку у мене є рядки зв'язку Oracle, які можуть бути в простому тексті, наприклад:

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

або в base64, як

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

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


Погодьтеся, конкретні випадки також накладають певні додаткові швидкі перевірки. Так само, як і закодовані з'єднання в простому тексті та 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;
            }
        }

чому ви спершу намагаєтеся перетворити, а потім керувати іншими речами
Snr

@Snr ви праві. Я думаю, що це те, що йому потрібно змінити: if (value.EndsWith ("=")) {value = value.Trim (); int mod4 = value.Length% 4; if (mod4! = 0) {return false; } Convert.FromBase64String (значення); повернути правду; } else {return false; }
Вадід хан

2

Я буду використовувати так, щоб мені не потрібно знову викликати метод перетворення

   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

Імхо, це насправді неможливо. Усі розміщені рішення не вдається для рядків типу "тест" тощо. Якщо їх можна розділити на 4, не є нульовими або порожніми, і якщо вони є дійсним символом base64, вони пройдуть усі тести. Це може бути багато рядків ...

Таким чином, немає іншого реального рішення, крім того, щоб знати, що це базовий 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 \ = \ + \ / \ s \ n] + $ / s

яка запевняє, що рядок містить лише AZ, az, 0-9, '+', '/', '=' та пробіли.


Це не завжди вірний спосіб вогню сказати. Base64 робить деякі накладки для вас, використовуючи =символ в кінці. Якщо ця прокладка недійсна, це невірне кодування base64, навіть якщо воно відповідає вашому регулярному вираженню. Ви можете продемонструвати це, знайшовши базовий рядок 64 з 1 або 2 =в кінці, видаливши їх і спробувавши їх розшифрувати.
vcsjones

Я вважаю, що ОП попросили вловлювати нелегальних персонажів, а не, якщо str був законним Base64. Якщо останнє, ви маєте рацію, хоча помилки заміщення в Base64 легше відловлювати за допомогою винятку.
Роб Райш

Неправда, принаймні .Net версія base64 аналізатор повністю ігнорує прокладку.
Jay

0

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

Або ще краще ознайомитись із цим питанням про stackoverflow


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


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

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

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

    }

Це не вдається. Спробуйте передати рядок із чотирма символами так само, як "aaaa".
Bettimms
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.