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


127

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

// 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は、base 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倍高速でした。

例外ベースの方法でテストした方が良い点があると思います。それがいつなのか、私には分からない。


1
これは、チェックをどの程度「徹底」したいかによって異なります。他の人が答えたように、正規表現を使用していくつかの事前検証を使用できますが、それが唯一の指標ではありません。base64エンコーディングでは、場合によっては=符号を使用したパディングが必要です。パディングが正しくない場合、入力が式と一致してもエラーが発生します。
vcsjones

1
条件は、base64文字列のみを満たしているわけではありません。文字列を考えてみて\n\fLE16ください-あなたのメソッドはこれに対して誤検知を引き起こします。誰にでも読んで間違いのない方法を探しています。なるほど、FormatExceptionをキャッチや仕様に適した正規表現を使用することをお勧めしますstackoverflow.com/questions/475074/...を
nullable、

上記のメソッドがfalseを返す場合、文字列を正しい長さに埋め込むにはどうすればよいですか?
ポールアレクサンダー

3
RegExは次のようになるべきだと思います@"^[a-zA-Z0-9\+/]*={0,2}$"
アザター

このソリューションは信頼できません。同じ文字列を4つ追加すると失敗します。
Bettimms

回答:


49

Base64文字列は文字のみで構成され'A'..'Z', 'a'..'z', '0'..'9', '+', '/'、長さを4の倍数にするために最後に最大3つの「=」が埋め込まれることが多いため、Base64文字列を認識するのは非常に簡単です。しかし、これらを比較する代わりに、 d例外が発生した場合は、無視することをお勧めします。


1
私はあなたが正しい軌道に乗っていると思います。私はいくつかのテストを行なったし、4ではなく3の倍数であるようだ
クリス・マリン

1
エンコードを成功させるには、エンコード時に長さが3の倍数である必要があります。申し訳ありませんが、そうです、そうです...エンコードされた文字列の長さは4の倍数です。そのため、3 '='までパディングします。
Anirudh Ramanathan 2011

4
あなたが最初に複数のものについて言及したので、正しいとマークされました。ソリューションの実装で質問を更新しました。問題が発生した場合はお知らせください。
Chris Mullins、2011年

47

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

1
そんなことは知らなかった。c#7.2を使用している場合、これが新しい答えになると思います
Chris Mullins

4
.NET Core 2.1+または.NET Standard 2.1+でのみ機能
Cyrus

C#はコンパイラで、TryFromBase64Stringは.NETフレームワークのAPIです:)
user960567

これは、パディングされていない文字列に対してfalseを返しますConvert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span<byte>(new byte[base64.Length]), out _)。修正は次のとおりです。ありがとうございました。
rvnlord

44

例外をキャッチしたくなかったと言っていましたね。しかし、例外をキャッチする方が信頼性が高いので、先に進んでこの回答を投稿します。

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のおかげで状態を更新しました。


1
base64String.Contains複数回呼び出すとbase64String、文字列が大きい場合にパフォーマンスが低下する可能性があります。
NucS

@NucSそうです、ここではコンパイル済みの正規表現を使用できます。
harsimranb 2016年

1
あなたがチェックできbase64String== null || base64String.Length == 0string.IsNullOrEmpty(base64String)
ダニエル・チューリップ

Base64には問題なく空白(改行など)を含めることができることに注意してください。それらはパーサーによって無視されます。
ティモシー

2
.NETソースコードにアクセスできるようになったので、FromBase64String()関数がこれらすべてのチェックを実行することがわかります。referencesource.microsoft.com/#mscorlib/system/…有効なbase64文字列の場合は、2回チェックしています。例外を試す/キャッチするだけの方が安いかもしれません。
iheartcsharp

16

正規表現は次のようにする必要があります:

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

末尾の「=」記号は1つまたは2つだけ一致し、3つは一致しません。

sチェックされる文字列である必要があります。名前空間のRegex一部ですSystem.Text.RegularExpressions


1
文字列の長さがmod = 4 = 0であるかどうかをチェックしません
calingasan

7

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

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


1
これは異常なケースで、値を使用するのは64でない可能性が高いので、例外のオーバーヘッドを避けたいと思います。以前に確認する方がはるかに高速です。クリアテキストのパスワードから継承した古いシステムをハッシュ値に変換しようとしています。
Chris Mullins

2
正規表現は、タイラーが提案しているものより速くなることはありません。
Vincent Koeman

私の投稿の下部にあるコメントを参照してください。特にハッシュされたパスワードのような小さな文字列の場合、作業している文字列の長さによっては、最初にテストする方が速くなると思います。文字列は正規表現に到達するために4の倍数である必要があり、小さな文字列の正規表現は非常に大きな文字列よりも高速です。
Chris Mullins、2011年

2
完璧な世界では、ビジネスロジックが設計されている、または例外をスローすることがわかっているコードを記述しないでください。例外のtry / catchブロックは、決定ブロックとして使用するにはコストがかかりすぎます。
Ismail Hawayel、2018

7

完全を期すために、いくつかの実装を提供したいと思います。一般的に、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;
    }
}

4

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

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

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

またはbase64のように

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

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


同意します、ケースの詳細は、特定の追加の高速チェックも課します。プレーンテキストの接続文字列とエンコードされたbase64のように。
Oybek 2014年

2

Knibb Highフットボールルール!

これは比較的速く正確であるはずですが、徹底的なテストは行わなかったと認めます。

コストのかかる例外や正規表現を回避し、検証に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
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;
            }
        }

なぜ最初に変換してから他のものを制御しようとするのか
Snr

@Snrあなたは正しい。私はこれが彼が変更する必要があると思います:if(value.EndsWith( "=")){value = value.Trim(); int mod4 = value.Length%4; if(mod4!= 0){falseを返す; } Convert.FromBase64String(value); trueを返します。} else {falseを返す; }
ワジドカーン

2

このように使用するので、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

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

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

1

これは本当に不可能です。「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.
}

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


0

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


私が理解した場合、終了文字はエンコードされたテキストの最終的な長さによって異なります。したがって、エンコードされたテキストが長さ%4でない場合、 '='が含まれます。
Rafael Diego Nicoletti、2018年

0

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

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

これにより、文字列にはAZ、az、0-9、「+」、「/」、「=」、および空白のみが含まれます。


それは必ずしも確実な方法ではありません。Base64は=、最後に文字を使用していくつかのパディングを行います。そのパディングが無効な場合、正規表現と一致していても、それは正しいbase64エンコーディングではありません。これをデモするには=、最後に1または2が付いたbase 64文字列を見つけて削除し、デコードしてみます。
vcsjones 2011年

OPがstrが正当なBase64であるかどうかではなく、不正な文字をトラップするように求めたと思います。後者の場合は正しいですが、Base64のパディングエラーは、例外を使用してトラップする方が簡単です。
Rob Raisch、2011年

正しくありません。少なくとも.Netバージョンのbase64パーサーはパディングを完全に無視します。
Jay

0

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

またはより良いまだこのスタックオーバーフローの質問をチェックしてください


0

非常によく似た要件があり、ユーザーに<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;
        }
    }
}

ご覧のとおり、を<canvas>使用するとデフォルトで返されるimage / pngタイプの文字列が必要です.toDataURL()


0

Base64または通常の文字列を確認する

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

}


0

すべての回答は1つの関数に要約され、結果が正確であることを100%保証します。


1)関数を以下のように使用します。

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

2)以下は関数です:

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

-1

正規表現のアイデアチェックが気に入っています。正規表現は高速で、コーディングのオーバーヘッドを節約できる場合があります。元の問い合わせには、これだけを行う更新がありました。しかし、文字列がnullにならないとは思いもしません。拡張機能を展開して、ソース文字列の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);

    }

これは失敗します。「aaaa」のように同じ4文字の文字列を渡してみてください。
Bettimms
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.