Mật khẩu cũng cần "ướp và băm với Muối và Tiêu" như thịt vậy!

Cuối cùng thì cũng đến tối thứ 6 cuối tuần; mình định viết tiếp seri Con trỏ nhưng lại lòi ra 1 chủ đề nghe vui tai nên mình quyết định viết về nó trước. "Băm mật khẩu với Muối và Tiêu." 

Cuộc sống nhạt nhẽo quá, chúng ta cần thêm muối vào cho đỡ nhạt; ngoài ra cần thêm chút cay của hạt tiêu vào cho cuộc sống thêm phần nảy lửa kịch tính! Còn nấu ăn thì mình ngu si nên ko dám xạo chó; ko chẳng may đời cha ăn mặn thì đời con khát nước, mà đời cháu lại đi đái thôi. 😁

Well, trong Mật mã học cũng vậy; các thuật toán che giấu thông tin cũng được đặt tên theo ý tưởng như vậy thôi! Salt và Pepper là 2 thuật toán được sử dụng để tạo ra cơ chế phòng vệ chống lại hành vi tấn công từ điển, mà là 1 dạng của hình thức tấn công mật khẩu. Salt thì mình đã nghe Sư Phụ giới thiệu hồi học An toàn thông tin trên lớp rồi; còn Pepper thì đúng là lần đầu tiên nghe đến, đúng là "ếch ngồi đáy giếng"! Nhưng thôi biết còn hơn ko, dù j thì hồi lớp 11 mình cũng đã từng đóng vai "hoàng tử ếch" của bạn ấy rồi nên nhớ lại cũng thấy thích thú! 😆

Các vấn đề đọc được.

Tối hôm thứ 2 đầu tuần, trên đường về quê để sáng hôm sau xuống huyện đôi khám Nghĩa vụ quân sự, mình có vào blog của anh Dương Ngọc Thái (thaidn - cái nickname quen thuộc) đọc ít bài giết thời gian. Thú thật là mình ko thích việc anh ấy tỏ ra như 1 "sĩ phu phẫn uất"; nhưng thực sự thì chuyên môn về Mật mã học cũng như An toàn thông tin của anh ấy rất là bá đạo! Do đó từ ngày biết blog, mình cũng thi thoảng vào đọc bài mỗi khi rảnh.

Tối đó, trong lúc ngồi ở công ty đợi xe đến đón, mình có đọc đến bài Băm mật khẩu đúng cách. Đọc xong mình chợt nhớ ra hồi trước Sư Phụ có nói về kỹ thuật Salting - thêm muối để làm chậm tấn công từ điển rồi; nhưng cái hay ho ở đây là ngoài Muối ra, bài viết còn nói đến Tiêu nữa. Hiếu kỳ quá, mình liền đọc hết bình luận của bài viết rồi google search rồi đọc một thôi một hồi về Pepper Algorithm. Sau khi đọc xong đủ để giải thích mấy câu hỏi trong bài viết, ko hiểu sao 1 thằng chưa bao giờ say xe như mình lại tự nhiên có hiện tượng buồn nôn! Con xe 7 chỗ mà lão tài xế bật nhạc tung zời; 1 seri liên khúc Phạm Trưởng, rồi là nhạc của thanh niên Lê Bảo Bình (về nhà google search mới biết); nghe thấy ốm vãi đái!

Anh Thái DN nêu ra mấy câu hỏi như sau:
  • Sử dụng các thuật toán scrypt, bcrypt, PBKDF2 hoặc argon2 để thay thế cho các thuật toán MD5, SHA2 ... để băm mật khẩu?
  • Băm mỗi mật khẩu với 1 Muối ngẫu nhiên và ko cần giữ bí mật, có thể lưu cùng chỗ với mật khẩu?
  • Băm tất cả mật khẩu với 1 Tiêu bí mật, lưu ở chỗ khác với mật khẩu?
  • Vì sao Muối thôi là ko đủ, mà phải có cả Tiêu?
Mình sẽ giải quyết từng vấn đề một như sau.

Timing attack?

Không hẳn là không xảy ra khả năng này. Bọn hacker luôn có 1 động lực thần bí khiến chúng không từ bất cứ thủ đoạn j để đạt được mục đích. Đúng là mất dạy là mẹ thành công mà. Timing Attack là một trong vô số các hình thức mà được gọi chung là Side - Channel Attack (tấn công kênh kề). Thể loại tấn công này nghe qua thì tường chừng vô cùng xàm xí; nhưng thực chất thì bạn sẽ ớ người ra kiểu "wtf" sau khi hiểu được 1 hình thức nào đó của thể loại tấn công này! Đương nhiên, mình sẽ viết một bài viết riêng để giới thiệu về Side - Channel Attack sau; còn bây giờ là thời lượng của Muối và Tiêu nhé!

Về mặt cấu trúc thuật toán, các thuật toán như MD5 và SHA2 có tốc độ xử lý rất nhanh (thuật toán đơn giản mà), do đó sẽ gặp phải vấn đề có thể brutefoce attack khi kẻ tấn công sở hữu một khả năng tính toán vô cùng mạnh mẽ. Hơn nữa, vấn đề ở 2 thuật toán này còn nằm ở việc "đụng độ". Nghĩa là với 2 giá trị đầu vào khác nhau, 2 thuật toán này có khả năng sẽ băm được ra cùng 1 đầu ra. Điều này đem lại lợi thế cực kỳ lớn cho hacker khi mà chúng không cần vét cạn không gian từ điển! Mà biết đâu với quan điểm của Birthday paradox thì chúng lại chỉ cần vét 1 phần từ điển thôi ấy chứ!

Còn 4 thuật toán mà anh Thái DN nêu ra như scrypt, bcrypt, PBKDF2 hoặc argon2; thì theo như các bình luận của bài viết, tuy chúng phức tạp hơn, có thời gian thực hiện lâu hơn; nhưng chúng lại hạn chế được "đụng độ" cũng như vì thời gian thực hiện lâu hơn nên việc bruteforce cũng không khả thi. Ngoài ra, chính vì thời gian thực hiện thuật toán lâu hơn, nên sẽ hạn chế được "Timing attack". Hacker sẽ không thể biết được cấu trúc của thuật toán băm mật khẩu; cũng khó có thể đoán được từng ký tự mật khẩu theo bất cứ hình thức Side - Channel Attack nào khác!

Mật khẩu của bạn sẽ lạc trôi như nào?

Có bao giờ bạn tự hỏi việc mình nhập mật khẩu thành ********** rồi POST lên server thì chúng sẽ đi về đâu và bị đày đọa như nào không? Liệu mật khẩu của mình có bị lộ không? Yên tâm đi, với giao thức HTTPS, SSL/TLS sẽ lo mã hóa đường truyền client - server, và mọi thứ bạn gửi lên server thì chỉ có bạn và server biết thôi!

Nhưng vấn đề chưa xong; nếu cứ lưu mật khẩu dưới dạng bản rõ (plaintext) trong cơ sở dữ liệu thì có an toàn không? Đương nhiên chết sấp mặt rồi! Nếu hacker có thể SQL Injection thành công thì coi như toàn bộ database (cơ sở dữ liệu) sẽ bị phơi bày! Lưu ý rằng SQL Injection bây giờ đã trở thành đại diện cho hình thức tấn công tiêm chích dò dẫm vào database để leo thang đặc quyền Admin rồi nhé; nên ko có chuyện bạn dùng NoSQL thì NoInjection đâu; vẫn bị Injection hết!

Do đó, vấn đề ở đây là phải làm thế nào để dấp diếm đi không để mật khẩu tồn tại trong DB (database) dưới dạng bản rõ! Vậy có 2 cách: mã hóa (encrypt) và băm (hash). Vậy ta nên chọn cách nào? 
  • Mã hóa mật khẩu (password) thành bản mã (cipher)? Bạn nghĩ mà xem, nếu đã là "mã hóa" thì sẽ có thể "giải mã". Hơn nữa không một hệ thống nào rảnh đến mức sinh ra cho mỗi user 1 khóa cả; mà nếu mã hóa thì họ sẽ sử dụng chung một khóa cho tất cả các mật khẩu. Điều này có nghĩa là nếu ai đó có khả năng mã hóa - giải mã thì có thể nẫng được toàn bộ mật khẩu của bạn sau khi giải mã ngược toàn bộ DB.
  • Băm mật khẩu (password) thành cốt (digest)? Khác với mã hóa, băm là hàm một chiều; tức là ta không thể dịch ngược lại từ cốt về bản rõ mật khẩu, hơn nữa còn có khả năng xảy ra đụng độ. Nhưng bạn nghĩ mà xem, ta đâu có cần phải bảo mật mật khẩu đến mức mã hóa chúng vào rồi dính phải nguy cơ bị dịch ngược? Với 1 user, sau khi gửi bản rõ mật khẩu lên, ta chỉ cần băm và so khớp với mật khẩu đã băm được lưu trong DB là đủ để xác minh mật khẩu đó có đúng ko! Đương nhiên có khả năng xuất hiện đụng độ; nhưng với các hàm băm xịn thì khả năng đụng độ là rất khó xảy ra. Do đó nếu băm mật khẩu gửi lên mà ra được cốt trùng với cốt đã lưu trong DB thì người dùng đó là hợp lệ!
Vậy tóm lại, với mức độ yêu cầu chỉ cần che đậy, ta cũng chỉ cần băm, mà không cần thiết phải mã hóa! Vậy vấn đề quy về việc phải thiết kế hàm băm sao cho xịn nhất có thể!

Điều này nói thật thì không dễ đâu. Trong lịch sử Mật mã học, tất cả các hàm băm được thiết kế ra cho dù nổi tiếng thế nào thì cũng đều đã được chứng minh là kém xịn rồi; bạn ko cần thiết phải thử đâu. Khổ cái là ông USER nữa ấy, ông ấy thường hay quên, nên ông ấy thường hay đặt mật khẩu sao cho dễ nhớ nhất! Càng dễ nhớ cho ông ấy bao nhiêu thì chứng tỏ mật khẩu càng đơn giản bấy nhiêu. Mà mật khẩu càng đơn giản bao nhiêu thì việc vét cạn không gian từ điển trong tấn công từ điển lại càng khả quan bấy nhiêu!

Ta ko thể nào bắt ép USER phải nhớ 1 chuỗi mật khẩu toàn những ký tự ghép lại thành 1 chuỗi vô nghĩa (ví dụ như "2oq87r3ARF*p3 r9"); nhưng nếu không làm vậy (mà cứ đặt mật khẩu kiểu "1235aye") thì đúng là biếu mật khẩu cho hacker rồi còn j! Người dùng bị lộ mật khẩu, sẽ mất niềm tin ở dịch vụ; nhiều người bị như vậy thì còn phục vụ ai được nữa! Do đó chúng ta chỉ có thể khuyến cáo USER nên đặt mật khẩu sao cho có cả chữ thường lẫn chữ hoa, chữ số và các ký tự đặc biệt. Càng lằng nhằng thì càng khó đoán!

Đoán ở đây ko phải là việc hacker ngồi "đoán" theo nghĩa đen đâu nhé! Nên nhớ rằng từ rất lâu rồi, trên thế giới đã lưu hành các bộ dữ liệu về "mật khẩu thông dụng" của USER trên toàn thế giới. Theo mình tìm hiểu thì có một list như vậy tên là Rainbow Table; có thể tải miễn phí hoặc trả chút phí để có được danh sách mật khẩu phục vụ tấn công từ điển. Đừng có coi thường cái này, cái mật khẩu "1234aye" ở trên chắc chắn có trong từ điển đó đấy! Bạn nghĩ mật khẩu của bạn 100% không nằm trong đó? 

Do đó, nếu cứ đem nguyên mật khẩu của USER đi băm thì chắc chắn khả năng cao sẽ dính tấn công từ điển; vì mật khẩu của USER thường là tồi vì dễ bị dò ra được! Ở một số hệ thống sẽ đưa ra khuyến cáo yêu cầu người dùng đặt lại mật khẩu cho đến khi nào hệ thống nhận thấy đủ đỡ tồi hơn thì sẽ chấp nhận mật khẩu. Tuy nhiên cách đó cũng chỉ hạn chế rất ít việc bị tấn công từ điển thôi. Do đó các kỹ thuật nấu ăn sau được ra đời!

Cơ chế phòng thủ của Salt - Muối: làm chậm tấn công từ điển.

Muối này bản chất là một chuỗi bit ngẫu nhiên được "trộn" cùng vào với mật khẩu; sau đó sẽ đem băm cả 2 thứ này cùng lúc. Giả sử USER1 sẽ có mật khẩu là password1 và được gán thêm 1 Muối salt1 là một chuỗi bit ngẫu nhiên. Khi đó ta sẽ đem băm để tạo thành hash(password1||salt1). Người ta sẽ lưu hash(password1||salt1) vào cùng một chỗ với salt1.

Có thể thấy sau khi thực hiện băm thêm Muối, không gian mật khẩu sẽ được nở ra một cách thực sự theo hàm số mũ. Có nghĩa là nếu Muối có độ dài k bit thì tấn công từ điển sẽ bị làm chậm 2k lần! Trong thực tế thì Muối này có thể coi là một tham số khóa trong DB, mỗi USER sẽ có một Muối riêng, và được gán ngẫu nhiên. Muối sẽ được lấy ra để trộn với mật khẩu mà User gửi lên hệ thống. Sau khi băm, nếu hợp lệ, nghĩa là truy cập hợp lệ. Nếu không có Muối thì sẽ không thể xác thực được User, do đó Muối phải nằm cùng DB với hash(password||salt).

Do đó, nếu hacker có thể leak được DB và show được ra toàn bộ hash(password||salt) và salt; thì chúng cũng mất thêm gấp 2k lần thời gian để tấn công từ điển. Từ đó có thể thấy rằng, cho dù User có đặt mật khẩu tồi đi chăng nữa thì Muối cũng sẽ làm cho độ phức tạp của tất cả các hash(password||salt) trở lên same same nhau; khiến hacker khó có thể thực hiện phương án tấn công từ điển hơn! Lưu ý rằng ko nên "ăn mặn" quá nhé; coi chừng băm mất thời gian đó!

Quay trở lại câu hỏi của anh Thái DN, bạn chắc sẽ thấy lạ vì anh ấy nói "muối ko cần giữ bí mật"! Ờ thì đương nhiên là khi đặt Muối ở cùng DB với hash(password||salt) thì nghĩa là Muối đã ko còn bí mật nữa rồi; nhưng cũng ko phải "ko cần giữ bí mật" đến mức bạn đem show hết hàng ra cho người khác xem đâu nhé! Về mặt lý tưởng, Muối cũng nên được giữ bí mật và lưu ở DB khác. Tuy nhiên thực tế đâu có "lý tưởng". Nếu làm vậy, ta sẽ phải mất công ánh xạ từng hash(password||salt) với salt tương ứng; điều này nghĩ qua thôi cũng thấy tốn kém rồi. Hơn nữa nếu bảo vệ Muối, thì ta lại phải băm Muối? Nghe cứ vô lý thế quái nào ấy nhỉ! Mà nếu ko băm thì lại phải đối mặt với việc DB chứa Muối có thể bị leak. OK fine; vậy như anh Thái DN nói là quá đủ. Không cần phải bảo vệ Muối đến mức đặc biệt quá, chỉ cần đặt nó cùng hash(password||salt) là đủ bảo mật rồi!

Cơ chế phòng thủ của Pepper - Tiêu: kết hợp lợi thế của cả mã hóa vào băm.

Muối ở trên đã làm rất tốt việc làm chậm tấn công từ điển với cả những mật khẩu tồi. Nhưng sự thật ở đây, chỉ là làm chậm mà thôi; chứ không phải ngăn chặn! 😅

Đúng là cho dù salting (thêm Muối) đã làm chậm đáng kể việc tấn công từ điển của hacker rồi; nhưng thực tế thì bọn hacker luôn có những động lực phi thường đến mức không tưởng để đạt được mục đích! Do đó, làm chậm thôi là không đủ để bảo vệ mật khẩu của User khỏi những lý tưởng cao vời vợi! Do đó Pepper ra đời để làm cuộc sống thêm cay và sướng!

Theo như định nghĩa về Pepper Algorithm, Tiêu ở đây chỉ cần duy nhất 1; ko cần mỗi User một Tiêu riêng. Tất cả mật khâủ của mọi User đều được băm theo cách thức hash(password||salt||pepper) trước khi lưu vào DB. Nhưng lưu ý rằng Tiêu này phải được giữ bí mật và được lưu ở khác vị trí với DB lưu hash(password||salt||pepper)! Đương nhiên rồi, nếu lưu cùng chỗ thì Tiêu cũng chả khác con khỉ j so với Muối cả, xàm xí vãi!

Do đó, Tiêu cần phải được giữ bí mật. Vì chỉ có 1 Tiêu duy nhất cho một Service, nên việc lưu trữ cũng như lấy Tiêu ra sử dụng đều không phải là khó khăn! Nếu hệ thống của bạn có nhiều Service thì nên chọn mỗi Service một Tiêu riêng nhé, không nên đặt cả hệ thống 1 Tiêu! Và cũng vì Tiêu là bí mật, do đó Tiêu đem lại cho chúng ta thêm một hàng rào phòng thủ mới. Vì Tiêu bí mật, nên có thể coi Tiêu là khóa; do đó hàng rào phòng thủ này mang màu sắc của mã hóa. Nhưng nó ko phải mã hóa đâu nhé, vì bạn ko thể nào dịch ngược từ hash(password||salt||pepper) thành bất cứ cái j có ý nghĩa đâu!

Tại sao lại là "băm" chứ ko phải "mã hóa"?

Vấn đề này mình đã trình bày ở trên rồi, nhưng để chốt lại bài viết thì mình xin khẳng định thêm về băm trong việc lưu trữ mật khẩu.

Mã hóa thì có khả năng sẽ bị giải mã, nhưng băm thì never, trừ khi bruteforce attack mà thôi! Do đó, Muối và Tiêu đã xuất hiện và phối hợp với nhau rất ăn ý để tạo nên hương vị của việc lưu trữ mật khẩu. Nếu Muối góp phần làm chậm việc tấn công từ điển, thì Tiêu lại đóng vai trò như một khóa để ngăn chặn tấn công từ điển một cách thực sự! Trừ khi hacker biết được Tiêu; còn ko thì mật khẩu của bạn luôn luôn ở trong trạng thái an toàn nhất! Sự kết hợp này giữa Muối và Tiêu đã mang lại lợi thế của cả 2 phương thức dấp diếm dữ liệu là băm và mã hóa!

Khi bạn nhấn nút "LOGIN" để thực hiện đăng nhập, quá trình tương tác thực tế thường sẽ là:

  • Trình duyệt của bạn gửi một HTTP GET Request để lấy về Token giúp cho quá trình đăng nhập. Token này chính là Muối mà chúng ta đã tìm hiểu ở trên. Server sẽ nhìn vào userID mà trả về Token tương ứng.
  • Token này sẽ được trộn vào cùng password của bạn, rồi thực hiện mã hóa base64 (sida vãi) rồi gửi lên Server bằng giao thức HTTPS thông qua HTTP POST Request.
Ở một số trường hợp, Server sẽ không gửi về Token mà yêu cầu Client phải POST luôn thông tin đăng nhập. Nói chung mỗi cách xử lý đều có ưu điểm riêng, tuy nhiên việc để lộ Muối là nên hạn chế để tránh trường hợp attacker có được userID rồi lấy được Muối, sẽ lại có ích cho việc tấn công từ điển nếu Server không sử dụng Tiêu.

Mấu chốt ở đây chính là giao thức HTTPS. Chữ S cuối cùng chính là viết tắt của giao thức SSL/TLS. Không có SSL/TLS, hệ thống thương mại điện tử của cả nhân loại sẽ sụp đổ ngay và luôn. Tuy nhiên SSL/TLS không phải bất bại. Thực tế SSL/TLS đã trải qua rất nhiều phiên bản trong lịch sử để có được mức độ bảo mật như ngày hôm nay chúng ta sử dụng. Có điều SSL/TLS vẫn có thể bị tấn công bằng kỹ thuật Downgrade Attack mà tôi trình bày trong các bài viết tiếp theo.

Cuối cùng thì cảm ơn anh Thái DN đã đem đến cho em những kiến thức mới; dù em ko thích cách suy nghĩ của anh về các nhà lãnh đạo, nhưng em luôn hóng các bài viết mới của anh về kỹ thuật!

Nhận xét

Bài đăng phổ biến từ blog này

Trên con đường tu đạo luôn cực kỳ theo đuổi!

C++ Con trỏ (Pointer) toàn thư: Phần 4: Con trỏ "đa cấp". Đánh nhau bằng con trỏ.

Vừa ngộ ra sự vi diệu của Padding Oracle Attack thì được tin crush hồi lớp 12 sắp cưới.