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 nêu ra trong 25 sai lầm lập trình nguy hiểm nhất :
Mã hóa cứng một tài khoản bí mật và mật khẩu vào phần mềm của bạn là vô cùng thuận tiện - đối với các kỹ sư có chuyên môn 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 đều trở nên dễ bị tấn công khi mật khẩu đó chắc chắn trở nên không xác định. Và bởi vì nó được mã hóa khó, nên đó là một vấn đề lớn cần khắc phục.
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 mật khẩu sang dạng 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 chứa định nghĩa kỹ lưỡng hơn, ví dụ và nhiều thông tin khác về vấn đề này.
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
sau khi hiển thị hộp thoại yêu cầu nhập tên người dùng và mật khẩu. Khi cần kết nối với cơ sở dữ liệu, bạn chỉ có thể sử dụng getUsername
và getPassword
các phương thức để lấy các giá trị được lưu trữ. Thông tin xác thực đăng nhập sẽ không được mã hóa cứng thành 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: Các tệp ưu tiên chỉ là các 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ì 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.
Ghi chú quan trọng hơn: Đã có rất nhiều cuộc thảo luận trong các bình luận 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 đăng nhập 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 xác thực đă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. Java Preference
lớp sẽ lưu tên người dùng và mật khẩu ở dạng văn bản thuần túy, nhưng tệp tùy chọn sẽ chỉ có thể đọc được bởi người dùng được ủy quyền. 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à 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 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 xác thực đă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 đăng nhập 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 cấp 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 để làm việc này.
Thứ tự hoạt động cơ bản là:
- Ứng dụng khách xác thực với cấp logic nghiệp vụ bằng tên người dùng / mật khẩu cá nhân của người dùng. Tên người dùng và mật khẩu được người dùng biết và không liên quan đến thông tin đăng nhập cơ sở dữ liệu theo bất kỳ cách nào.
- Nếu xác thực thành công, máy khách đưa ra yêu cầu đối với tầng logic nghiệp vụ yêu cầu một số thông tin từ cơ sở dữ liệu. Ví dụ, một kho sản phẩm. Lưu ý rằng yêu cầu của khách hàng không phải là một truy vấn SQL; nó là một lệnh gọi thủ tục từ xa chẳng hạn như
getInventoryList
. - Tầng logic nghiệp vụ kết nối với cơ sở dữ liệu và truy xuất thông tin được yêu cầu. Tầng logic nghiệp vụ chịu trách nhiệm hình thành một truy vấn SQL an toàn dựa trên yêu cầu của người dùng. Bất kỳ tham số nào của truy vấn SQL đều phải được làm sạch để ngăn chặn các cuộc tấn công chèn vào SQL.
- Tầng logic nghiệp vụ gửi lại danh sách khoảng không quảng cáo cho ứng dụng khách.
- Khách hàng hiển thị danh sách khoảng không quảng cáo cho người dùng.
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.