These days, the company is investigating the internal data account leakage. The reason is that it is found that some intern cute actually uploaded the source code to GitHub privately with the account and password, resulting in the leakage of core data. The child has not been beaten by the society, the consequences of this kind of thing can be big or small. \

Before talking about this is I have a feeling, my experience of TM was delete library, now to think of it in the mind also uncomfortable, I also am the plaintext password mistakenly submitted to making database account, which is then big baby give me test library is deleted, I have a long memory behind the configuration file content encryption, data security problem to be reckoned with, life or job transfer, Sensitive data must be desensitized.

If you are not familiar with the desensitization concept, you can take a look at the 6 kinds of data desensitization scheme that I have written before. Desensitization is simply described in the paper, and then two common desensitization scenes are shared in the work.

Configuration desensitization

I used Jasypt, a Java encryption and decryption tool, which provides two desensitization modes of single-key symmetric encryption and asymmetric encryption.

Symmetric single-key encryption: a key with salt can be used for both encryption and decryption.

Asymmetric encryption: Use the public key and private key to encrypt and decrypt the content.

The above two encryption methods are very simple to use, let’s take springboot integrated single-key symmetric encryption as an example.

The jasypt-spring-boot-starter JAR is introduced first

<! <dependency> <groupId>com.github. Ulisesbocchio </groupId> <artifactId>jasypt-spring-boot-starter</artifactId> The < version > 2.1.0 < / version > < / dependency >Copy the code

Configuration files to the secret key configuration items jasypt. The encryptor, password, and it will need to be the value of desensitization value to replace the previously encrypted content ENC (mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l).

We can be freely defined the format, such as to ABC mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l format, as long as the configuration prefixes and suffixes.

jasypt:
  encryptor:
    property:
      prefix: "abc["
      suffix: "]"
Copy the code

The ENC(XXX) format is used to identify whether the value needs to be decrypted. If this format is not used, jasypt keeps the original value and does not decrypt the configuration item when it is loaded.

Spring: a datasource: url: JDBC: mysql: / / 2:3306 / xiaofu? useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&ze oDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai username: xiaofu password: ENC (mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l) # secret-key jasypt: the encryptor: password: programmers in something (but does not support Chinese)Copy the code

The secret key is a property with high security requirements, so it is generally not recommended to put it in the project directly. You can inject the -d parameter during startup or put it in the configuration center to avoid leakage.

Java jar - Djasypt. The encryptor. Password = 1123 springboot - jasypt - 2.3.3. The jarCopy the code

Pre-generated encrypted values that can be generated by calling the API in code

@Autowired private StringEncryptor stringEncryptor; public void encrypt(String content) { String encryptStr = stringEncryptor.encrypt(content); System.out.println(" Encrypted contents: "+ encryptStr); }Copy the code

Jar is the jasypt core JAR package, input text to be encrypted, password secret key, Algorithm indicates the encryption algorithm used.

Java - cp D: \ maven_lib \ org \ jasypt \ jasypt \ 1.9.3 \ jasypt - 1.9.3. Jar org. Jasypt. Intf. Cli. JasyptPBEStringEncryptionCLI input="root" password=xiaofu algorithm=PBEWithMD5AndDESCopy the code

If you can start normally after a meal operation, it means that the configuration file desensitization is no problem.

Sensitive field desensitization

Production environment user privacy data, such as mobile phone number, ID card or some account configuration information, storage should be not landing desensitization, that is, when entering our system will be real-time desensitization processing.

User data into the system, desensitization after persistent to the database, users query data but also reverse decryption. Such scenarios typically require global processing, so AOP facets are a good fit.

If the method is applied to the @encryptMethod annotation, check whether the input field contains the @encryptField annotation. If the method is applied to the @encryptMethod annotation, check whether the input field contains the @encryptField annotation. If yes, the corresponding field content is encrypted.

@Documented
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {

    String[] value() default "";
}
Copy the code
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptMethod {

    String type() default ENCRYPT;
}
Copy the code

The implementation of section is also relatively simple, to enter the encryption, return results decryption. In order to facilitate reading here only posted part of the code, the complete case Github address: github.com/chengxy-nds…

@Slf4j @Aspect @Component public class EncryptHandler { @Autowired private StringEncryptor stringEncryptor; @Pointcut("@annotation(com.xiaofu.annotation.EncryptMethod)") public void pointCut() { } @Around("pointCut()") public Object around(ProceedingJoinPoint joinPoint) {/** * encrypt */ encrypt(joinPoint); /** * decrypt */ Object decrypt = decrypt(joinPoint); return decrypt; } public void encrypt(ProceedingJoinPoint joinPoint) { try { Object[] objects = joinPoint.getArgs(); if (objects.length ! = 0) { for (Object o : objects) { if (o instanceof String) { encryptValue(o); } else { handler(o, ENCRYPT); }} catch (IllegalAccessException e) {e.printStackTrace();}} catch (IllegalAccessException e) {e.printstackTrace (); } } public Object decrypt(ProceedingJoinPoint joinPoint) { Object result = null; try { Object obj = joinPoint.proceed(); if (obj ! = null) { if (obj instanceof String) { decryptValue(obj); } else { result = handler(obj, DECRYPT); }} catch (Throwable e) {e.printstackTrace (); } return result; }... }Copy the code

Next, we’ll test the effect of the section annotations by desensitizing @encryptField with the mobile and ADDRESS annotations.

@EncryptMethod @PostMapping(value = "test") @ResponseBody public Object testEncrypt(@RequestBody UserVo user, @EncryptField String name) { return insertUser(user, name); } private UserVo insertUser(UserVo user, String name) {system.out.println (" encrypted data: user" + json.tojsonString (user)); return user; } @Data public class UserVo implements Serializable { private Long userId; @EncryptField private String mobile; @EncryptField private String address; private String age; }Copy the code

Request this interface, see the parameters are successfully encrypted, and the data returned to the user is still the data before desensitization, in line with our expectations, then this simple desensitization implementation is done.

Know what you are and why you are

Although Jasypt tool is simple and easy to use, as a programmer, we should not only be satisfied with skilled use, but also need to understand the underlying implementation principle, which is very important for the subsequent debugging of bugs and secondary development of extension functions.

Personally, I think the principle of Jasypt configuration file desensitization is very simple. Before using the configuration information, intercept the operation of obtaining the configuration and decrypt the corresponding encryption configuration before using it.

Specific is not so we simply look at the implementation of the source code, since it is integrated by springboot, then we will start from jasypt-spring-boot-starter source code.

Starter code is small, the main work is through the SPI mechanism registration services and @ Import annotations to inject JasyptSpringBootAutoConfiguration pre-processing of the class.

Registered in front loading class EnableEncryptablePropertiesConfiguration a EnableEncryptablePropertiesBeanFactoryPostProcessor core processing class.

Its constructor takes two parameters, ConfigurableEnvironment used to retrieve information, all attachment EncryptablePropertySourceConverter do resolution with configuration information.

Ultimately found to be responsible for decrypting the processing class EncryptablePropertySourceWrapper, it through to the Spring property management class PropertySource < T > do develop, rewrite the getProperty (String name) method, All values in the specified format such as ENC(x) wrap are decrypted when obtaining the configuration.

Now that we know the principle of the subsequent secondary development, such as: switching encryption algorithm or to achieve their own desensitization tool is much easier.

Github address: github.com/chengxy-nds…

PBE algorithm

Jasypt is an encryption algorithm that is packaged from the JDK’s JCe. jar package. It essentially uses an algorithm provided by the JDK. By default, Jasypt uses the PBE algorithm PBEWITHMD5ANDDES. PBE, WITH, MD5, AND DES.

PBE algorithm (Password Based Encryption) is a password-based Encryption algorithm, which is characterized by the Password is controlled by the user and multiple Encryption methods such as random numbers are added to ensure data security.

In essence, PBE algorithm does not really build new encryption and decryption algorithms, but to our known algorithms to do the packaging. For example: commonly used message digest algorithm MD5 and SHA algorithm, symmetric encryption algorithm DES, RC2, etc., and PBE algorithm is a reasonable combination of these algorithms, which also echo the name of the previous algorithm.

Since THE PBE algorithm uses our more commonly used symmetric encryption algorithm, it will involve the key problem. But it has no concept of key itself, only password password, the key is the password through encryption algorithm calculation.

The password itself is not very long, so it cannot be used as a substitute for the key, but it can be easily deciphered by brute force attack, which is where the salt comes in.

Salt is usually random information, such as random numbers and time stamps. Salt is attached to passwords that are algorithmically calculated to make them harder to decipher.

The source code of the cat

Simple understanding of PBE algorithm, go back to look at Jasypt source code is how to achieve encryption and decryption.

At the time of encryption first instantiation SecretKeyFactory secret key factory, produce eight values, salt. The default use jasypt encryptor. RandomSaltGenerator generator.

Public byte[] encrypt(byte[] message) {// According to the specified algorithm, Initialize the secret key factory final SecretKeyFactory factory = SecretKeyFactory. GetInstance (algorithm1); / / salt value generator, only 8 byte [] salt. = saltGenerator generateSalt (8); // final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations); SecretKey = factory.generatesecret (keySpec); // Build final Cipher cipherEncrypt = cipher.getInstance (algorithm1); cipherEncrypt.init(Cipher.ENCRYPT_MODE, key); / / ciphertext head (salt) byte [] params = cipherEncrypt. GetParameters () getEncoded (); Byte [] encryptedMessage = cipherencrypt.dofinal (message); byte[] encryptedMessage = cipherencrypt.dofinal (message); Return bytebuffer.allocate (1 + params.length + encryptedMessage.length).put((byte) params.length) .put(params) .put(encryptedMessage) .array(); }Copy the code

Because the default is the random salt generator, the same content is encrypted differently each time.

So how do we match when we decrypt?

The final encryptedMessage consists of two parts: the params header contains the password and a randomly generated salt value.

encryption

During decryption, the params contents will be deconstructed based on the contents of the encryptedMessage, the salt value and password will be parsed, and the actual contents will be decrypted by calling the underlying JDK algorithm.

@override @sneakythrows public byte[] Decrypt (byte[] encryptedMessage) {// Obtains the cipher-text header int paramsLength = Byte.toUnsignedInt(encryptedMessage[0]); Int messageLength = encryptedMessage.length - Paramslength-1; byte[] params = new byte[paramsLength]; byte[] message = new byte[messageLength]; System.arraycopy(encryptedMessage, 1, params, 0, paramsLength); System.arraycopy(encryptedMessage, paramsLength + 1, message, 0, messageLength); / / initialize the secret key factory final SecretKeyFactory factory = SecretKeyFactory. GetInstance (algorithm1); final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); SecretKey key = factory.generateSecret(keySpec); / / build head salt password parameters AlgorithmParameters AlgorithmParameters = AlgorithmParameters. GetInstance (algorithm1); algorithmParameters.init(params); // Build a Cipher and call the underlying algorithm final Cipher cipherDecrypt = cipher.getInstance (algorithm1); cipherDecrypt.init( Cipher.DECRYPT_MODE, key, algorithmParameters ); return cipherDecrypt.doFinal(message); }Copy the code