.NET Framework 1.1用HMACサンプル

会社で作ったシステムのAPIを別システムから呼び出してもらう際、HMAC-SHA256で署名してもらおうと思ったら「うちはレガシーシステムで.NET1.1使ってるからHMACSHA256クラスがないぞゴルァ!」って怒られた。
ただ、.NET1.1にはSHA256自体はあるので、HMACの部分だけC#で作ってサンプルとして提供した。
今のご時世、AmazonさんのAPIを呼び出すのにもHMAC-SHA256が必要なので、レガシーシステムを保守している場合は必要になるかもしれない。
実際にはVS2005を使って書いており.NET1.1ではビルドしてないので、どこかでうっかりビルドエラーがでるかもしれない。でも、本体部分はそのまま.NET1.1で使えるはず。
ちゃんとテストしてないのでご利用は自己責任で。

using System;
using System.Security.Cryptography;

namespace OreOreHMACSampleForDotNet11
{
    public class Hmac
    {
        public static readonly int BLOCK_SIZE = 64;
        public static byte[] ProcessHmac(byte[] srcText, byte[] srcKey, HashAlgorithm algorithm)
        {
            // HMACMD5とHMACSHA1用のテスト:http://www.ipa.go.jp/security/rfc/RFC2202JA.html
            // 処理の内容:http://www.ipa.go.jp/security/rfc/RFC2104JA.html
            if (srcKey.Length > BLOCK_SIZE)
            {
                srcKey = algorithm.ComputeHash(srcKey);
            }
            byte[] key = Padding(srcKey, (char)0x00, BLOCK_SIZE);
            byte[] ipad = Padding(new byte[0], (char)0x36, BLOCK_SIZE);
            byte[] opad = Padding(new byte[0], (char)0x5C, BLOCK_SIZE);

            return algorithm.ComputeHash(Append(Xor(key, opad), algorithm.ComputeHash(Append(Xor(key, ipad), srcText))));
        }
        private static byte[] Padding(byte[] src, char c, int blockSize)
        {
            int length = src.Length;
            byte[] result = new byte[blockSize];
            Array.Copy(src, result, length);
            for (int i = length; i < blockSize; i++)
            {
                result[i] = (byte)c;
            }
            return result;
        }
        private static byte[] Xor(byte[] x, byte[] y)
        {
            int length = Math.Min(x.Length, y.Length);
            byte[] result = new byte[length];
            for (int i = 0; i < length; i++)
            {
                result[i] = (byte)(x[i] ^ y[i]);
            }
            return result;
        }
        private static byte[] Append(byte[] x, byte[] y)
        {
            byte[] result = new byte[x.Length + y.Length];
            Array.Copy(x, result, x.Length);
            Array.Copy(y, 0, result, x.Length, y.Length);
            return result;
        }
    }
}

おまけとして「http://www.ipa.go.jp/security/rfc/RFC2202JA.html」にあったテストのSHA1部分のサンプルも記載。
ここでSHA1ManagerとしているところをSHA256ManagerとすればAmazon APIの署名でも使えるはず(テストコード自体はSHA1ハッシュ値なので注意)。

    public class Program
    {
        public static void Main(string[] args)
        {
            // HMAC自体のテスト
            testHmac();
        }
        private static void testHmac()
        {
            // 参考:http://www.ipa.go.jp/security/rfc/RFC2202JA.html
            byte[] text = null;
            byte[] key = null;
            byte[] expect = null;

            text = Encoding.UTF8.GetBytes("Hi There");
            key = HexStringToBytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
            expect = HexStringToBytes("b617318655057264e28bc0b6fb378c8ef146be00");
            ExecuteSample(text, key, expect);

            text = Encoding.UTF8.GetBytes("what do ya want for nothing?");
            key = Encoding.UTF8.GetBytes("Jefe");
            expect = HexStringToBytes("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");
            ExecuteSample(text, key, expect);

            text = HexStringToBytes("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd");
            key = HexStringToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
            expect = HexStringToBytes("125d7342b9ac11cd91a39af48aa17b4f63f175d3");
            ExecuteSample(text, key, expect);

            text = HexStringToBytes("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd");
            key = HexStringToBytes("0102030405060708090a0b0c0d0e0f10111213141516171819");
            expect = HexStringToBytes("4c9007f4026250c6bc8414f9bf50c86c2d7235da");
            ExecuteSample(text, key, expect);

            text = Encoding.UTF8.GetBytes("Test With Truncation");
            key = HexStringToBytes("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c");
            expect = HexStringToBytes("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04");
            ExecuteSample(text, key, expect);

            text = Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key - Hash Key First");
            key = HexStringToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
            expect = HexStringToBytes("aa4ae5e15272d00e95705637ce8a3b55ed402112");
            ExecuteSample(text, key, expect);

            text = Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data");
            key = HexStringToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
            expect = HexStringToBytes("e8e99d0f45237d786d6bbaa7965c7808bbff1a91");
            ExecuteSample(text, key, expect);
        }
        private static void ExecuteSample(byte[] text, byte[] key, byte[] expect)
        {
            byte[] actual = ProcessHmacSha1(text, key);
            Debug.WriteLine("text [" + BytesToHexString(text) + "]");
            Debug.WriteLine("key [" + BytesToHexString(key) + "]");
            Debug.WriteLine("sig expect [" + BytesToHexString(expect) + "]");
            Debug.WriteLine("sig actual [" + BytesToHexString(actual) + "]");
            for (int i = 0; i < expect.Length; i++)
            {
                if (actual[i] != expect[i])
                {
                    Debug.WriteLine("NG!");
                    return;
                }
            }
            Debug.WriteLine("OK!");
        }
        private static byte[] ProcessHmacSha1(byte[] srcText, byte[] srcKey)
        {
            HashAlgorithm algorithm = new SHA1Managed();
            return Hmac.ProcessHmac(srcText, srcKey, algorithm);
        }
        private static string BytesToHexString(byte[] bytes)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.Length; i++)
            {
                sb.Append(bytes[i].ToString("x2"));
            }
            return sb.ToString();
        }
        private static byte[] HexStringToBytes(string byteString)
        {
            int length = byteString.Length;
            if (length % 2 == 1)
            {
                byteString = "0" + byteString;
                length++;
            }
            ArrayList data = new ArrayList();
            for (int i = 0; i < length - 1; i = i + 2)
            {
                string buf = byteString.Substring(i, 2);
                data.Add(Convert.ToByte(buf, 16));
            }
            return (byte[])data.ToArray(typeof(byte));
        }
    }

※なお、上記コードの「BytesToHexString」「HexStringToBytes」は「http://wawatete.ddo.jp/exec/program/cs/binary_hexandbyte.html」から拝借したものを.NET1.1用に非ジェネリクス化したもの。