注意:本节内容主要参考自
- 《Java加密与解密的艺术(第2版)》第9章“带密钥的消息摘要算法--数字签名算法”
- 《大型分布式网站架构(设计与实践)》第3章“互联网安全架构”
14.1、数字签名算法
特点:
- 非对称加密算法+消息摘要算法的结合体
- 抗否认性、认证数据来源、防止数据被篡改(具体意思与做法查看下边的过程与类比部分)
- 私钥加密(签名)、公钥解密(验证)
过程:
1)消息发送者产生一个密钥对(私钥+公钥),然后将公钥发送给消息接收者
2)消息发送者使用消息摘要算法对原文进行加密(加密后的密文称作摘要)
3)消息发送者将上述的摘要使用私钥加密得到密文--这个过程就被称作签名处理,得到的密文就被称作签名(注意,这个签名是名词)
4)消息发送者将原文与密文发给消息接收者
5)消息接收者使用公钥对密文(即签名)进行解密,得到摘要值content1
6)消息接收者使用与消息发送者相同的消息摘要算法对原文进行加密,得到摘要值content2
7)比较content1是不是与content2相等,若相等,则说明消息没有被篡改(消息完整性),也说明消息却是来源于上述的消息发送方(因为其他人是无法伪造签名的,这就完成了“抗否认性”和“认证消息来源”)
类比:
手写签名:
- 张三在合同上签了自己的名字,这样张三在后来想否认自己的这个合同内容时,就不行了(抗否认性)
- 在张三签过名之后,若合同内容又发生了变化,这个时候签名就可以看做无效了
- 当然,我们通过合同上张三的签名,我们就可以知道签名的来源是张三(认证数据来源)
常见的数字签名算法:
- RSA(数字签名算法的经典,也是最常用的数字签名算法)
- DSA(是后续数字签名算法的基础)
- ECDSA(ECC+DSA的结合体,相较于其他数字签名算法,速度快,强度高,签名短,但是使用还没有RSA广泛)
14.2、RSA
常见算法:
- MD5WithRSA(JDK6)
- SHA1WithRSA(JDK6)
- SHA256WithRSA(>=JDK1.6.45,Bouncy Castle-->简称BC)
注意:在《Java加密与解密(第二版)》一书中,说JDK6不支持SHA256WithRSA,但是经过我自己测试1.6.45是支持的。
实现方式:(参考上边三行)
- JDK
- BC
14.2.1、基于JDK6实现的MD5WithRSA或SHA1WithRSA或SHA256WithRSA算法
1 package com.uti.rsa.digital; 2 3 import java.io.UnsupportedEncodingException; 4 import java.security.InvalidKeyException; 5 import java.security.KeyFactory; 6 import java.security.KeyPair; 7 import java.security.KeyPairGenerator; 8 import java.security.NoSuchAlgorithmException; 9 import java.security.PrivateKey; 10 import java.security.PublicKey; 11 import java.security.Signature; 12 import java.security.SignatureException; 13 import java.security.spec.InvalidKeySpecException; 14 import java.security.spec.PKCS8EncodedKeySpec; 15 import java.security.spec.X509EncodedKeySpec; 16 17 import org.apache.commons.codec.binary.Base64; 18 19 /** 20 * 基于BC的RSA数字签名算法 21 */ 22 public class RSACoderBC { 23 private static final String ENCODING = "UTF-8"; 24 private static final String KEY_ALGORITHM = "RSA";//非对称加密密钥算法 25 private static final String SIGNATURE_ALGORITHM = "MD5withRSA";//指定数字签名算法(可以换成SHA1withRSA或SHA256withRSA) 26 private static final int KEY_SIZE = 512;//非对称密钥长度(512~1024之间的64的整数倍) 27 28 /** 29 * 生成发送方密钥对 30 */ 31 public static KeyPair initKey() throws NoSuchAlgorithmException{ 32 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);//密钥对生成器 33 keyPairGenerator.initialize(KEY_SIZE);//指定密钥长度 34 KeyPair keyPair = keyPairGenerator.generateKeyPair();//生成密钥对 35 return keyPair; 36 } 37 38 /** 39 * 还原公钥 40 * @param pubKey 二进制公钥 41 */ 42 public static PublicKey toPublicKey(byte[] pubKey) throws NoSuchAlgorithmException, 43 InvalidKeySpecException{ 44 KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);//密钥工厂 45 return keyFactory.generatePublic(new X509EncodedKeySpec(pubKey));//还原公钥 46 } 47 48 /** 49 * 还原私钥 50 * @param priKey 二进制私钥 51 */ 52 public static PrivateKey toPrivateKey(byte[] priKey) throws NoSuchAlgorithmException, 53 InvalidKeySpecException{ 54 KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);//密钥工厂 55 return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(priKey));//还原私钥 56 } 57 58 /** 59 * 私钥加密(签名) 60 * @param data 待加密数据 61 * @param keyByte 私钥 62 */ 63 public static byte[] encryptPriKey(String data, byte[] keyByte) throws NoSuchAlgorithmException, 64 InvalidKeySpecException, 65 InvalidKeyException, 66 SignatureException, 67 UnsupportedEncodingException { 68 PrivateKey priKey = toPrivateKey(keyByte);//还原私钥 69 70 Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); 71 signature.initSign(priKey); 72 signature.update(data.getBytes(ENCODING)); 73 74 return signature.sign(); 75 } 76 77 /** 78 * 公钥解密(验证) 79 * @param data 原文(待加密数据,也成为“待校验数据”) 80 * @param keyByte 公钥 81 * @param sign 密文(也称作“签名”) 82 */ 83 public static boolean decryptPubKey(String data, byte[] keyByte, byte[] sign) throws NoSuchAlgorithmException, 84 InvalidKeySpecException, 85 InvalidKeyException, 86 SignatureException, 87 UnsupportedEncodingException { 88 PublicKey pubKey = toPublicKey(keyByte);//还原公钥 89 90 Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); 91 signature.initVerify(pubKey); 92 signature.update(data.getBytes(ENCODING)); 93 94 return signature.verify(sign); 95 } 96 97 /** 98 * 获取公钥 99 */ 100 public static byte[] getPublicKey(KeyPair keyPair){ 101 return keyPair.getPublic().getEncoded(); 102 } 103 104 /** 105 * 获取私钥 106 */ 107 public static byte[] getPrivateKey(KeyPair keyPair){ 108 return keyPair.getPrivate().getEncoded(); 109 } 110 111 /** 112 * 测试 113 */ 114 public static void main(String[] args) throws NoSuchAlgorithmException, 115 InvalidKeyException, 116 InvalidKeySpecException, 117 SignatureException, 118 UnsupportedEncodingException { 119 byte[] pubKey1;//甲方公钥 120 byte[] priKey1;//甲方私钥 121 122 /*********************测试是否可以正确生成以上2个key*********************/ 123 KeyPair keyPair1 = RSACoderBC.initKey();//生成甲方密钥对 124 pubKey1 = RSACoderBC.getPublicKey(keyPair1); 125 priKey1 = RSACoderBC.getPrivateKey(keyPair1); 126 127 System.out.println("甲方公钥pubKey1-->"+Base64.encodeBase64String(pubKey1)+"@@pubKey1.length-->"+pubKey1.length); 128 System.out.println("甲方私钥priKey1-->"+Base64.encodeBase64String(priKey1)+"@@priKey1.length-->"+priKey1.length); 129 130 /*********************测试甲方使用私钥加密数据向乙方发送,乙方使用公钥解密数据*********************/ 131 System.out.println("甲方-->乙方"); 132 String data = "找一个好姑娘啊!你好吗,孩子"; 133 byte[] encodeStr = RSACoderBC.encryptPriKey(data, priKey1);//加密(签名) 134 System.out.println("甲方加密后的数据-->"+Base64.encodeBase64String(encodeStr)+"@@encodeStr.length-->"+encodeStr.length); 135 boolean decodeStr = RSACoderBC.decryptPubKey(data, pubKey1, encodeStr); 136 System.out.println("乙方检验结果-->"+decodeStr); 137 } 138 }
注意点:
- 代码与RSA非对称加密算法(查看第十二章)基本一样,只是将其中的私钥加密与公钥解密部分的加解密算法改为签名和验证算法,当然没有“公钥加密,私钥解密”
疑问:在《Java加密与解密的艺术(第二版)》一书中,作者说:“RSA数字签名算法的签名值与密钥长度相同”,但是在我的测试中,结果不一致:
1)在我配置非对称密钥为512的时候,测出来的公钥长度是96位,私钥长度是345位,与512差很远,那这里的512到底是怎么计算的?
2)消息摘要的结果长度是64(摘要值的二进制自己数组长度),不知道到底是怎么与密钥长度相同的?
以上两个问题,有知道的朋友请指点一下,谢谢!