您的位置:首页 > 编程语言 > Java开发

Java与CSP数据兼容之二:Java兼容CSP导出的RSA私钥数据

2015-10-27 15:40 633 查看
      在Java中,如果想创建一个RSA私钥对象,常见的办法有三种:

1、由PKCS8格式的Encoded私钥数据创建

2、由pfx12格式的证书数据创建

3、直接用私钥模和指数数据创建

对于第一种方法,常用于Java语言内部、或者是OpenSSL库之间。

对于第二种方法,是比较直接的,直接从含有私钥的证书中获取私钥。

对于第三种方法,是由最原始的数据构造私钥对象,那么该方法也适用和CSP之间交换私钥数据。

      下面针对这三种不同的方法,介绍实现过程:

一、由PKCS8格式的Encoded数据创建

      如果知道私钥的PKCS8格式的编码数据(Java本身导出的私钥数据就是该格式,OpenSSL库也支持该格式的私钥输出),那么可以使用下面的代码创建私钥对象。

/** 由Encoded私钥数据(PKCS8格式)构造私钥对象,私钥数据用Base64编码 */
public static RSAPrivateKey CreatePrivateKeyFromString(String base64EncodedPriKey)  throws Exception {
/**将证书内容解码成二进制**/
Base64.Decoder decoder = Base64.getDecoder();
byte[] encodedPrikeyData = decoder.decode(base64EncodedPriKey);

/**构造私钥对象**/
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(encodedPrikeyData);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);

return (RSAPrivateKey)keyFactory.generatePrivate(pkcs8KeySpec);
}


二、由Pfx12证书数据创建

      如果已知私钥所在的Pfx12证书,那么可以直接通过该证书获取私钥对象。当然,同时需要知道该Pfx12证书的私钥保护密码。相关代码如下:

/** 由Pfx证书创建私钥对象,Pfx证书内容用Base64编码 */
public static RSAPrivateKey CreatePrivateKeyFromCert(String base64PfxCert, String alias, String pin) throws Exception{
/**将证书内容解码成二进制**/
Base64.Decoder decoder = Base64.getDecoder();
byte[] certData = decoder.decode(base64PfxCert);
ByteArrayInputStream in = new ByteArrayInputStream(certData);

KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(in, pin.toCharArray());

RSAPrivateKey priKey = (RSAPrivateKey)ks.getKey(alias, pin.toCharArray());

return priKey;
}
注意:其中参数alias为证书的别名,也就是证书“使用者”中的“CN”字段。

三、由模和指数数据创建

       如果想要把CSP中导出的私钥数据,传入Java代码使用,那么就只能使用这种方法。需要注意的是:Windows CryptoAPI导出的数据为大端模式,而Java的数据是以小端存放,故需要转化之后才能传入Java。具体做法如下:

1、通过CryptoAPI函数CryptExportKey()导出私钥数据,前提是该密钥对生成(或导入)时使用了CRYPT_EXPORTABLE标记,否则私钥是不能导出;

2、解读私钥数据快,得到模和私钥指数数据;

3、将数据由大端模式转化为小端模式;

4、构造Java的RSA私钥对象。

CryptoAPI导出私钥数据的代码如下:

DWORD ExportPriKey(HCRYPTPROV hProv, DWORD dwKeyUsage, LPBYTE lpbtPriKey, 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, PRIVATEKEYBLOB, 0, NULL, &dwDataLen))
{
dwError = GetLastError();
goto FREE_MEMORY;
}

//	返回数据长度
if (!lpbtPriKey)
{
dwError = 0;
*lpdwLen = dwDataLen;
goto FREE_MEMORY;
}

//	导出密钥对中的私钥数据
if (!CryptExportKey(hKeyPair, 0, PRIVATEKEYBLOB, 0, lpbtPriKey, &dwDataLen))
{
dwError = GetLastError();
goto FREE_MEMORY;
}
*lpdwLen = dwDataLen;

FREE_MEMORY:
if (hKeyPair)
{
CryptDestroyKey(hKeyPair);
hKeyPair = NULL;
}

return dwError;
}


CryptoAPI到处的私钥数据块有BLOBHEADER+RSAPUBKEY+私钥数据组成。下图为一个私钥数据块的内容解析:



基于这个私钥数据块的结构,我们可以使用下面的代码来解读各个项的具体数据:

/* CSP导出的私钥数据 */
static BYTE pbPvk[] = {
0x07, 0x02, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x52, 0x53, 0x41, 0x32, 0x00, 0x04, 0x00, 0x00,
0x01, 0x00, 0x01, 0x00, 0xe3, 0xa9, 0x4a, 0x3b, 0x0d, 0x85, 0xaa, 0x32, 0x3a, 0x49, 0xcd, 0x37,
0x38, 0xb1, 0x93, 0xa1, 0x44, 0x57, 0x5a, 0xb2, 0x73, 0x5d, 0xc1, 0x96, 0xb4, 0x94, 0xe0, 0xc4,
0x83, 0x33, 0x9a, 0x48, 0x1e, 0x34, 0x2e, 0xd0, 0x54, 0x8a, 0xab, 0x5a, 0x2f, 0x6b, 0x65, 0x22,
0xc5, 0x9d, 0x6c, 0x49, 0xde, 0x8c, 0xc3, 0x71, 0xe3, 0x22, 0x1a, 0x9a, 0xfb, 0xf6, 0x5d, 0x7c,
0x8c, 0xca, 0x45, 0xe0, 0xc7, 0x74, 0x19, 0xfd, 0x63, 0x07, 0x52, 0x8f, 0xc7, 0xfa, 0xdf, 0x4f,
0x50, 0x7e, 0xb3, 0x8e, 0x2b, 0x53, 0x68, 0x16, 0x5c, 0x0b, 0x26, 0xae, 0x5b, 0xd9, 0xe2, 0x47,
0x51, 0xef, 0x65, 0xcb, 0x82, 0xe9, 0x18, 0xc3, 0x37, 0x78, 0xad, 0x67, 0x25, 0x1f, 0x0f, 0xea,
0x0b, 0x88, 0x04, 0xd2, 0xad, 0xc6, 0x82, 0x3b, 0x6c, 0x75, 0x08, 0xb8, 0xab, 0x68, 0xc6, 0x48,
0x9d, 0x35, 0xd0, 0x8e, 0xc1, 0x69, 0xeb, 0xf9, 0xec, 0xb3, 0x29, 0xf8, 0x9a, 0x30, 0x02, 0x4f,
0x99, 0xb1, 0xd1, 0x24, 0xe7, 0x77, 0xfa, 0xb5, 0x12, 0xbc, 0x8b, 0xab, 0xa6, 0xfc, 0x23, 0x49,
0x08, 0x0f, 0x76, 0x0b, 0xb0, 0x24, 0x4e, 0x6c, 0x78, 0x2b, 0xc9, 0xe1, 0x26, 0x7c, 0x21, 0x95,
0xa7, 0xd4, 0x7a, 0x7d, 0x36, 0x39, 0x1b, 0x75, 0x3c, 0xc7, 0x2a, 0xd8, 0x18, 0xea, 0x26, 0x25,
0x3a, 0xf7, 0x50, 0xc9, 0xa3, 0x54, 0x73, 0xda, 0xda, 0x68, 0xde, 0x36, 0x64, 0x50, 0x6f, 0x10,
0x8e, 0x47, 0xfe, 0xe6, 0x04, 0x07, 0x00, 0x42, 0x6e, 0xc9, 0x10, 0xce, 0x31, 0x5f, 0x19, 0x6b,
0xf0, 0xea, 0x17, 0xc7, 0xf6, 0x3e, 0xa7, 0x3c, 0x12, 0x4f, 0x47, 0x91, 0x59, 0xd8, 0x44, 0x56,
0x81, 0x60, 0x9d, 0x40, 0x15, 0x8a, 0x63, 0xf1, 0x83, 0xab, 0x51, 0x8c, 0x93, 0x03, 0x13, 0x6f,
0xe6, 0x17, 0x9b, 0xb5, 0xc1, 0xeb, 0x33, 0xba, 0xa1, 0x79, 0x3b, 0x29, 0x10, 0x8f, 0xd5, 0x20,
0x5c, 0x9a, 0x36, 0x5c, 0x93, 0x3a, 0x8b, 0x42, 0x32, 0x3f, 0x6e, 0x3f, 0xa7, 0x6d, 0xeb, 0x92,
0x35, 0x4f, 0x07, 0xc0, 0x84, 0xab, 0x9b, 0x5f, 0x1b, 0x89, 0xda, 0xca, 0x4a, 0x2d, 0x1c, 0xec,
0x58, 0x83, 0xec, 0x68, 0x9f, 0x86, 0x9e, 0xe1, 0x27, 0x06, 0x84, 0x45, 0x7f, 0xf3, 0xb5, 0x2b,
0x4d, 0xbc, 0xd5, 0x92, 0xab, 0x21, 0x28, 0x82, 0x00, 0x44, 0xd3, 0x0c, 0xb2, 0x90, 0x73, 0x38,
0xc5, 0x7c, 0x6b, 0x50, 0xb4, 0xbb, 0x40, 0x14, 0x69, 0xac, 0xdb, 0x18, 0x3d, 0xc7, 0x4a, 0x98,
0x4a, 0x85, 0x4d, 0x6a, 0xf9, 0xbc, 0x79, 0x60, 0xda, 0x64, 0x64, 0x35, 0xe5, 0x06, 0x0d, 0x95,
0xef, 0x0f, 0x49, 0xc1, 0x36, 0x7a, 0xa9, 0xf5, 0x83, 0x1c, 0xe2, 0xef, 0xd3, 0x18, 0x54, 0xf5,
0xee, 0xcf, 0x20, 0x9a, 0x17, 0x45, 0x32, 0xa0, 0x7c, 0x34, 0x30, 0xd5, 0x44, 0x98, 0x86, 0x5a,
0xf9, 0x2a, 0x31, 0x33, 0xfc, 0x18, 0xac, 0x2c, 0xda, 0x40, 0x0a, 0x9a, 0x98, 0x87, 0xe8, 0xbc,
0x1e, 0xaa, 0x6e, 0xb3, 0x01, 0x5d, 0xd9, 0x5e, 0x0c, 0x37, 0x99, 0x19, 0xde, 0x06, 0x68, 0xe6,
0xba, 0x7d, 0x3b, 0x19, 0x52, 0x7e, 0x5b, 0x7a, 0x23, 0x27, 0x76, 0x4e, 0x25, 0x13, 0xf7, 0x74,
0x26, 0xd6, 0x49, 0x48, 0x01, 0x02, 0x12, 0x38, 0xde, 0x1f, 0x62, 0xda, 0x4b, 0x0e, 0x46, 0x2b,
0x41, 0xf5, 0x02, 0xd0, 0xc1, 0xe4, 0xe7, 0xfa, 0x18, 0x57, 0x6c, 0xd5, 0x7b, 0xda, 0xda, 0x6e,
0xc0, 0x5c, 0xe5, 0x59, 0xb2, 0x25, 0x75, 0x9b, 0x44, 0x90, 0xaf, 0xdc, 0x5d, 0xa1, 0xcc, 0x73,
0x63, 0x21, 0x7e, 0xab, 0xa9, 0x6d, 0x87, 0xf3, 0x26, 0xf2, 0x30, 0x42, 0x6b, 0xda, 0xf0, 0xa3,
0x89, 0x92, 0xba, 0x29, 0x2c, 0xc3, 0x88, 0x80, 0x06, 0x43, 0x74, 0xd8, 0xe4, 0x4b, 0x20, 0x3e,
0x91, 0xb0, 0x83, 0x1b, 0x7a, 0x29, 0x68, 0xcd, 0x4c, 0xa5, 0x40, 0x75, 0xed, 0x53, 0x0b, 0xb3,
0x76, 0x8d, 0xc4, 0x92, 0x12, 0x8b, 0x7a, 0x1e, 0xb7, 0x7e, 0xd0, 0xac, 0xfb, 0xfb, 0xf3, 0xca,
0xef, 0xbd, 0x20, 0x61, 0x0f, 0xd9, 0x89, 0x3a, 0x8b, 0x7b, 0x1c, 0xa9, 0x83, 0x49, 0xdd, 0xd6,
0x60, 0x2c, 0xd7, 0x43,
};

int _tmain(int argc, _TCHAR* argv[])
{
ULONG ulIndex = 0;

BLOBHEADER blobheader = {0};
RSAPUBKEY rsapubkey = {0};
BYTE modulus[128] = {0};
BYTE prime1[64] = {0};
BYTE prime2[64] = {0};
BYTE exponent1[64] = {0};
BYTE exponent2[64] = {0};
BYTE coefficient[64] = {0};
BYTE privateExponent[128] = {0};

//块头数据
memcpy(&blobheader, pbPvk, sizeof(blobheader));
ulIndex += sizeof(blobheader);
//公钥数据
memcpy(&rsapubkey, pbPvk + ulIndex, sizeof(RSAPUBKEY));
ulIndex += sizeof(RSAPUBKEY);
//模
memcpy(modulus, pbPvk + ulIndex, rsapubkey.bitlen/8);
ulIndex += rsapubkey.bitlen/8;
//
memcpy(prime1, pbPvk + ulIndex, rsapubkey.bitlen/16);
ulIndex += rsapubkey.bitlen/16;
//
memcpy(prime2, pbPvk + ulIndex, rsapubkey.bitlen/16);
ulIndex += rsapubkey.bitlen/16;
//
memcpy(exponent1, pbPvk + ulIndex, rsapubkey.bitlen/16);
ulIndex += rsapubkey.bitlen/16;
//
memcpy(exponent2, pbPvk + ulIndex, rsapubkey.bitlen/16);
ulIndex += rsapubkey.bitlen/16;
//
memcpy(coefficient, pbPvk + ulIndex, rsapubkey.bitlen/16);
ulIndex += rsapubkey.bitlen/16;
//私钥指数
memcpy(privateExponent, pbPvk + ulIndex, rsapubkey.bitlen/8);
ulIndex += rsapubkey.bitlen/8;

return 0;
}


记住:要将模和私钥指数数据转化为小端模式:

BYTE btTemp = 0;
for (ULONG i = 0; i < rsapubkey.bitlen/8 / 2; i++)
{
btTemp = modulus[i];
modulus[i] = modulus[rsapubkey.bitlen/8 - (i + 1)];
modulus[rsapubkey.bitlen/8 - ( i + 1)] = btTemp;
//
btTemp = privateExponent[i];
privateExponent[i] = privateExponent[rsapubkey.bitlen/8 - (i + 1)];
privateExponent[rsapubkey.bitlen/8 - ( i + 1)] = btTemp;
}
然后为了方便Java代码生成私钥对象,将模和私钥指数都按16进制字符串打印,结果如下:

模数据:

8ED0359D48C668ABB808756C3B82C6ADD204880BEA0F1F2567AD7837C318E982CB65EF5147E2D95BAE260B5C1668532B8EB37E504FDFFAC

78F520763FD1974C7E045CA8C7C5DF6FB9A1A22E371C38CDE496C9DC522656B2F5AAB8A54D02E341E489A3383C4E094B496C15D73B25A57

44A193B13837CD493A32AA850D3B4AA9E3

私钥指数数据:

43D72C60D6DD4983A91C7B8B3A89D90F6120BDEFCAF3FBFBACD07EB71E7A8B1292C48D76B30B53ED7540A54CCD68297A1B83B0913E204B

E4D87443068088C32C29BA9289A3F0DA6B4230F226F3876DA9AB7E216373CCA15DDCAF90449B7525B259E55CC06EDADA7BD56C5718FAE7

E4C1D002F5412B460E4BDA621FDE38120201

      好了,现在在Java代码中,就可以由模和私钥指数数据创建私钥对象了。具体代码如下:

/**
* 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 RSAPrivateKey CreatePrivateKeyFromModulus(String modulusIn16Radix, String exponentIn16Radix)  throws Exception {
BigInteger m = new BigInteger(modulusIn16Radix, 16);
BigInteger e = new BigInteger(exponentIn16Radix, 16);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(m, e);
RSAPrivateKey  rsaPriKey = (RSAPrivateKey)keyFactory.generatePrivate(keySpec);
return rsaPriKey;
}
}

public class Main {
static final String priKeyModulusInHex = "8ED0359D48C668ABB808756C3B82C6ADD204880BEA0F1F2567AD7837C318E982CB65EF5147E2D95BAE260B5C1668532B8EB37E504FDFFAC78F520763FD1974C7E045CA8C7C5DF6FB9A1A22E371C38CDE496C9DC522656B2F5AAB8A54D02E341E489A3383C4E094B496C15D73B25A5744A193B13837CD493A32AA850D3B4AA9E3";
static final String prikeyExpInHex = "43D72C60D6DD4983A91C7B8B3A89D90F6120BDEFCAF3FBFBACD07EB71E7A8B1292C48D76B30B53ED7540A54CCD68297A1B83B0913E204BE4D87443068088C32C29BA9289A3F0DA6B4230F226F3876DA9AB7E216373CCA15DDCAF90449B7525B259E55CC06EDADA7BD56C5718FAE7E4C1D002F5412B460E4BDA621FDE38120201";

public static void main(String[] args) {
try {
PrivateKey priKey = RSA.CreatePrivateKeyFromModulus(priKeyModulusInHex, prikeyExpInHex);
byte[] priKeyData = priKey.getEncoded();
String priKeyStr = Base64.getEncoder().encodeToString(priKeyData);
System.out.println(priKeyStr);
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
上面Java代码创建私钥对象成功后,将私钥数据的PKCS8编码内容以Base64格式输出如下:

MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAI7QNZ1IxmiruAh1bDuCxq3SBIgL6g8fJWeteDfDGOmCy2XvUUfi2VuuJgtcFmhTK46zf

lBP3/rHj1IHY/0ZdMfgRcqMfF32+5oaIuNxw4zeSWydxSJlay9aq4pU0C40HkiaM4PE4JS0lsFdc7JaV0Shk7E4N81JOjKqhQ07SqnjAgEAAoGAQ9csYNbdS

YOpHHuLOonZD2Egve/K8/v7rNB+tx56ixKSxI12swtT7XVApUzNaCl6G4OwkT4gS+TYdEMGgIjDLCm6komj8NprQjDyJvOHbamrfiFjc8yhXdyvkESbdSWy

WeVcwG7a2nvVbFcY+ufkwdAC9UErRg5L2mIf3jgSAgECAQACAQACAQACAQACAQA=
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java CryptoAPI RSA私钥