处理源代码中用于身份验证的密码 [英] Handling passwords used for auth in source code

查看:26
本文介绍了处理源代码中用于身份验证的密码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Assuming that I'm trying to pull from a RESTful api that uses basic authentication / basic certificates, what would be the best way to store that user name and password in my program? Right now it's just sitting there in plaintext.

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

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

Is there some way of doing this that is more security minded?

谢谢

推荐答案


重要提示:

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


Important note:

If you're designing the authentication system as a whole, you shouldn't store passwords, even if they're encrypted. You store a hash, and check if passwords provided during login match the same hash. That way, a security breach on your database avoids getting your users' passwords exposed.

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

With that said, for situations where you are going to store data as-is (in this case passwords), then with an inner-to-outer mindset, here are some steps to protect your process:

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

First step, you should change your password-handling from String to character array.

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

The reason for this is that a String is an immutable object, and so it's data will not be cleansed immediately even if the object is set to null; The data is set for garbage-collection instead, and this poses security problems because malicious programs might gain access to that String (password) data before it is cleaned.

这就是为什么 Swing 的 JPasswordField 的 getText() 方法已弃用,为什么 getPassword() 使用字符数组.

This is the main reason why Swing's JPasswordField's getText() method is deprecated, and why getPassword() uses character arrays.

第二步是加密您的凭据,仅在身份验证过程中临时解密它们.或者在服务器端散列它们,存储散列,然后忘记"原始密码.

The second step is to encrypt your credentials, only decrypting them temporarily during the authentication process. Or to hash them server-side, store that hash, and "forget" the original password.

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

This, similarly to the first step, makes sure your vulnerability-time is as small as possible.

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

It is recommended that your credentials are not hard-coded, and that instead, you store them in a centralized, configurable and easily-maintainable manner, such as a configuration or properties file, or a database.

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

You should encrypt your credentials before saving the file, and additionally, you can apply a second encryption to the file itself (2-layer encryption to the credentials, and 1-layer to other file contents).

请注意,上面提到的两个加密过程中的每一个都可以是多层的.每个加密都可以是 三重数据加密标准(又名 TDES 和 3DES) 的单独应用,如一个概念性的例子.

Note that each of the two encryption processes mentioned above can be multiple-layered themselves. Each encryption can be an individual application of Triple Data Encryption Standard (AKA TDES and 3DES), as a conceptual example.

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

After your local environment is properly protected (but remember, it's never ever "safe"!), the third step is apply basic protection to your transmission process, by using TLS (Transport Layer Security) or SSL (Secure Sockets Layer).

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

The forth step is to apply other protection methods.

例如,将混淆技术应用于您的使用"程序.编译,以避免(即使很快)暴露您的安全措施,以防您的程序被 Ms.夏娃、马洛里先生或其他人(坏人)并被反编译.

For example, applying obfuscation techniques to your "to-use" compile, to avoid (even if shortly) the exposure of your security measures in case your program is obtained by Ms. Eve, Mr. Mallory, or someone else (the bad-guys) and decompiled.

更新 1:

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

By @Damien.Bell 's request, here is an example that covers the first and second steps:

    //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: 
" + readFile_NoDecryption(NO_ENCRYPTION) + "


");
        System.out.println("SINGLE LAYER ENCRYPTION: 
" + readFile_NoDecryption(SINGLE_LAYER) + "


");
        System.out.println("DOUBLE LAYER ENCRYPTION: 
" + readFile_NoDecryption(DOUBLE_LAYER) + "


");

        /*
         * 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]
" + decryptedContent + "


");
        //Decrypt second layer (secure data).
        for (String line : decryptedContent.split("
")) {
            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());
    }

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

A full example, addressing every protection step, would far exceed what I think is reasonable for this question, since it's about "what are the steps", not "how to apply them".

这会大大超出我的答案(最后是抽样),而 S.O 上的其他问题.已经针对这些步骤的如何做",更合适,并且对每个单独步骤的实施提供更好的解释和抽样.

It would far over-size my answer (at last the sampling), while other questions here on S.O. are already directed on the "How to" of those steps, being far more appropriate, and offering far better explanation and sampling on the implementation of each individual step.

这篇关于处理源代码中用于身份验证的密码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆