由于最近负责项目的license模块,对称加密、非对称加密、摘要加密、签名都大量用到,所以想写个系列博客探究下Java中各种加密算法的使用和注意事项,既然这样,那就先从签名开始吧!
No.1 签名是什么
现实中,由于我们每个人的笔迹近似独一无二,所以一旦我们在文件中签字就无法抵赖说不是自己签的,因为对方可以做笔迹鉴定。
计算机的世界更加错综复杂,A向B发送了一个文件,中途可能别拦截,然后可能被篡改或者替换,B怎么知道收到的文件一定是A发送的原件呢?
- 为了解决这个问题,聪明的计算机大佬们发明了数字签名这么个东东,数字签名并不是说这个签名是一串数字,而是为了区分传统的手写签名。
- A发送文件的同时也把这个文件的签名发送给B,B只要验证文件签名是正确的,就可以认为文件是A发送的而且是原件。
也就是说,签名可以让B很自信的确定收到的文件是否被 篡改 和 替换 。
No.2 签名是怎么产生的
我们先来看看签名是怎么生成的,简单来说有如下几步:
- 生成唯一的非对称加密公钥和私钥对,一般是RSA算法;
- 对要发送的文件计算摘要,一般是MD5算法;
- 使用私钥对摘要进行加密,得到签名
为什么要计算摘要呢?主要是出于性能考虑,非对称加密安全性较好,但是不足之处是加密和解密过程复杂,如果要加密的字符很多多性能影响会很大。所以一般没有人会对整个问价进行加密生成签名,虽然这样也能达到签名的目的。
为什么使用私钥加密?大部分情况下,我们使用公钥加密、私钥解密,但是签名却是使用私钥加密,公钥解密。这是因为如果用公钥加密,则必须把私钥发送给要验证签名的一方,不仅麻烦而且很危险;而使用私钥加密,公钥验证方可以很容易得到,不存在泄漏风险,而且由于发送的签名只是摘要,就算被拦截用公钥解密,得到的信息也只是一串字符,没有任何意义。
综上所示,签名的生成方生成签名,并把签名和公钥发送给验证方,验证方根据公钥解密得到摘要,然后对接收到的文件重新计算摘要,两个摘要一样就说明文件没有被替换和篡改。
No.3 签名 VS 篡改+替换
为什么签名可以防止篡改呢?因为签名是通告摘要算法(MD5)产生的,任何一位数据的修改都会导致摘要发生变化,所以验证方只要判断签名解密后的摘要和文件计算的摘要一致,就说明文件未被篡改。
为什么签名可以防止文件被整个替换呢?如果攻击者另外找一个假文件,自己生成假的签名呢?别忘了,因为签名生成方产生的公钥和私钥对是唯一的,而且公钥验证方可以很容易获取到,如果签名是伪造的,则解密会出现错误,或者解密得到的摘要和源文件摘要会大不相同。
综上所示,B通过验证签名就可以知道这个文件一定是A发送的,而且一定是A发送的原件,所以就可以无条件的信任这个文件啦~
No.4 Java签名生成与验证
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class SignatureUtil{
/**得到产生的私钥/公钥对
* @return KeyPair
*/
public static KeyPair getKeypair(){
//产生RSA密钥对(myKeyPair)
KeyPairGenerator myKeyGen = null;
try {
myKeyGen = KeyPairGenerator.getInstance("RSA");
myKeyGen.initialize(1024);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
KeyPair myKeyPair = myKeyGen.generateKeyPair();
return myKeyPair;
}
/**根据私钥和信息生成签名
* @param privateKey
* @param data
* @return 签名的Base64编码
*/
public static String getSignature(PrivateKey privateKey,String data){
Signature sign;
String res = "";
try {
sign = Signature.getInstance("MD5WithRSA");
sign.initSign(privateKey);
sign.update(data.getBytes());
byte[] signSequ = sign.sign();
res = Base64.getEncoder().encodeToString(signSequ);
}catch(NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return res;
}
/**验证签名
* @param publicKey 公钥的Base64编码
* @param sign 签名的Base64编码
* @param data 生成签名的原数据
* @return
*/
public static boolean verify(String publicKey, String sign, String data){
boolean res = true;
try {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(publicK);
signature.update(data.getBytes());
res = signature.verify(Base64.getDecoder().decode(sign));
}catch(NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeySpecException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return res;
}
public static void main(String[] args) {
String data = "给我签名吧!";
/*(1)生成公钥和私钥对*/
KeyPair keyPair = getKeypair();
String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
System.out.println("公钥:" + publicKey);
System.out.println("私钥:" + privateKey);
/*(2)用私钥生成签名*/
PrivateKey pk = keyPair.getPrivate();
String signStr = getSignature(pk,data);
System.out.println("签名是:" + signStr);
/*(3)验证签名*/
System.out.println("验证签名的结果是:" + verify(publicKey,signStr,data));
}
}
运行上面代码得到如下结果:
公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDElEXn3KJdoIAGhM3lDwnxm0a1w+G6WQpZrDkeBSJtarWEMXZ6+dF5aL+yGeXtHyCmo0X97mgfEwoe9ElK4u0iDb4zmPB9K8IC5+3K2+XJcQKdtSVO9AOp1SDxr5AT3kHHe+HBaknQdnqkb4dNc5nAUAXAK1FRTP0vgRAb9R+iSwIDAQAB
私钥:MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMSURefcol2ggAaEzeUPCfGbRrXD4bpZClmsOR4FIm1qtYQxdnr50Xlov7IZ5e0fIKajRf3uaB8TCh70SUri7SINvjOY8H0rwgLn7crb5clxAp21JU70A6nVIPGvkBPeQcd74cFqSdB2eqRvh01zmcBQBcArUVFM/S+BEBv1H6JLAgMBAAECgYAIQrrVTX49NPtsSrRkRceDMaU9Cig4Lnmy3vvfeRPDSVKrZXC3JjxZP7+eelwhJMe4eO/+BcC2XZR1TIqv7O1OCBjuI42hyEnqntCZ1PeSMtp3UQ3SmTEwo6X16qp4OPz4GQrlMGwnWhARGDZsXJdwWiUHAWwmC/DUYRiowkZQoQJBAPbwTVGowwqibEB2n1SDu0DyNSFxlJPhBAv+qgxWDbkSprDr/NY6nLf+0tOmXnTNP4MFuuFYkdkn4i60I+nZhekCQQDLyucpvdeyWSLzBJanalAll11H7iLzBmedBGN3UM763xXSrQ4Tp9s66ZW4LpdWMhvjvSvqrYDKwh+YKlgSy+ITAkBBrrliNxlqArn4i5TlzgRIyiQHuUZj7z48Uoi4r0sHJ0bfWGXwNbbp2gYJ9f654r46A5QpzH0+3bTz50aGNS3BAkEAxA2HJZkFEQa/oJshdB3KzN85ViG6baITu/Kk3fxXovFKxUrG6BHrzlk5N99aqAm82vL6dOJFrMnkKzdRU4PhEQJBANAiS9e2nU0r8m203zlti8Ee8Y+SqVzxhPDF3HjL1Zf9SxF0CNe67AABMk5VB9SnOGiuz5X862ZAiCtKJMi+ASs=
签名是:l55XNEvRwTo24mcEtpggsK6IPWOo11ar9wa7ZuKCbBen06wj3tOkBgZvzW5Rmw+bybr1lByhV1SC4X+LuFMVewf5aNOLseKlMBd7ERv2nUyfrxk7ymL9NtdJy/gKOreB/IK0joULHa/m6crjVCiiyls3MbNPfomvmDqH0VbmKYs=
验证签名的结果是:true