数字签名是一种确保数据完整性和验证发送者身份的方法,它在许多不同的场景中被广泛应用,如电子邮件系统确认邮件真的是由声称的发送者发出的以及确保邮件内容没有被篡改;在线支付系统中确认交易请求的合法性;电子商务系统中验证订单信息的真实性和完整性等等。它的主要目的是为了确保数据的来源可靠、数据本身没有被篡改,并且能够验证发送者的身份。其主要流程是发送方使用私钥对消息生成数字签名(消息的hash),然后将签名附加到消息中并将其发送给接收方,接收方收到消息后会提取签名,并计算出消息的哈希值,然后使用发送方的公钥验证签名,判断签名是否有效,数字签名提供了相当高的安全保证。
使用Java原生不依赖第三方库实现整个流程
import java.security.*;
public class DigitalSignatureExample {
public static void main(String[] args) {
try {
// Step 1: 发送方创建消息并生成数字签名
KeyPair keyPair = generateKeyPair();
String message = "Hello, Pat!";
byte[] signature = createSignature(keyPair.getPrivate(), message);
// Step 2: 发送方将签名附加到消息中并将其发送给接收方
// 将签名转换为十六进制字符串,并将其附加到消息末尾
String signedMessage = message + " [Signature:" + bytesToHex(signature) + "]";
System.out.println("Signed Message: " + signedMessage);
// Step 3: 接收方接收消息,提取签名,并计算消息的哈希值
// 提取原始消息,去掉签名部分
String receivedMessage = signedMessage.split("\\[")[0].trim();
// 提取签名部分,注意使用 "\\[Signature:" 来准确定位签名开始的部分
String signatureHex = signedMessage.split("\\[Signature:")[1].split("]")[0].trim();
// 将提取出的十六进制签名转换为字节数组
byte[] receivedSignature = hexToBytes(signatureHex);
// 计算接收到的消息的哈希值
byte[] computedHash = computeHash(receivedMessage);
// Step 4: 接收方使用发送方的公钥验证签名
boolean isVerified = verifySignature(keyPair.getPublic(), receivedSignature, computedHash);
// Step 5: 结果验证,判断签名是否有效
if (isVerified) {
// 说明此过程确保了消息的来源和完整性
System.out.println("签名有效,这条信息没有被篡改。");
} else {
System.out.println("签名无效,这条信息可能被人篡改了.");
}
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
e.printStackTrace();
}
}
/**
* 生成一对公私密钥对,用于签名和验证
*
* @return KeyPair 包含生成的公钥和私钥
* @throws NoSuchAlgorithmException 当指定的算法不可用时抛出
*/
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048); // 初始化密钥生成器,使用2048位的密钥长度
return keyGen.generateKeyPair();
}
/**
* 使用私钥对消息生成数字签名
*
* @param privateKey 用于签名的私钥
* @param message 要签名的消息
* @return 生成的数字签名
* @throws NoSuchAlgorithmException 当指定的算法不可用时抛出
* @throws SignatureException 签名过程中的异常
* @throws InvalidKeyException 提供的私钥无效异常
*/
private static byte[] createSignature(PrivateKey privateKey, String message) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
// 计算消息的SHA-256哈希值
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(message.getBytes());
// 使用SHA256withRSA算法创建签名对象
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey); // 初始化签名对象,使用私钥
signature.update(hash); // 更新签名对象,使用消息的哈希值
return signature.sign(); // 生成并返回签名
}
/**
* 计算消息的SHA-256哈希值
*
* @param message 要计算哈希值的消息
* @return 计算出的哈希值
* @throws NoSuchAlgorithmException 当指定的算法不可用时抛出
*/
private static byte[] computeHash(String message) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return digest.digest(message.getBytes()); // 返回消息的SHA-256哈希值
}
/**
* 使用公钥验证数字签名是否有效
*
* @param publicKey 用于验证的公钥
* @param signature 要验证的数字签名
* @param hash 消息的哈希值
* @return 签名是否有效
* @throws NoSuchAlgorithmException 当指定的算法不可用时抛出
* @throws SignatureException 验证签名过程中的异常
* @throws InvalidKeyException 如果提供的公钥无效
*/
private static boolean verifySignature(PublicKey publicKey, byte[] signature, byte[] hash) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
Signature signVer = Signature.getInstance("SHA256withRSA");
signVer.initVerify(publicKey); // 初始化验证对象,使用公钥
signVer.update(hash); // 更新验证对象,使用消息的哈希值
return signVer.verify(signature); // 验证签名是否有效
}
/**
* 将字节数组转换为十六进制字符串
*
* @param bytes 要转换的字节数组
* @return 转换后的十六进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b)); // 将每个字节转换为两位十六进制字符串并附加到结果中
}
return sb.toString();
}
/**
* 将十六进制字符串转换为字节数组
*
* @param hexString 要转换的十六进制字符串
* @return 转换后的字节数组
*/
private static byte[] hexToBytes(String hexString) {
// 确保字符串长度是偶数,如果不是则抛出异常
if (hexString.length() % 2 != 0) {
throw new IllegalArgumentException("Invalid hexadecimal string length: must be even.");
}
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
// 将两个十六进制字符转换为一个字节
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return data; // 返回转换后的字节数组
}
}
为什么说数字签名技术提供了相当高的安全保证呢,也是通过以下几点。
-
非对称加密:首先私钥由发送方保留,而公钥可以公开。非对称加密的一个关键特性是使用私钥加密的信息只能使用对应的公钥解密,反之亦然。
-
消息摘要:在生成签名之前,消息首先使用SHA-256算法计算出一个固定长度的哈希值,这是一个非常强大的哈希算法,截止目前还未被攻破。即使原始消息只有很小的变化,其哈希值也会完全不同,从而确保任何对原始消息的更改都会被检测到。
-
生成数字签名:发送方使用自己的私钥对消息的哈希值进行签名。由于私钥只有发送方持有,因此其他人无法伪造该签名。
-
验证数字签名:接收方使用发送方的公钥来验证签名。如果签名是有效的,则意味着签名确实是由持有对应私钥的实体创建的,并且消息自签名以来没有被篡改。
-
来源确认:因为私钥是唯一的,并且只有发送方知道,所以当接收方成功验证了签名,就可以确信消息是由持有该私钥的实体发送的。