Mã hóa mật khẩu trong tập tin cấu hình? [đóng cửa]


130

Tôi có một chương trình đọc thông tin máy chủ từ tệp cấu hình và muốn mã hóa mật khẩu trong cấu hình đó có thể được đọc bởi chương trình của tôi và được giải mã.

Yêu cầu:

  • Mã hóa mật khẩu văn bản gốc được lưu trữ trong tệp
  • Giải mã mật khẩu được mã hóa đọc từ tệp từ chương trình của tôi

Bất kỳ đề xuất nào về cách tôi sẽ làm về điều này? Tôi đã nghĩ đến việc viết thuật toán của riêng tôi nhưng tôi cảm thấy nó sẽ không an toàn khủng khiếp.

Câu trả lời:


172

Một cách đơn giản để làm điều này là sử dụng Mã hóa dựa trên mật khẩu trong Java. Điều này cho phép bạn mã hóa và giải mã một văn bản bằng cách sử dụng mật khẩu.

Này về cơ bản có nghĩa là khởi tạo một javax.crypto.Ciphervới thuật toán "AES/CBC/PKCS5Padding"và nhận được một chìa khóa từ javax.crypto.SecretKeyFactoryvới "PBKDF2WithHmacSHA512"thuật toán.

Dưới đây là một ví dụ mã (được cập nhật để thay thế biến thể dựa trên MD5 kém an toàn hơn):

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class ProtectedConfigFile {

    public static void main(String[] args) throws Exception {
        String password = System.getProperty("password");
        if (password == null) {
            throw new IllegalArgumentException("Run with -Dpassword=<password>");
        }

        // The salt (probably) can be stored along with the encrypted data
        byte[] salt = new String("12345678").getBytes();

        // Decreasing this speeds down startup time and can be useful during testing, but it also makes it easier for brute force attackers
        int iterationCount = 40000;
        // Other values give me java.security.InvalidKeyException: Illegal key size or default parameters
        int keyLength = 128;
        SecretKeySpec key = createSecretKey(password.toCharArray(),
                salt, iterationCount, keyLength);

        String originalPassword = "secret";
        System.out.println("Original password: " + originalPassword);
        String encryptedPassword = encrypt(originalPassword, key);
        System.out.println("Encrypted password: " + encryptedPassword);
        String decryptedPassword = decrypt(encryptedPassword, key);
        System.out.println("Decrypted password: " + decryptedPassword);
    }

    private static SecretKeySpec createSecretKey(char[] password, byte[] salt, int iterationCount, int keyLength) throws NoSuchAlgorithmException, InvalidKeySpecException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
        SecretKey keyTmp = keyFactory.generateSecret(keySpec);
        return new SecretKeySpec(keyTmp.getEncoded(), "AES");
    }

    private static String encrypt(String property, SecretKeySpec key) throws GeneralSecurityException, UnsupportedEncodingException {
        Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        pbeCipher.init(Cipher.ENCRYPT_MODE, key);
        AlgorithmParameters parameters = pbeCipher.getParameters();
        IvParameterSpec ivParameterSpec = parameters.getParameterSpec(IvParameterSpec.class);
        byte[] cryptoText = pbeCipher.doFinal(property.getBytes("UTF-8"));
        byte[] iv = ivParameterSpec.getIV();
        return base64Encode(iv) + ":" + base64Encode(cryptoText);
    }

    private static String base64Encode(byte[] bytes) {
        return Base64.getEncoder().encodeToString(bytes);
    }

    private static String decrypt(String string, SecretKeySpec key) throws GeneralSecurityException, IOException {
        String iv = string.split(":")[0];
        String property = string.split(":")[1];
        Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(base64Decode(iv)));
        return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
    }

    private static byte[] base64Decode(String property) throws IOException {
        return Base64.getDecoder().decode(property);
    }
}

Một vấn đề còn tồn tại: Bạn nên lưu trữ mật khẩu mà bạn sử dụng để mã hóa mật khẩu ở đâu? Bạn có thể lưu trữ nó trong tệp nguồn và làm xáo trộn nó, nhưng không quá khó để tìm lại nó. Ngoài ra, bạn có thể cung cấp nó như một thuộc tính hệ thống khi bạn bắt đầu quá trình Java ( -DpropertyProtectionPassword=...).

Vấn đề tương tự vẫn còn nếu bạn sử dụng KeyStore, cũng được bảo vệ bằng mật khẩu. Về cơ bản, bạn sẽ cần phải có một mật khẩu chính ở đâu đó và thật khó để bảo vệ.


3
Cảm ơn ví dụ mã, gần như tôi đã làm nó như thế nào. Liên quan đến mật khẩu bảo vệ mật khẩu tôi gặp phải vấn đề tương tự, tôi đã từ bỏ phương pháp làm xáo trộn nó ngay bây giờ nhưng chưa có giải pháp chấp nhận được, cảm ơn vì những gợi ý của bạn.
Petey B

7
"Ngoài ra, bạn có thể cung cấp nó như một thuộc tính hệ thống khi bạn bắt đầu quá trình Java (-DpropertyProtectionPassword = ...)". Lưu ý rằng điều này sẽ giúp trích xuất mật khẩu bằng cách sử dụng "ps fax" trên (GNU / Linux) / UNIX.
Ztyx

7
@Ben Đó là cách thông thường để mã hóa vào Base64 để cho phép bạn lưu trữ giá trị kết quả trong cột cơ sở dữ liệu tệp hoặc chuỗi văn bản hoặc tương tự.
RB.

4
@ V.7 không. MD5 hoàn toàn không an toàn cho việc băm mật khẩu và không bao giờ được thiết kế cho mục đích đó. Không bao giờ sử dụng nó cho điều đó. Những ngày này, Argon2 là tốt nhất. Xem owasp.org/index.php/Password_Storage_Cheat_Sheetparagonie.com/blog/2016/02/how-safely-store-password-in-2016
Kimball Robinson

3
Cách này tốt hơn nhiều. Tất nhiên, một muối ngẫu nhiên an toàn và số lần lặp là 40K (bảo thủ, cấp thấp) sẽ đẹp hơn, nhưng ít nhất bạn đã chỉ ra những điều này trong các nhận xét và PBKDF2 và AES / CBC là những cải tiến rõ ràng. Tôi nghĩ thật tuyệt vời khi bạn xử lý việc này, bằng cách cập nhật câu trả lời; Tôi sẽ gỡ bỏ cảnh báo. Bình chọn bình luận của bạn để mọi người không ngạc nhiên khi tìm thấy mã được cập nhật (họ có thể xem các chỉnh sửa để tìm mã cũ mà tôi cho là). Có thể là một ý tưởng tốt để làm sạch các bình luận cũ của bạn là tốt.
Maarten Bodewes

20

Có, chắc chắn không viết thuật toán của riêng bạn. Java có rất nhiều API mã hóa.

Nếu HĐH bạn đang cài đặt có kho khóa, thì bạn có thể sử dụng nó để lưu trữ khóa mật mã mà bạn sẽ cần mã hóa và giải mã dữ liệu nhạy cảm trong cấu hình hoặc các tệp khác.


4
+1 để sử dụng KeyStore! Không có gì hơn là xáo trộn nếu bạn đang lưu trữ khóa trong tệp Jar.
ninesided

2
Nếu tất cả những gì cần thiết là không có mật khẩu được lưu trữ trong văn bản rõ ràng, thì các kho khóa là quá mức cần thiết.
Thorbjørn Ravn Andersen

20

Kiểm tra jasypt , một thư viện cung cấp các khả năng mã hóa cơ bản với nỗ lực tối thiểu.


16

Tôi nghĩ rằng cách tiếp cận tốt nhất là đảm bảo rằng tệp cấu hình của bạn (chứa mật khẩu của bạn) chỉ có thể truy cập được vào một tài khoản người dùng cụ thể . Ví dụ: bạn có thể có một người dùng ứng dụng cụ thể appusermà chỉ những người đáng tin cậy mới có mật khẩu (và họ susẽ sử dụng mật khẩu ).

Bằng cách đó, không có mật mã gây phiền nhiễu và bạn vẫn có mật khẩu an toàn.

EDIT: Tôi giả sử rằng bạn không xuất cấu hình ứng dụng của mình ra ngoài môi trường đáng tin cậy (điều mà tôi không chắc sẽ có ý nghĩa gì, được đưa ra câu hỏi)


4

Để giải quyết các vấn đề về mật khẩu chính - cách tiếp cận tốt nhất là không lưu trữ mật khẩu ở bất cứ đâu, ứng dụng nên mã hóa mật khẩu cho chính nó - để chỉ có nó mới có thể giải mã chúng. Vì vậy, nếu tôi đang sử dụng tệp .config tôi sẽ làm như sau, mySinstall.config :

ryptTheseKeys = secretKey, AnotherSecret

secretKey = unsotectedPasswordThatIputHere

AnotherSecret = AnotherPass

someKey = unsotectedSettingIdontCare About

vì vậy tôi sẽ đọc các khóa được đề cập trong mã hóaTheseKeys, áp dụng ví dụ Brodwalls từ trên lên trên và viết lại vào tệp với một điểm đánh dấu nào đó (giả sử là mật mã :) để cho ứng dụng biết không làm điều đó một lần nữa, đầu ra sẽ như thế này:

ryptTheseKeys = secretKey, AnotherSecret

secretKey = mật mã: ii4jfj304fjhfj934fouh938

otherSecret = mật mã: jd48jofh48h

someKey = unsotectedSettingIdontCare About

Chỉ cần đảm bảo giữ bản gốc ở nơi an toàn của riêng bạn ...


2
Vâng, đây là từ 3 năm trước. Để tránh khóa chính, tôi đã kết thúc bằng cách sử dụng các khóa RSA được cấp từ CA nội bộ của chúng tôi. Quyền truy cập vào khóa riêng được bảo vệ bằng cách được mã hóa bằng dấu vân tay của phần cứng máy.
Petey B

Tôi thấy, âm thanh khá vững chắc. đẹp.
dùng1007231

@ user1007231 - Nơi giữ - "Chỉ cần đảm bảo giữ bản gốc ở nơi an toàn của riêng bạn ..."?
nanosoft

@PeteyB - Không hiểu sao? Bạn có thể chỉ cho tôi một số liên kết có thể khai sáng cho tôi. Cảm ơn
nanosoft

@nanosoft - Nhận "Aegis Secure Key USB" và lưu trữ trong tài liệu văn bản ở đó hoặc trên giấy trong ví của bạn
user1007231

4

Điểm quan trọng, và con voi trong phòng và tất cả những thứ đó, là nếu ứng dụng của bạn có thể giữ được mật khẩu, thì một hacker có quyền truy cập vào hộp cũng có thể bị giữ lại!

Cách duy nhất xoay quanh vấn đề này là ứng dụng yêu cầu "mật khẩu chính" trên bàn điều khiển bằng cách sử dụng Tiêu chuẩn đầu vào, sau đó sử dụng cách này để giải mã mật khẩu được lưu trong tệp. Tất nhiên, điều này hoàn toàn không thể khiến ứng dụng khởi động không cần giám sát cùng với HĐH khi khởi động.

Tuy nhiên, ngay cả với mức độ khó chịu này, nếu một hacker quản lý để có quyền truy cập root (hoặc thậm chí chỉ truy cập khi người dùng chạy ứng dụng của bạn), anh ta có thể bỏ bộ nhớ và tìm mật khẩu ở đó.

Điều cần đảm bảo, là không để toàn bộ công ty có quyền truy cập vào máy chủ sản xuất (và qua đó là mật khẩu), và đảm bảo rằng không thể bẻ khóa hộp này!


Các giải pháp thực sự là để lưu trữ khóa riêng của bạn ở một nơi khác, giống như một thẻ hoặc một HSM: en.wikipedia.org/wiki/Hardware_security_module
atom88



0

Tùy thuộc vào mức độ an toàn mà bạn cần các tệp cấu hình hoặc mức độ tin cậy của ứng dụng của bạn, http://activemq.apache.org/encrypted-passwords.html có thể là một giải pháp tốt cho bạn.

Nếu bạn không quá sợ mật khẩu bị giải mã và việc cấu hình bằng cách sử dụng một bean để lưu khóa mật khẩu có thể thực sự đơn giản. Tuy nhiên, nếu bạn cần bảo mật hơn, bạn có thể đặt bí mật biến môi trường và xóa nó sau khi khởi chạy. Với điều này, bạn phải lo lắng về việc ứng dụng / máy chủ bị sập và ứng dụng không tự động khởi chạy lại.


sử dụng HSM là cách lý tưởng: en.wikipedia.org/wiki/Hardware_security_module
nguyên tử88

-8

Nếu bạn đang sử dụng java 8 , có thể tránh sử dụng bộ mã hóa và giải mã Base64 bên trong bằng cách thay thế

return new BASE64Encoder().encode(bytes);

với

return Base64.getEncoder().encodeToString(bytes);

return new BASE64Decoder().decodeBuffer(property);

với

return Base64.getDecoder().decode(property);

Lưu ý rằng giải pháp này không bảo vệ dữ liệu của bạn vì các phương pháp giải mã được lưu trữ ở cùng một nơi. Nó chỉ làm cho nó khó khăn hơn để phá vỡ. Chủ yếu là nó tránh in nó và hiển thị cho mọi người nhầm lẫn.


26
Base64 không mã hóa.
jwilleke

1
Base64 không phải là mã hóa và đó là ví dụ tồi tệ nhất mà bạn có thể cung cấp ... rất nhiều người tin rằng Base64 là một thuật toán mã hóa, vì vậy tốt hơn là đừng nhầm lẫn chúng ...
robob

lưu ý rằng phương thức decode () ở đầu tệp nguồn thực hiện mã hóa thực tế. Tuy nhiên, mã hóa và giải mã base64 này là cần thiết để chuyển đổi từ một chuỗi các byte để truyền chức năng này một cái gì đó mà nó có thể sử dụng (một byte byte byte [])
atom88 5/12/17
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.