返回知识库中心
技术实现

审计日志防篡改:SM3哈希链技术全解析

如何用国密算法构建不可篡改的审计日志系统?本文深入解析 SM3 哈希链的设计原理和实现。

数凭电子会计档案··技术实现

审计日志防篡改:SM3哈希链技术全解析

如何用国密算法构建不可篡改的审计日志系统?本文深入解析 SM3 哈希链的设计原理和实现。


摘要

在等保 2.0 三级和 DA/T 94-2022 标准的要求下,企业必须确保审计日志的完整性和不可篡改性。本文介绍基于国密 SM3 算法的哈希链技术,通过将每条日志与前一条日志关联,构建防篡改的审计日志体系。


一、为什么需要防篡改审计日志?

1.1 合规要求

标准/规范要求
等保 2.0 三级审计记录应包括日期、时间、类型、主体标识,结果应保护免受删除、修改或覆盖
DA/T 94-2022电子会计档案管理系统应建立审计机制,记录应具有防篡改保护
GB/T 39784-2021审计员应能独立查看审计日志,系统应确保日志不可篡改

1.2 传统方案的缺陷

传统审计日志的问题:

┌─────────────────────────────────────┐
│ 日志1: 2024-03-22 用户A登录系统      │
│ 日志2: 2024-03-22 用户B删除档案      │  ← 可以被偷偷修改
│ 日志3: 2024-03-22 用户C导出数据      │
└─────────────────────────────────────┘

问题:没有技术手段防止日志被修改

二、SM3 哈希链原理

2.1 核心思想

将每条日志与前一条日志的哈希值关联,形成链式结构。

日志1 → SM3(内容1 + NULL) → Hash1
                    ↓
日志2 → SM3(内容2 + Hash1) → Hash2
                    ↓
日志3 → SM3(内容3 + Hash2) → Hash3
                    ↓
日志N → SM3(内容N + HashN-1) → HashN

2.2 防篡改机制

如果篡改者修改了任意一条日志:

场景:修改日志2的内容
┌─────────────────────────────────────┐
│ 日志1: Hash1 = SM3(内容1 + NULL)    │ → 不变
│ 日志2: Hash2 = SM3(新内容2 + Hash1) │ → 被修改,但 Hash2 值不变!
│ 日志3: Hash3 = SM3(内容3 + Hash2)    │ → 验证失败!Hash3 ≠ SM3(内容3 + 修改后的Hash2)
└─────────────────────────────────────┘

验证:从日志1开始重新计算哈希链,发现日志3的哈希不匹配

2.3 日志数据结构

public class AuditLog {
    private Long id;                    // 日志ID
    private String content;             // 日志内容(JSON)
    private String logHash;             // 当前日志的SM3哈希
    private String prevLogHash;         // 前一条日志的哈希
    private LocalDateTime timestamp;    // 时间戳

    // 验证方法
    public boolean verify() {
        // 计算当前日志应有哈希
        String expectedHash = Sm3Utils.hash(
            this.content + (this.prevLogHash != null ? this.prevLogHash : "")
        );
        return expectedHash.equals(this.logHash);
    }
}

三、代码实现

3.1 SM3 哈希计算

/**
 * SM3 哈希工具类
 */
public class Sm3Utils {

    /**
     * 计算字符串的 SM3 哈希值
     */
    public static String hash(String input) {
        SM3Digest digest = new SM3Digest();
        byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
        digest.update(inputBytes, 0, inputBytes.length);

        byte[] hashBytes = new byte[digest.getDigestSize()];
        digest.doFinal(hashBytes, 0);

        return bytesToHex(hashBytes);
    }

    /**
     * 计算日志哈希(内容 + 前序哈希)
     */
    public static String hashLog(String content, String prevHash) {
        String input = content + (prevHash != null ? prevHash : "");
        return hash(input);
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}

3.2 日志保存服务

/**
 * 审计日志服务
 */
@Service
public class AuditLogService {

    @Autowired
    private AuditLogRepository auditLogRepository;

    /**
     * 记录审计日志
     */
    public AuditLog record(String content) {
        // 1. 获取前一条日志的哈希
        String prevHash = getLastLogHash();

        // 2. 计算当前日志哈希
        String currentHash = Sm3Utils.hashLog(content, prevHash);

        // 3. 创建日志记录
        AuditLog log = new AuditLog();
        log.setContent(content);
        log.setLogHash(currentHash);
        log.setPrevLogHash(prevHash);
        log.setTimestamp(LocalDateTime.now());

        // 4. 保存到数据库
        return auditLogRepository.save(log);
    }

    /**
     * 获取最后一条日志的哈希
     */
    private String getLastLogHash() {
        return auditLogRepository.findTopByOrderByTimestampDesc()
                .map(AuditLog::getLogHash)
                .orElse(null);
    }

    /**
     * 验证哈希链完整性
     */
    public boolean verifyChain(LocalDateTime start, LocalDateTime end) {
        List<AuditLog> logs = auditLogRepository
                .findByTimestampBetweenOrderByTimestamp(start, end);

        String prevHash = null;

        for (AuditLog log : logs) {
            // 计算应有哈希
            String expectedHash = Sm3Utils.hashLog(log.getContent(), prevHash);

            // 验证哈希是否匹配
            if (!expectedHash.equals(log.getLogHash())) {
                // 记录异常
                log.error("哈希链验证失败:日志ID={}, 预期={}, 实际={}",
                        log.getId(), expectedHash, log.getLogHash());
                return false;
            }

            prevHash = expectedHash;
        }

        return true;
    }
}

3.3 数据库表设计

CREATE TABLE sys_audit_log (
    id BIGSERIAL PRIMARY KEY,
    content JSONB NOT NULL,              -- 日志内容
    log_hash VARCHAR(64) NOT NULL,       -- 当前日志的SM3哈希
    prev_log_hash VARCHAR(64),           -- 前一条日志的哈希
    timestamp TIMESTAMP NOT NULL,        -- 时间戳
    operator VARCHAR(50),                -- 操作人
    operation VARCHAR(20),               -- 操作类型
    resource_type VARCHAR(50),           -- 资源类型
    resource_id VARCHAR(100),            -- 资源ID
    ip_address VARCHAR(45),              -- IP地址
    user_agent TEXT,                     -- 用户代理
    created_at TIMESTAMP DEFAULT NOW(),
    CONSTRAINT uk_audit_log_hash UNIQUE (log_hash)
);

-- 索引
CREATE INDEX idx_audit_log_timestamp ON sys_audit_log(timestamp);
CREATE INDEX idx_audit_log_operator ON sys_audit_log(operator);
CREATE INDEX idx_audit_log_operation ON sys_audit_log(operation);

四、验证工具

4.1 链式验证

/**
 * 哈希链验证器
 */
public class ChainVerifier {

    /**
     * 验证指定范围的哈希链
     */
    public ChainVerificationResult verify(
            List<AuditLog> logs,
            VerificationScope scope) {

        ChainVerificationResult result = new ChainVerificationResult();
        result.setTotalLogs(logs.size());

        String prevHash = null;
        int verifiedCount = 0;

        for (int i = 0; i < logs.size(); i++) {
            AuditLog log = logs.get(i);

            // 计算应有哈希
            String expectedHash = Sm3Utils.hashLog(
                log.getContent(),
                prevHash
            );

            // 验证哈希
            if (expectedHash.equals(log.getLogHash())) {
                verifiedCount++;
            } else {
                // 发现篡改
                result.addTamperedLog(i, log);
                result.setTampered(true);
            }

            prevHash = expectedHash;
        }

        result.setVerifiedLogs(verifiedCount);

        return result;
    }

    /**
     * 快速验证(仅验证锚点)
     */
    public boolean quickVerify(AuditLog anchor, String expectedHash) {
        return anchor.getLogHash().equals(expectedHash);
    }
}

4.2 可视化展示

哈希链验证结果:
┌────────────────────────────────────────────────────┐
│ 总日志数:1000 条                                   │
│ 已验证:998 条                                      │
│ 疑似篡改:2 条                                      │
│                                                    │
│ 篡改位置:                                          │
│ - 日志 #456: 2024-03-15 10:23:45 用户删除档案      │
│ - 日志 #457: 2024-03-15 10:24:10 系统备份完成      │
│                                                    │
│ 建议:立即导出原始日志备份,启动安全调查             │
└────────────────────────────────────────────────────┘

五、最佳实践

5.1 定期验证

@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void scheduledVerification() {
    LocalDateTime now = LocalDateTime.now();
    LocalDateTime yesterday = now.minusDays(1);

    boolean isValid = auditLogService.verifyChain(yesterday, now);

    if (!isValid) {
        // 发送告警
        alertService.sendAlert("审计日志哈希链验证失败,可能存在篡改");
    }
}

5.2 日志备份

/**
 * 日志备份服务
 */
public class LogBackupService {

    /**
     * 导出日志备份(含哈希链验证信息)
     */
    public void exportBackup(LocalDate date) {
        List<AuditLog> logs = auditLogService.getByDate(date);

        // 生成备份文件
        String filename = String.format(
            "audit-log-backup-%s.json",
            date.format(DateTimeFormatter.ISO_DATE)
        );

        BackupFile backup = new BackupFile();
        backup.setLogs(logs);
        backup.setExportTime(LocalDateTime.now());
        backup.setVerificationResult(
            auditLogService.verifyChain(date.atStartOfDay(), date.plusDays(1).atStartOfDay())
        );

        // 签名备份文件
        String signature = signBackup(backup);
        backup.setSignature(signature);

        // 保存到备份位置
        backupStorage.save(filename, backup.toJson());
    }
}

5.3 安全建议

措施说明
数据库权限审计日志表只允许 INSERT,禁止 UPDATE/DELETE
定期备份每日自动备份,备份文件单独签名保存
多级存储热数据(在线)、温数据(归档)、冷数据(离线)
区块链存证关键日志哈希上链,增强可信度
审计员独立审计员角色独立,只能查看不能修改

六、总结

SM3 哈希链技术是实现审计日志防篡改的有效方案:

特点说明
不可篡改任意修改都会破坏哈希链,立即被发现
可验证任何人都可以验证日志完整性
国密算法符合信创环境要求
实现简单核心代码不到 100 行

在等保 2.0 三级和 DA/T 94-2022 的要求下,企业应采用此类技术确保审计日志的完整性和可信度。


相关阅读

了解更多申请试用 | 预约演示

相关推荐

想要了解 DigiVoucher 如何助您实现合规归档?

我们的专家团队可为您提供定制化的电子会计档案单套制实施建议。