android中利用Openssl代码中动态生成证书生成证书
一、 需求背景
公司开发的支付系统中的App考虑到安全问题,需要使用OpenSSL单独为每个用户生成支付证书。然后用于数据加密与后台通信。按照给的需求,百度,谷歌都用了,每天都在搜各种资料,但找到的资料大部分都是利用Win或者Linux命令生成证书,或者服务器生成自签证书,然后放在本地资源文件夹下打进Apk使用之类。问了圈内几个认识的同行朋友,都没碰到过这种需求。客户端自签证书,还每个客户端生成一个,头一次碰,头疼,毕竟也不是安全加密方面的专家,Openssl也是第一次接触,NDk啥的咱也是萌新,诶~。
流程图:
二、 开发流程
做的时候一直卡在怎么生成证书这里,哪怕是在代码中调用Linux命令行都试了,还是不行NDK开发没怎么接触,临时抱佛脚压力贼大。突然有一天后台那边甩过来一篇关于OpenSSL的博客,瞬间打通任督二脉。传送门blog.csdn.net/do_bset_you…,感谢大佬,我们只是站在巨人的肩膀上搞事情。
为了实现需求无所不用其极,直接就去Maven仓库搜相关java开源库,选择Glade 依赖方式
//openssl加密库
// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.58'
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.58'
implementation group: 'org.bouncycastle', name: 'bcmail-jdk15on', version: '1.58'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.9'
implementation group: 'commons-io', name: 'commons-io', version: '2.6'
// https://mvnrepository.com/artifact/org.hyperledger.fabric-sdk-java/fabric-sdk-java
// implementation group: 'org.hyperledger.fabric-sdk-java', name: 'fabric-sdk-java', version: '1.4.7'
// https://mvnrepository.com/artifact/commons-codec/commons-codec
implementation group: 'commons-codec', name: 'commons-codec', version: '1.12'
剩下的就是依葫芦画瓢,变身CV战士了:
生成证书代码:
mCertDestPath = getFilePath(this, "") + "/cert.p12";
private void createCert(String payPassword) {
try {
Log.i(TAG, "certDestPath: " + mCertDestPath);
X509Dao impl = new X509CertDaoImpl();
Date createDate = null;
if (FileUtils.fileExists(mCertDestPath)) {
if (!checkCertDate()) {
String issuer = "C=CN,ST=GD,L=GD,O=***,OU=***,CN=***";
BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
String alias = BaseApp.getApplication().getLoginInfo().getLoginAccount();
createDate = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(createDate);
cal.add(Calendar.DATE, 90);//证书有效期
Date newDate = cal.getTime();
impl.createCert(issuer, createDate, newDate, mCertDestPath, serial, mKeyPassword, alias);
} else {
createDate = impl.getCreationDate(mCertDestPath, mKeyPassword);
}
} else {
String issuer = "C=CN,ST=GD,L=GD,O=***,OU=***,CN=***";
BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
String alias = BaseApp.getApplication().getLoginInfo().getName();
createDate = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(createDate);
cal.add(Calendar.DATE, 90);//设置证书有效期为90天
Date newDate = cal.getTime();
impl.createCert(issuer, createDate, newDate, mCertDestPath, serial, mKeyPassword, alias);
}
if (!FileUtils.fileExists(mCertDestPath)) {
ToastUtils.showShortToast(BaseApp.getApplication(), getString(R.string.text_create_cert_fail));
return;
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//获取当前时间
PublicKey publicKeyFromCert = impl.getPublicKey(mCertDestPath, mKeyPassword);//读取本地证书的公钥对象
String publicKey = new String(Base64.encodeBase64(publicKeyFromCert.getEncoded()));//获取公钥
Log.i(TAG, "publicKeyFromCert: " + publicKeyFromCert + "\n");
Log.i(TAG, "publicKey: " + publicKey);
String encrypt = AESUtil.encrypt(publicKey);//将公钥用AES加密
Log.i(TAG, "encrypt_publicKey: " + encrypt);
Log.i(TAG, "decrypt_publicKey: " + AESUtil.decrypt(encrypt));
mPresenter.openQrPay(Message.obtain(PayQrCodeActivity.this), format.format(createDate), encrypt, payPassword);//将加密后的公钥、密码、证书创建时间传到后台,后台保存
} catch (Exception e) {
e.printStackTrace();
}
}
验签:
public boolean checkCertDate() {
boolean b = false;
try {
KeyStore ks = initKs(mCertDestPath, mKeyPassword);
X509 x509 = new X509(ks, BaseApp.getApplication().getLoginInfo().getName(), mKeyPassword);
b = x509.checkData(new Date());//验证证书有效期
} catch (Exception e) {
e.printStackTrace();
}
return b;
}
private KeyStore initKs(String filePath, String password) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
KeyStore ks = KeyStore.getInstance("PKCS12");
FileInputStream fis = new FileInputStream(filePath);
// If the keystore password is empty(""), then we have to set
// to null, otherwise it won't work!!!
char[] nPassword = null;
if ((password == null) || password.trim().equals("")) {
nPassword = null;
} else {
nPassword = password.toCharArray();
}
ks.load(fis, nPassword);
fis.close();
return ks;
}
对后台返回的公钥加密过QrCode进行解密
private void getPayQrcodeSuccess(Bundle data) {
String encrypt = data.getString("qrCode");
if (TextUtils.isEmpty(encrypt)) {
ToastUtils.showShortToast(BaseApp.getApplication(), getString(R.string.text_get_pay_qrcode_fail));//获取失败
return;
}
X509CertDaoImpl impl = new X509CertDaoImpl();
PrivateKey privateKeyFromCert = null;
try {
privateKeyFromCert = impl.getPrivateKey(mCertDestPath, BaseApp.getApplication().getLoginInfo().getLoginAccount());
String privateKey = new String(Base64.encodeBase64(privateKeyFromCert.getEncoded()));
Log.i(TAG, "encrypt: " + encrypt);
String qrCode = RSAUtil.decrypt(encrypt, privateKey);
Log.i(TAG, "qrCode: " + qrCode);
ParseUrlToBitmap(BaseApp.getApplication().getLoginInfo().getFace(), qrCode);
} catch (Exception e) {
e.printStackTrace();
}
}
X509类
public class X509 {
private final PrivateKey privateKey;
private final X509Certificate certificate;
public X509(KeyStore ks, String certName, String password) {
try {
this.privateKey = (PrivateKey) ks.getKey(certName, password.toCharArray());
this.certificate = (X509Certificate) ks.getCertificate(certName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 验证证书有效期
public boolean checkData(Date date) {
boolean status = true;
try {
this.certificate.checkValidity(date);
} catch (CertificateExpiredException e) {
e.printStackTrace();
status = false;
} catch (CertificateNotYetValidException e) {
e.printStackTrace();
status = false;
}
return status;
}
// 加密
public byte[] encrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance(this.privateKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, this.privateKey);
return cipher.doFinal(data);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
// 解密
public byte[] decrypt(byte[] data) {
PublicKey publicKey = this.certificate.getPublicKey();
try {
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
// 签名
public byte[] sign(byte[] data) {
try {
Signature signature = Signature.getInstance(this.certificate.getSigAlgName());
signature.initSign(this.privateKey);
signature.update(data);
return signature.sign();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
// 验签
public boolean verify(byte[] data, byte[] sign) {
try {
Signature signature = Signature.getInstance(this.certificate.getSigAlgName());
signature.initVerify(this.certificate);
signature.update(data);
return signature.verify(sign);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
static KeyStore loadKeyStore(String keyStoreFile, String password) {
try (InputStream input = new BufferedInputStream(new FileInputStream(keyStoreFile))) {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(input, password.toCharArray());
return ks;
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
/*
* keytool -genkeypair -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 36500 -alias mycert -keystore my.keystore -dname "CN=www.test.com, OU=sample, O=sample, L=BJ, ST=BJ, C=CN" -keypass 123456 -storepass 456789
*
* keytool -list -keystore my.keystore -storepass 456789
*
* */
// public static void main(String[] args) throws Exception {
// byte[] data = "Hello,使用X.509证书进行加密和签名!".getBytes("UTF-8");
// // 读取KeyStore
// KeyStore ks = loadKeyStore("D:\\develop\\IntelliJ\\source\\my.keystore", "456789");
// // 读取证书
// X509 x509 = new X509(ks, "mycert", "123456");
// // 加密
// byte[] encryptData = x509.encrypt(data);
// System.out.println("encryptData: " + Base64.encodeBase64String(encryptData));
// // 解密
// byte[] decryptData = x509.decrypt(encryptData);
// System.out.println("decryptData: " + new String(decryptData, "UTF-8"));
// // 签名
// byte[] sign = x509.sign(data);
// System.out.println("sign: " + Base64.encodeBase64String(sign));
// // 验证签名
// boolean verify = x509.verify(data, sign);
// System.out.println("verify: " + verify);
// }
}
X509CertDaoImpl:
public class X509CertDaoImpl implements X509Dao {
public static final String Default_keyType = "PKCS12";
public static final String Default_KeyPairGenerator = "RSA";
public static final String Default_Signature = "SHA1withRSA";
public static final String cert_type = "X509";
public static final Integer Default_KeySize = 2048;
static {
// 系统添加BC加密算法 以后系统中调用的算法都是BC的算法
Security.addProvider(new BouncyCastleProvider());
}
@Override
public void createCert(String issuer, Date notBefore, Date notAfter, String certDestPath,
BigInteger serial, String keyPassword, String alias) throws Exception {
//产生公私钥对
KeyPairGenerator kpg = KeyPairGenerator.getInstance(Default_KeyPairGenerator);
kpg.initialize(Default_KeySize);
KeyPair keyPair = kpg.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 组装证书
X500Name issueDn = new X500Name(issuer);
X500Name subjectDn = new X500Name(issuer);
//组装公钥信息
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo
.getInstance(new ASN1InputStream(publicKey.getEncoded())
.readObject());
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
issueDn, serial, notBefore, notAfter, subjectDn,
subjectPublicKeyInfo);
//证书的签名数据
ContentSigner sigGen = new JcaContentSignerBuilder(Default_Signature).build(privateKey);
X509CertificateHolder holder = builder.build(sigGen);
byte[] certBuf = holder.getEncoded();
X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance(cert_type).generateCertificate(new ByteArrayInputStream(certBuf));
// 创建KeyStore,存储证书
KeyStore store = KeyStore.getInstance(Default_keyType);
store.load(null, null);
store.setKeyEntry(alias, keyPair.getPrivate(),
keyPassword.toCharArray(), new Certificate[]{certificate});
// FileOutputStream fout =new FileOutputStream(certDestPath);
File fs = new File(certDestPath);
FileOutputStream fout = new FileOutputStream(fs);
store.store(fout, keyPassword.toCharArray());
fout.close();
}
@Override
public void printCert(String certPath, String keyPassword) throws Exception {
char[] charArray = keyPassword.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
System.out.println("keystore type=" + ks.getType());
Enumeration enumas = ks.aliases();
String keyAlias = null;
if (enumas.hasMoreElements()) {
keyAlias = (String) enumas.nextElement();
System.out.println("alias=[" + keyAlias + "]");
}
System.out.println("is key entry=" + ks.isKeyEntry(keyAlias));
PrivateKey prikey = (PrivateKey) ks.getKey(keyAlias, charArray);
Certificate cert = ks.getCertificate(keyAlias);
PublicKey pubkey = cert.getPublicKey();
System.out.println("cert class = " + cert.getClass().getName());
System.out.println("cert = " + cert);
System.out.println("public key = " + pubkey);
System.out.println("private key = " + prikey);
}
@Override
public Date getCreationDate(String certPath, String keyPassword) throws Exception {
Date creationDate = null;
char[] charArray = keyPassword.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
System.out.println("keystore type=" + ks.getType());
Enumeration enumas = ks.aliases();
String keyAlias = null;
if (enumas.hasMoreElements()) {
keyAlias = (String) enumas.nextElement();
creationDate = ks.getCreationDate(keyAlias);
}
return creationDate;
}
@Override
public PublicKey getPublicKey(String certPath, String keyPassword) throws Exception {
char[] charArray = keyPassword.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
Enumeration enumas = ks.aliases();
String keyAlias = null;
if (enumas.hasMoreElements()) {
keyAlias = (String) enumas.nextElement();
Certificate certificate = ks.getCertificate(keyAlias);
return ks.getCertificate(keyAlias).getPublicKey();
}
return null;
}
@Override
public PrivateKey getPrivateKey(String certPath, String keyPassword) throws Exception {
char[] charArray = keyPassword.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
Enumeration enumas = ks.aliases();
String keyAlias = null;
if (enumas.hasMoreElements()) {
keyAlias = (String) enumas.nextElement();
Certificate certificate = ks.getCertificate(keyAlias);
return (PrivateKey) ks.getKey(keyAlias, charArray);
}
return null;
}
@Override
public void certDelayTo(Date endTime, String certPath, String password) throws Exception {
}
@Override
public void changePassword(String certPath, String oldPwd, String newPwd) throws Exception {
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, oldPwd.toCharArray());
fis.close();
FileOutputStream output = new FileOutputStream(certPath);
ks.store(output, newPwd.toCharArray());
output.close();
}
@Override
public void deleteAlias(String certPath, String password, String alias, String entry) throws Exception {
char[] charArray = password.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
if (ks.containsAlias(alias)) {
ks.deleteEntry(entry);
FileOutputStream output = new FileOutputStream(certPath);
ks.store(output, password.toCharArray());
output.close();
} else {
throw new Exception("该证书未包含别名--->" + alias);
}
}
// public static void main(String[] args) throws Exception {
// X509Dao impl=new X509CertDaoImpl();
// String issuer="C=CN,ST=BJ,L=BJ,O=testserver,OU=testserver,CN=testserver";
// String certDestPath="d://test.p12";
//// String certDestPath= Environment.getDataDirectory() + "/test.p12" ;
// BigInteger serial=BigInteger.valueOf(System.currentTimeMillis());
// String keyPassword="123";
// String alias="test";
// impl.createCert(issuer, new Date(), new Date("2017/09/27"), certDestPath, serial, keyPassword, alias);
// //impl.changePassword(certDestPath, "123", "123");
// //impl.createCert(issuer, new Date(), new Date("2017/09/27"), certDestPath, serial, keyPassword, alias);
// //未实现
// impl.certDelayTo(new Date("2017/09/28"), certDestPath, keyPassword);
// //impl.printCert(certDestPath, keyPassword);
// }
}
X509Dao:
public interface X509Dao {
/**
* @param issuer 发布者 C=CN,ST=BJ,L=BJ,O=组织,OU=单位,CN=CCERT
* @param notBefore 使用日期
* @param notAfter 到期
* @param certDestPath 生成证书地址
* @param serial 证书序列号
* @param alias 证书别名
* @throws Exception
*/
void createCert(String issuer, Date notBefore, Date notAfter, String certDestPath, BigInteger serial,
String keyPassword, String alias) throws Exception;
/**
* 输出证书信息
*
* @param certPath 证书地址
* @param keyPassword 证书密码
*/
void printCert(String certPath, String keyPassword) throws Exception;
/**
* 返回证书创建日期
*
* @param certPath 证书路径
* @param keyPassword 证书密码
* @return
* @throws Exception
*/
Date getCreationDate(String certPath, String keyPassword) throws Exception;
/**
* 返回公钥
*
* @param certPath 证书路径
* @param keyPassword 证书密码
* @return
* @throws Exception
*/
PublicKey getPublicKey(String certPath, String keyPassword) throws Exception;
/**
* 返回私钥
*
* @param certPath
* @param keyPassword
* @return
* @throws Exception
*/
PrivateKey getPrivateKey(String certPath, String keyPassword) throws Exception;
/**
* @param endTime 延期时间
* @param certPath 证书地址
* @param password 密码
* @throws Exception 目前未实现,
*/
void certDelayTo(Date endTime, String certPath, String password) throws Exception;
/**
* 修改密码
*
* @param certPath 证书地址 密码
* @param oldPwd 原始密码
* @param newPwd 新密码
* @throws Exception
*/
void changePassword(String certPath, String oldPwd, String newPwd) throws Exception;
/**
* 删除证书
*
* @param certPath 证书地址
* @param password 密码
* @param alias 别名
* @param entry 条目
* @throws Exception
*/
void deleteAlias(String certPath, String password, String alias, String entry) throws Exception;
}
完整流程就是,用户请求开通扫码支付功能,输入密码,用密码创建证书,获取证书公钥,将加密后的公钥、证书创建日期、支付密码传到服务端,服务端验证,通过则开通成功。然后设置定时器。每分钟向服务器请求QrCode。服务端将QrCode用保存的公钥加密后返回给客户端,客户端通过RSA使用私钥解密,获得真实Qrcode的值,然后初始化二维码图片显示。
end
到此总算是把需求搞定了,记在这里,希望能帮到后来要用到的人。觉得有用可以点个👍再走。