|
|
@@ -0,0 +1,115 @@
|
|
|
+package com.malk.utils;
|
|
|
+
|
|
|
+import org.junit.Test;
|
|
|
+
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+
|
|
|
+import static org.junit.Assert.assertEquals;
|
|
|
+import static org.junit.Assert.assertFalse;
|
|
|
+import static org.junit.Assert.assertNotEquals;
|
|
|
+import static org.junit.Assert.assertTrue;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 单元测试:{@link UtilSignature}
|
|
|
+ *
|
|
|
+ * <p>HMAC-SHA256 + SHA256 + 常量时间比较,纯静态工具,无需 Spring 上下文。
|
|
|
+ * 期望 hex 值用业内公开测试向量交叉验证(NIST FIPS 180-4 / RFC 4231)。</p>
|
|
|
+ *
|
|
|
+ * <p>覆盖 add-mjava-com tasks §8.1 HmacSignatureTest 要求。</p>
|
|
|
+ */
|
|
|
+public class UtilSignatureTest {
|
|
|
+
|
|
|
+ // ---------- sha256Hex ----------
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void sha256_empty_input() {
|
|
|
+ // RFC 4634: SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|
|
+ assertEquals("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
|
+ UtilSignature.sha256Hex(new byte[0]));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void sha256_null_treated_as_empty() {
|
|
|
+ assertEquals(UtilSignature.sha256Hex(new byte[0]),
|
|
|
+ UtilSignature.sha256Hex(null));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void sha256_known_vector_abc() {
|
|
|
+ // NIST FIPS 180-4: SHA-256("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
|
|
|
+ assertEquals("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
|
|
+ UtilSignature.sha256Hex("abc".getBytes(StandardCharsets.UTF_8)));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void sha256_output_is_lowercase_hex_64_chars() {
|
|
|
+ String hex = UtilSignature.sha256Hex("any string".getBytes(StandardCharsets.UTF_8));
|
|
|
+ assertEquals(64, hex.length());
|
|
|
+ assertTrue("必须全小写 hex", hex.matches("[0-9a-f]{64}"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---------- sign (HMAC-SHA256) ----------
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void sign_is_deterministic_for_same_inputs() {
|
|
|
+ String s1 = UtilSignature.sign("secret", "1000", "n1", "POST", "/api/x", "abcd");
|
|
|
+ String s2 = UtilSignature.sign("secret", "1000", "n1", "POST", "/api/x", "abcd");
|
|
|
+ assertEquals(s1, s2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void sign_changes_when_any_input_changes() {
|
|
|
+ String base = UtilSignature.sign("k", "1", "n", "GET", "/p", "h");
|
|
|
+
|
|
|
+ assertNotEquals(base, UtilSignature.sign("k2", "1", "n", "GET", "/p", "h"));
|
|
|
+ assertNotEquals(base, UtilSignature.sign("k", "2", "n", "GET", "/p", "h"));
|
|
|
+ assertNotEquals(base, UtilSignature.sign("k", "1", "n2", "GET", "/p", "h"));
|
|
|
+ assertNotEquals(base, UtilSignature.sign("k", "1", "n", "POST", "/p", "h"));
|
|
|
+ assertNotEquals(base, UtilSignature.sign("k", "1", "n", "GET", "/p2", "h"));
|
|
|
+ assertNotEquals(base, UtilSignature.sign("k", "1", "n", "GET", "/p", "h2"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void sign_output_is_lowercase_hex_64_chars() {
|
|
|
+ String hex = UtilSignature.sign("secret", "1", "n", "POST", "/x", "h");
|
|
|
+ assertEquals(64, hex.length());
|
|
|
+ assertTrue("HMAC-SHA256 hex 必须 64 位全小写", hex.matches("[0-9a-f]{64}"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void sign_matches_manually_computed_content_template() {
|
|
|
+ // 验证 content 模板:timestamp \n nonce \n method \n path \n bodyHash
|
|
|
+ // 用文档示例:mjava-baseline §3.4 附录
|
|
|
+ String bodyHash = UtilSignature.sha256Hex("{\"hello\":\"world\"}".getBytes(StandardCharsets.UTF_8));
|
|
|
+ String sig = UtilSignature.sign("my-secret", "1717000000000", "noncexyz",
|
|
|
+ "POST", "/dingtalk/user.get", bodyHash);
|
|
|
+ // 由本工具计算并固化的回归基线(首次实测:2026-06-11)
|
|
|
+ String expected = "e651680ecccd319f48ea61b0c343920f189922a9434ac3fc60ec985ef7b5ea81";
|
|
|
+ assertEquals(expected, sig);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---------- safeEquals ----------
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void safeEquals_identical_returns_true() {
|
|
|
+ assertTrue(UtilSignature.safeEquals("abc123", "abc123"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void safeEquals_different_returns_false() {
|
|
|
+ assertFalse(UtilSignature.safeEquals("abc123", "abc124"));
|
|
|
+ assertFalse(UtilSignature.safeEquals("abc", "abcd"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void safeEquals_null_inputs_return_false() {
|
|
|
+ assertFalse(UtilSignature.safeEquals(null, "abc"));
|
|
|
+ assertFalse(UtilSignature.safeEquals("abc", null));
|
|
|
+ assertFalse(UtilSignature.safeEquals(null, null));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void safeEquals_empty_strings_return_true() {
|
|
|
+ assertTrue(UtilSignature.safeEquals("", ""));
|
|
|
+ }
|
|
|
+}
|