目录
- 数字签名
- 1 定义
- 2 数字签名特点
- 3 应用场景
- 4 JDK支持的信息摘要算法
- 5 Bouncy Castle 支持的信息摘要算法
- 6 算法调用示例
数字签名
1 定义
数字签名(digital signature)是一种电子签名,也可以表示为一种数学算法,通常用于验证消息(例如,电子邮件、信用卡交易或数字文档)的真实性和完整性。
数字签名并没有创建新的算法,主要是结合使用信息摘要算法(MD,SHA)和非对称加密算法(RSA,DSA)。信息摘要算法用来验证数据完整性,非对称加密算法用来进行身份验证。
消息发送方用摘要算法和私钥加密生成签名,接收方用公钥解密验证签名,再用相同的摘要算法验证数据完整性。
一个典型的消息发送过程如下:
2 数字签名特点
- 防篡改:数据不会被修改,MAC算法也有这个特点。
- 防抵赖:消息签署者不能抵赖。
- 防伪造:发送的消息不能够伪造,MAC算法也有这个特点。
3 应用场景
数字签名具有许多重要的应用,例如在电子政务活动中的电子公文、网上报税、网上投票,在电子商务活动中的电子订单、电子账单、电子收据、电子合同、电子现金等电子文档都需要通过数字签名来保证文档的真实性和有效性;甚至于人们日常使用频繁的电子邮件,当涉及重要内容时,也需要通过数字签名技术来对邮件的发送者进行确认和保证邮件内容未被篡改,并且邮件的发送者也不能对发出的邮件进行否认。由此可见,数字签名技术早已深入应用到国家的政治、军事、经济和人们生活中的各个方面,并将在国家数字 化进程中发挥越来越重要的作用。
4 JDK支持的信息摘要算法
JDK8原生算法列表,可参第一篇博文: https://blog.csdn.net/yunyun1886358/article/details/128592503#311_JDK_Provider_63
5 Bouncy Castle 支持的信息摘要算法
Bouncy Castle算法列表,可参第一篇博文:
https://editor.csdn.net/md/?articleId=128592503#323_Bouncy_Castle_Provider_568
6 算法调用示例
下面的代码将JDK提供的几种数字签名算法用枚枚举类进行了封装。
package com.qupeng.crypto.algorithm.oop;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
public class DigitalSignatureAlgotithmTest {
@Test
public void sign() throws Exception {
String signatureStr = DigitalSignatureAlgotithm.MD2withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.MD2withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.MD5withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.MD5withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.SHA1withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.SHA1withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.SHA1withDSA.signByPrivateKeyFromFile("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.SHA1withDSA.verifySignatureByPublicKeyFromFile("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.SHA256withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.SHA256withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.SHA224withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.SHA224withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.SHA384withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.SHA384withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.SHA512withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.SHA512withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.RIPEMD128withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.RIPEMD128withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
signatureStr = DigitalSignatureAlgotithm.RIPEMD160withRSA.signByPrivateKeyFromKeyStore("1234567890");
Assert.assertTrue(DigitalSignatureAlgotithm.RIPEMD160withRSA.verifySignatureByPublicKeyFromCA("1234567890", signatureStr));
}
}
package com.qupeng.crypto.algorithm.oop;
import com.qupeng.crypto.util.CryptoUtils;
import com.qupeng.crypto.util.DigitalCertificationUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.util.Base64;
public enum DigitalSignatureAlgotithm {
MD2withRSA("MD2withRSA", "RSA", "default"),
MD5withRSA("MD5withRSA", "RSA", "default"),
SHA1withRSA("SHA1withRSA", "RSA", "default"),
SHA1withDSA("SHA1withDSA", "DSA", "default"),
SHA256withRSA("SHA256withRSA", "RSA", "default"),
SHA224withRSA("SHA224withRSA", "RSA", "BC"),
SHA384withRSA("SHA384withRSA", "RSA", "BC"),
SHA512withRSA("SHA512withRSA", "RSA", "BC"),
RIPEMD128withRSA("RIPEMD128withRSA", "RSA", "BC"),
RIPEMD160withRSA("RIPEMD160withRSA", "RSA", "BC");
private static Path keyStorePath;
private static Path certificatePath;
static {
Security.addProvider(new BouncyCastleProvider());
try {
keyStorePath = Paths.get(DigitalSignatureAlgotithm.class.getClassLoader().getResource("testing-keystore.p12").toURI());
certificatePath = Paths.get(DigitalSignatureAlgotithm.class.getClassLoader().getResource("testing-ca.cer").toURI());
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
private String algorithm;
private String encryptionAlgorithm;
private String providerName;
DigitalSignatureAlgotithm(String algorithm, String encryptionAlgorithm, String providerName) {
this.algorithm = algorithm;
this.encryptionAlgorithm = encryptionAlgorithm;
this.providerName = providerName;
}
public String signByPrivateKeyFromKeyStore(String plainText) throws Exception {
PrivateKey privateKey = CryptoUtils.getPrivateKeyFromKeyStore(keyStorePath, "testing-keys", "123456", "PKCS12");
return sign(plainText, privateKey);
}
public String signByPrivateKeyFromFile(String plainText) throws Exception {
String privateKeyStr = CryptoUtils.readPrivateKeyFromFile(this.encryptionAlgorithm);
PrivateKey privateKey = CryptoUtils.getPrivateKeyByStr(this.encryptionAlgorithm, privateKeyStr);
return sign(plainText, privateKey);
}
public String sign(String plainText, PrivateKey privateKey) throws Exception {
Signature signature;
if ("default".equals(providerName)) {
signature = Signature.getInstance(this.algorithm);
} else {
signature = Signature.getInstance(this.algorithm, providerName);
}
signature.initSign(privateKey);
signature.update(plainText.getBytes());
byte[] signatureBytes = signature.sign();
String cipherText = Base64.getEncoder().encodeToString(signatureBytes);
System.out.println(String.format("%s plain text: %s -> digital signature: %s", this.algorithm, plainText, cipherText));
return cipherText;
}
public boolean verifySignatureByPublicKeyFromCA(String plainText, String signatureStr) throws Exception {
PublicKey publicKey = DigitalCertificationUtils.getPublicKeyFromCA(certificatePath, "X.509");
return verifySignature(plainText, signatureStr, publicKey);
}
public boolean verifySignatureByPublicKeyFromFile(String plainText, String signatureStr) throws Exception {
String publicKeyStr = CryptoUtils.readPublicKeyFromFile(this.encryptionAlgorithm);
PublicKey publicKey = CryptoUtils.getPublicKeyByStr(this.encryptionAlgorithm, publicKeyStr);
return verifySignature(plainText, signatureStr, publicKey);
}
public boolean verifySignature(String plainText, String signatureStr, PublicKey publicKey) throws Exception {
Signature signature;
if ("default".equals(providerName)) {
signature = Signature.getInstance(this.algorithm);
} else {
signature = Signature.getInstance(this.algorithm, providerName);
}
signature.initVerify(publicKey);
signature.update(plainText.getBytes());
boolean verifyResult = signature.verify(Base64.getDecoder().decode(signatureStr));
System.out.println(String.format("Signature: %s is %s", signatureStr, verifyResult ? "valid" : "invalid"));
return verifyResult;
}
}
package com.qupeng.crypto.util;
import com.qupeng.crypto.algorithm.oop.NumberGenerationAlgorithm;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.stream.Collectors;
public class CryptoUtils {
public final static Path RSA_PUBLIC_KEY_FILE_PATH = Paths.get("C:", "Users", "Administrator", "IdeaProjects", "javasedemo", "src", "main", "resources", "rsa-public-key.txt");
public final static Path RSA_PRIVATE_KEY_FILE_PATH = Paths.get("C:", "Users", "Administrator", "IdeaProjects", "javasedemo", "src", "main", "resources", "rsa-private-key.txt");
public final static Path DSA_PUBLIC_KEY_FILE_PATH = Paths.get("C:", "Users", "Administrator", "IdeaProjects", "javasedemo", "src", "main", "resources", "dsa-public-key.txt");
public final static Path DSA_PRIVATE_KEY_FILE_PATH = Paths.get("C:", "Users", "Administrator", "IdeaProjects", "javasedemo", "src", "main", "resources", "dsa-private-key.txt");
static {
Security.addProvider(new BouncyCastleProvider());
}
public static void printAllSecurityProviders() {
for (Provider provider : Security.getProviders())
{
System.out.println("Provider: " + provider.getName() + " (ver " + provider.getVersion() + ")");
}
}
public static void printAllAlgorithmsOfProviders() {
for (Provider provider : Security.getProviders())
{
System.out.println("Provider: " + provider.getName() + " (ver " + provider.getVersion() + ")");
System.out.print(" Algorithms: ");
ArrayList<String> algos = new ArrayList<String>();
for (Provider.Service service : provider.getServices())
{
algos.add(String.format( "%s (%s)", service.getAlgorithm(), service.getType()));
}
java.util.Collections.sort(algos);
String algorsStr = algos.toString();
algorsStr = algorsStr.substring(1, algorsStr.length()-1);
System.out.println(algorsStr);
System.out.println();
}
}
public static String printAllSecurityProvidersInMdTable() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Provider Name|Provider Version|Algorithm Type|Algorithm Name\r\n");
stringBuilder.append("|:-|:-|:-|:-\r\n");
Map<String, Map<String, String>> providers2Algorithms = Arrays.stream(Security.getProviders())
.collect(Collectors.toMap(provider -> provider.getName() + "@" + provider.getVersion(), provider -> provider.getServices().stream().collect(Collectors.toMap(service -> service.getType(), service -> service.getAlgorithm(), (algorithm1, algorithm2) -> algorithm1 + "@" + algorithm2))));
providers2Algorithms.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEachOrdered(entryProvider -> {
String[] provider = entryProvider.getKey().split("@");
Map<String, String> algoType2AlgoName = entryProvider.getValue();
int[] rowNumber = {0};
algoType2AlgoName.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEachOrdered(entryAlgorithm -> {
StringBuilder algorithmCellStr = new StringBuilder();
int[] numberOfAlgorithm = {1};
Arrays.stream(entryAlgorithm.getValue().split("@")).sorted(String::compareTo).forEachOrdered(algorithm -> {
algorithmCellStr.append(algorithm);
if (0 == numberOfAlgorithm[0] % 1) {
algorithmCellStr.append("<br>");
}
numberOfAlgorithm[0]++;
});
stringBuilder.append(String.format("|%s|%s|%s|%s\r\n", 0 == rowNumber[0] ? provider[0] : "", 0 == rowNumber[0] ? provider[1] : "", entryAlgorithm.getKey(), algorithmCellStr.toString()));
rowNumber[0]++;
});
});
return stringBuilder.toString();
}
public static void writeKeyPairToFile(KeyPair keyPair, String algorithm) throws IOException {
writeKeyPairToFile(keyPair, "DSA".equalsIgnoreCase(algorithm) ? DSA_PUBLIC_KEY_FILE_PATH : RSA_PUBLIC_KEY_FILE_PATH, "DSA".equalsIgnoreCase(algorithm) ? DSA_PRIVATE_KEY_FILE_PATH : RSA_PRIVATE_KEY_FILE_PATH);
}
public static void writeKeyPairToFile(KeyPair keyPair, Path publicKeyFilePath, Path privateKeyFilePath) throws IOException {
writeKeyToFile(keyPair.getPublic(), publicKeyFilePath);
writeKeyToFile(keyPair.getPrivate(), privateKeyFilePath);
}
public static String readPublicKeyFromFile(String algorithm) throws IOException {
return readKeyStrFromFile("DSA".equalsIgnoreCase(algorithm) ? DSA_PUBLIC_KEY_FILE_PATH : RSA_PUBLIC_KEY_FILE_PATH);
}
public static String readPrivateKeyFromFile(String algorithm) throws IOException {
return readKeyStrFromFile("DSA".equalsIgnoreCase(algorithm) ? DSA_PRIVATE_KEY_FILE_PATH : RSA_PRIVATE_KEY_FILE_PATH);
}
public static String readKeyStrFromFile(Path keyFilePath) throws IOException {
try (FileChannel keyFileChannel = FileChannel.open(keyFilePath, StandardOpenOption.READ)) {
byte[] bytes = new byte[0];
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int readCount = keyFileChannel.read(byteBuffer);
while (0 < readCount) {
byteBuffer.flip();
int length = bytes.length;
bytes = Arrays.copyOf(bytes, bytes.length + readCount);
System.arraycopy(byteBuffer.array(), 0, bytes, length, readCount);
byteBuffer.clear();
readCount = keyFileChannel.read(byteBuffer);
}
String keyStr = new String(bytes);
return keyStr;
}
}
public static void writeKeyToFile(Key key, Path filePath) throws IOException {
byte[] keyBytes = key.getEncoded();
String keyStr = new BASE64Encoder().encode(keyBytes);
if (Files.notExists(filePath)) {
Files.createFile(filePath);
}
try(FileChannel keyFileChannel = FileChannel.open(filePath, StandardOpenOption.WRITE)) {
ByteBuffer byteBuffer = ByteBuffer.allocate(keyStr.getBytes().length);
byteBuffer.put(keyStr.getBytes());
byteBuffer.flip();
keyFileChannel.write(byteBuffer);
}
}
public static PublicKey getPublicKeyByStr(String algorithm, String secretKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
byte[] keyBytes = new BASE64Decoder().decodeBuffer(secretKeyStr);
X509EncodedKeySpec encPubKeySpec = new X509EncodedKeySpec(keyBytes);
PublicKey publicKey = KeyFactory.getInstance(algorithm).generatePublic(encPubKeySpec);
return publicKey;
}
public static PrivateKey getPrivateKeyByStr(String algorithm, String secretKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
byte[] keyBytes = new BASE64Decoder().decodeBuffer(secretKeyStr);
PKCS8EncodedKeySpec encPriKeySpec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey privateKey = KeyFactory.getInstance(algorithm).generatePrivate(encPriKeySpec);
return privateKey;
}
public static KeyPair getKeyPair(String algorithm, int secretKeyBitLength, String randomSeedStr, NumberGenerationAlgorithm ngAlgorithm) throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
SecureRandom random = SecureRandom.getInstance(ngAlgorithm.getAlgorithmName());
random.setSeed(randomSeedStr.getBytes());
keyPairGenerator.initialize(secretKeyBitLength, random);
return keyPairGenerator.generateKeyPair();
}
public static String wrapSecretKey(String keyString) throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128, new SecureRandom("123456".getBytes()));
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.WRAP_MODE, secretKey);
SecretKeySpec key = new SecretKeySpec(keyString.getBytes(), "AES");
byte[] bytes = cipher.wrap(key);
return Hex.encodeHexString(bytes);
}
public static String unwrapSecretKey(String keyString) throws Exception {
byte[] rawKey = Hex.decodeHex(keyString);
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128, new SecureRandom("123456".getBytes()));
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.UNWRAP_MODE, secretKey);
SecretKey key = (SecretKey) cipher.unwrap(rawKey, "AES", Cipher.SECRET_KEY);
return new String(key.getEncoded());
}
public static String wrapPrivateKey() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException {
SecureRandom secureRandom = SecureRandom.getInstance(NumberGenerationAlgorithm.SHA1_PRNG.getAlgorithmName());
secureRandom.setSeed("12345".getBytes());
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.WRAP_MODE, secretKey, secureRandom);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024, secureRandom);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
System.out.println(Hex.encodeHexString(keyPair.getPrivate().getEncoded()));
byte[] keyBytes = cipher.wrap(keyPair.getPrivate());
String wrappedKeyStr = Hex.encodeHexString(keyBytes);
System.out.println(wrappedKeyStr);
return wrappedKeyStr;
}
public static String unwrapPrivateKey(String keyStr) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, DecoderException {
SecureRandom secureRandom = SecureRandom.getInstance(NumberGenerationAlgorithm.SHA1_PRNG.getAlgorithmName());
secureRandom.setSeed("12345".getBytes());
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.UNWRAP_MODE, secretKey, secureRandom);
byte[] keyBytes = Hex.decodeHex(keyStr);
PrivateKey privateKey = (PrivateKey) cipher.unwrap(keyBytes, "RSA", Cipher.PRIVATE_KEY);
return Hex.encodeHexString(privateKey.getEncoded());
}
public static PublicKey getPublicKeyFromCA(Path certificatePath, String certificationType) throws Exception {
CertificateFactory certificateFactory = CertificateFactory.getInstance(certificationType);
try (FileInputStream in = new FileInputStream(certificatePath.toFile())) {
Certificate certificate = certificateFactory.generateCertificate(in);
return certificate.getPublicKey();
}
}
public static PublicKey getPublicKeyFromKeyStore(Path keyStorePath, String alias, String password, String keyStoreType) throws Exception {
try (FileInputStream is = new FileInputStream(keyStorePath.toFile())) {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(is, password.toCharArray());
return keyStore.getCertificate(alias).getPublicKey();
}
}
public static PrivateKey getPrivateKeyFromKeyStore(Path keyStorePath, String alias, String password, String keyStoreType) throws Exception {
try (FileInputStream is = new FileInputStream(keyStorePath.toFile())) {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(is, password.toCharArray());
return (PrivateKey) keyStore.getKey(alias, password.toCharArray());
}
}
}