Java 构造和验证Gigya签名
我编写了一个方法,根据gigya的签名,根据指定的时间戳和UID来验证gigya签名。以下是Gigya的psuedo代码: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
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);
}
。。。对的忽略注意事项和实现说明,您的代码会按过程检出
不过,我想推测几点:
编辑: 我核对了一下。测试代码:
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)) { ... }