公钥私钥的原则:
为什么用非对称加密?
一个网站要对传输进行加密,如果用对称加密,则有以下几种情况:
1 每个访问用户的秘钥相同。这种情况,服务器只要保存一个秘钥。除非你的网站用户是特定的内部用户,否则用户拿到秘钥就能对密文进行解密。
2 每个访问用户的秘钥不一样。这种情况,服务器需要保存大量秘钥。如果网站有上亿用户,则需要存储上亿的秘钥,维护成本巨大。
如果是非对称加密,网站只需要保存自己的私钥,用户可以随意下载网站公钥。
数字签名,可以保证收到的文件没有被篡改,也可以保证发送者的身份。因为私钥生产了数字签名,私钥是不公开的。
说了这么多,还是写段代码来试试看。
/**
* Bestpay.com.cn Inc.
* Copyright (c) 2011-2018 All Rights Reserved.
*/
package rsa;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
/**
*
* @author huyajun
* @version $Id: RSAsecurityTest.java, v 0.1 2018年3月14日 下午6:09:53 huyajun Exp $
*/
public class RSAsecurityTest {
public static String PUBLIC_KEY = "pub_key";
public static String PRIVATE_KEY = "pri_key";
/**
* BASE64解密
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) {
return Base64.getDecoder().decode(key);
}
/**
* BASE64加密
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) {
return Base64.getEncoder().encodeToString(key);
}
/**
* 初始化密钥对
*
* @return
*/
public static Map<String, String> initRsaKey() {
//1.初始化秘钥
KeyPairGenerator keyPairGenerator;
try {
//1.初始化秘钥
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
//秘钥长度
keyPairGenerator.initialize(512);
//初始化秘钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//公钥
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
//私钥
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, String> keyMap = new HashMap<String, String>(2);
keyMap.put(PUBLIC_KEY, encryptBASE64(rsaPublicKey.getEncoded()));
keyMap.put(PRIVATE_KEY, encryptBASE64(rsaPrivateKey.getEncoded()));
return keyMap;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
///////////////////////////////////////////////////////////////////////
/**
* 公钥处理,返回base64编码的字符串
* @param file
* @param rsaPublicKeyStr
* @param model 加密:Cipher.ENCRYPT_MODE;解密:Cipher.DECRYPT_MODE
* @return 公钥处理后的字符串,base64编码
*/
public static String publicKeyDeal(String file, String rsaPublicKeyStr, int model) {
try {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
decryptBASE64(rsaPublicKeyStr));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(model, publicKey);
byte[] result = cipher.doFinal(decryptBASE64(file));
// 必须用base64 进行编解码
return encryptBASE64(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 私钥 处理,返回base64编码的字符串
* @param file
* @param rsaPrivateKeyStr
* @param model 加密:Cipher.ENCRYPT_MODE;解密:Cipher.DECRYPT_MODE
* @return 私钥处理后的字符串,base64编码
*/
public static String privateKeyDeal(String file, String rsaPrivateKeyStr, int model) {
try {
//生成私钥
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
decryptBASE64(rsaPrivateKeyStr));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
//初始化加密
cipher.init(model, privateKey);
byte[] result = cipher.doFinal(decryptBASE64(file));
//不能返回, new String(result) ,会出现乱码,导致没法解码
// 必须用base64 进行编解码
return encryptBASE64(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String EncoderByMd5(String str) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] result = md5.digest(str.getBytes("utf-8"));
return toHex(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String toHex(byte[] bytes) {
final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
StringBuilder ret = new StringBuilder(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
}
return ret.toString();
}
public static void main(String[] args) {
Map<String, String> keyMap = initRsaKey();
String file = "甜橙金融,互联网金融行业第三";
// 公钥加密,私钥解密,常规用法;
if (keyMap != null) {
String str = publicKeyDeal(encryptBASE64(file.getBytes()), keyMap.get(PUBLIC_KEY),
Cipher.ENCRYPT_MODE);
System.out.println("公钥对明文加密:" + str);
String str2 = privateKeyDeal(str, keyMap.get(PRIVATE_KEY), Cipher.DECRYPT_MODE);
System.out.println("私钥对密文解密:" + new String(decryptBASE64(str2)));
}
System.out.println("");
// 私钥加密,公钥解密,得到明文;-- 数字签名 用这个道理
if (keyMap != null) {
String str = privateKeyDeal(encryptBASE64(file.getBytes()), keyMap.get(PRIVATE_KEY),
Cipher.ENCRYPT_MODE);
System.out.println("私钥对明文加密:" + str);
String str2 = publicKeyDeal(str, keyMap.get(PUBLIC_KEY), Cipher.DECRYPT_MODE);
System.out.println("公钥对密文解密:" + new String(decryptBASE64(str2)));
}
System.out.println("");
/////////////////////////////////////////
// 数字签名
// 生产摘要
String md5Str = EncoderByMd5(file);
System.out.println("发送方生成摘要:" + md5Str);
//2用私钥 对摘要进行加密
String signStr = privateKeyDeal(encryptBASE64(md5Str.getBytes()), keyMap.get(PRIVATE_KEY),
Cipher.ENCRYPT_MODE);
System.out.println("发送方用私钥对摘要加密:" + signStr);
//3 发送原文+摘要密文(数字签名) 给接收方
System.out.println("接收方收到原文和摘要");
//4 接收方用公钥解密摘要
String md5Str_Decrypt = new String(decryptBASE64(publicKeyDeal(signStr,
keyMap.get(PUBLIC_KEY), Cipher.DECRYPT_MODE)));
System.out.println("接收方用公钥对摘要解密:" + md5Str_Decrypt);
//5 用同样的算法对原文生成摘要
String md5Str2 = EncoderByMd5(file);
System.out.println("接收方生成的摘要:" + md5Str2);
//6 对比公钥解密的摘要和用原文生成的摘要
if (md5Str2.equals(md5Str_Decrypt)) {
System.out.println("数字签名验证成功!");
} else {
System.out.println("数字签名验证失败!");
}
}
}
代码可以看出来:用公钥加密后,用对应的私钥可以解密;反过来,私钥加密后,用对应的公钥也可以解密。
整个数字签名的流程稍微有点复杂。Java 还提供了专门的签名类,省去了这些繁琐的步骤。
代码如下:
/**
* Bestpay.com.cn Inc.
* Copyright (c) 2011-2018 All Rights Reserved.
*/
package rsa;
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.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author huyajun
* @version $Id: SignTest.java, v 0.1 2018年4月22日 下午7:29:08 huyajun Exp $
*/
public class SignTest {
private static final String PUBLIC_KEY = "PUBLIC_KEY";
private static final String PRIVATE_KEY = "PRIVATE_KEY";
/**
*
* @param args
*/
public static void main(String[] args) {
Map<String, String> keyMap = initRsaKey();
String content = "123";
String sign = null;
try {
sign = sign(content.getBytes(), keyMap.get(PRIVATE_KEY));
System.out.println("私钥签名结果:" + sign);
} catch (Exception e) {
e.printStackTrace();
}
//5.公钥验签
try {
boolean flag = verify(content.getBytes(), keyMap.get(PUBLIC_KEY), sign);
System.out.println("公钥验证数字签名:" + flag);
} catch (Exception e) {
e.printStackTrace();
}
}
////////////////////////////////////////////////////////
/**
* BASE64解密
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) {
return Base64.getDecoder().decode(key);
}
/**
* BASE64加密
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) {
return Base64.getEncoder().encodeToString(key);
}
/**
* 初始化密钥对
*
* @return
*/
public static Map<String, String> initRsaKey() {
//1.初始化秘钥
KeyPairGenerator keyPairGenerator;
try {
//1.初始化秘钥
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
//秘钥长度
keyPairGenerator.initialize(512);
//初始化秘钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//公钥
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
//私钥
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, String> keyMap = new HashMap<String, String>(2);
keyMap.put(PUBLIC_KEY, encryptBASE64(rsaPublicKey.getEncoded()));
keyMap.put(PRIVATE_KEY, encryptBASE64(rsaPrivateKey.getEncoded()));
return keyMap;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
/**
* 私钥签名
*
* @param data 原文件
* @param privateKey
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
//构造PKCS8EncodedKeySpec对象
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decryptBASE64(privateKey));
//指定加密算法
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//取私钥匙对象
PrivateKey privateKeyObj = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
//用私钥对信息生成数字签名
Signature signature = Signature.getInstance("MD5withRSA");// MD2withRSA SHA1withRSA MD5withRSA
signature.initSign(privateKeyObj);
signature.update(data);
return encryptBASE64(signature.sign());
}
/**
* 校验数字签名
* @param data 加密数据
* @param publicKey 公钥
* @param sign 数字签名
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
//构造X509EncodedKeySpec对象
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decryptBASE64(publicKey));
//指定加密算法
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//生成公钥匙对象
PublicKey publicKeyObj = keyFactory.generatePublic(x509EncodedKeySpec);
Signature signature = Signature.getInstance("MD5withRSA");// MD2withRSA SHA1withRSA MD5withRSA
signature.initVerify(publicKeyObj);
signature.update(data);
//验证签名是否正常
return signature.verify(decryptBASE64(sign));
}
}
Signature 类帮我们实现了生成数字签名和校验数字签名的方法,直接用,比自己去实现方便的多。
Signature.getInstance("MD5withRSA"); 表示用MD5做摘要,用RSA做加解密。同理你还可以选择SHA1withRSA 。 注意生成签名和验签的方法要相同。
今天就讲到这里,下一篇文章,我们介绍区块链知识点之--共识算法。敬请期待
作者简介:甜橙金融技术部技术总监,负责公司核心平台设计与开发,新技术预研与落地。10年代码和架构设计经验,之前在国内知名一线互联网公司从事核心系统的开发和设计。