android 中使用OpenSSl

1,809 阅读7分钟

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

到此总算是把需求搞定了,记在这里,希望能帮到后来要用到的人。觉得有用可以点个👍再走。