Como verificar se há uma seqüência codificada Base64 válida

127

Existe uma maneira no C # para ver se uma string está codificada na Base 64, além de apenas tentar convertê-la e ver se há algum erro? Eu tenho código de código como este:

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

Desejo evitar a exceção "Caractere inválido em uma seqüência de caracteres Base-64" que ocorre se o valor não for uma string base 64 válida. Quero apenas verificar e retornar false em vez de manipular uma exceção, porque espero que, às vezes, esse valor não seja uma cadeia de base 64. Existe alguma maneira de verificar antes de usar a função Convert.FromBase64String?

Obrigado!

Atualização:
Obrigado por todas as suas respostas. Aqui está um método de extensão que você pode usar até agora, para garantir que sua string seja aprovada em Convert.FromBase64String sem uma exceção. O .NET parece ignorar todos os espaços finais e finais ao converter para a base 64, para que "1234" seja válido e "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 aqueles que se perguntam sobre o desempenho dos testes versus captura e exceção, na maioria dos casos, para essa coisa de base 64, é mais rápido verificar do que capturar a exceção até atingir um determinado comprimento. Quanto menor o comprimento, mais rápido é

Em meus testes muito pouco científicos: para 10000 iterações para caracteres de 100.000 a 110000, era 2,7 vezes mais rápido testar primeiro.

Para 1000 iterações para caracteres de 1 a 16 caracteres para um total de 16.000 testes, foi 10,9 vezes mais rápido.

Estou certo de que há um ponto em que se torna melhor testar com o método baseado em exceção. Só não sei em que ponto.

Chris Mullins
fonte
1
Depende de quão "completo" você deseja que o cheque seja. Você pode usar uma pré-validação usando uma regex como outras pessoas responderam, mas esse não é o único indicador. A codificação base64 requer preenchimento em alguns casos, usando o =sinal. Se o preenchimento estiver errado, ocorrerá um erro, mesmo que a entrada corresponda a uma expressão.
vcsjones
1
Sua condição não atende exclusivamente às seqüências base64. Considere a string \n\fLE16- seu método produziria um falso positivo para isso. Para quem lê e procura um método infalível; Eu recomendaria capturar o FormatException ou usar um RegEx adequado às especificações, consulte stackoverflow.com/questions/475074/… .
anulável
se o método acima retornar false, como posso inserir a string no comprimento correto?
Paul Alexander
3
Eu acredito que o RegEx deve ser@"^[a-zA-Z0-9\+/]*={0,2}$"
azatar
Esta solução não é confiável. Falha se você adicionar 4 caracteres iguais.
Bettimms

Respostas:

49

É muito fácil reconhecer uma string Base64, pois ela será composta apenas de caracteres 'A'..'Z', 'a'..'z', '0'..'9', '+', '/'e geralmente é preenchida no final com até três '=', para tornar o comprimento múltiplo de 4. Mas, em vez de compará-las, você ' d seria melhor ignorar a exceção, se ela ocorrer.

Anirudh Ramanathan
fonte
1
Eu acho que você está no caminho certo. Eu fiz alguns testes e parece que é múltiplos de 4 em vez de 3.
Chris Mullins
1
Seu comprimento precisa ser múltiplo de 3, no momento da codificação, para uma codificação bem-sucedida! Desculpe por isso ... e sim, você está certo ... A cadeia codificada tem um comprimento que é múltiplo de 4. É por isso que adicionamos 3 '='.
Anirudh Ramanathan
4
Marcado como correto porque você foi o primeiro a mencionar a coisa múltipla. Atualizei minha pergunta com uma implementação da solução, avise-me se houver algum problema com ela.
Chris Mullins
47

Use Convert.TryFromBase64String do 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
fonte
1
Eu não sabia que isso era uma coisa. Eu acho que isso deve ser a nova resposta, se estiver usando C # 7.2
Chris Mullins
4
Funciona apenas no .NET Núcleo 2.1+ ou .NET padrão 2.1 +
Cyrus
C # é um compilador e TryFromBase64String é API do .NET framework :)
user960567 20/07
Isso irá retornar falso para cordas não-acolchoados, aqui é uma correção: Convert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span<byte>(new byte[base64.Length]), out _). Obrigado.
rvnlord 28/07
44

Eu sei que você disse que não queria pegar uma exceção. Mas, como capturar uma exceção é mais confiável, irei adiante e publicarei esta resposta.

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

Atualização: atualizei a condição graças ao oybek para melhorar ainda mais a confiabilidade.

harsimranb
fonte
1
chamar base64String.Containsvárias vezes pode resultar em baixo desempenho, por base64Stringser uma string grande.
NucS
@NucS Você está certo, podemos usar um regex compilado aqui.
harsimranb
1
você pode verificar base64String== null || base64String.Length == 0comstring.IsNullOrEmpty(base64String)
Daniël Tulp
Observe que um Base64 pode conter espaços em branco (por exemplo, quebras de linha) sem problemas. Eles são ignorados pelo analisador.
Timóteo
2
Como temos acesso ao código-fonte .NET, agora podemos ver que a função FromBase64String () faz todas essas verificações. referencesource.microsoft.com/#mscorlib/system/… Se for uma sequência base64 válida, você a está verificando duas vezes. Talvez chepaer apenas tentar / pegar a exceção.
Iheartcsharp
16

Eu acredito que o regex deve ser:

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

Combinando apenas um ou dois sinais '=' à direita, não três.

sdeve ser a sequência que será verificada. Regexfaz parte do System.Text.RegularExpressionsespaço para nome.

jazzdev
fonte
1
não verifica se o comprimento da string é mod de 4 = 0
calingasan
7

Por que não capturar a exceção e retornar False?

Isso evita sobrecarga adicional no caso comum.

Tyler Eaves
fonte
1
Este é um caso incomum, acho que, onde vou usar o valor, é mais provável que não seja a base 64, portanto, prefiro evitar a sobrecarga da exceção. É muito mais rápido verificar antes. Estou tentando converter um sistema antigo que herdei de senhas de texto não criptografado em valores de hash.
Chris Mullins
2
Expressões regulares nunca são mais rápidas do que o que Tyler está sugerindo.
Vincent Koeman
Veja o comentário na parte inferior da minha postagem. Acho que, dependendo do tamanho das strings com as quais você está trabalhando, pode ser mais rápido testar primeiro, especialmente para strings pequenas, como senhas com hash. A cadeia precisa ser um múltiplo de 4 para chegar até a regex e, em seguida, a regex em uma cadeia pequena é mais rápida do que em uma cadeia muito grande.
Chris Mullins
2
Em um mundo perfeito, não se deve escrever código cuja lógica de negócios seja projetada ou conhecida por lançar exceções. O bloco de tentativa / captura de exceção é muito caro para ser usado como um bloco de decisão.
Ismail Hawayel
7

Por uma questão de integridade, quero fornecer alguma implementação. De um modo geral, o Regex é uma abordagem cara, especialmente se a string for grande (o que acontece ao transferir arquivos grandes). A abordagem a seguir tenta primeiro as formas mais rápidas de detecção.

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

Conforme sugerido por Sam , você também pode alterar um pouco o código fonte. Ele fornece uma abordagem com melhor desempenho para a última etapa dos testes. A rotina

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

pode ser usado para substituir a if (!Base64Chars.Contains(value[i]))linha porif (IsInvalid(value[i]))

O código fonte completo com aprimoramentos de Sam será parecido com este (comentários removidos para maior clareza)

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

A resposta deve depender do uso da string. Há muitas seqüências de caracteres que podem ser "base64 válidas" de acordo com a sintaxe sugerida por vários pôsteres, mas que podem decodificar "corretamente", sem exceção, para lixo. Exemplo: a sequência 8char Portlandé Base64 válida. Qual é o sentido de afirmar que isso é Base64 válido? Eu acho que em algum momento você gostaria de saber que essa string deve ou não ser decodificada em Base64.

No meu caso, eu tenho cadeias de conexão Oracle que podem estar em texto sem formatação como:

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

ou em base64 como

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

Eu só tenho que verificar a presença de um ponto e vírgula, porque isso prova que NÃO é base64, que é obviamente mais rápido do que qualquer método acima.

Roland
fonte
Concordo, as especificidades do caso também impõem certas verificações rápidas adicionais. Assim como a conexão de texto simples vs a base64 codificada.
Oybek
2

Regras do futebol de Knibb High!

Isso deve ser relativamente rápido e preciso, mas admito que não fiz um teste completo, apenas alguns.

Isso evita exceções caras, regex e também evita o loop através de um conjunto de caracteres, em vez disso, usa intervalos ascii para validação.

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
fonte
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
fonte
por que você primeiro tentar converter, então, controlar outras coisas
Snr
@ SNR você está certo. Eu acho que é isso que ele precisa mudar: if (value.EndsWith ("=")) {value = value.Trim (); int mod4 = value.Length% 4; if (mod4! = 0) {retorne false; } Convert.FromBase64String (valor); return true; } else {return false; }
Wajid khan
2

Vou usar assim para não precisar chamar o método convert novamente

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

Decodifique, recodifique e compare o resultado com a sequência 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
fonte
1

Imho isso não é realmente possível. Todas as soluções postadas falham em strings como "test" e assim por diante. Se eles puderem ser divididos por 4, não forem nulos ou vazios, e se tiverem um caractere base64 válido, serão aprovados em todos os testes. Isso pode ser muitas cordas ...

Portanto, não há outra solução real a não ser saber que essa é uma sequência codificada de base 64 . O que eu vim com isso é o seguinte:

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

Eu espero que a string decodificada comece com uma certa estrutura, então eu verifico isso.

teste
fonte
0

Certo. Apenas certifique-se cada personagem está dentro a-z, A-Z, 0-9, /, ou +, e as extremidades da corda com ==. (Pelo menos, essa é a implementação mais comum Base64. Você pode encontrar algumas implementações que usam caracteres diferentes de /ou +para os dois últimos caracteres.)


fonte
Se eu entendi, os caracteres finais dependem do tamanho final do texto codificado. Portanto, se o texto codificado não tiver o comprimento% 4, '=' será incluído.
Rafael Diego Nicoletti
0

Sim, como o Base64 codifica dados binários em seqüências ASCII usando um conjunto limitado de caracteres, você pode simplesmente verificá-lo com esta expressão regular:

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

que garantirá que a string contenha apenas AZ, az, 0-9, '+', '/', '=' e espaço em branco.

Rob Raisch
fonte
Isso nem sempre é uma maneira certa de dizer. Base64 faz algum preenchimento para você usar o =caractere no final. Se esse preenchimento for inválido, não será uma codificação base64 correta, mesmo que corresponda ao seu regex. Você pode demonstrar isso localizando uma string de base 64 com 1 ou 2 =no final, removendo-as e tentando decodificá-la.
vcsjones
Acredito que o OP pediu para capturar caracteres ilegais, não se o str fosse Base64 legal. Neste último caso, você está correto, embora os erros de preenchimento no Base64 sejam mais fáceis de interceptar usando exceções.
Rob Raisch
Não é verdade, pelo menos a versão .Net do analisador base64 ignora completamente o preenchimento.
Jay
0

Eu sugeriria a criação de uma regex para fazer o trabalho. Você terá que procurar algo assim: [a-zA-Z0-9 + / =] Você também terá que verificar o comprimento da string. Não tenho certeza, mas tenho certeza de que, se alguma coisa for cortada (além do preenchimento "="), ela explodirá.

Ou melhor ainda, confira esta pergunta sobre o stackoverflow

Jay
fonte
0

Acabei de ter um requisito muito semelhante no qual deixo o usuário fazer alguma manipulação de imagem em um <canvas>elemento e, em seguida, envio a imagem resultante recuperada .toDataURL()para o back-end. Eu queria fazer alguma validação do servidor antes de salvar a imagem e implementar um ValidationAttributeuso de parte do código de outras respostas:

[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 você pode ver, estou esperando uma string de tipo imagem / png, que é o padrão retornado <canvas>ao usar .toDataURL().

germankiwi
fonte
0

Verifique Base64 ou sequência normal

bool público 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
fonte
0

Todas as respostas foram digeridas em uma função que garante 100% de que seus resultados serão precisos.


1) Use a função como abaixo:

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

2) Abaixo está a função:

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

Gosto da ideia de uma verificação de expressão regular. Expressões regulares podem ser rápidas e, às vezes, salvar a sobrecarga de codificação. o inquérito original, teve uma atualização que fez exatamente isso. Acho, porém, que nunca posso assumir que as seqüências não seriam nulas. Eu expandiria a função Extension para verificar a cadeia de origem quanto a caracteres nulos ou apenas em espaço em branco.

    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
fonte
Isso falha. Tente passar uma string que tenha 4 caracteres iguais a 'aaaa'.
Bettimms