web-dev-qa-db-ja.com

有効なBase64エンコード文字列を確認する方法

C#には、文字列を変換してエラーがあるかどうかを確認する以外に、文字列がBase 64でエンコードされているかどうかを確認する方法がありますか?私はこのようなコードを持っています:

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

値が有効なbase 64文字列でない場合に発生する「Base 64文字列の無効な文字」例外を回避したい。例外を処理するのではなく、単にチェックしてfalseを返すようにしたいのは、この値がbase 64文字列にならない場合があるからです。 Convert.FromBase64String関数を使用する前に確認する方法はありますか?

ありがとう!

更新:
ご回答ありがとうございます。これまでにすべて使用できる拡張メソッドを次に示します。これは、文字列がConvert.FromBase64Stringを例外なく渡すことを確認するようです。 .NETは、ベース64に変換するときにすべての末尾と末尾のスペースを無視するようです。したがって、「1234」は有効で、「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);

}

テストとキャッチと例外のパフォーマンスについて疑問がある場合、このbase 64のほとんどの場合、特定の長さに達するまで例外をキャッチするよりもチェックする方が高速です。長さが短いほど速くなります

私の非常に非科学的なテストでは:文字長100,000-110000の10000回の反復では、最初にテストするのは2.7倍高速でした。

文字の長さが1〜16文字の1000回の反復で、合計16,000回のテストでは10.9倍高速でした。

例外ベースの方法でテストする方が良くなるポイントがあると確信しています。私はそれがどの時点にあるのかわかりません。

107
Chris Mullins

Base64文字列は文字'A'..'Z', 'a'..'z', '0'..'9', '+', '/'のみで構成され、長さを4の倍数にするために最大で2つの '='で埋められることが多いため、Base64文字列を認識するのは非常に簡単です。これらを比較すると、例外が発生した場合は無視する方が良いでしょう。

43

例外をキャッチしたくないと言ったのは知っています。ただし、例外をキャッチする方が信頼性が高いため、この回答を投稿します。

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

更新:信頼性をさらに向上させるために、 oybek のおかげで条件を更新しました。

32
harsimranb

C#7.2のConvert.TryFromBase64Stringを使用します

public static bool IsBase64String(string base64)
{
   Span<byte> buffer = new Span<byte>(new byte[base64.Length]);
   return Convert.TryFromBase64String(base64, buffer , out int bytesParsed);
}
18
Tomas Kubes

正規表現は次のようにすべきだと思います:

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

3つではなく、1つまたは2つの末尾の「=」記号のみに一致します。

sは、チェックされる文字列でなければなりません。 RegexSystem.Text.RegularExpressions名前空間の一部です。

14
jazzdev

なぜ単に例外をキャッチして、Falseを返さないのですか?

これにより、一般的なケースで追加のオーバーヘッドが回避されます。

7
Tyler Eaves

完全を期すために、実装を提供したいと思います。一般的に、Regexは高価なアプローチです。特に文字列が大きい場合(大きなファイルを転送する場合に発生します)。次のアプローチでは、最初に最速の検出方法を試します。

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

編集

Sam で示唆されているように、ソースコードをわずかに変更することもできます。彼は、テストの最後のステップでより良いパフォーマンスのアプローチを提供します。ルーチン

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

if (!Base64Chars.Contains(value[i]))行をif (IsInvalid(value[i]))で置き換えるために使用できます

Sam から強化された完全なソースコードは次のようになります(わかりやすくするためにコメントを削除しました)

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

答えは、文字列の使用法に依存する必要があります。いくつかの投稿者によって提案された構文に従って「有効なbase64」であるかもしれない多くの文字列がありますが、例外なくジャンクに「正しく」デコードするかもしれません。例:8char文字列Portlandは有効なBase64です。これが有効なBase64であると述べることのポイントは何ですか?ある時点で、この文字列をBase64でデコードする必要があるかどうかを知りたいと思うでしょう。

私の場合、次のようなプレーンテキストのOracle接続文字列があります。

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

またはbase64のような

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

セミコロンの存在を確認する必要があります。これは、それがbase64ではないことを証明しているためです。もちろん、上記の方法よりも高速です。

4
Roland
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;
            }
        }
2
user3181503

Convertメソッドを再度呼び出す必要がないように、このように使用します

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

Knibb High Footballルール!

これは比較的高速で正確なはずですが、徹底的なテストを行ったのではなく、ほんのわずかです。

検証にascii範囲を使用する代わりに、高価な例外、正規表現、および文字セットのループを回避します。

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;
    }
2
Jason K

これは実際には不可能です。 "test"などの文字列に対して投稿されたすべてのソリューションが失敗します。 4で分割できる場合、nullまたは空ではなく、有効なbase64文字であれば、すべてのテストに合格します。それは多くの文字列にすることができます...

したがって、これがbase 64エンコード文字列であることを知っている以外の実際の解決策はありません。私が思いついたのはこれです:

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

デコードされた文字列が特定の構造で始まることを期待しているので、それを確認します。

1
testing

私は正規表現チェックのアイデアが好きです。正規表現は高速であり、コーディングのオーバーヘッドを時々節約できます。元の問い合わせには、これを行う更新がありました。しかし、文字列がnullではないと仮定することはできません。拡張機能を展開して、ソース文字列のヌルまたは空白のみの文字を確認します。

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

    }
0
Joseph

この仕事をするために正規表現を作成することをお勧めします。次のようなものを確認する必要があります:[a-zA-Z0-9 +/=]また、文字列の長さも確認する必要があります。私はこれについてはわかりませんが、何かがトリミングされた場合(パディング「=」以外)が爆発するかどうかはかなり確信しています。

または、より良いチェックアウト このstackoverflowの質問

0
Jay

承知しました。各文字がa-zA-Z0-9/、または+内にあり、文字列が==で終わることを確認してください。 (少なくとも、これは最も一般的なBase64実装です。最後の2文字に/または+とは異なる文字を使用する実装がいくつか見つかるかもしれません。)

0
bdares

はい、 Base64 は限られた文字セットを使用してバイナリデータをASCII文字列にエンコードするため、次の正規表現で簡単に確認できます。

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

これにより、文字列にはA〜Z、a〜z、0〜9、「+」、「/」、「=」、および空白のみが含まれることが保証されます。

0
Rob Raisch

デコード、再エンコード、結果を元の文字列と比較します

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

ユーザーに<canvas>要素で画像操作を行わせ、.toDataURL()で取得した結果の画像をバックエンドに送信するという非常によく似た要件がありました。イメージを保存する前にサーバーの検証を行い、他の回答のコードを使用してValidationAttributeを実装しました。

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

ご覧のように、.toDataURL()を使用するときに<canvas>によって返されるデフォルトであるimage/pngタイプの文字列を期待しています。

0
germankiwi