22
头图

Hello everyone! I'm Xiaofu~

The company is investigating the leakage of internal data accounts in the past few days. The reason is that some cute interns secretly transmitted the source code to GitHub with their account numbers and passwords, which led to the leakage of core data. The child still has not been beaten by the society. The consequences of the incident can be large or small.

Speaking of this I have more feeling, and before I TM deleted library experience, to think of it now and my heart still uncomfortable, I am also the database account password in clear text mistakenly submitted to GitHub , then give me a big baby which test libraries I deleted it. I have a long memory and encrypted the content of the configuration file. The data security problem really cannot be underestimated. Regardless of work or life, sensitive data must be desensitized.

If you are not familiar with the concept of desensitization, you can take a look at the six data desensitization schemes big factories are also using, which gives a brief description of desensitization, and then I will share two of my work. A more common desensitization scene.

Configure desensitization

To achieve configuration desensitization, I used Java , an encryption and decryption tool of Jasypt , which provides two desensitization methods: single-key symmetric encryption and asymmetric encryption.

Single-key symmetric encryption: a key plus salt can be used as the basis for encryption and decryption of content at the same time;

Asymmetric encryption: only use the public key and the private key to encrypt and decrypt the content;

The above two encryption methods are very simple to use. Let's take the springboot integrated single-key symmetric encryption method as an example.

First introduce jasypt-spring-boot-starter jar

 <!--配置文件加密-->
 <dependency>
     <groupId>com.github.ulisesbocchio</groupId>
     <artifactId>jasypt-spring-boot-starter</artifactId>
     <version>2.1.0</version>
 </dependency>

Add profile keys CI jasypt.encryptor.password , and will require desensitization value replacement values to pre-encrypted content ENC(mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l) .

We can define this format at will. For example abc[mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l] format, we only need to configure the prefix and suffix.

jasypt:
  encryptor:
    property:
      prefix: "abc["
      suffix: "]"

The ENC(XXX) format is mainly to facilitate the identification of whether the value needs to be decrypted. If it is not configured in accordance with this format, jasypt will keep the original value when loading the configuration item without decryption.

spring:
  datasource:
    url: jdbc:mysql://1.2.3.4:3306/xiaofu?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&ze oDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
    username: xiaofu
    password: ENC(mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l)

# 秘钥
jasypt:
  encryptor:
    password: 程序员内点事(然而不支持中文)

The secret key is an attribute with relatively high security requirements, so it is generally not recommended to put it directly in the project. It can be -D parameter at startup, or placed in the configuration center to avoid leakage.

java -jar -Djasypt.encryptor.password=1123  springboot-jasypt-2.3.3.RELEASE.jar

The pre-generated encrypted value can be generated by calling the API in the code

@Autowired
private StringEncryptor stringEncryptor;

public void encrypt(String content) {
    String encryptStr = stringEncryptor.encrypt(content);
    System.out.println("加密后的内容:" + encryptStr);
}

Or generated by the following Java command, several parameters D:\maven_lib\org\jasypt\jasypt\1.9.3\jasypt-1.9.3.jar are jasypt core jar package, input to be encrypted text, password secret key, algorithm is 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=PBEWithMD5AndDES

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

Sensitive field desensitization

The private data of users in the production environment, such as mobile phone number, ID card, or some account configuration information, must be desensitized without landing when entering the warehouse, that is, desensitization processing in real time when entering our system.

User data enters the system, and persists to the database after desensitization, and reverse decryption is required when the user queries the data. This kind of scene generally requires global processing, so it is not AOP aspect to realize it.

First, customize the two annotations @EncryptField and @EncryptMethod to use in the field attributes and methods respectively. The realization idea is very simple. As long as the method is applied to the @EncryptMethod @EncryptField parameter field is marked with the 06108b1420380a annotation. If there is, the corresponding field content will be encrypted.

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

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

    String type() default ENCRYPT;
}

The realization of the aspect is also relatively simple, the entry is encrypted, and the result is decrypted. For the convenience of reading, only part of the code is posted here, the complete case Github address: https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-jasypt

@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(joinPoint);
        /**
         * 解密
         */
        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);
                    }
                    //TODO 其余类型自己看实际情况加
                }
            }
        } 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);
                }
                //TODO 其余类型自己看实际情况加
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return result;
    }
    。。。
}

Next to test the effect of the aspect annotation, we desensitize the mobile and address with the annotation @EncryptField

@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("加密后的数据: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;
}

Request this interface, see that the parameters are successfully encrypted, and the data returned to the user is still the data before desensitization, which meets our expectations, then this simple desensitization implementation is over.

Know it

Jasypt tool is simple and easy to use, as a programmer, we can't just be satisfied with it. It is necessary to understand the underlying implementation principle, which is very important for subsequent debugging of bugs and secondary development and extension of functions.

I personally think that Jasypt configuration file is very simple. It is nothing more than intercepting the operation of obtaining the configuration before using the configuration information, and decrypting the corresponding encrypted configuration before using it.

This is not the case. Let’s take a brief look at the implementation of the source code. Since it is springboot , let’s start with the jasypt-spring-boot-starter source code.

starter less code, the main work is through SPI mechanisms registrars and @Import class annotations to inject require pre-treatment JasyptSpringBootAutoConfiguration .

In the pre-load class EnableEncryptablePropertiesConfiguration registered a core processing class EnableEncryptablePropertiesBeanFactoryPostProcessor .

Its constructor has two parameters, ConfigurableEnvironment used to obtain all the associated information, and EncryptablePropertySourceConverter analyzes the configuration information.

I found out that the specific processing class EncryptablePropertySourceWrapper is responsible for decryption. It Spring attribute management class PropertySource<T> and rewrites the getProperty(String name) method. When obtaining the configuration, all the values of the ENC(x)

Now that we know the principle, it will be much easier for us to follow up our secondary development, such as switching encryption algorithms or implementing our own desensitization tools.

Case Github address: https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-jasypt

PBE algorithm

Let’s talk about Jasypt . In fact, it is JCE.jar package. In essence, it still uses the algorithm provided by the JDK. The default is PBE algorithm PBEWITHMD5ANDDES . It is very interesting to see the name of this algorithm. Take a look at each sentence, PBE, WITH, MD5, AND, DES seem to have a bit of a story, keep reading.

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

In essence, the PBE algorithm does not really build a new encryption and decryption algorithm, but instead wraps the algorithm we already know. For example, commonly used message digest algorithms MD5 and SHA , symmetric encryption algorithms DES , RC2 etc., and PBE algorithm is a reasonable combination of these algorithms, which also echoes the name of the previous algorithm.

Since the PBE algorithm uses our more commonly used symmetric encryption algorithm, it will involve the issue of keys. But it does not have the concept of a key itself, only a password is a password, and the key is a password calculated through an encryption algorithm.

The password itself is not very long, so it cannot be used to replace the key. The password can be easily deciphered by brute force attacks. At this time, salt must be added.

The salt is usually some random information, such as random numbers and timestamps. The salt is attached to the password, and the difficulty of deciphering is increased through algorithm calculation.

The tricky in the source code

To understand the PBE algorithm briefly, look back and see Jasypt source code of 06108b14203e81 implements encryption and decryption.

When encrypting, first instantiate the key factory SecretKeyFactory to generate an eight-bit salt value, and use the jasypt.encryptor.RandomSaltGenerator generator by default.

public byte[] encrypt(byte[] message) {
    // 根据指定算法,初始化秘钥工厂
    final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm1);
    // 盐值生成器,只选八位
    byte[] salt = saltGenerator.generateSalt(8);
    // 
    final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations);
    // 盐值、口令生成秘钥
    SecretKey key = factory.generateSecret(keySpec);

    // 构建加密器
    final Cipher cipherEncrypt = Cipher.getInstance(algorithm1);
    cipherEncrypt.init(Cipher.ENCRYPT_MODE, key);
    // 密文头部(盐值)
    byte[] params = cipherEncrypt.getParameters().getEncoded();

    // 调用底层实现加密
    byte[] encryptedMessage = cipherEncrypt.doFinal(message);

    // 组装最终密文内容并分配内存(盐值+密文)
    return ByteBuffer
            .allocate(1 + params.length + encryptedMessage.length)
            .put((byte) params.length)
            .put(params)
            .put(encryptedMessage)
            .array();
}

Since the default random salt value generator, resulting in the same contents each time the encrypted content is different .

So how should it correspond to when decrypting?

Looking at the source code above, it is found that the final encrypted text is composed of two parts. The params message header contains the password and the randomly generated salt value, and the encryptedMessage ciphertext.

加密

In the ciphertext decryption based encryptedMessage dismantling the content params contents parsed salt value and the password, decrypt actual content JDK call underlying algorithms.

@Override
@SneakyThrows
public byte[] decrypt(byte[] encryptedMessage) {
    // 获取密文头部内容
    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);

    // 初始化秘钥工厂
    final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm1);
    final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
    SecretKey key = factory.generateSecret(keySpec);

    // 构建头部盐值口令参数
    AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(algorithm1);
    algorithmParameters.init(params);

    // 构建加密器,调用底层算法
    final Cipher cipherDecrypt = Cipher.getInstance(algorithm1);
    cipherDecrypt.init(
            Cipher.DECRYPT_MODE,
            key,
            algorithmParameters
    );
    return cipherDecrypt.doFinal(message);
}

解密

I'm Xiaofu, see you next time~

I have compiled hundreds of technical e-books for students in need. The technology group is almost full, students who want to join can add my friends and blow up the technology with the big guys.

E-book address

Personal public number: Programmer's point of affairs , welcome to communicate


程序员小富
2.7k 声望5.3k 粉丝