前言
本节将主要说明一下如何生成一对非对称秘钥,然后使用私钥加密和公钥加密。在前文中我们已经提到了在接口之间的通信为了确保数据的正确性和可用性,我们需要对报文做一个特殊的处理。这个”特殊”的处理有不同的实现,有的使用Token认证,有的使用加签加密,有的使用其他的方法,不论使用哪一种方法我们可以确定的是:是双方约定好的。
p7分离式签名
首先学习生成非对称秘钥和使用公私钥时,我们学习一下什么是p7分离式签名。参考文档
x509是数字证书的规范,P7和P12是两种封装形式。
- P7
P7一般是把证书分成两个文件,一个公钥一个私钥,有PEM和DER两种编码方式。PEM比较多见,就是纯文本的,P7一般是分发公钥用,看到的就是一串可见字符串,扩展名经常是.crt,.cer,.key等。DER是二进制编码。 - P12
P12是把证书压成一个文件,.pfx 。主要是考虑分发证书,私钥是要绝对保密的,不能随便以文本方式散播。所以P7格式不适合分发。.pfx中可以加密码保护,所以相对安全些。
本节中我们将通过实际例子来生成一对X509的P7的DER编码方式非对称秘钥,同时使用该对秘钥做加签解签的正向和逆向验证。
生成非对称秘钥
首先我们需要生成一对X509的P7的DER编码方式非对称秘钥,有兴趣的朋友去看看官网文档
非对称秘钥的生成使用原生JDK里面的封装就OK了。
- 生成非对称秘钥
/**
* 生成一对非对称钥匙,并保存在项目根路径下 PrivateKeyX509.der:私钥文件 PublicKeyX509.der:公钥文件
* @throws Exception
*/
public static void generateRSAKeyPair() throws Exception {
// 获取指定的加密算法,可根据接口加密规则修改
KeyPairGenerator gen = KeyPairGenerator.getInstance(BankConfiguration.KEY_ALGORITHM);
// 生成RSA公私钥对
int KEY_LENGTH = 1024;// 密钥长度,默认1024
gen.initialize(KEY_LENGTH);
KeyPair pair = gen.generateKeyPair();
PublicKey publicKey = pair.getPublic();
PrivateKey privateKey = pair.getPrivate();
// System.out.println("\t公钥格式: " + publicKey.getFormat() + ", 私钥格式: " +
// 将私钥按默认格式编码写入到普通文件中
FileOutputStream privateKeyFile = new FileOutputStream("PrivateKeyPKCS8.der");
privateKeyFile.write(privateKey.getEncoded()); // 对私钥调用getEncoded()获得的是PKCS#8
// DER格式
privateKeyFile.close();
// 将公钥按默认格式编码写入到普通文件中
FileOutputStream publicKeyFile = new FileOutputStream("PublicKeyX509.der");
publicKeyFile.write(publicKey.getEncoded()); // 对公钥调用getEncoded()获得的是X.509
// DER格式
publicKeyFile.close();
logger.info("秘钥对写入成功!");
}
执行如上方法之后会在我们的项目的Root Path下生成一对非对称秘钥
说明:如上图所示,PublicKeyX509.der是非对称秘钥的公钥,PrivateKeyPKCS8.der是私钥,通常而言我公布我们的公钥,保留私钥;对明文A1使用私钥加签,使用公钥解密获得密文A2,若A1=A2则表示规则和算法正确,否则表示在加签和解签的逆运算过程中,要么没有按照规则操作要么秘钥对有异常。
秘钥入径
首先呢秘钥入径是我个人习惯说法,它表示将指定的秘钥对放到项目的指定路加下的过程。以后我们再一次需要使用公钥和私钥时只需要读取指定路径下的文件即可,这样方便我们管理文件。
我们以Maven工程为例子,在src/main/resource目录下新建一个Keys文件夹,然后把私钥对扔进去:
-
然后再Pom.xml文件中加几个jar包:
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
加签解签
本节中我们展示的代码中将牵涉到加签之后加密,解签之前解密的逻辑,因而为了简洁方便我变一次性把代码放出来。
- Keys.java
package com.x509.der.emaples;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
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.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
public class Keys {
private static final Logger logger = Logger.getLogger(Keys.class);
public static final int PRIVATE = 0;
public static final int PUBLIC = 1;
public static String path =Keys.class.getClassLoader().getResource("Keys/").getPath();
/**
* 生成一对非对称钥匙,并保存在项目根路径下 PrivateKeyX509.der:私钥文件 PublicKeyX509.der:公钥文件
* 目前测试环境已生成一对,注释该方法
* @throws Exception
*/
public static void generateRSAKeyPair() throws Exception {
// 获取指定的加密算法,可根据接口加密规则修改
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
// 生成RSA公私钥对
int KEY_LENGTH = 1024;// 密钥长度,默认1024
gen.initialize(KEY_LENGTH);
KeyPair pair = gen.generateKeyPair();
PublicKey publicKey = pair.getPublic();
PrivateKey privateKey = pair.getPrivate();
// System.out.println("\t公钥格式: " + publicKey.getFormat() + ", 私钥格式: " +
// 将私钥按默认格式编码写入到普通文件中
FileOutputStream privateKeyFile = new FileOutputStream("PrivateKeyPKCS8.der");
privateKeyFile.write(privateKey.getEncoded()); // 对私钥调用getEncoded()获得的是PKCS#8
// DER格式
privateKeyFile.close();
// 将公钥按默认格式编码写入到普通文件中
FileOutputStream publicKeyFile = new FileOutputStream("PublicKeyX509.der");
publicKeyFile.write(publicKey.getEncoded()); // 对公钥调用getEncoded()获得的是X.509
// DER格式
publicKeyFile.close();
}
/**
* 加签 value 需加签字段, privateKeyName 秘钥文件名 返回:加签之后的串
*/
public static String addSign(String vale) {
byte[] signatureInBase64 = new byte[] {};
String keypath = path + "PrivateKeyPKCS8.der";
// 签名示例
logger.info("开始签名...");
// 从文件中读取私钥
try {
PrivateKey privateKeyLoadedFromFile = (PrivateKey) getKeyFromFile(keypath, 0);
// 初始化签名算法
Signature sign = Signature.getInstance("SHA256withRSA");
// 建议SHA256withRSA
// sign.initSign(privateKey);//指定签名所用私钥
sign.initSign(privateKeyLoadedFromFile);
// 指定使用从文件中读取的私钥
byte[] data = vale.getBytes();// 待签名明文数据
logger.info("待签名的明文串: " + new String(data));
// 更新用于签名的数据
sign.update(data);
// 签名
byte[] signature = sign.sign();
// 将签名signature转为BASE64编码,用于HTTP传输
Base64 base64 = new Base64();
signatureInBase64 = base64.encodeBase64(signature);
logger.info("Base64格式编码的签名串: " + signatureInBase64);
logger.info("签名结束...");
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new String(signatureInBase64);
}
/**
* 验签 value 待验签明文数据, signature 收到的签名(报文中的签名) publickeyName 公钥文件名
* 使用p2p平台自身公钥验签方法
* @param value 明文
* @param signature 密文
* @return
*/
public static boolean varifySign(String value, String signature) {
String keypath = path + "PublicKeyX509.der";
boolean flag = false;
// 验签示例
logger.info("验签开始...");
// 从文件中读取公钥
try {
PublicKey publicKeyLoadedFromFile = (PublicKey) getKeyFromFile(keypath, 1);
// 将签名signature转为BASE64编码,用于HTTP传输
Base64 base64 = new Base64();
// 从Base64还原得到签名
byte[] signatureFromBase64 = base64.decodeBase64(signature.getBytes());
// 初始化验签算法
Signature verifySign = Signature.getInstance("SHA256withRSA");
// verifySign.initVerify(publicKey);
// 指定验签所用公钥
verifySign.initVerify(publicKeyLoadedFromFile);
byte[] data = value.getBytes();// 待签名明文数据
// data为待验签的数据(明文)
verifySign.update(data);
logger.info("待验签的明文串:" + new String(data) + " 签名(BASE64格式):" + signature);
// 验签
flag = verifySign.verify(signatureFromBase64);
logger.info("验签是否通过:" + flag);// true为验签成功
logger.info("验签结束!...");
} catch (FileNotFoundException e) {
logger.info("验签异常!..." + e);
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
logger.info("验签异常!..." + e);
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
logger.info("验签异常!..." + e);
e.printStackTrace();
} catch (SignatureException e) {
// TODO Auto-generated catch block
logger.info("验签异常!..." + e);
e.printStackTrace();
}
return flag;
}
/**
* 文件读取公共方法
* @param filename
* @param type
* @return
* @throws FileNotFoundException
*/
private static Key getKeyFromFile(String filename, int type) throws FileNotFoundException {
FileInputStream fis = null;
fis = new FileInputStream(filename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b;
try {
while ((b = fis.read()) != -1) {
baos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] keydata = baos.toByteArray();
Key key = null;
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
switch (type) {
case PRIVATE:
PKCS8EncodedKeySpec encodedPrivateKey = new PKCS8EncodedKeySpec(keydata);
key = kf.generatePrivate(encodedPrivateKey);
return key;
case PUBLIC:
X509EncodedKeySpec encodedPublicKey = new X509EncodedKeySpec(keydata);
key = kf.generatePublic(encodedPublicKey);
return key;
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return key;
}
public static void main(String[] args) {
String str = "HelloWorld!";
String sign = addSign(str);
System.out.println("加签加密之后的密文:"+sign);
System.out.println("验签是否成功?"+varifySign(str, sign));
}
}
- 日志
2018/04/20 14:48:14,397 [com.x509.der.emaples.Keys]-[INFO] 开始签名...
2018/04/20 14:48:14,414 [com.x509.der.emaples.Keys]-[INFO] 待签名的明文串: HelloWorld!
2018/04/20 14:48:19,472 [com.x509.der.emaples.Keys]-[INFO] Base64格式编码的签名串: [B@4d405ef7
2018/04/20 14:48:19,472 [com.x509.der.emaples.Keys]-[INFO] 签名结束...
加签加密之后的密文:fh0XwHpIL51twTALjyMX5FThtzZX/r6x8GajwhpjeMuz3kfO0jqK6x6q9ncfaH1I0/iA7CIi+7U5mEAyDizz7qV6muWbsGXFfWfjZsi3EWZrQpUNbnqNrUnMmb/t1j60pcBgqd3lCLXxSK3ojP/j0/o/ja2jNJ6SDqwEWDuIC4Q=
2018/04/20 14:48:19,472 [com.x509.der.emaples.Keys]-[INFO] 验签开始...
2018/04/20 14:48:19,475 [com.x509.der.emaples.Keys]-[INFO] 待验签的明文串:HelloWorld! 签名(BASE64格式):fh0XwHpIL51twTALjyMX5FThtzZX/r6x8GajwhpjeMuz3kfO0jqK6x6q9ncfaH1I0/iA7CIi+7U5mEAyDizz7qV6muWbsGXFfWfjZsi3EWZrQpUNbnqNrUnMmb/t1j60pcBgqd3lCLXxSK3ojP/j0/o/ja2jNJ6SDqwEWDuIC4Q=
2018/04/20 14:48:19,476 [com.x509.der.emaples.Keys]-[INFO] 验签是否通过:true
2018/04/20 14:48:19,476 [com.x509.der.emaples.Keys]-[INFO] 验签结束!...
验签是否成功?true
小结
- 首先我们需要确定我们到底使用哪一种类型的秘钥对,本节中使用的是X509的P7的DER编码方式非对称秘钥。
- 确定好秘钥对类型之后便编写生成秘钥对代码。
- 然后将秘钥对放在项目的指定位置,需要时边去读取。
- 本节内容还只是个很简单很基本的例子,在实际的应用中将会复杂许多。