Java与CSP数据兼容之一:Java兼容CSP导出的RSA公钥数据

本文介绍在Java中如何从X509证书、X509Encoded公钥数据及Windows CryptoAPI导出的公钥数据生成RSA公钥对象的具体步骤与示例代码。
      Java中导入公钥数据,最直接的方式是导入X509证书数据,从中获取公钥对象。但是有时为了和客户端C++程序、特别是Windows平台数据兼容,需要把Windows下通过CryptoAPI导出的公钥数据转化为Java里的公钥对象,这样就需要做一定的转化。

      下面将讲述Java中这三种生成RSA公钥对象的方法:

一、通过X509证书创建

如果已知公钥所在的证书文件(X509格式),那么可以直接通过证书获取对应的公钥对象。其相关代码如下:

    /** 
     * Created by Singler on 2015/10/12. 
     * RSA加解密实现类 
     * 
     */  
    public class RSA {  
        /** 算法 */  
        private static String ALGORITHM = "RSA";  
        /** RSA bits */  
        private static int KEYSIZE = 1024;    
          
        /** 由X509格式的证书内容创建公钥对象,证书内容用Base64编码 **/  
        public static RSAPublicKey CreatePublicKeyFromCert(String base64X509Cert) throws Exception {  
            /**将证书内容解码成二进制**/  
            Base64.Decoder decoder = Base64.getDecoder();  
            byte[] certData = decoder.decode(base64X509Cert);  
      
            /**构造证书对象**/  
            CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509");  
            ByteArrayInputStream bain = new ByteArrayInputStream(certData);  
            X509Certificate Cert = (X509Certificate)certificatefactory.generateCertificate(bain);  
      
            /**获取公钥对象**/  
            RSAPublicKey pukKey = (RSAPublicKey)Cert.getPublicKey();  
      
            return pukKey;  
        }  
    }     
      
    public class Main {  
        static final String base64X509EncCert = "MIICfzCCAeygAwIBAgIQQpcVM898DJ9O2voeAMomJTAJBgUrDgMCHQUAMDcxGzAZBgNVBAoeEm1ZbF93AWVwW1eLwU5mTi1fwzEYMBYGA1UEAxMPWkpDQSBUZWNobm9sb2d5MB4XDTExMTIyMjAyNTkxMFoXDTM5MTIzMTIzNTk1OVowNzEbMBkGA1UECh4SbVlsX3cBZXBbV4vBTmZOLV/DMRgwFgYDVQQDEw9aSkNBIFRlY2hub2xvZ3kwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAI7QNZ1IxmiruAh1bDuCxq3SBIgL6g8fJWeteDfDGOmCy2XvUUfi2VuuJgtcFmhTK46zflBP3/rHj1IHY/0ZdMfgRcqMfF32+5oaIuNxw4zeSWydxSJlay9aq4pU0C40HkiaM4PE4JS0lsFdc7JaV0Shk7E4N81JOjKqhQ07SqnjAgMBAAGjgZMwgZAwDwYDVR0TAQH/BAUwAwEB/zATBgNVHSUEDDAKBggrBgEFBQcDAjBoBgNVHQEEYTBfgBA3FRkusjCPJCGc33EBFu0zoTkwNzEbMBkGA1UECh4SbVlsX3cBZXBbV4vBTmZOLV/DMRgwFgYDVQQDEw9aSkNBIFRlY2hub2xvZ3mCEEKXFTPPfAyfTtr6HgDKJiUwCQYFKw4DAh0FAAOBgQCHLkBcCbH4OGN9OpinUYmAGsjQWB42wYDjgvX1pG0BQEvI1dPp42/OzlNTKApKyCqNUDvcCIE1bS1g9+K277NOoeYupBdz2voqS4mAZKstGauCfseeu8y8tjedbRgvjHokyATFmX6EK7NcBucm4eNDTqU+oqnzg4ypAnGtJtYFZg==";  
      
        public static void main(String[] args) {  
      
            try {  
                PublicKey pubKey = RSA.CreatePublicKeyFromCert(base64X509EncCert);  
                byte[] pubKeyData = pubKey.getEncoded();  
                String pubKeyStr = Convert.bytesToHexString(pubKeyData);  
                System.out.println(pubKeyStr);  
            }  
            catch (Exception e) {  
                System.out.println(e.getMessage());  
            }  
        }  
    }  
该RSA公钥的X509 Encoded数据以16进制输出结果如下:

    /** 
     * Created by Singler on 2015/10/12. 
     * RSA加解密实现类 
     * 
     */  
    public class RSA {  
        /** 算法 */  
        private static String ALGORITHM = "RSA";  
        /** RSA bits */  
        private static int KEYSIZE = 1024;  
      
        /** 由X509 Encoded公钥数据构造公钥对象,公钥数据用Base64编码 */  
        public static RSAPublicKey CreatePublicKeyFromString(String base64EncodedPubKey) throws Exception {  
            /**将证书内容解码成二进制**/  
            Base64.Decoder decoder = Base64.getDecoder();  
            byte[] encodedPubkeyData = decoder.decode(base64EncodedPubKey);  
      
            /**构造公钥对象**/  
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(encodedPubkeyData);  
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);  
            return (RSAPublicKey)keyFactory.generatePublic(x509EncodedKeySpec);  
        }  
    }  
      
    public class Main {  
        static String base64X509PubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw+D4pTERUemPtGIsterieWoTbRTNHgZ0DN9Z4qfrS03eWP339/JBkGyKwlBykCzYDt8L0YMW/1Xg1appYGvfo4mnVvxzIbc1tuanH1Z+wnXbrHqgW5ndlMjZ328mDyDIRHPmzA8FMYSvPGJtnrv4H9Tc+CeKrmiWskbzxBg4T/MUxYsk/+WWg6y5dUU61rwr19HyBZKq93xH8IOctgeRxsf6oItQJ1y1Nd55XVEul2Lh5KtTZzZMWAwreIq/EhZJH0XdbOeHhy78/fMmQtkKpJqLpRMTas1ZBWmVnqHxgBQcvheOu6cNGJYbadisJDk7Yo6MW3M7kRbybeU7C9yf7QIDAQAB";  
       
        public static void main(String[] args) {  
      
            try {  
                PublicKey pubKey = RSA.CreatePublicKeyFromString(base64X509PubKey);  
                byte[] pubKeyData = pubKey.getEncoded();  
                String pubKeyStr = Convert.bytesToHexString(pubKeyData);  
                System.out.println(pubKeyStr);  
            }  
            catch (Exception e) {  
                System.out.println(e.getMessage());  
            }  
        }  
    }  

三、通过Windows CryptoAPI导出的RSA公钥数据创建

有时RSA公钥数据来源于Windows客户端,Windows下常用的RSA函数来源于微软的CryptoAPI,其导出的RSA公钥数据为PUBLICKEYBLOB类型的数据块,数据块包含结构体BLOBHEADER和RSAPUBKEY。此时如果想把该RSA公钥应用于Java,就需要通过最原始的模和指数数据创建RSA公钥对象了。同时要注意,由于CrytoAPI的数据是大端模式,而Java里是小端模式,所以从CryptoAPI得到的模和指数都要经过转化才能在Java中使用。

具体过程如下:

1、使用CryptoAPI导出RSA公钥数据

2、得到公钥模和指数

3、大端到小端的转化

4、在Java中构造公钥对象

下面是C++调用CryptoAPI导出公钥数据的代码。

    #include "stdafx.h"  
    #include <windows.h>  
    #include <atlconv.h>  
      
    DWORD ExportPubKey(HCRYPTPROV hProv, DWORD dwKeyUsage, LPBYTE lpbtPubKey, LPDWORD lpdwLen);  
    void Reverse(LPBYTE lpData, DWORD dwLen);  
    void PrintDataInHex(LPBYTE lpData, DWORD dwLen);  
      
    int _tmain(int argc, _TCHAR* argv[])  
    {     
        DWORD dwErr = 0;  
        DWORD dwFlag = CRYPT_FIRST;  
        DWORD dwParamLen = 2048;  
        BYTE btParamData[2048] = {0};  
        HCRYPTPROV hDefaultProv = NULL;  
        HCRYPTPROV hProv = NULL;  
        TCHAR *pContainer = NULL;  
        const TCHAR tcCSPName[] = {_T("ZJCA CSP v1.0 for EnterSafe ePass3000gm")};  
        //  
        DWORD dwPubKeyLen = 0;  
        DWORD dwModulusLen = 0;  
        LPBYTE lpbtPubKey = NULL;  
        LPBYTE lpbtModulus = NULL;  
        BYTE btPubexp[4] = {0};  
        BLOBHEADER *pBlobHeader = NULL;  
        RSAPUBKEY *pRSAPubKey = NULL;  
          
        USES_CONVERSION;  
      
        if (!CryptAcquireContext(&hDefaultProv, NULL, tcCSPName, PROV_RSA_FULL, 0))  
        {  
            DWORD dwErr = GetLastError();  
            printf("函数CryptAcquireContext失败! dwError = 0x%x\n", dwErr);  
            goto END;  
        }  
              
        //获取CSP所有的容器名称,找到加密密钥所在容器  
        while (CryptGetProvParam(hDefaultProv, PP_ENUMCONTAINERS, btParamData, &dwParamLen, dwFlag))  
        {  
            dwFlag = CRYPT_NEXT;  
    #ifdef UNICODE  
            pContainer = A2W((char*)btParamData);  
    #else  
            pContainer = btParamData;  
    #endif  
            if (CryptAcquireContext(&hProv, pContainer, tcCSPName, PROV_RSA_FULL, 0))  
            {         
                HCRYPTKEY hKeyPair = NULL;  
                if (CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hKeyPair) && hKeyPair)  
                {  
                    CryptDestroyKey(hKeyPair);  
                    break;  
                }  
            }  
        }  
        if (!hProv)  
        {  
            printf("Not found keyset!\n");  
            goto END;  
        }  
      
        // 获取公钥数据长度  
        dwErr = ExportPubKey(hProv, AT_KEYEXCHANGE, NULL, &dwPubKeyLen);  
        if (0 != dwErr)  
        {  
            printf("ExportPubKey() failed! dwErr = 0x%x\n", dwErr);  
            goto END;  
        }  
          
        // 获取公钥数据  
        lpbtPubKey = new BYTE[dwPubKeyLen];  
        dwErr = ExportPubKey(hProv, AT_KEYEXCHANGE, lpbtPubKey, &dwPubKeyLen);  
        if (0 != dwErr)  
        {  
            printf("ExportPubKey() failed! dwErr = 0x%x\n", dwErr);  
            goto END;  
        }  
      
        // 输出公钥数据  
        printf("\nRSA public key:\n");  
        PrintDataInHex(lpbtPubKey, dwPubKeyLen);  
      
        // 获取公钥模数据,并换成小端模式  
        pBlobHeader = (BLOBHEADER*)lpbtPubKey;  
        pRSAPubKey = (RSAPUBKEY*)(lpbtPubKey + sizeof(BLOBHEADER));  
        dwModulusLen = pRSAPubKey->bitlen / 8;  
        lpbtModulus = new BYTE[dwModulusLen];  
        memcpy(lpbtModulus, lpbtPubKey + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), dwModulusLen);  
        Reverse(lpbtModulus, dwModulusLen);  
        printf("\nRSA public key modulus:\n");  
        PrintDataInHex(lpbtModulus, dwModulusLen);  
      
        // 获取公钥指数数据  
        memcpy(btPubexp, &pRSAPubKey->pubexp, 4);  
        printf("\nRSA public key exp:\n");  
        PrintDataInHex(btPubexp, 4);  
      
    END:  
        if (lpbtModulus)  
        {  
            delete []lpbtModulus;  
            lpbtModulus = NULL;  
        }  
        if (lpbtPubKey)  
        {  
            delete []lpbtPubKey;  
            lpbtPubKey = NULL;  
        }  
        if (hDefaultProv)  
        {  
            CryptReleaseContext(hDefaultProv, 0);  
            hDefaultProv = NULL;  
        }  
        if (hProv)  
        {  
            CryptReleaseContext(hProv, 0);  
            hProv = NULL;  
        }  
        getchar();  
        return 0;  
    }  
      
    DWORD ExportPubKey(HCRYPTPROV hProv, DWORD dwKeyUsage, LPBYTE lpbtPubKey, LPDWORD lpdwLen)  
    {  
        DWORD dwError = 0;  
        DWORD dwDataLen = 0;  
        HCRYPTKEY hKeyPair = NULL;  
      
        if (!hProv || !lpdwLen)  
        {  
            return 1;  
        }  
      
        //  获取密钥对句柄  
        if (!CryptGetUserKey(hProv, dwKeyUsage, &hKeyPair) || !hKeyPair)  
        {  
            dwError = GetLastError();  
            return dwError;  
        }  
      
        //  导出密钥对中的公钥数据长度  
        if (!CryptExportKey(hKeyPair, 0, PUBLICKEYBLOB, 0, NULL, &dwDataLen))  
        {  
            dwError = GetLastError();  
            goto FREE_MEMORY;  
        }  
      
        //  返回数据长度  
        if (!lpbtPubKey)  
        {  
            dwError = 0;  
            *lpdwLen = dwDataLen;  
            goto FREE_MEMORY;  
        }  
          
        //  导出密钥对中的公钥数据  
        if (!CryptExportKey(hKeyPair, 0, PUBLICKEYBLOB, 0, lpbtPubKey, &dwDataLen))  
        {  
            dwError = GetLastError();  
            goto FREE_MEMORY;  
        }  
        *lpdwLen = dwDataLen;  
      
    FREE_MEMORY:  
        if (hKeyPair)  
        {  
            CryptDestroyKey(hKeyPair);  
            hKeyPair = NULL;  
        }  
      
        return dwError;  
    }  
      
    void Reverse(LPBYTE lpData, DWORD dwLen)  
    {  
        BYTE temp = 0;  
        for (ULONG i = 0; i < dwLen / 2; i++)  
        {  
            temp = lpData[i];  
            lpData[i] = lpData[dwLen - (i + 1)];  
            lpData[dwLen - ( i + 1)] = temp;  
        }  
    }  
      
    void PrintDataInHex(LPBYTE lpData, DWORD dwLen)  
    {  
        for (DWORD dwIndex = 0; dwIndex < dwLen; dwIndex++)  
        {  
            printf("%02X ", lpData[dwIndex]);  
            if ((dwIndex + 1) % 16 == 0)  
            {  
                printf("\n");  
            }  
        }  
    }  
程序输入结果如下:


下一步,需要把C++得到的模和指数数据,按16进制的字符串形式组织好,然后构造成RSA公钥对象。具体代码如下:

    /** 
     * Created by Singler on 2015/10/12. 
     * RSA加解密实现类 
     * 
     */  
    public class RSA {  
        /** 算法 */  
        private static String ALGORITHM = "RSA";  
        /** RSA bits */  
        private static int KEYSIZE = 1024;  
      
        /** 由模和指数构造公钥对象,模和指数由16进制字符串表示 */  
        public static RSAPublicKey CreatePublicKeyFromModulus(String modulusIn16Radix, String exponentIn16Radix) throws Exception {  
            BigInteger m = new BigInteger(modulusIn16Radix, 16);  
            BigInteger e = new BigInteger(exponentIn16Radix, 16);  
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);  
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);  
            RSAPublicKey  rsaPubKey = (RSAPublicKey)keyFactory.generatePublic(keySpec);  
            return rsaPubKey;  
        }  
    }  
      
      
    public class Main {  
        static final String pubKeyModulusInHex = "C3E0F8A5311151E98FB4622CB5EAE2796A136D14CD1E06740CDF59E2A7EB4B4DDE58FDF7F7F241906C8AC25072902CD80EDF0BD18316FF55E0D5AA69606BDFA389A756FC7321B735B6E6A71F567EC275DBAC7AA05B99DD94C8D9DF6F260F20C84473E6CC0F053184AF3C626D9EBBF81FD4DCF8278AAE6896B246F3C418384FF314C58B24FFE59683ACB975453AD6BC2BD7D1F20592AAF77C47F0839CB60791C6C7FAA08B50275CB535DE795D512E9762E1E4AB5367364C580C2B788ABF1216491F45DD6CE787872EFCFDF32642D90AA49A8BA513136ACD590569959EA1F180141CBE178EBBA70D18961B69D8AC24393B628E8C5B733B9116F26DE53B0BDC9FED";  
        static final String pubKeyExpInHex = "01000100";  
      
        public static void main(String[] args) {  
      
            try {  
                PublicKey pubKey = RSA.CreatePublicKeyFromModulus(pubKeyModulusInHex, pubKeyExpInHex);  
                byte[] pubKeyData = pubKey.getEncoded();  
                String pubKeyStr = Base64.getEncoder().encodeToString(pubKeyData); //Convert.bytesToHexString(pubKeyData);  
                System.out.println(pubKeyStr);  
            }  
            catch (Exception e) {  
                System.out.println(e.getMessage());  
            }  
        }  
    }  

该段Java代码输入的X509 Encoded公钥数据如下:

MIIBIzANBgkqhkiG9w0BAQEFAAOCARAAMIIBCwKCAQEAw+D4pTERUemPtGIsterieWoTbRTNHgZ0DN9Z4qfrS03eWP339/JBkGyKwlBykCzYDt8L0YMW/1Xg1appYGvfo4mnVvxzIbc1tuanH1Z+wnXbrHqgW5ndlMjZ328mDyDIRHPmzA8FMYSvPGJtnrv4H9Tc+CeKrmiWskbzxBg4T/MUxYsk/+WWg6y5dUU61rwr19HyBZKq93xH8IOctgeRxsf6oItQJ1y1Nd55XVEul2Lh5KtTZzZMWAwreIq/EhZJH0XdbOeHhy78/fMmQtkKpJqLpRMTas1ZBWmVnqHxgBQcvheOu6cNGJYbadisJDk7Yo6MW3M7kRbybeU7C9yf7QIEAQABAA== 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值