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

3 篇文章 0 订阅

      在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=

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值