Java实现HMAC-SM3消息认证码:轻量级数据完整性校验与来源验证方案
1. 项目概述为什么需要“报文口令”的SM3签名方案在金融、政务、物联网这些对数据完整性和来源认证有极高要求的领域我们经常需要处理一种场景如何证明一份数据比如一份订单、一条指令、一个配置文件在传输过程中没有被篡改并且确实来自合法的发送方单纯对报文内容做哈希摘要只能解决篡改问题但无法验证来源。而传统的数字签名如SM2虽然能同时解决这两个问题但其计算开销相对较大在某些高频或资源受限的场景下可能成为瓶颈。这时“报文口令”的SM3签名方案就成了一种非常实用的轻量级选择。这里的“口令”并非用户登录密码而是一个预先在通信双方之间安全共享的密钥Secret Key。其核心思想是将原始报文Message和这个共享的口令Key组合起来一起送入SM3哈希算法进行计算生成一个唯一的“签名摘要”。接收方在拿到报文后使用相同的口令和SM3算法重新计算摘要并与收到的摘要进行比对。如果一致则证明报文在传输过程中是完整的且发送方拥有正确的口令即通过了认证。这个方案本质上是基于密钥的哈希消息认证码HMAC思想而SM3是我国商用密码算法标准中的哈希算法其安全强度与SHA-256相当。用Java来实现它意味着我们可以将这套安全机制无缝集成到各种企业级应用、微服务或Android应用中。最近在排查一些数据一致性问题和设计轻量级API鉴权时我发现很多团队还在用MD5或简单拼接哈希安全隐患不小。所以今天我就把从原理到踩坑、从代码到优化的完整实现方案梳理出来希望能帮你构建更可靠的数据安全防线。2. 核心原理与方案设计拆解2.1 SM3算法与HMAC机制回顾SM3算法是一种密码杂凑算法输入任意长度的数据输出一个固定长度256位即32字节的哈希值。它具有抗碰撞性难以找到两个不同的输入得到相同输出和抗第二原像攻击给定一个输入难以找到另一个输入得到相同输出的特性是生成数据“指纹”的可靠工具。但是单纯的SM3(报文)存在一个缺陷任何人都可以计算这个哈希值。如果攻击者截获了报文和其SM3哈希他可以篡改报文后重新计算并替换哈希值接收方无法察觉。HMACHash-based Message Authentication Code机制就是为了解决这个问题而生的。其核心公式可以简化为HMAC-SM3(Key, Message) SM3( (Key ⊕ opad) || SM3( (Key ⊕ ipad) || Message ) )。其中Key是共享密钥。opadouter pad是字节0x5c重复填充至分组长度的常量。ipadinner pad是字节0x36重复填充至分组长度的常量。||表示拼接操作。这个结构通过让密钥与两个不同的常量进行异或并与报文进行两次哈希运算确保了即使知道了报文和最终的HMAC值在不知道密钥的情况下也无法伪造出对应新报文的合法HMAC值。我们的“报文口令”签名本质上就是要实现一个HMAC-SM3。2.2 方案整体设计思路我们的目标是构建一个健壮的、易于集成的Java组件。设计时需要重点考虑以下几点密钥管理口令密钥不能硬编码在代码中。我们需要设计从安全配置中心、环境变量或加密存储中读取密钥的接口。编码处理报文和口令可能是字符串如JSON、XML也可能是字节数组如图片、文件。算法核心处理的是字节因此需要统一、明确的字符编码如UTF-8转换逻辑。输出格式生成的摘要通常是二进制字节数组但为了方便在HTTP头、日志或数据库中存储传输通常需要转换为十六进制Hex字符串或Base64字符串。性能与线程安全SM3计算对象MessageDigest的创建成本较高。在高并发场景下我们需要考虑复用或使用线程本地存储ThreadLocal来优化性能并确保线程安全。错误处理对无效输入空报文、空密钥、不支持的编码、算法初始化失败等情况应有清晰的异常提示。基于以上考虑我设计的方案包含以下几个核心类SM3DigestGenerator: 核心生成器封装HMAC-SM3逻辑。SM3Signer: 面向业务的门面类提供简单的字符串/字节数组签名方法。KeyProvider: 密钥提供者接口用于解耦密钥获取逻辑。配套的工具类用于处理Hex和Base64编码。3. 核心工具类与密钥管理实现在动手实现HMAC-SM3之前我们需要准备一些基础设施。首先是编码解码工具这是避免“乱码坑”的关键。3.1 编码工具类实现摘要输出通常是二进制字节数组但文本协议如HTTP需要字符串形式。这里提供Hex和Base64两种最常用的格式。import java.util.Base64; public class CodecUtil { private static final char[] HEX_CHARS 0123456789abcdef.toCharArray(); /** * 将字节数组转换为小写十六进制字符串。 * 自己实现比依赖第三方库更轻量且避免编码问题。 */ public static String encodeHex(byte[] data) { if (data null) { return null; } StringBuilder hexString new StringBuilder(data.length * 2); for (byte b : data) { // 一个字节转换成两个十六进制字符 int v b 0xFF; // 确保是无符号值 hexString.append(HEX_CHARS[v 4]); // 高4位 hexString.append(HEX_CHARS[v 0x0F]); // 低4位 } return hexString.toString(); } /** * 将十六进制字符串转换回字节数组。 * 注意处理可能存在的空格或0x前缀这里简单处理实际可根据需求增强。 */ public static byte[] decodeHex(String hexString) { if (hexString null || hexString.isEmpty()) { return new byte[0]; } String cleanHex hexString.toLowerCase().replace(0x, ).replace( , ); if (cleanHex.length() % 2 ! 0) { throw new IllegalArgumentException(Invalid hex string length.); } byte[] data new byte[cleanHex.length() / 2]; for (int i 0; i data.length; i) { int high Character.digit(cleanHex.charAt(i * 2), 16); int low Character.digit(cleanHex.charAt(i * 2 1), 16); if (high -1 || low -1) { throw new IllegalArgumentException(Invalid hex character.); } data[i] (byte) ((high 4) | low); } return data; } /** * 使用JDK标准Base64编码URL安全无换行。 */ public static String encodeBase64(byte[] data) { if (data null) { return null; } return Base64.getEncoder().withoutPadding().encodeToString(data); } /** * Base64解码。 */ public static byte[] decodeBase64(String base64String) { if (base64String null || base64String.isEmpty()) { return new byte[0]; } return Base64.getDecoder().decode(base64String); } }注意在金融等规范场景十六进制字母大小写可能有要求通常大写。上述实现输出小写如需大写可将HEX_CHARS改为大写字母或使用Integer.toHexString()并补零但性能稍差。统一规范很重要。3.2 可扩展的密钥提供者接口硬编码密钥是安全大忌。我们应该定义一个接口让业务方决定密钥从哪里来。/** * 密钥提供者接口。 * 实现类可以从配置文件、环境变量、数据库、密钥管理系统KMS或加密机中获取密钥。 */ public interface KeyProvider { /** * 获取密钥的字节数组形式。 * return 共享密钥的字节数组。不应返回null可返回空数组。 * throws SecurityException 当无法获取密钥时抛出如配置缺失、KMS访问失败。 */ byte[] getKey() throws SecurityException; }然后提供几个常用的实现/** * 从系统环境变量获取密钥。 */ public class EnvKeyProvider implements KeyProvider { private final String envVarName; public EnvKeyProvider(String envVarName) { this.envVarName envVarName; } Override public byte[] getKey() { String key System.getenv(envVarName); if (key null || key.trim().isEmpty()) { throw new SecurityException(Environment variable envVarName not set or empty.); } // 假设环境变量中存储的是Base64或Hex编码的密钥字符串 // 这里简单处理为直接获取UTF-8字节实际应根据存储格式调用CodecUtil解码 return key.getBytes(StandardCharsets.UTF_8); } } /** * 从应用配置如Spring的Value获取密钥的简单实现。 */ public class StaticKeyProvider implements KeyProvider { private final byte[] keyBytes; public StaticKeyProvider(String keyStr) { this(keyStr, StandardCharsets.UTF_8); } public StaticKeyProvider(String keyStr, Charset charset) { if (keyStr null) { throw new IllegalArgumentException(Key string cannot be null.); } this.keyBytes keyStr.getBytes(charset); } public StaticKeyProvider(byte[] keyBytes) { if (keyBytes null) { throw new IllegalArgumentException(Key bytes cannot be null.); } this.keyBytes keyBytes.clone(); // 防御性拷贝 } Override public byte[] getKey() { return keyBytes.clone(); // 每次返回拷贝防止外部修改 } }实操心得在StaticKeyProvider中对传入的字节数组进行克隆clone()并在getKey()时返回克隆体这是一个重要的安全实践。这可以防止调用方在获取密钥引用后意外或恶意地修改底层数组破坏了密钥的机密性。虽然对于字符串Java的不可变性提供了保护但对于字节数组我们必须保持这种防御性编程习惯。4. HMAC-SM3核心生成器实现这是整个方案的心脏。我们将严格遵循RFC 2104中定义的HMAC结构来实现但使用SM3作为底层哈希函数。4.1 SM3DigestGenerator 核心代码import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class SM3DigestGenerator { private static final String SM3_ALGORITHM SM3; private static final int BLOCK_SIZE 64; // SM3的分组长度是64字节512位与SHA-256相同。 private final ThreadLocalMessageDigest sm3DigestThreadLocal; public SM3DigestGenerator() { this.sm3DigestThreadLocal ThreadLocal.withInitial(() - { try { // 获取SM3算法实例 return MessageDigest.getInstance(SM3_ALGORITHM); } catch (NoSuchAlgorithmException e) { // 如果抛出此异常说明运行环境未提供SM3算法实现。 // 需要引入Bouncy Castle等安全提供者并在启动时加载Security.addProvider(new BouncyCastleProvider()); throw new RuntimeException(SM3 algorithm not available. Please ensure a JCE provider like BouncyCastle is installed and registered., e); } }); } /** * 计算HMAC-SM3签名。 * * param key 共享密钥 * param message 原始报文 * return HMAC-SM3摘要的二进制字节数组32字节 */ public byte[] generateHmac(byte[] key, byte[] message) { if (key null) { throw new IllegalArgumentException(Key cannot be null.); } if (message null) { message new byte[0]; // 允许空报文按空字节数组处理 } byte[] processedKey processKey(key); MessageDigest digest sm3DigestThreadLocal.get(); digest.reset(); // 必须重置因为ThreadLocal是复用的 // 计算 innerHash: SM3((key ⊕ ipad) || message) digest.update(processedKey); for (int i 0; i processedKey.length; i) { processedKey[i] ^ 0x36; // ipad 0x36 } digest.update(processedKey); digest.update(message); byte[] innerHash digest.digest(); // 恢复 processedKey 为原始状态然后计算 outerHash for (int i 0; i processedKey.length; i) { processedKey[i] ^ (0x36 ^ 0x5c); // 先异或0x36恢复原值再异或0x5c } digest.reset(); digest.update(processedKey); digest.update(innerHash); return digest.digest(); } /** * 处理密钥如果密钥长度大于分组长度则先对其做SM3哈希使其缩短为摘要长度32字节。 * 如果密钥长度小于分组长度则用0x00填充至分组长度。 * * param key 原始密钥 * return 处理后的密钥长度为BLOCK_SIZE */ private byte[] processKey(byte[] key) { byte[] processedKey new byte[BLOCK_SIZE]; if (key.length BLOCK_SIZE) { // 密钥太长先哈希 MessageDigest digest sm3DigestThreadLocal.get(); digest.reset(); digest.update(key); byte[] hashedKey digest.digest(); System.arraycopy(hashedKey, 0, processedKey, 0, hashedKey.length); // hashedKey只有32字节后面32字节已经是0数组初始化默认值 } else if (key.length BLOCK_SIZE) { // 密钥较短用0填充 System.arraycopy(key, 0, processedKey, 0, key.length); // 剩余部分保持为0初始化值 } else { // 密钥长度正好等于分组长度直接使用 System.arraycopy(key, 0, processedKey, 0, BLOCK_SIZE); } return processedKey; } }4.2 关键实现细节剖析ThreadLocal优化MessageDigest.getInstance(SM3)是一个相对耗时的操作。在高并发场景下为每个请求都创建新的实例会带来不必要的开销。使用ThreadLocal可以为每个线程缓存一个独立的MessageDigest实例避免了重复创建的消耗同时也保证了线程安全因为每个线程操作的是自己的实例。注意在每次使用前必须调用digest.reset()来清除之前计算的状态。密钥处理processKey这是HMAC标准定义的关键步骤不能省略。密钥过长64字节直接用长密钥与ipad/opad异或可能会削弱安全性。标准做法是先对长密钥做一次SM3哈希将其压缩为32字节的摘要再用这个摘要填充到64字节后32字节补0。密钥过短64字节在密钥后面补00x00直到64字节。这确保了所有密钥在参与异或运算前长度一致。为什么要异或ipad和opad直接拼接key和message然后哈希即SM3(key||message)是一种朴素的做法但存在一些密码学上的潜在弱点。通过异或不同的常量ipad0x36, opad0x5c相当于对密钥进行了“变换”再与报文进行嵌套哈希这种结构HMAC被证明是更安全的能够有效抵御某些类型的攻击。digest.update()与digest.digest()update方法可以多次调用用于追加数据特别适合处理流式数据或大文件。digest方法则完成最终计算并重置摘要对象。在我们的实现中先update处理后的密钥和报文再调用digest得到innerHash然后重置摘要对象再update密钥和innerHash最后digest得到最终结果。这个顺序不能错。5. 业务层门面与完整使用示例为了让业务代码调用更简洁我们创建一个SM3Signer门面类它整合了密钥提供、编码转换和核心计算。5.1 SM3Signer 门面类import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; public class SM3Signer { private final SM3DigestGenerator digestGenerator; private final KeyProvider keyProvider; private final Charset defaultCharset; public SM3Signer(KeyProvider keyProvider) { this(keyProvider, StandardCharsets.UTF_8); } public SM3Signer(KeyProvider keyProvider, Charset defaultCharset) { if (keyProvider null) { throw new IllegalArgumentException(KeyProvider cannot be null.); } this.digestGenerator new SM3DigestGenerator(); this.keyProvider keyProvider; this.defaultCharset defaultCharset ! null ? defaultCharset : StandardCharsets.UTF_8; } /** * 为字符串报文生成HMAC-SM3签名十六进制输出。 * param message 字符串报文 * return 十六进制格式的签名摘要 */ public String signToHex(String message) { return signToHex(message, defaultCharset); } public String signToHex(String message, Charset charset) { byte[] signature sign(message, charset); return CodecUtil.encodeHex(signature); } /** * 为字符串报文生成HMAC-SM3签名Base64输出。 * param message 字符串报文 * return Base64格式的签名摘要 */ public String signToBase64(String message) { return signToBase64(message, defaultCharset); } public String signToBase64(String message, Charset charset) { byte[] signature sign(message, charset); return CodecUtil.encodeBase64(signature); } /** * 为字节数组报文生成HMAC-SM3签名十六进制输出。 * param message 字节数组报文 * return 十六进制格式的签名摘要 */ public String signToHex(byte[] message) { byte[] signature sign(message); return CodecUtil.encodeHex(signature); } /** * 为字节数组报文生成HMAC-SM3签名原始字节输出。 * param message 字节数组报文 * return 签名摘要的字节数组 */ public byte[] sign(byte[] message) { byte[] key keyProvider.getKey(); return digestGenerator.generateHmac(key, message); } /** * 内部方法将字符串报文转换为字节后签名。 */ private byte[] sign(String message, Charset charset) { if (message null) { return sign(new byte[0]); } return sign(message.getBytes(charset)); } /** * 验证签名。 * param message 原始报文字符串 * param signature 待验证的签名十六进制字符串 * return 验证是否通过 */ public boolean verifyHex(String message, String signature) { return verifyHex(message, signature, defaultCharset); } public boolean verifyHex(String message, String signature, Charset charset) { String calculatedSig signToHex(message, charset); // 使用恒定时间比较防止时序攻击对于HMAC验证很重要 return constantTimeEquals(calculatedSig, signature); } /** * 验证签名。 * param message 原始报文字节数组 * param signature 待验证的签名字节数组 * return 验证是否通过 */ public boolean verify(byte[] message, byte[] signature) { byte[] calculatedSig sign(message); return constantTimeEquals(calculatedSig, signature); } /** * 恒定时间比较防止通过比较耗时推测签名正确与否的时序攻击。 */ private boolean constantTimeEquals(byte[] a, byte[] b) { if (a null || b null) { return false; } if (a.length ! b.length) { return false; } int result 0; for (int i 0; i a.length; i) { result | (a[i] ^ b[i]); } return result 0; } private boolean constantTimeEquals(String a, String b) { if (a null || b null) { return false; } // 先比较长度长度不同直接返回false但这不是恒定时间。 // 为了简单实现我们可以先转换为字符数组再比较但更严谨的做法是使用MessageDigest.isEqual。 // 这里使用JDK提供的安全比较方法Java 1.6 return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8)); } }5.2 完整集成使用示例假设我们有一个Spring Boot的支付回调接口需要验证通知的签名。步骤1配置密钥。在application.yml中配置或从环境变量APP_SM3_KEY读取。app: security: sm3-key: your_shared_secret_key_here_at_least_16_bytes步骤2创建配置类与Bean。Configuration public class Sm3Config { Value(${app.security.sm3-key}) private String sm3Key; Bean public KeyProvider sm3KeyProvider() { // 实际生产中密钥可能来自KMS这里用静态密钥示例 return new StaticKeyProvider(sm3Key, StandardCharsets.UTF_8); } Bean public SM3Signer sm3Signer(KeyProvider keyProvider) { return new SM3Signer(keyProvider, StandardCharsets.UTF_8); } }步骤3在业务服务中使用。Service public class PaymentCallbackService { Autowired private SM3Signer sm3Signer; /** * 处理支付回调。 * param callbackJson 回调报文JSON字符串 * param receivedSignature 回调头中携带的签名Hex格式 * return 验签是否通过 */ public boolean verifyCallback(String callbackJson, String receivedSignature) { // 1. 使用相同的密钥和报文计算签名 String calculatedSignature sm3Signer.signToHex(callbackJson); // 2. 安全地比较签名 boolean isValid sm3Signer.verifyHex(callbackJson, receivedSignature); if (isValid) { // 验签通过处理业务逻辑 processPayment(callbackJson); return true; } else { // 验签失败记录告警拒绝请求 log.warn(Invalid signature received for callback: {}, callbackJson); return false; } } /** * 生成对外请求的签名。 * param requestBody 请求体 * return 要放入HTTP头如X-Signature的签名 */ public String generateRequestSignature(String requestBody) { return sm3Signer.signToHex(requestBody); } private void processPayment(String json) { // 解析JSON更新订单状态等... } }步骤4在Controller或拦截器中应用。RestController RequestMapping(/api/callback) public class CallbackController { Autowired private PaymentCallbackService callbackService; PostMapping(/payment) public ResponseEntityString handlePaymentCallback(RequestBody String body, RequestHeader(X-Signature) String signature) { try { boolean valid callbackService.verifyCallback(body, signature); if (valid) { return ResponseEntity.ok(SUCCESS); } else { return ResponseEntity.status(HttpStatus.FORBIDDEN).body(INVALID_SIGNATURE); } } catch (Exception e) { log.error(Callback processing error, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(SYSTEM_ERROR); } } }6. 常见问题、性能调优与安全考量在实际部署和高压场景下你会遇到一些问题。下面是我踩过坑后总结的经验。6.1 常见问题排查表问题现象可能原因排查步骤与解决方案抛出NoSuchAlgorithmException: SM3运行环境未安装SM3算法提供者。1. 确认项目依赖了Bouncy CastleBC库如org.bouncycastle:bcprov-jdk15to18。2. 在应用启动时主类或配置类注册提供者Security.addProvider(new BouncyCastleProvider());。本地验签通过线上失败1. 密钥不一致。2. 报文编码不一致。3. 空格、换行符差异。1.核对密钥检查双方系统读取的密钥是否完全相同注意首尾空格、换行符。2.统一编码确保双方在将字符串转换为字节时使用相同的字符集强烈建议强制使用UTF-8。3.报文规范化对于JSON/XML在计算签名前是否进行了格式化如去除多余空格、统一属性顺序建议约定对原始报文字符串或序列化后的字节流直接签名避免二次处理。签名验证时时而成功时而失败1. 报文在传输或处理中被意外修改如URL解码、HTML转义。2. 使用了线程不安全的MessageDigest实例。1.日志记录将接收到的原始报文和签名打印到日志注意脱敏与发送方日志对比。2.检查实现确认SM3DigestGenerator中使用了ThreadLocal或每次创建新实例并且在使用前正确调用了digest.reset()。性能瓶颈CPU使用率高在高并发下频繁创建MessageDigest实例。1.使用ThreadLocal正如我们实现的那样这是最有效的优化。2.对象池对于更极致的场景可以考虑使用Apache Commons Pool等库管理MessageDigest对象池但ThreadLocal在大多数场景下已足够。密钥泄露风险密钥硬编码在代码或配置文件中。1.使用密钥管理系统将密钥存储在专业的KMS如HashiCorp Vault, AWS KMS, 阿里云KMS中应用在启动时动态获取。2.环境变量/保密卷在容器化部署中通过环境变量或Kubernetes Secrets注入。3.定期轮换建立密钥轮换机制KeyProvider接口便于实现此逻辑。6.2 性能调优建议预热在系统启动后可以预先模拟调用几次签名方法让ThreadLocal完成MessageDigest实例的初始化避免第一个请求的延迟。批量处理如果需要为大量小报文生成签名可以考虑将它们拼接成一个大的字节数组在报文间加入明确的分隔符如换行符或特定标记然后只计算一次HMAC。但务必谨慎这改变了语义必须确保业务逻辑允许并且接收方能以完全相同的方式拆分和验证。异步处理对于非实时验签的场景如日志审计可以将报文和签名放入消息队列由后台消费者异步处理避免阻塞主业务流程。6.3 安全强化考量密钥强度共享密钥口令应有足够的长度和熵。建议至少16字节128位推荐32字节256位。避免使用简单的单词、日期或默认值。防重放攻击HMAC-SM3保证了报文的完整性和真实性但无法防止攻击者重放一个有效的报文签名对。为了解决重放攻击通常需要在报文中加入一个一次性或时间相关的标识例如时间戳在报文中包含当前时间戳如timestamp接收方验证签名后再检查时间戳是否在可接受的窗口内如±5分钟。随机数Nonce每次请求使用一个唯一的随机数服务端缓存已使用过的Nonce拒绝重复的Nonce。这需要服务端有状态记录。在实际的API签名设计中timestamp和nonce是常见的组合。签名输出直接输出二进制摘要32字节最紧凑。使用Hex编码会膨胀到64字符Base64编码约为44字符。根据传输协议HTTP头、URL参数等选择适合的编码注意URL安全。恒定时间比较在SM3Signer.verify方法中我使用了MessageDigest.isEqual或自定义的constantTimeEquals。这是为了防止时序攻击。如果使用普通的String.equals()或数组逐字节比较并在发现第一个不同字符时就返回false攻击者可以通过精确测量比较操作的耗时来逐步推测出正确的签名是什么。恒定时间比较确保无论比较结果如何执行时间都基本一致。7. 进阶话题与SM2数字签名的对比与选型你可能会问有了HMAC-SM3为什么还需要SM2它们的主要区别和应用场景如下特性HMAC-SM3 (本文方案)SM2 数字签名密码学原理对称密码学。双方共享同一个密钥。非对称密码学。使用公钥/私钥对。签名用私钥验签用公钥。密钥管理相对复杂。需要在通信双方之间安全地预先共享和保管同一个密钥。N方通信需要N*(N-1)/2个密钥。相对简单。私钥由签名方严格保密公钥可以公开分发。任何持有公钥的人都可以验签。性能非常快。只涉及哈希运算和异或计算开销小。较慢。涉及椭圆曲线上的标量乘法和模逆运算计算开销比HMAC大几个数量级。功能提供消息认证完整性来源验证。提供数字签名完整性来源验证不可否认性。不可否认性不具备。因为双方拥有相同密钥任何一方都可以生成有效签名无法向第三方证明是对方发的。具备。只有私钥持有者能生成签名公钥持有者可验证可作为法律证据。典型应用场景1.内部微服务间API调用认证。2.系统与固定合作方之间的数据交换。3.软件内部模块间的完整性校验。4.对性能要求极高的实时流数据认证。1.公开API的调用签名如微信支付、支付宝开放平台。2.软件发布包签名验证发布者身份。3.电子合同、法律文书签名。4.SSL/TLS证书。选型建议选择HMAC-SM3当你需要在一个受控的、双方互信的环境中进行高速的数据完整性校验和来源验证且不需要“不可否认性”时。例如公司内部的服务网格Service Mesh中 sidecar 对请求的认证。选择SM2当你需要向不信任的或公开的客户端提供服务或者业务场景需要法律意义上的不可否认性时。例如面向开发者的开放平台API。在某些复杂系统中甚至可以组合使用先用SM2签名一个会话密钥再用该会话密钥作为HMAC-SM3的密钥来保护后续的大量数据传输兼顾了身份认证的强度和批量数据保护的性能。

相关新闻

OpenClaw + Kimi 2.5:构建确定性AI工具链的工程实践

OpenClaw + Kimi 2.5:构建确定性AI工具链的工程实践

1. 这不是又一个“AI机器人套壳教程”:OpenClaw Kimi 2.5 的真实定位与能力边界 你点开这篇教程,大概率是因为在飞书群聊里看到别人甩出一句“让Clawdbot查下上周的销售数据”,然后它真就从CRM导出表格、生成摘要、还附带了趋势图&#xff…

2026/6/24 4:37:58阅读更多 →
GLM-4.7开源实测:Agentic Coding如何实现全栈任务闭环

GLM-4.7开源实测:Agentic Coding如何实现全栈任务闭环

1. 项目概述:这不是一次普通升级,而是一次“编程范式迁移”的实锤落地“突发!智谱 GLM-4.7 深夜发布,编程开源第一!”——这个标题里没有一个字是夸张。我盯着控制台里跑出的第7个可直接运行的全栈Demo时,手…

2026/6/24 4:37:58阅读更多 →
安捷伦GC-MS经典分析套件:含谱库匹配、峰面积定量与合规报告模板的完整部署包

安捷伦GC-MS经典分析套件:含谱库匹配、峰面积定量与合规报告模板的完整部署包

本文还有配套的精品资源,点击获取 简介:一套开箱即用的安捷伦MSD ChemStation经典版(G1701FA)安装资源,专为气相色谱-质谱联用数据处理设计。支持NIST等标准谱库快速检索,通过QEdit、DOSCAN、DOLIST和Ea…

2026/6/24 4:32:57阅读更多 →
第21届智能车竞赛单车定向组比赛科目细则

第21届智能车竞赛单车定向组比赛科目细则

第二十一届全国大学生智能汽车竞赛比赛规则第21届全国大学生智能汽车竞赛提问与回答:单车定向组别 01 单车定向比赛细则 一、科目1:直线竞速 这个科目是比赛车模高速行进的能力。 车模从发车区出发, 直线行驶到掉头区返回。 发车区到调头区域…

2026/6/24 5:48:02阅读更多 →
10分钟掌握Swift-Verge状态管理:面向初学者的实用入门教程

10分钟掌握Swift-Verge状态管理:面向初学者的实用入门教程

10分钟掌握Swift-Verge状态管理:面向初学者的实用入门教程 【免费下载链接】swift-Verge 🟣 A robust Swift state-management framework designed for complex applications, featuring an integrated ORM for efficient data handling. 项目地址: ht…

2026/6/24 5:48:02阅读更多 →
3分钟极速部署:让小爱音箱秒变AI语音助手的终极改造指南

3分钟极速部署:让小爱音箱秒变AI语音助手的终极改造指南

3分钟极速部署:让小爱音箱秒变AI语音助手的终极改造指南 【免费下载链接】mi-gpt 🏠 将小爱音箱接入 ChatGPT 和豆包,改造成你的专属语音助手。 项目地址: https://gitcode.com/GitHub_Trending/mi/mi-gpt 还在为小爱音箱的"人工…

2026/6/24 5:48:02阅读更多 →
PhoneVR性能优化技巧:如何降低延迟提升VR体验

PhoneVR性能优化技巧:如何降低延迟提升VR体验

PhoneVR性能优化技巧:如何降低延迟提升VR体验 【免费下载链接】PhoneVR Use Steam VR-enabled applications with your phone as HMD (Head-mounted display). The only Open-Source solution to similar commercial packages like VRidge, iVRy, Trinus etc etc. …

2026/6/24 5:48:02阅读更多 →
Pandora与CVE漏洞分析:Kaspersky CVE-2023-23349等安全漏洞实战利用指南 [特殊字符]

Pandora与CVE漏洞分析:Kaspersky CVE-2023-23349等安全漏洞实战利用指南 [特殊字符]

Pandora与CVE漏洞分析:Kaspersky CVE-2023-23349等安全漏洞实战利用指南 🔐 【免费下载链接】pandora A red team tool that assists into extracting/dumping master credentials and/or entries from different password managers. 项目地址: https:…

2026/6/24 5:48:02阅读更多 →
Linux 再生龙系统迁移方法

Linux 再生龙系统迁移方法

一、前言 安装系统的方法有很多如 光盘/U盘 iso直接安装:只需制作系统启动盘即可,适用于少量的个人用户使用 2、PXE无人值守:通常用于新机器部署操作系统,需要搭建专门的服务并且要实现无人值守还得定制ks文件较为复杂&#xf…

2026/6/24 5:43:02阅读更多 →
【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体 一文搞定到底什么是智能体【人工智能】一文搞定到底什么是智能体一. LM,WorkFlow,Agent分别有什么么不同二. Agent的思考过程是怎样的三. Agent的五个核心部分1)LLM2)Prompt3)Me…

2026/6/23 7:04:52阅读更多 →
嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

1. 嵌入式GUI控件:从原理到实战的深度解析在嵌入式系统开发中,图形用户界面(GUI)的设计与实现往往是项目从“能用”到“好用”的关键一跃。不同于资源充沛的PC或移动平台,嵌入式设备的GUI需要在有限的CPU性能、内存空间…

2026/6/24 2:12:09阅读更多 →
Google AI Studio 300美元额度的真相与实战指南

Google AI Studio 300美元额度的真相与实战指南

1. 这300美金不是“送钱”,而是Google埋下的第一道技术门槛 你看到标题里那个醒目的“$300美金”时,第一反应可能是:又一个免费额度?领完就完事?我亲手试过——这300美金根本不是红包,而是一张入场券&…

2026/6/23 5:55:37阅读更多 →
TaskJuggler脚本编程入门:用代码实现自动化项目管理

TaskJuggler脚本编程入门:用代码实现自动化项目管理

TaskJuggler脚本编程入门:用代码实现自动化项目管理 【免费下载链接】TaskJuggler TaskJuggler - Project Management beyond Gantt chart drawing 项目地址: https://gitcode.com/gh_mirrors/ta/TaskJuggler TaskJuggler是一款强大的开源项目管理工具&#…

2026/6/24 0:02:41阅读更多 →
终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果

终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果

终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果 【免费下载链接】angular-mobile-nav An angular navigation service for mobile applications 项目地址: https://gitcode.com/gh_mirrors/an/angular-mobile-nav angular-mobile-nav是一款专为…

2026/6/24 0:02:41阅读更多 →
Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作

Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作

Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作 【免费下载链接】Wan2.1-Fun-V1.1-1.3B-InP 项目地址: https://ai.gitcode.com/hf_mirrors/PAI/Wan2.1-Fun-V1.1-1.3B-InP Wan2.1-Fun-V1.1-1.3B-InP是一款强大的AI视频创作工具,…

2026/6/24 0:02:41阅读更多 →