CRAM-MD5はPOP, IMAPで使用されるチャレンジ・レスポンス方式の認証方法で、HMAC-MD5ハッシュアルゴリズムを用いてパスワードを暗号化する。 IMAPのAUTHENTICATEコマンドおよびPOPのAUTHコマンドにてCRAM-MD5による認証を行う場合のシーケンスは次のようになる。
C: 0001 AUTHENTICATE CRAM-MD5
S: + PDQwMDEzNDQxMTIxNDM1OTQuMTI3MjQ5OTU1MEBtYWlsLmV4YW1wbGUubmV0Pg==
C: dXNlciAxZDFiOTFiN2FkM2ZjMjYxZjljZDgyOTUzMWYyMzVlYw==
S: 0001 OK Logged in.
C: AUTH CRAM-MD5
S: + PDQwMDEzNDQxMTIxNDM1OTQuMTI3MjQ5OTU1MEBtYWlsLmV4YW1wbGUubmV0Pg==
C: dXNlciAxZDFiOTFiN2FkM2ZjMjYxZjljZDgyOTUzMWYyMzVlYw==
S: +OK
AUTHENTICATEコマンド/AUTHコマンドで送受信される内容はBase64エンコードされるため、上記の送受信内容をデコードすると次のようになっている。 ここではユーザー名にuser
、パスワードにpass
を使用している。
C: 0001 AUTHENTICATE CRAM-MD5
S: + <4001344112143594.1272499550@mail.example.net>
C: user 1d1b91b7ad3fc261f9cd829531f235ec
S: 0001 OK Logged in.
C: AUTH CRAM-MD5
S: + <4001344112143594.1272499550@mail.example.net>
C: user 1d1b91b7ad3fc261f9cd829531f235ec
S: +OK
AUTHENTICATEコマンド/AUTHコマンドを送信すると、サーバーからはまずタイムスタンプとホスト名を含むチャレンジコードがBase64エンコードされた上で返される。 上記の例では<4001344112143594.1272499550@mail.example.net>
がチャレンジコードである。
クライアントは、サーバーから受信したチャレンジコードに対してパスワードをキーにしたHMAC-MD5ハッシュアルゴリズムを適用し、さらに認証を行うユーザ名とHMAC-MD5を適用したチャレンジコードを連結し、それをBase64エンコードしてレスポンスとして返す。 上記の例では1d1b91b7ad3fc261f9cd829531f235ec
がHMAC-MD5ハッシュ化したチャレンジコードである。
この処理を図式化して表記すると以下のようになる。
C: AUTH CRAM-MD5
S: + Base64(チャレンジコード)
C: Base64(ユーザ名 HMAC-MD5(パスワード, チャレンジコード))
S: +OK
(ここでチャレンジコード = <タイムスタンプ@ホスト名>)
C#でのCRAM-MD5認証の実装
Mono/.NET FrameworkではHMAC-MD5ハッシュ値の計算にSystem.Security.Cryptography.HMACMD5クラスのComputeHashメソッドを用いることが出来る。 次の例は受信したチャレンジコードと、ユーザ名、パスワードを引数としてCRAM-MD5認証のレスポンスを返すメソッド。
public string AuthenticateCramMD5(string challenge, string username, string password)
{
// Base64デコードしたチャレンジコードに対してパスワードをキーとしたHMAC-MD5ハッシュ値を計算する
var keyed = (new HMACMD5(Encoding.ASCII.GetBytes(password))).ComputeHash(Convert.FromBase64String(challenge));
// 計算したHMAC-MD5ハッシュ値のbyte[]を16進表記の文字列に変換する
var digest = string.Empty;
foreach (var octet in keyed) {
digest += octet.ToString("x02");
}
// ユーザ名と計算したHMAC-MD5ハッシュ値をBase64エンコードしてレスポンスとして返す
return Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0} {1}", username, digest)));
}
C#でのHMAC-MD5の実装
次の例はRFC 2104で記述されている内容に基づいて作成したHMAC-MD5ハッシュ値の計算を行うメソッド。 HMACMD5.ComputeHashメソッドに相当するものを実装したもの。 (コメントはRFCより抜粋)
public byte[] HmacMd5(byte[] key, byte[] text)
{
const int B = 64;
// 2. Definition of HMAC
// The definition of HMAC requires a cryptographic hash function,
// which we denote by H,
var H = new MD5CryptoServiceProvider();
// and a secret key K
var K = new byte[B];
// We assume H to be a cryptographic hash function where data is
// hashed by iterating a basic compression function on blocks of data.
// We denote by B the byte-length of such blocks
// (B=64 for all the above mentioned examples of hash functions)
// The authentication key K can be of any length up to B, the
// block length of the hash function.
if (B < key.Length)
throw new InvalidOperationException("key length is too long");
// We define two fixed and different strings ipad and opad as follows
// (the 'i' and 'o' are mnemonics for inner and outer):
var ipad = new byte[B]; // ipad = the byte 0x36 repeated B times
var opad = new byte[B]; // opad = the byte 0x5C repeated B times
// To compute HMAC over the data `text' we perform
// H(K XOR opad, H(K XOR ipad, text))
// (1) append zeros to the end of K to create a B byte string
key.CopyTo(K, 0);
for (var i = key.Length; i < B; i++) {
K[i] = 0x00;
}
for (var i = 0; i < B; i++) {
// (2) XOR (bitwise exclusive-OR) the B byte string computed in
// step (1) with ipad
ipad[i] = (byte)(K[i] ^ 0x36);
// (5) XOR (bitwise exclusive-OR) the B byte string computed in
// step (1) with opad
opad[i] = (byte)(K[i] ^ 0x5c);
}
// (3) append the stream of data 'text' to the B byte string
// resulting from step (2)
var hi = new byte[ipad.Length + text.Length]; // hi = K XOR ipad, text
ipad.CopyTo(hi, 0);
text.CopyTo(hi, ipad.Length);
// (4) apply H to the stream generated in step (3)
var Hi = H.ComputeHash(hi); // Hi = H(hi) = H(K XOR ipad, text)
// (6) append the H result from step (4) to the B byte string
// resulting from step (5)
var ho = new byte[opad.Length + Hi.Length]; // ho = K XOR opad, H(hi)
opad.CopyTo(ho, 0);
Hi .CopyTo(ho, opad.Length);
// (7) apply H to the stream generated in step (6) and output
// the result
H.Initialize();
return H.ComputeHash(ho); // Ho = H(ho) = H(K XOR opad, H(hi))
}