SHA-2 (Thuật toán băm an toàn 2), trong đó SHA-256 cũng là một phần, chúng là một trong những thuật toán băm phổ biến nhất hiện nay. Ở bài viết này, mình sẽ chia sẻ cho các bạn cách thức mà thuật toán hoạt động. Với SHA-2 được biết đến với khả năng bảo mật cấp cao (không giống như SHA-1) và tốc độ của nó. Trong các trường hợp không tạo key, chẳng hạn như khai thác Bitcoin, thuật toán băm nhanh như SHA-2 sẽ là lợi thế cho bạn.
Hàm băm là gì ?
Hàm băm được chia làm 3 loại chính:
- Xáo trộn dữ liệu.
- Chấp nhận đầu vào tùy ý và đầu ra cố định.
- Giúp thao tác dữ liệu mà không thay đổi.
SHA-2 có họ hàng với SHA-256 ?
SHA-2 là một thuật toán băm dữ liệu. Chúng đều sử dụng cùng một thuật toán nhưng sử dụng các hằng số khác nhau. Ví dụ: SHA-256 đặt các hằng số bổ sung xác định hành vi của thuật toán SHA-2, một trong những hằng số này là kích thước đầu ra, 256 và 512 trong SHA-256 và SHA-512 tham chiếu đến kích thước tương ứng với các bit.
SHA-2 là con của SHA-1 ?
SHA-2 là sự kế thừa của hàm băm SHA-1 và vẫn là một trong những hàm băm mạnh nhất được sử dụng ngày nay. SHA-256, trái ngược với SHA-1, chúng không bị xâm phạm. Vì lý do này, bạn chẳng có lý do gì để sử dụng SHA-1 vì nó không an toàn. Tính linh hoạt của kích thước đầu ra (224, 256, 512, ….) cũng cho phép SHA-2 ghép nối tốt với các KDF và mật mã phổ biến như AES-256.
“hello world” đầu tiên
Bước xử lý:
Trước tiên ta cần đổi chữ hello world
thành nhị phân.
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111
01110010 01101100 01100100
Tiếp đên ta nối 1 sau chuỗi nhị phân.
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111
01110010 01101100 01100100 1
Nối 0 cho đến khi dữ liệu là bội số của 512, ít hơn 64 bit (Ở đây của mình là 448 bit).
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111
01110010 01101100 01100100 10000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
Nối 64 bit vào cuối, trong đó 64 bit là số nguyên big-endian đại diện cho độ dài của đầu vào ban đầu ở dạng nhị phân. Ở đây của mình là 88 hoặc trong hệ nhị phân là “1011000”.
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111
01110010 01101100 01100100 10000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 01011000
Bây giờ đã có đầu vào của mình, và đầu vào đó sẽ luôn chia hết cho 512.
Khởi tạo Giá trị băm (h)
Bây giờ chúng ta tạo 8 giá trị băm. Đây là các hằng số được mã hóa cứng đại diện cho 32 bit đầu tiên của phần phân số của căn bậc hai của 8 số nguyên tố đầu tiên: 2, 3, 5, 7, 11, 13, 17, 19.
h0 := 0x6a09e667
h1 := 0xbb67ae85
h2 := 0x3c6ef372
h3 := 0xa54ff53a
h4 := 0x510e527f
h5 := 0x9b05688c
h6 := 0x1f83d9ab
h7 := 0x5be0cd19
Khởi tạo Hằng số tròn (k)
Tương tự như trên đây, ở đây ta tạo một số hằng số (Tìm hiểu thêm về hằng số và khi nào sử dụng chúng). Lần này chúng ta có 64 hằng số, mỗi giá trị (0-63) là 32 bit đầu tiên của các phần phân số của căn bậc hai của 64 số nguyên tố đầu tiên (2 – 311).
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
Vòng lặp Chunk
Các bước sau sẽ xảy ra cho mỗi “đoạn” dữ liệu 512-bit từ đầu vào của mình. Ở đây, vì “hello world” quá ngắn, nên mình chỉ có một đoạn. Tại mỗi lần lặp lại của vòng lặp, mình sẽ thay đổi các giá trị băm h0-h7 và đây sẽ là đầu ra.
Tạo lịch trình nhắn tin (w)
Sao chép dữ liệu đầu vào từ bước 1 vào một mảng mới trong đó mỗi mục nhập là một từ 32 bit:
01101000011001010110110001101100 01101111001000000111011101101111
01110010011011000110010010000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000001011000
Thêm 48 từ khác được khởi tạo bằng 0, như vậy là có một mảng w[0… 63]
01101000011001010110110001101100 01101111001000000111011101101111
01110010011011000110010010000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000001011000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
...
...
00000000000000000000000000000000 00000000000000000000000000000000
Sửa đổi các chỉ mục zero-ed ở cuối mảng bằng cách sử dụng thuật toán sau:
Đối với mình từ w[16… 63]:
- s0 = (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
- s1 = (w[i- 2] rightrotate 17) xor (w[i- 2] rightrotate 19) xor (w[i- 2] rightshift 10)
- w[i] = w[i-16] + s0 + w[i-7] + s1
Với [16] hãy xem cách hoạt động của nó như thế nào.
w[1] rightrotate 7:
01101111001000000111011101101111 -> 11011110110111100100000011101110
w[1] rightrotate 18:
01101111001000000111011101101111 -> 00011101110110111101101111001000
w[1] rightshift 3:
01101111001000000111011101101111 -> 00001101111001000000111011101101
s0 = 11011110110111100100000011101110 XOR 00011101110110111101101111001000 XOR 00001101111001000000111011101101
s0 = 11001110111000011001010111001011
w[14] rightrotate 17:
00000000000000000000000000000000 -> 00000000000000000000000000000000
w[14] rightrotate19:
00000000000000000000000000000000 -> 00000000000000000000000000000000
w[14] rightshift 10:
00000000000000000000000000000000 -> 00000000000000000000000000000000
s1 = 00000000000000000000000000000000 XOR 00000000000000000000000000000000 XOR 00000000000000000000000000000000
s1 = 00000000000000000000000000000000
w[16] = w[0] + s0 + w[9] + s1
w[16] = 01101000011001010110110001101100 + 11001110111000011001010111001011 + 00000000000000000000000000000000 + 00000000000000000000000000000000
// Mình bổ sung thêm được tính theo modulo 2 ^ 32
w[16] = 00110111010001110000001000110111
Điều này để lại cho mình 64 từ trong nội dung thông báo (w):
01101000011001010110110001101100 01101111001000000111011101101111
01110010011011000110010010000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000000 00000000000000000000000001011000
00110111010001110000001000110111 10000110110100001100000000110001
11010011101111010001000100001011 01111000001111110100011110000010
00101010100100000111110011101101 01001011001011110111110011001001
00110001111000011001010001011101 10001001001101100100100101100100
01111111011110100000011011011010 11000001011110011010100100111010
10111011111010001111011001010101 00001100000110101110001111100110
10110000111111100000110101111101 01011111011011100101010110010011
00000000100010011001101101010010 00000111111100011100101010010100
00111011010111111110010111010110 01101000011001010110001011100110
11001000010011100000101010011110 00000110101011111001101100100101
10010010111011110110010011010111 01100011111110010101111001011010
11100011000101100110011111010111 10000100001110111101111000010110
11101110111011001010100001011011 10100000010011111111001000100001
11111001000110001010110110111000 00010100101010001001001000011001
00010000100001000101001100011101 01100000100100111110000011001101
10000011000000110101111111101001 11010101101011100111100100111000
00111001001111110000010110101101 11111011010010110001101111101111
11101011011101011111111100101001 01101010001101101001010100110100
00100010111111001001110011011000 10101001011101000000110100101011
01100000110011110011100010000101 11000100101011001001100000111010
00010001010000101111110110101101 10110000101100000001110111011001
10011000111100001100001101101111 01110010000101111011100000011110
10100010110101000110011110011010 00000001000011111001100101111011
11111100000101110100111100001010 11000010110000101110101100010110
Nén
- Khởi tạo các biến a, b, c, d, e, f, g, h và đặt chúng bằng các giá trị băm hiện tại tương ứng. h0, h1, h2, h3, h4, h5, h6, h7
- Chạy vòng lặp nén. Vòng lặp nén sẽ thay đổi các giá trị từ a…h. Vòng lặp nén như sau:
- Từ 0 đến 63
- S1 = (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25)
- ch = (e and f) xor ((not e) and g)
- temp1 = h + S1 + ch + k[i] + w[i]
- S0 = (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22)
- maj = (a and b) xor (a and c) xor (b and c)
- temp2 := S0 + maj
- h = g
- g = f
- f = e
- e = d + temp1
- d = c
- c = b
- b = a
- a = temp1 + temp2
- Từ 0 đến 63
Hãy xem qua lần lặp đầu tiên, tất cả phép cộng được tính theo modulo 2 ^ 32:
a = 0x6a09e667 = 01101010000010011110011001100111
b = 0xbb67ae85 = 10111011011001111010111010000101
c = 0x3c6ef372 = 00111100011011101111001101110010
d = 0xa54ff53a = 10100101010011111111010100111010
e = 0x510e527f = 01010001000011100101001001111111
f = 0x9b05688c = 10011011000001010110100010001100
g = 0x1f83d9ab = 00011111100000111101100110101011
h = 0x5be0cd19 = 01011011111000001100110100011001
e rightrotate 6:
01010001000011100101001001111111 -> 11111101010001000011100101001001
e rightrotate 11:
01010001000011100101001001111111 -> 01001111111010100010000111001010
e rightrotate 25:
01010001000011100101001001111111 -> 10000111001010010011111110101000
S1 = 11111101010001000011100101001001 XOR 01001111111010100010000111001010 XOR 10000111001010010011111110101000
S1 = 00110101100001110010011100101011
e and f:
01010001000011100101001001111111
& 10011011000001010110100010001100 =
00010001000001000100000000001100
not e:
01010001000011100101001001111111 -> 10101110111100011010110110000000
(not e) and g:
10101110111100011010110110000000
& 00011111100000111101100110101011 =
00001110100000011000100110000000
ch = (e and f) xor ((not e) and g)
= 00010001000001000100000000001100 xor 00001110100000011000100110000000
= 00011111100001011100100110001100
// k[i] là hằng số vòng
// w[i] là batch
temp1 = h + S1 + ch + k[i] + w[i]
temp1 = 01011011111000001100110100011001 + 00110101100001110010011100101011 + 00011111100001011100100110001100 + 01000010100010100010111110011000 + 01101000011001010110110001101100
temp1 = 01011011110111010101100111010100
a rightrotate 2:
01101010000010011110011001100111 -> 11011010100000100111100110011001
a rightrotate 13:
01101010000010011110011001100111 -> 00110011001110110101000001001111
a rightrotate 22:
01101010000010011110011001100111 -> 00100111100110011001110110101000
S0 = 11011010100000100111100110011001 XOR 00110011001110110101000001001111 XOR 00100111100110011001110110101000
S0 = 11001110001000001011010001111110
a and b:
01101010000010011110011001100111
& 10111011011001111010111010000101 =
00101010000000011010011000000101
a and c:
01101010000010011110011001100111
& 00111100011011101111001101110010 =
00101000000010001110001001100010
b and c:
10111011011001111010111010000101
& 00111100011011101111001101110010 =
00111000011001101010001000000000
maj = (a and b) xor (a and c) xor (b and c)
= 00101010000000011010011000000101 xor 00101000000010001110001001100010 xor 00111000011001101010001000000000
= 00111010011011111110011001100111
temp2 = S0 + maj
= 11001110001000001011010001111110 + 00111010011011111110011001100111
= 00001000100100001001101011100101
h = 00011111100000111101100110101011
g = 10011011000001010110100010001100
f = 01010001000011100101001001111111
e = 10100101010011111111010100111010 + 01011011110111010101100111010100
= 00000001001011010100111100001110
d = 00111100011011101111001101110010
c = 10111011011001111010111010000101
b = 01101010000010011110011001100111
a = 01011011110111010101100111010100 + 00001000100100001001101011100101
= 01100100011011011111010010111001
Toàn bộ phép tính đó được thực hiện thêm 63 lần nữa, sửa đổi các biến a-h trong suốt. Chúng tôi sẽ không làm điều đó bằng tay nhưng chúng tôi sẽ kết thúc bằng:
h0 = 6A09E667 = 01101010000010011110011001100111
h1 = BB67AE85 = 10111011011001111010111010000101
h2 = 3C6EF372 = 00111100011011101111001101110010
h3 = A54FF53A = 10100101010011111111010100111010
h4 = 510E527F = 01010001000011100101001001111111
h5 = 9B05688C = 10011011000001010110100010001100
h6 = 1F83D9AB = 00011111100000111101100110101011
h7 = 5BE0CD19 = 01011011111000001100110100011001
a = 4F434152 = 01001111010000110100000101010010
b = D7E58F83 = 11010111111001011000111110000011
c = 68BF5F65 = 01101000101111110101111101100101
d = 352DB6C0 = 00110101001011011011011011000000
e = 73769D64 = 01110011011101101001110101100100
f = DF4E1862 = 11011111010011100001100001100010
g = 71051E01 = 01110001000001010001111000000001
h = 870F00D0 = 10000111000011110000000011010000
Sửa đổi giá trị
Sau khi vòng lặp nén, ở trên trong vòng lặp chunk, mình sửa đổi các giá trị băm bằng cách thêm các biến tương ứng a-h. Và tất cả các phép cộng ở đây đều là modulo 2 ^ 32.
h0 = h0 + a = 10111001010011010010011110111001
h1 = h1 + b = 10010011010011010011111000001000
h2 = h2 + c = 10100101001011100101001011010111
h3 = h3 + d = 11011010011111011010101111111010
h4 = h4 + e = 11000100100001001110111111100011
h5 = h5 + f = 01111010010100111000000011101110
h6 = h6 + g = 10010000100010001111011110101100
h7 = h7 + h = 11100010111011111100110111101001
Kết hợp Băm cuối cùng
Cuối cùng ghép tất cả lại với nhau và tèn tèn, ta có một phép nối chuỗi đơn giản.
digest = h0 append h1 append h2 append h3 append h4 append h5 append h6 append h7 = B94D27B9934D3E08A52E52D7DA7DABFAC484EFE37A5380EE9088F7ACE2EFCDE9
Xong! Mình đã xem qua từng bước (không có một số lần lặp lại) của SHA-256 với chi tiết tuyệt vời 🙂
Kết luận
Nếu bạn muốn xem tất cả các bước mình vừa làm ở trên, thì đây mình sẽ giải thích rõ hơn:
Bước 1: Tất cả các biến là số nguyên không dấu 32 bit và phép cộng được tính theo modulo 2 ^ 32
Bước 2: Đối với mỗi vòng lặp, có một hằng số vòng k[i] và một mục nhập trong mảng thông báo w[i], 0 ≤ i ≤ 63
Bước 3: Sử dụng 8 biến từ a đến h
Bước 4: Sử dụng quy ước big-endian khi biểu thị các hằng số trong mã hóa này, và khi phân tích cú pháp tin nhắn sẽ chặn dữ liệu từ byte thành từ, **Ví dụ:** từ đầu tiên của tin nhắn đầu vào "abc" sau phần đệm là 0x61626380
Khởi tạo giá trị băm (32 bit đầu tiên của phần phân số của căn bậc hai của 8 số nguyên tố đầu tiên 2..19):
h0 := 0x6a09e667
h1 := 0xbb67ae85
h2 := 0x3c6ef372
h3 := 0xa54ff53a
h4 := 0x510e527f
h5 := 0x9b05688c
h6 := 0x1f83d9ab
h7 := 0x5be0cd19
Khởi tạo mảng các hằng số vòng (32 bit đầu tiên của phần phân số của căn bậc hai của 64 số nguyên tố đầu tiên 2..311):
k[0..63] :=
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
Pre-processing (Padding):
Bắt đầu với nội dung có độ dài L bit
Nối bit '1' duy nhất
Nối K '0' bit, trong đó K là số nhỏ nhất >= 0 sao cho L + 1 + K + 64 là bội số của 512
Thêm L dưới dạng số nguyên big-endian 64bit, làm cho tổng độ dài sau xử lý là bội số của 512bit
Xử lý tin nhắn theo các phần 512bit liên tiếp, chia nhỏ tin nhắn thành các đoạn 512bit
for each chunk
create a 64-entry message schedule array w[0..63] of 32-bit words
(Các giá trị ban đầu trong w[0..63] không quan trọng) sao chép đoạn vào 16 từ đầu tiên w[0..15] của mảng tin nhắn
Kéo dài 16 từ đầu tiên thành 48 từ còn lại w[16..63] của mảng tin nhắn:
for i from 16 to 63
s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
s1 := (w[i- 2] rightrotate 17) xor (w[i- 2] rightrotate 19) xor (w[i- 2] rightshift 10)
w[i] := w[i-16] + s0 + w[i-7] + s1
Khởi tạo các biến thành giá trị băm hiện tại:
a := h0
b := h1
c := h2
d := h3
e := h4
f := h5
g := h6
h := h7
Nén vòng lặp chính:
for i from 0 to 63
S1 := (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25)
ch := (e and f) xor ((not e) and g)
temp1 := h + S1 + ch + k[i] + w[i]
S0 := (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22)
maj := (a and b) xor (a and c) xor (b and c)
temp2 := S0 + maj
h := g
g := f
f := e
e := d + temp1
d := c
c := b
b := a
a := temp1 + temp2
Thêm đoạn đã nén vào giá trị băm hiện tại:
h0 := h0 + a
h1 := h1 + b
h2 := h2 + c
h3 := h3 + d
h4 := h4 + e
h5 := h5 + f
h6 := h6 + g
h7 := h7 + h
Tạo giá trị băm cuối cùng (big-endian): digest: = hash: = h0 append h1 append h2 append h3 append h4 append h5 append h6 append h7
Nguồn: viblo.asia