RSA签名算法概述及使用
一、加密算法种类
1、密钥
密钥,一般就是一个字符串或数字,在加密或者解密时传递给加密或解密算法,以使算法能够正确对明文加密或者对密文解密。
2、加密算法分类
大体上分为单向加密和双向加密。
2.1、单向加密
单向加密就是非可逆加密,就是不可解密的加密方法,由于其在加密后会生成唯一的加密串,故而经常用于检测数据传输过程中是否被修改。常见的单向加密有MD5、SHA、HMAC。我们只是把他们作为加密的基础,单纯的以上三种加密并不可靠。
2.2、双向加密
双向加密又可分为对称加密和非对称加密。你想进行加解密操作的时候需要具备两样东西:秘钥和加解密算法。
2.3、对称加密
对称加密算法的特点是加密使用的密钥和解密使用的密钥是相同的。也就是说,加密和解密都是使用的同一个密钥。因此对称加密算法要保证安全性的话,密钥自然要做好保密,只能让使用的人知道,不能对外公开。
2.4、非对称加密
在非对称加密算法中,有公钥和私钥两种密钥,其中,公钥是公开的,不需要保密,私钥由个人持有,必须妥善保管和注意保密。加密和解密使用两种不同的密钥,是它得名的原因。估计大家都听说过RSA,这就是一种常见的,应用很广的非对称加密算法。
二、RSA
1、那RSA 是什么呢?
RSA加密算法是一种非对称加密算法,所谓非对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密。这样就可以在不直接传递密钥的情况下,完成解密。这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。是由一对密钥来进行加解密的过程,分别称为公钥和私钥。两者之间有数学相关,该加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性。通常个人保存私钥,公钥是公开的(可能同时多人持有)。
2、原理
3、RSA加密、签名区别
加密和签名都是为了安全性考虑,但略有不同。常有人问加密和签名是用私钥还是公钥?其实都是对加密和签名的作用有所混淆。简单的说,加密是为了防止信息被泄露,而签名是为了防止信息被篡改。这里举2个例子说明。
第一个场景:战场上,B要给A传递一条消息,内容为某一指令。
RSA的加密过程如下:
(1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
(2)A传递自己的公钥给B,B用A的公钥对消息进行加密。
(3)A接收到B加密的消息,利用A自己的私钥对消息进行解密。
在这个过程中,只有2次传递过程,第一次是A传递公钥给B,第二次是B传递加密消息给A,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行解密,防止了消息内容的泄露。
第二个场景:A收到B发的消息后,需要进行回复“收到”。
RSA签名的过程如下:
(1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
(2)A用自己的私钥对消息加签,形成签名,并将加签的消息和消息本身一起传递给B。
(3)B收到消息后,在获取A的公钥进行验签,如果验签出来的内容与消息本身一致,证明消息是A回复的。
在这个过程中,只有2次传递过程,第一次是A传递加签的消息和消息本身给B,第二次是B获取A的公钥,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行签名,即使知道了消息内容,也无法伪造带签名的回复给B,防止了消息内容的篡改。
但是,综合两个场景你会发现,第一个场景虽然被截获的消息没有泄露,但是可以利用截获的公钥,将假指令进行加密,然后传递给A。第二个场景虽然截获的消息不能被篡改,但是消息的内容可以利用公钥验签来获得,并不能防止泄露。所以在实际应用中,要根据情况使用,也可以同时使用加密和签名,比如A和B都有一套自己的公钥和私钥,当A要给B发送消息时,先用B的公钥对消息加密,再对加密的消息使用A的私钥加签名,达到既不泄露也不被篡改,更能保证消息的安全性。
总结:公钥加密、私钥解密、私钥签名、公钥验签。
三、测试代码
RSAUtil 工具类
public class RsaUtil {
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
public static final String privateKeyA = "privateKeyA";
public static final String publicKeyA = "publicKeyA";
public static final String publicKeyB = "publicKeyB";
public static final String privateKeyB = "privateKeyB";
/**
* 获取私钥
* @param privateKey 私钥字符串
* @return PrivateKey
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
return keyFactory.generatePrivate(keySpec);
}
/**
* 获取公钥
* @param publicKey 公钥字符串
* @return PublicKey
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return keyFactory.generatePublic(keySpec);
}
/**
* 随机生成密钥对
* @throws NoSuchAlgorithmException
*/
public static Map<Integer,String> genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024,new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
Map<Integer, String> keyMap = new HashMap();
keyMap.put(0,publicKeyString); //0表示公钥
keyMap.put(1,privateKeyString); //1表示私钥
return keyMap;
}
/**
* RSA公钥加密
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt( String str, String publicKey ) throws Exception{
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] data = str.getBytes("UTF-8");
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
String outStr = Base64.encodeBase64String(encryptedData);
return outStr;
}
/**
* RSA私钥解密
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey) throws Exception{
//64位解码加密后的字符串
byte[] data = Base64.decodeBase64(str.getBytes("UTF-8"));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
String outStr = new String(decryptedData);
return outStr;
}
/**
* 签名
* @param data 待签名数据
* @param privateKey 私钥
* @return 签名
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
byte[] keyBytes = privateKey.getEncoded();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey key = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(key);
signature.update(data.getBytes());
return new String(Base64.encodeBase64(signature.sign()));
}
/**
* 验签
* @param srcData 原始字符串
* @param publicKey 公钥
* @param sign 签名
* @return 是否验签通过
*/
public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {
byte[] keyBytes = publicKey.getEncoded();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(key);
signature.update(srcData.getBytes());
return signature.verify(Base64.decodeBase64(sign.getBytes()));
}
}
测试类client
假设 生成两对公钥、私钥,如上的工具类中,以便下面使用
public class TestClient {
public static void main(String[] args) {
// client 是 A server 是 B
// testSign();
// testPassword();
testSignAndPassword();
}
public static void testPassword(){
TestServer testServer = new TestServer();
try {
PasswordOrder passwordOrder = new PasswordOrder("1",new BigDecimal(100),"描述信息","weixin");
System.out.println("加密前的数据:" + JSONObject.toJSONString(passwordOrder));
String encrypt = RsaUtil.encrypt(JSONObject.toJSONString(passwordOrder), RsaUtil.publicKeyA);
System.out.println("加密后的数据:" + encrypt);
testServer.testPassword(encrypt);
} catch (Exception e){
e.printStackTrace();
}
}
public static void testSign(){
try {
TestServer testServer = new TestServer();
ClientOrder order = new ClientOrder("1",new BigDecimal(100),"描述信息","weixin");
String data = MapUtil.mapToString(MapUtil.beanToMap(order));
String sign = RsaUtil.sign(data, RsaUtil.getPrivateKey(RsaUtil.privateKeyA));
order.setSign(sign);
System.out.println("客户端请求数据为原数据和sign:" + JSONObject.toJSONString(order));
testServer.testSign(order);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 先签名后加密,最后把密文传到服务端
*/
public static void testSignAndPassword(){
try {
TestServer testServer = new TestServer();
ClientOrder order = new ClientOrder("1",new BigDecimal(100),"描述信息","weixin");
String data = MapUtil.mapToString(MapUtil.beanToMap(order));
String sign = RsaUtil.sign(data, RsaUtil.getPrivateKey(RsaUtil.privateKeyA));
order.setSign(sign);
System.out.println("签名:" + sign);
System.out.println("加密前的数据:" + JSONObject.toJSONString(order));
String encrypt = RsaUtil.encrypt(JSONObject.toJSONString(order), RsaUtil.publicKeyB);
System.out.println("加密后的数据:" + encrypt);
testServer.testSignAndPassword(encrypt);
} catch (Exception e){
e.printStackTrace();
}
}
}
测试类server
public class TestServer {
public void testSign(ClientOrder order){
try {
System.out.println("服务端收到的数据:" + JSONObject.toJSONString(order));
String sign = order.getSign();
order.setSign(null);
String data = MapUtil.mapToString(MapUtil.beanToMap(order));
boolean verify = RsaUtil.verify(data, RsaUtil.getPublicKey(RsaUtil.publicKeyA), sign);
System.out.println("验签结果:" + verify);
} catch (Exception e){
e.printStackTrace();
}
}
public void testPassword(String encrypt) {
try {
String decrypt = RsaUtil.decrypt(encrypt, RsaUtil.privateKeyA);
PasswordOrder passwordOrder = JSONObject.parseObject(decrypt, PasswordOrder.class);
System.out.println("解密后的数据:" + JSONObject.toJSONString(passwordOrder));
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 先解密,再验签
* @param encrypt
*/
public void testSignAndPassword(String encrypt) {
try {
System.out.println("收到的数据:" + encrypt);
String decrypt = RsaUtil.decrypt(encrypt, RsaUtil.privateKeyB);
System.out.println("解密后的数据:" + decrypt);
ClientOrder clientOrder = JSONObject.parseObject(decrypt, ClientOrder.class);
String sign = clientOrder.getSign();
clientOrder.setSign(null);
String data = MapUtil.mapToString(MapUtil.beanToMap(clientOrder));
boolean verify = RsaUtil.verify(data, RsaUtil.getPublicKey(RsaUtil.publicKeyA), sign);
System.out.println("验签结果:" + verify);
} catch (Exception e){
e.printStackTrace();
}
}
}
注意:
1、因为rsa在加解密的时候,有长度限制,所以在加解密的时候应该采用分段式加解密。
2、当需要同时使用加解密的时候,必须是先签名后加密,假设是 A 请求 B ,A用A的私钥生成签名,再用B的公钥加密,最后把密文传B,B 收到密文的时候,先用B的私钥进行解密,再用A的公钥验签。
使用场景:
1、只对数据加密 , 此时只需要server端生成一对公私钥,把公钥交给client,这种情况主要用在web与服务器的交互,比如传密码之类的。(防泄漏)
2、只对数据签名,此时只需要client端生成一对公私钥,把公钥交给server,这种情况主要用在公司内部与第三方对接,比如,公司有个项目A需要接入微信,支付宝,京东等,即,A 是client,第三方则是server,这样每次调用第三方都需要把数据签名后,发给第三方,然后进行验签。(防篡改)
3、对数据进行签名和加密,此时client和server两方各自生成一对公私钥,相互交换公钥,这种情况主要用在企业之间的数据传输,比如 甲 乙 两个企业要通信。(防篡改和防泄漏)