在不同于 .net 的节点中创建的安全令牌

Secure token created in node different from .net

我正在尝试在 nodejs 中实现 reCaptcha Secure Tokens

查看了 Java and in .NET 中的示例并为节点创建了这个版本:

exports.getSecureToken = function() {
  var algorithm = 'aes-128-ecb';
  var tokenObj = { session_id: 'ab0069ec-3c2c-436c-868b-43c7a10db229'/*uuid.v4()*/, ts_ms: 1446560931992/*(new Date()).getTime()*/ };
  var text = JSON.stringify(tokenObj);

  var shaHash = new Buffer(crypto.createHash('sha1').update('6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO'/*config.reCAPTCHASecret*/).digest('hex'), 'hex');

  var key = shaHash.slice(0, 16);

  var cipher = crypto.createCipher(algorithm, key, key);

  var encryptedToken = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');

  var result = encryptedToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

  return result;
};

问题是在 .NET 中我得到了一个可用的令牌(使用 post 中未包含的正确密钥),但在节点中我为相同的输入得到了不同的令牌,但它没有'有效:

.NET - LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI

节点 - EGr7drd1JEylwzLGakZ6dpPRSf2nFdpzHOrJlLZlyHYmVRj5obAw7WjPt4W5l0vsywNEqCQ-2_d7qIZOMiOedianfBrQPOBaOmmq44IOB8Q

我看到密钥和输入在加密之前是相同的(在 .NET 和节点中),所以问题一定(?)是密码,有线索吗?

.NET代码供参考:

public static void Main(string[] args)
{
    //Your code goes here
    Console.WriteLine(EncryptJsonToken(GetJsonToken()));
}

public static string GetJsonToken()
{
  //Example: {"session_id": e6e9c56e-a7da-43b8-89fa-8e668cc0b86f,"ts_ms":1421774317718}
  string jsonRequest = "{" + string.Format("\"session_id\": {0},\"ts_ms\":{1}", "ab0069ec-3c2c-436c-868b-43c7a10db229", 1446560931992) + "}";
  return jsonRequest;
}

public static byte[] getKey()
{
  string secretKey = "6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO";
  SHA1 sha = SHA1.Create();
  byte[] dataToHash = Encoding.UTF8.GetBytes(secretKey);
  byte[] shaHash = sha.ComputeHash(dataToHash);
  byte[] first16OfHash = new byte[16];
  Array.Copy(shaHash, first16OfHash, 16);
  return first16OfHash;
}

public static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
  // Check arguments. 
  if (plainText == null || plainText.Length <= 0)
    throw new ArgumentNullException("plainText");
  if (Key == null || Key.Length <= 0)
    throw new ArgumentNullException("Key");
  if (IV == null || IV.Length <= 0)
    throw new ArgumentNullException("IV");
  byte[] encrypted;
  // Create an AesManaged object 
  // with the specified key and IV. 
  using (AesManaged aesAlg = new AesManaged())
  {
    aesAlg.Key = Key;
    aesAlg.IV = IV;
    aesAlg.Padding = PaddingMode.PKCS7;
    aesAlg.Mode = CipherMode.ECB;

    // Create a decrytor to perform the stream transform.
    ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

    // Create the streams used for encryption. 
    using (MemoryStream msEncrypt = new MemoryStream())
    {
      using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
      {
        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
        {

          //Write all data to the stream.
          swEncrypt.Write(plainText);
        }
        encrypted = msEncrypt.ToArray();
      }
    }
  }
  // Return the encrypted bytes from the memory stream. 
  return encrypted;
}

public static string EncryptJsonToken(string jsonToken)
{
  byte[] encrypted = EncryptStringToBytes_Aes(jsonToken, getKey(), getKey());

  //Base64 encode the encrypted data
  //Also applys the URL variant of base64 encoding, unfortunately the HttpServerUtility.UrlTokenEncode(encrypted) seems to truncate the last value from the string so we can't use it?
  return Convert.ToBase64String(encrypted, Base64FormattingOptions.None).Replace("=", String.Empty).Replace('+', '-').Replace('/', '_');
}

要在 .NET 中调试:DEMO

你有两个问题:

  • 您正在使用 JSON.stringify() 生成有效的 JSON 字符串,但 C# 代码中的 GetJsonToken() 方法不会生成有效的 JSON 字符串。 UUID 缺少 ",由于某种原因,session_id 键和它的值之间有一个 space。您必须在 JavaScript:

    中反映这些差异
    var uuidToken = "ab0069ec-3c2c-436c-868b-43c7a10db229";
    var time = 1446560931992;
    var text = "{\"session_id\": "+uuidToken+",\"ts_ms\":"+time+"}";
    
  • 没有这个功能crypto.createCipher(algorithm, key, key)。但是有crypto.createCipheriv(algorithm, key, iv)createCipher(algorithm, password) 如果有密码而不是您没有的密钥,则可以使用。由于 ECB 模式没有 IV,您可以传入一个空(二进制)字符串作为 IV。

完整代码:

var crypto = require("crypto");

var algorithm = 'aes-128-ecb';

var uuidToken = "ab0069ec-3c2c-436c-868b-43c7a10db229";
var time = 1446560931992;
var text = "{\"session_id\": "+uuidToken+",\"ts_ms\":"+time+"}";
console.log("Token: " + text);

var shaHash = crypto.createHash('sha1').update('6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO').digest();
var key = shaHash.slice(0, 16);

var cipher = crypto.createCipheriv(algorithm, key, "");
var encryptedToken = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');

var result = encryptedToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

console.log("result:   " + result);
console.log("expected: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI");

输出:

Token: {"session_id": ab0069ec-3c2c-436c-868b-43c7a10db229,"ts_ms":1446560931992}
result:   LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI
expected: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI