|
|||||||||||||||||||||||||||||||
| < 前の頁上の頁次の頁 > | |||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||
![]() |
| [拡大] |
拡張は必ず行われます。これは元のメッセージ長を64で割った際のあまりがちょうど56であった場合でも拡張されるということです。メッセージ長が56バイトならば120バイトに、120バイトなら184バイトに拡張されます。
最小で1バイト(0x80の1バイト)、最大で64バイトのデータが付加されて拡張されます。
【引用】
c. オリジナルメッセージの bit 数 l を 2 ワードで表記する。 l < 2^32 であれば、最初のワードはすべて 0 になる。 これらの 2 ワードをメッセージに付加する。 例:オリジナルメッセージが(b)と同じである場合、l = 40 となる ( l はパディングを行う前に計算することに注意)。 40 を 2 ワード表記すると16進数で 00000000 00000028 となる。 それゆえパディング処理により最終的に得られるメッセージは、16進数で次のようになる。
61626364 65800000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000028
次に、元のメッセージのビット数を8バイトの整数値にしてその上位バイトから順に足します。
(この時にビット数がunsigned long {符号なし4バイトの整数値}に収まるのなら、上位4バイトはすべて0x00になります)
このビット長の付加でメッセージの大きさが64の倍数となります。
![]() |
| [拡大] |
例えば“abcde”というメッセージの場合には、そのビット長40を付加します。
![]() |
| [拡大] |
※ メッセージの拡張手順をまとめると以下のようになります。
計算に使用する関数と定数を準備します。
【引用】
SHA-1 では、論理関数のシーケンス f(0), f(1),..., f(79) を使用する。 それぞれの f(t), 0 <= t <= 79 では、3 つの 32bit ワード B, C, D を処理し 1つの 32bit ワードを出力する。 ワード B, C, D に対し、f(t;B,C,D) を以下のように定義する。
f(t;B,C,D) = (B AND C) OR ((NOT B) AND D) ( 0 <= t <= 19) f(t;B,C,D) = B XOR C XOR D (20 <= t <= 39) f(t;B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59) f(t;B,C,D) = B XOR C XOR D (60 <= t <= 79)
SHA-1は80回の演算を行って求めますが、その際に使用する関数を準備します。 各関数はunsigned longのB, C, Dを引数にとり、以下のように定義します。
/* 関数1: 0〜19回目の演算で使用 */
unsigned long f00(unsigned long B, unsigned long C, unsigned long D) {
return (B & C) | ((~B) & D);
}
/* 関数2:20〜39回目の演算で使用 */
unsigned long f20(unsigned long B, unsigned long C, unsigned long D) {
return B ^ C ^ D;
}
/* 関数3:40〜59回目の演算で使用 */
unsigned long f40(unsigned long B, unsigned long C, unsigned long D) {
return (B & C) | (B & D) | (C & D);
}
/* 関数4:60〜79回目の演算で使用 */
unsigned long f60(unsigned long B, unsigned long C, unsigned long D) {
return B ^ C ^ D;
}
【引用】
SHA-1では、定数ワードのシーケンス K(0), K(1), ... , K(79) を使用する。 それぞれは、16 進表記で以下のように定義される。
K(t) = 5A827999 ( 0 <= t <= 19) K(t) = 6ED9EBA1 (20 <= t <= 39) K(t) = 8F1BBCDC (40 <= t <= 59) K(t) = CA62C1D6 (60 <= t <= 79)
算出時にはunsigned long の配列定数 K[] が使用されます。 その配列は以下のように定義されます。
const unsigned long K[] = {
0x5A827999, /* 0〜19回目の演算で使用 */
0x6ED9EBA1, /* 20〜39回目の演算で使用 */
0x8F1BBCDC, /* 40〜59回目の演算で使用 */
0xCA62C1D6 /* 60〜79回目の演算で使用 */
};
64バイト毎にSHA-1の値を算出します。
【引用】
以下の 6.1 と 6.2 で示されている 2 つの方法は、どちらも同じダイジェスト値を出力する。 方法 2 は方法 1 に比べ、32bit ワード 64 個分の記憶域を使用しなくて済むが、ステップ c におけるそれぞれの W[t] のアドレス計算が複雑であるため実行時間が長くなるようである。 同じ結果を出力する他の計算方法も存在する。
計算方法は複数ありますが、ここでは方法1と方法2の二つを紹介します。 方法1は代表的な方法で、方法2はメモリ使用量を抑える事ができるが計算時間が多少長くなるような方法です。
【引用】
メッセージダイジェストは、4 章で示されているメッセージパディングを使用して計算する。
複数ある計算方法のうちの代表的なものです。 この方法では、ステップ1で拡張したメッセージを使用して計算します。
【引用】
計算に際して 2 つのバッファを使用する。 それぞれのバッファは 32bit ワード 5 つ分で構成されている。 さらに 32 bit ワード 80 個分のワードシーケンスを 1 つ使用する。 1 つの 5 ワードバッファにおけるそれぞれのワードを A,B,C,D,E とする。 もうひとつの 5 ワードバッファにおけるそれぞれのワードを H0, H1, H2, H3, H4 とする。 80 ワードシーケンスにおけるそれぞれのワードは W(0), W(1),..., W(79) とする。 そして 1 ワードのバッファ TEMP を使用する。
(中略)
ワードブロックを処理する前に、それぞれの H を以下のように初期化する(16 進で表記している)。
H0 = 67452301 H1 = EFCDAB89 H2 = 98BADCFE H3 = 10325476 H4 = C3D2E1F0
計算に使用する変数を準備します。
unsigned long A, B, C, D, E; unsigned long H0 = 0x67452301; unsigned long H1 = 0xEFCDAB89; unsigned long H2 = 0x98BADCFE; unsigned long H3 = 0x10325476; unsigned long H4 = 0xC3D2E1F0; unsigned long W[80]; unsigned long TEMP;
【引用】
そして M(1), M(2), ... , M(n) の計算を行う。 M(i)の処理を以下のように行う。
算出はステップ1で拡張したメッセージを64バイト毎のブロックに分割して行います。
| ブロック毎の演算 | |||||
|
各ブロックにa〜eの5段階で処理を行い、SHA-1の値を算出します。 方法1-a【引用】 ステップ1で拡張したメッセージを64バイト毎に分割して、配列W[0]〜W[15]にセットします。
int t; /* ループ変数 */
unsigned char *block; /* 拡張したメッセージへのポインタ */
/* 拡張したメッセージへのポインタをblockにセット */
……
/* 配列W[0]〜W[15]に値セット */
for (t = 0; t < 16; t++) {
W[t] = block[t*4 ] << 24;
W[t] |= block[t*4+1] << 16;
W[t] |= block[t*4+2] << 8;
W[t] |= block[t*4+3];
}
方法1-b【引用】 配列W[16]〜W[79]に以下のようにして値をセットします。
/* unsigned long値の各ビットを循環してシフトするマクロ */
#define SHA1CircularShift(bits,word) \
(((word) << (bits)) | ((word) >> (32-(bits))))
int t; /* ループ変数 */
/* 配列W[16]〜W[79]に値セット */
for (t = 16; t < 80; t++) {
W[t] = SHA1CircularShift(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
}
方法1-c【引用】 変数A, B, C, D, Eのそれぞれに、H0, H1, H2, H3, H4の値をセットします。 A = H0; B = H1; C = H2; D = H3; E = H4; 方法1-d【引用】 TEMP = S^5(A) + f(t;B,C,D) + E + W(t) + K(t); E = D; D = C; C = S^30(B); B = A; A = TEMP; それぞれの変数・関数を用いて以下の計算を行います。
/* unsigned long値の各ビットを循環してシフトするマクロ */
#define SHA1CircularShift(bits,word) \
(((word) << (bits)) | ((word) >> (32-(bits))))
int t; /* ループ変数 */
/* 0〜19回目の演算 */
for (t = 0; t < 20; t++) {
TEMP = SHA1CircularShift(5, A) + f00(B, C, D) + E + W[t] + K[0];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
/* 20〜39回目の演算 */
for (t = 20; t < 40; t++) {
TEMP = SHA1CircularShift(5, A) + f20(B, C, D) + E + W[t] + K[1];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
/* 40〜59回目の演算 */
for (t = 40; t < 60; t++) {
TEMP = SHA1CircularShift(5, A) + f40(B, C, D) + E + W[t] + K[2];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
/* 60〜79回目の演算 */
for (t = 60; t < 80; t++) {
TEMP = SHA1CircularShift(5, A) + f60(B, C, D) + E + W[t] + K[3];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
方法1-e【引用】 HO, H1, H2, H3, H4にA, B, C, D, Eを加算します。 H0 += A; H1 += B; H2 += C; H3 += D; H4 += E; |
2つ目の方法です。方法1と比べるとメモリの使用をおさえる事ができますが実行時間が長くなります。
| ブロック毎の演算 | ||||
|
各ブロックにa〜dの4段階で処理を行い、SHA-1の値を算出します。 方法2-a【引用】 ステップ1で拡張したメッセージを64バイト毎に分割して、配列W[0]〜W[15]にセットします。
int t; /* ループ変数 */
unsigned char *block; /* 拡張したメッセージへのポインタ */
/* 拡張したメッセージへのポインタをblockにセット */
……
/* 配列W[0]〜W[15]に値セット */
for (t = 0; t < 16; t++) {
W[t] = block[t*4 ] << 24;
W[t] |= block[t*4+1] << 16;
W[t] |= block[t*4+2] << 8;
W[t] |= block[t*4+3];
}
方法2-b【引用】 変数A, B, C, D, Eのそれぞれに、H0, H1, H2, H3, H4の値をセットします。 A = H0; B = H1; C = H2; D = H3; E = H4; 方法2-c【引用】
s = t AND MASK;
if (t >= 16) W[s] = S^1(W[(s + 13) AND MASK] XOR W[(s + 8) AND MASK]
XOR W[(s + 2) AND MASK] XOR W[s]);
TEMP = S^5(A) + f(t;B,C,D) + E + W[s] + K(t);
E = D; D = C; C = S^30(B); B = A; A = TEMP;
それぞれの変数・関数を用いて以下の計算を行います。
/* unsigned long値の各ビットを循環してシフトするマクロ */
#define SHA1CircularShift(bits,word) \
(((word) << (bits)) | ((word) >> (32-(bits))))
int t; /* ループ変数 */
unsigned long s; /* 演算用ワーク */
const unsigned long MASK = 0x0000000f; /* 演算用マスク */
/* 0〜15回目の演算 */
for (t = 0; t < 16; t++) {
s = t & MASK;
TEMP = SHA1CircularShift(5, A) + f00(B, C, D) + E + W[s] + K[0];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
/* 16〜19回目の演算 */
for (t = 16; t < 20; t++) {
s = t & MASK;
W[s] = SHA1CircularShift(1, W[(s+13) & MASK] ^ W[(s+8) & MASK]
^ W[(s+2) & MASK] ^ W[s]);
TEMP = SHA1CircularShift(5, A) + f00(B, C, D) + E + W[s] + K[0];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
/* 20〜39回目の演算 */
for (t = 20; t < 40; t++) {
s = t & MASK;
W[s] = SHA1CircularShift(1, W[(s+13) & MASK] ^ W[(s+8) & MASK]
^ W[(s+2) & MASK] ^ W[s]);
TEMP = SHA1CircularShift(5, A) + f20(B, C, D) + E + W[s] + K[1];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
/* 40〜59回目の演算 */
for (t = 40; t < 60; t++) {
s = t & MASK;
W[s] = SHA1CircularShift(1, W[(s+13) & MASK] ^ W[(s+8) & MASK]
^ W[(s+2) & MASK] ^ W[s]);
TEMP = SHA1CircularShift(5, A) + f40(B, C, D) + E + W[s] + K[2];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
/* 60〜79回目の演算 */
for (t = 60; t < 80; t++) {
s = t & MASK;
W[s] = SHA1CircularShift(1, W[(s+13) & MASK] ^ W[(s+8) & MASK]
^ W[(s+2) & MASK] ^ W[s]);
TEMP = SHA1CircularShift(5, A) + f60(B, C, D) + E + W[s] + K[3];
E = D;
D = C;
C = SHA1CircularShift(30, B);
B = A;
A = TEMP;
}
方法2-d【引用】 HO, H1, H2, H3, H4にA, B, C, D, Eを加算します。 H0 += A; H1 += B; H2 += C; H3 += D; H4 += E; |
結果を出力します。
【引用】
M(n)の処理を行うと、メッセージダイジェストは、以下の5ワードで表される 160bit の 文字列として得られる。
H0 H1 H2 H3 H4
H0, H1, H2, H3, H4にSHA-1の値が格納されています。 出力する場合には各変数の上位バイトから順に16進数で行います。
![]() |
| [拡大] |
RFC3174にはサンプルプログラムが付記されています。 サンプルプログラムを実行すると、決められたメッセージ(文字列)のSHA-1の値を算出して表示します。
| sha1_src.zip (クリックでSkyDriveへ移動します) 5KB MD5:09f82b0e928548327cf7d6c9e5cad57b |
| sha1_exe.zip (クリックでSkyDriveへ移動します) 16KB MD5:8cafb94a4fdf0a7d247714427936b538 |
ソースファイルをコンパイルする際には、以下のようにします。
% gcc sha1test.c sha1.c -osha1
C:\> cl sha1test.c sha1.c -osha1
| 注意 |
プログラムではstdlint.hを使用しています。
このヘッダーが無いシステム上でコンパイルするには次のように各ファイルを修正してください。
|
(感想はMD5とほぼ同じです)
正直に言って専門家でないと良くわかりません。 RFC3174もいかにもな感じでわかりにくいです。 英語版Wikipediaが比較的簡単ですが、これで理解できない場合には手を出さない方が良いと思われます。
多くの言語では標準で関数(メソッド)が用意されていますので、それを利用するのが正解でしょう。 やはり自前で、という場合でもRFC3174のサンプルの流用で事足ります。
SHAは比較的安全だと言われていますが、それはビット長が大きいものです。 できる限りSHA-224より長いものを利用しましょう。