Java 构造和验证Gigya签名

Java 构造和验证Gigya签名,java,encoding,base64,gigya,Java,Encoding,Base64,Gigya,我编写了一个方法,根据gigya的签名,根据指定的时间戳和UID来验证gigya签名。以下是Gigya的psuedo代码: string constructSignature(string timestamp, string UID, string secretKey) { // Construct a "base string" for signing baseString = timestamp + "_" + UID; // Convert the base str

我编写了一个方法,根据gigya的签名,根据指定的时间戳和UID来验证gigya签名。以下是Gigya的psuedo代码:

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

以下是我的方法(省略异常处理):

此方法返回
false
,即使它不应该返回。有人能发现我的实现有什么问题吗?我想知道调用方或gigya本身是否存在问题-“您的方法已签出”是一个有效答案

我使用ApacheCommons的类进行编码

在中还可以找到关于签名的进一步(有些冗余)信息,以防有所帮助

为了进一步澄清这一点
uid
时间戳
,以及
签名
都是从gigya设置的Cookie中获取的。为了验证这些文件是否被伪造,我正在使用
uid
时间戳
,并确保可以使用我的密钥重建
签名
。事实上,当它不应该表明在流程中的某个点上存在bug/格式问题时,它失败了,无论是我的方法、前端还是gigya本身。这个问题的目的本质上是为了排除上述方法中的错误

注意:我还尝试了URL编码
uid

String baseString = timestamp + "_" + URLEncoder.encode(uid, "UTF-8");
虽然我认为这无关紧要,因为它只是一个整数。
时间戳
也是如此

更新:

根本问题已经解决,但问题本身仍然悬而未决。有关更多详细信息,请参阅

更新2:

原来我对我使用的apache的
Base64
类感到困惑——我的代码没有使用,而是使用了。这种混乱源于我的项目中大量的第三方库,以及我多年来对Apache库中的许多
Base64
实现的无知——我现在意识到这是为了解决这个问题。说到编码,我好像迟到了

在切换Commons编解码器的版本后,该方法的行为正常


我要把悬赏奖颁给,因为我是现场的,但请把两个答案都投票给他们,因为他们有着卓越的洞察力!我会让悬赏暂时开放,这样他们就能得到应有的关注。

代码审查时间到了!我喜欢做这些。让我们检查一下你的解决方案,看看我们的失败之处

在散文中,我们的目标是用下划线将时间戳和UID连接在一起,将UTF-8的结果强制为字节数组,将给定的Base64密钥强制为第二个字节数组,将两个字节数组一起,然后将结果转换回Base64。很简单,对吧

(是的,该伪代码有一个bug。)

现在,让我们逐步了解您的代码:

public boolean verifyGigyaSig(String uid, String timestamp, String signature) {
这里的方法签名很好。尽管很明显,您需要确保创建的时间戳和正在验证的时间戳使用完全相同的格式(否则,这将始终失败),并且字符串是UTF-8编码的

()

这很好(,)。但是,在将来,考虑显式使用字符串连接,而不是依赖.< /P> 注意到目前为止的文档在是否使用“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);
是的,这是正确的(,)。我没有什么要补充的

    // Convert the signature to a BASE64
    String calculatedSignature = Base64.encodeBase64String(signatureBytes);
正确,而且

    // Return true iff constructed signature equals specified signature
    return signature.equals(calculatedSignature);
}
。。。对的忽略注意事项和实现说明,您的代码会按过程检出

不过,我想推测几点:

  • 您是否按照定义,用UTF-8编码UID或时间戳的输入字符串?如果你没有做到这一点,你就不会得到你期望的结果

  • 您确定密钥正确且编码正确吗?确保在调试器中检查此选项

  • 因此,如果您可以使用Java或其他语言访问签名生成算法,请在调试器中验证整个过程。如果做不到这一点,合成一个将帮助您检查您的工作,因为

  • 还应该报告伪代码错误

    我相信在这里检查您的工作,特别是字符串编码,将揭示正确的解决方案


    编辑: 我核对了一下。测试代码:

    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);
        }
    }
    
    我在一个调试器中验证了这一点,以防出现我在可视化分析中无法检测到的空白,并且断言没有命中。他们一模一样。为了确定,我还检查了一段

    以下是sans Javadoc(作者:Raviv Pavel):

    根据我在上面所做的一些更改更改函数签名,并运行此测试用例,可以正确验证这两个签名:

    // 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编解码器似乎很好。

    我将仔细研究一下您的Base-64编码和解码

    您是否使用第三方库进行此操作?如果是,哪一个?如果没有,您能否发布自己的实现或至少一些示例输入和输出(用十六进制表示字节)

    有时所使用的“额外”基64字符(用字符替换“/”和“+”)之间存在差异。也可以省略填充,这将导致字符串比较失败


    正如我所怀疑的,是Base-64编码导致了这种差异。但是,造成问题的是尾随空格,而不是填充或符号的差异

    您使用的方法总是将CRLF附加到其输出。Gigya签名不包括本tra
    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==
    
    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);
    }
    
    public static void main(String... argv)
      throws Exception
    {
      final String u = "";
      final String t = "";
      final String s = MyConfig.getGigyaSecretKey();
    
      final String signature = sign(u, t, s);
      System.out.print("Original valid? ");
      /* This prints "false" */
      System.out.println(SigUtils.validateUserSignature(u, t, s, signature));
    
      final String stripped = signature.replaceAll("\r\n$", "");
      System.out.print("Stripped valid? ");
      /* This prints "true" */
      System.out.println(SigUtils.validateUserSignature(u, t, s, stripped));
    }
    
    /* This is the original computation included in the question. */
    static String sign(String uid, String timestamp, String key)
      throws Exception
    {
      String baseString = timestamp + "_" + uid;
      byte[] baseBytes = baseString.getBytes("UTF-8");
      byte[] secretKeyBytes = Base64.decodeBase64(key);
      Mac mac = Mac.getInstance("HmacSHA1");
      mac.init(new SecretKeySpec(secretKeyBytes, "HmacSHA1"));
      byte[] signatureBytes = mac.doFinal(baseBytes);
      return Base64.encodeBase64String(signatureBytes);
    }
    
    if (SigUtils.validateUserSignature(uid, timestamp, secretKey, signature)) { ... }