我尝试移植Google代码以获取其验证码(https://github.com/google/recaptcha-java/blob/master/appengine/src/main/java/com/google/recaptcha/STokenUtils.java)的安全令牌,但收效甚微:
原始实用程序具有以下内容:
private static final String CIPHER_INSTANCE_NAME = "AES/ECB/PKCS5Padding";
private static String encryptAes(String input, String siteSecret) {
try {
SecretKeySpec secretKey = getKey(siteSecret);
Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return BaseEncoding.base64Url().omitPadding().encode(cipher.doFinal(input.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static SecretKeySpec getKey(String siteSecret){
try {
byte[] key = siteSecret.getBytes("UTF-8");
key = Arrays.copyOf(MessageDigest.getInstance("SHA").digest(key), 16);
return new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static void main(String [] args) throws Exception {
//Hard coded the following to get a repeatable result
String siteSecret = "12345678";
String jsonToken = "{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}";
System.out.println(" json token: " + jsonToken);
System.out.println(" siteSecret: " + siteSecret);
System.out.println(" Encrypted stoken: " + encryptAes(jsonToken, siteSecret));
鉴于我硬编码的值,我将Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns"
作为加密令牌返回。
我的Java和加密技能不仅有点生疏,而且在C#中并不总是直接模拟。我尝试将encrypeAes()
和getKey()
与以下内容合并,这是不正确的:
public static string EncryptText(string PlainText, string siteSecret)
{
using (RijndaelManaged aes = new RijndaelManaged())
{
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
var bytes = Encoding.UTF8.GetBytes(siteSecret);
SHA1 sha1 = SHA1.Create();
var shaKey = sha1.ComputeHash(bytes);
byte[] targetArray = new byte[16];
Array.Copy(shaKey, targetArray, 16);
aes.Key = targetArray;
ICryptoTransform encrypto = aes.CreateEncryptor();
byte[] plainTextByte = ASCIIEncoding.UTF8.GetBytes(PlainText);
byte[] CipherText = encrypto.TransformFinalBlock(plainTextByte, 0, plainTextByte.Length);
return HttpServerUtility.UrlTokenEncode(CipherText); //Equivalent to java's BaseEncoding.base64Url()?
}
}
C#版本产生的值不正确:Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=
答案 0 :(得分:4)
您的代码几乎按预期工作。只是您以某种方式混淆了Java版本的输出(可能还有C#版本)。
如果我执行你的Java代码(使用Guava 18.0的JDK 7& 8),我得到了
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U
如果我执行你的C#代码(DEMO),我会得到
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U1
因此,C#版本还有一个" 1"在末尾。它应该是一个填充字符,但不是。这意味着HttpServerUtility.UrlTokenEncode()
并未提供符合标准的URL安全Base64编码,您不应该使用它。另请参阅this Q&A。
可以从正常的Base64编码(比较RFC4648中的表1和2)轻松派生URL安全的Base64编码,如Marc Gravell在this answer中所示:
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes) .TrimEnd(padding).Replace('+', '-').Replace('/', '_');
使用:
static readonly char[] padding = { '=' };
那不是全部。如果我们把你的Java输出
Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=
和decrypt它,然后我们得到以下标记:
{"session_id":"4182e173-3a24-4c10-b76c-b85a36be1173","ts_ms":1445786965574}
与您的代码中的令牌不同:
{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}
剩下的主要问题是您使用了无效的JSON。 JSON中的字符串和键需要包含在"
而不是'
。
这意味着加密令牌实际应该是(使用代码中有效版本的令牌):
D9rOP07fYgBfza5vbGsvdPe8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBsAWBDgtdSozv4jS_auBU-CgjlrJ_430LgYcathLLd9U
答案 1 :(得分:3)
这是一个C#实现,它重现与Java代码相同的结果:
class Program
{
public static byte[] GetKey(string siteSecret)
{
byte[] key = Encoding.UTF8.GetBytes(siteSecret);
return SHA1.Create().ComputeHash(key).Take(16).ToArray();
}
public static string EncryptAes(string input, string siteSecret)
{
var key = GetKey(siteSecret);
using (var aes = AesManaged.Create())
{
if (aes == null) return null;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.Key = key;
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
var enc = aes.CreateEncryptor(key, new byte[16]);
return UrlSafeBase64(enc.TransformFinalBlock(inputBytes,0,input.Length));
}
}
// http://stackoverflow.com/a/26354677/162671
public static string UrlSafeBase64(byte[] bytes)
{
return Convert.ToBase64String(bytes).TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
static void Main(string[] args)
{
string siteSecret = "12345678";
string jsonToken = "{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}";
Console.WriteLine(" json token: " + jsonToken);
Console.WriteLine(" siteSecret: " + siteSecret);
Console.WriteLine(EncryptAes(jsonToken, siteSecret));
Console.ReadLine();
}
}
我不知道为什么你说你从Java程序中得到Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns
,因为我没有得到那个输出。我从C#版本和Java版本获得的输出是:
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U
正如你在这里看到的那样:
Java版本是从您的代码中复制/粘贴的,并且正在使用guava-18.0并使用JDK8 x64编译(我不是Java专家,所以我只是添加它们以防它们有任何区别)。