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

127

Existe-t-il un moyen en C # de voir si une chaîne est encodée en Base 64 autre que d'essayer de la convertir et de voir s'il y a une erreur? J'ai un code de code comme celui-ci:

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

Je veux éviter l'exception «Caractère invalide dans une chaîne de base 64» qui se produit si la valeur n'est pas une chaîne de base 64 valide. Je veux simplement 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!

Mise à jour:
Merci pour toutes vos réponses. Voici une méthode d'extension que vous pouvez tous utiliser jusqu'à présent, elle semble garantir que votre chaîne passera 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, tout comme "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 à la capture et à l'exception, dans la plupart des cas, pour cette chose de base 64, il est plus rapide de vérifier que d'attraper l'exception jusqu'à ce que vous atteigniez une certaine longueur. Plus la longueur est petite, plus vite c'est

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

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

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

Chris Mullins
la source
1
Cela dépend du degré de «rigueur» que vous souhaitez pour la vérification. Vous pouvez utiliser une pré-validation en utilisant une expression régulière comme d'autres ont répondu, mais ce n'est pas le seul indicateur. l'encodage base64 nécessite un remplissage dans certains cas en utilisant le =signe. Si le remplissage est incorrect, cela donnera une erreur même si l'entrée correspond à une expression.
vcsjones
1
Votre condition ne satisfait pas exclusivement les chaînes base64. Considérez la chaîne \n\fLE16- votre méthode donnerait un faux positif pour cela. Pour tous ceux qui lisent et recherchent une méthode infaillible; Je recommanderais d'attraper l'exception FormatException ou d'utiliser un RegEx adapté aux spécifications, voir stackoverflow.com/questions/475074/… .
nullable
si la méthode ci-dessus renvoie false, comment puis-je remplir la chaîne à la bonne longueur?
Paul Alexander
3
Je crois que le RegEx devrait être@"^[a-zA-Z0-9\+/]*={0,2}$"
azatar
Cette solution n'est pas fiable. Il échoue si vous ajoutez 4 mêmes chaînes de caractères.
Bettimms

Réponses:

49

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 avec jusqu'à trois '=', pour faire de la longueur un multiple de 4. Mais au lieu de les comparer, vous ' Il vaut mieux ignorer l'exception, si elle se produit.

Anirudh Ramanathan
la source
1
Je pense que vous êtes sur le bon chemin. J'ai fait quelques tests et il semble que ce soit des multiples de 4 au lieu de 3.
Chris Mullins
1
Sa longueur doit être un multiple de 3, au moment de l'encodage, pour un encodage réussi! Désolé à ce sujet ... et oui, vous avez raison ... La chaîne codée a une longueur qui est un multiple de 4. C'est pourquoi nous ajouterions jusqu'à 3 '='.
Anirudh Ramanathan
4
Marqué comme correct parce que vous avez été le premier à mentionner la chose multiple. J'ai mis à jour ma question avec une implémentation de la solution, faites-moi savoir si vous rencontrez des problèmes avec elle.
Chris Mullins
48

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);
}
Tomas Kubes
la source
1
Je ne savais pas que c'était une chose. Je pense que cela devrait être la nouvelle réponse, si vous utilisez c # 7.2
Chris Mullins
4
Fonctionne uniquement dans .NET Core 2.1+ ou .NET Standard 2.1+
Cyrus
C # est un compilateur et TryFromBase64String est l'API du framework .NET :)
user960567
Ceci renvoie faux pour les chaînes non rembourrés, voici une solution: Convert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span<byte>(new byte[base64.Length]), out _). Je vous remercie.
rvnlord
44

Je sais que vous avez dit que vous ne vouliez pas attraper une exception. Mais, parce qu'il est plus fiable d'attraper une exception, je vais publier 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é.

harsimranb
la source
1
appeler base64String.Containsplusieurs fois peut entraîner des performances médiocres dans le cas d' base64Stringune grande chaîne.
NucS du
@NucS Vous avez raison, nous pouvons utiliser une regex compilée ici.
harsimranb
1
vous pouvez vérifier base64String== null || base64String.Length == 0avecstring.IsNullOrEmpty(base64String)
Daniël Tulp
Notez qu'un Base64 peut contenir des espaces (par exemple des sauts de ligne) sans problème. Ils sont ignorés par l'analyseur.
Timothy
2
Puisque nous avons accès au code source .NET maintenant, nous pouvons voir que la fonction FromBase64String () effectue toutes ces vérifications. referencesource.microsoft.com/#mscorlib/system/ ... S'il s'agit d'une chaîne base64 valide, vous la vérifiez deux fois. Il est peut-être préférable d'essayer / d'attraper l'exception.
iheartcsharp
16

Je pense que l'expression régulière devrait être:

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

Correspondant uniquement à un ou deux signes «=» de fin, pas trois.

sdevrait être la chaîne qui sera vérifiée. Regexfait partie de l' System.Text.RegularExpressionsespace de noms.

jazzdev
la source
2
ne vérifie pas si la longueur de la chaîne est mod de 4 = 0
calingasan
7

Pourquoi ne pas simplement attraper l'exception et renvoyer False?

Cela évite une surcharge supplémentaire dans le cas courant.

Tyler Eaves
la source
1
C'est un cas inhabituel, je suppose, où je vais utiliser la valeur est plus susceptible de ne pas être en base 64, donc je préférerais éviter la surcharge de l'exception. Il est beaucoup plus rapide de vérifier avant. J'essaie de convertir un ancien système hérité de mots de passe en texte clair en valeurs hachées.
Chris Mullins
2
Les expressions régulières ne sont jamais plus rapides que ce que suggère Tyler.
Vincent Koeman
Voir le commentaire au bas de mon post. Je pense qu'en fonction de la longueur des chaînes avec lesquelles vous travaillez, il peut être plus rapide à tester en premier, en particulier pour les petites chaînes comme les mots de passe hachés. La chaîne doit être un multiple de 4 pour même accéder à l'expression régulière, puis l'expression régulière sur une petite chaîne est plus rapide que sur une très grande chaîne.
Chris Mullins
2
Dans un monde parfait, il ne faut pas écrire de code dont la logique métier est conçue ou est connue pour lever des exceptions. Le bloc try / catch d'exception est trop cher pour être utilisé comme bloc de décision.
Ismail Hawayel
7

Par souci d'exhaustivité, je souhaite fournir une mise en œuvre. De manière générale, Regex est une approche coûteuse, surtout si la chaîne est volumineuse (ce qui se produit lors du transfert de fichiers volumineux). L'approche suivante essaie d'abord les moyens les plus rapides de détection.

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

ÉDITER

Comme suggéré par Sam , vous pouvez également modifier légèrement le code source. Il propose 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 if (!Base64Chars.Contains(value[i])) ligne parif (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;
    }
}
Oybek
la source
4

La réponse doit dépendre de l'utilisation de la chaîne. Il y a beaucoup de chaînes qui peuvent être "base64 valide" selon la syntaxe suggérée par plusieurs afficheurs, mais qui peuvent "correctement" décoder, sans exception, en junk. Exemple: la chaîne 8char Portlandest valide en Base64. Quel est l'intérêt de dire qu'il s'agit de base64 valide? Je suppose qu'à un moment donné, vous voudrez savoir que cette chaîne devrait ou ne devrait pas ê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.

Roland
la source
D'accord, les spécificités des cas imposent également certaines vérifications rapides supplémentaires. Tout comme la chaîne de connexion en texte brut ou encodée en base64.
Oybek
2

Règles du football Knibb High!

Cela devrait être relativement rapide et précis, mais j'avoue que je ne l'ai pas soumis à un test approfondi, juste quelques-uns.

Il évite les exceptions coûteuses, regex, et évite également de parcourir un jeu de caractères, en utilisant à la place 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;
    }
Jason K
la source
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
la source
pourquoi vous essayez d'abord de convertir puis de contrôler d'autres choses
Snr
@Snr vous avez raison. Je pense que c'est ce qu'il doit changer: if (value.EndsWith ("=")) {value = value.Trim (); int mod4 = valeur.Longueur% 4; if (mod4! = 0) {retourne faux; } Convert.FromBase64String (valeur); retourne vrai; } else {return false; }
Wajid khan
2

J'utiliserai comme ça pour ne pas avoir besoin d'appeler à nouveau la méthode de conversion

   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;
    }
Yaseer Arafat
la source
2

Décodez, réencodez et comparez le résultat à 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;
}
PKOS
la source
1

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

Il n'y a donc pas de vraie solution autre que de savoir qu'il s'agit d'une chaîne encodé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, alors je vérifie cela.

essai
la source
0

Sûr. Assurez - vous que chaque personnage est à l' intérieur a-z, A-Z, 0-9, /, ou +, et les extrémités de chaîne avec ==. (Au moins, c'est l'implémentation Base64 la plus courante. Vous pouvez trouver des implémentations qui utilisent des caractères différents de /ou +pour les deux derniers caractères.)


la source
Si j'ai bien compris, les caractères de fin dépendent de la longueur finale du texte encodé. Donc, si le texte encodé n'est pas de longueur% 4, alors '=' est inclus.
Rafael Diego Nicoletti
0

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

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

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

Rob Raisch
la source
Ce n'est pas toujours un moyen sûr de le dire. Base64 fait un peu de remplissage pour vous en utilisant le =caractère à la fin. Si ce remplissage n'est pas valide, ce n'est pas un encodage base64 correct, même s'il correspond à votre regex. Vous pouvez en faire la démonstration en trouvant une chaîne de base 64 avec 1 ou 2 =à la fin, en les supprimant et en essayant de la décoder.
vcsjones
Je crois que l'OP a demandé de piéger les caractères illégaux, pas si le str était légal Base64. Dans ce dernier cas, vous avez raison, bien que les erreurs de remplissage en Base64 soient plus faciles à intercepter à l'aide d'exceptions.
Rob Raisch
Ce n'est pas vrai, au moins la version .Net de l'analyseur base64 ignore complètement le remplissage.
Jay
0

Je suggérerais de créer une regex pour faire le travail. Vous devrez vérifier quelque chose comme ceci: [a-zA-Z0-9 + / =] Vous devrez également vérifier la longueur de la chaîne. Je ne suis pas sûr sur celui-ci, mais je suis presque sûr que si quelque chose est coupé (autre que le rembourrage "="), il exploserait.

Ou mieux encore, consultez cette question stackoverflow

Geai
la source
0

Je viens d'avoir une exigence très similaire où je laisse l'utilisateur faire une manipulation d'image dans un <canvas>élément, puis envoyer l'image résultante récupérée avec .toDataURL()au backend. Je voulais faire une validation du serveur avant d'enregistrer l'image et j'ai implémenté un en ValidationAttributeutilisant une partie du code 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 voir, j'attends une chaîne de type image / png, qui est la valeur par défaut renvoyée par <canvas>lors de l'utilisation .toDataURL().

germankiwi
la source
0

Vérifiez Base64 ou chaîne normale

public bool IsBase64Encoded (chaîne de caractères)

{

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

}

Navdeep Kapil
la source
0

Toutes les réponses ont été digérées dans 1 fonction qui garantit à 100% que ses résultats seront exacts.


1) Utilisez la fonction comme ci-dessous:

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

2) Voici la fonction:

  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;
        }
    }
Muhammad Ali
la source
-1

J'aime l'idée d'une vérification d'expression régulière. Les expressions régulières peuvent être rapides et économiser parfois la surcharge de codage. l'enquête originale, avait une mise à jour qui a fait exactement cela. Je trouve cependant que je ne peux jamais supposer que les chaînes ne seraient pas nulles. J'étendrais la fonction Extension pour vérifier la chaîne source pour les caractères nuls ou pour les espaces uniquement.

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

    }
Joseph
la source
Cela échoue. Essayez de passer une chaîne de 4 caractères identique à «aaaa».
Bettimms