10

署名を作成するための Gigya の指示に基づいて、指定されたタイムスタンプと UID に対して gigya 署名を検証するメソッドを作成しました。これを行うためのGigyaの疑似コードは次のとおりです。

string constructSignature(string timestamp, string UID, string secretKey) {
    // Construct a "base string" for signing
    baseString = timestamp + "_" + UID;
    // Convert the base string into a binary array
    binaryBaseString = ConvertUTF8ToBytes(baseString);
    // Convert secretKey from BASE64 to a binary array
    binaryKey = ConvertFromBase64ToBytes(secretKey);
    // Use the HMAC-SHA1 algorithm to calculate the signature 
    binarySignature = hmacsha1(binaryKey, baseString);
    // Convert the signature to a BASE64
    signature = ConvertToBase64(binarySignature);
    return signature;
}

[原文のまま]

これが私の方法です(例外処理は省略されています):

public boolean verifyGigyaSig(String uid, String timestamp, String signature) {

    // Construct the "base string"
    String baseString = timestamp + "_" + uid;

    // Convert the base string into a binary array
    byte[] baseBytes = baseString.getBytes("UTF-8");

    // Convert secretKey from BASE64 to a binary array
    String secretKey = MyConfig.getGigyaSecretKey();
    byte[] secretKeyBytes = Base64.decodeBase64(secretKey);

    // Use the HMAC-SHA1 algorithm to calculate the signature 
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(new SecretKeySpec(secretKeyBytes, "HmacSHA1"));
    byte[] signatureBytes = mac.doFinal(baseBytes);

    // Convert the signature to a BASE64
    String calculatedSignature = Base64.encodeBase64String(signatureBytes);

    // Return true iff constructed signature equals specified signature
    return signature.equals(calculatedSignature);
}

このメソッドは、返すfalseべきではない場合でも返されます。誰かが私の実装に問題があることを見つけることができますか? 呼び出し元または gigya 自体に問題があるのではないかと考えています。 「あなたのメソッドはチェックアウトします」は有効な答えです。

エンコードには Apache Commons のBase64クラスを使用しています。

署名に関する詳細な (やや冗長な) 情報は、Gigya の FAQにもあります。

これをさらに明確にするためにuidtimestamp、およびsignatureはすべて gigya によって設定された Cookie から取得されています。これらがなりすましではないことを確認するために、 と を取得uidし、秘密鍵を使用して再構築できることtimestampを確認します。signature私の方法、フロントエンド、または gigya 自体のいずれかで、プロセスのある時点でバグ/フォーマットの問題を示してはならないときに失敗するという事実。この質問の目的は、基本的に上記の方法のバグを除外することです。

注: URLエンコーディングも試しましたuid

String baseString = timestamp + "_" + URLEncoder.encode(uid, "UTF-8");

単なる整数なので、これは問題ではないと思いますが。についても同様ですtimestamp

アップデート:

根本的な問題は解決されましたが、質問自体は未解決のままです。詳細については、私の回答を参照してください。

更新 2:

Base64私が使用していた Apache のクラスについて混乱していたことが判明しました。私のコードは、 Commons Codec バージョンではなく、Commons Net バージョンを使用していました。この混乱は、私のプロジェクトの大量のサードパーティ ライブラリと、何Base64年にもわたって Apache ライブラリからの多くの実装を知らなかったことから生じました。Commons Codecが対処することを意図した状況であることがわかりました。エンコーディングに関しては、パーティーに遅れているようです。

Commons Codec のバージョンに切り替えた後、メソッドは正しく動作します。

@ericksonの回答は的を射ていたので、報奨金を授与しますが、優れた洞察のために両方の回答に賛成票を投じてください! 彼らが当然の注目を集めるように、私は今のところ懸賞金を開いたままにします。

4

3 に答える 3

7

コードレビュータイム!私はこれらをするのが大好きです。あなたのソリューションをチェックして、どこに落ちるか見てみましょう。

散文的に言えば、私たちの目標は、タイムスタンプと UID をアンダースコアで結合し、結果を UTF-8 からバイト配列に変換し、特定の Base64 秘密鍵を 2 番目のバイト配列に変換し、SHA-1で 2 つのバイト配列を結合することです。次に、結果を Base64 に変換します。シンプルですね。

(はい、その疑似コードにはバグがあります。)

コードをステップ実行しましょう。

public boolean verifyGigyaSig(String uid, String timestamp, String signature) {

ここでのメソッド署名は問題ありません。明らかに、作成したタイムスタンプと検証しているタイムスタンプがまったく同じ形式を使用していること (そうしないと、これは常に失敗します) と、文字列が UTF-8 でエンコードされていることを確認する必要があります。

(文字列エンコーディングが Java でどのように機能するかについての詳細)

    // Construct the "base string"
    String baseString = timestamp + "_" + uid;

    // Convert the base string into a binary array
    byte[] baseBytes = baseString.getBytes("UTF-8");

これで問題ありません (参照 a参照 b )。ただし、将来的には、この機能をサポートするためにコンパイラ時の最適化にStringBuilder頼るのではなく、for String 連結を明示的に使用することを検討してください。

この時点までのドキュメントは、文字セット識別子として「UTF-8」または「UTF8」のどちらを使用するかについて一貫性がないことに注意してください。ただし、「UTF-8」は受け入れられる識別子です。「UTF8」は、レガシーと互換性の目的で保持されていると思います。

    // Convert secretKey from BASE64 to a binary array
    String secretKey = MyConfig.getGigyaSecretKey();
    byte[] secretKeyBytes = Base64.decodeBase64(secretKey);

持て!これはカプセル化を破ります。機能的には正しいですが、別のソースから引き込むよりも、これをパラメーターとしてメソッドに渡した方がよいでしょう (したがって、この場合、コードを の詳細に結び付けますMyConfig)。別にこれでもいいです。

    // Use the HMAC-SHA1 algorithm to calculate the signature 
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(new SecretKeySpec(secretKeyBytes, "HmacSHA1"));
    byte[] signatureBytes = mac.doFinal(baseBytes);

はい、これは正しいです (参照 a参照 b参照 c )。ここに追加するものは何もありません。

    // Convert the signature to a BASE64
    String calculatedSignature = Base64.encodeBase64String(signatureBytes);

正しい、そして...

    // Return true iff constructed signature equals specified signature
    return signature.equals(calculatedSignature);
}

... 正しい。警告と実装上の注意を無視して、コードは手続き的にチェックアウトします。

ただし、いくつかの点について推測します。

  1. ここで定義されているように、UIDまたはタイムスタンプの入力文字列を UTF-8 エンコードしていますか? そうしないと、期待する結果が得られません。

  2. 秘密鍵が正しく、適切にエンコードされていますか? デバッガーでこれを確認してください。

  3. さらに言えば、Java などの署名生成アルゴリズムにアクセスできる場合は、デバッガーで全体を確認してください。これに失敗した場合は、ドキュメントでエンコーディングに関する注意事項が提起されているため、合成すると作業を確認するのに役立ちます。

疑似コードのバグも報告する必要があります。

ここでの作業、特に文字列エンコーディングを確認すると、正しい解決策が明らかになると思います。


編集:

の実装をBase64Apache Commons Codec のに対してチェックしました。テストコード:

import org.apache.commons.codec.binary.Base64;
import static com.gigya.socialize.Base64.*;

import java.io.IOException;

public class CompareBase64 {
    public static void main(String[] args) 
      throws IOException, ClassNotFoundException {
        byte[] test = "This is a test string.".getBytes();
        String a = Base64.encodeBase64String(test);
        String b = encodeToString(test, false);
        byte[] c = Base64.decodeBase64(a);
        byte[] d = decode(b);
        assert(a.equals(b));
        for (int i = 0; i < c.length; ++i) {
            assert(c[i] == d[i]);
        }
        assert(Base64.encodeBase64String(c).equals(encodeToString(d, false)));
        System.out.println(a);
        System.out.println(b);
    }
}

簡単なテストは、出力が同等であることを示しています。出力:

dGhpcyBpcyBteSB0ZXN0IHN0cmluZw==
dGhpcyBpcyBteSB0ZXN0IHN0cmluZw==

視覚的な分析で検出できない空白があり、アサートがヒットしなかった場合に備えて、デバッガーでこれを確認しました。それらは同一です。念のため、lorem ipsumの段落もチェックしました。

Javadocを使用しない署名ジェネレータのソース コードは次のとおりです (著者のクレジット: Raviv Pavel)。

public static boolean validateUserSignature(String UID, String timestamp, String secret, String signature) throws InvalidKeyException, UnsupportedEncodingException
{
    String expectedSig = calcSignature("HmacSHA1", timestamp+"_"+UID, Base64.decode(secret)); 
    return expectedSig.equals(signature);   
}

private static String calcSignature(String algorithmName, String text, byte[] key) throws InvalidKeyException, UnsupportedEncodingException  
{
    byte[] textData  = text.getBytes("UTF-8");
    SecretKeySpec signingKey = new SecretKeySpec(key, algorithmName);

    Mac mac;
    try {
        mac = Mac.getInstance(algorithmName);
    } catch (NoSuchAlgorithmException e) {
        return null;
    }

    mac.init(signingKey);
    byte[] rawHmac = mac.doFinal(textData);

    return Base64.encodeToString(rawHmac, false);           
}

上記で行った変更の一部に合わせて関数シグネチャを変更し、このテスト ケースを実行すると、両方のシグネチャが正しく検証されます。

// Redefined your method signature as: 
//  public static boolean verifyGigyaSig(
//      String uid, String timestamp, String secret, String signature)

public static void main(String[] args) throws 
  IOException,ClassNotFoundException,InvalidKeyException,
  NoSuchAlgorithmException,UnsupportedEncodingException {

    String uid = "10242048";
    String timestamp = "imagine this is a timestamp";
    String secret = "sosecure";

    String signature = calcSignature("HmacSHA1", 
              timestamp+"_"+uid, secret.getBytes());
    boolean yours = verifyGigyaSig(
              uid,timestamp,encodeToString(secret.getBytes(),false),signature);
    boolean theirs = validateUserSignature(
              uid,timestamp,encodeToString(secret.getBytes(),false),signature);
    assert(yours == theirs);
}

もちろん、再現されているように、問題は Commons Net にありますが、Commons Codec は問題ないようです。

于 2012-04-13T02:25:50.990 に答える
5

昨日、ついにこの問題についてgigyaから連絡がありましたが、独自のサーバー側Java APIが、このユースケースを処理するためのメソッドを公開していることがわかりましたSigUtils.validateUserSignature

if (SigUtils.validateUserSignature(uid, timestamp, secretKey, signature)) { ... }

今日、私はこの呼び出しが正しく動作していることを確認できたので、当面の問題を解決し、この投稿全体を私にとって一種の面倒な瞬間に変えます。

でも:

自分のホームロール方式が機能しない理由にはまだ興味があります(とにかく賞金があります)。来週もう一度調べて、SigUtilsクラスファイルと比較して、何が悪かったのかを突き止めます。

于 2012-04-14T20:58:06.867 に答える