How to encrypt the database password of springboot project

Time:2021-8-1

preface

In our daily development, we may be free to expose the database password directly in clear text in the configuration file. This can be done in the development environment, but it is quite not recommended in the production environment. After all, security is no trivial matter. No one knows when the password will be revealed inexplicably. Today, let’s talk about how to encrypt the database password in the spring boot project

text

Scheme 1: use Druid database connection pool to encrypt database password

1. Pom.xml introduces Druid package

To facilitate other operations, Druid’s starter is directly introduced here

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

2. Use com.alibaba.druid.filter.config.configtools to generate public and private keys

ps:There are two ways to generate, one is to use the command line, and the other is to write a tool class directly. The examples in this article are generated directly from tool classes

Tool class codes are as follows

/**
 *Alibaba Druid encryption and decryption rules:
 *Plaintext password + private key encryption = encryption password
 *Encryption password + public key decryption = plaintext password
 */
public final class DruidEncryptorUtils {

    private static String privateKey;

    private static String publicKey;

    static {
        try {
            String[] keyPair = ConfigTools.genKeyPair(512);
            privateKey = keyPair[0];
            System.out.println(String.format("privateKey-->%s",privateKey));
            publicKey = keyPair[1];
            System.out.println(String.format("publicKey-->%s",publicKey));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        }
    }

    /**
     *Plaintext encryption
     * @param plaintext
     * @return
     */
    @SneakyThrows
    public static String encode(String plaintext){
        System. Out. Println ("plaintext string:" + plaintext);
        String ciphertext = ConfigTools.encrypt(privateKey,plaintext);
        System. Out. Println ("encrypted string:" + ciphertext);
        return ciphertext;
    }

    /**
     *Decryption
     * @param ciphertext
     * @return
     */
    @SneakyThrows
    public static String decode(String ciphertext){
        System. Out. Println ("encrypted string:" + ciphertext);
        String plaintext = ConfigTools.decrypt(publicKey,ciphertext);
        System. Out. Println ("decrypted string:" + plaintext);

        return plaintext;
    }

3. Modify the configuration file content information of the database

a 、Change Password

Replace the password with the password generated by the tool class druidencryptoutils

 password: ${DATASOURCE_PWD:HB5FmUeAI1U81YJrT/T6awImFg1/Az5o8imy765WkVJouOubC2H80jqmZrr8L9zWKuzS/8aGzuQ4YySAkhywnA==}

b、Filter enable config

 filter:
                config:
                    enabled: true

c. Configure connectionproperties property

 connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.publickey}

ps:Spring.datasource.publickey is the public key generated for the tool class

Appendix:Full database configuration

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: ${DATASOURCE_URL:jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai}
        username: ${DATASOURCE_USERNAME:root}
        password: ${DATASOURCE_PWD:HB5FmUeAI1U81YJrT/T6awImFg1/Az5o8imy765WkVJouOubC2H80jqmZrr8L9zWKuzS/8aGzuQ4YySAkhywnA==}
        publickey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIvP9xF4RCM4oFiu47NZY15iqNOAB9K2Ml9fiTLa05CWaXK7uFwBImR7xltZM1frl6ahWAXJB6a/FSjtJkTZUJECAwEAAQ==
        druid:
            #Number of initial connections
            initialSize: 5
            #Minimum number of connection pools
            minIdle: 10
            #Maximum number of connection pools
            maxActive: 20
            #Configure the time to get the connection wait timeout
            maxWait: 60000
            #Configure how often to detect idle connections that need to be closed. The unit is milliseconds
            timeBetweenEvictionRunsMillis: 60000
            #Configure the minimum lifetime of a connection in the pool, in milliseconds
            minEvictableIdleTimeMillis: 300000
            #Configure the maximum lifetime of a connection in the pool, in milliseconds
            maxEvictableIdleTimeMillis: 900000
            #The configuration detects whether the connection is valid
            validationQuery: SELECT 1 FROM DUAL
            testWhileIdle: true
            testOnBorrow: false
            testOnReturn: false
            webStatFilter:
                enabled: true
            statViewServlet:
                enabled: true
                #Set the white list. If it is not filled in, all accesses are allowed
                allow:
                url-pattern: /druid/*
                #Console admin user name and password
                login-username:
                login-password:
            filter:
                stat:
                    enabled: true
                    #Slow SQL record
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true
                config:
                    enabled: true
            connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.publickey}

Scheme 2: use jasypt to encrypt the database password

1. Pom.xml introduces jasypt package

<dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>${jasypt.verison}</version>
        </dependency>

2. The plaintext password is encrypted by using the tool class provided by jasypt

Encryption tools are as follows

public final class JasyptEncryptorUtils {


    private static final String salt = "lybgeek";

    private static BasicTextEncryptor basicTextEncryptor = new BasicTextEncryptor();

    static {
        basicTextEncryptor.setPassword(salt);
    }

    private JasyptEncryptorUtils(){}

    /**
     *Plaintext encryption
     * @param plaintext
     * @return
     */
    public static String encode(String plaintext){
        System. Out. Println ("plaintext string:" + plaintext);
        String ciphertext = basicTextEncryptor.encrypt(plaintext);
        System. Out. Println ("encrypted string:" + ciphertext);
        return ciphertext;
    }

    /**
     *Decryption
     * @param ciphertext
     * @return
     */
    public static String decode(String ciphertext){
        System. Out. Println ("encrypted string:" + ciphertext);
        ciphertext = "ENC(" + ciphertext + ")";
        if (PropertyValueEncryptionUtils.isEncryptedValue(ciphertext)){
            String plaintext = PropertyValueEncryptionUtils.decrypt(ciphertext,basicTextEncryptor);
            System. Out. Println ("decrypted string:" + plaintext);
            return plaintext;
        }
        System.out.println ("decryption failed");
        return "";
    }
}

3. Modify the configuration file content information of the database

a、Enc wraps the encrypted string generated with jasyptencryptorutils

password: ${DATASOURCE_PWD:ENC(P8m43qmzqN4c07DCTPey4Q==)}

b、Configure the key and specify the encryption and decryption algorithm

jasypt:
    encryptor:
        password: lybgeek
        algorithm: PBEWithMD5AndDES
        iv-generator-classname: org.jasypt.iv.NoIvGenerator

Because my tool class uses encryption and decryption, the tool class is basictextencryptor, and its corresponding configuration encryption and decryption is pbewithmd5anddes and org.jasypt.iv.noivgenerator

ps:In the production environment, it is recommended to configure the key in the following ways to avoid key disclosure

java -jar -Djasypt.encryptor.password=lybgeek

Appendix:Full database configuration

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: ${DATASOURCE_URL:ENC(kT/gwazwzaFNEp7OCbsgCQN7PHRohaTKJNdGVgLsW2cH67zqBVEq7mN0BTIXAeF4/Fvv4l7myLFx0y6ap4umod7C2VWgyRU5UQtKmdwzQN3hxVxktIkrFPn9DM6+YahM0xP+ppO9HaWqA2ral0ejBCvmor3WScJNHCAhI9kHjYc=)}
        username: ${DATASOURCE_USERNAME:ENC(rEQLlqM5nphqnsuPj3MlJw==)}
        password: ${DATASOURCE_PWD:ENC(P8m43qmzqN4c07DCTPey4Q==)}
        druid:
            #Number of initial connections
            initialSize: 5
            #Minimum number of connection pools
            minIdle: 10
            #Maximum number of connection pools
            maxActive: 20
            #Configure the time to get the connection wait timeout
            maxWait: 60000
            #Configure how often to detect idle connections that need to be closed. The unit is milliseconds
            timeBetweenEvictionRunsMillis: 60000
            #Configure the minimum lifetime of a connection in the pool, in milliseconds
            minEvictableIdleTimeMillis: 300000
            #Configure the maximum lifetime of a connection in the pool, in milliseconds
            maxEvictableIdleTimeMillis: 900000
            #The configuration detects whether the connection is valid
            validationQuery: SELECT 1 FROM DUAL
            testWhileIdle: true
            testOnBorrow: false
            testOnReturn: false
            webStatFilter:
                enabled: true
            statViewServlet:
                enabled: true
                #Set the white list. If it is not filled in, all accesses are allowed
                allow:
                url-pattern: /druid/*
                #Console admin user name and password
                login-username:
                login-password:
            filter:
                stat:
                    enabled: true
                    #Slow SQL record
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true
jasypt:
    encryptor:
        password: lybgeek
        algorithm: PBEWithMD5AndDES
        iv-generator-classname: org.jasypt.iv.NoIvGenerator

Scheme 3: user defined implementation

Implementation principle:Modifying datasource with spring post processor

1. Custom encryption and decryption tool class

/**
 *Using the encryption and decryption tool encapsulated by hutool, taking AES symmetric encryption algorithm as an example
 */
public final class EncryptorUtils {

    private static String secretKey;



    static {
        secretKey = Hex.encodeHexString(SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded());
        System.out.println("secretKey-->" + secretKey);
        System.out.println("--------------------------------------------------------------------------------------");
    }

    /**
     *Plaintext encryption
     * @param plaintext
     * @return
     */
    @SneakyThrows
    public static String encode(String plaintext){
        System. Out. Println ("plaintext string:" + plaintext);
        byte[] key = Hex.decodeHex(secretKey.toCharArray());
        String ciphertext =  SecureUtil.aes(key).encryptHex(plaintext);
        System. Out. Println ("encrypted string:" + ciphertext);

        return ciphertext;
    }

    /**
     *Decryption
     * @param ciphertext
     * @return
     */
    @SneakyThrows
    public static String decode(String ciphertext){
        System. Out. Println ("encrypted string:" + ciphertext);
        byte[] key = Hex.decodeHex(secretKey.toCharArray());
        String plaintext = SecureUtil.aes(key).decryptStr(ciphertext);
        System. Out. Println ("decrypted string:" + plaintext);

        return plaintext;
    }

    /**
     *Plaintext encryption
     * @param plaintext
     * @return
     */
    @SneakyThrows
    public static String encode(String secretKey,String plaintext){
        System. Out. Println ("plaintext string:" + plaintext);
        byte[] key = Hex.decodeHex(secretKey.toCharArray());
        String ciphertext =  SecureUtil.aes(key).encryptHex(plaintext);
        System. Out. Println ("encrypted string:" + ciphertext);

        return ciphertext;
    }

    /**
     *Decryption
     * @param ciphertext
     * @return
     */
    @SneakyThrows
    public static String decode(String secretKey,String ciphertext){
        System. Out. Println ("encrypted string:" + ciphertext);
        byte[] key = Hex.decodeHex(secretKey.toCharArray());
        String plaintext = SecureUtil.aes(key).decryptStr(ciphertext);
        System. Out. Println ("decrypted string:" + plaintext);

        return plaintext;
    }

}

2. Write post processor

public class DruidDataSourceEncyptBeanPostProcessor implements BeanPostProcessor {

    private CustomEncryptProperties customEncryptProperties;

    private DataSourceProperties dataSourceProperties;

    public DruidDataSourceEncyptBeanPostProcessor(CustomEncryptProperties customEncryptProperties, DataSourceProperties dataSourceProperties) {
        this.customEncryptProperties = customEncryptProperties;
        this.dataSourceProperties = dataSourceProperties;
    }



    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof DruidDataSource){
            if(customEncryptProperties.isEnabled()){
                DruidDataSource druidDataSource = (DruidDataSource)bean;
                System.out.println("--------------------------------------------------------------------------------------");
                String username = dataSourceProperties.getUsername();
                druidDataSource.setUsername(EncryptorUtils.decode(customEncryptProperties.getSecretKey(),username));
                System.out.println("--------------------------------------------------------------------------------------");
                String password = dataSourceProperties.getPassword();
                druidDataSource.setPassword(EncryptorUtils.decode(customEncryptProperties.getSecretKey(),password));
                System.out.println("--------------------------------------------------------------------------------------");
                String url = dataSourceProperties.getUrl();
                druidDataSource.setUrl(EncryptorUtils.decode(customEncryptProperties.getSecretKey(),url));
                System.out.println("--------------------------------------------------------------------------------------");
            }

        }
        return bean;
    }
}

3. Modify the configuration file content information of the database

a 、Change Password

Replace the password with the encrypted password generated by the custom encryption tool class

  password: ${DATASOURCE_PWD:fb31cdd78a5fa2c43f530b849f1135e7}

b 、Specify the key and turn on encryption

custom:
    encrypt:
        enabled: true
        secret-key: 2f8ba810011e0973728afa3f28a0ecb6

ps:Similarly, the secret key should not be directly exposed in the configuration file. It can be specified with – dcustom.encrypt.secret-key

Appendix:Full database configuration

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: ${DATASOURCE_URL:dcb268cf3a2626381d2bc5c96f94fb3d7f99352e0e392362cb818a321b0ca61f3a8dad3aeb084242b745c61a1d3dc244ed1484bf745c858c44560dde10e60e90ac65f77ce2926676df7af6b35aefd2bb984ff9a868f1f9052ee9cae5572fa015b66a602f32df39fb1bbc36e04cc0f148e4d610a3e5d54f2eb7c57e4729c9d7b4}
        username: ${DATASOURCE_USERNAME:61db3bf3c6d3fe3ce87549c1af1e9061}
        password: ${DATASOURCE_PWD:fb31cdd78a5fa2c43f530b849f1135e7}
        druid:
            #Number of initial connections
            initialSize: 5
            #Minimum number of connection pools
            minIdle: 10
            #Maximum number of connection pools
            maxActive: 20
            #Configure the time to get the connection wait timeout
            maxWait: 60000
            #Configure how often to detect idle connections that need to be closed. The unit is milliseconds
            timeBetweenEvictionRunsMillis: 60000
            #Configure the minimum lifetime of a connection in the pool, in milliseconds
            minEvictableIdleTimeMillis: 300000
            #Configure the maximum lifetime of a connection in the pool, in milliseconds
            maxEvictableIdleTimeMillis: 900000
            #The configuration detects whether the connection is valid
            validationQuery: SELECT 1 FROM DUAL
            testWhileIdle: true
            testOnBorrow: false
            testOnReturn: false
            webStatFilter:
                enabled: true
            statViewServlet:
                enabled: true
                #Set the white list. If it is not filled in, all accesses are allowed
                allow:
                url-pattern: /druid/*
                #Console admin user name and password
                login-username:
                login-password:
            filter:
                stat:
                    enabled: true
                    #Slow SQL record
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true
custom:
    encrypt:
        enabled: true
        secret-key: 2f8ba810011e0973728afa3f28a0ecb6

summary

For the above three schemes, I recommend jasypt, because it can encrypt not only the password, but also other contents. Druid can only encrypt the database password. As for the customized scheme, it belongs to practice. After all, there are already things in open source, so don’t make your own wheels.

Finally, another note is that if jasypt is higher than version 2 and lower than 3.0.3, the dynamic refresh configuration of the configuration center, such as Apollo or Nacos, will become invalid (the latest version of 3.0.3 officially fixes this problem).
How to encrypt the database password of springboot project

If the configuration center is used, jasypt recommends using version 3 or less or version 3.0.3

Demo link

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-datasouce-encrypt