在源代码中处理用于身份验证的密码

新手上路,请多包涵

假设我试图从使用基本身份验证/基本证书的 RESTful api 中提取,那么在我的程序中存储该用户名和密码的最佳方法是什么?现在它只是以明文形式存放在那里。

 UsernamePasswordCredentials creds = new UsernamePasswordCredentials("myName@myserver","myPassword1234");

有没有一些更安全的方法来做到这一点?

谢谢

原文由 A_Elric 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 323
2 个回答

重要的提示:

如果您将身份验证系统作为一个整体来设计,则不应存储密码,即使它们是加密的。您存储哈希值,并检查登录期间提供的密码是否与相同的哈希值匹配。这样,数据库的安全漏洞就可以避免暴露用户的密码。

话虽如此,对于您要按原样存储数据(在本例中为密码)的情况,然后采用由内而外的思维方式,以下是保护您的流程的一些步骤:


第一步,您应该将密码处理从 String 更改为 character array

原因是 String 是一个 immutable 对象,因此即使对象设置为 null ,它的数据也不会立即被清理;数据被设置为垃圾收集,这会带来安全问题,因为恶意程序可能会在清理数据之前访问 String (密码)数据。

这就是为什么 Swing 的 JPasswordField 的 getText() 方法被弃用,以及为什么 getPassword() 使用字符数组 的主要原因。


第二步是加密您的凭据,仅在身份验证过程中临时解密它们。或者在服务器端对它们进行哈希处理,存储该哈希值,然后“忘记”原始密码。

这与第一步类似,可确保您的漏洞时间尽可能短。

建议不要对您的凭据进行硬编码,而是以集中、可配置且易于维护的方式存储它们,例如配置或属性文件或数据库。

您应该在保存文件之前加密您的凭据,此外,您可以对文件本身应用二次加密(对凭据进行 2 层加密,对其他文件内容进行 1 层加密)。

请注意,上面提到的两个加密过程中的每一个本身都可以是多层的。作为概念示例,每个加密都可以是 三重数据加密标准(AKA TDES 和 3DES) 的单独应用。


在您的本地环境得到适当保护后(但请记住,它永远不会“安全”!),第三步是通过使用 TLS(传输层安全性)或 SSL(安全套接字层) 对您的传输过程应用基本保护。


第四步是应用其他保护方法。

例如,将混淆技术应用于您的“使用”编译,以避免(即使很快)您的安全措施暴露,以防万一您的程序被 Eve 女士、Mallory 先生或其他人(坏-伙计们) 并反编译。


更新 1:

应@Damien.Bell 的要求,这里有一个涵盖第一步和第二步的示例:

     //These will be used as the source of the configuration file's stored attributes.
    private static final Map<String, String> COMMON_ATTRIBUTES = new HashMap<String, String>();
    private static final Map<String, char[]> SECURE_ATTRIBUTES = new HashMap<String, char[]>();
    //Ciphering (encryption and decryption) password/key.
    private static final char[] PASSWORD = "Unauthorized_Personel_Is_Unauthorized".toCharArray();
    //Cipher salt.
    private static final byte[] SALT = {
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,};
    //Desktop dir:
    private static final File DESKTOP = new File(System.getProperty("user.home") + "/Desktop");
    //File names:
    private static final String NO_ENCRYPTION = "no_layers.txt";
    private static final String SINGLE_LAYER = "single_layer.txt";
    private static final String DOUBLE_LAYER = "double_layer.txt";

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws GeneralSecurityException, FileNotFoundException, IOException {
        //Set common attributes.
        COMMON_ATTRIBUTES.put("Gender", "Male");
        COMMON_ATTRIBUTES.put("Age", "21");
        COMMON_ATTRIBUTES.put("Name", "Hypot Hetical");
        COMMON_ATTRIBUTES.put("Nickname", "HH");

        /*
         * Set secure attributes.
         * NOTE: Ignore the use of Strings here, it's being used for convenience only.
         * In real implementations, JPasswordField.getPassword() would send the arrays directly.
         */
        SECURE_ATTRIBUTES.put("Username", "Hypothetical".toCharArray());
        SECURE_ATTRIBUTES.put("Password", "LetMePass_Word".toCharArray());

        /*
         * For demosntration purposes, I make the three encryption layer-levels I mention.
         * To leave no doubt the code works, I use real file IO.
         */
        //File without encryption.
        create_EncryptedFile(NO_ENCRYPTION, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 0);
        //File with encryption to secure attributes only.
        create_EncryptedFile(SINGLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 1);
        //File completely encrypted, including re-encryption of secure attributes.
        create_EncryptedFile(DOUBLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 2);

        /*
         * Show contents of all three encryption levels, from file.
         */
        System.out.println("NO ENCRYPTION: \n" + readFile_NoDecryption(NO_ENCRYPTION) + "\n\n\n");
        System.out.println("SINGLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(SINGLE_LAYER) + "\n\n\n");
        System.out.println("DOUBLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(DOUBLE_LAYER) + "\n\n\n");

        /*
         * Decryption is demonstrated with the Double-Layer encryption file.
         */
        //Descrypt first layer. (file content) (REMEMBER: Layers are in reverse order from writing).
        String decryptedContent = readFile_ApplyDecryption(DOUBLE_LAYER);
        System.out.println("READ: [first layer decrypted]\n" + decryptedContent + "\n\n\n");
        //Decrypt second layer (secure data).
        for (String line : decryptedContent.split("\n")) {
            String[] pair = line.split(": ", 2);
            if (pair[0].equalsIgnoreCase("Username") || pair[0].equalsIgnoreCase("Password")) {
                System.out.println("Decrypted: " + pair[0] + ": " + decrypt(pair[1]));
            }
        }
    }

    private static String encrypt(byte[] property) throws GeneralSecurityException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));

        //Encrypt and save to temporary storage.
        String encrypted = Base64.encodeBytes(pbeCipher.doFinal(property));

        //Cleanup data-sources - Leave no traces behind.
        for (int i = 0; i < property.length; i++) {
            property[i] = 0;
        }
        property = null;
        System.gc();

        //Return encryption result.
        return encrypted;
    }

    private static String encrypt(char[] property) throws GeneralSecurityException {
        //Prepare and encrypt.
        byte[] bytes = new byte[property.length];
        for (int i = 0; i < property.length; i++) {
            bytes[i] = (byte) property[i];
        }
        String encrypted = encrypt(bytes);

        /*
         * Cleanup property here. (child data-source 'bytes' is cleaned inside 'encrypt(byte[])').
         * It's not being done because the sources are being used multiple times for the different layer samples.
         */
//      for (int i = 0; i < property.length; i++) { //cleanup allocated data.
//          property[i] = 0;
//      }
//      property = null; //de-allocate data (set for GC).
//      System.gc(); //Attempt triggering garbage-collection.

        return encrypted;
    }

    private static String encrypt(String property) throws GeneralSecurityException {
        String encrypted = encrypt(property.getBytes());
        /*
         * Strings can't really have their allocated data cleaned before CG,
         * that's why secure data should be handled with char[] or byte[].
         * Still, don't forget to set for GC, even for data of sesser importancy;
         * You are making everything safer still, and freeing up memory as bonus.
         */
        property = null;
        return encrypted;
    }

    private static String decrypt(String property) throws GeneralSecurityException, IOException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
        return new String(pbeCipher.doFinal(Base64.decode(property)));
    }

    private static void create_EncryptedFile(
                    String fileName,
                    Map<String, String> commonAttributes,
                    Map<String, char[]> secureAttributes,
                    int layers)
                    throws GeneralSecurityException, FileNotFoundException, IOException {
        StringBuilder sb = new StringBuilder();
        for (String k : commonAttributes.keySet()) {
            sb.append(k).append(": ").append(commonAttributes.get(k)).append(System.lineSeparator());
        }
        //First encryption layer. Encrypts secure attribute values only.
        for (String k : secureAttributes.keySet()) {
            String encryptedValue;
            if (layers >= 1) {
                encryptedValue = encrypt(secureAttributes.get(k));
            } else {
                encryptedValue = new String(secureAttributes.get(k));
            }
            sb.append(k).append(": ").append(encryptedValue).append(System.lineSeparator());
        }

        //Prepare file and file-writing process.
        File f = new File(DESKTOP, fileName);
        if (!f.getParentFile().exists()) {
            f.getParentFile().mkdirs();
        } else if (f.exists()) {
            f.delete();
        }
        BufferedWriter bw = new BufferedWriter(new FileWriter(f));
        //Second encryption layer. Encrypts whole file content including previously encrypted stuff.
        if (layers >= 2) {
            bw.append(encrypt(sb.toString().trim()));
        } else {
            bw.append(sb.toString().trim());
        }
        bw.flush();
        bw.close();
    }

    private static String readFile_NoDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException {
        File f = new File(DESKTOP, fileName);
        BufferedReader br = new BufferedReader(new FileReader(f));
        StringBuilder sb = new StringBuilder();
        while (br.ready()) {
            sb.append(br.readLine()).append(System.lineSeparator());
        }
        return sb.toString();
    }

    private static String readFile_ApplyDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException {
        File f = new File(DESKTOP, fileName);
        BufferedReader br = new BufferedReader(new FileReader(f));
        StringBuilder sb = new StringBuilder();
        while (br.ready()) {
            sb.append(br.readLine()).append(System.lineSeparator());
        }
        return decrypt(sb.toString());
    }

一个完整的例子,解决每一个保护步骤,将远远超出我认为这个问题的合理范围,因为它是关于 “步骤是什么” ,而不是 “如何应用它们”

它会大大超过我的答案(最后是抽样),而这里关于 SO 的其他问题已经针对这些步骤的 “如何做” ,更合适,并提供更好的解释和抽样实施每一步。

原文由 CosmicGiant 发布,翻译遵循 CC BY-SA 4.0 许可协议

如果您使用的是基本身份验证,则应将其与 SSL 结合使用,以避免以 base64 编码的纯文本形式传递您的凭据。您不想让嗅探您的数据包的人轻松获取您的凭据。另外,不要在源代码中硬编码您的凭据。使它们可配置。从配置文件中读取它们。您应该在将凭据存储在配置文件中之前对其进行加密,并且您的应用程序应该在从配置文件中读取凭据后对其进行解密。

原文由 Athens Holloway 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题