2016/5/12 SHA-256 C#으로 구현하기.
처음부터 바이트 단위로 잘라서 연산을하다보니 쉬프트 연산에서 다시 바꿔줘야하는 불편함. Initial vector와 연산상수들 초기값 설정할 때 2,3차원배열이 되는 불편함이 있었다.
1
2
3
4
5
6
7
8
|
public byte[,] hashInit = new byte[8, 4] { { 0x6a, 0x09, 0xe6, 0x67 },
{0xbb,0x67,0xae,0x85 },
{0x3c,0x6e,0xf3,0x72 },
{0xa5,0x4f,0xf5,0x3a },
{0x51,0x0e,0x52,0x7f },
{0x9b,0x05,0x68,0x8c },
{0x1f,0x83,0xd9,0xab },
{0x5b,0xe0,0xcd,0x19 } }
| cs |
1
2
3
4
5
6
7
8
9
|
public byte[,,] constants = new byte[8, 8, 4] {
{ { 0x42,0x8a,0x2f,0x98 }, {0x71,0x37,0x44,0x91 }, {0xb5,0xc0,0xfb,0xcf }, {0xe9,0xb5,0xdb,0xa5 }, {0x39,0x56,0xc2,0x5b }, {0x59,0xf1,0x11,0xf1 }, {0x92,0x3f,0x82,0xa4 }, {0xab,0x1c,0x5e,0xd5 } },
{ { 0xd8,0x07,0xaa,0x98 }, {0x12,0x83,0x5b,0x01 }, {0x24,0x31,0x85,0xbe }, {0x55,0x0c,0x7d,0xc3 }, {0x72,0xbe,0x5d,0x74 }, {0x80,0xde,0xb1,0xfe }, {0x9b,0xdc,0x06,0xa7 }, {0xc1,0x9b,0xf1,0x74 } },
{ { 0xe4,0x9b,0x69,0xc1 }, {0xef,0xbe,0x47,0x86 }, {0x0f,0xc1,0x9d,0xc6 }, {0x24,0x0c,0xa1,0xcc }, {0x2d,0xe9,0x2c,0x6f }, {0x4a,0x74,0x84,0xaa }, {0x5c,0xb0,0xa9,0xdc }, {0x76,0xf9,0x88,0xda } },
{ { 0x98,0x3e,0x51,0x52 }, {0xa8,0x31,0xc6,0x6d }, {0xb0,0x03,0x27,0xc8 }, {0xbf,0x59,0x7f,0xc7 }, {0xc6,0xe0,0x0b,0xf3 }, {0xd5,0xa7,0x91,0x47 }, {0x06,0xca,0x63,0x51 }, {0x14,0x29,0x29,0x67 } },
{ { 0x27,0xb7,0x0a,0x85 }, {0x2e,0x1b,0x21,0x38 }, {0x4d,0x2c,0x6d,0xfc }, {0x53,0x38,0x0d,0x13 }, {0x65,0x0a,0x73,0x54 }, {0x76,0x6a,0x0a,0xbb }, {0x81,0xc2,0xc9,0x2e }, {0x92,0x72,0x2c,0x85 } },
{ { 0xa2,0xbf,0xe8,0xa1 }, {0xa8,0x1a,0x66,0x4b }, {0xc2,0x4b,0x8b,0x70 }, {0xc7,0x6c,0x51,0xa3 }, {0xd1,0x92,0xe8,0x19 }, {0xd6,0x99,0x06,0x24 }, {0xf4,0x0e,0x35,0x85 }, {0x10,0x6a,0xa0,0x70 } },
{ { 0x19,0xa4,0xc1,0x16 }, {0x1e,0x37,0x6c,0x08 }, {0x27,0x48,0x77,0x4c }, {0x34,0xb0,0xbc,0xb5 }, {0x39,0x1c,0x0c,0xb3 }, {0x4e,0xd8,0xaa,0x4a }, {0x5b,0x9c,0xca,0x4f }, {0x68,0x2e,0x6f,0xf3 } },
{ { 0x74,0x8f,0x82,0xee }, {0x78,0xa5,0x63,0x6f }, {0x84,0xc8,0x78,0x14 }, {0x8c,0xc7,0x02,0x08 }, {0x90,0xbe,0xff,0xfa }, {0xa4,0x50,0x6c,0xeb }, {0xbe,0xf9,0xa3,0xf7 }, {0xc6,0x71,0x78,0xf2 } } };
| cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public byte[] padding(FileStream fs)
{
byte firstpad = 0x80;
byte remainpad = 0x00;
byte[] lengthpad = new byte[8];
long fileLength=fs.Length;
byte[] entPad = new byte[64]; //total pad(length+pad) is smaller than 512bits(64bytes)
int padSize = 56 - ((int)fileLength % 56); //how many 10s needed
entPad[0] = firstpad;//first padding byte =1000 0000
for ( int i = 1; i<padSize;i++)
{
entPad[i] = remainpad;
}
lengthpad = BitConverter.GetBytes(fileLength*8);
for ( int i = 0; i<8; i++)//long type length to byte array whose size is 8, append it to the pad as big endian
{
entPad[i + padSize] = lengthpad[7-i];
}
return entPad;
}
public static byte[] ROTR(byte[] array, int n)
{
byte[] result = new byte[4];
result[0] = (int)array[(n / 4) % 4] << (n % 4) | (int)array[((n / 4) + 1) % 4] >> (4 - (n % 4));
return result;
}
| cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
String Filename = @"D:\teul jung\visualStudioProjects\Sha256_Hash_File_IO\Plain.txt"; //read file
FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read); //make filestream
byte[,] h = new byte[8,4];
int offset = 0;
int padSize;
bool cont = true;
int readFile;
byte[] paddings = new byte[64];
byte[] work = new byte[64];
HHeader head = new HHeader();
HHeader.HashFile(fs);
paddings = head.padding(fs); //get padding array
padSize = 56 - ((int)fs.Length % 56) + 8;
////////////////start hashing function///////////////////////
for (int j = 0; j < 8; j++)//initializing
{
for (int i = 0; i < 4; i++)
{
h[j, i] = head.hashInit[j, i];
}
}
while(cont)//hashing
{
readFile = fs.Read(work, offset, 64);//break the message into 512bits (8*64byte=512) workset.
offset = readFile + offset;//move forward file pointer as much as byte reading
if(readFile == 64)
{
}
else if(readFile >= 0) //add padding to workset
{
cont = false; // file end
//for(int i=0; i<fs)
//work[readFile - 1]=
}
else
{
Console.WriteLine("The error in block number occured"); // inappropriate padding
}
//hasing
}
| cs |
여기까지가 내가 작성한 코드
패딩부분은 잘 작동해서 좋았다. 근데 ROTR도 32비트가 기준이다 보니 연산식이 저렇게 된다. 보낼 때도 byte한칸씩 4개를 모아서 보내줘야했다.
나중에 다른분들 코드를 보고 uint32 dest = (uint32)byte[0] | (uint32)byte[1]<<8 | (uint32)byte[2] << 16 | (uint32)byte[3] << 24 식을 통해 uint32로 변형하는 방법이 있다는걸 알았다.
아무튼 하다가 너무 불편해서 github를 통해 공개하신 분의 코드를 참조해서 만들고 분석하려고 한다. 다 똑같고 main함수만 작성해서 원하는 파일의 해쉬를 해보려고 한다.
Copyright (c) 2010 Yuri K. Schlesner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
sha 클래스
메인 부분
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using System.Collections.ObjectModel;
namespace Sha256_Hash_File_IO
{
public class HHeader
{
public UInt32[] K = new UInt32[64] {
0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2
};
//public UInt32[] hashInit = new UInt32[8] {
// 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
//};
private UInt32[] H = new UInt32[8]
{
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
};
public static UInt32 ROTR(UInt32 objt, int n)
{
Debug.Assert(n < 32, "ROTR Error : shift over 32");
return (objt << n | objt >> (32 - n));
}
public static UInt32 ROTL(UInt32 objt, int n)
{
Debug.Assert(n < 32, "ROTL Error : shift over 32");
return (objt >> n | objt << (32 - n));
}
public static UInt32 Ch(UInt32 x, UInt32 y, UInt32 z)
{
return (x&y)^((~x) & z);
}
public static UInt32 Maj(UInt32 x,UInt32 y,UInt32 z)
{
return (x & y) ^ (x & z) ^ (y & z);
}
public static UInt32 Sig0(UInt32 x)
{
return ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22);
}
public static UInt32 Sig1(UInt32 x)
{
return ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25);
}
public static UInt32 sSig0(UInt32 x)
{
return ROTR(x, 7) ^ ROTR(x, 18) ^ (x>>3);
}
public static UInt32 sSig1(UInt32 x)
{
return ROTR(x, 17) ^ ROTR(x, 19) ^ (x>>10);
}
public void processBlock(UInt32[] M)
{
Debug.Assert(M.Length == 16); //32*16 = 512
UInt32[] W = new UInt32[64]; //256 bytes of Working Set
for( int t=0;t<16;++t)
{
W[t] = M[t];
}
for(int t=16;t<64;++t)
{
W[t] = sSig1(W[t - 2]) + W[t - 7] + sSig0(W[t - 15]) + W[t - 16];
}
UInt32 a = H[0],
b = H[1],
c = H[2],
d = H[3],
e = H[4],
f = H[5],
g = H[6],
h = H[7];
for(int t=0;t<64;++t)
{
UInt32 T1 = h + Sig1(e) + Ch(e, f, g) + K[t] + W[t];
UInt32 T2 = Sig0(a) + Maj(a, b, c);
h = g;
g = f;
f = e;
e = d + T1;
d = c;
c = b;
b = a;
a = T1 + T2;
}
H[0] = a + H[0];
H[1] = b + H[1];
H[2] = c + H[2];
H[3] = d + H[3];
H[4] = e + H[4];
H[5] = f + H[5];
H[6] = g + H[6];
H[7] = h + H[7];
}
public static string ArrayToString(ReadOnlyCollection<byte> arr)
{
StringBuilder s = new StringBuilder(arr.Count * 2);
for (int i = 0; i < arr.Count; ++i)
{
s.AppendFormat("{0:x2}", arr[i]);
}
return s.ToString();
}
private byte[] pending_block = new byte[64];
private uint pending_block_off = 0;
private UInt32[] uint_buffer = new uint[16];
private UInt64 bits_processed = 0;
private bool closed = false;
private void AddData(byte[] data, uint offset, uint len)
{
if (closed)
throw new InvalidOperationException("Adding data to a closed hasher.");
if (len == 0)
return;
bits_processed += len * 8;
while (len > 0)
{//cut into 8196 bytes and every 64byte are hashed without padding. now, remaining 261bytes come to this function, and 9bytes are left
uint amount_to_copy;
if (len < 64)
{
if (pending_block_off + len > 64)//마지막의 경우 1바이트 0x80이 패딩으로 들어오는데 이게 성립할 경우는 pending_block_off가 64바이트여야함
amount_to_copy = 64 - pending_block_off;
else
amount_to_copy = len;
}
else
amount_to_copy = 64 - pending_block_off;
Array.Copy(data, offset,pending_block, pending_block_off, amount_to_copy); //data 배열에서 offset부터 pending_block(64byte)에 pending_block_off에서 amount_to_copy만큼 복사
len -= amount_to_copy;
offset += amount_to_copy;
pending_block_off += amount_to_copy;//pending_block_off엔 9개만큼 들어갔으니 9, pending_block엔 9bytes 나머지가 입력, 이 둘이 왜 클래스변수로 선언되었는지 알 수 있을듯.
if(pending_block_off == 64)
{
toUintArray(pending_block, uint_buffer); //pending_block_off array goes to Hash process
processBlock(uint_buffer);
pending_block_off = 0;
}
}
}
public ReadOnlyCollection<byte> GetHash()
{
return toByteArray(GetHashUInt32());
}
public ReadOnlyCollection<UInt32> GetHashUInt32()
{
if(!closed)
{
UInt64 size_temp = bits_processed;
AddData(new byte[1] { 0x80 }, 0, 1); //마지막 9바이트를 남겨둔 상태 //만약 딱 64바이트로 나누어 떨어지면 어떻게 될지도 확인할것
uint available_space = 64 - pending_block_off;
if (available_space < 8)//길이 정보를 위한
available_space += 64;
byte[] padding = new byte[available_space]; //여기서 나머지 00000.... 바이트 생성해서
for (uint i = 1; i <= 8; ++i)
{
padding[padding.Length - i] = (byte)size_temp;
size_temp >>= 8;
}//쩐다... 바이트씩 넣고 >>8 해주는 거기에 MSB, LSB 같은 order도 해결할 수 있음 다른 암호화 알고리즘이나 File system에서도 쓸 수 있을거같다.
AddData(padding, 0u, (uint)padding.Length);
Debug.Assert(pending_block_off == 0);//64바이트로 해쉬가 되어야 이 값을 가질 수 있음
closed = true;
}
return Array.AsReadOnly(H);
}
private static ReadOnlyCollection<byte> toByteArray(ReadOnlyCollection<UInt32> src)//byte 배열로 바꿔주는
{
byte[] dest = new byte[src.Count * 4];
int pos = 0;
for(int i=0;i<src.Count;++i)
{
dest[pos++] = (byte)(src[i] >> 24);
dest[pos++] = (byte)(src[i] >> 16);
dest[pos++] = (byte)(src[i] >> 8);
dest[pos++] = (byte)(src[i]);
}
return Array.AsReadOnly(dest);
}
private static void toUintArray(byte[] src,UInt32[] dest)//직접 가르켜서 하는 건 call by reference와 같지만
{
for(uint i = 0,j=0; i< dest.Length; ++i,j+=4)
{
dest[i] = ((UInt32)src[j + 0] << 24) | ((UInt32)src[j + 1] << 16) | ((UInt32)src[j + 2] << 8) | ((UInt32)src[j + 3]);
}
}
//private static ReadOnlyCollection<byte> HashFile(Stream fs)
public static void HashFile(Stream fs,ref byte[] dest)//ref 예약어 사용한 것.
{
HHeader sha = new HHeader();
byte[] buf= new byte[8196];
uint bytes_read;
do
{
bytes_read = (uint)fs.Read(buf, 0, buf.Length);
if (bytes_read == 0)
break;
sha.AddData(buf, 0, bytes_read); //얘는 패딩하는애가 아니라 64바이트만큼 입력된것들을 우선 해쉬해놓는다. //정확히 말하면 pending_block에 원하는 데이터를 붙이면서 패딩조건을 업데이트하고 만약 64바이트가 되면 해쉬를 한다.
}
while (bytes_read > 0);
//여기까지 64바이트씩 해쉬. 마지막 패딩과 나머지 값들은 아래 GetHash를 통해 마무리 한다.
//sha클래스에 현재까지 해쉬한정보 그리고 pending_block에 나머지 해쉬할 것들이 들어있다.
dest=(sha.GetHash()).ToArray<byte>(); //새로 할당한 메모리를 넣는건 call by value로 된다.
}
}
}
| cs |
sha 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace Sha256_Hash_File_IO
{
class Program
{
static void Main(string[] args)
{
String Filename = @"D:\teul jung\visualStudioProjects\Sha256_Hash_File_IO\test.txt"; //read file
FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read); //make filestream
byte[] getHashValByte = new byte[32];
HHeader head = new HHeader();
HHeader.HashFile(fs,ref getHashValByte);
for(int i=0;i<32;++i)
{
Console.Write("{0:x}",getHashValByte[i]);
}
Console.WriteLine();
}
}
}
| cs |
메인 부분
댓글
댓글 쓰기