Các .class
tệp Java có thể được dịch ngược khá dễ dàng. Làm cách nào để bảo vệ cơ sở dữ liệu của mình nếu tôi phải sử dụng dữ liệu đăng nhập trong mã?
Các .class
tệp Java có thể được dịch ngược khá dễ dàng. Làm cách nào để bảo vệ cơ sở dữ liệu của mình nếu tôi phải sử dụng dữ liệu đăng nhập trong mã?
Câu trả lời:
Không bao giờ mã hóa mật khẩu cứng vào mã của bạn. Điều này gần đây đã được đưa ra trong Top 25 sai lầm lập trình nguy hiểm nhất :
Mã hóa cứng một tài khoản và mật khẩu bí mật vào phần mềm của bạn cực kỳ thuận tiện - đối với các kỹ sư đảo ngược có tay nghề cao. Nếu mật khẩu giống nhau trên tất cả phần mềm của bạn, thì mọi khách hàng sẽ dễ bị tấn công khi mật khẩu đó chắc chắn bị biết. Và bởi vì nó được mã hóa khó, đó là một vấn đề rất lớn để sửa chữa.
Bạn nên lưu trữ thông tin cấu hình, bao gồm cả mật khẩu, trong một tệp riêng biệt mà ứng dụng sẽ đọc khi khởi động. Đó là cách thực sự duy nhất để ngăn mật khẩu bị rò rỉ do dịch ngược (không bao giờ biên dịch nó thành nhị phân để bắt đầu).
Để biết thêm thông tin về lỗi phổ biến này, bạn có thể đọc bài viết CWE-259 . Bài viết có định nghĩa kỹ lưỡng hơn, ví dụ và nhiều thông tin khác về vấn đề.
Trong Java, một trong những cách dễ nhất để làm điều này là sử dụng lớp Preferences. Nó được thiết kế để lưu trữ tất cả các loại cài đặt chương trình, một số cài đặt có thể bao gồm tên người dùng và mật khẩu.
import java.util.prefs.Preferences;
public class DemoApplication {
Preferences preferences =
Preferences.userNodeForPackage(DemoApplication.class);
public void setCredentials(String username, String password) {
preferences.put("db_username", username);
preferences.put("db_password", password);
}
public String getUsername() {
return preferences.get("db_username", null);
}
public String getPassword() {
return preferences.get("db_password", null);
}
// your code here
}
Trong đoạn mã trên, bạn có thể gọi setCredentials
phương thức sau khi hiển thị hộp thoại yêu cầu tên người dùng và mật khẩu. Khi bạn cần kết nối với cơ sở dữ liệu, bạn chỉ có thể sử dụng các phương thức getUsername
và getPassword
để truy xuất các giá trị được lưu trữ. Thông tin đăng nhập sẽ không được mã hóa cứng thành các tệp nhị phân của bạn, vì vậy việc dịch ngược sẽ không gây ra rủi ro bảo mật.
Lưu ý quan trọng: Tệp ưu tiên chỉ là tệp XML văn bản thuần túy. Đảm bảo bạn thực hiện các bước thích hợp để ngăn người dùng trái phép xem các tệp thô (quyền UNIX, quyền Windows, v.v.). Trong Linux, ít nhất, đây không phải là vấn đề, vì việc gọi Preferences.userNodeForPackage
sẽ tạo tệp XML trong thư mục chính của người dùng hiện tại, mà người dùng khác không thể đọc được. Trong Windows, tình hình có thể khác.
Lưu ý quan trọng hơn: Đã có rất nhiều cuộc thảo luận trong các nhận xét của câu trả lời này và những người khác về kiến trúc chính xác cho tình huống này. Câu hỏi ban đầu không thực sự đề cập đến bối cảnh mà ứng dụng đang được sử dụng, vì vậy tôi sẽ nói về hai tình huống mà tôi có thể nghĩ đến. Đầu tiên là trường hợp người sử dụng chương trình đã biết (và được phép biết) thông tin xác thực cơ sở dữ liệu. Thứ hai là trường hợp bạn, nhà phát triển, đang cố gắng giữ bí mật thông tin xác thực cơ sở dữ liệu với người sử dụng chương trình.
Trường hợp đầu tiên: Người dùng được phép biết thông tin đăng nhập cơ sở dữ liệu
Trong trường hợp này, giải pháp tôi đã đề cập ở trên sẽ hoạt động. Lớp Java Preference
sẽ lưu trữ tên người dùng và mật khẩu dưới dạng văn bản thuần túy, nhưng tệp tùy chọn sẽ chỉ có người dùng được ủy quyền mới có thể đọc được. Người dùng có thể chỉ cần mở tệp XML tùy chọn và đọc thông tin đăng nhập, nhưng đó không phải là một rủi ro bảo mật vì người dùng đã biết thông tin đăng nhập để bắt đầu.
Trường hợp thứ hai: Cố gắng ẩn thông tin đăng nhập khỏi người dùng
Đây là trường hợp phức tạp hơn: người dùng không nên biết thông tin đăng nhập nhưng vẫn cần truy cập vào cơ sở dữ liệu. Trong trường hợp này, người dùng đang chạy ứng dụng có quyền truy cập trực tiếp vào cơ sở dữ liệu, có nghĩa là chương trình cần biết thông tin đăng nhập trước thời hạn. Giải pháp tôi đề cập ở trên không thích hợp cho trường hợp này. Bạn có thể lưu trữ thông tin đăng nhập cơ sở dữ liệu trong một tệp tùy chọn, nhưng người dùng sẽ có thể đọc tệp đó, vì họ sẽ là chủ sở hữu. Trên thực tế, không có cách nào tốt để sử dụng trường hợp này một cách an toàn.
Trường hợp đúng: Sử dụng kiến trúc nhiều tầng
Cách chính xác để làm điều đó là có một lớp giữa, ở giữa máy chủ cơ sở dữ liệu và ứng dụng khách của bạn, xác thực từng người dùng và cho phép thực hiện một số hoạt động hạn chế. Mỗi người dùng sẽ có thông tin đăng nhập của riêng họ, nhưng không phải cho máy chủ cơ sở dữ liệu. Thông tin xác thực sẽ cho phép truy cập vào lớp giữa (tầng logic nghiệp vụ) và sẽ khác nhau đối với mỗi người dùng.
Mỗi người dùng sẽ có tên người dùng và mật khẩu của riêng họ, có thể được lưu trữ cục bộ trong tệp tùy chọn mà không có bất kỳ rủi ro bảo mật nào. Đây được gọi là kiến trúc ba tầng (các tầng là máy chủ cơ sở dữ liệu, máy chủ logic nghiệp vụ và ứng dụng khách của bạn). Nó phức tạp hơn, nhưng nó thực sự là cách an toàn nhất để thực hiện loại việc này.
Thứ tự hoạt động cơ bản là:
getInventoryList
.Lưu ý rằng trong toàn bộ quy trình, ứng dụng khách không bao giờ kết nối trực tiếp với cơ sở dữ liệu . Tầng logic nghiệp vụ nhận yêu cầu từ người dùng đã xác thực, xử lý yêu cầu của khách hàng về danh sách khoảng không quảng cáo và chỉ sau đó thực thi truy vấn SQL.
Đặt mật khẩu vào một tệp mà ứng dụng sẽ đọc. KHÔNG BAO GIỜ nhúng mật khẩu vào tệp nguồn. Giai đoạn = Stage.
Ruby có một mô-đun ít được biết đến được gọi là DBI :: DBRC để sử dụng như vậy. Tôi không nghi ngờ rằng Java có một tương đương. Dù sao, nó không khó để viết một.
Bạn đang viết một ứng dụng web? Nếu vậy, hãy sử dụng JNDI để cấu hình nó bên ngoài ứng dụng. Tổng quan có sẵn tại đây :
JNDI cung cấp một cách thống nhất cho ứng dụng để tìm và truy cập các dịch vụ từ xa qua mạng. Dịch vụ từ xa có thể là bất kỳ dịch vụ doanh nghiệp nào, bao gồm dịch vụ nhắn tin hoặc dịch vụ dành riêng cho ứng dụng, nhưng tất nhiên, ứng dụng JDBC chủ yếu quan tâm đến dịch vụ cơ sở dữ liệu. Sau khi một đối tượng DataSource được tạo và đăng ký với dịch vụ đặt tên JNDI, một ứng dụng có thể sử dụng API JNDI để truy cập vào đối tượng DataSource đó, đối tượng này sau đó có thể được sử dụng để kết nối với nguồn dữ liệu mà nó đại diện.
Bất kể bạn làm gì, thông tin nhạy cảm sẽ được lưu trữ trong một số tệp ở đâu đó. Mục tiêu của bạn là làm cho nó càng khó càng tốt. Bạn có thể đạt được bao nhiêu điều này phụ thuộc vào dự án, nhu cầu và độ dày của ví tiền của công ty bạn.
Cách tốt nhất là không lưu trữ bất kỳ mật khẩu nào ở bất kỳ đâu. Điều này đạt được bằng cách sử dụng các hàm băm để tạo và lưu trữ các băm mật khẩu:
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366
Các thuật toán băm là một hàm một chiều. Chúng biến bất kỳ lượng dữ liệu nào thành một "dấu vân tay" có độ dài cố định không thể đảo ngược. Chúng cũng có đặc tính là nếu đầu vào thay đổi dù chỉ một bit nhỏ, thì hàm băm kết quả sẽ hoàn toàn khác (xem ví dụ ở trên). Điều này rất tốt để bảo vệ mật khẩu, vì chúng tôi muốn lưu trữ mật khẩu ở dạng bảo vệ chúng ngay cả khi bản thân tệp mật khẩu bị xâm phạm, nhưng đồng thời, chúng tôi cần xác minh rằng mật khẩu của người dùng là chính xác.
Lưu ý không liên quan: Ngày xưa của Internet, khi bạn nhấp vào liên kết 'quên mật khẩu của tôi', các trang web sẽ gửi cho bạn mật khẩu văn bản thuần túy của bạn qua email. Họ có thể đang lưu trữ chúng trong cơ sở dữ liệu ở đâu đó. Khi tin tặc có quyền truy cập vào cơ sở dữ liệu của họ, họ sẽ có quyền truy cập vào tất cả các mật khẩu. Vì nhiều người dùng sẽ sử dụng cùng một mật khẩu trong nhiều trang web, đây là một vấn đề bảo mật lớn. May mắn thay, ngày nay đây không phải là một thực tế phổ biến.
Bây giờ đến câu hỏi: cách tốt nhất để lưu trữ mật khẩu là gì? Tôi sẽ coi giải pháp này (dịch vụ xác thực và quản lý người dùng của stormpath) là một giải pháp khá lý tưởng:
Rõ ràng bạn không phải là google hay ngân hàng, vì vậy đây là một giải pháp quá mức cần thiết cho bạn. Nhưng sau đó là câu hỏi: Dự án của bạn yêu cầu bao nhiêu bảo mật, bạn có bao nhiêu thời gian và tiền bạc?
Đối với nhiều ứng dụng, mặc dù không được khuyến khích, lưu trữ mật khẩu được mã hóa cứng trong mã có thể là một giải pháp đủ tốt. Tuy nhiên, bằng cách dễ dàng thêm một số bước bảo mật bổ sung từ danh sách trên, bạn có thể làm cho ứng dụng của mình an toàn hơn nhiều.
Ví dụ: giả sử bước 1 không phải là một giải pháp chấp nhận được cho dự án của bạn. Bạn không muốn người dùng nhập mật khẩu mọi lúc, hoặc bạn thậm chí không muốn / cần người dùng biết mật khẩu. Bạn vẫn có thông tin nhạy cảm ở đâu đó và bạn muốn bảo vệ thông tin này. Bạn có một ứng dụng đơn giản, không có máy chủ để lưu trữ các tệp của bạn hoặc điều này quá phức tạp cho dự án của bạn. Ứng dụng của bạn chạy trên các môi trường không thể lưu trữ các tệp một cách an toàn. Đây là một trong những trường hợp xấu nhất, nhưng vẫn với một số biện pháp bảo mật bổ sung, bạn có thể có giải pháp an toàn hơn nhiều. Ví dụ: bạn có thể lưu trữ thông tin nhạy cảm trong một tệp và bạn có thể mã hóa tệp. Bạn có thể có khóa cá nhân mã hóa cứng được mã hóa trong mã. Bạn có thể làm xáo trộn mã, vì vậy bạn sẽ khiến ai đó khó bẻ khóa nó hơn một chút.liên kết này . (Tôi muốn cảnh báo bạn một lần nữa rằng điều này không an toàn 100%. Một hacker thông minh có kiến thức và công cụ phù hợp có thể hack được. Nhưng dựa trên yêu cầu và nhu cầu của bạn, đây có thể là một giải pháp đủ tốt cho bạn).
Câu hỏi này cho biết cách lưu trữ mật khẩu và các dữ liệu khác trong tệp được mã hóa: Mã hóa dựa trên mật khẩu AES 256-bit của Java
MD5 là một thuật toán băm, không phải là một thuật toán mã hóa, trong ngắn hạn bạn không thể lấy lại được bởi vì bạn đã băm, bạn chỉ có thể so sánh. Lý tưởng nhất là nó nên được sử dụng khi lưu trữ thông tin xác thực người dùng chứ không phải db tên người dùng và mật khẩu. Tên người dùng db và pwd phải được mã hóa và lưu giữ trong một tệp cấu hình, để thực hiện ít nhất.