Băm và lưu password đúng cách

Sau khi đọc bài viết băm mật khẩu đúng cách của anh thaidn, mình nhớ lại lúc mình mới ra trường, cũng đã từng nghĩ về yếu tố này ( lúc đó mình khá thích môn Bảo Mật Thông Tin ở trường ) nhưng chưa khi nào hiểu tường tận. Chỉ biết là không nên :

  • Lưu password ở dạng plain-text.
  • Hash với một thuật toán hash mạnh, không nên xài MD5, SHA-1 …
  • Hash với salt.

Chỉ hiểu rằng phải nên làm thế, nhưng không hiểu tại sao lại như vậy và một số ít câu hỏi khác cũng chưa vấn đáp được như :

  • Password user gửi lên, nên hash ở client hay ở server.
  • Salt nên lưu ở đâu.
  • Salt có cần giữ bí mật hay không?
  • Salt chung cho tất cả, hay salt riêng cho từng user?

Hôm nay, mình quyết định hành động đi tìm câu vấn đáp cho những yếu tố mình vướng mắc, thay vì mặc định nó đúng .

1. Tại sao không nên lưu plain-text, encrypt hoặc dùng MD5, SHA-1

Nếu lưu plain-text, database bị hack, SQL-injection, password user chìa ra theo một cách không thể dễ dàng hơn để đánh cắp.

Nếu mã hóa 2 chiều, sẽ luôn có một cách để giải thuật bằng một chìa-khóa nào đó, sẽ phải tìm cách lưu chìa-khóa một cách bảo đảm an toàn .
MD5 và SHA-1 được chứng tỏ có đụng độ, nghĩa là 2 password khác nhau, khi hash bằng MD5 hoặc SHA-1 hoàn toàn có thể ra cùng một chuỗi .

2. Tại sao phải salt

Ta đã biết, hash algorithm là one-way-function, tức là không hề suy ngược trực tiếp ra password nếu có hash_value ( khác với mã hóa, hoàn toàn có thể giải thuật trải qua chìa-khóa ) .

Tuy nhiên vẫn có cách để từ hash_value có thể suy gián tiếp ra được password ví dụ brute-force attach, dictionary attach -> điểm chung là ta cần thử và đoán password nhiều lần cho tới khi đúng cái cần tìm.

Một cách khác đó là ta có thể tính toán trước giá trị hash của tất cả các trường hợp và của tất cả các thuật toán -> cách này khó, tốn thời gian, nhưng bây giờ với tốc độ tính toán của máy tính, ta vẫn có thể làm được. Bảng lưu trữ password + hash_value của password gọi là Rainbow Table, có thể tự tạo hoặc tải một số bản miễn phí hoặc trả tiền để mua. Từ bây giờ nếu ta có hash_value ta có thể mapping để suy ra được password.

Tuy nhiên nếu ta chỉ hash mỗi password, ta gặp yếu tố đó là :

  • 2 password giống nhau (user vô tình trùng password) thì chuỗi hash(password) sẽ giống nhau.
  • User cố tình đặt password đơn giản và phổ biến (ví dụ password < 4 kí tự, toàn số, toàn chữ) -> dễ nhớ cho user nhưng dễ tra ngược.

Và nếu chỉ hash password thì nếu mất hash_value, hoàn toàn có thể tra trong rainbow table để tìm ra được password của người dùng .

Giờ ta thử thay vì hash(password) ta sẽ hash(salt + password):

Từ md5(123456)

id |          hash_md5                |
---------------------------------------
1  | e10adc3949ba59abbe56e057f20f883e |

Thành md5(7nWZLcCK0vsPzIM + 123456)

id |          hash_md5                |    salt         |
---------------------------------------------------------
1  | 0510210d4b370165658bdc0d0b005244 | 7nWZLcCK0vsPzIM |

Giờ giả sử, ta mất bảng dữ liệu gồm hash_md5, salt, kẻ tấn công sẽ phải tính toán lại rainbow table của tất cả các trường hợp cộng với salt. Nếu salt là random cho từng user, kẻ tấn công sẽ phải tính toán toàn bộ trường hợp cộng với riêng từng salt cho toàn bộ user.

Ngân sách chi tiêu cho 2 phép tính trên là vô cùng lớn và tốn rất nhiều thời hạn để triển khai. Vậy tóm lại mục tiêu của salt và random-salt là :

  • Bảo vệ user kể cả khi user dùng password phổ biến và password không mạnh vì user không thể nhớ được các password phức tạp nhưng tốc độ tính toán của máy tính thì càng ngày càng nhanh.
  • Tạo ra nhiều chi phí tính toán, kẻ tấn công không thể tính toán trước rainbow table.

=> Ta vấn đáp đc 3 câu hỏi :

  • Salt có thể lưu trong database, cùng với hash_value.
  • Không cần tìm cách giữ bí mật salt, nhưng cũng đừng tự ý công khai salt.
  • Nhưng bắt buộc phải random salt cho từng user.

3. Hash ở đâu?

Giờ giả sử hash(password) ở client-side thì vấn đề là gì?

  • Biết được thuật toán dùng để hash.
  • Salt sẽ phải sinh ra ở client, vì ta cần hash password với salt (hash(salt + password)), và db chỉ lưu kết quả hash, không lưu salt.
  • Nhưng nếu salt sinh ra ở client và salt random thì làm sao để compare với hash_value trong database? Vì lần chứng thực tới, salt sẽ lại random và sẽ khác với kết quả trong database -> salt phải duy nhất cho tất cả các trường hợp.
  • Hoặc salt có thể lưu ở DB, nhưng server phải gửi salt về trước cho user trước khi thực hiện hash -> dễ dàng biết được salt hơn.

Nhìn sơ thì thấy việc dùng duy nhất một salt đã chống lại vấn đề ở mục số 2. Vậy tiến trình xác nhận đúng là ra làm sao ?

  • User sẽ gửi plain-text password lên server và over HTTPs.
  • Server sẽ kiểm tra trong database lấy ra salt của user đó, cộng chuỗi ta được salt + password.
  • Thực hiện hash(salt + password) trên server side.
  • Compare kết quả trên với hash_md5 trong database.

4. Tại sao dùng bcrypt thay cho SHA-512

Kết quả của SHA-512 có độ dài 128 kí tự, độ dài của key là 64 bytes. Trông có vẻ như cũng khá chắc như đinh, vậy tại sao OWASP recommend sử dụng PBKDF2, bcrypt hoặc scrypt hơn là SHA2 ?
SHA2 là hash algorithm ( tất yếu ), nó được phong cách thiết kế với tiềm năng là vận tốc, với những CPU tân tiến, hoàn toàn có thể generate hàng triệu tác dụng trên giây. Nếu dùng một thuật toán có vận tốc như SHA2 tức là bạn đã đem lợi điểm tới cho kẻ tiến công brute-force. Thuật toán nhanh + thông số kỹ thuật server mạnh, việc brute-force càng trở lên nhanh gọn hơn .

Trong khi đó, bcrypt được gọi là slow-hash algorithm, bcrypt() mất 100ms để tính toán ra chuỗi hash, chậm hơn 10.000 lần so với sha1().

Có nghĩa là vẫn đạt được mục tiêu hash nhưng giảm thiểu rủi ro tiềm ẩn tiến công brute-force .

Tóm lại: SHA-512 không phải là một thuật toán yếu, mà vấn đề là SHA-512 không phù hợp cho việc hash password. Nếu cần hash password thì ta nên dùng các thuật toán slow hash như PBKDF2, bcrypt và scrypt.

5. Tại sao cần Pepper?

Một thực tiễn là nếu bạn chỉ có “ muối ” mà không có “ tiêu ”, ăn thịt gà luộc sẽ không ngon : v. Giả sử, database bạn chạy RAID-1, một ổ cứng hư và cần thay một ổ cứng mới. Nhưng như ta biết, đĩa bị hư là mirror của đĩa còn lại, bạn phải tiêu hủy ổ cứng hư đó nếu không ai đó hoàn toàn có thể lục thùng rác và tái tạo lại một phần tài liệu trong đĩa hư đó .

Xin lưu ý, bạn cần wipe trước khi vứt bỏ một ổ cứng có dữ liệu dù cá nhân hay server, tuy nhiên đĩa bị sốc điện, bad-sector thì wipe cũng chưa đủ an toàn, tốt nhất nên ngiền ra bã.

Dù random-salt đã làm tăng ngân sách tạo ra rainbow table nhưng đời không biết đâu mà lần, kẻ tiến công luôn có những động lực ngoạn mục để đạt được cái mình muốn. Giả sử kẻ tiến công có một siêu siêu máy tính và một mirror ổ cứng hư lục từ một cái thùng rác nào đó. Với siêu máy tính đó, ta có rainbow-table để tra ngược ra password cần tìm .

Vậy làm sao để giảm thiểu nguy cơ trên? Nguyên tắc là không bỏ tất cả trứng trong một giỏ, đó là pepper. Pepper là một chuỗi tương tự như salt, nhưng khác biệt là ta cần giữ bí mật pepper, lưu ở một chỗ khác ngoài database, và không cần pepper-per-user, chỉ cần 1 pepper là đủ.

Từ

hash(salt + password)

Thành

hash(pepper + salt + password)

Ta nên lưu pepper ở application hoặc ở một service khác, nếu database bị compromise, thì kẻ tiến công cũng không có pepper để tạo ra rainbow-table .
6. Bonus

Trong khi tìm câu vấn đáp để viết bài này, mình tìm được một bài blog của dropbox nói về cách họ lưu password như thế nào. Thấy có 2 điểm khá hay nên muốn nói thêm .

  • Trước khi hash password với salt-per-user, họ có SHA512(password) trước để cố định độ dài của input-password. Theo Dropbox thì việc này giải quyết 2 issues của bcrypt
    • Một số implementation của bcrypt cắt đầu vào còn 72 bytes.
    • Một số implementation khác của bcrypt thì không cắt đầu vào nhưng dẫn tới một vấn đề khác là DoS attack bởi vì cho phép độ dài mật khẩu tùy ý.
  • Dropbox cũng có global pepper nhưng thay vì dùng nó để hash thì họ encrypt. Tức là thay vì hash(pepper + salt + password) thì họ AES256(salt + password) + global-pepper-key. Theo như họ giải thích thì pepper là một lớp phòng thủ sâu hơn và lưu trữ ở một nơi riêng biệt. Nhưng đồng nghĩa với việc là nơi lưu trữ pepper vẫn có thể bị compromise, và khi bị compromise thì việc rotate key không dễ dàng. Dùng pepper để encrypt vẫn đạt được mục đích bảo mật tương tự nhưng thêm khả năng rotate key khi bị compromise.

7. Migrate

Sau khi viết bài này, Có một thắc mắc về việc làm thế nào migrate một mạng lưới hệ thống dùng SHA1 – > bcrypt. Mình thấy đây là một điểm cũng khá thiết yếu nên xin phép note thêm .
Ở đây có 2 trường hợp :

  • Hash password bằng SHA1 nhưng không có salt -> mình gọi là sha1_value.
  • Hash password bằng SHA1 + trong DB có salt-per-user, mình có sha1_value, salt.

Về sáng tạo độc đáo thì ta sẽ dùng SHA1 như cách Dropbox dùng SHA512 để cố định và thắt chặt độ dài input .

Với trường hợp đầu ta cần sinh ra thêm salt-per-user và migrate bcrypt(salt, sha1_value) trong đó sha1_value = SHA1(password) -> Tương tự cách của dropbox, may mắn là do sai thiết kế từ đầu (thiếu salt) nên migrate dễ.

Với trường hợp thứ 2 thì hơi lằng nhằng hơn 1 xíu, ta cần migrate kiểu bcrypt(salt, SHA1(salt, password)) nếu thực sự muốn bcrypt với salt hoặc thực ra chỉ cần bcrypt(SHA1(salt, password) cũng được. Tùy tình huống bạn cần tradeoff có thể lựa chọn cách phù hợp.

8. Thông tin của bạn có an toàn?

Như ta thấy, ta đã làm rất nhiều thứ để bảo vệ rằng thông tin của tất cả chúng ta trở lên bảo đảm an toàn. Vậy thực sự thông tin của tất cả chúng ta đã bảo đảm an toàn hay không ? Câu vấn đáp rất tiếc là KHÔNG ? tin tức của tất cả chúng ta hoàn toàn có thể bảo đảm an toàn với “ bên ngoài ” nhưng không bảo đảm an toàn với “ bên trong ”. Tại sao lại vậy ?

Giờ bắt đầu với thông tin cơ bản nhất như địa chỉ, email, sở thích … Khi ta build staing environment, thông tin user được copy sang một môi trường khác, developer có thể overwrite hash_value của user bằng value của hash(salt + 654321) và dễ dàng đăng nhập trên staing environment với password là 654321 và tất cả các thông tin cơ bản của user đã có thể đọc được. Nhưng developer vẫn không biết chính xác password của user là gì.

Nhưng, lại là nhưng. Người quản trị mạng lưới hệ thống có mọi quyền trên server mà họ quản trị, password dù đã over HTTPs nhưng tới server vẫn là plain-text. Vẫn hoàn toàn có thể capture request ngay ở đầu server và output ra plain-text password của user và ở đầu cuối thì người quản trị mạng lưới hệ thống vẫn biết được đúng mực password của user là gì. Đó là lí do tại sao ta không nên dùng một password cho toàn bộ những dịch vụ .
Vậy làm thế nào để bảo vệ info của user trong trường hợp này, rất tiếc về mặt kỹ thuật không có cách nào để bảo vệ việc này. Ta chỉ hoàn toàn có thể vận dụng policy, NDA về mặt con người để hạn chế yếu tố này thôi .

9. Ref

Lưu ý:

  • Trong bài viết dùng MD5 để minh họa cho đơn giản, trong thực tế ta không dùng MD5.
  • Trong bài viết giả định kẻ tấn công bằng cách nào đó có db users, trong thực tế việc này rất khó do có các tầng bảo mật khác như thiết kế infrastructure, define rule/policy về con người … Trong bài này mình không bàn đến các vấn đề đó.

Advertisement

Chia sẻ:

Thích bài này:

Thích

Đang tải …

Băm và lưu password đúng cách

Bài viết liên quan
Hotline 24/7: O984.666.352
Alternate Text Gọi ngay