เข้ารหัสรหัสผ่านในไฟล์กำหนดค่า? [ปิด]


130

ฉันมีโปรแกรมที่อ่านข้อมูลเซิร์ฟเวอร์จากไฟล์การกำหนดค่าและต้องการเข้ารหัสรหัสผ่านในการกำหนดค่านั้นซึ่งโปรแกรมของฉันสามารถอ่านและถอดรหัสได้

ข้อกำหนด:

  • เข้ารหัสรหัสผ่านข้อความธรรมดาเพื่อเก็บไว้ในไฟล์
  • ถอดรหัสรหัสผ่านที่เข้ารหัสที่อ่านจากไฟล์จากโปรแกรมของฉัน

มีคำแนะนำเกี่ยวกับวิธีการดำเนินการนี้หรือไม่? ฉันกำลังคิดที่จะเขียนอัลกอริทึมของตัวเอง แต่ฉันรู้สึกว่ามันไม่ปลอดภัยอย่างมาก

คำตอบ:


172

วิธีง่ายๆในการดำเนินการนี้คือใช้การเข้ารหัสด้วยรหัสผ่านใน Java ช่วยให้คุณเข้ารหัสและถอดรหัสข้อความโดยใช้รหัสผ่าน

นี้โดยทั่วไปหมายถึงการเริ่มต้นjavax.crypto.Cipherกับขั้นตอนวิธีการ"AES/CBC/PKCS5Padding"และได้รับกุญแจจากjavax.crypto.SecretKeyFactoryกับ"PBKDF2WithHmacSHA512"อัลกอริทึม

นี่คือตัวอย่างโค้ด (อัปเดตเพื่อแทนที่ตัวแปรที่ใช้ MD5 ที่มีความปลอดภัยน้อยกว่า):

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);
    }
}

ปัญหาหนึ่งยังคงอยู่: คุณควรเก็บรหัสผ่านที่ใช้เข้ารหัสรหัสผ่านไว้ที่ไหน? คุณสามารถจัดเก็บไว้ในซอร์สไฟล์และทำให้สับสน แต่ก็ไม่ยากเกินไปที่จะค้นหาอีกครั้ง หรือคุณสามารถให้เป็นคุณสมบัติของระบบเมื่อคุณเริ่มกระบวนการ Java ( -DpropertyProtectionPassword=...)

ปัญหาเดิมยังคงอยู่หากคุณใช้ KeyStore ซึ่งได้รับการป้องกันด้วยรหัสผ่านเช่นกัน โดยทั่วไปคุณจะต้องมีรหัสผ่านหลักหนึ่งรหัสและมันค่อนข้างยากที่จะป้องกัน


3
ขอบคุณสำหรับตัวอย่างโค้ดฉันทำมันได้ดีแค่ไหน เกี่ยวกับรหัสผ่านที่ป้องกันรหัสผ่านที่ฉันประสบปัญหาเดียวกันนั้นฉันใช้วิธีการทำให้สับสนในตอนนี้ แต่ยังไม่ได้หาวิธีแก้ไขที่ยอมรับได้ขอบคุณสำหรับคำแนะนำของคุณ
Petey B

7
"หรือคุณสามารถให้เป็นคุณสมบัติของระบบเมื่อคุณเริ่มกระบวนการ Java (-DpropertyProtectionPassword = ... )" โปรดทราบว่าวิธีนี้จะทำให้สามารถแยกรหัสผ่านโดยใช้ "ps fax" บน (GNU / Linux) / UNIX
Ztyx

7
@Ben เป็นเรื่องปกติที่จะเข้ารหัสเป็น Base64 เพื่อให้คุณสามารถจัดเก็บค่าผลลัพธ์ในคอลัมน์ฐานข้อมูลไฟล์ข้อความหรือสตริงหรือที่คล้ายกัน
RB.

4
@ V.7 ไม่. MD5 ไม่ปลอดภัยอย่างยิ่งสำหรับการแฮชรหัสผ่านและไม่เคยออกแบบมาเพื่อจุดประสงค์นั้น อย่าใช้มันเป็นอันขาด ทุกวันนี้ Argon2 ดีที่สุด ดูowasp.org/index.php/Password_Storage_Cheat_Sheetและ paragonie.com/blog/2016/02/how-safely-store-password-in-2016
Kimball Robinson

3
วิธีนี้ดีกว่ามาก แน่นอนว่าเกลือสุ่มที่ปลอดภัยและการนับซ้ำของ (อนุรักษ์นิยมต่ำสุด) ที่ 40K จะดีกว่า แต่อย่างน้อยคุณได้ระบุสิ่งเหล่านี้ในความคิดเห็นและ PBKDF2 และ AES / CBC เป็นการปรับปรุงที่ชัดเจน ฉันคิดว่ามันยอดเยี่ยมมากที่คุณจัดการเรื่องนี้โดยการอัปเดตคำตอบ ฉันจะลบคำเตือน โหวตความคิดเห็นของคุณเพื่อให้ทุกคนไม่แปลกใจที่พบรหัสที่อัปเดต (พวกเขาสามารถดูการแก้ไขเพื่อค้นหารหัสเก่าที่ฉันคิด) อาจเป็นความคิดที่ดีในการล้างความคิดเห็นเก่าของคุณด้วย
Maarten Bodewes

20

ใช่อย่าเขียนอัลกอริทึมของคุณเองอย่างแน่นอน Java มี API การเข้ารหัสจำนวนมาก

หากระบบปฏิบัติการที่คุณติดตั้งมีที่เก็บคีย์คุณสามารถใช้เพื่อจัดเก็บคีย์การเข้ารหัสลับของคุณที่คุณจะต้องเข้ารหัสและถอดรหัสข้อมูลที่ละเอียดอ่อนในการกำหนดค่าหรือไฟล์อื่น ๆ ของคุณ


4
+1 สำหรับการใช้ KeyStore! ไม่มีอะไรมากไปกว่าความสับสนหากคุณเก็บคีย์ไว้ในไฟล์ Jar
ninesided

2
หากสิ่งที่จำเป็นทั้งหมดไม่ใช่การจัดเก็บรหัสผ่านเป็นข้อความที่ชัดเจนการจัดเก็บคีย์จะมากเกินไป
Thorbjørn Ravn Andersen

20

ลองดูjasyptซึ่งเป็นห้องสมุดที่มีความสามารถในการเข้ารหัสขั้นพื้นฐานโดยใช้ความพยายามขั้นต่ำ


16

ฉันคิดว่าแนวทางที่ดีที่สุดคือตรวจสอบให้แน่ใจว่าไฟล์กำหนดค่าของคุณ (ที่มีรหัสผ่าน) สามารถเข้าถึงได้เฉพาะบัญชีผู้ใช้ที่ระบุ ตัวอย่างเช่นคุณอาจมีผู้ใช้เฉพาะแอปพลิเคชันappuserซึ่งมีเพียงผู้ที่เชื่อถือได้เท่านั้นที่มีรหัสผ่าน (และผู้ที่suต้องการใช้)

ด้วยวิธีนี้จะไม่มีค่าใช้จ่ายในการเข้ารหัสที่น่ารำคาญและคุณยังมีรหัสผ่านที่ปลอดภัย

แก้ไข: ฉันสมมติว่าคุณไม่ได้ส่งออกการกำหนดค่าแอปพลิเคชันของคุณนอกสภาพแวดล้อมที่เชื่อถือได้ (ซึ่งฉันไม่แน่ใจว่าจะสมเหตุสมผลหากได้รับคำถาม)


4

เพื่อแก้ปัญหารหัสผ่านหลัก - แนวทางที่ดีที่สุดคือไม่เก็บรหัสผ่านไว้ที่ใดก็ตามแอปพลิเคชันควรเข้ารหัสรหัสผ่านด้วยตัวเองเพื่อให้มีเพียงรหัสผ่านเท่านั้นที่สามารถถอดรหัสได้ ดังนั้นถ้าฉันใช้ไฟล์. config ฉันจะทำสิ่งต่อไปนี้mySettings.config :

encryptTheseKeys = secretKey, anotherSecret

secretKey = unprotectedPasswordThatIputHere

anotherSecret = anotherPass

someKey = unprotectedSettingIdontCareAbout

ดังนั้นฉันจะอ่านในคีย์ที่กล่าวถึงใน encryptTheseKeys ใช้ตัวอย่าง Brodwalls จากด้านบนและเขียนกลับไปยังไฟล์ด้วยเครื่องหมายบางประเภท (สมมติว่าcrypt :)เพื่อให้แอปพลิเคชันรู้ว่าอย่าทำ อีกครั้งผลลัพธ์จะมีลักษณะดังนี้:

encryptTheseKeys = secretKey, anotherSecret

secretKey = crypt: ii4jfj304fjhfj934fouh938

anotherSecret = crypt: jd48jofh48h

someKey = unprotectedSettingIdontCareAbout

อย่าลืมเก็บต้นฉบับไว้ในที่ปลอดภัยของคุณเอง ...


2
ใช่นี่คือเมื่อ 3 ปีที่แล้ว เพื่อหลีกเลี่ยงมาสเตอร์คีย์ฉันลงเอยด้วยการใช้คีย์ RSA ที่ออกจาก CA ภายในของเรา การเข้าถึงคีย์ส่วนตัวได้รับการป้องกันโดยการเข้ารหัสด้วยการพิมพ์ลายนิ้วมือของฮาร์ดแวร์เครื่อง
Petey B

ฉันเห็นว่าฟังดูค่อนข้างมั่นคง ดี
user1007231

@ user1007231 - เก็บไว้ที่ไหน - "อย่าลืมเก็บต้นฉบับไว้ในที่ปลอดภัยของคุณเอง ... "?
nanosoft

@PeteyB - ไม่เข้าใจเหรอ? คุณช่วยชี้ลิงก์ที่สามารถให้ความกระจ่างแก่ฉันได้ไหม ขอบคุณ
nanosoft

@nanosoft - รับ "Aegis Secure Key USB" และจัดเก็บในเอกสารข้อความที่นั่นหรือบนกระดาษในกระเป๋าสตางค์ของคุณ
user1007231

4

ประเด็นใหญ่และช้างในห้องและทั้งหมดนั้นก็คือหากแอปพลิเคชันของคุณสามารถเก็บรหัสผ่านได้แฮ็กเกอร์ที่สามารถเข้าถึงกล่องได้ก็สามารถจับมันได้เช่นกัน!

วิธีเดียวในการแก้ไขปัญหานี้คือแอปพลิเคชันขอ "รหัสผ่านหลัก" บนคอนโซลโดยใช้อินพุตมาตรฐานจากนั้นใช้รหัสผ่านนี้เพื่อถอดรหัสรหัสผ่านที่เก็บไว้ในไฟล์ แน่นอนว่าสิ่งนี้ทำให้สมบูรณ์เป็นไปไม่ได้ที่จะให้แอปพลิเคชันเริ่มต้นโดยไม่ต้องดูแลพร้อมกับระบบปฏิบัติการเมื่อเริ่มระบบ

อย่างไรก็ตามแม้จะมีความน่ารำคาญระดับนี้หากแฮ็กเกอร์สามารถเข้าถึงรูทได้ (หรือแม้แต่เข้าถึงในขณะที่ผู้ใช้เรียกใช้แอปพลิเคชันของคุณ) เขาก็สามารถทิ้งหน่วยความจำและค้นหารหัสผ่านที่นั่นได้

สิ่งที่ต้องแน่ใจคืออย่าปล่อยให้ทั้ง บริษัท เข้าถึงเซิร์ฟเวอร์การผลิต (และด้วยรหัสผ่าน) และตรวจสอบให้แน่ใจว่ามันเป็นไปไม่ได้ที่จะแตกกล่องนี้!


วิธีแก้ปัญหาที่แท้จริงคือการจัดเก็บคีย์ส่วนตัวของคุณไว้ที่อื่นเช่นการ์ดหรือ HSM: en.wikipedia.org/wiki/Hardware_security_module
atom88

1

ลองใช้วิธีการเข้ารหัส ESAPIs ง่ายต่อการกำหนดค่าและคุณยังสามารถเปลี่ยนคีย์ได้อย่างง่ายดาย

http://owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/org/owasp/esapi/Encryptor.html

คุณ

1) เข้ารหัส 2) ถอดรหัส 3) ลงชื่อ 4) ยกเลิกการลงชื่อ 5) การแฮช 6) ลายเซ็นตามเวลาและอื่น ๆ อีกมากมายด้วยไลบรารีเดียว


1

ดูว่ามีอะไรบ้างใน Jetty สำหรับจัดเก็บรหัสผ่าน (หรือแฮช) ในไฟล์การกำหนดค่าและพิจารณาว่าการเข้ารหัส OBF อาจมีประโยชน์สำหรับคุณหรือไม่ จากนั้นดูในแหล่งที่มาว่าทำอย่างไร

http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html


0

ขึ้นอยู่กับความปลอดภัยที่คุณต้องการไฟล์การกำหนดค่าหรือความน่าเชื่อถือของแอปพลิเคชันของคุณhttp://activemq.apache.org/encrypted-passwords.htmlอาจเป็นทางออกที่ดีสำหรับคุณ

หากคุณไม่กลัวว่ารหัสผ่านจะถูกถอดรหัสมากเกินไปและการกำหนดค่าโดยใช้ bean เพื่อจัดเก็บคีย์รหัสผ่านนั้นทำได้ง่ายมาก อย่างไรก็ตามหากคุณต้องการความปลอดภัยมากขึ้นคุณสามารถตั้งค่าตัวแปรสภาพแวดล้อมด้วยข้อมูลลับและลบออกหลังจากเปิดตัว ด้วยเหตุนี้คุณจึงต้องกังวลว่าแอปพลิเคชัน / เซิร์ฟเวอร์จะหยุดทำงานและแอปพลิเคชันจะไม่เปิดใหม่โดยอัตโนมัติ


การใช้ HSM เป็นวิธีที่ดีที่สุด: en.wikipedia.org/wiki/Hardware_security_module
atom88

-8

หากคุณใช้java 8การใช้ตัวเข้ารหัสและตัวถอดรหัส Base64 ภายในสามารถหลีกเลี่ยงได้โดยการเปลี่ยน

return new BASE64Encoder().encode(bytes);

กับ

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

และ

return new BASE64Decoder().decodeBuffer(property);

กับ

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

โปรดทราบว่าโซลูชันนี้ไม่ได้ปกป้องข้อมูลของคุณเนื่องจากวิธีการถอดรหัสถูกเก็บไว้ในที่เดียวกัน มันทำให้ยากขึ้นที่จะทำลาย โดยทั่วไปแล้วจะหลีกเลี่ยงการพิมพ์และแสดงให้ทุกคนโดยไม่ได้ตั้งใจ


26
Base64 ไม่ใช่การเข้ารหัส
jwilleke

1
Base64 ไม่ใช่การเชื่อมต่อและเป็นตัวอย่างที่แย่ที่สุดที่คุณสามารถให้ได้ ... หลายคนเชื่อว่า base64 เป็นการเข้ารหัสแบบ algortihm ดังนั้นดีกว่าที่จะไม่สับสน ...
robob

สังเกตว่าเมธอด decode () ที่ด้านบนของไฟล์ต้นทางทำการเข้ารหัสจริง อย่างไรก็ตามการเข้ารหัสและถอดรหัส base64 นี้จำเป็นในการแปลงจากสตริงเป็นไบต์เพื่อที่จะส่งผ่านฟังก์ชันนี้ไปใช้งานได้ (a byte array byte [])
atom88
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.