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库也支持该格式的私钥输出),那么可以使用下面的代码创建私钥对象。
二、由Pfx12证书数据创建
如果已知私钥所在的Pfx12证书,那么可以直接通过该证书获取私钥对象。当然,同时需要知道该Pfx12证书的私钥保护密码。相关代码如下:
三、由模和指数数据创建
如果想要把CSP中导出的私钥数据,传入Java代码使用,那么就只能使用这种方法。需要注意的是:Windows CryptoAPI导出的数据为大端模式,而Java的数据是以小端存放,故需要转化之后才能传入Java。具体做法如下:
1、通过CryptoAPI函数CryptExportKey()导出私钥数据,前提是该密钥对生成(或导入)时使用了CRYPT_EXPORTABLE标记,否则私钥是不能导出;
2、解读私钥数据快,得到模和私钥指数数据;
3、将数据由大端模式转化为小端模式;
4、构造Java的RSA私钥对象。
CryptoAPI导出私钥数据的代码如下:
CryptoAPI到处的私钥数据块有BLOBHEADER+RSAPUBKEY+私钥数据组成。下图为一个私钥数据块的内容解析:
基于这个私钥数据块的结构,我们可以使用下面的代码来解读各个项的具体数据:
记住:要将模和私钥指数数据转化为小端模式:
模数据:
8ED0359D48C668ABB808756C3B82C6ADD204880BEA0F1F2567AD7837C318E982CB65EF5147E2D95BAE260B5C1668532B8EB37E504FDFFAC
78F520763FD1974C7E045CA8C7C5DF6FB9A1A22E371C38CDE496C9DC522656B2F5AAB8A54D02E341E489A3383C4E094B496C15D73B25A57
44A193B13837CD493A32AA850D3B4AA9E3
私钥指数数据:
43D72C60D6DD4983A91C7B8B3A89D90F6120BDEFCAF3FBFBACD07EB71E7A8B1292C48D76B30B53ED7540A54CCD68297A1B83B0913E204B
E4D87443068088C32C29BA9289A3F0DA6B4230F226F3876DA9AB7E216373CCA15DDCAF90449B7525B259E55CC06EDADA7BD56C5718FAE7
E4C1D002F5412B460E4BDA621FDE38120201
好了,现在在Java代码中,就可以由模和私钥指数数据创建私钥对象了。具体代码如下:
MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAI7QNZ1IxmiruAh1bDuCxq3SBIgL6g8fJWeteDfDGOmCy2XvUUfi2VuuJgtcFmhTK46zf
lBP3/rHj1IHY/0ZdMfgRcqMfF32+5oaIuNxw4zeSWydxSJlay9aq4pU0C40HkiaM4PE4JS0lsFdc7JaV0Shk7E4N81JOjKqhQ07SqnjAgEAAoGAQ9csYNbdS
YOpHHuLOonZD2Egve/K8/v7rNB+tx56ixKSxI12swtT7XVApUzNaCl6G4OwkT4gS+TYdEMGgIjDLCm6komj8NprQjDyJvOHbamrfiFjc8yhXdyvkESbdSWy
WeVcwG7a2nvVbFcY+ufkwdAC9UErRg5L2mIf3jgSAgECAQACAQACAQACAQACAQA=
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对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树
- [原创]java局域网聊天系统