c# - Cómo comprobar si hay una cadena codificada Base64 válida
decode text (15)
¡Reglas de fútbol de Knibb High!
Esto debería ser relativamente rápido y preciso, pero admito que no lo sometí a una prueba exhaustiva, solo unos pocos.
Evita las costosas excepciones, expresiones regulares, y también evita el bucle a través de 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;
}
¿Hay alguna forma en C # para ver si una cadena está codificada en Base 64, además 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 "Caracteres no válidos en una cadena Base-64" que ocurre si el valor no es válido en la cadena base 64. Quiero simplemente 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 manera de verificar antes de usar la función Convert.FromBase64String?
¡Gracias!
Actualizar:
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 excepción. .NET parece ignorar todos los espacios finales y finales al convertir a la base 64 por lo que "1234" es válido y también lo es "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 la prueba frente a la captura y la excepción, en la mayoría de los casos para esta base 64 es más rápido verificar que capturar la excepción hasta llegar a cierta longitud. Cuanto menor es la longitud más rápida es
En mi prueba muy poco científica: para 10000 iteraciones para una longitud de caracteres de 100,000 a 110000 fue 2,7 veces más rápido para probar primero.
Para 1000 iteraciones para caracteres de 1 a 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.
¿Por qué no solo atrapar la excepción y devolver False?
Esto evita una sobrecarga adicional en el caso común.
Acabo de tener un requisito muy similar en el que estoy dejando que el usuario manipule la imagen en un elemento <canvas>
y luego envíe la imagen resultante recuperada con .toDataURL()
al back-end. Quería hacer algo de validación del servidor antes de guardar la imagen y haber implementado un ValidationAttribute
usando parte 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 tipo imagen / png, que es la predeterminada devuelta por <canvas>
cuando se usa .toDataURL()
.
Creo que la expresión regular debería ser:
Regex.IsMatch(s, @"^[a-zA-Z0-9/+/]*={0,2}$"
Solo coincide uno o dos signos ''='', no tres.
Es bastante fácil reconocer una cadena de Base64, ya que solo estará compuesta por los caracteres ''A''..''Z'', ''a''..''z'', ''0''..''9'', ''+'', ''/''
y a menudo se rellena al final con hasta dos'' = '', para hacer que la longitud sea múltiplo de 4. Pero en lugar de comparar esto, sería mejor que ignorara la excepción, si ocurriera.
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, a la basura. Ejemplo: el 8char string Portland
es válido Base64. ¿Cuál es el punto de afirmar que esto es válido Base64? Supongo que en algún punto querrás saber que esta cadena debería o no ser decodificada con Base64.
En mi caso, tengo cadenas de conexión de Oracle que pueden estar en texto plano como:
Data source=mydb/DBNAME;User Id=Roland;Password=.....`
o en base64 me gusta
VXNlciBJZD1sa.....................................==
Solo tengo que verificar la presencia de un punto y coma, porque eso prueba que NO es base64, que es, por supuesto, más rápido que cualquier método anterior.
Me gusta la idea de un control de expresión regular. Las expresiones regulares pueden ser rápidas, y guardar la sobrecarga de codificación a veces. la consulta original, tenía una actualización que solo hizo esto. Sin embargo, encuentro que nunca puedo suponer que las cadenas no serán nulas. Expandiría la función Extensión para verificar que la cadena de origen tenga caracteres nulos o de espacios 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);
}
Por supuesto. Solo asegúrese de que cada carácter esté dentro de az
, AZ
, 0-9
, /
, o +
, y la cadena termina con ==
. (Al menos, esa es la implementación de Base64 más común. Puede encontrar algunas implementaciones que usan caracteres diferentes de /
o +
para los dos últimos caracteres).
Sé que dijiste que no querías atrapar una excepción. Pero, como capturar una excepción es más confiable, seguiré adelante y publicaré esta respuesta.
public static bool IsBase64(this string base64String) {
// Credit: oybek https://.com/users/794764/oybek
if (base64String== null || base64String.Length == 0 || 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 fiabilidad.
Sí, dado que Base64 codifica datos binarios en cadenas ASCII usando 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.
Solo para completar, quiero proporcionar alguna implementación. En términos generales, Regex es un enfoque costoso, especialmente si la cadena es grande (lo que sucede cuando se transfieren archivos de gran tamaño). 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
Como sugirió Sam , también puedes cambiar el código fuente ligeramente. Proporciona un enfoque de mejor rendimiento 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 if (!Base64Chars.Contains(value[i]))
line with if (IsInvalid(value[i]))
El código fuente completo con mejoras de Sam se verá así (se eliminaron los comentarios 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;
}
}
Sugeriría crear una expresión regular para hacer el trabajo. Deberá 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 sobre stackflow
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://.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;
}
Imho esto no es realmente posible. Todas las soluciones publicadas fallan en cadenas como "prueba", etc. Si se pueden dividir en 4, no son nulos o están vacíos, y si tienen un carácter base64 válido, pasarán todas las pruebas. Eso puede ser muchas cadenas ...
Entonces, no hay otra 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 cierta estructura, así que lo compruebo.
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;
}
}