refactor: evolve common-core payload compatibility (#4237)

This commit is contained in:
Logic
2026-07-24 15:07:35 +08:00
committed by GitHub
parent b832d7ace4
commit 6152c07320
2 changed files with 67 additions and 5 deletions
@@ -17,8 +17,11 @@
package org.apache.hertzbeat.common.util;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -46,6 +49,12 @@ public final class AesUtil {
private static final String AES = "AES";
private static final byte[] PAYLOAD_HEADER = {'H', 'B', 'A', '2'};
private static final int IV_LENGTH = 16;
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
/**
* Encryption key The AES encryption key is 16 bits.
* If the AES encryption key is larger than 16 bits, an error message is displayed
@@ -87,14 +96,21 @@ public final class AesUtil {
SecretKeySpec keySpec = new SecretKeySpec(encryptKey.getBytes(StandardCharsets.UTF_8), AES);
// cipher based on the algorithm AES
Cipher cipher = Cipher.getInstance(ALGORITHM_STR);
byte[] initializationVector = new byte[IV_LENGTH];
SECURE_RANDOM.nextBytes(initializationVector);
// init cipher Encrypt_mode or Decrypt_mode operation, the second parameter is the KEY used
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(encryptKey.getBytes(StandardCharsets.UTF_8)));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(initializationVector));
// get content bytes, must utf-8
byte[] byteEncode = content.getBytes(StandardCharsets.UTF_8);
// encode content to byte array
byte[] byteAes = cipher.doFinal(byteEncode);
byte[] payload = ByteBuffer.allocate(PAYLOAD_HEADER.length + initializationVector.length + byteAes.length)
.put(PAYLOAD_HEADER)
.put(initializationVector)
.put(byteAes)
.array();
// base64 encode content
return Base64.getEncoder().encodeToString(byteAes);
return Base64.getEncoder().encodeToString(payload);
} catch (Exception e) {
log.error("aes encode content error: {}", e.getMessage(), e);
return content;
@@ -135,13 +151,36 @@ public final class AesUtil {
SecretKeySpec keySpec = new SecretKeySpec(decryptKey.getBytes(StandardCharsets.UTF_8), AES);
// cipher based on the algorithm AES
Cipher cipher = Cipher.getInstance(ALGORITHM_STR);
// init cipher Encrypt_mode or Decrypt_mode operation, the second parameter is the KEY used
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(decryptKey.getBytes(StandardCharsets.UTF_8)));
// base64 decode content
byte[] bytesContent = Base64.getDecoder().decode(content);
byte[] initializationVector;
if (hasPayloadHeader(bytesContent)) {
if (bytesContent.length <= PAYLOAD_HEADER.length + IV_LENGTH) {
throw new IllegalArgumentException("Invalid encrypted payload");
}
initializationVector = Arrays.copyOfRange(
bytesContent, PAYLOAD_HEADER.length, PAYLOAD_HEADER.length + IV_LENGTH);
bytesContent = Arrays.copyOfRange(
bytesContent, PAYLOAD_HEADER.length + IV_LENGTH, bytesContent.length);
} else {
initializationVector = decryptKey.getBytes(StandardCharsets.UTF_8);
}
// init cipher Encrypt_mode or Decrypt_mode operation, the second parameter is the KEY used
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(initializationVector));
// decode content to byte array
return cipher.doFinal(bytesContent);
}
private static boolean hasPayloadHeader(byte[] payload) {
if (payload.length < PAYLOAD_HEADER.length) {
return false;
}
for (int index = 0; index < PAYLOAD_HEADER.length; index++) {
if (payload[index] != PAYLOAD_HEADER[index]) {
return false;
}
}
return true;
}
/**
* Determine whether it is encrypted
@@ -20,10 +20,14 @@ package org.apache.hertzbeat.common.util;
import static org.apache.hertzbeat.common.util.AesUtil.aesDecode;
import static org.apache.hertzbeat.common.util.AesUtil.aesEncode;
import static org.apache.hertzbeat.common.util.AesUtil.isCiphertext;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import org.junit.jupiter.api.Test;
/**
@@ -43,12 +47,31 @@ class AesUtilTest {
void testAesEncode() {
String originalText = "This is a secret message";
String encryptedText = aesEncode(originalText, VALID_KEY);
String secondEncryptedText = aesEncode(originalText, VALID_KEY);
assertNotEquals(originalText, encryptedText);
assertNotEquals(encryptedText, secondEncryptedText);
byte[] encryptedPayload = Base64.getDecoder().decode(encryptedText);
byte[] secondEncryptedPayload = Base64.getDecoder().decode(secondEncryptedText);
assertArrayEquals(new byte[] {'H', 'B', 'A', '2'}, Arrays.copyOfRange(encryptedPayload, 0, 4));
assertFalse(Arrays.equals(
VALID_KEY.getBytes(StandardCharsets.UTF_8), Arrays.copyOfRange(encryptedPayload, 4, 20)));
assertFalse(Arrays.equals(
Arrays.copyOfRange(encryptedPayload, 4, 20),
Arrays.copyOfRange(secondEncryptedPayload, 4, 20)));
String decryptedText = aesDecode(encryptedText, VALID_KEY);
assertEquals(originalText, decryptedText);
}
@Test
void testLegacyCiphertextCanBeDecoded() {
String legacyCiphertext = "muJNZwhxg173v6EZAXb5TK8L5XlmDE5xmc9XTryH6Qk=";
assertEquals("This is a secret message", aesDecode(legacyCiphertext, VALID_KEY));
assertTrue(isCiphertext(legacyCiphertext, VALID_KEY));
}
@Test
void testAesDecode() {
// Test with invalid key