遇到的問題
在一個與Ruby語言對接的項目中,決定使用RSA算法來作為數據傳輸的加密與簽名算法。但是,在使用Ruby生成后給我的私鑰時,卻發生了異常:IOException: algid parse error, not a sequence
原因以及解決辦法
通常JAVA中使用的RSA私鑰格式必須為PKCS8格式,但是Ruby可以使用pkcs1格式的私鑰。另外,在使用openssl生成RSA密鑰對時,如果需要得到PKCS8格式的私鑰需要多進行一步操作,因此可能為了麻煩,Ruby方就直接使用PKCS1格式的私鑰。
這對於JAVA來說非常難受,但只要知道了原因,解決起來並不是難事。可能會有將PKCS1轉為PKCS8格式的JAVA代碼,但是網上資料不多,於是只好使用簡單粗暴的辦法,手動使用工具來轉換私鑰的格式。
使用openssl轉換
下載openssl工具,將私鑰按照指定格式輸入至一個pem文件中,然后復制到openssl目錄下,打開openssl后輸入命令:
pkcs8 -topk8 -inform PEM -in 文件名.pem -outform PEM -nocrypt
顯示的私鑰即為PKCS8格式的私鑰。
PS:java使用時務必刪除首尾行的“-----”字符串以及所有換行。
使用在線工具
用法與上面的方法一致,記得刪除無關字符。
對於RSA的密鑰對,通常公鑰都是PKCS8格式的,但是說不定就會遇到PKCS1格式,上面的網站就不能用了,別急,還有一個呢:
RSA算法
RSA是一種非對稱加密算法,簡單的來說就是加密與解密的所使用的密鑰都不相同,因此相對於加解密都使用一個密鑰的對稱加密算法來說具有更好的安全性。此外,它還可以作為數字簽名算法,使用數字簽名的目的就是為了驗證數據的來源是否正確以及數據是否被修改過。
Java工具類
這里簡單放一下本人使用的RSA工具類,包含了生成密鑰對、加解密以及簽名和校驗簽名的方法。
/**
* RSA加解密、創建與校驗簽名的工具類
*/
public class RSAUtils {
public static final String PUBLIC_KEY = "PUBLIC_KEY";
public static final String PRIVATE_KEY = "PRIVATE_KEY";
private static final Base64.Encoder base64Encoder = Base64.getEncoder();
private static final Base64.Decoder base64Decoder = Base64.getDecoder();
private static final String ALGORITHM = "RSA";
/**
* 簽名算法
*/
private static final String SIGN_TYPE = "SHA1withRSA";
/**
* 密鑰長度
*/
private static final Integer KEY_LENGTH = 1024;
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 生成秘鑰對,公鑰和私鑰
*
* @return 秘鑰鍵值對
* @throws Exception 創建秘鑰對異常
*/
public static Map<String, Key> genKeyPair() throws Exception {
Map<String, Key> keyMap = new HashMap<>();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_LENGTH); // 秘鑰字節數
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* 公鑰加密
*
* @param data 加密前數據
* @param publicKey 公鑰
* @return 加密后數據
* @throws Exception 加密異常
*/
public static byte[] encryptByPublicKey(byte[] data, PublicKey publicKey) throws Exception {
// 加密數據,分段加密
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLength = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
while (inputLength - offset > 0) {
if (inputLength - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offset, inputLength - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
public static byte[] encryptByPublicKey(byte[] data, String publicKeyBase64Encoded) throws Exception {
return encryptByPublicKey(data, parseString2PublicKey(publicKeyBase64Encoded));
}
/**
* 私鑰解密
*
* @param data 解密前數據
* @param privateKey 私鑰
* @return 解密后數據
* @throws Exception 解密異常
*/
public static byte[] decryptByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
// 解密數據,分段解密
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int inputLength = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
while (inputLength - offset > 0) {
if (inputLength - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(data, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offset, inputLength - offset);
}
out.write(cache);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
public static byte[] decryptByPrivateKey(byte[] data, String privateKeyBase64Encoded) throws Exception {
return decryptByPrivateKey(data, parseString2PrivateKey(privateKeyBase64Encoded));
}
/**
* 創建簽名
*
* @param source 要簽名的信息
* @param privateKey 私鑰
* @return 簽名
* @throws Exception 簽名異常
*/
public static byte[] createSign(String source, PrivateKey privateKey) throws Exception {
Signature signet = Signature.getInstance(SIGN_TYPE);
signet.initSign(privateKey);
signet.update(source.getBytes());
return signet.sign();
}
public static byte[] createSign(String source, String privateKeyBase64Encoded) throws Exception {
return createSign(source, parseString2PrivateKey(privateKeyBase64Encoded));
}
/**
* 校驗簽名
*
* @param expected 期望信息
* @param sign 簽名
* @param publicKey 公鑰
* @return 結果
* @throws Exception 校驗異常
*/
public static boolean checkSign(String expected, byte[] sign, PublicKey publicKey) throws Exception {
Signature signetCheck = Signature.getInstance(SIGN_TYPE);
signetCheck.initVerify(publicKey);
signetCheck.update(expected.getBytes());
return signetCheck.verify(sign);
}
public static boolean checkSign(String expected, byte[] sign, String publicKeyBase64Encoded) throws Exception {
return checkSign(expected, sign, parseString2PublicKey(publicKeyBase64Encoded));
}
/**
* 將base64格式的公鑰轉換為對象
*
* @param publicKeyBase64Encoded base64的公鑰
* @return 公鑰
* @throws Exception 轉換異常
*/
public static PublicKey parseString2PublicKey(String publicKeyBase64Encoded) throws Exception {
return KeyFactory.getInstance(ALGORITHM).generatePublic(
new X509EncodedKeySpec(base64Decoder.decode(publicKeyBase64Encoded)));
}
/**
* 將base64格式的私鑰轉換為對象
*
* @param privateKeyBase64Encoded base64的私鑰
* @return 私鑰
* @throws Exception 轉換異常
*/
public static PrivateKey parseString2PrivateKey(String privateKeyBase64Encoded) throws Exception {
return KeyFactory.getInstance(ALGORITHM).generatePrivate(
new PKCS8EncodedKeySpec(base64Decoder.decode(privateKeyBase64Encoded)));
}
}
另附調用方法:
public static void main(String[] args) throws Exception {
// write your code here
// 創建密鑰對
Map<String, Key> map = RSAUtils.genKeyPair();
PublicKey publicKey = (PublicKey) map.get(RSAUtils.PUBLIC_KEY);
PrivateKey privateKey = (PrivateKey) map.get(RSAUtils.PRIVATE_KEY);
System.out.println("創建的密鑰對:");
System.out.println("公鑰:" + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
System.out.println("私鑰:" + Base64.getEncoder().encodeToString(privateKey.getEncoded()));
String info = "hello world!";
System.out.println("原文為:" + info);
String str = Base64.getEncoder().encodeToString(RSAUtils.encryptByPublicKey(info.getBytes(), publicKey));
String sign = Base64.getEncoder().encodeToString(RSAUtils.createSign(info, privateKey));
System.out.println(">>>>>>>>>>>");
System.out.println("密文為:" + str);
System.out.println("簽名為:" + sign);
System.out.println(">>>>>>>>>>>");
String resultInfo = new String(RSAUtils.decryptByPrivateKey(Base64.getDecoder().decode(str), privateKey));
Boolean resultSign = RSAUtils.checkSign(info, Base64.getDecoder().decode(sign), publicKey);
System.out.println(String.format("解密結果:%s,簽名校驗結果:%s", resultInfo, resultSign));
}
執行結果:
創建的密鑰對:
公鑰:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTWSrzD61bczhKTrar8Xu4sm6zSVu+xo2ur0b4iSif0Xufm7OK/T2k0jwjdvTSg7BoR0dqXMjvlPEyUIZORFcT2HzNnl58zwzdW7S2XeMFtL10SBZpjcp1zPGbiJPde6fzqFNDLKJgj4P37f+BTJTr7UORSMdFr2OwrLOvWnUgEQIDAQAB
私鑰:MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJNZKvMPrVtzOEpOtqvxe7iybrNJW77Gja6vRviJKJ/Re5+bs4r9PaTSPCN29NKDsGhHR2pcyO+U8TJQhk5EVxPYfM2eXnzPDN1btLZd4wW0vXRIFmmNynXM8ZuIk917p/OoU0MsomCPg/ft/4FMlOvtQ5FIx0WvY7Css69adSARAgMBAAECgYBJbGhjgA9hf5OwK3MJUSbWjUtuWYK3GNenET5rQGWW5dsVWI/qFXDvPber8G3krKxt+f7TOHMEN5LNAKU8QP+maGXL99uzoBHf6kHQWgwYWvD3kHfJEww1nv3/9a6P9Z0ZiL4DfiYy1tWCZ9gv6KLID05mC4NXiEr4TYhkcyYT8QJBANdP5iDDAJxKfLuFu00Y5jJ+Tuee7nRUjrSob2jRRBrZcsccCITR34aOr1+pZwZCPoHisyWjQL+mi/JKV9Y3Iw0CQQCvMWaok2g55ZEsBMlB38t/UfavGvnLqd7StZg+J+n0VFCfr05QiW9tn+IEtZGOHY317BgrpJ5dhVclIH5g3EAVAkBsWqwwLpJfFOlCoaFJwk8OeBwTWhscdfU/G0i90hpY/LdTVls/JDM+Dw5YsPLE5o94Y/LN7SNHj3P8IcekaSj9AkBvtnKdwBFQCeD+Trb++HPM5jkFA5CRm+poNj+0MsNud21JxgGMPXb+UltPYXBFTPc+/6OSANCzFdmx5PxxS0DZAkEAuicRUzcZThNu/9XWA24fU1w1+fT2T4hgANYmTJntaw+0m5cB03UQvvadGb9gslbDgaLWALS9mOLTPy+3nOHdng==
原文為:hello world!
>>>>>>>>>>>
密文為:kiRSpYMdfcgoWosXVwKHUZQUzFAykiSqF3HQba0QHBwW7UEUDE9pZcx2lZxlHZzS3hhLeRl9CxwpF6ZE5Mza1GuW8DnZJ0k3RFlyyYaTvUWCH0GRq1BgRB8f/ouwbw2opdVd2kIfltRWWU4gvvyvFs2wPfZnyDsMJpyM3GpwnzE=
簽名為:BQ5R0nGWj9IXNhqiZjCR23YTPKhS3ryAZEO76EaaF6awmEeL1/Ptf0IL8bFD57JurM2aybF6MXkFPb4dMaotZoyiHUMcUdnhLXOpRVvbtDXuyDjCWTwUqqjMyvO9qNm9s16veJj7B4Cu5r1jw4M6wR7vfDJm+89Amzrh+6dG+l0=
>>>>>>>>>>>
解密結果:hello world!,簽名校驗結果:true