Hello everyone! I am Xiaofu~
In the past few days, the company has been investigating the leakage of internal data accounts. The reason is that it was found that some intern Xiaokai actually privately uploaded the source code with the account number and password GitHub
, resulting in the leakage of core data, and the child has not been beaten by society. This kind of thing The consequences can be big or small.
Speaking of this, I feel more emotional. Before my experience of TM being deleted from the database , I still feel uncomfortable when I think about it. I also submitted the plaintext password of the database account by mistake GitHub
, and then the big baby deleted it from the test database for me. Later, I remembered and encrypted the contents of the configuration file. The data security problem should not 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 6 data desensitization schemes I wrote before, which are also used by large factories. The desensitization is briefly described in the article. The following two are more common in the sharing work. desensitization scene.
Configure desensitization
To realize the desensitization of the configuration, I used Java
an encryption and decryption tool Jasypt
, which provides 单密钥对称加密
and 非对称加密
two desensitization methods.
Single-key symmetric encryption: A key is added with salt, which can be used as the basis for both encryption and decryption of content;
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 import the jasypt-spring-boot-starter
jar
<!--配置文件加密-->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
The configuration file adds the key configuration item jasypt.encryptor.password
, and replaces the value that needs to be desensitized value
with the pre-encrypted content ENC(mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l)
.
We can define this format at will. For example, if we want the 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 for the convenience of identifying whether the value needs to jasypt
be decrypted.
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 high security requirements, so it is generally not recommended to put it directly in the project. It can be -D
injected through the parameters at startup, or placed in the configuration center to avoid leakage.
java -jar -Djasypt.encryptor.password=1123 springboot-jasypt-2.3.3.RELEASE.jar
Pre-generated encrypted values, which can be generated by in-code API calls
@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 the jasypt core jar package, input
the text to be encrypted, password
the secret key, and algorithm
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 one operation, it means that the desensitization of the configuration file is no problem.
Desensitization of sensitive fields
The private data of users in the production environment, such as mobile phone numbers, ID cards, or some account configuration information, must be desensitized without landing when entering the warehouse, that is, real-time desensitization is required when entering our system.
User data enters the system, desensitized and persisted to the database, and reverse decryption is required when users query data. This kind of scenario generally requires global processing, so it is not AOP
suitable to use the aspect to achieve.
First, customize two annotations @EncryptField
and @EncryptMethod
use them on field attributes and methods respectively. The implementation idea is very simple. As long as the method is applied to the @EncryptMethod
annotation, check whether the input field is annotated @EncryptField
with annotation, and encrypt the corresponding field content if there is.
@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 implementation of the aspect is also relatively simple, encrypting the entry and decrypting the returned result. For the convenience of reading, only part of the code is posted here, and the Github address of the complete case: 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 facet annotations, we desensitize fields mobile
and address
annotations .@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 and see that the parameters are successfully encrypted, and the data returned to the user is still the data before desensitization, which is in line with our expectations, then this simple desensitization implementation is over.
know why know why
Jasypt
Although the tool is simple and easy to use, as programmers, we can’t just be satisfied with skilled use. It is necessary to understand the underlying implementation principle, which is very important for subsequent debugging of bugs and secondary development of extended functions.
Personally, I think Jasypt
the principle of configuration file desensitization 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.
Is this the case? Let's take a brief look at the implementation of the source code. Since it is springboot
integrated in a way, let's start with the jasypt-spring-boot-starter
source code.
starter
There is very little code, and the main work is to inject the classes that need to be preprocessed by SPI
registering services and annotations through the mechanism .@Import
JasyptSpringBootAutoConfiguration
EnableEncryptablePropertiesConfiguration
A core processing class is registered in the front-loading class EnableEncryptablePropertiesBeanFactoryPostProcessor
.
Its constructor has two parameters, which are ConfigurableEnvironment
used to obtain all the attachment information and EncryptablePropertySourceConverter
parse the configuration information.
I found the processing class specifically responsible for decryption EncryptablePropertySourceWrapper
. It rewrote the method by extending the Spring
attribute management class . When obtaining the configuration, all the values wrapped in the specified format such as ENC(x) are all decrypted.PropertySource<T>
getProperty(String name)
Now that we know the principle, it will be much easier for us to follow up with 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
the encryption algorithm used in China. In fact, it is JCE.jar
encapsulated on the basis of the JDK package. In essence, it still uses the algorithm provided by the JDK. The default algorithm is used. It is PBE
very PBEWITHMD5ANDDES
interesting to see the name of this algorithm. Look, PBE, WITH, MD5, AND, DES seem to have some stories, keep reading.
PBE
Algorithm ( Password Based Encryption
, encryption based on password (password)) is a password-based encryption algorithm, which is characterized in that the password is mastered by the user, and the security of data is ensured by adding random number multiple encryption and other methods.
In essence, the PBE algorithm does not really build a new encryption and decryption algorithm, but wraps the algorithm we already know. For example: commonly used message digest algorithms MD5
and SHA
algorithms, symmetric encryption algorithms DES
, RC2
etc., and the 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 problem of the key. But it does not have the concept of a key itself, only the password, and the key is calculated by the password through the encryption algorithm.
The password itself is not very long, so it cannot be used to replace the key. Only the password can be easily deciphered by brute force attacks. At this time, it is necessary to add some salt .
The salt is usually some random information, such as random numbers and timestamps. The salt is attached to the password, and the algorithm is calculated to increase the difficulty of deciphering.
The tricks in the source code
Simply understand the PBE algorithm, and look back at how Jasypt
the source code implements encryption and decryption.
When encrypting, first instantiate the key factory to generate an eight-bit salt value, which is the generator SecretKeyFactory
used by default .jasypt.encryptor.RandomSaltGenerator
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 random salt value generator is used by default, the encrypted content of the same content is different each time .
So how to correspond when decrypting it?
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, the randomly generated salt value, and encryptedMessage
the ciphertext.
encryptedMessage
When decrypting, the content will be disassembled according to the content of the ciphertext to params
parse the salt value and password, and the actual content will be decrypted by calling the JDK underlying algorithm.
@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~
Hundreds of various technical e-books have been sorted out, and students in need can pick them up. The technical group is almost full, and students who want to join can add me as friends, and let’s talk about technology with the big guys.
Personal public number: Programmer's internal affairs , welcome to communicate