构建和验证Gigya签名

Pau*_*ora 10 java base64 encoding 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 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;
}
Run Code Online (Sandbox Code Playgroud)

[原文如此]

这是我的方法(省略了异常处理):

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);
}
Run Code Online (Sandbox Code Playgroud)

这种方法false即使不应该返回.任何人都可以发现我的实施有问题吗?我想知道调用者或gigya本身是否存在问题 - "你的方法检出"是一个有效的答案.

我正在使用Apache Commons的Base64类进行编码.

有关签名的更多(有点多余)信息也可以在Gigya的FAQ中找到,如果有帮助的话.

为了进一步澄清:uid,timestamp并且signature都是从gigya设置的cookie中获取的.为了验证这些不被欺骗,我以uidtimestamp,并确保signature可以使用我的密钥进行重构.它失败的事实是它不应该在过程中的某个时刻指出错误/格式问题,无论是使用我的方法,还是使用我的方法,还是使用gigya本身.这个问题的目的主要是排除上述方法中的错误.

注意:我也尝试过URL编码uid:

String baseString = timestamp + "_" + URLEncoder.encode(uid, "UTF-8");
Run Code Online (Sandbox Code Playgroud)

虽然我认为这不重要,因为它只是一个整数.同样的道理timestamp.

更新:

基本问题已经解决,但问题本身仍然存在.有关详细信息,请参阅我的回答

更新2:

事实证明我对Base64我使用的apache 类中的哪些类感到困惑- 我的代码不是使用Commons Codec版本而是使用Commons Net版本.这种混乱来自我的项目的大量第三方库以及我Base64多年来从Apache库中对许多实现的无知- 这种情况我现在意识到Commons Codec旨在解决这个问题.在编码方面看起来我迟到了.

切换到Commons Codec的版本后,该方法行为正常.

我将把奖金奖励给@erickson,因为他的回答很明显,但请为他们出色的见解提供两个答案!我现在暂时放开赏金,这样他们就得到了他们应得的关注.

eri*_*son 10

我会仔细看看你的Base-64编码和解码.

您是否正在使用第三方库?如果是这样,哪一个?如果没有,你可以发布自己的实现或至少一些示例输入和输出(用十六进制表示字节)?

有时使用的"额外"Base-64字符存在差异(将字符替换为'/'和'+').填充也可以省略,这将导致字符串比较失败.


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

encodeBase64String()您正在使用的方法始终将CRLF附加到其输出.Gigya签名不包括此尾随空格.比较这些字符串是否相等只会因为空格的这种差异而失败.

使用encodeBase64String()Commons Codec库(而不是Commons Net)创建有效签名.

如果我们将签名计算分解出来,并根据Gigya SDK的验证程序测试其结果,我们可以看到删除CRLF会创建一个有效的签名:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 这些问题是为什么我通常比较**解码的**二进制blob而不是它们的base64字符串编码. (2认同)

MrG*_*mez 7

代码审查时间!我喜欢这些.让我们检查一下你的解决方案,看看我们的位置.

在散文中,我们的目标是强调 - 将时间戳和UID连接在一起,将UTF-8的结果强制转换为字节数组,将给定的Base64密钥强制转换为第二个字节数组,SHA-1将两个字节数组放在一起,然后将结果转换回Base64.简单吧?

(是的,那个伪代码有一个bug.)

现在让我们逐步完成您的代码:

public boolean verifyGigyaSig(String uid, String timestamp, String signature) {
Run Code Online (Sandbox Code Playgroud)

你的方法签名很好.虽然很明显,但您需要确保创建的时间戳和正在验证的时间戳使用完全相同的格式(否则,这将始终失败)并且您的字符串是UTF-8编码的.

(关于String编码如何在Java中工作的更多细节)

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

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

这很好(参考文献a,参考文献b).但是,在将来,请考虑StringBuilder明确使用字符串连接,而不是依赖编译器时间优化来支持此功能.

请注意,到目前为止,文档是否与使用"UTF-8"或"UTF8"作为charset标识符不一致.但是,"UTF-8"是可接受的标识符; 我认为"UTF8"是为了传统和兼容性而保留的.

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

拿着它!这破坏了封装.它在功能上是正确的,但如果你把它作为参数传递给你的方法而不是把它从另一个源中拉出来(因此在这种情况下将你的代码耦合到细节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);
Run Code Online (Sandbox Code Playgroud)

是的,这是正确的(参考文献a,参考文献b,参考文献c).我在这里没有什么可补充的.

    // Convert the signature to a BASE64
    String calculatedSignature = Base64.encodeBase64String(signatureBytes);
Run Code Online (Sandbox Code Playgroud)

正确,......

    // Return true iff constructed signature equals specified signature
    return signature.equals(calculatedSignature);
}
Run Code Online (Sandbox Code Playgroud)

......正确的.忽略警告和实施说明,您的代码会在程序上进行检查.

不过我会推测几点:

  1. 你是UTF-8编码的输入字符串为您的UID 您的时间戳,定义在这里?如果你没有这样做,你将不会得到你期望的结果!

  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);
    }
}
Run Code Online (Sandbox Code Playgroud)

简单的测试表明它们的输出具有可比性.输出:

dGhpcyBpcyBteSB0ZXN0IHN0cmluZw==
dGhpcyBpcyBteSB0ZXN0IHN0cmluZw==
Run Code Online (Sandbox Code Playgroud)

我在调试器中验证了这一点,以防可能存在我在视觉分析中无法检测到的空格,并且断言未命中.他们是完全相同的.我还检查了一段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);           
}
Run Code Online (Sandbox Code Playgroud)

根据我上面做的一些更改并运行此测试用例来更改函数签名会导致两个签名都被正确验证:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

当然,正如转载的那样,问题在于Commons Net,而Commons Codec似乎没问题.


Pau*_*ora 5

好吧,我昨天终于从gigya那里听到了关于这个问题的回复,结果发现他们自己的服务器端Java API公开了一个处理这个用例的方法,SigUtils.validateUserSignature:

if (SigUtils.validateUserSignature(uid, timestamp, secretKey, signature)) { ... }
Run Code Online (Sandbox Code Playgroud)

今天我能够验证这个调用的行为是否正确,这样就可以解决眼前的问题,并将整个帖子转化为一种面孔的时刻.

然而:

我仍然对我为什么自己的自制方法不起作用感兴趣(而且无论如何我还有奖励).我将在下周再次检查它,并将其与SigUtils类文件进行比较,以试图找出问题所在.