Cómo verificar una cadena codificada Base64 válida

127

¿Hay alguna manera en C # para ver si una cadena está codificada en Base 64, aparte de tratar de convertirla y ver si hay un error? Tengo un código de código como este:

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

Quiero evitar la excepción "Carácter inválido en una cadena Base-64" que ocurre si el valor no es una cadena base 64 válida. Solo quiero verificar y devolver falso en lugar de manejar una excepción porque espero que a veces este valor no sea una cadena base 64. ¿Hay alguna forma de verificar antes de usar la función Convert.FromBase64String?

¡Gracias!

Actualización:
Gracias por todas sus respuestas. Aquí hay un método de extensión que todos pueden usar hasta ahora, parece asegurarse de que su cadena pasará Convert.FromBase64String sin una excepción. .NET parece ignorar todos los espacios finales y finales al convertir a base 64, por lo que "1234" es válido y también "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);

}

Para aquellos que se preguntan sobre el rendimiento de las pruebas frente a la captura y la excepción, en la mayoría de los casos para esta cosa de base 64 es más rápido verificar que detectar la excepción hasta que alcance una cierta longitud. Cuanto menor es la longitud, más rápido es

En mis pruebas muy poco científicas: para 10000 iteraciones para la longitud de caracteres 100,000 - 110000, fue 2,7 veces más rápido probar primero.

Para 1000 iteraciones para caracteres de longitud 1 - 16 caracteres para un total de 16,000 pruebas, fue 10.9 veces más rápido.

Estoy seguro de que hay un punto en el que es mejor probar con el método basado en excepciones. Simplemente no sé en qué punto es eso.

Chris Mullins
fuente
1
Depende de cuán "exhaustivo" desee que sea el cheque. Puede usar alguna validación previa utilizando una expresión regular como otros han respondido, pero ese no es el único indicador. La codificación base64 requiere relleno en algunos casos con el =signo. Si el relleno es incorrecto, dará un error aunque la entrada coincida con una expresión.
vcsjones
1
Su condición no satisface exclusivamente las cadenas base64. Considere la cadena \n\fLE16: su método arrojaría un falso positivo para esto. Para cualquiera que lea y busque un método infalible; Recomendaría capturar la FormatException o usar un RegEx adecuado a las especificaciones, consulte stackoverflow.com/questions/475074/… .
Anulable
Si el método anterior devuelve falso, ¿cómo puedo rellenar la cadena a la longitud correcta?
Paul Alexander
3
Creo que el RegEx debería ser@"^[a-zA-Z0-9\+/]*={0,2}$"
azatar
Esta solución no es confiable. Falla si agrega la misma cadena de 4 caracteres.
Bettimms

Respuestas:

49

Es bastante fácil reconocer una cadena Base64, ya que solo estará compuesta de caracteres 'A'..'Z', 'a'..'z', '0'..'9', '+', '/'y a menudo se rellena al final con hasta tres '=', para hacer que la longitud sea múltiplo de 4. Pero en lugar de compararlos, usted ' Sería mejor ignorar la excepción, si ocurre.

Anirudh Ramanathan
fuente
1
Creo que estás en el camino correcto. Hice algunas pruebas y parece que son múltiplos de 4 en lugar de 3.
Chris Mullins
1
¡Su longitud debe ser un múltiplo de 3, al momento de la codificación, para una codificación exitosa! Perdón por eso ... y sí, tienes razón ... La cadena codificada tiene una longitud que es un múltiplo de 4. Es por eso que rellenaríamos hasta 3 '='.
Anirudh Ramanathan
44
Marcado Correcto porque fuiste el primero en mencionar la cosa múltiple. Actualicé mi pregunta con una implementación de la solución, avíseme si ve algún problema con ella.
Chris Mullins
47

Use Convert. tryFromBase64String desde 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);
}
Tomás Kubes
fuente
1
No sabía que era una cosa. Creo que esta debería ser la nueva respuesta, si usa c # 7.2
Chris Mullins
44
Funciona solo en .NET Core 2.1+ o .NET Standard 2.1+
Cyrus
C # es un compilador y TryFromBase64String es API de .NET Framework :)
user960567
Esto devolverá falso para las cadenas no acolchada, que aquí hay una solución: Convert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span<byte>(new byte[base64.Length]), out _). Gracias.
rvnlord
44

Sé que dijiste que no querías atrapar una excepción. Pero, dado que detectar una excepción es más confiable, seguiré adelante y publicaré esta respuesta.

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

Actualización: He actualizado la condición gracias a oybek para mejorar aún más la confiabilidad.

harsimranb
fuente
1
llamar base64String.Containsvarias veces puede resultar en un bajo rendimiento en caso de base64Stringser una cadena grande.
NucS
@NucS Tienes razón, podemos usar una expresión regular compilada aquí.
harsimranb
1
puedes consultar base64String== null || base64String.Length == 0constring.IsNullOrEmpty(base64String)
Daniël Tulp
Tenga en cuenta que un Base64 puede contener espacios en blanco (por ejemplo, saltos de línea) sin problemas. Son ignorados por el analizador.
Timothy
2
Dado que ahora tenemos acceso al código fuente .NET, podemos ver que la función FromBase64String () realiza todas estas comprobaciones. referencesource.microsoft.com/#mscorlib/system/… Si es una cadena base64 válida, entonces la está verificando dos veces. Tal vez sea chepaer intentar / atrapar la excepción.
iheartcsharp
16

Creo que la expresión regular debería ser:

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

Solo coincide con uno o dos signos '=' finales, no con tres.

sdebe ser la cadena que se verificará. Regexes parte del System.Text.RegularExpressionsespacio de nombres.

jazzdev
fuente
1
no comprueba si la longitud de la cadena es mod de 4 = 0
calingasan
7

¿Por qué no capturar la excepción y devolver False?

Esto evita gastos generales adicionales en el caso común.

Aleros Tyler
fuente
1
Este es un caso inusual, supongo que donde voy a usar el valor es más probable que no sea base 64, por lo que preferiría evitar la sobrecarga de la excepción. Es mucho más rápido verificar antes. Estoy tratando de convertir un sistema antiguo que heredé de contraseñas de texto sin cifrar a valores hash.
Chris Mullins
2
Las expresiones regulares nunca son más rápidas de lo que sugiere Tyler.
Vincent Koeman
Vea el comentario al final de mi publicación. Creo que, dependiendo de la longitud de las cadenas con las que esté trabajando, puede ser más rápido probar primero, especialmente para cadenas pequeñas como contraseñas hash. La cadena tiene que ser un múltiplo de 4 para llegar incluso a la expresión regular, y luego la expresión regular en una cadena pequeña es más rápida que en una cadena muy grande.
Chris Mullins
2
En un mundo perfecto, uno no debería escribir código cuya lógica de negocios esté diseñada o se sepa que arroja excepciones. El bloque try / catch de excepción es demasiado costoso para usarse como bloque de decisión.
Ismail Hawayel
7

Solo en aras de la integridad, quiero proporcionar alguna implementación. En términos generales, Regex es un enfoque costoso, especialmente si la cadena es grande (lo que sucede al transferir archivos grandes). El siguiente enfoque intenta primero las formas más rápidas de detección.

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

EDITAR

Según lo sugerido por Sam , también puede cambiar ligeramente el código fuente. Proporciona un enfoque de mejor desempeño para el último paso de las pruebas. La rutina

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

se puede usar para reemplazar la if (!Base64Chars.Contains(value[i]))línea conif (IsInvalid(value[i]))

El código fuente completo con mejoras de Sam se verá así (comentarios eliminados para mayor claridad)

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
fuente
4

La respuesta debe depender del uso de la cadena. Hay muchas cadenas que pueden ser "base64 válida" de acuerdo con la sintaxis sugerida por varios carteles, pero que pueden decodificar "correctamente", sin excepción, la basura. Ejemplo: la cadena 8char Portlandes válida Base64. ¿Cuál es el punto de afirmar que esto es válido Base64? Supongo que en algún momento querrás saber que esta cadena debería o no ser decodificada en Base64.

En mi caso, tengo cadenas de conexión Oracle que pueden estar en texto plano como:

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

o en base64 como

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

Solo tengo que verificar la presencia de un punto y coma, porque eso prueba que NO es base64, que por supuesto es más rápido que cualquier método anterior.

Roland
fuente
De acuerdo, los detalles del caso también imponen ciertas verificaciones rápidas adicionales. Al igual que las cadenas de texto sin formato vs codificación base64.
Oybek
2

Knibb High reglas de fútbol!

Esto debería ser relativamente rápido y preciso, pero admito que no lo sometí a una prueba exhaustiva, solo algunas.

Evita excepciones costosas, expresiones regulares, y también evita recorrer un conjunto de caracteres, en su lugar utiliza rangos ascii para la validación.

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
fuente
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;
            }
        }
usuario3181503
fuente
por qué primero intentas convertir y luego controlas otras cosas
Snr
@Snr tienes razón. Creo que esto es lo que necesita cambiar: if (value.EndsWith ("=")) {value = value.Trim (); int mod4 = value.Length% 4; if (mod4! = 0) {return false; } Convert.FromBase64String (valor); volver verdadero; } else {return false; }
Wajid khan
2

Usaré así para no tener que volver a llamar al método de conversión.

   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
fuente
2

Decodifique, vuelva a codificar y compare el resultado con la cadena original

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
fuente
1

En mi humilde opinión esto no es realmente posible. Todas las soluciones publicadas fallan para cadenas como "prueba", etc. Si pueden dividirse entre 4, no son nulos o están vacíos, y si son un carácter base64 válido, pasarán todas las pruebas. Eso puede ser muchas cadenas ...

Por lo tanto, no hay una solución real que no sea saber que esta es una cadena codificada en base 64 . Lo que se me ocurrió es esto:

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

Espero que la cadena decodificada comience con una determinada estructura, así que verifico eso.

pruebas
fuente
0

Por supuesto. Sólo asegúrese de que cada personaje está dentro a-z, A-Z, 0-9, /, o +, y los extremos de cadena con ==. (Al menos, esa es la implementación de Base64 más común. Es posible que encuentre algunas implementaciones que usan caracteres diferentes /o +para los dos últimos caracteres).


fuente
Si entendí, los caracteres finales dependen de la longitud final del texto codificado. Entonces, si el texto codificado no tiene la longitud% 4, entonces se incluye '='.
Rafael Diego Nicoletti el
0

Sí, dado que Base64 codifica datos binarios en cadenas ASCII utilizando un conjunto limitado de caracteres, simplemente puede verificarlo con esta expresión regular:

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

lo que asegurará que la cadena solo contenga AZ, az, 0-9, '+', '/', '=' y espacios en blanco.

Rob Raisch
fuente
Esa no siempre es una forma segura de decirlo. Base64 hace un poco de relleno para ti usando el =personaje al final. Si ese relleno no es válido, no es una codificación base64 correcta, aunque coincida con su expresión regular. Puede hacer una demostración de esto encontrando una cadena base 64 con 1 o 2 =al final, eliminándolas e intentando decodificarla.
vcsjones
Creo que el OP pidió atrapar a los personajes ilegales, no si el str era legal Base64. Si es lo último, tiene razón, aunque los errores de relleno en Base64 son más fáciles de atrapar usando excepciones.
Rob Raisch
No es cierto, al menos la versión .Net del analizador base64 ignora el relleno por completo.
Jay
0

Sugeriría crear una expresión regular para hacer el trabajo. Tendrá que verificar algo como esto: [a-zA-Z0-9 + / =] También deberá verificar la longitud de la cadena. No estoy seguro de esto, pero estoy bastante seguro de que si algo se recorta (aparte del relleno "=") explotaría.

O mejor aún, mira esta pregunta de stackoverflow

Arrendajo
fuente
0

Acabo de tener un requisito muy similar en el que dejo que el usuario realice alguna manipulación de imagen en un <canvas>elemento y luego envíe la imagen resultante recuperada .toDataURL()al backend. Quería hacer una validación del servidor antes de guardar la imagen y he implementado un ValidationAttributeuso del código de otras respuestas:

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

Como puede ver, estoy esperando una cadena de tipo imagen / png, que es el valor predeterminado que se devuelve <canvas>al usar .toDataURL().

germankiwi
fuente
0

Verifique Base64 o cadena normal

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

}

Navdeep Kapil
fuente
0

Todas las respuestas fueron digeridas en 1 función que garantiza al 100% que sus resultados serán precisos.


1) Use la función de la siguiente manera:

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

2) A continuación se muestra la función:

  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
fuente
-1

Me gusta la idea de un cheque de expresión regular. Las expresiones regulares pueden ser rápidas y a veces ahorran la sobrecarga de codificación. La consulta original, tenía una actualización que hizo justamente esto. Sin embargo, encuentro que nunca puedo asumir que las cadenas no serían nulas. Expandiría la función de Extensión para verificar que la cadena de origen no contenga caracteres nulos o en blanco.

    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
fuente
Esto falla Intenta pasar una cadena que tenga 4 caracteres iguales a 'aaaa'.
Bettimms