C#中的原始HMAC-SHA1 - hmac_hash()PHP等效

I am attempting to integrate Desk.com's Multipass SSO into my website, and am having trouble generating the correct HMAC-SHA1 signature (so say the error logs). Here are the directions from Desk.com's website:

  1. Build a SHA1 HMAC using your multipass API key and your finished multipass token.
  2. Base64 encode the resulting HMAC.

According to the logs, my multipass token appears to be correct. First, the code in PHP that works perfectly:

// Build an HMAC-SHA1 signature using the multipass string and your API key
$signature = hash_hmac("sha1", $multipass, $api_key, true);
// Base64 encode the signature
$signature = base64_encode($signature);

^ note that hash_hmac's 'true' value is outputting information in raw binary - I'm not sure if this is the case in my C# code

Next, my C# code that is not working correctly:

protected string getSignature(string multipass)
{
     string api_key = "my_key_goes_here";
     HMACSHA1 hmac = new HMACSHA1(Encoding.ASCII.GetBytes(api_key));
     hmac.Initialize();
     byte[] buffer = Encoding.ASCII.GetBytes(multipass);
     string signature = BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();
     return Convert.ToBase64String(Encoding.ASCII.GetBytes(signature));
}

This is the result of (literally) hours of searching and trying multiple different ways. I would be very grateful if I could get this figured out.

If you need a reference, check out this page by Desk.com: http://dev.desk.com/docs/portal/multipass. It has code examples and outlines the instructions for completing the code.

Edit: here is my multipass generation code.

protected string getMultipass(UserData user_data)
        {
            // Encode the data into a JSON object
            JavaScriptSerializer s = new JavaScriptSerializer();
            string json_data = s.Serialize(user_data);

            // Acquire the Web.config appSettings
            string site_key = "my_site_here";
            string api_key = "my_key_here";
            string iv = "OpenSSL for Ruby";

            // Using byte arrays now instead of strings
            byte[] encrypted = null;
            byte[] bIV = Encoding.ASCII.GetBytes(iv);
            byte[] data = Encoding.ASCII.GetBytes(json_data);

            // XOR the first block (16 bytes)
            // once before the full XOR
            // so it gets double XORed
            for (var i = 0; i < 16; i++)
                data[i] = (byte)(data[i] ^ bIV[i]);

            // Pad using block size of 16 bytes
            int pad = 16 - (data.Length % 16);
            Array.Resize(ref data, data.Length + pad);
            for (var i = 0; i < pad; i++)
                data[data.Length - pad + i] = (byte)pad;

            // Use the AesManaged object to do the encryption
            using (AesManaged aesAlg = new AesManaged())
            {
                aesAlg.IV = bIV;
                aesAlg.KeySize = 128;

                // Create the 16-byte salted hash
                SHA1 sha1 = SHA1.Create();
                byte[] saltedHash = sha1.ComputeHash(Encoding.UTF8.GetBytes(api_key + site_key), 0, (api_key + site_key).Length);
                Array.Resize(ref saltedHash, 16);
                aesAlg.Key = saltedHash;

                // Encrypt using the AES managed object
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        csEncrypt.Write(data, 0, data.Length);
                        csEncrypt.FlushFinalBlock();
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }

            // Return the Base64-encoded encrypted data
            return Convert.ToBase64String(encrypted, Base64FormattingOptions.None)
                .TrimEnd("=".ToCharArray()) // Remove trailing "=" characters
                .Replace("+", "-") // Change "+" to "-"
                .Replace("/", "_"); // Change "/" to "_"
        }

You can see the following code that works:

static string create(string userDetails) { 
      string accountKey = "YOUR_ACCOUNT_KEY";
      string apiKey = "YOUR_API_KEY";        
      string initVector = "OpenSSL for Ruby"; // DO NOT CHANGE

      byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
      byte[] keyBytesLong;
      using( SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider() ) {
        keyBytesLong = sha.ComputeHash( Encoding.UTF8.GetBytes( apiKey + accountKey ) );
      }
      byte[] keyBytes = new byte[16];
      Array.Copy(keyBytesLong, keyBytes, 16);

      byte[] textBytes = Encoding.UTF8.GetBytes(userDetails);
      for (int i = 0; i < 16; i++) {
        textBytes[i] ^= initVectorBytes[i];
      }

      // Encrypt the string to an array of bytes
      byte[] encrypted = encryptStringToBytes_AES(textBytes, keyBytes, initVectorBytes);
      string encoded = Convert.ToBase64String(encrypted);   
      return HttpUtility.UrlEncode(encoded);
    }

    static byte[] encryptStringToBytes_AES(byte[] textBytes, byte[] Key, byte[] IV) {
      // Declare the stream used to encrypt to an in memory
      // array of bytes and the RijndaelManaged object
      // used to encrypt the data.
      using( MemoryStream msEncrypt = new MemoryStream() )
      using( RijndaelManaged aesAlg = new RijndaelManaged() )
      {
        // Provide the RijndaelManaged object with the specified key and IV.
        aesAlg.Mode = CipherMode.CBC;
        aesAlg.Padding = PaddingMode.PKCS7;
        aesAlg.KeySize = 128;
        aesAlg.BlockSize = 128;
        aesAlg.Key = Key;
        aesAlg.IV = IV;
        // Create an encrytor to perform the stream transform.
        ICryptoTransform encryptor = aesAlg.CreateEncryptor();

        // Create the streams used for encryption.
        using( CryptoStream csEncrypt = new CryptoStream( msEncrypt, encryptor, CryptoStreamMode.Write ) ) {
          csEncrypt.Write( textBytes, 0, textBytes.Length );
          csEncrypt.FlushFinalBlock();
        }

        byte[] encrypted = msEncrypt.ToArray(); 
        // Return the encrypted bytes from the memory stream.
        return encrypted;
      }
    }

I hope it works for you.