构建和validationGigya签名

我根据Gigya 构建签名的说明编写了一个方法来validation指定时间戳和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; } 

[原文如此]

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

 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中找到,如果有帮助的话。

为了进一步说明这一点uidtimestampsignature都是从gigya设置的cookie中获取的。 为了validation这些没有被欺骗,我正在使用uidtimestamp ,并确保可以使用我的密钥重建signature 。 事实上,当它不应该在过程中的某个时刻指出错误/格式问题,无论是使用我的方法,还是使用我的方法,还是使用gigya本身。 这个问题的目的主要是排除上述方法中的错误。

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

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

虽然我认为这不重要,因为它只是一个整数。 timestamp

更新:

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

更新2:

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

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

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

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

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

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


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

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

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

如果我们将签名计算分解出来,并根据Gigya SDK的validation程序测试其结果,我们可以看到删除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); } 

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

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

(是的,那个伪代码有一个错误。)

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

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

你的方法签名很好。 虽然很明显,但您需要确保创建的时间戳和正在validation的时间戳使用完全相同的格式(否则,这将始终失败)并且您的字符串是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"); 

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

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

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

拿着它! 这破坏了封装 。 它在function上是正确的,但是如果你把它作为参数传递给你的方法而不是从另一个源中引入它会更好(因此在这种情况下将你的代码耦合到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. 您是否使用UTF-8编码您的UID 时间戳的输入字符串,如此处所定义的那样? 如果你没有这样做,你将不会得到你期望的结果!

  2. 您确定密钥是否正确且编码正确吗? 确保在调试器中检查这一点!

  3. 就此而言,如果您可以使用Java或其他方式访问签名生成算法,请在调试器中validation整个事物。 如果不这样做,合成一个将帮助您检查您的工作,因为文档中提出了编码警告 。

还应该报告伪代码错误。

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


编辑:

我检查了他们对Apache Commons Codec的 Base64实现 。 测试代码:

 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== 

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

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

 // 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似乎没问题。

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

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

今天我能够validation这个调用是否正常,这样就可以解决眼前的问题,并将整个post转化为一种面对我的问题。

然而:

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