RegEx do analizowania lub sprawdzania poprawności danych Base64


99

Czy można użyć wyrażenia regularnego do walidacji lub oczyszczenia danych Base64? To proste pytanie, ale czynniki, które napędzają to pytanie, sprawiają, że jest ono trudne.

Mam dekoder Base64, który nie może w pełni polegać na danych wejściowych, aby postępować zgodnie ze specyfikacjami RFC. Tak więc problemy, z którymi się spotykam, to problemy takie jak być może dane Base64, które mogą nie zostać podzielone na 78 (myślę, że to 78, musiałbym dwukrotnie sprawdzić RFC, więc nie daj mi znać, jeśli dokładna liczba jest błędna) znak linie lub że linie nie mogą kończyć się CRLF; w tym, że może mieć tylko CR lub LF, a może żadnego.

Miałem więc cholernie dużo czasu analizując sformatowane w ten sposób dane Base64. Z tego powodu niezawodne dekodowanie przykładów, takich jak poniższe, staje się niemożliwe. Dla zwięzłości wyświetlę tylko częściowe nagłówki MIME.

Content-Transfer-Encoding: base64

VGhpcyBpcyBzaW1wbGUgQVNDSUkgQmFzZTY0IGZvciBTdGFja092ZXJmbG93IGV4YW1wbGUu

Ok, więc parsowanie nie stanowi problemu i jest dokładnie takim wynikiem, jakiego byśmy oczekiwali. W 99% przypadków użycie dowolnego kodu przynajmniej do sprawdzenia, czy każdy znak w buforze jest prawidłowym znakiem base64, działa idealnie. Ale następny przykład rzuca klucz do miksu.

Content-Transfer-Encoding: base64

http://www.stackoverflow.com
VGhpcyBpcyBzaW1wbGUgQVNDSUkgQmFzZTY0IGZvciBTdGFja092ZXJmbG93IGV4YW1wbGUu

Jest to wersja kodowania Base64, którą widziałem w niektórych wirusach i innych rzeczach, które próbują wykorzystać to, że niektórzy czytelnicy poczty chcą analizować mime za wszelką cenę, w przeciwieństwie do tych, które ściśle przestrzegają tej książki, a raczej RFC; Jeśli będziesz.

Mój dekoder Base64 dekoduje drugi przykład do następującego strumienia danych. Pamiętaj, że oryginalny strumień to wszystkie dane ASCII!

[0x]86DB69FFFC30C2CB5A724A2F7AB7E5A307289951A1A5CC81A5CC81CDA5B5C1B19481054D0D
2524810985CD94D8D08199BDC8814DD1858DAD3DD995C999B1BDDC8195E1B585C1B194B8

Czy ktoś ma dobry sposób na rozwiązanie obu problemów jednocześnie? Nie jestem pewien, czy jest to w ogóle możliwe, poza wykonaniem dwóch przekształceń danych z zastosowanymi różnymi regułami i porównaniem wyników. Jeśli jednak przyjmiesz takie podejście, którym wynikom ufasz? Wygląda na to, że heurystyka ASCII jest najlepszym rozwiązaniem, ale o ile więcej kodu, czasu wykonania i złożoności dodałoby to do czegoś tak skomplikowanego, jak skaner wirusów, w który ten kod jest faktycznie zaangażowany? Jak wyszkoliłbyś silnik heurystyczny, aby dowiedzieć się, co jest akceptowalnym Base64, a co nie?


AKTUALIZACJA:

Jeśli chodzi o liczbę wyświetleń, które nadal uzyskuje to pytanie, zdecydowałem się opublikować proste wyrażenie regularne, którego używam w aplikacji C # od 3 lat, z setkami tysięcy transakcji. Szczerze mówiąc, najbardziej podoba mi się odpowiedź udzielona przez Gumbo , dlatego wybrałem ją jako wybraną odpowiedź. Ale dla każdego, kto używa C # i szuka bardzo szybkiego sposobu, aby przynajmniej wykryć, czy ciąg znaków lub bajt [] zawiera prawidłowe dane Base64, czy nie, stwierdziłem, że poniższe elementy działają bardzo dobrze dla mnie.

[^-A-Za-z0-9+/=]|=[^=]|={3,}$

I tak, to jest po prostu na STRING danych Base64, a nie prawidłowo sformatowany RFC1341 wiadomości. Jeśli więc masz do czynienia z danymi tego typu, weź to pod uwagę przed próbą użycia powyższego wyrażenia regularnego. Jeśli masz do czynienia z Base16, Base32, Radix lub nawet Base64 do innych celów (adresy URL, nazwy plików, kodowanie XML itp.), To zdecydowanie zalecamy przeczytanie RFC4648, o którym Gumbo wspomniał w swojej odpowiedzi, ponieważ musisz być zdrowy świadomy zestawu znaków i terminatorów używanych przez implementację przed próbą użycia sugestii w tym zestawie pytań / odpowiedzi.


Myślę, że musisz lepiej zdefiniować zadanie. Nie jest jasne, jaki jest Twój cel: być surowym? przeanalizować 100% próbek? ...
ADEpt

Pierwszym przykładem powinno być 'VGhpcyBpcyBhIHNpbXBsZSBBU0NJSSBCYXNlNjQgZXhhbXBsZSBmb3IgU3RhY2tPdmVyZmxvdy4 ='
jfs

Dlaczego nie skorzystać ze standardowego rozwiązania w swoim języku? Dlaczego potrzebujesz odręcznego parsera opartego na wyrażeniach regularnych?
jfs

1
Świetne pytanie. Chociaż wypróbowałem wyrażenie regularne UPDATE , uruchamiając je na SHA zakodowanym w base64, zwróconym przez NPM i nie udało się, podczas gdy wyrażenie regularne w wybranej odpowiedzi działa dobrze .
Josh Habdas

1
Nie jestem pewien, w jaki sposób wyrażenie regularne UPDATE jest nadal publikowane bez korekty, ale wygląda na to, że autor chciał umieścić ^poza nawiasami klamrowymi, jako kotwicę początkową. Jednak znacznie lepszym wyrażeniem regularnym, bez komplikowania się, jak zaakceptowana odpowiedź, byłoby^[-A-Za-z0-9+/]*={0,3}$
kael

Odpowiedzi:


147

Z RFC 4648 :

Podstawowe kodowanie danych jest używane w wielu sytuacjach do przechowywania lub przesyłania danych w środowiskach, które, być może ze starszych powodów, są ograniczone do danych US-ASCII.

Zależy to więc od celu wykorzystania zakodowanych danych, czy dane te należy uznać za niebezpieczne.

Ale jeśli szukasz tylko wyrażenia regularnego pasującego do słów zakodowanych w standardzie Base64, możesz użyć następującego:

^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$

10
Najprostszym rozwiązaniem byłoby usunięcie wszystkich białych znaków (które są ignorowane zgodnie z RFC) przed walidacją.
Ben Blank,

2
Ostatnia grupa nieprzechwytywana dla wypełnienia jest opcjonalna.
Gumbo,

4
Na początku byłem sceptyczny co do złożoności, ale to całkiem dobrze potwierdza. Jeśli chcesz po prostu dopasować base64-ish, wymyśliłbym zrobienie ^ [a-zA-Z0-9 + /] = {0,3} $, to jest lepsze!
Lodewijk

3
@BogdanNechyporenko To dlatego, że namejest to prawidłowe kodowanie Base64 sekwencji bajtów (szesnastkowej) 9d a9 9e.
Marten

3
^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$musi uciec od reakcji
khizar syed

37
^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$

Ten jest dobry, ale dopasuje pusty ciąg

Ten nie pasuje do pustego ciągu:

^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$

2
Dlaczego pusty ciąg jest nieprawidłowy?
Josh Lee

8
nie jest. ale jeśli używasz wyrażenia regularnego, aby dowiedzieć się, czy dany ciąg jest base64, czy nie, prawdopodobnie nie jesteś zainteresowany pustymi ciągami. Przynajmniej wiem, że nie jestem.
njzk2

4
@LayZee: jeśli to zrobisz, wymuś ciąg base64, aby zawierał co najmniej blok o rozmiarze 4, renderując prawidłowe wartości, takie jak MQ==brak dopasowania do wyrażenia
njzk2

5
@ruslan ani nie powinno. to nie jest prawidłowy ciąg bazowy 64. (rozmiar to 23, czyli nie // 4). AQENVg688MSGlEgdOJpjIUC=jest prawidłową formą.
njzk2

1
@JinKwon base64 kończy się na 0, 1 lub 2 =. Ostatni ?pozwala na 0 =. Zastąpienie go {1}wymaga zakończenia 1 lub 2=
njzk2

4

Ani „ : ”, ani „ . ” Nie pojawią się w prawidłowym Base64, więc myślę, że możesz jednoznacznie odrzucić tę http://www.stackoverflow.comlinię. W Perlu powiedzmy coś takiego

my $sanitized_str = join q{}, grep {!/[^A-Za-z0-9+\/=]/} split /\n/, $str;

say decode_base64($sanitized_str);

może być tym, czego chcesz. Produkuje

To jest prosty przykład ASCII Base64 dla StackOverflow.


Mogę się z tym zgodzić, ale tak się składa, że ​​wszystkie INNE litery w adresie URL są poprawnymi kodami base64 ... Więc gdzie rysujesz tę linię? Tylko na przerwach? (Widziałem takie, w których jest tylko kilka losowych znaków w środku linii. Nie można rzucić reszty linii tylko z tego powodu, IMHO) ...
LarryF

@LarryF: jeśli nie jest sprawdzanie integralności danych zakodowanych w base-64, nie można powiedzieć, co zrobić z jakimkolwiek blokiem danych base-64 zawierającym nieprawidłowe znaki. Jaka jest najlepsza heurystyka: zignoruj ​​nieprawidłowe znaki (dopuszczając wszystkie poprawne), odrzuć wiersze lub odrzuć partię?
Jonathan Leffler

(ciąg dalszy): krótka odpowiedź brzmi „to zależy” - od tego, skąd pochodzą dane i jaki jest w nich bałagan.
Jonathan Leffler

(wznowione): Z komentarzy do pytania wynika, że ​​chcesz zaakceptować wszystko, co może być base-64. Więc po prostu zamapuj każdy znak, który nie znajduje się w twoim alfabecie base-64 (pamiętaj, że istnieją kodowania bezpieczne dla adresów URL i inne tego typu warianty), w tym znaki nowej linii i dwukropki, i weź to, co zostało.
Jonathan Leffler

3

Najlepsze wyrażenie regularne, jakie udało mi się do tej pory znaleźć, jest tutaj https://www.npmjs.com/package/base64-regex

który w aktualnej wersji wygląda następująco:

module.exports = function (opts) {
  opts = opts || {};
  var regex = '(?:[A-Za-z0-9+\/]{4}\\n?)*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)';

  return opts.exact ? new RegExp('(?:^' + regex + '$)') :
                    new RegExp('(?:^|\\s)' + regex, 'g');
};

Może lepiej bez \\n?.
Jin Kwon,

To się nie powiedzie w przypadku ciągów JSON
idleberg

3

Aby sprawdzić poprawność obrazu base64 , możemy użyć tego wyrażenia regularnego

/ ^ data: image / (?: gif | png | jpeg | bmp | webp) (?:; charset = utf-8) ?; base64, (?: [A-Za-z0-9] | [+ /] ) + = {0,2}

  private validBase64Image(base64Image: string): boolean {
    const regex = /^data:image\/(?:gif|png|jpeg|bmp|webp)(?:;charset=utf-8)?;base64,(?:[A-Za-z0-9]|[+/])+={0,2}/;
    return base64Image && regex.test(base64Image);
  }

0

Oto alternatywne wyrażenie regularne:

^(?=(.{4})*$)[A-Za-z0-9+/]*={0,2}$

Spełnia następujące warunki:

  • Długość struny musi być wielokrotnością czterech - (?=^(.{4})*$)
  • Treść musi składać się ze znaków alfanumerycznych lub + lub / - [A-Za-z0-9+/]*
  • Może mieć do dwóch znaków dopełniających (=) na końcu - ={0,2}
  • Akceptuje puste ciągi
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.