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进制输出结果如下:
30820122300d06092a864886f70d01010105000382010f003082010a0282010100c3e0f8a5311151e98fb4622cb5eae2796a136d14cd1e06740cdf59e2a7eb4b4dde58fdf7f7f241906c8ac25072902cd80edf0bd18316ff55e0d5aa69606bdfa389a756fc7321b735b6e6a71f567ec275dbac7aa05b99dd94c8d9df6f260f20c84473e6cc0f053184af3c626d9ebbf81fd4dcf8278aae6896b246f3c418384ff314c58b24ffe59683acb975453ad6bc2bd7d1f20592aaf77c47f0839cb60791c6c7faa08b50275cb535de795d512e9762e1e4ab5367364c580c2b788abf1216491f45dd6ce787872efcfdf32642d90aa49a8ba513136acd590569959ea1f180141cbe178ebba70d18961b69d8ac24393b628e8c5b733b9116f26de53b0bdc9fed0203010001
二、通过X509 Encoded数据创建
如果知道RSA公钥的X509 Encoded数据(比如上例中的输出),那可以直接由该数据创建公钥对象。主要代码如下:
/**
* 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
#include
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==