在.Net中可靠地检查字符串是否为base64编码

3

在我开始之前:是的,我已经检查了此主题的其他问题和答案,无论在这里还是其他地方。

我找到了一个示例字符串,即使它实际上并没有进行base64编码,.Net也会对其进行base64解码。以下是示例:

Rhinocort Aqueous 64mcg / dose鼻喷雾

.Net方法Convert.FromBase64String在解码此字符串时不会引发异常,因此我的IsBase64Encoded方法会为此字符串返回true。

有趣的是,如果我使用cygwin base64 -d命令以此字符串作为输入,则会失败并显示“invalid input”消息。

更有趣的是,我认为属于此可执行文件的源代码(http://libb64.sourceforge.net/)使用相同的结果“解码”了此字符串,就像我从.Net Convert.FromBase64String中得到的结果一样。我将继续寻找希望在其他地方找到线索,但现在我感到困惑。

有什么想法吗?


那就是问题所在,它没有抛出异常。 - MrBoffin
5
.NET版本会忽略空白字符(包括空格、换行符、回车符和制表符),因为这些字符不是有效的。使用base64 -di命令也可以得到相同的结果。没有一种真正可靠的方法来检查数据是否为base-64编码,只能依赖某种启发式方法。任何长度是4的倍数并且由[a-zA-Z0-9]匹配的字符串都可以被解码,但这些字符串可能不代表实际的base-64编码数据。 - Mike Zboray
谢谢Mike。你说的关于base64 -di“解码”这个字符串是正确的。看起来我得找到其他一些编码方式,以便我能够识别它们是否已经被编码了。 - MrBoffin
@mikez:你必须添加填充,= * 2 到那个正则表达式中。 - Jim Mischel
4
要让电脑更有趣,试着让它判断这是Base64编码、十六进制编码还是一个流行乐队:"ABBA"。 - H H
显示剩余2条评论
2个回答

1

不幸的是,那不是一个解决方案。问题在于字符串将进行base64解码而不会失败。然而,在CodeProject页面底部有另一个答案,它使用正则表达式来查看它是否像是一个base64编码的文本。虽然不是万无一失,但这可能是我迄今为止见过的最好的想法。 - MrBoffin

0
当字符串经过Base64解码并且解码后的数据包含特殊字符时,我们或许可以得出它不是有效的Base64(这取决于编码方式)。另外,有时候我们期望传递的数据是Base64格式,但是有可能没有用'='正确填充。因此,一种方法使用“严格”的Base64规则,而另一种则是“宽容”的。
    [TestMethod]
    public void CheckForBase64()
    {
        Assert.IsFalse(IsBase64DataStrict("eyJhIjoiMSIsImIiOiI2N2NiZjA5MC00ZGRiLTQ3OTktOTlmZi1hMjhhYmUyNzQwYjEiLCJmIjoiMSIsImciOiIxIn0"));
        Assert.IsTrue(IsBase64DataForgiving("eyJhIjoiMSIsImIiOiI2N2NiZjA5MC00ZGRiLTQ3OTktOTlmZi1hMjhhYmUyNzQwYjEiLCJmIjoiMSIsImciOiIxIn0"));
        Assert.IsFalse(IsBase64DataForgiving("testing123"));
        Assert.IsFalse(IsBase64DataStrict("ABBA"));
        Assert.IsFalse(IsBase64DataForgiving("6AC648C9-C08F-4F9D-A0A5-3904CF15ED3E"));
    }
    public bool IsBase64DataStrict(string data)
    {
        if (string.IsNullOrWhiteSpace(data)) return false;
        if ((new Regex(@"[^A-Z0-9+\/=]", RegexOptions.IgnoreCase)).IsMatch(data)) return false;
        if (data.Length % 4 != 0) return false;
        var e = data.IndexOf('=');
        var l = data.Length;
        if (!(e == -1 || e == l - 1 || (e == l - 2 && data[l - 1] == '='))) return false;
        var decoded = string.Empty;
        try
        {
            byte[] decodedData = Convert.FromBase64String(data);
            decoded = Encoding.UTF8.GetString(decodedData);
        }
        catch(Exception)
        {
            return false;
        }
        //check for special chars that you know should not be there
        char current;
        for (int i = 0; i < decoded.Length; i++)
        {
            current = decoded[i];
            if (current == 65533) return false;
            if (!((current == 0x9 || current == 0xA || current == 0xD) ||
                ((current >= 0x20) && (current <= 0xD7FF)) ||
                ((current >= 0xE000) && (current <= 0xFFFD)) ||
                ((current >= 0x10000) && (current <= 0x10FFFF))))
            {
                return false;
            }
        }
        return true;
    }

    public bool IsBase64DataForgiving(string data)
    {
        if (string.IsNullOrWhiteSpace(data)) return false;

        //it could be made more forgiving by replacing any spaces with '+' here

        if ((new Regex(@"[^A-Z0-9+\/=]", RegexOptions.IgnoreCase)).IsMatch(data)) return false;
        
        //this is the forgiving part
        if (data.Length % 4 > 0)
            data = data.PadRight(data.Length + 4 - data.Length % 4, '=');
        
        var e = data.IndexOf('=');
        var l = data.Length;
        if (!(e == -1 || e == l - 1 || (e == l - 2 && data[l - 1] == '='))) return false;
        var decoded = string.Empty;
        try
        {
            byte[] decodedData = Convert.FromBase64String(data);
            decoded = Encoding.UTF8.GetString(decodedData);
        }
        catch (Exception)
        {
            return false;
        }
        //check for special chars that you know should not be there
        char current;
        for (int i = 0; i < decoded.Length; i++)
        {
            current = decoded[i];
            if (current == 65533) return false;
            if (!((current == 0x9 || current == 0xA || current == 0xD) ||
                ((current >= 0x20) && (current <= 0xD7FF)) ||
                ((current >= 0xE000) && (current <= 0xFFFD)) ||
                ((current >= 0x10000) && (current <= 0x10FFFF))))
            {
                return false;
            }
        }
        return true;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接