RSA签名算法
在非对称加密中我们可以看到,甲乙双方要进行通信,用publicKey进行加密,用priavteKey解密,这个时候会出现一个问题,
如果黑客用你的publicKey对消息进行加密,然后冒充甲发送给乙,乙怎么确定这个消息是甲发送,还是有人伪造甲发送的呢,
所以我们就需要数字签证算法,发送加密消息的时候同时需要发送签名,而这个签名是需要甲的privateKey计算的,而乙要
验证这个签名,是否是合法的,它会用甲的publicKey进行验证,如果验证成功,证明这个消息确实是甲发送的
数字签名就是发送方用自己的私钥对消息进行签名,然后接受方用发送方的私钥来验证签名是否有效,我们可以把数字签名
混入了公钥和私钥的一种摘要,publicKey和原始签名和message着三个中的任何一个被修改了,签名都是无效的
数字签名的目的是:
1. 确认信息是某个发送方发送的,因为只有他用自己的privateKey签名,其他人才能用它的publicKey去验证这个签名
2. 发送方还不能抵赖他发送了消息,因为用谁的publicKey成功验证的签名,就一定是谁用privateKey签名的,所以privateKey
就相当于用户的身份
3. 最后数字签名保证在传输过程中没有被修改
常用的数字签名算法有:
1. MD5WithRSA
2. SHA1WithRSA
3. SHA256WithRSA
package com.learn.securl;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* 使用RSA签名算法的时候
* 我们同样要生成privateKey和publicKey
* @author Leon.Sun
*
*/
public class SecRSASignature {
PrivateKey sk;
PublicKey pk;
/**
* 生成公钥/私钥对
* @throws GeneralSecurityException
*/
public SecRSASignature() throws GeneralSecurityException{
/**
* 生成KeyPair的方法和使用RSA加密是一样的
*/
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
/**
* 从以保存的字节中(例如:读取文件)恢复公钥/私钥
* @param pk
* @param sk
* @throws GeneralSecurityException
*/
public SecRSASignature(byte[] pk, byte[] sk) throws GeneralSecurityException{
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pk);
this.pk = kf.generatePublic(pkSpec);
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(sk);
this.sk = kf.generatePrivate(skSpec);
}
/**
* 把私钥导出为字节
* @return
*/
public byte[] getProvateKey(){
return this.sk.getEncoded();
}
/**
* 把公钥导出为字节
* @return
*/
public byte[] getPublicKey(){
return this.pk.getEncoded();
}
/**
* sign by sk
* 然后我们定义一个sign方法
* 用于对数据进行签名
* 当我们使用签名算法的时候
* @param message
* @return
* @throws GeneralSecurityException
*/
public byte[] sign(byte[] message) throws GeneralSecurityException{
/**
* 我们首先通过Signature.getInstance
* 然后传入签名算法的算法名称
* 这里我们使用SHA1withRSA
* 我们就得到一个Signature对象
*/
Signature signature = Signature.getInstance("SHA1withRSA");
/**
* 然后我们调用initSign
* 传入我们的privateKey
* 初始化一个签名
*/
signature.initSign(this.sk);
/**
* 紧接着我们用update方法传入一个消息
* 表示这个消息进行签名
*/
signature.update(message);
/**
* 接着我们用signature的sign方法
* 就可以获得签名以后的字节
*/
return signature.sign();
}
/**
* sign by pk
* 我们再定义一个verify方法来验证签名
* 验证签名的时候
* 我们需要传入原始信息和签名信息
* @param message
* @param sign
* @return
* @throws GeneralSecurityException
*/
public boolean verify(byte[] message, byte[] sign) throws GeneralSecurityException{
/**
* 同样我们调用Signature.getInstance
* 然后传入SHA1withRSA
* 获得一个Signature对象
*/
Signature signature = Signature.getInstance("SHA1withRSA");
/**
* 这个时候我们要调用initVerify
* 然后传入publicKey
* 表示我们要验证的签名
* 验证签名只能使用publicKey
*/
signature.initVerify(this.pk);
/**
* 然后我们传入原始的信息
*/
signature.update(message);
/**
* 最后我们调用verigy并传入签名信息
* 表示对签名进行验证
* 签名的结果为true或者false
*/
return signature.verify(sign);
}
/**
* 现在我们用main来对RSA签名进行测试
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
byte[] message = "Hello, 使用SHA1withRSA算法进行数字签名!".getBytes();
SecRSASignature rsas = new SecRSASignature();
/**
* 我们首先通过sign方法对信息进行签名
*/
byte[] sign = rsas.sign(message);
/**
* 然后打印出签名的结果
*/
System.out.println("sign: " + Base64.getEncoder().encodeToString(sign));
/**
* 然后我们通过verify方法对信息和签名进行验证
*/
boolean verified = rsas.verify(message, sign);
/**
* 然后打印出签名的结果
*
* verify: true
* 我们用原始的公钥进行的签名结果为true,
*/
System.out.println("verify: " + verified);
// 用另一个公钥验证:
/**
* 如果我们创建另一个Signature对象
* 然后用另一个公钥去验证
*/
boolean verified2 = new SecRSASignature().verify(message, sign);
/**
* 我们可以看到验证的结果为false
*
* verify with another public key: false
* 我们用另一个publicKey进行的签名结果为false
*/
System.out.println("verify with another public key: " + verified2);
// 修改原始信息:
/**
* 当我们把原始信息修改为一个字节的时候
*/
message[0] = 100;
/**
* 我们再对信息和签名进行验证
* 我们可以看到改正过的信息验证的结果也应该为false
*
* verify changed message: false
* 我们对原始信息进行的改动结果为false
*/
boolean verified3 = rsas.verify(message, sign);
System.out.println("verify changed message: " + verified3);
}
}
现在我们总结一下:
1. 数字前面就是用发送方的私钥对原始数据进行签名
2. 只有用发送方的公钥才能通过签名验证,这样做的目的是为了防止伪造发送方,防止抵赖发送过信息,
防止发送信息发送过程中被修改
3. 常用的算法有:MD5withRSA,SHA1withRSA,SHA256withRSA