1.1 密鑰對生成
RSA非對稱加密密鑰對,可以用OpenSSL的命令生成,也可以直接在線生成(http://web.chacuo.net/netrsakeypair)。
- 秘鑰位數:1024位(bit) 或 2048位(bit) 即,秘鑰的長度。在加解密時可能需要分段加解密。1024b和2048b要求的block是不同的。
- 秘鑰格式:PKCS#8格式 pkcs#1格式的也可以轉成pkcs#8
- 證書密碼:指的是私鑰文件的密碼。如果需要加密,可以指定。 無密碼的私鑰以“-----BEGIN PRIVATE KEY-----”開頭,有密碼的私鑰以“-----BEGIN ENCRYPTED PRIVATE KEY-----”開頭。
注意,在線生成的密鑰對,是有開頭和結尾標記的,並且有換行符。如果直接copy到程序配置里,則要把這些去掉。
1.2 數字證書格式
格式 | 擴展名 | 說明 |
X.509 PEM格式 | .pem .cer .crt | Base64編碼的ASCII文件,以"-----BEGIN CERTIFICATE-----"開頭,以"-----END CERTIFICATE-----"結尾。可存放證書,也可存放私鑰。 |
X.509 DER格式 | .der .cer .crt | 用於存放證書,它是2進制形式存放的,不含私鑰。 |
【公鑰證書格式英文介紹】
PEM Format The PEM format is the most common format that Certificate Authorities issue certificates in. PEM certificates usually have extentions such as .pem, .crt, .cer, and .key. They are Base64 encoded ASCII files and contain "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" statements. Server certificates, intermediate certificates, and private keys can all be put into the PEM format. Apache and other similar servers use PEM format certificates. Several PEM certificates, and even the private key, can be included in one file, one below the other, but most platforms, such as Apache, expect the certificates and private key to be in separate files. DER Format The DER format is simply a binary form of a certificate instead of the ASCII PEM format. It sometimes has a file extension of .der but it often has a file extension of .cer so the only way to tell the difference between a DER .cer file and a PEM .cer file is to open it in a text editor and look for the BEGIN/END statements. All types of certificates and private keys can be encoded in DER format. DER is typically used with Java platforms. The SSL Converter can only convert certificates to DER format. If you need to convert a private key to DER, please use the OpenSSL commands on this page
【私鑰證書格式】
格式 | 擴展名 | 說明 |
PKCS#7/P7B格式 | .p7b .p7c | Base64編碼的ASCII文件,以"-----BEGIN PKCS7-----"開頭,以"-----END PKCS7-----"結尾。其中,p7b以樹狀展示證書鏈(certificate chain),同時也支持單個證書,不含私鑰。 |
PKCS#12/PFX格式 | .pfx .p12 | 用於存放個人證書/私鑰,他通常包含保護密碼,2進制文件。 |
【私鑰證書格式英文介紹】
PKCS#7/P7B Format The PKCS#7 or P7B format is usually stored in Base64 ASCII format and has a file extention of .p7b or .p7c. P7B certificates contain "-----BEGIN PKCS7-----" and "-----END PKCS7-----" statements. A P7B file only contains certificates and chain certificates, not the private key. Several platforms support P7B files including Microsoft Windows and Java Tomcat. PKCS#12/PFX Format The PKCS#12 or PFX format is a binary format for storing the server certificate, any intermediate certificates, and the private key in one encryptable file. PFX files usually have extensions such as .pfx and .p12. PFX files are typically used on Windows machines to import and export certificates and private keys. When converting a PFX file to PEM format, OpenSSL will put all the certificates and the private key into a single file. You will need to open the file in a text editor and copy each certificate and private key (including the BEGIN/END statments) to its own individual text file and save them as certificate.cer, CACert.cer, and privateKey.key respectively.
- 公鑰,可以對外給任何人的加密和解密密碼,公開的,可以任何人訪問
- 私鑰,私鑰是一定要嚴格保護的,通過私鑰可以生成公鑰,但是從公鑰可以認為是永遠無法推導出私鑰的。 ∴ pfx文件一般都有文件密碼。
- pfx→cer 可以用OpenSSL
OpenSSL工具可以生成RSA公私鑰和證書格式轉換。不同格式的證書之間可以做如下轉換:
PEM → DER
PEM → P7B
PEM → PFX
P7B → PEM
P7B → PFX
PFX → PEM
DER → PEM
開發語言與私鑰證書的關系
開發語言
|
私鑰格式 | 證書格式 |
JAVA | .key.p8 | .crt |
PHP | .key.pem | .cert.pem |
.NET | .key.der | .crt |
其它 | .key.pem | .cert.pem |
我們是java語言,工程propertites配置:
#商戶私鑰 PKCS#8標准的私鑰 90000002.mer.prikey.path=cert/90000002.key.p8 #平台公鑰 X.509證書(DER格式的二進制文件) plat.cert.path=cert/umpay.cert.crt
1.3 RSA在支付api中的使用
下游交易渠道端:持有每個子商戶的私鑰 和 三方支付平台的公鑰
三方支付平台:持有下游渠道的每個子商戶的公鑰 和 三方平台自己的私鑰
獲取公鑰(PublicKey)/私鑰(PrivateKey)
2.1 代碼 RSACertUtil.java
兩種獲取Key的方式:1)從證書獲取;2)從密鑰串獲取

package rsademo; import com.umpay.core.util.Base64; import com.umpay.core.util.ProFileUtil; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; public class RSACertUtils { /** * 讀公鑰證書文件umpay.cert.crt,得到X509證書 * * @return * @throws Exception */ public static X509Certificate getCert() throws Exception { byte[] b = ProFileUtil.getFileByte("plat.cert.path"); try { ByteArrayInputStream bais = new ByteArrayInputStream(b); CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(bais); } catch (CertificateException e) { e.printStackTrace(); } return null; } /** * 從X509公鑰證書獲取PublicKey * * @return * @throws Exception */ public static PublicKey getPublicKey() throws Exception { X509Certificate x509Certificate = getCert(); try { byte[] keyBytes = x509Certificate.getPublicKey().getEncoded(); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(x509KeySpec); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeySpecException e) { e.printStackTrace(); } return null; } /** * 從公鑰字符串得到公鑰對象 * * @param publicKeyStr * @return */ public static PublicKey getPublicKeyFromKeyString(String publicKeyStr) { try { byte[] keyBytes = Base64.decode(publicKeyStr.getBytes()); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(x509KeySpec); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeySpecException e) { e.printStackTrace(); } return null; } /** * 從私鑰文件 merId.key.p8 得到私鑰對象 * * @param merId * @return */ public static PrivateKey getPrivateKey(String merId) { try { byte[] key = ProFileUtil.getFileByte(merId + ".mer.prikey.path"); PKCS8EncodedKeySpec e = new PKCS8EncodedKeySpec(key); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(e); } catch (IOException e1) { e1.printStackTrace(); } catch (NoSuchAlgorithmException e1) { e1.printStackTrace(); } catch (InvalidKeySpecException e1) { e1.printStackTrace(); } return null; } /** * 根據私鑰字符串得到私鑰對象 * * @param privateKeyStr * @return * @throws Exception */ public static PrivateKey getPrivateKeyFromKeyString(String privateKeyStr) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] decodedKey = Base64.decode(privateKeyStr.getBytes()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); return keyFactory.generatePrivate(keySpec); } }
2.2 java.security下的Key
2.3 java.security.spec下兩個spec
2.3.1 PKCS8EncodedKeySpec
* This class represents the ASN.1 encoding of a private key, * encoded according to the ASN.1 type {@code PrivateKeyInfo}. * The {@code PrivateKeyInfo} syntax is defined in the PKCS#8 standard * as follows: * * <pre> * PrivateKeyInfo ::= SEQUENCE { * version Version, * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, * privateKey PrivateKey, * attributes [0] IMPLICIT Attributes OPTIONAL } * * Version ::= INTEGER * * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier * * PrivateKey ::= OCTET STRING * * Attributes ::= SET OF Attribute * </pre>
*/
public class PKCS8EncodedKeySpec extends EncodedKeySpec {
//class實現代碼(略)
}
2.3.2 X509EncodedKeySpec
/** * This class represents the ASN.1 encoding of a public key, * encoded according to the ASN.1 type {@code SubjectPublicKeyInfo}. * The {@code SubjectPublicKeyInfo} syntax is defined in the X.509 * standard as follows: * * <pre> * SubjectPublicKeyInfo ::= SEQUENCE { * algorithm AlgorithmIdentifier, * subjectPublicKey BIT STRING } * </pre> */ public class X509EncodedKeySpec extends EncodedKeySpec {
//class實現代碼(略)
}
RSA加密/RSA解密
3.1 利用公鑰加密,利用私鑰解密
3.2 RSA加解密代碼 RSACipherUtil.java
加解密主要借助於javax.crypto.Cipher來實現

package rsademo; import com.umpay.core.util.Base64; import javax.crypto.Cipher; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; public class RSACipherUtil { /** * 公鑰加密 * * @param data * @return * @throws Exception */ public static String encrypt(PublicKey publicKey, String data, String charset) throws Exception { Cipher cipher = Cipher.getInstance(KeyFactory.getInstance("RSA").getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] cipherText = cipher.doFinal(data.getBytes(charset)); byte[] encodedByte = Base64.encode(cipherText); return new String(encodedByte).replace("\n", ""); } /** * RSA私鑰解密 * * @param privateKey * @param data * @return * @throws Exception */ public static String decrypt(PrivateKey privateKey, String data, String charset) throws Exception { byte[] byteData = Base64.decode(data.getBytes(charset)); Cipher cipher = Cipher.getInstance(KeyFactory.getInstance("RSA").getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] retB = cipher.doFinal(byteData); return new String(retB); } }
RSA簽名(加簽/驗簽)
利用私鑰簽名,利用公鑰驗簽/公鑰證書驗簽
4.1 常用的數據簽名/驗簽過程描述
生成rsa簽名的過程:
請求實體requestModel → 轉換成requestMap<String,String> → 將map的key進行排序 → 得到簽名原串 plainText → 讀取properties,根據property讀私鑰文件或私鑰串得到 PrivateKey privateKey → 生成RSA簽名 signData= sign(privateKey, plainText)→ Base64編碼 → 轉換成簽名字符串 signature=new String(base64Bytes)
驗簽的過程:
解析請求報文,得到簽名原串 plainText → 從請求頭或請求報文里得到簽名 signature → Base64解碼 signatureData = Base64.decode(signature.getBytes(charset)); → 讀取properties,根據公鑰證書文件得到X509Certificate 或 公鑰串轉換成PublicKey → 驗簽 verifySign(X509Certificate/PublicKey, plainText, signatureData)
*base64不是加密算法,但也是SSL經常使用的一種算法,它是編碼方式,用來把ascii碼和二進制碼之間互轉。
4.2 RSA簽名驗簽代碼 RSASignUtil.java

package rsademo; import com.umpay.core.util.Base64; import java.io.UnsupportedEncodingException; import java.security.*; import java.security.cert.X509Certificate; public class RSASignUtil { private static final String charset = "UTF-8"; private static final String signType = "spay"; /** * 生成簽名 * * @param plainText * @param privateKey * @return */ public static String sign(PrivateKey privateKey, String plainText) { try { Signature signature = getSignatureObj(signType); signature.initSign(privateKey);//public final void initSign(PrivateKey privateKey) signature.update(plainText.getBytes(charset)); byte[] signData = signature.sign(); return new String(Base64.encode(signData)); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (SignatureException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } /** * 通過PublicKey來驗簽 * * @param publicKey * @param plainText * @param signature * @return * @throws Exception */ public static boolean verifySign(PublicKey publicKey, String plainText, String signature) throws Exception { byte[] signData = Base64.decode(signature.getBytes(charset)); try { Signature sig = getSignatureObj(signType); sig.initVerify(publicKey);//public final void initVerify(PublicKey publicKey) sig.update(plainText.getBytes(charset)); return sig.verify(signData); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (SignatureException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return false; } /** * 通過公鑰X509證書來驗簽 * * @param cert * @param plainText * @param signature * @return * @throws Exception */ public static boolean verifySign(X509Certificate cert, String plainText, String signature) throws Exception { byte[] signData = Base64.decode(signature.getBytes(charset)); try { Signature sig = getSignatureObj(signType); sig.initVerify(cert);//public final void initVerify(Certificate certificate) sig.update(plainText.getBytes(charset)); return sig.verify(signData); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (SignatureException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return false; } private static Signature getSignatureObj(String signType) { String shaAlgorithm = null; if ("rest".equals(signType)) { shaAlgorithm = "SHA256withRSA"; } else if ("spay".equals(signType)) { shaAlgorithm = "SHA1withRSA"; } try { return Signature.getInstance(shaAlgorithm); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null; } } }
測試
package rsademo; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.UUID; public class TestMain { public static final String publicKeyString = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvGGQDrUk7ejBfgR6cwDhTUqDe" + "1+ooXzwowLfRRDqu1N0O9KyeAsY8nI8HUvzYGXODNMBEKZ2v8Ck7lelVoxlgkIAT" + "GHB2nM+TBZOPQAdF0X4crJh1yWjdnrGO5fluqUalwRvYIG91mqIPnvSGL9mLDIhi" + "7PR/duEe7KzwDCi3DQIDAQAB"; public static final String privateKeyString = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAK8YZAOtSTt6MF+B" + "HpzAOFNSoN7X6ihfPCjAt9FEOq7U3Q70rJ4CxjycjwdS/NgZc4M0wEQpna/wKTuV" + "6VWjGWCQgBMYcHacz5MFk49AB0XRfhysmHXJaN2esY7l+W6pRqXBG9ggb3Waog+e" + "9IYv2YsMiGLs9H924R7srPAMKLcNAgMBAAECgYEAgzW85QB7K2XyT+87WG23B8GY" + "qcWVRDGxrDxWwyvk6dS73xQ9Mp+TnCIaEHwA25Oe+0iRd8LT1t8alvtNAo6ZWYU9" + "Ek0yuNTJ5YpWIal+A3c25Zm8Ir3CgkCvq7+q+4OOhC4rOMoY6G8rxQQ7fm4noW0A" + "lZbgQJrhaqDfute4v2ECQQDVZZpZgNU7u9E2CoAXUjUMGWmapdyAsfdZo8kq7mP3" + "g/4JdcGVykxP2YTI1rR2/6vPvnRZN8wcSNVRoM+qWTCFAkEA0g0/3m5TD6wR5ajj" + "SoIMmYgu5OFToKVX1mDoPKBnrjsxzuPZHm/KhRtkRzafd+vs/DbLs60QtQTmyY7q" + "BZm26QJBAMq5KgeLF4cWpupS0VrWUuS6o5MxrCdqadPzf6FUNQ2ni8cK4ivdsd9N" + "ghKVvX0q59qEUN2M30+jdVuFjKKE9k0CQBtIHUOGkMM4Vhq+FMdYnMpUJcMUgQgc" + "cYwmigNV0iGPDqkQbuLFIkinhh65uXyZ5+3aMBrmH4VjXZZQOZUAogECQHs7Ywr+" + "ZSMllnuYOM9z+dXCcQRGKfcVa90fGo3bjrqanyhGyjAwwfECajPlTHm75AdgWegH" + "XXWxFu93sms7KJY="; public static void main(String[] args) throws Exception { // testCipher(); testSign(); } public static void testCipher() throws Exception { String text = "abcd" + UUID.randomUUID(); // Key publicKey = RSACertUtils.getPublicKey(); PublicKey publicKey = RSACertUtils.getPublicKeyFromKeyString(TestMain.publicKeyString); String encryptStr = RSACipherUtil.encrypt(publicKey, text, charset); System.out.println("encryptStr:" + encryptStr); // Key privateKey = RSACertUtils.getPrivateKey(merId); PrivateKey privateKey = RSACertUtils.getPrivateKeyFromKeyString(TestMain.privateKeyString); String decryptStr = RSACipherUtil.decrypt(privateKey, encryptStr, charset); System.out.println(decryptStr); System.out.println(text.equals(decryptStr)); } public static void testSign() throws Exception { System.out.println(publicKeyString); String text = "{\"trade_no\":\"1904261100329133\",\"amount\":\"1\",\"mer_id\":\"90000002\",\"mer_date\":\"20190426\",\"order_id\":\"0306262632992608256R\"}"; PrivateKey privateKey = RSACertUtils.getPrivateKeyFromKeyString(privateKeyString); // PrivateKey privateKey = RSACertUtils.getPrivateKey("111"); String signature = RSASignUtil.sign(privateKey, text); System.out.println("signature:" + signature); PublicKey publicKey = RSACertUtils.getPublicKeyFromKeyString(publicKeyString); X509Certificate certificate = RSACertUtils.getCert(); boolean verifiedOK = RSASignUtil.verifySign(publicKey, text, signature); // boolean verifiedOK =RSASignUtil.verifySign(certificate,text,signature); System.out.println(verifiedOK); } }
常見錯誤/異常:
RSA解密報錯 javax.crypto.BadPaddingException: Decryption error