Raw HMAC-SHA1 in C# -- hmac_hash() PHP equivalent
<p>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:</p>

<ol>
<li>Build a SHA1 HMAC using your multipass API key and your finished multipass token.</li>
<li>Base64 encode the resulting HMAC.</li>
</ol>

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

<pre><code>// 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);
</code></pre>

<p>^ 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</p>

<p>Next, my C# code that is <em>not</em> working correctly:</p>

<pre><code>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));
}
</code></pre>

<p>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. </p>

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

<p>Edit: here is my multipass generation code.</p>

<pre><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 &lt; 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 &lt; 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 "_"
        }
</code></pre>
