From e3c817f99ff351ed58e7c2bdca6a46a63ef8b34f Mon Sep 17 00:00:00 2001 From: msslulu <1484036491@qq.com> Date: Thu, 30 Apr 2026 01:50:21 -0700 Subject: [PATCH 1/2] feat: jwtutil,SM2EncryptionUtil,SM3PasswordUtil UT --- .../tinyengine/it/login/utils/JwtUtil.java | 7 + .../it/login/utils/SM3PasswordUtil.java | 6 + .../it/login/utils/JwtUtilTest.java | 352 +++++++++++++ .../it/login/utils/SM2EncryptionUtilTest.java | 337 ++++++++++++ .../it/login/utils/SM3PasswordUtilTest.java | 493 ++++++++++++++++++ 5 files changed, 1195 insertions(+) create mode 100644 base/src/test/java/com/tinyengine/it/login/utils/JwtUtilTest.java create mode 100644 base/src/test/java/com/tinyengine/it/login/utils/SM2EncryptionUtilTest.java create mode 100644 base/src/test/java/com/tinyengine/it/login/utils/SM3PasswordUtilTest.java diff --git a/base/src/main/java/com/tinyengine/it/login/utils/JwtUtil.java b/base/src/main/java/com/tinyengine/it/login/utils/JwtUtil.java index 41f8a0e4..148ce10a 100644 --- a/base/src/main/java/com/tinyengine/it/login/utils/JwtUtil.java +++ b/base/src/main/java/com/tinyengine/it/login/utils/JwtUtil.java @@ -132,6 +132,9 @@ public String generateToken(String username, String roles, String userId, */ public String generateToken(String username, String roles, String userId, List tenants, Integer platformId) { + if(username==null ){ + throw new IllegalArgumentException("Username must not be null"); + } return generateToken(username, roles, userId, (Object) tenants, platformId); } @@ -253,6 +256,10 @@ private T getClaimFromToken(String token, String claimName, Class clazz) * 验证 Token 是否有效(包含黑名单检查和过期检查) */ public boolean validateToken(String token) { + if (token == null) { + throw new IllegalArgumentException("Token must not be null or empty"); + } + try { // 检查是否在黑名单中 if (tokenBlacklistService.isTokenBlacklisted(token)) { diff --git a/base/src/main/java/com/tinyengine/it/login/utils/SM3PasswordUtil.java b/base/src/main/java/com/tinyengine/it/login/utils/SM3PasswordUtil.java index 244bf523..0aec0e98 100644 --- a/base/src/main/java/com/tinyengine/it/login/utils/SM3PasswordUtil.java +++ b/base/src/main/java/com/tinyengine/it/login/utils/SM3PasswordUtil.java @@ -35,6 +35,9 @@ public class SM3PasswordUtil { * SM3 哈希计算 */ public static String sm3Hash(String data, String salt) throws Exception { + if(data == null || salt == null) { + throw new IllegalArgumentException("数据和盐值不能为空"); + } MessageDigest md = MessageDigest.getInstance(SM3_ALGORITHM, "BC"); String dataWithSalt = data + salt; byte[] hash = md.digest(dataWithSalt.getBytes("UTF-8")); @@ -54,6 +57,9 @@ public static PasswordResult createPassword(String plainPassword) throws Excepti * 验证用户密码 */ public static boolean verifyPassword(String inputPassword, String storedHash, String salt) throws Exception { + if(inputPassword == null || storedHash == null || salt == null) { + throw new IllegalArgumentException("输入密码、存储哈希和盐值不能为空"); + } String computedHash = sm3Hash(inputPassword, salt); return computedHash.equals(storedHash); } diff --git a/base/src/test/java/com/tinyengine/it/login/utils/JwtUtilTest.java b/base/src/test/java/com/tinyengine/it/login/utils/JwtUtilTest.java new file mode 100644 index 00000000..bcde12e9 --- /dev/null +++ b/base/src/test/java/com/tinyengine/it/login/utils/JwtUtilTest.java @@ -0,0 +1,352 @@ +package com.tinyengine.it.login.utils; + +import cn.hutool.core.util.ReflectUtil; +import com.tinyengine.it.login.service.TokenBlacklistService; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.core.env.Environment; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +class JwtUtilTest { + + @Mock + private TokenBlacklistService tokenBlacklistService; + @Mock + private Environment environment; + + + @InjectMocks + private JwtUtil jwtUtil; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + + ReflectUtil.setFieldValue(jwtUtil, "tokenBlacklistService", tokenBlacklistService); + ReflectUtil.setFieldValue(jwtUtil, "environment", environment); + String testSecret = "myTestSecretKeyThatIsLongEnoughForHS256Algorithm"; + ReflectUtil.setFieldValue(jwtUtil, "cachedSecret", testSecret); + + when(environment.getActiveProfiles()).thenReturn("dev".split(",")); + // Also set expiration time if it's not a constant but an injected value + + } + @Test + void validateSecretConfigurationThrowsExceptionWhenSecretIsMissingInNonDevProfile() { + when(environment.getActiveProfiles()).thenReturn(new String[]{"prod"}); + + assertThrows(IllegalStateException.class, jwtUtil::validateSecretConfiguration); + } + + @Test + void validateSecretConfigurationGeneratesSecretInDevProfile() { + when(environment.getActiveProfiles()).thenReturn(new String[]{"dev"}); + assertDoesNotThrow(jwtUtil::validateSecretConfiguration); + } + + + @Test + void validateSecretConfigurationThrowsExceptionForInvalidSecret() { + when(environment.getActiveProfiles()).thenReturn(new String[]{"prod"}); + System.setProperty("SECRET_STRING", "short"); + assertThrows(IllegalStateException.class, jwtUtil::validateSecretConfiguration); + } + @Test + void getSecretKeyReturnsValidKey() { + // 使用足够长的密钥(>32字节) + String testSecret = "myTestSecretKeyThatIsLongEnoughForHS256Algorithm"; + + jwtUtil.validateSecretConfiguration(); // 现在不会 NPE + SecretKey secretKey = jwtUtil.getSecretKey(); + + assertNotNull(secretKey); + assertEquals("HmacSHA256", secretKey.getAlgorithm()); + + } + + @Test + void generateTokenReturnsValidToken() { + String username = "testUser"; + String roles = "USER"; + String userId = "123"; + Integer platformId = 1; + + String token = jwtUtil.generateToken(username, roles, userId, null, platformId); + + assertNotNull(token); + assertFalse(token.isEmpty()); + assertEquals(username, jwtUtil.getUsernameFromToken(token)); + assertEquals(roles, jwtUtil.getRolesFromToken(token)); + assertEquals(userId, jwtUtil.getUserIdFromToken(token)); + assertEquals(platformId, jwtUtil.getPlatformIdFromToken(token)); + } + + @Test + void generateTokenThrowsExceptionForNullUsername() { + assertThrows(IllegalArgumentException.class, () -> jwtUtil.generateToken(null, "USER", "123", null, 1), + "Null username should throw IllegalArgumentException"); + } + + @Test + void generateTokenHandlesEmptyRoles() { + String token = jwtUtil.generateToken("testUser", "", "123", null, 1); + + assertNotNull(token); + assertFalse(token.isEmpty()); + assertEquals("", jwtUtil.getRolesFromToken(token)); + } + + @Test + void generateTokenHandlesNullUserId() { + // Given + String username = "testUser"; + String roles = "USER"; + Integer platformId = 1; + Object tenants = null; // 显式传入 null + // When + String token = jwtUtil.generateToken(username, roles, null, tenants, platformId); + + // Then + assertNotNull(token); + assertFalse(token.isEmpty()); + + // 使用 assertAll 聚合断言,避免单个失败中断其余检查 + assertAll("Token claims validation", + () -> assertNull(jwtUtil.getUserIdFromToken(token), "userId should be null"), + () -> assertEquals(username, jwtUtil.getUsernameFromToken(token), "username mismatch"), + () -> assertEquals(roles, jwtUtil.getRolesFromToken(token), "roles mismatch"), + () -> assertEquals(platformId, jwtUtil.getPlatformIdFromToken(token), "platformId mismatch"), + () -> assertEquals(Collections.emptyList(), jwtUtil.getTenantsFromToken(token), "tenants should be empty list") + ); + } + + @Test + void generateTokenHandlesNullTenants() { + String token = jwtUtil.generateToken("testUser", "USER", "123", null, 1); + + assertNotNull(token); + assertFalse(token.isEmpty()); + assertTrue(jwtUtil.getTenantsFromToken(token).isEmpty()); + } + + @Test + void generateTokenHandlesEmptyTenantsList() { + String token = jwtUtil.generateToken("testUser", "USER", "123", new ArrayList<>(), 1); + + assertNotNull(token); + assertFalse(token.isEmpty()); + assertTrue(jwtUtil.getTenantsFromToken(token).isEmpty()); + } + + @Test + void generateTokenHandlesNullPlatformId() { + String token = jwtUtil.generateToken("testUser", "USER", "123", null, null); + + assertNotNull(token); + assertFalse(token.isEmpty()); + assertNull(jwtUtil.getPlatformIdFromToken(token)); + } + + @Test + void generateTokenHandlesSpecialCharactersInUsername() { + String username = "user!@#$%^&*()"; + String token = jwtUtil.generateToken(username, "USER", "123", null, 1); + + assertNotNull(token); + assertFalse(token.isEmpty()); + assertEquals(username, jwtUtil.getUsernameFromToken(token)); + } + + @Test + void generateTokenHandlesLongUsername() { + String username = "a".repeat(256); + String token = jwtUtil.generateToken(username, "USER", "123", null, 1); + + assertNotNull(token); + assertFalse(token.isEmpty()); + assertEquals(username, jwtUtil.getUsernameFromToken(token)); + } + + @Test + void generateTokenHandlesCombinationOfNullAndValidParameters() { + assertThrows(IllegalArgumentException.class, () -> jwtUtil.generateToken(null, "USER", "123", null, 1), + "Null username should throw IllegalArgumentException"); + + } + + @Test + void validateTokenReturnsTrueForValidToken() { + String token = jwtUtil.generateToken("validUser", "USER", "123", null, 1); + when(tokenBlacklistService.isTokenBlacklisted(token)).thenReturn(false); + + boolean isValid = jwtUtil.validateToken(token); + + assertTrue(isValid, "Valid token should return true"); + } + + @Test + void validateTokenReturnsFalseForBlacklistedToken() { + String token = "blacklistedToken"; + when(tokenBlacklistService.isTokenBlacklisted(token)).thenReturn(true); + + boolean isValid = jwtUtil.validateToken(token); + + assertFalse(isValid, "Blacklisted token should return false"); + } + + @Test + void validateTokenReturnsFalseForExpiredToken() { + String expiredToken = createExpiredToken("expiredUser", "USER", "999"); + + when(tokenBlacklistService.isTokenBlacklisted(expiredToken)).thenReturn(false); + + // Simulate token expiration by waiting or mocking expiration + boolean isValid = jwtUtil.validateToken(expiredToken); + + assertFalse(isValid, "Expired token should return false"); + } + + @Test + void validateTokenReturnsFalseForMalformedToken() { + String malformedToken = "malformedToken"; + + boolean isValid = jwtUtil.validateToken(malformedToken); + + assertFalse(isValid, "Malformed token should return false"); + } + + @Test + void validateTokenReturnsFalseForInvalidSignature() { + String token = jwtUtil.generateToken("validUser", "USER", "123", null, 1); + String tamperedToken = token + "tampered"; + + boolean isValid = jwtUtil.validateToken(tamperedToken); + + assertFalse(isValid, "Token with invalid signature should return false"); + } + + @Test + void validateTokenThrowsExceptionForNullToken() { + assertThrows(IllegalArgumentException.class, () -> jwtUtil.validateToken(null), + "Null token should throw IllegalArgumentException"); + } + + @Test + void validateTokenReturnsFalseForEmptyToken() { + String emptyToken = ""; + + boolean isValid = jwtUtil.validateToken(emptyToken); + + assertFalse(isValid, "Empty token should return false"); + } + + @Test + void validateTokenReturnsFalseForTokenWithMissingClaims() { + String token = jwtUtil.generateToken("validUser", null, null, null, null); + when(tokenBlacklistService.isTokenBlacklisted(token)).thenReturn(true); + + boolean isValid = jwtUtil.validateToken(token); + + assertFalse(isValid, "Token with missing claims should return false"); + } + + @Test + void validateTokenHandlesTokenWithExtraClaims() { + String token = jwtUtil.generateToken("testUser", "USER", "123", null, 1); + // Add extra claims manually + String tokenWithExtraClaims = token + ".extraClaims"; + + boolean isValid = jwtUtil.validateToken(tokenWithExtraClaims); + + assertFalse(isValid, "Token with extra claims should return false"); + } + + @Test + void validateTokenHandlesTokenWithInvalidExpirationDate() { + String token = jwtUtil.generateToken("testUser", "USER", "123", null, 1); + when(tokenBlacklistService.isTokenBlacklisted(token)).thenReturn(true); + + // Simulate invalid expiration date + String tamperedToken = token.replace("exp", "invalidExp"); + + boolean isValid = jwtUtil.validateToken(tamperedToken); + + assertFalse(isValid, "Token with invalid expiration date should return false"); + } + + @Test + void validateTokenHandlesTokenWithNullClaims() { + String token = jwtUtil.generateToken("testUser", "USER", "123", null, 1); + when(tokenBlacklistService.isTokenBlacklisted(token)).thenReturn(true); + + // Simulate null claims + String tamperedToken = token.replace("claims", "null"); + + boolean isValid = jwtUtil.validateToken(tamperedToken); + + assertFalse(isValid, "Token with null claims should return false"); + } + + @Test + void validateTokenHandlesTokenWithEmptyClaims() { + String token = jwtUtil.generateToken("testUser", "USER", "123", null, 1); + when(tokenBlacklistService.isTokenBlacklisted(token)).thenReturn(true); + + // Simulate empty claims + String tamperedToken = token.replace("claims", "{}"); + + boolean isValid = jwtUtil.validateToken(tamperedToken); + + assertFalse(isValid, "Token with empty claims should return false"); + } + + @Test + void validateTokenHandlesTokenWithInvalidAlgorithm() { + String token = jwtUtil.generateToken("testUser", "USER", "123", null, 1); + when(tokenBlacklistService.isTokenBlacklisted(token)).thenReturn(true); + + // Simulate invalid algorithm + String tamperedToken = token.replace("HS256", "RS256"); + + boolean isValid = jwtUtil.validateToken(tamperedToken); + + assertFalse(isValid, "Token with invalid algorithm should return false"); + } + + /** + * 辅助方法:生成一个已经过期的JWT(不通过JwtUtil,直接使用相同的密钥) + */ + private String createExpiredToken(String username, String roles, String userId) { + Map claims = new HashMap<>(); + claims.put("username", username); + claims.put("roles", roles); + claims.put("userId", userId); + claims.put("platformId", 1); + claims.put("tenants", new ArrayList<>()); + + // 使用与JwtUtil中完全相同的密钥 + String testSecret = "myTestSecretKeyThatIsLongEnoughForHS256Algorithm"; + SecretKey key = Keys.hmacShaKeyFor(testSecret.getBytes(StandardCharsets.UTF_8)); + + return Jwts.builder() + .claims(claims) + .subject(username) + .issuedAt(new Date(System.currentTimeMillis() - 120_000)) // 2分钟前签发 + .expiration(new Date(System.currentTimeMillis() - 60_000)) // 1分钟前过期 + .signWith(key) + .compact(); + } + +} \ No newline at end of file diff --git a/base/src/test/java/com/tinyengine/it/login/utils/SM2EncryptionUtilTest.java b/base/src/test/java/com/tinyengine/it/login/utils/SM2EncryptionUtilTest.java new file mode 100644 index 00000000..a0ffb20e --- /dev/null +++ b/base/src/test/java/com/tinyengine/it/login/utils/SM2EncryptionUtilTest.java @@ -0,0 +1,337 @@ +package com.tinyengine.it.login.utils; + +import org.bouncycastle.jcajce.provider.util.BadBlockException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; + +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.PublicKey; +import java.security.PrivateKey; + + +import static org.junit.jupiter.api.Assertions.*; + +class SM2EncryptionUtilTest { + + + private KeyPair keyPair; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + keyPair = SM2EncryptionUtil.generateSM2KeyPair(); + } + + @Test + void generateSM2KeyPair() throws Exception { + assertNotNull(keyPair, "KeyPair should not be null"); + assertNotNull(keyPair.getPrivate(), "PrivateKey should not be null"); + assertNotNull(keyPair.getPublic(), "PublicKey should not be null"); + } + + @Test + void encryptAndDecrypt() throws Exception { + String plainText = "Hello, SM2!"; + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void getPublicKeyFromBase64() throws Exception { + PublicKey publicKey = keyPair.getPublic(); + String base64PublicKey = java.util.Base64.getEncoder().encodeToString(publicKey.getEncoded()); + + PublicKey decodedPublicKey = SM2EncryptionUtil.getPublicKeyFromBase64(base64PublicKey); + assertNotNull(decodedPublicKey, "Decoded PublicKey should not be null"); + assertEquals(publicKey, decodedPublicKey, "Decoded PublicKey should match the original"); + } + + @Test + void getPrivateKeyFromBase64() throws Exception { + PrivateKey privateKey = keyPair.getPrivate(); + String base64PrivateKey = java.util.Base64.getEncoder().encodeToString(privateKey.getEncoded()); + + PrivateKey decodedPrivateKey = SM2EncryptionUtil.getPrivateKeyFromBase64(base64PrivateKey); + assertNotNull(decodedPrivateKey, "Decoded PrivateKey should not be null"); + assertEquals(privateKey, decodedPrivateKey, "Decoded PrivateKey should match the original"); + } + + @Test + void encryptThrowsExceptionForNullPublicKey() { + String plainText = "Hello, SM2!"; + assertThrows(java.security.InvalidKeyException.class, () -> SM2EncryptionUtil.encrypt(plainText, null), + "Encrypting with null PublicKey should throw InvalidKeyException"); + } + + @Test + void decryptThrowsExceptionForNullPrivateKey() { + String base64CipherText = "dummyCipherText"; + assertThrows(java.security.InvalidKeyException.class, () -> SM2EncryptionUtil.decrypt(base64CipherText, null), + "Decrypting with null PrivateKey should throw InvalidKeyException"); + } + + @Test + void encryptAndDecryptWithEmptyString() { + String plainText = ""; + PublicKey publicKey = keyPair.getPublic(); + assertThrows(BadBlockException.class, () -> SM2EncryptionUtil.encrypt(plainText, publicKey), + "Encrypting an empty string should throw BadBlockException"); + } + + @Test + void encryptAndDecryptWithString() throws Exception { + String plainText = "hello"; + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void encryptAndDecryptWithSpecialCharacters() throws Exception { + String plainText = "!@#$%^&*()_+{}|:\"<>?"; + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void encryptAndDecryptWithLongText() throws Exception { + String plainText = "a".repeat(1000); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void getPublicKeyFromInvalidBase64() { + String invalidBase64 = "invalidBase64Key"; + assertThrows(java.security.spec.InvalidKeySpecException.class, () -> SM2EncryptionUtil.getPublicKeyFromBase64(invalidBase64), + "Invalid Base64 key should throw InvalidKeySpecException"); + } + + @Test + void decryptWithInvalidCipherText() { + String invalidCipherText = "invalidCipherText"; + PrivateKey privateKey = keyPair.getPrivate(); + + assertThrows(Exception.class, () -> SM2EncryptionUtil.decrypt(invalidCipherText, privateKey), + "Decrypting invalid cipher text should throw an exception"); + } + + @Test + void encryptThrowsExceptionForNullPlainText() { + PublicKey publicKey = keyPair.getPublic(); + assertThrows(NullPointerException.class, () -> SM2EncryptionUtil.encrypt(null, publicKey), + "Encrypting with null plain text should throw NullPointerException"); + } + + @Test + void decryptThrowsExceptionForNullCipherText() { + PrivateKey privateKey = keyPair.getPrivate(); + assertThrows(NullPointerException.class, () -> SM2EncryptionUtil.decrypt(null, privateKey), + "Decrypting with null cipher text should throw NullPointerException"); + } + + @Test + void encryptAndDecryptWithUnicodeCharacters() throws Exception { + String plainText = "你好,世界!🌍"; + System.out.println("Original plain text: " + plainText); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + System.out.println("Encrypted text: " + encryptedText); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void encryptAndDecryptWithLargeKeyPair() throws Exception { + KeyPair largeKeyPair = SM2EncryptionUtil.generateSM2KeyPair(); // Assuming larger keys can be generated + String plainText = "Large key pair test"; + PublicKey publicKey = largeKeyPair.getPublic(); + PrivateKey privateKey = largeKeyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void encryptThrowsExceptionForNullKeyPair() { + String plainText = "Test with null key pair"; + assertThrows(InvalidKeyException.class, () -> SM2EncryptionUtil.encrypt(plainText, null), + "Encrypting with null key pair should throw InvalidKeyException"); + } + + @Test + void decryptThrowsExceptionForTamperedCipherText() throws Exception { + String plainText = "Tampered cipher text test"; + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + String tamperedCipherText = encryptedText.substring(0, encryptedText.length() - 1) + "A"; + + assertThrows(Exception.class, () -> SM2EncryptionUtil.decrypt(tamperedCipherText, privateKey), + "Decrypting tampered cipher text should throw an exception"); + } + + @Test + void encryptAndDecryptWithMaximumLengthInput() throws Exception { + String plainText = "a".repeat(245); // Adjust length based on SM2 limits + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void encryptAndDecryptWithNonUTF8Characters() throws Exception { + String plainText = new String(new byte[]{(byte) 0xC3, (byte) 0x28}, "ISO-8859-1"); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void decryptWithIncorrectPrivateKey() throws Exception { + String plainText = "Test with incorrect private key"; + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + + KeyPair anotherKeyPair = SM2EncryptionUtil.generateSM2KeyPair(); + PrivateKey incorrectPrivateKey = anotherKeyPair.getPrivate(); + + assertThrows(Exception.class, () -> SM2EncryptionUtil.decrypt(encryptedText, incorrectPrivateKey), + "Decrypting with an incorrect private key should throw an exception"); + } + + @Test + void encryptAndDecryptWithBinaryData() throws Exception { + byte[] binaryData = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05}; + String plainText = new String(binaryData, "ISO-8859-1"); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original binary data"); + } + + @Test + void encryptAndDecryptWithMixedCharacterSets() throws Exception { + String plainText = "Hello123!@#你好🌟"; + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void decryptThrowsExceptionForEmptyCipherText() { + String emptyCipherText = ""; + PrivateKey privateKey = keyPair.getPrivate(); + + assertThrows(BadBlockException.class, () -> SM2EncryptionUtil.decrypt(emptyCipherText, privateKey), + "Decrypting with empty cipher text should throw BadBlockException"); + } + + @Test + void encryptAndDecryptWithRepeatedPatterns() throws Exception { + String plainText = "abc123abc123abc123"; + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } + + @Test + void encryptThrowsExceptionForNullInput() { + PublicKey publicKey = keyPair.getPublic(); + assertThrows(NullPointerException.class, () -> SM2EncryptionUtil.encrypt(null, publicKey), + "Encrypting null input should throw NullPointerException"); + } + + @Test + void decryptThrowsExceptionForCorruptedBase64CipherText() { + String corruptedCipherText = "corruptedBase64=="; + PrivateKey privateKey = keyPair.getPrivate(); + + assertThrows(IllegalArgumentException.class, () -> SM2EncryptionUtil.decrypt(corruptedCipherText, privateKey), + "Decrypting corrupted Base64 cipher text should throw IllegalArgumentException"); + } + + @Test + void encryptAndDecryptWithVeryLargeInput() throws Exception { + String plainText = "a".repeat(5000); // Adjust size based on SM2 limits + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String encryptedText = SM2EncryptionUtil.encrypt(plainText, publicKey); + assertNotNull(encryptedText, "Encrypted text should not be null"); + + String decryptedText = SM2EncryptionUtil.decrypt(encryptedText, privateKey); + assertNotNull(decryptedText, "Decrypted text should not be null"); + assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text"); + } +} \ No newline at end of file diff --git a/base/src/test/java/com/tinyengine/it/login/utils/SM3PasswordUtilTest.java b/base/src/test/java/com/tinyengine/it/login/utils/SM3PasswordUtilTest.java new file mode 100644 index 00000000..7536d7a3 --- /dev/null +++ b/base/src/test/java/com/tinyengine/it/login/utils/SM3PasswordUtilTest.java @@ -0,0 +1,493 @@ +package com.tinyengine.it.login.utils; + +import com.tinyengine.it.login.model.PasswordResult; +import org.junit.jupiter.api.*; +import org.mockito.MockitoAnnotations; + +import java.security.KeyPair; + +import static org.junit.jupiter.api.Assertions.*; + +class SM3PasswordUtilTest { + + + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + } + + + @Test + @DisplayName("sm3Hash 应返回固定长度的十六进制字符串") + void sm3HashReturnsFixedLengthHex() throws Exception { + String data = "testPassword"; + String salt = "testSalt123"; + String hash = SM3PasswordUtil.sm3Hash(data, salt); + // SM3 哈希输出 256 位,即 64 个十六进制字符 + assertEquals(64, hash.length()); + assertTrue(hash.matches("[0-9a-f]{64}")); + } + + @Test + @DisplayName("相同输入和盐值应产生相同哈希") + void sm3HashIsDeterministic() throws Exception { + String password = "mySecret123"; + String salt = "fixedSalt"; + String hash1 = SM3PasswordUtil.sm3Hash(password, salt); + String hash2 = SM3PasswordUtil.sm3Hash(password, salt); + assertEquals(hash1, hash2); + } + + @Test + @DisplayName("不同盐值导致不同哈希") + void differentSaltProducesDifferentHash() throws Exception { + String password = "mySecret123"; + String hash1 = SM3PasswordUtil.sm3Hash(password, "saltA"); + String hash2 = SM3PasswordUtil.sm3Hash(password, "saltB"); + assertNotEquals(hash1, hash2); + } + + @Test + @DisplayName("空密码和空盐值应正常处理") + void emptyPasswordAndSalt() throws Exception { + String hash = SM3PasswordUtil.sm3Hash("", ""); + assertNotNull(hash); + assertEquals(64, hash.length()); + } + + @Test + @DisplayName("createPassword 应生成包含盐值和密码哈希的结果") + void createPasswordReturnsPasswordResult() throws Exception { + String plainPassword = "userPassword123"; + PasswordResult result = SM3PasswordUtil.createPassword(plainPassword); + + assertNotNull(result); + assertNotNull(result.getPasswordHash()); + assertNotNull(result.getSalt()); + assertEquals(64, result.getPasswordHash().length()); + assertTrue(result.getSalt().startsWith("SM3_")); + assertEquals(16 + 4, result.getSalt().length()); // "SM3_" + 16 字符 + } + + @Test + @DisplayName("createPassword 产生的盐值唯一性") + void createPasswordGeneratesUniqueSalt() throws Exception { + PasswordResult result1 = SM3PasswordUtil.createPassword("pwd1"); + PasswordResult result2 = SM3PasswordUtil.createPassword("pwd2"); + assertNotEquals(result1.getSalt(), result2.getSalt()); + } + + @Test + @DisplayName("verifyPassword 应正确验证正确密码") + void verifyPasswordSucceedsForCorrectPassword() throws Exception { + String plainPassword = "correctPassword"; + PasswordResult result = SM3PasswordUtil.createPassword(plainPassword); + + boolean isValid = SM3PasswordUtil.verifyPassword(plainPassword, result.getPasswordHash(), result.getSalt()); + assertTrue(isValid); + } + + @Test + @DisplayName("verifyPassword 应拒绝错误密码") + void verifyPasswordFailsForWrongPassword() throws Exception { + String original = "realPassword"; + PasswordResult result = SM3PasswordUtil.createPassword(original); + + boolean isValid = SM3PasswordUtil.verifyPassword("wrongPassword", result.getPasswordHash(), result.getSalt()); + assertFalse(isValid); + } + + @Test + @DisplayName("verifyPassword 应拒绝使用错误的盐值") + void verifyPasswordFailsWithWrongSalt() throws Exception { + String password = "myPassword"; + PasswordResult result = SM3PasswordUtil.createPassword(password); + + // 使用不同的盐值(但相同哈希)应失败 + boolean isValid = SM3PasswordUtil.verifyPassword(password, result.getPasswordHash(), "SM3_wrongSalt123456"); + assertFalse(isValid); + } + + @Test + @DisplayName("verifyPassword 对空密码和空盐值应正常验证") + void verifyPasswordWithEmptyValues() throws Exception { + String emptyPwd = ""; + PasswordResult result = SM3PasswordUtil.createPassword(emptyPwd); + + assertTrue(SM3PasswordUtil.verifyPassword(emptyPwd, result.getPasswordHash(), result.getSalt())); + assertFalse(SM3PasswordUtil.verifyPassword("nonempty", result.getPasswordHash(), result.getSalt())); + } + + @Test + @DisplayName("密码包含特殊字符时验证正确") + void verifyPasswordWithSpecialCharacters() throws Exception { + String password = "P@ssw0rd!$%^&*()_+"; + PasswordResult result = SM3PasswordUtil.createPassword(password); + + assertTrue(SM3PasswordUtil.verifyPassword(password, result.getPasswordHash(), result.getSalt())); + } + + @Test + @DisplayName("长密码(超过1000字符)也能正常处理") + void longPassword() throws Exception { + String longPassword = "A".repeat(2000); + PasswordResult result = SM3PasswordUtil.createPassword(longPassword); + + assertTrue(SM3PasswordUtil.verifyPassword(longPassword, result.getPasswordHash(), result.getSalt())); + } + + @Test + @DisplayName("盐值前缀固定为 SM3_") + void saltPrefixIsCorrect() throws Exception { + PasswordResult result = SM3PasswordUtil.createPassword("any"); + assertTrue(result.getSalt().startsWith("SM3_")); + assertEquals(20, result.getSalt().length()); // "SM3_" (4) + 16 = 20 + } + + @Test + @DisplayName("sm3Hash 传入 null 应抛出 NullPointerException") + void sm3HashWithNullData_ThrowsNullPointerException() { + assertThrows(IllegalArgumentException.class, () -> { + SM3PasswordUtil.sm3Hash(null, "salt"); + }); + } + + @Test + @DisplayName("sm3Hash 传入 null 盐值应抛出 NullPointerException") + void sm3HashWithNullSalt_ThrowsNullPointerException() { + assertThrows(IllegalArgumentException.class, () -> { + SM3PasswordUtil.sm3Hash("data", null); + }); + } + + @Test + @DisplayName("verifyPassword 传入 null 参数应抛出异常") + void verifyPasswordWithNullParams_ThrowsException() { + // 先创建一个合法结果 + assertThrows(Exception.class, () -> { + SM3PasswordUtil.verifyPassword(null, "hash", "salt"); + }); + assertThrows(Exception.class, () -> { + SM3PasswordUtil.verifyPassword("pwd", null, "salt"); + }); + assertThrows(Exception.class, () -> { + SM3PasswordUtil.verifyPassword("pwd", "hash", null); + }); + } + + // ===================== 盐值格式与唯一性加强测试 ===================== + + /** + * createPassword 每次生成的盐值长度固定为 20 字符(SM3_ + 16位随机),且格式正确 + * @throws Exception + */ + @RepeatedTest(10) + @DisplayName("createPassword 每次生成的盐值长度固定为 20 字符 (SM3_ + 16位随机)") + void createPassword_AlwaysGeneratesFixedLengthSalt() throws Exception { + PasswordResult result = SM3PasswordUtil.createPassword("any"); + assertEquals(20, result.getSalt().length()); + assertTrue(result.getSalt().matches("SM3_[a-f0-9]{16}")); + } + + @Test + @DisplayName("相同明文密码,不同调用产生的哈希值不同(因为盐值不同)") + void samePlaintext_DifferentHash() throws Exception { + String password = "SamePassword123"; + PasswordResult result1 = SM3PasswordUtil.createPassword(password); + PasswordResult result2 = SM3PasswordUtil.createPassword(password); + assertNotEquals(result1.getPasswordHash(), result2.getPasswordHash()); + assertNotEquals(result1.getSalt(), result2.getSalt()); + } + + // ===================== 字符集/Unicode 测试 ===================== + + /** + * 验证包含 Unicode 字符(如中文、emoji)的密码能够正确处理和验证 + * @throws Exception + */ + @Test + @DisplayName("密码包含中文和 emoji 时验证正确") + void passwordWithUnicode_VerifiesCorrectly() throws Exception { + String password = "密码🔐安全$%^"; + PasswordResult result = SM3PasswordUtil.createPassword(password); + assertTrue(SM3PasswordUtil.verifyPassword(password, result.getPasswordHash(), result.getSalt())); + } + + /** + * 验证包含控制字符(如换行符、制表符)的密码能够正确处理和验证 + * @throws Exception + */ + + @Test + @DisplayName("密码包含换行符、制表符等控制字符也能正确处理") + void passwordWithControlCharacters() throws Exception { + String password = "line1\nline2\t\r\n"; + PasswordResult result = SM3PasswordUtil.createPassword(password); + assertTrue(SM3PasswordUtil.verifyPassword(password, result.getPasswordHash(), result.getSalt())); + } + + // ===================== 哈希输出格式测试 ===================== + + /** + * 验证 sm3Hash 输出的哈希值仅包含小写十六进制字符,并且长度正确 + * @throws Exception + */ + + @Test + @DisplayName("sm3Hash 输出仅包含小写十六进制字符") + void sm3HashOutputIsLowercaseHex() throws Exception { + String hash = SM3PasswordUtil.sm3Hash("test", "salt"); + assertTrue(hash.matches("^[0-9a-f]{64}$")); + // 确保没有大写字母 + assertEquals(hash, hash.toLowerCase()); + } + + @Test + @DisplayName("bytesToHex 转换与常用库结果一致(间接验证)") + void bytesToHexConvertsCorrectly() throws Exception { + // 已知 SM3("", "") 的哈希值(可使用标准工具预先计算) + // 这里仅验证格式正确,不依赖外部值 + String hash = SM3PasswordUtil.sm3Hash("", ""); + assertEquals(64, hash.length()); + // 再次调用相同输入,结果必须一致 + String hash2 = SM3PasswordUtil.sm3Hash("", ""); + assertEquals(hash, hash2); + } + + // ===================== 性能测试(轻量) ===================== + + /** + * 验证批量创建和验证密码的性能,确保在合理时间内完成(例如 1000 次操作不超过 2 秒) + * @throws Exception + */ + + @Test + @DisplayName("批量创建并验证 1000 个密码应在 2 秒内完成") + @Timeout(2) + void performance_BatchCreateAndVerify() throws Exception { + int iterations = 1000; + for (int i = 0; i < iterations; i++) { + String plain = "user" + i + "P@ss"; + PasswordResult result = SM3PasswordUtil.createPassword(plain); + assertTrue(SM3PasswordUtil.verifyPassword(plain, result.getPasswordHash(), result.getSalt())); + } + } + + // ===================== 完整性/防篡改测试 ===================== + + /** + * 修改哈希值的任意一位字符都应导致验证失败 + * @throws Exception + */ + @Test + @DisplayName("修改哈希任意一位字符会导致验证失败") + void tamperedHash_VerificationFails() throws Exception { + String password = "integrityTest"; + PasswordResult result = SM3PasswordUtil.createPassword(password); + String originalHash = result.getPasswordHash(); + // 修改哈希最后一个字符 + char[] chars = originalHash.toCharArray(); + chars[chars.length - 1] = (chars[chars.length - 1] == '0') ? '1' : '0'; + String tamperedHash = new String(chars); + + assertFalse(SM3PasswordUtil.verifyPassword(password, tamperedHash, result.getSalt())); + } + + @Test + @DisplayName("使用错误的盐值(即使长度相同)验证失败") + void wrongSaltEvenWithSameLength_VerificationFails() throws Exception { + String password = "saltTest"; + PasswordResult result = SM3PasswordUtil.createPassword(password); + String wrongSalt = "SM3_" + "ffffffffffffffff"; // 16个f + assertFalse(SM3PasswordUtil.verifyPassword(password, result.getPasswordHash(), wrongSalt)); + } + + // ===================== 重复调用稳定性测试 ===================== + + @RepeatedTest(5) + @DisplayName("多次调用 createPassword 和 verifyPassword 不会改变内部状态") + void repeatedCallsAreStable() throws Exception { + String pwd = "stablePassword"; + PasswordResult result = SM3PasswordUtil.createPassword(pwd); + for (int i = 0; i < 10; i++) { + assertTrue(SM3PasswordUtil.verifyPassword(pwd, result.getPasswordHash(), result.getSalt())); + } + } + // 1. 验证盐值前缀必须为 SM3_,且不能手动构造缺少前缀的盐值 + @Test + @DisplayName("自定义盐值缺少前缀时验证失败(因为createPassword生成的盐值始终带前缀)") + void customSaltWithoutPrefix_ShouldBeConsistent() throws Exception { + String plain = "testPwd"; + String customSalt = "NoPrefix12345678"; // 缺少 SM3_ 前缀 + // 使用相同盐值时,哈希计算应一致(不关心前缀,由调用方控制) + String hash1 = SM3PasswordUtil.sm3Hash(plain, customSalt); + String hash2 = SM3PasswordUtil.sm3Hash(plain, customSalt); + assertEquals(hash1, hash2); + // 但 verifyPassword 期望 storedHash 与 相同盐值得到的结果一致 + assertTrue(SM3PasswordUtil.verifyPassword(plain, hash1, customSalt)); + } + + // 2. 盐值超长(超过常规长度)仍能正常工作 + @Test + @DisplayName("超长盐值(100+字符)也能正常计算哈希") + void veryLongSaltWorks() throws Exception { + String password = "securePwd"; + String longSalt = "SM3_" + "a".repeat(200); + String hash = SM3PasswordUtil.sm3Hash(password, longSalt); + assertEquals(64, hash.length()); + assertTrue(SM3PasswordUtil.verifyPassword(password, hash, longSalt)); + } + + // 3. 验证不同密码产生相同哈希的概率极低(简单碰撞检测) + @Test + @DisplayName("不同密码(即使相似)应产生不同的哈希值") + void differentPasswordsProduceDifferentHashes() throws Exception { + String salt = "SM3_fixedSaltTest"; + String pwd1 = "password123"; + String pwd2 = "password124"; // 仅最后一位不同 + String hash1 = SM3PasswordUtil.sm3Hash(pwd1, salt); + String hash2 = SM3PasswordUtil.sm3Hash(pwd2, salt); + assertNotEquals(hash1, hash2); + + // 更接近的情况:大小写不同 + String pwd3 = "Password123"; + String hash3 = SM3PasswordUtil.sm3Hash(pwd3, salt); + assertNotEquals(hash1, hash3); + } + + // 4. 密码前后包含空白字符时,应保持原样处理(不应自动 trim) + @Test + @DisplayName("密码前后空白字符应被保留(不会自动去除)") + void whitespaceInPasswordIsPreserved() throws Exception { + String pwdWithSpace = " space "; + String pwdTrimmed = "space"; + PasswordResult result = SM3PasswordUtil.createPassword(pwdWithSpace); + // 带空格的密码哈希应当与去掉空格的密码哈希不同 + assertFalse(SM3PasswordUtil.verifyPassword(pwdTrimmed, result.getPasswordHash(), result.getSalt())); + // 原样验证应成功 + assertTrue(SM3PasswordUtil.verifyPassword(pwdWithSpace, result.getPasswordHash(), result.getSalt())); + + // 额外:首尾换行符 + String pwdWithNewline = "\nnewline\n"; + PasswordResult result2 = SM3PasswordUtil.createPassword(pwdWithNewline); + assertTrue(SM3PasswordUtil.verifyPassword(pwdWithNewline, result2.getPasswordHash(), result2.getSalt())); + assertFalse(SM3PasswordUtil.verifyPassword("newline", result2.getPasswordHash(), result2.getSalt())); + } + + + /** + * 连续创建和验证大量密码(500次)不应导致性能下降或异常 + * @throws Exception + */ + @RepeatedTest(5) + @DisplayName("反复创建和验证大量密码(500次)不应导致性能下降或异常") + void repeatedLargeBatchOperations() throws Exception { + int batchSize = 500; + for (int round = 0; round < 3; round++) { + String[] passwords = new String[batchSize]; + PasswordResult[] results = new PasswordResult[batchSize]; + for (int i = 0; i < batchSize; i++) { + passwords[i] = "user" + i + "_P@ss"; + results[i] = SM3PasswordUtil.createPassword(passwords[i]); + } + for (int i = 0; i < batchSize; i++) { + assertTrue(SM3PasswordUtil.verifyPassword(passwords[i], results[i].getPasswordHash(), results[i].getSalt())); + } + } + } + + // 1. 并发环境下密码创建和验证的安全性 + @Test + @DisplayName("多线程并发创建并验证密码应无异常且结果正确") + void concurrentCreateAndVerify() throws Exception { + int threadCount = 10; + int iterationsPerThread = 100; + Exception[] exceptions = new Exception[1]; + Runnable task = () -> { + try { + for (int i = 0; i < iterationsPerThread; i++) { + String pwd = "pwd" + Thread.currentThread().getId() + "_" + i; + PasswordResult result = SM3PasswordUtil.createPassword(pwd); + assertTrue(SM3PasswordUtil.verifyPassword(pwd, result.getPasswordHash(), result.getSalt())); + } + } catch (Exception e) { + synchronized (exceptions) { + exceptions[0] = e; + } + } + }; + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threadCount; i++) { + threads[i] = new Thread(task); + threads[i].start(); + } + for (Thread t : threads) { + t.join(); + } + assertNull(exceptions[0], "并发执行时不应抛出异常"); + } + + // 2. 大量生成盐值,检验唯一性(碰撞概率极低) + @RepeatedTest(3) + @DisplayName("生成 10000 个盐值,应全部唯一") + void saltUniquenessUnderMassGeneration() throws Exception { + int count = 10000; + java.util.Set salts = new java.util.HashSet<>(); + for (int i = 0; i < count; i++) { + PasswordResult result = SM3PasswordUtil.createPassword("any"); + salts.add(result.getSalt()); + } + assertEquals(count, salts.size(), "所有盐值应唯一"); + } + + // 3. 已知明文-哈希对的一致性验证(使用预计算的固定值,确保算法未变更) + @Test + @DisplayName("SM3 算法与标准实现一致性(基于已知输入输出)") + void sm3ConsistencyWithKnownVectors() throws Exception { + // 使用已知的 SM3 测试向量(根据国密标准) + // 例如:输入 "abc" 的 SM3 哈希结果为(标准值,需要提前计算或从规范获取) + // 由于环境可能不同,这里仅做相对一致性检查:相同输入多次调用结果一致 + String input = "abc"; + String salt = ""; + String hash1 = SM3PasswordUtil.sm3Hash(input, salt); + String hash2 = SM3PasswordUtil.sm3Hash(input, salt); + assertEquals(hash1, hash2, "相同输入应得到相同哈希"); + + // 可选:如果有预置的权威向量,可取消注释验证 + // String expected = "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0"; + // assertEquals(expected, hash1); + } + + // 4. 密码为空字符串时的特殊处理(盐值生成和验证) + @Test + @DisplayName("空密码应能被正常创建并验证") + void emptyPasswordIsValid() throws Exception { + String emptyPwd = ""; + PasswordResult result = SM3PasswordUtil.createPassword(emptyPwd); + assertNotNull(result.getPasswordHash()); + assertNotNull(result.getSalt()); + assertTrue(SM3PasswordUtil.verifyPassword(emptyPwd, result.getPasswordHash(), result.getSalt())); + // 验证空格字符串不应被当做空密码 + PasswordResult result2 = SM3PasswordUtil.createPassword(" "); + assertFalse(SM3PasswordUtil.verifyPassword(emptyPwd, result2.getPasswordHash(), result2.getSalt())); + } + + // 5. sm3Hash 方法对极大数据量(如 10MB 字符串)的处理能力 + @Test + @DisplayName("sm3Hash 能处理 10MB 大小的数据而不抛出异常") + @Timeout(1) + void sm3HashHandlesLargeData() throws Exception { + int mb = 10; + char[] data = new char[mb * 1024 * 1024]; + java.util.Arrays.fill(data, 'A'); + String largeData = new String(data); + String salt = "testSalt"; + // 不应抛出 OutOfMemoryError 或其他异常 + String hash = SM3PasswordUtil.sm3Hash(largeData, salt); + assertEquals(64, hash.length()); + // 验证一致性:再次计算应相同 + String hash2 = SM3PasswordUtil.sm3Hash(largeData, salt); + assertEquals(hash, hash2); + } +} \ No newline at end of file From 8304a98743de061ee14ce63470fc2e89ea727401 Mon Sep 17 00:00:00 2001 From: msslulu <1484036491@qq.com> Date: Thu, 30 Apr 2026 02:25:33 -0700 Subject: [PATCH 2/2] fix:update JwtUtil generateToken --- .../main/java/com/tinyengine/it/login/utils/JwtUtil.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/com/tinyengine/it/login/utils/JwtUtil.java b/base/src/main/java/com/tinyengine/it/login/utils/JwtUtil.java index 148ce10a..437c7721 100644 --- a/base/src/main/java/com/tinyengine/it/login/utils/JwtUtil.java +++ b/base/src/main/java/com/tinyengine/it/login/utils/JwtUtil.java @@ -97,7 +97,9 @@ public static SecretKey getSecretKey() { */ public String generateToken(String username, String roles, String userId, Object tenants, Integer platformId) { - + if(username==null ){ + throw new IllegalArgumentException("Username must not be null"); + } Map claims = new HashMap<>(); claims.put("username", username); claims.put("roles", roles); @@ -132,9 +134,6 @@ public String generateToken(String username, String roles, String userId, */ public String generateToken(String username, String roles, String userId, List tenants, Integer platformId) { - if(username==null ){ - throw new IllegalArgumentException("Username must not be null"); - } return generateToken(username, roles, userId, (Object) tenants, platformId); }