はてなのWSSE認証をC#で実装してみた

プログラムで撮影した自分のコスプレ写真など、はてなフォトライフにプログラムからアップロードするには、はてなのWeb APIを使用する必要があります。*1 はてなのWeb APIを使用するには、WSSE認証が必要なので、そのC#実装をまとめます。
http://developer.hatena.ne.jp/ja/documents/auth/apis/wsse

概要

はてなのユーザIDとパスワードからWSSE認証のヘッダを生成するライブラリ。*2

WSSE認証とは

詳しくは、上記URIgoogle先生に尋ねてください。

要は、Basic認証は、ユーザ名とパスワードが平文でサーバーに送られてしまうから、脆弱だお。だから、ダイジェスト認証の一つのWSSE認証を使うんだお!ってことでしょ?たぶん。

HatenaAccount.cs

using System;
using System.Security.Cryptography;
using System.Text;

namespace Harunakasoft.Library
{
    public class HatenaAccount
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="userName">ユーザ名</param>
        /// <param name="password">パスワード</param>
        public HatenaAccount(string userName, string password)
        {
#if DEBUG
            logger.Debug("はてなアカウント UserName=" + userName);
            logger.Debug("はてなアカウント Password=" + password);
#endif
            if (userName == null)
            {
                throw new ArgumentNullException("ユーザ名がNULL");
            }
            if (userName.Length == 0)
            {
                throw new ArgumentException("ユーザ名が空");
            }
            if (password == null)
            {
                throw new ArgumentNullException("パスワードがNULL");
            }
            if (password.Length == 0)
            {
                throw new ArgumentException("パスワードが空");
            }

            header = MakeWSSEHeader(userName, password);
            logger.Info("はてなアカウントヘッダ作成完了");
            logger.Info(" header=" + header);
        }

        /// <summary>
        /// WSSEヘッダの作成
        /// </summary>
        /// <param name="userName">ユーザ名</param>
        /// <param name="password">パスワード</param>
        /// <returns>WSSEヘッダ</returns>
        private string MakeWSSEHeader(string userName, string password)
        {
            DateTime created = new DateTime(DateTime.Now.Ticks);
            string nonce = GenNounce(40);
            byte[] nonceBytes = Encoding.ASCII.GetBytes(nonce);
            string nonce64 = Convert.ToBase64String(nonceBytes);
            string digest = GenDigest(password, created, nonce);
            string createdString =  created.ToString("s");
            string header = string.Format(
                @"UsernameToken Username=""{0}"", PasswordDigest=""{1}"", Nonce=""{2}"", Created=""{3}""",
                userName, digest, nonce64, createdString);
            return header;
        }

        /// <summary>
        /// Digestの作成
        /// </summary>
        /// <param name="password">パスワード</param>
        /// <param name="created">作成日時</param>
        /// <param name="nonce64">Nonce</param>
        /// <returns>Digest</returns>
        private string GenDigest(string password, DateTime created, string nonce)
        {
            byte[] digest;
            using (SHA1Managed sha1 = new SHA1Managed())
            {
                string digestText = nonce + created.ToString("s") + password;
                byte[] digestBytes= Encoding.ASCII.GetBytes(digestText);
                digest = sha1.ComputeHash(digestBytes);
            }
            string digest64 = Convert.ToBase64String(digest);
            return digest64;
        }

        /// <summary>
        /// Nonceの作成
        /// </summary>
        /// <param name="length">Nonce長</param>
        /// <returns>Nonce</returns>
        private string GenNounce(int length)
        {
            RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider();
            byte[] buffer = new byte[length];
            rnd.GetBytes(buffer);
            string nonce64 = Convert.ToBase64String(buffer);
            return nonce64;
        }

        /// <summary>
        /// ヘッダ
        /// </summary>
        public string WSSEHeader { get { return header; } }
        private readonly string header;

        private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    }
}

*1:もちろん、自分のコスプレ写真をアップロードする必要があるは、別の問題ですw

*2:たぶん、はてな以外でも使えると思われる