数字签名(Digital Signature)
数字签名
数字签名又称公钥数字签名、电子签章。
为什么需要数字签名?
从之前的学习里可以知道,消息认证码可以识别篡改或者发送者身份是否被伪装,也就是验证消息的完整性,
还可以对消息进行认证。但是消息认证码的缺陷就在于它的共享密钥上面。由于共享密钥的原因,导致无法防止抵赖。
数字签名就是为了解决抵赖的问题的。解决的方法就是让通信双方的共享密钥不同,从密钥上能区分出谁是谁。
什么是数字签名?
只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。
数字签名具有不可抵赖性(不可否认性)。
数字签名是非对称密钥加密技术与数字摘要技术的应用。
简单地说,所谓数字签名就是附加在数据单元上的一些数据,或是对数据单元所作的密码变换。
这种数据或变换允许数据单元的接收者用以确认数据单元的来源和数据单元的完整性并保护数据,防止被人(例如接收者)进行伪造。
它是对电子形式的消息进行签名的一种方法,一个签名消息能在一个通信网络中传输。
在数字签名中,有 2 种行为:
- 生成消息签名的行为
- 验证消息签名的行为
生成消息签名的人是由消息发送者完成的,也称为“对消息签名”。生成签名就是根据消息内容计算数字签名的值。
验证数字签名的人是第三方。第三方验证消息的来源是否属于发送者。验证结果可以是成功,也可以是失败。
严格的来说,RSA 算法中公钥加密和数字签名正好是完全相反的关系,但是在其他公钥算法中有可能和数字签名不是完全相反的关系。
在公钥算法中,公钥用来加密,私钥用来解密。
在数字签名中,公钥用来解密(验证签名),私钥用来加密(生成签名)。
数字签名算法
基于公钥密码体制和私钥密码体制都可以获得数字签名,主要是基于公钥密码体制的数字签名。包括普通数字签名和特殊数字签名。
普通数字签名算法有RSA、ElGamal、Fiat-Shamir、Guillou- Quisquarter、Schnorr、Ong-Schnorr-Shamir数字签名算法、
Des/DSA,椭圆曲线数字签名算法和有限自动机数字签名算法等。
特殊数字签名有盲签名、代理签名、群签名、不可否认签名、公平盲签名、门限签名、具有消息恢复功能的签名等,
它与具体应用环境密切相关。
数字签名中使用的Hash算法
在2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。
故而,目前,SHA-1是流行的用于创建数字签名的Hash算法。
签名过程
发送报文时,发送方用一个哈希函数从报文文本中生成报文摘要,然后用发送方的私钥对这个摘要进行加密,
这个加密后的摘要将作为报文的数字签名和报文一起发送给接收方,
接收方首先用与发送方一样的哈希函数从接收到的原始报文中计算出报文摘要,
接着再公钥来对报文附加的数字签名进行解密,如果这两个摘要相同、那么接收方就能确认该报文是发送方的。
代码样例
import com.sun.org.apache.xml.internal.security.utils.Base64;
import java.security.*;
public class SignatureDemo {
public static void main(String[] args) throws Exception {
String a = "123";
PublicKey publicKey = loadPublicKeyFromFile("RSA", "a.pub");
PrivateKey privateKey = loadPrivateKeyFromFile("RSA", "a.pri");
String signaturedData = getSignature(a, "sha256withrsa", privateKey);
boolean b = verifySignature(a, "sha256withrsa", publicKey, signaturedData);
}
/**
* 从文件中加载公钥
*
* @param algorithm : 算法
* @param filePath : 文件路径
* @return : 公钥
* @throws Exception
*/
private static PublicKey loadPublicKeyFromFile(String algorithm, String filePath) throws Exception {
// 将文件内容转为字符串
String keyString = FileUtils.readFileToString(new File(filePath), Charset.forName("UTF-8"));
return loadPublicKeyFromString(algorithm, keyString);
}
/**
* 从文件中加载私钥
*
* @param algorithm : 算法
* @param filePath : 文件路径
* @return : 私钥
* @throws Exception
*/
private static PrivateKey loadPrivateKeyFromFile(String algorithm, String filePath) throws Exception {
// 将文件内容转为字符串
String keyString = FileUtils.readFileToString(new File(filePath), Charset.forName("UTF-8"));
return loadPrivateKeyFromString(algorithm, keyString);
}
/**
* 生成签名
*
* @param input : 原文
* @param algorithm : 算法
* @param privateKey : 私钥
* @return : 签名
* @throws Exception
*/
private static String getSignature(String input, String algorithm, PrivateKey privateKey) throws Exception {
// 获取签名对象
Signature signature = Signature.getInstance(algorithm);
// 初始化签名
signature.initSign(privateKey);
// 传入原文
signature.update(input.getBytes());
// 开始签名
byte[] sign = signature.sign();
// 对签名数据进行Base64编码
return Base64.encode(sign);
}
/**
* 校验签名
*
* @param input : 原文
* @param algorithm : 算法
* @param publicKey : 公钥
* @param signaturedData : 签名
* @return : 数据是否被篡改
* @throws Exception
*/
private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signaturedData) throws Exception {
// 获取签名对象
Signature signature = Signature.getInstance(algorithm);
// 初始化签名
signature.initVerify(publicKey);
// 传入原文
signature.update(input.getBytes());
// 校验数据
return signature.verify(Base64.decode(signaturedData));
}
}
数字签名应用
- 安全信息公告
- 软件下载
- 公钥证书
- SSL/TLS
对数字签名的攻击
中间人攻击公钥
这里的攻击主要是攻击公钥。如何进行公钥之间的认证,还是需要使用公钥证书。
对单向散列函数的攻击
对单向散列函数进行碰撞攻击,生成另外一条不同的消息,使其与签名所绑定的消息具有相同的散列值。
利用数字签名攻击公钥密码
由于 RSA 和数字签名互为逆向操作。于是可以利用这一性质,对 RSA 进行攻击。
让发送者对 RSA 密文进行签名(用私钥加密),就相当于是 RSA 中的解密操作。
防止这种攻击有几种方法:
- 不要直接对任何消息进行签名,对散列值进行签名比较安全
- 公钥密码和数字签名使用不同的密钥对
- 绝对不要对意思不清楚的消息进行签名,就像在看不懂的合同上任意盖章
潜在伪造
即使签名的对象是无任何意义的消息,例如随机比特序列,
如果攻击者能够生成合法的数字签名(即攻击者生成的签名能够正常通过校验),
这也算是对这种签名算法的一种潜在威胁。
在用 RSA 来解密消息的数字签名算法中,潜在伪造是可能的。
只要将随机比特序列 S 用 RSA 的公钥加密生成密文 M,那么,S 就是 M 的合法数字签名,
由于攻击者是可以获取公钥的,因此对数字签名的潜在伪造就实现了。
为了防止这种情况,人们改良了 RSA ,开发出了新的签名算法,RSA-PSS(Probabilistic Signature Scheme)。
RSA-PSS 并不是对消息本身进行签名,而是对其散列值进行签名,为了提高安全性,在计算散列值的时候还对消息加盐(salt)。
其他攻击
对公钥密码的攻击都可以用于对数字签名的攻击,例如暴力破解私钥,尝试对 RSA 的 N 进行质因数分解等等
数字签名无法解决的问题
数字签名所用到的公钥密码中的公钥需要另外认证,防止中间人攻击。认证用于验证签名的公钥必须属于真正的发送者。
似乎陷入了一个死循环。
数字签名用来识别消息篡改,伪装以及防止抵赖。但是我们又必须从没有被伪装的发送者得到没有被篡改的公钥才行。
为了验证得到的公钥是否合法,必须使用证书。证书是将公钥当做一条消息,由一个可信的第三方对其签名后所得到的公钥。