142

I have a program that reads server information from a configuration file and would like to encrypt the password in that configuration that can be read by my program and decrypted.

Requirements:

  • Encrypt plaintext password to be stored in the file
  • Decrypt the encrypted password read in from the file from my program

How would I go about doing this? I was thinking of writing my own algorithm, but I feel it would be terribly insecure.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Petey B
  • 11,439
  • 25
  • 81
  • 101
  • 12
    I love how so many of stackoverflow's best Q&A's are ones it deems not meeting its guidelines (e.g, questions asking for recommendations). lol.... – Vahid Pazirandeh Oct 30 '20 at 23:49

10 Answers10

188

A simple way of doing this is to use Password Based Encryption in Java. This allows you to encrypt and decrypt a text by using a password.

This basically means initializing a javax.crypto.Cipher with algorithm "AES/CBC/PKCS5Padding" and getting a key from javax.crypto.SecretKeyFactory with the "PBKDF2WithHmacSHA512" algorithm.

Here is a code example (updated to replace the less secure MD5-based variant):

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

One problem remains: Where should you store the password that you use to encrypt the passwords? You can store it in the source file and obfuscate it, but it's not too hard to find it again. Alternatively, you can give it as a system property when you start the Java process (-DpropertyProtectionPassword=...).

The same issue remains if you use the KeyStore, which also is protected by a password. Basically, you will need to have one master password somewhere, and it's pretty hard to protect.

Johannes Brodwall
  • 7,673
  • 5
  • 34
  • 28
  • 3
    Thanks for the code example, it's pretty much how i ended up doing it. In regards to the password that protects the passwords i ran into that same problem, i went witht the obfuscate it method for now but havnt come up with an acceptable solution yet, thanks for your suggestions. – Petey B Jul 15 '09 at 20:39
  • Theoretically, you could store that password as a system property, and read it at runtime. Assuming you only have access to that account / server, that would be the ultimate way to do it. Not to mention you could have different files, for different environments, and different property passwords for each. – Spencer Kormos Dec 20 '12 at 20:56
  • 10
    "Alternatively, you can give it as a system property when you start the Java process (-DpropertyProtectionPassword=...)". Note that this would make it possible to extract the password using "ps fax" on (GNU/Linux)/UNIX. – Ztyx Apr 29 '13 at 06:31
  • 2
    Why do you encode to Base64? – Cheetah Jul 23 '13 at 16:14
  • 7
    @Ben It's common practice to encode to Base64 to allow you to store the resultant value in a text-file or string-based database column, or similar. – RB. Oct 01 '13 at 16:09
  • 1
    Does this solution mean that if an attacker has access to your source code, they can decrypt the passwords? – dwjohnston May 27 '15 at 00:52
  • 2
    yes @dwjohnston , access to your code they can decrypt. but you could save the secret key (SecretKey) to a file that lives outside of your source code. – Jeryl Cook Feb 04 '16 at 15:13
  • Use MD5 or other hash algorythm. That will give you the best security. – Artfaith Mar 30 '16 at 08:44
  • Hashing is better if you need to verify a secret. But when you need to store a secret you will need to submit to another system, hashing doesn't help. – Johannes Brodwall Mar 30 '16 at 09:24
  • 5
    @V.7 nope. MD5 is absolutely not secure for password hashing and was never designed for that purpose. Never use it for that. These days, Argon2 is best. See https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet and https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016 – Kimball Robinson Jul 22 '16 at 18:52
  • 2
    @MaartenBodewes Thank you for pointing me in the direection of PBKDF2. I learned stuff both about the algorithm and the theory around slow hashes. Good stuff to learn. I've updated the code - I hope you like the new version better. – Johannes Brodwall Apr 17 '17 at 19:28
  • 3
    Much better this way. Of course a secure random salt and an iteration count of a (conservative, low end) of 40K would be nicer, but at least you've indicated these things in the comments and PBKDF2 and AES/CBC are definite improvements. I think it is great how you handled this, by updating the answer; I'll at remove the warning. Voted up your comment so that people aren't surprised to find the updated code (they can look at the edits to find the old code I suppose). Might be a good idea to clean up your old comments as well. – Maarten Bodewes Apr 17 '17 at 19:32
  • 1
    Thanks for the vote! I'm happy to learn - that's why we're here. :-) I updated the iteration count to 40k and changed the comment to reflect this. I've also deleted an earlier comment. Regarding the salt: Is it correct that the salt can be stored in the prop files with the encrypted properties? I also wonder if you have any insights into why other key-lengths than 128 (and 132, it turns out) throw exception. – Johannes Brodwall Apr 18 '17 at 19:43
  • 1
    @dwjohnston - I've updated the code example to avoid the possible confusion with hardcoding the password. Hope this makes it clearer. – Johannes Brodwall Apr 18 '17 at 19:47
  • This doesn't work anymore. – ifly6 Feb 05 '18 at 20:28
  • @ifly6 What error/problem do you encounter? – Johannes Brodwall Feb 28 '18 at 21:16
  • I’d have to run it and check my computer, but iirc, it has to do with some libraries no longer existing in Java 9’s JDK. – ifly6 Feb 28 '18 at 21:19
  • @ifly6 i tried to run this with Oracle JDK 9.0.4 and get the expected behavior. Please provide more information if I am going to help. :-) – Johannes Brodwall Mar 01 '18 at 19:31
  • 1
    The most secure way to store the secret key would be in an HSM (Hardware Security Module) https://en.wikipedia.org/wiki/Hardware_security_module – atom88 Sep 05 '18 at 20:47
  • 1
    You could also store your secret key on a USB drive with a PIN "Padlock" type of device like this one: CORSAIR Flash Padlock 3 16GB Secure USB 3.0 Flash Drive with Keypad 256bit AES Encryption Model CMFPLA3B-16GB https://www.newegg.com/Product/Product.aspx?Item=9SIAH9B7YN2197&cm_re=corsair_usb_padlock-_-9SIAH9B7YN2197-_-Product – atom88 Sep 05 '18 at 20:54
22

Check out jasypt, which is a library offering basic encryption capabilities with minimum effort.

Kaitsu
  • 4,094
  • 3
  • 30
  • 37
20

Yes, definitely don't write your own algorithm. Java has lots of cryptography APIs.

If the OS you are installing upon has a keystore, then you could use that to store your crypto keys that you will need to encrypt and decrypt the sensitive data in your configuration or other files.

JeeBee
  • 17,476
  • 5
  • 50
  • 60
18

I think that the best approach is to ensure that your configuration file (containing your password) is only accessible to a specific user account. For example, you might have an application-specific user, appuser, to which only trusted people have the password (and to which they su to).

That way, there isn't any annoying cryptography overhead and you still have a password which is secure.

I am assuming that you are not exporting your application configuration outside of a trusted environment (which I'm not sure would make any sense, given the question).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
6

The big point, and the elephant in the room and all that, is that if your application can get hold of the password, then a hacker with access to the box can get hold of it too!

The only way somewhat around this, is that the application asks for the "master password" on the console using Standard Input, and then uses this to decrypt the passwords stored on file. Of course, this completely makes is impossible to have the application start up unattended along with the OS when it boots.

However, even with this level of annoyance, if a hacker manages to get root access (or even just access as the user running your application), he could dump the memory and find the password there.

The thing to ensure, is to not let the entire company have access to the production server (and thereby to the passwords), and make sure that it is impossible to crack this box!

stolsvik
  • 5,253
  • 7
  • 43
  • 52
  • 2
    The real solution is to store your private key somewhere else, like a card or an HSM: https://en.wikipedia.org/wiki/Hardware_security_module – atom88 Dec 05 '17 at 18:21
  • 2
    @atom88 how do you plugin the HSM to your cloud server? – benez Oct 19 '21 at 13:42
  • 1
    There are what they call "Network HSM's" that can be done over a network. Presumably you could somehow connect your physical HSM to a cloud provider. AWS for example can generate keys or you can BYOK (Bring your own keys) with you to use for things like encryption/decryption. It's all about whom you trust (cloud provider), admins at the cloud provider, or your network layer, etc. – atom88 Oct 28 '21 at 23:27
4

Well to solve the problems of master password - the best approach is not to store the password anywhere, the application should encrypt passwords for itself - so that only it can decrypt them. So if I was using a .config file I would do the following, mySettings.config:

encryptTheseKeys=secretKey,anotherSecret

secretKey=unprotectedPasswordThatIputHere

anotherSecret=anotherPass

someKey=unprotectedSettingIdontCareAbout

so I would read in the keys that are mentioned in the encryptTheseKeys, apply the Brodwalls example from above on them and write them back to the file with a marker of some sort (lets say crypt:) to let the application know not to do it again, the output would look like this:

encryptTheseKeys=secretKey,anotherSecret

secretKey=crypt:ii4jfj304fjhfj934fouh938

anotherSecret=crypt:jd48jofh48h

someKey=unprotectedSettingIdontCareAbout

Just make sure to keep the originals in your own secure place...

user1007231
  • 133
  • 7
  • 2
    Yeah, this is from 3 years ago. To avoid the master key, I ended up using RSA keys issued from our internal CA. Access to the private key is protected by being encrypted with a finger print of the machine hardware. – Petey B Jan 17 '12 at 22:31
  • I see, sounds pretty solid. nice. – user1007231 Jan 17 '12 at 23:07
  • @user1007231 - Where to keep - "Just make sure to keep the originals in your own secure place..." ? – nanosoft Mar 24 '15 at 12:07
  • @PeteyB - Didn't understood ? Can you point me to some links that can enlighten me. Thanks – nanosoft Mar 24 '15 at 12:09
  • @nanosoft - Get a "Aegis Secure Key USB" and store in a text doc there or on paper in your wallet – user1007231 Jun 23 '15 at 16:19
  • or store them in an HSM (Hardware Security Module) https://en.wikipedia.org/wiki/Hardware_security_module – atom88 Dec 05 '17 at 18:17
1

See what is available in Jetty for storing password (or hashes) in configuration files, and consider if the OBF encoding might be useful for you. Then see in the source how it is done.

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
  • The link is (effectively) broken. It redirects to a generic page. – Peter Mortensen Jan 26 '23 at 23:14
  • What is *"OBF"*? Do you mean *[OFB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB))*? – Peter Mortensen Jan 26 '23 at 23:15
  • @PeterMortensen If you do not know the Internet Archive, it is a very useful resource. https://web.archive.org/web/20061218182526/http://docs.codehaus.org/display/JETTY/Securing%2BPasswords demonstrates the OBF encoding available back then in Jetty. It appears that this particular page did not survive the codehaus and eclipse migrations, so it may not be possible anymore. – Thorbjørn Ravn Andersen Jan 27 '23 at 17:39
1

Try using ESAPIs encryption methods. It's easy to configure and you can also easily change your keys.

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

You

  1. encrypt
  2. decrypt
  3. sign
  4. unsign
  5. hashing
  6. time based signatures and much more with just one library.
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rohit Salecha
  • 893
  • 11
  • 9
0

Depending on how secure you need the configuration files or how reliable your application is, http://activemq.apache.org/encrypted-passwords.html may be a good solution for you.

If you are not too afraid of the password being decrypted and it can be really simple to configure using a bean to store the password key. However, if you need more security you can set an environment variable with the secret and remove it after launch. With this you have to worry about the application / server going down and not application not automatically relaunching.

CPrescott
  • 285
  • 3
  • 10
-11

If you are using Java 8, the use of the internal Base64 encoder and decoder can be avoided by replacing

return new BASE64Encoder().encode(bytes);

with

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

and

return new BASE64Decoder().decodeBuffer(property);

with

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

Note that this solution doesn't protect your data as the methods for decrypting are stored in the same place. It just makes it more difficult to break. Mainly it avoids to print it and show it to everybody by mistake.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Antonio Raposo
  • 523
  • 6
  • 9
  • 29
    Base64 is not encryption. – jwilleke Jun 21 '15 at 07:49
  • 3
    Base64 is not ancryption and it's the worst example you can provide...a lot of people believe that base64 is an encryption algortihm, so better to not confuse them... – robob May 15 '17 at 13:24
  • 1
    notice that the decode() method in the top of the source file does the actual encryption. However, this base64 encoding and decoding is needed to convert from a string the bytes in order to pass this function something that it can use (a byte array byte[]) – atom88 Dec 05 '17 at 18:08