3、数字签名技术:非对称加密基础、RSA与ECDSA签名、签名验证流程、EDR日志签名实践
聊到EDR的数据完整性,数字签名是绕不开的核心技术。说白了,它就是给日志文件盖一个「防伪章」——谁盖的、盖完之后有没有被人动过,一验便知。我在做EDR产品时,日志被篡改是最头疼的问题之一,而数字签名恰恰是解决这个问题的利器。
3.1 非对称加密基础
先说说非对称加密。它跟咱们平时用的对称加密不一样。对称加密就像一把钥匙开一把锁,加密解密用同一个密钥。而非对称加密,用的是「密钥对」——一把公钥,一把私钥。
公钥可以公开,谁都能拿到。私钥必须保密,只有你自己知道。用私钥签名的东西,任何人都可以用公钥来验证。反过来,用公钥加密的数据,只有持有私钥的人才能解密。
我刚开始接触这个概念时,总觉得有点绕。后来想了个比喻:私钥就像你的手写签名,公钥就像银行里留的签名样本。你签了字,银行拿样本一比对,就知道是不是你本人签的。嗯,这个比喻虽然不完全准确,但理解原理够用了。
核心要点:
- 私钥签名,公钥验签——这是数字签名的基本模式
- 公钥加密,私钥解密——这是加密通信的基本模式
- 两者不能混用,否则安全性会出大问题
3.2 RSA与ECDSA签名
目前主流的签名算法,一个是RSA,一个是ECDSA。我在项目中两种都用过,各有各的适用场景。
RSA签名
RSA诞生于1977年,算是老前辈了。它的安全性基于大整数分解的难度——给你两个大质数相乘的结果,想反推出是哪两个质数,非常困难。
RSA签名有个特点:签名长度固定。比如用2048位的RSA,签名就是256字节。不管你的日志文件是1KB还是1GB,签名长度都一样。这一点在EDR场景下很实用。
我曾经遇到过一个坑:RSA的密钥生成速度比较慢,尤其是在嵌入式设备上。有一次在IoT网关设备上部署EDR代理,生成2048位密钥对花了将近10秒。后来我改用预生成密钥的方式,才解决了这个问题。
ECDSA签名
ECDSA(椭圆曲线数字签名算法)是后来者,但势头很猛。它的安全性基于椭圆曲线离散对数问题。同样的安全强度下,ECDSA的密钥长度比RSA短得多。
举个例子:256位的ECDSA,安全性和3072位的RSA差不多。但ECDSA的签名长度只有64字节左右,而RSA要384字节。你想想看,在日志量巨大的EDR场景下,这个差距意味着什么?
| 对比项 | RSA-2048 | ECDSA-P256 |
|---|---|---|
| 密钥长度 | 2048位 | 256位 |
| 签名长度 | 256字节 | 64字节 |
| 签名速度 | 较慢 | 较快 |
| 验签速度 | 较快 | 较慢 |
| 安全性 | 高 | 高(同等强度下密钥更短) |
我的建议:
如果EDR代理运行在资源受限的设备上(比如路由器、摄像头),优先考虑ECDSA。如果是服务器端,RSA和ECDSA都可以,但要注意验签性能——RSA验签比ECDSA快,这在日志量大的场景下是个优势。
3.3 签名验证流程
签名和验证的流程,其实不复杂。我画了一张图,把整个过程串起来,你看完就明白了。
流程其实就三步:
- 签名方:对日志数据做哈希(比如SHA-256),然后用私钥对哈希值签名,得到签名数据。日志原文和签名一起发送出去。
- 传输:日志和签名通过网络传输到EDR服务器。注意,这里日志原文是明文的,签名是附加的。
- 验证方:收到日志后,同样做一次哈希。然后用公钥解密签名,得到原始的哈希值。两个哈希值一比对——一致,说明日志没被改过;不一致,说明有人动了手脚。
注意:
签名只能保证数据完整性,不能保证数据机密性。日志原文还是明文的,想加密得另外做。另外,公钥的分发必须安全——如果公钥被替换了,那签名验证就形同虚设。我见过有人直接把公钥硬编码在代码里,这种做法其实不太安全。
3.4 EDR日志签名实践
理论说完了,咱们看看实际怎么用。下面是一个EDR日志签名的代码示例,用Python写的,用的是ECDSA算法。
import hashlib
from ecdsa import SigningKey, VerifyingKey, NIST256p
# 生成密钥对
private_key = SigningKey.generate(curve=NIST256p)
public_key = private_key.get_verifying_key()
# 模拟EDR日志数据
log_data = b"2024-01-15 10:30:45, PID=1234, EVENT=PROCESS_CREATE, CMD=/bin/sh"
# 签名过程
def sign_log(log_bytes, priv_key):
# 1. 计算日志的哈希值
log_hash = hashlib.sha256(log_bytes).digest()
# 2. 用私钥对哈希值签名
signature = priv_key.sign(log_hash)
return signature
# 验证过程
def verify_log(log_bytes, sig, pub_key):
# 1. 计算日志的哈希值
log_hash = hashlib.sha256(log_bytes).digest()
# 2. 用公钥验证签名
try:
return pub_key.verify(sig, log_hash)
except:
return False
# 执行签名
signature = sign_log(log_data, private_key)
print(f"签名长度: {len(signature)} 字节")
# 验证签名
result = verify_log(log_data, signature, public_key)
print(f"验证结果: {'通过' if result else '失败'}")
# 模拟篡改
tampered_data = b"2024-01-15 10:30:45, PID=1234, EVENT=PROCESS_CREATE, CMD=/bin/bash"
result2 = verify_log(tampered_data, signature, public_key)
print(f"篡改后验证: {'通过' if result2 else '失败'}")
这段代码跑起来,你会看到:原始日志验证通过,篡改后的日志验证失败。这就是数字签名的核心价值。
实践经验:
在实际的EDR产品中,我一般会这样做:
- 每个日志文件生成一个独立的签名文件(比如 .sig),和日志文件放在一起
- 签名时带上时间戳,防止重放攻击
- 定期轮换密钥,私钥存储在HSM(硬件安全模块)或安全飞地中
- 验签失败时,不仅要告警,还要保留原始日志作为取证依据
我曾经遇到过一个真实案例:某客户的EDR日志被篡改了,攻击者删除了自己执行恶意命令的记录。幸好我们用了数字签名,验签时发现日志和签名对不上,立刻触发了告警。虽然日志被改了,但至少我们知道「有人动过日志」这个事实,顺着这条线索最终追溯到了攻击者。
所以说,数字签名在EDR里不是锦上添花,而是雪中送炭。没有它,日志的完整性就是一句空话。
本章小结:
- 非对称加密是数字签名的基础,私钥签名、公钥验签
- RSA和ECDSA各有优劣,选型要看具体场景
- 签名验证的核心是哈希比对,日志原文+签名一起传输
- 实际部署时要注意密钥管理、时间戳、告警机制