web-dev-qa-db-fra.com

Comment vérifier si une chaîne encodée en Base64 est valide

Existe-t-il un moyen en C # de voir si une chaîne est codée en Base 64 autrement qu'en essayant simplement de la convertir et de voir s'il y a une erreur? J'ai un code comme ça:

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

Je veux éviter l'exception "Caractère non valide dans une chaîne en base 64" qui se produit si la valeur n'est pas une chaîne en base 64 valide. Je veux juste vérifier et renvoyer false au lieu de gérer une exception car je m'attends à ce que parfois cette valeur ne soit pas une chaîne de base 64. Existe-t-il un moyen de vérifier avant d'utiliser la fonction Convert.FromBase64String?

Merci! 

Mettre à jour:
Merci pour toutes vos réponses. Voici une méthode d’extension que vous pouvez tous utiliser jusqu’à présent. Il semble s’assurer que votre chaîne transmettra Convert.FromBase64String sans exception. .NET semble ignorer tous les espaces de fin et de fin lors de la conversion en base 64, donc "1234" est valide et donc "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);

}

Pour ceux qui s’interrogent sur les performances des tests par rapport aux captures et aux exceptions, dans la plupart des cas, il est plus rapide de vérifier que d’attraper l’exception jusqu’à atteindre une certaine longueur. Plus la longueur est courte, plus vite

Lors de mes tests très peu scientifiques: Pour 10000 itérations pour des longueurs de caractères de 100 000 à 110000, il était 2,7 fois plus rapide à tester en premier. 

Pour 1000 itérations pour une longueur de caractères de 1 à 16 caractères pour un total de 16 000 tests, il était 10,9 fois plus rapide.

Je suis sûr qu'il y a un point où il devient préférable de tester avec la méthode basée sur les exceptions. Je ne sais pas à quel moment c'est.

90
Chris Mullins

Il est assez facile de reconnaître une chaîne Base64, car elle ne sera composée que de caractères 'A'..'Z', 'a'..'z', '0'..'9', '+', '/' et elle est souvent complétée à la fin par deux "=" au maximum, pour que la longueur soit un multiple de 4. Mais au lieu Il vaudrait mieux ignorer l'exception, si cela se produit.

37
Anirudh Ramanathan

Je sais que vous avez dit que vous ne vouliez pas attraper une exception. Mais, comme attraper une exception est plus fiable, je vais aller de l'avant et poster cette réponse.

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

Mise à jour: J'ai mis à jour la condition grâce à oybek pour améliorer encore la fiabilité.

28
harsimranb

Je crois que la regex devrait être:

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

Correspondant seulement à un ou deux signes de fin "=", pas trois.

s devrait être la chaîne qui sera vérifiée. Regex fait partie de l'espace de noms System.Text.RegularExpressions.

12
jazzdev

Pourquoi ne pas attraper l'exception et renvoyer False?

Cela évite des frais généraux supplémentaires dans le cas courant.

7
Tyler Eaves

Par souci d’exhaustivité, je souhaite fournir une implémentation ..__ En général, Regex est une approche coûteuse, en particulier si la chaîne est volumineuse (ce qui se produit lors du transfert de fichiers volumineux). L’approche suivante essaie d’abord les méthodes de détection les plus rapides.

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

MODIFIER

Comme suggéré par Sam , vous pouvez également modifier légèrement le code source. Il fournit une approche plus performante pour la dernière étape des tests. 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;
    } 

peut être utilisé pour remplacer la ligne if (!Base64Chars.Contains(value[i])) par if (IsInvalid(value[i]))

Le code source complet avec les améliorations de Sam ressemblera à ceci (commentaires supprimés pour plus de clarté)

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

La réponse doit dépendre de l'utilisation de la chaîne. Il existe de nombreuses chaînes qui peuvent être "base64 valide" selon la syntaxe suggérée par plusieurs afficheurs, mais qui peuvent "correctement" décoder, sans exception, de la malbouffe. Exemple: la chaîne 8char Portland est valide Base64. Quel est le point de dire que ceci est valide Base64? Je suppose qu’à un moment donné, vous voudriez savoir que cette chaîne devrait ou non être décodée en Base64.

Dans mon cas, j'ai des chaînes de connexion Oracle qui peuvent être en texte brut comme: 

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

ou en base64 comme 

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

Je dois juste vérifier la présence d'un point-virgule, car cela prouve que ce n'est pas base64, ce qui est bien sûr plus rapide que n'importe quelle méthode ci-dessus.

4
Roland
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;
            }
        }
2
user3181503

Je vais utiliser comme ça pour ne pas avoir à appeler à nouveau la méthode 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;
    }
1
Yaseer Arafat

Utilisez Convert.TryFromBase64String à partir de 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
Tomas Kubes

Knibb High règles de football! 

Cela devrait être relativement rapide et précis, mais j’avoue que je n’ai pas subi d’essais approfondis, juste quelques-uns.

Il évite les exceptions coûteuses et les expressions rationnelles, et évite également de parcourir en boucle un jeu de caractères au lieu d'utiliser des plages ascii pour la validation.

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;
    }
1
Jason K

J'aime l'idée d'un chèque d'expressions régulières. Les expressions régulières peuvent être rapides et économiser parfois la surcharge de codage. l'enquête initiale, avait une mise à jour qui a fait cela. Je trouve cependant que je ne peux jamais supposer que les chaînes ne seraient pas nulles. Je voudrais développer la fonction Extension pour vérifier la chaîne source pour null, ou uniquement des caractères d'espacement.

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

    }
0
Joseph

Je suggérerais de créer une expression rationnelle pour faire le travail .. Vous devrez vérifier quelque chose comme ceci: [a-zA-Z0-9 +/=]. la ficelle. Je ne suis pas sûr sur celui-ci, mais je suis à peu près sûr que si quelque chose est coupé (à part le rembourrage "="), il va exploser.

Ou mieux encore consultez cette question de stackoverflow

0
Jay

Sûr. Assurez-vous simplement que chaque caractère est compris entre a-z, A-Z, 0-9, / et + et que la chaîne se termine par ==. (Du moins, c'est l'implémentation Base64 la plus courante. Vous pouvez trouver certaines implémentations utilisant des caractères différents de / ou + pour les deux derniers caractères.)

0
bdares

Oui, puisque Base64 code les données binaires en ASCII - chaînes utilisant un jeu de caractères limité, vous pouvez simplement le vérifier avec cette expression régulière:

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

ce qui assurera que la chaîne ne contient que AZ, a-z, 0-9, '+', '/', '=' et des espaces.

0
Rob Raisch

Ce n'est pas vraiment possible. Toutes les solutions publiées échouent pour des chaînes telles que "test", etc. S'ils peuvent être divisés par 4, s'ils ne sont pas nuls ou vides, et s'ils constituent un caractère base64 valide, ils réussiront tous les tests. Cela peut être beaucoup de chaînes ...

Il n’existe donc aucune solution réelle autre que sachant qu’il s’agit d’une chaîne codée en base 64}. Voici ce que j'ai trouvé:

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

Je m'attends à ce que la chaîne décodée commence par une certaine structure, je vérifie donc.

0
testing

Décodez, réencodez et comparez le résultat avec la chaîne d'origine

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

Je viens d'avoir une exigence très similaire: je laisse l'utilisateur manipuler des images dans un élément <canvas>, puis envoyer l'image résultante récupérée avec .toDataURL() au serveur. Je voulais faire une validation du serveur avant de sauvegarder l'image et j'ai implémenté une ValidationAttribute en utilisant une partie du code provenant d'autres réponses:

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

Comme vous pouvez le constater, j'attends une chaîne de type image/png, qui est la valeur par défaut renvoyée par <canvas> lorsque vous utilisez .toDataURL().

0
germankiwi