一. RSA加密算法介绍
RSA又叫非对称加密算法,这类加密算法有一对秘钥,其中一个用来加密一个用来解密。这一对秘钥中你可以选择一个作为私钥(自己保存),另一个作为公钥(对外公开)。用私钥加密的内容只能用对应的公钥解密,反之用公钥加密的内容只能用对应的私钥解密。还有一种对称加密算法,其加密秘钥和解密秘钥为同一个秘钥,比如DES。
二. RSA加密过程
假设A 产生了一对秘钥,私钥自己保存,公钥对外公开,且B获得了A的公钥。在A,B通信的过程中:A向B发送信息:A用自己的私钥加密,B只能用A的公钥解密。B向A发送信息:B用A的公钥加密数据,A只能用自己的私钥解密这样就保证了数据的安全传输;但是这中间存在问题,如果B向A发送数据的过程中被C拦截了,且C也有A的公钥,这样C就可以用A的公钥重新加密一份数据发送给A,这样就篡改了B发送给A的数据。为了避免这种情况,就要说到数字签名的作用了。
三.私钥签名,公钥验签
因为在数据传输过程中有可能被篡改,因此我们要使用数字签名技术来校验发送人的身份,并且事后发送人不能抵赖。下面是数字签名的过程:用户还是A和B
1 B向A发送 信息 并且用约定好的摘要算法,把 信息 生成一个摘要,同时B用自己的私钥对这个摘要进行加密,生成的加密摘要就叫B的签名
2 把该信息和摘要一块发送给A
3 A收到B发送的信息,把该信息用相同的摘要算法生成一个摘要,然后用B的公钥解密A发送过来的摘要,得到一个明文摘要,对比这个明文摘要和B生成的摘要,如果相同说明该信息是B发送的并且该信息没有被篡改过。
四.如何生成私钥和公钥
生成公钥和私钥需要调用jdk_1.8下的rt.jar 的包
/*
* 获取私钥
*
* @param keyMap 密钥对
* @return String
*/
public static String getPrivateKey(Map<String, Object> keyMap) {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return Base64.encodeBase64String(key.getEncoded());
}
/**
* 获取公钥
*
* @param keyMap 密钥对
* @return String
*/
public static String getPublicKey(Map<String, Object> keyMap) {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return Base64.encodeBase64String(key.getEncoded());
}
/**
* 生成密钥对(公钥和私钥)
*/
public static Map<String, Object> genKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(INITIALIZE_LENGTH);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
public static void main(String[] args) throws Exception {
Map<String, Object> keyPair = RSAUtil.genKeyPair();
//重新生成公钥和对应的私钥
String publicKey = RSAUtil.getPublicKey(keyPair);
String privateKey = RSAUtil.getPrivateKey(keyPair);
System.out.println("publicKey=+>"+publicKey);
System.out.println("privateKey=+>"+privateKey);
}
五.如何使用私钥进行加密
- RSA加密的方式-》将传递过来的参数通过参数的属性作为key,传递的值作为value.放入到TreeMap 中,并把排除掉sign 属性以外的属性以key = value 的方式放入到set中去重。重新提取属性并使用&连接。最后将整体的字符串进行byte[]转换,并加密成base64位的密码。
- 使用RSA进行加密需要注意对应参数的顺序。
/**
* 校验数字签名
*
* @param params 参数集合
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
* @return boolean
*/
public static boolean verify(Map<String, Object> params, String publicKey, String sign) {
return verify(signData(params).getBytes(), publicKey, sign);
}
public static String signData(Map<String, Object> params) {
Map<String, Object> newParams = new TreeMap<>();
for (Map.Entry<String, Object> entry : params.entrySet()) {
if ("sign".equals(entry.getKey()) || StringUtil.isNull(entry.getValue())) {
continue;
}
newParams.put(entry.getKey(), entry.getValue());
}
List<String> pairs = new ArrayList<>();
for (Map.Entry<String, Object> entry : newParams.entrySet()) {
pairs.add(entry.getKey() + "=" + entry.getValue());
}
String signData = StringUtil.join(pairs.toArray(), "&");
log.info("signData={}", signData);
return signData;
}
/**
* 校验数字签名
*
* @param data 数据
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
* @return boolean
*/
public static boolean verify(byte[] data, String publicKey, String sign) {
try {
byte[] keyBytes = Base64.decodeBase64(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(Base64.decodeBase64(sign));
} catch (Exception e) {
throw new BusinessException("校验数字签名出错");
}
}
六. 如何使用公钥进行验签
public static void main(String[] args) throws Exception {
Map<String, Object> keyPair = RSAUtil.genKeyPair();
Menu menu = new Menu();
menu.setIcon("1");
menu.setName("2");
menu.setPath("3");
menu.setDescription("4");
//入参
Map<String, Object> dataMap = BeanUtil.beanToMap(menu);
//私钥加密生成签名
String sign = RSAUtil.generateRSASign(dataMap, privateKey);
//公钥验签
boolean checkSign = RSAUtil.verify(dataMap, publicKey, sign);
}
总结:RSA 加密在实际中的使用场景
第一种:(甲系统与乙系统双方持有对方的公钥,保留自己的私钥)
RSA加密生成的公钥和私钥实际上是一对互相可以加密解密的钥匙对,换句话说 你可以用私钥加密,用公钥解密。但是由于一些特定的加密工具为了方便加密传输位数而指定了公钥的秘钥值,私钥是根据公钥的值计算得出的,所以一般私钥比较大且安全性更高,留作自己系统去解密其他系统传来的数据。
但是我们说道,公钥是可能给复数个其他系统的,你无法得知是哪个系统给你传输的这个数据,所以就有了签名这一说。现在有甲乙两个系统,甲系统要给乙系统传输数据,甲系统的数据使用自己的私钥对传递的信息进行加密,然后加一个签名字段放入要传输的数据中然后整体数据再使用公钥进行加密,传输给乙系统。乙系统拿到数据后,先使用自己的私钥解密数据,然后剔除签名字段,然后解密签名字段看与剔除签名字段的数据进行对比,这一步叫做验签,如果相等则代表此数据时由甲方传来且数据没有被篡改。
java中也提供了相应的签名方法类Signature可以直接使用,现在假设使用json字符串传输做一次签名验签的案例。
第二种 实际使用的优化方法
rsa算法是一种取模计算,当加密位数很大时(比如2048位生成密钥),实际上消耗的计算量还是很大的,而且由于有签名的存在,相当于传了两份数据过去,一正一反加密验签消耗的计算量就非常大了,为了优化加密传输及减少系统计算量,我们一般需要采取一些优化方法:
1.采取散列算法简化签名
签名实际上只需要验证数据是否没有被篡改/是否是对方系统发来的数据 实际上是没有必要吧整个数据都进行加密传输,可以使用散列算法来进行压缩验证,常用的有sha256和md5,然后将计算后的值进行签名,接收方使用同样的方法进行压缩验签。
2.生成随机对称加密密钥,使用对称加密加密数据,然后将密钥进行RSA加密明文传输
第一种优化虽然优化了签名,减少了数据传输和运算,但是数据本体还是很大,使用rsa加密仍然需要消耗大量的计算能力,于是就有了这种加密方式,生成随机的密钥对称加密数据,然后将对称的密钥进行rsa加密明文传输,对方接受到对称密钥的rsa加密后的字符串,再用私钥解密,得到对称加密的密钥对数据进行解密。