Come verificare la presenza di una stringa codificata Base64 valida


127

C'è un modo in C # per vedere se una stringa è codificata in Base 64 oltre al solo tentativo di convertirla e vedere se c'è un errore? Ho un codice come questo:

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

Voglio evitare l'eccezione "Carattere non valido in una stringa Base 64" che si verifica se il valore non è una stringa base 64 valida. Voglio solo controllare e restituire false invece di gestire un'eccezione perché mi aspetto che a volte questo valore non sarà una stringa di base 64. C'è un modo per controllare prima di utilizzare la funzione Convert.FromBase64String?

Grazie!

Aggiornamento:
grazie per tutte le tue risposte. Ecco un metodo di estensione che puoi utilizzare tutti finora sembra assicurarti che la tua stringa passi Convert.FromBase64String senza eccezioni. .NET sembra ignorare tutti gli spazi finali e finali durante la conversione in base 64, quindi "1234" è valido e lo è anche "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);

}

Per coloro che si interrogano sulle prestazioni di test, cattura ed eccezione, nella maggior parte dei casi per questa cosa in base 64 è più veloce controllare che catturare l'eccezione fino a raggiungere una certa lunghezza. Più piccola è la lunghezza più veloce è

Nei miei test molto poco scientifici: per 10000 iterazioni per la lunghezza dei caratteri 100.000 - 110000 è stato 2,7 volte più veloce testare prima.

Per 1000 iterazioni per caratteri di lunghezza compresa tra 1 e 16 caratteri per un totale di 16.000 test, era 10,9 volte più veloce.

Sono sicuro che c'è un punto in cui diventa meglio testare con il metodo basato sulle eccezioni. Non so a che punto sia.


1
Dipende da quanto "accurato" vuoi che sia il controllo. Puoi usare una pre-convalida usando un'espressione regolare come altri hanno risposto, ma questo non è l'unico indicatore. La codifica base64 richiede il riempimento in alcuni casi utilizzando il =segno. Se la spaziatura interna è errata, verrà visualizzato un errore anche se l'input corrisponde a un'espressione.
vcsjones

1
La tua condizione non soddisfa esclusivamente le stringhe base64. Considera la stringa \n\fLE16: il tuo metodo produrrebbe un falso positivo per questo. Per chi legge e cerca un metodo infallibile; Consiglierei di catturare FormatException o di utilizzare una RegEx adatta alle specifiche, vedere stackoverflow.com/questions/475074/… .
nullable

se il metodo sopra restituisce false, come posso riempire la stringa con la lunghezza corretta?
Paul Alexander

3
Credo che la RegEx dovrebbe essere@"^[a-zA-Z0-9\+/]*={0,2}$"
azatar

Questa soluzione non è affidabile. Non riesce se aggiungi la stessa stringa di 4 caratteri.
Bettimms

Risposte:


49

È abbastanza facile riconoscere una stringa Base64, poiché sarà composta solo di caratteri 'A'..'Z', 'a'..'z', '0'..'9', '+', '/'ed è spesso riempita alla fine con un massimo di tre "=", per rendere la lunghezza un multiplo di 4. Ma invece di confrontarli, tu " sarebbe meglio ignorare l'eccezione, se si verifica.


1
Penso che tu sia sulla strada giusta. Ho fatto alcuni test e sembra che siano multipli di 4 invece di 3.
Chris Mullins

1
La sua lunghezza deve essere un multiplo di 3, al momento della codifica, per una codifica corretta! Mi dispiace per questo ... e sì, hai ragione ... La stringa codificata ha una lunghezza che è un multiplo di 4. Ecco perché dovremmo riempire fino a 3 '='.
Anirudh Ramanathan

4
Contrassegnato come corretto perché sei stato il primo a menzionare la cosa multipla. Ho aggiornato la mia domanda con un'implementazione della soluzione fammi sapere se riscontri problemi con essa.
Chris Mullins

48

Utilizzare Convert.TryFromBase64String da 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
Non sapevo che fosse una cosa. Penso che questa dovrebbe essere la nuova risposta, se si utilizza c # 7.2
Chris Mullins

4
Funziona solo in .NET Core 2.1+ o .NET Standard 2.1+
Cyrus

C # è un compilatore e TryFromBase64String è API di .NET framework :)
user960567

Ciò restituirà false per stringhe non imbottito, ecco un fix: Convert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span<byte>(new byte[base64.Length]), out _). Grazie.
rvnlord

44

So che hai detto che non volevi prendere un'eccezione. Ma, poiché catturare un'eccezione è più affidabile, andrò avanti e posterò questa risposta.

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

Aggiornamento: ho aggiornato la condizione grazie a oybek per migliorare ulteriormente l'affidabilità.


1
chiamare base64String.Containspiù volte può comportare prestazioni scadenti in caso di base64Stringstringa di grandi dimensioni.
NucS

@NucS Hai ragione, possiamo usare un'espressione regolare compilata qui.
harsimranb

1
puoi verificare base64String== null || base64String.Length == 0constring.IsNullOrEmpty(base64String)
Daniël Tulp

Nota che un Base64 può contenere spazi (ad esempio interruzioni di riga) senza problemi. Vengono ignorati dal parser.
Timoteo

2
Poiché ora abbiamo accesso al codice sorgente .NET, possiamo vedere che la funzione FromBase64String () esegue tutti questi controlli. Referencesource.microsoft.com/#mscorlib/system/… Se si tratta di una stringa Base64 valida, la si controlla due volte. Forse è meglio provare / catturare l'eccezione.
iheartcsharp

16

Credo che la regex dovrebbe essere:

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

Solo corrispondenza di uno o due segni finali "=", non tre.

sdovrebbe essere la stringa che verrà controllata. Regexfa parte dello System.Text.RegularExpressionsspazio dei nomi.


2
non controlla se la lunghezza della stringa è mod di 4 = 0
calingasan

7

Perché non rilevare l'eccezione e restituire False?

Ciò evita l'overhead aggiuntivo nel caso comune.


1
Questo è un caso insolito, immagino che dove userò il valore è più probabile che non sia la base 64, quindi preferirei evitare il sovraccarico dell'eccezione. È molto più veloce controllare prima. Sto cercando di convertire un vecchio sistema che ho ereditato da password in chiaro in valori con hash.
Chris Mullins

2
Le espressioni regolari non sono mai più veloci di quanto suggerisce Tyler.
Vincent Koeman

Vedi il commento in fondo al mio post. Penso che a seconda della lunghezza delle stringhe con cui stai lavorando possa essere più veloce testare prima, specialmente per stringhe piccole come le password con hash. La stringa deve essere un multiplo di 4 per arrivare anche alla regex, quindi la regex su una stringa piccola è più veloce che su una stringa molto grande.
Chris Mullins

2
In un mondo perfetto, non si dovrebbe scrivere codice la cui logica di business è progettata o è nota per generare eccezioni. Il blocco try / catch di eccezione è troppo costoso per essere utilizzato come blocco decisionale.
Ismail Hawayel

7

Solo per motivi di completezza voglio fornire alcune implementazioni. In generale, Regex è un approccio costoso, soprattutto se la stringa è grande (cosa che accade quando si trasferiscono file di grandi dimensioni). Il seguente approccio cerca prima i modi più veloci di rilevamento.

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

MODIFICARE

Come suggerito da Sam , puoi anche modificare leggermente il codice sorgente. Fornisce un approccio più performante per l'ultima fase dei test. La routine

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

può essere utilizzato per sostituire la if (!Base64Chars.Contains(value[i]))riga conif (IsInvalid(value[i]))

Il codice sorgente completo con miglioramenti di Sam sarà simile a questo (commenti rimossi per chiarezza)

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

La risposta deve dipendere dall'utilizzo della stringa. Ci sono molte stringhe che possono essere "base64 valide" secondo la sintassi suggerita da diversi poster, ma che possono essere "correttamente" decodificate, senza eccezioni, in spazzatura. Esempio: la stringa 8car Portlandè valida Base64. Qual è il punto di affermare che questo è valido Base64? Immagino che a un certo punto vorresti sapere che questa stringa dovrebbe o non dovrebbe essere decodificata in Base64.

Nel mio caso, ho stringhe di connessione Oracle che potrebbero essere in testo normale come:

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

o in base64 come

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

Devo solo verificare la presenza di un punto e virgola, perché ciò dimostra che NON è base64, che è ovviamente più veloce di qualsiasi metodo sopra.


D'accordo, le specifiche del caso impongono anche alcuni controlli rapidi aggiuntivi. Proprio come la stringa di connessione in testo normale rispetto alla codifica base64.
Oybek

2

Knibb High regole del calcio!

Dovrebbe essere relativamente veloce e preciso, ma ammetto di non averlo sottoposto a un test approfondito, solo alcuni.

Evita costose eccezioni, regex ed evita anche il ciclo attraverso un set di caratteri, invece di utilizzare intervalli ASCII per la convalida.

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

perché prima provi a convertire poi controlli altre cose
Snr

@Snr hai ragione. Penso che questo sia ciò che deve cambiare: if (value.EndsWith ("=")) {value = value.Trim (); int mod4 = value.Length% 4; if (mod4! = 0) {return false; } Convert.FromBase64String (valore); restituire vero; } else {return false; }
Wajid khan

2

Lo userò in questo modo in modo da non dover chiamare nuovamente il metodo 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

Decodifica, ricodifica e confronta il risultato con la stringa originale

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

Imho questo non è davvero possibile. Tutte le soluzioni pubblicate falliscono per stringhe come "test" e così via. Se possono essere divisi per 4, non sono nulli o vuoti e se sono un carattere base64 valido, supereranno tutti i test. Possono essere molte stringhe ...

Quindi non esiste una soluzione reale oltre a sapere che questa è una stringa codificata in base 64 . Quello che ho trovato è questo:

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

Mi aspetto che la stringa decodificata inizi con una certa struttura, quindi lo controllo.


0

Sicuro. Basta fare in modo ogni personaggio è dentro a-z, A-Z, 0-9, /, o +, e le estremità di stringa con ==. (Almeno, questa è l'implementazione Base64 più comune. Potresti trovare alcune implementazioni che utilizzano caratteri diversi da /o +per gli ultimi due caratteri.)


Se ho capito, i caratteri finali dipendono dalla lunghezza finale del testo codificato. Quindi, se il testo codificato non è di lunghezza% 4, vengono inclusi "=".
Rafael Diego Nicoletti

0

Sì, poiché Base64 codifica i dati binari in stringhe ASCII utilizzando un set limitato di caratteri, puoi semplicemente controllarlo con questa espressione regolare:

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

che assicurerà che la stringa contenga solo AZ, az, 0-9, "+", "/", "=" e spazi bianchi.


Non è sempre un modo sicuro per dirlo. Base64 fa un po 'di riempimento per te usando il =carattere alla fine. Se il riempimento non è valido, non è una codifica base64 corretta, anche se corrisponde alla tua regex. Puoi dimostrarlo trovando una stringa di base 64 con 1 o 2 =alla fine, rimuovendoli e cercando di decodificarlo.
vcsjones

Credo che l'OP abbia chiesto di intercettare i personaggi illegali, non se la str fosse Base64 legale. In quest'ultimo caso, hai ragione, anche se gli errori di riempimento in Base64 sono più facili da intercettare usando le eccezioni.
Rob Raisch

Non è vero, almeno la versione .Net del parser base64 ignora completamente il riempimento.
Jay

0

Suggerirei di creare una regex per fare il lavoro. Dovrai controllare qualcosa del genere: [a-zA-Z0-9 + / =] Dovrai anche controllare la lunghezza della stringa. Non sono sicuro su questo, ma sono abbastanza sicuro che se qualcosa viene tagliato (a parte l'imbottitura "=") sarebbe esploso.

O meglio ancora controlla questa domanda su stackoverflow


0

Ho appena avuto un requisito molto simile in cui consento all'utente di manipolare l'immagine in un <canvas>elemento e quindi inviare l'immagine risultante recuperata con .toDataURL()al backend. Volevo fare un po 'di convalida del server prima di salvare l'immagine e ho implementato un ValidationAttributeutilizzo del codice da altre risposte:

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

Come puoi vedere, mi aspetto una stringa di tipo immagine / png, che è l'impostazione predefinita restituita da <canvas>quando si utilizza .toDataURL().


0

Controlla Base64 o stringa normale

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

Tutte le risposte sono state digerite in 1 funzione che garantisce al 100% che i suoi risultati saranno accurati.


1) Utilizzare la funzione come di seguito:

    string encoded = "WW91ckJhc2U2NHN0cmluZw==";
    msgbox("Is string base64=" + IsBase64(encoded));

2) Di seguito è la funzione:

  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

Mi piace l'idea di un controllo dell'espressione regolare. Le espressioni regolari possono essere veloci e, a volte, risparmiare il sovraccarico di codifica. l'inchiesta originale, aveva un aggiornamento che ha fatto proprio questo. Trovo però che non posso mai presumere che le stringhe non sarebbero nulle. Vorrei espandere la funzione di estensione per controllare la stringa di origine per caratteri null o solo spazi bianchi.

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

    }

Questo fallisce. Prova a passare una stringa che ha 4 caratteri uguali a "aaaa".
Bettimms,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.