公鑰與私鑰是成對的,一般的,我們認(rèn)為的是公鑰加密、私鑰解密、私鑰簽名、公鑰驗證,有人說成私鑰加密,公鑰解密時不對的。
公鑰與私鑰的生成有多種方式,可以通過程序生成(下文具體實現(xiàn)),可以通過openssl工具:
- # 生成一個私鑰,推薦使用1024位的秘鑰,秘鑰以pem格式保存到-out參數(shù)指定的文件中,采用PKCS1格式
- openssl genrsa -out rsa.pem 1024
- # 生成與私鑰對應(yīng)的公鑰,生成的是Subject Public Key,一般配合PKCS8格式私鑰使用
- openssl rsa -in rsa.pem -pubout -out rsa.pub
RSA生成公鑰與私鑰一般有兩種格式:PKCS1和PKCS8,上面的命令生成的秘鑰是PKCS1格式的,而公鑰是Subject Public Key,一般配合PKCS8格式私鑰使用,所以就可能會涉及到PKCS1和PKCS8之間的轉(zhuǎn)換:
- # PKCS1格式私鑰轉(zhuǎn)換為PKCS8格式私鑰,私鑰直接輸出到-out參數(shù)指定的文件中
- openssl pkcs8 -topk8 -inform PEM -in rsa.pem -outform pem -nocrypt -out rsa_pkcs8.pem
- # PKCS8格式私鑰轉(zhuǎn)換為PKCS1格式私鑰,私鑰直接輸出到-out參數(shù)指定的文件中
- openssl rsa -in rsa_pkcs8.pem -out rsa_pkcs1.pem
-
- # PKCS1格式公鑰轉(zhuǎn)換為PKCS8格式公鑰,轉(zhuǎn)換后的內(nèi)容直接輸出
- openssl rsa -pubin -in rsa.pub -RSAPublicKey_out
- # PKCS8格式公鑰轉(zhuǎn)換為PKCS1格式公鑰,轉(zhuǎn)換后的內(nèi)容直接輸出
- openssl rsa -RSAPublicKey_in -pubout -in rsa.pub
現(xiàn)實中,我們往往從pem、crt、pfx文件獲取公私和私鑰,crt、pfx的制作可以參考:簡單的制作ssl證書,并在nginx和IIS中使用。
Java實現(xiàn)
為簡化說明介紹,這里我直接封裝了一個工具類,因為要從pem、crt、pfx文件獲取公私和私鑰,因此引用了一個第三方包:BouncyCastle,可以直接在pom.xml中添加依賴:
- <dependency>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcprov-jdk15on</artifactId>
- <version>1.68</version>
- </dependency>
簡單封裝的RsaUtil.java:
- import java.io.FileInputStream;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.math.BigInteger;
- import java.security.KeyFactory;
- import java.security.KeyPair;
- import java.security.KeyPairGenerator;
- import java.security.KeyStore;
- import java.security.PrivateKey;
- import java.security.PublicKey;
- import java.security.SecureRandom;
- import java.security.Security;
- import java.security.Signature;
- import java.security.cert.Certificate;
- import java.security.cert.CertificateFactory;
- import java.security.cert.X509Certificate;
- import java.security.interfaces.RSAPrivateKey;
- import java.security.interfaces.RSAPublicKey;
- import java.security.spec.KeySpec;
- import java.security.spec.PKCS8EncodedKeySpec;
- import java.security.spec.RSAPrivateCrtKeySpec;
- import java.security.spec.RSAPublicKeySpec;
- import java.security.spec.X509EncodedKeySpec;
- import java.util.Enumeration;
-
- import javax.crypto.Cipher;
-
- import org.bouncycastle.asn1.ASN1Integer;
- import org.bouncycastle.asn1.ASN1Primitive;
- import org.bouncycastle.asn1.DLSequence;
- import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
- import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
- import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
- import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
- import org.bouncycastle.jce.provider.BouncyCastleProvider;
- import org.bouncycastle.util.io.pem.PemObject;
- import org.bouncycastle.util.io.pem.PemReader;
- import org.bouncycastle.util.io.pem.PemWriter;
-
- public class RsaUtil {
-
- static {
- Security.addProvider(new BouncyCastleProvider());
- }
-
- /**
- * 隨機(jī)生成密鑰對
- *
- * @param usePKCS8
- * 是否采用PKCS8填充模式
- */
- public static Object[] generateRsaKey(boolean usePKCS8) throws Exception {
- KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
- // 初始化
- keyPairGen.initialize(1024, new SecureRandom());
- // 生成一個密鑰對,保存在keyPair中
- KeyPair keyPair = keyPairGen.generateKeyPair();
- RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私鑰
- RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公鑰
-
- // 這兩個公私鑰是PKCS8格式的
- byte[] publicKeyBytes = publicKey.getEncoded();
- byte[] privateKeyBytes = privateKey.getEncoded();
-
- if (!usePKCS8) {
- // 將PSCK8格式公私鑰轉(zhuǎn)換為PKCS1格式
- publicKeyBytes = pkcs8ToPkcs1(false, publicKeyBytes);
- privateKeyBytes = pkcs8ToPkcs1(true, privateKeyBytes);
- }
-
- return new Object[] { publicKeyBytes, privateKeyBytes };
- }
-
- /**
- * 從Pem文件讀取密鑰對
- *
- * @param reader
- * 輸入流
- * @param pemFileName
- * pem文件
- */
- public static byte[] readFromPem(String pemFileName) throws Exception {
- PemReader pemReader = new PemReader(new FileReader(pemFileName));
- PemObject pemObject = pemReader.readPemObject();
- byte[] publicKey = pemObject.getContent();
- pemReader.close();
- return publicKey;
- }
-
- /**
- * 從Pem文件讀取密鑰
- *
- * @param isPrivateKey
- * 是否是私鑰
- * @param buffer
- * 字節(jié)
- * @param pemFileName
- * pem文件
- */
- public static void writeToPem(byte[] buffer, boolean isPrivateKey, String pemFileName) throws Exception {
-
- PemObject pemObject = new PemObject(isPrivateKey ? "RSA PRIVATE KEY" : "RSA PUBLIC KEY", buffer);
- FileWriter fileWriter = new FileWriter(pemFileName);
- PemWriter pemWriter = new PemWriter(fileWriter);
- pemWriter.writeObject(pemObject);
- pemWriter.close();
- }
-
- /**
- * 從crt文件讀取公鑰(pkcs8)
- *
- * @param crtFileName
- * crt文件
- * @return 公鑰
- */
- public static byte[] readPublicKeyFromCrt(String crtFileName) throws Exception {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- X509Certificate cert = (X509Certificate) cf.generateCertificate(new FileInputStream(crtFileName));
-
- PublicKey publicKey = cert.getPublicKey();
- return publicKey.getEncoded();
- }
-
- /**
- * 從pfx文件讀取秘鑰對(pkcs8)
- *
- * @param pfxFileName
- * pfx文件
- * @return 秘鑰對
- */
- public static Object[] readFromPfx(String pfxFileName, String password) throws Exception {
- KeyStore keystore = KeyStore.getInstance("PKCS12");
- char[] passwordChars = null;
- if (password == null || password.equals("")) {
- passwordChars = null;
- } else {
- passwordChars = password.toCharArray();
- }
- keystore.load(new FileInputStream(pfxFileName), passwordChars);
- Enumeration<String> enums = keystore.aliases();
-
- PrivateKey privateKey = null;
- Certificate certificate = null;
- while (enums.hasMoreElements()) {
- String alias = enums.nextElement();
- System.out.println(alias);
- if (keystore.isKeyEntry(alias)) {
- privateKey = (PrivateKey) keystore.getKey(alias, passwordChars);
- certificate = keystore.getCertificate(alias);
- }
- if (privateKey != null && certificate != null)
- break;
- }
- if (privateKey == null || certificate == null) {
- throw new Exception("fail to read key from pfx");
- }
-
- PublicKey publicKey = certificate.getPublicKey();
- return new Object[] { publicKey.getEncoded(), privateKey.getEncoded() };
- }
-
- /**
- * Pkcs8轉(zhuǎn)Pkcs1
- *
- * @param isPrivateKey
- * 是否是私鑰轉(zhuǎn)換
- * @param buffer
- * Pkcs1秘鑰
- * @return Pkcs8秘鑰
- * @throws Exception
- * 加密過程中的異常信息
- */
- public static byte[] pkcs8ToPkcs1(boolean isPrivateKey, byte[] buffer) throws Exception {
- if (isPrivateKey) {
- PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(buffer);
- return privateKeyInfo.parsePrivateKey().toASN1Primitive().getEncoded();
- } else {
- SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(buffer);
- return subjectPublicKeyInfo.parsePublicKey().toASN1Primitive().getEncoded();
- }
- }
-
- /**
- * Pkcs1轉(zhuǎn)Pkcs8
- *
- * @param isPrivateKey
- * 是否是私鑰轉(zhuǎn)換
- * @param buffer
- * Pkcs1秘鑰
- * @return Pkcs8秘鑰
- * @throws Exception
- * 加密過程中的異常信息
- */
- public static byte[] pkcs1ToPkcs8(boolean isPrivateKey, byte[] buffer) throws Exception {
- AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption);
- ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(buffer);
- if (isPrivateKey) {
- PrivateKeyInfo privateKeyInfo = new PrivateKeyInfo(algorithmIdentifier, asn1Primitive);
- return privateKeyInfo.getEncoded();
- } else {
- SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(algorithmIdentifier, asn1Primitive);
- return subjectPublicKeyInfo.getEncoded();
- }
- }
-
- /**
- * RSA公鑰
- *
- * @param usePKCS8
- * 是否采用PKCS8填充模式
- * @param publicKey
- * 公鑰
- * @return 公鑰
- * @throws Exception
- * 加密過程中的異常信息
- */
- public static RSAPublicKey generatePublicKey(boolean usePKCS8, byte[] publicKey) throws Exception {
- KeySpec keySpec;
- if (usePKCS8) {
- // PKCS8填充
- keySpec = new X509EncodedKeySpec(publicKey);
- } else {
- // PKCS1填充
- DLSequence sequence = (DLSequence) ASN1Primitive.fromByteArray(publicKey);
- BigInteger v1 = ((ASN1Integer) sequence.getObjectAt(0)).getValue();
- BigInteger v2 = ((ASN1Integer) sequence.getObjectAt(1)).getValue();
- keySpec = new RSAPublicKeySpec(v1, v2);
- }
-
- RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME).generatePublic(keySpec);
- return pubKey;
- }
-
- /**
- * RSA私鑰
- *
- * @param usePKCS8
- * 是否采用PKCS8填充模式
- * @param privateKey
- * 私鑰
- * @return 私鑰
- * @throws Exception
- * 解密過程中的異常信息
- */
- public static RSAPrivateKey generatePrivateKey(boolean usePKCS8, byte[] privateKey) throws Exception {
- KeySpec keySpec;
- if (usePKCS8) {
- // PKCS8填充
- keySpec = new PKCS8EncodedKeySpec(privateKey);
- } else {
- // PKCS1填充
- DLSequence sequence = (DLSequence) ASN1Primitive.fromByteArray(privateKey);
- // BigInteger v1= ((ASN1Integer)sequence.getObjectAt(0)).getValue();
- BigInteger v2 = ((ASN1Integer) sequence.getObjectAt(1)).getValue();
- BigInteger v3 = ((ASN1Integer) sequence.getObjectAt(2)).getValue();
- BigInteger v4 = ((ASN1Integer) sequence.getObjectAt(3)).getValue();
- BigInteger v5 = ((ASN1Integer) sequence.getObjectAt(4)).getValue();
- BigInteger v6 = ((ASN1Integer) sequence.getObjectAt(5)).getValue();
- BigInteger v7 = ((ASN1Integer) sequence.getObjectAt(6)).getValue();
- BigInteger v8 = ((ASN1Integer) sequence.getObjectAt(7)).getValue();
- BigInteger v9 = ((ASN1Integer) sequence.getObjectAt(8)).getValue();
- keySpec = new RSAPrivateCrtKeySpec(v2, v3, v4, v5, v6, v7, v8, v9);
- }
-
- RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME).generatePrivate(keySpec);
- return priKey;
- }
-
- /**
- * RSA公鑰加密
- *
- * @param value
- * 加密字符串
- * @param publicKey
- * 公鑰
- * @return 密文
- * @throws Exception
- * 加密過程中的異常信息
- */
- public static String rsaEncrypt(String value, RSAPublicKey publicKey) throws Exception {
- if (value == null || value.length() == 0)
- return "";
-
- // RSA加密
- Cipher cipher = Cipher.getInstance("RSA");
- cipher.init(Cipher.ENCRYPT_MODE, publicKey);
- byte[] buffer = cipher.doFinal(value.getBytes("utf-8"));
-
- // 使用hex格式輸出公鑰
- StringBuffer result = new StringBuffer();
- for (int i = 0; i < buffer.length; i++) {
- result.append(String.format("%02x", buffer[i]));
- }
- return result.toString();
- }
-
- /**
- * RSA私鑰解密
- *
- * @param value
- * 加密字符串
- * @param privateKey
- * 私鑰
- * @return 明文
- * @throws Exception
- * 解密過程中的異常信息
- */
- public static String rsaDecrypt(String value, RSAPrivateKey privateKey) throws Exception {
- if (value == null || value.length() == 0)
- return "";
-
- byte[] buffer = new byte[value.length() / 2];
- for (int i = 0; i < buffer.length; i++) {
- buffer[i] = (byte) Integer.parseInt(value.substring(i * 2, i * 2 + 2), 16);
- }
-
- // RSA解密
- Cipher cipher = Cipher.getInstance("RSA");
- cipher.init(Cipher.DECRYPT_MODE, privateKey);
- buffer = cipher.doFinal(buffer);
- return new String(buffer, "utf-8");
- }
-
- /**
- * RSA簽名
- *
- * @param value
- * 加密字符串
- * @param privateKey
- * 私鑰
- * @param halg
- * 加密算法,如MD5, SHA1, SHA256, SHA384, SHA512等
- * @return 簽名
- * @throws Exception
- * 簽名過程中的異常信息
- */
- public static String sign(String value, RSAPrivateKey privateKey, String halg) throws Exception {
-
- Signature s = Signature.getInstance(halg.toUpperCase().endsWith("WithRSA") ? halg : (halg + "WithRSA"));
-
- s.initSign(privateKey);
- s.update(value.getBytes("utf-8"));
-
- byte[] buffer = s.sign();
-
- // 使用hex格式輸出公鑰
- StringBuffer result = new StringBuffer();
- for (int i = 0; i < buffer.length; i++) {
- result.append(String.format("%02x", buffer[i]));
- }
- return result.toString();
- }
-
- /**
- * RSA簽名驗證
- *
- * @param value
- * 加密字符串
- * @param publicKey
- * 公鑰
- * @param halg
- * 加密算法,如MD5, SHA1, SHA256, SHA384, SHA512等
- * @return 簽名合法則返回true,否則返回false
- * @throws Exception
- * 驗證過程中的異常信息
- */
- public static boolean verify(String value, RSAPublicKey publicKey, String signature, String halg) throws Exception {
- Signature s = Signature.getInstance(halg.toUpperCase().endsWith("WithRSA") ? halg : (halg + "WithRSA"));
- s.initVerify(publicKey);
- s.update(value.getBytes("utf-8"));
-
- byte[] buffer = new byte[signature.length() / 2];
- for (int i = 0; i < buffer.length; i++) {
- buffer[i] = (byte) Integer.parseInt(signature.substring(i * 2, i * 2 + 2), 16);
- }
-
- return s.verify(buffer);
- }
-
- }
生成公鑰和私鑰:
- // 生成公私鑰
- Object[] rsaKey = RsaUtil.generateRsaKey(usePKCS8); //usePKCS8=true表示是否成PKCS8格式的公私秘鑰,否則乘車PKCS1格式的公私秘鑰
- byte[] publicKey = (byte[]) rsaKey[0];
- byte[] privateKey = (byte[]) rsaKey[1];
生成秘鑰后,需要保存,一般保存到pem文件中:
- // 保存到pem文件,filePath是保存目錄
- RsaUtil.writeToPem(publicKey, false, filePath + "rsa.pub");
- RsaUtil.writeToPem(privateKey, true, filePath + "rsa.pem");
可以保存到pem文件中,當(dāng)然也可以從pem文件中讀取了:
- // 從Pem文件讀取公私鑰,filePath是文件目錄
- byte[] publicKey = RsaUtil.readFromPem(filePath + "rsa.pub");
- byte[] privateKey = RsaUtil.readFromPem(filePath + "rsa.pem");
還可以從crt證書中讀取公鑰,而crt文件不包含私鑰,因此需要單獨(dú)獲取私鑰:
- // 從crt文件讀取公鑰(crt文件中不包含私鑰),filePath是文件目錄
- byte[] publicKey = RsaUtil.readPublicKeyFromCrt(filePath + "demo.crt");
- byte[] privateKey = RsaUtil.readFromPem(filePath + "demo.key");
pfx文件中包含了公鑰和私鑰,可以很方便就讀取到:
- // 從pfx文件讀取公私鑰,filePath是文件目錄
- Object[] rsaKey = RsaUtil.readFromPfx(filePath + "demo.pfx", "123456");
- byte[] publicKey = (byte[]) rsaKey[0];
- byte[] privateKey = (byte[]) rsaKey[1];
有時候我們還可能需要進(jìn)行秘鑰的轉(zhuǎn)換:
- // Pkcs8格式公鑰轉(zhuǎn)換為Pkcs1格式公鑰
- publicKey = RsaUtil.pkcs8ToPkcs1(false, publicKey);
- // Pkcs8格式私鑰轉(zhuǎn)換為Pkcs1格式私鑰
- privateKey = RsaUtil.pkcs8ToPkcs1(true, privateKey);
- // Pkcs1格式公鑰轉(zhuǎn)換為Pkcs8格式公鑰
- publicKey = RsaUtil.pkcs1ToPkcs8(false, publicKey);
- // Pkcs1格式私鑰轉(zhuǎn)換為Pkcs8格式私鑰
- privateKey = RsaUtil.pkcs1ToPkcs8(true, privateKey);
有了公鑰和私鑰,接下就就能實現(xiàn)加密、解密、簽名、驗證簽名等操作了:
- RSAPublicKey rsaPublicKey = RsaUtil.generatePublicKey(usePKCS8, publicKey);
- RSAPrivateKey rsaPrivateKey = RsaUtil.generatePrivateKey(usePKCS8, privateKey);
-
- String encryptText = RsaUtil.rsaEncrypt(text, rsaPublicKey);
- System.out.printf("【%s】經(jīng)過【RSA】加密后:%s\n", text, encryptText);
-
- String decryptText = RsaUtil.rsaDecrypt(encryptText, rsaPrivateKey);
- System.out.printf("【%s】經(jīng)過【RSA】解密后:%s\n", encryptText, decryptText);
-
- String signature = RsaUtil.sign(text, rsaPrivateKey, "MD5");
- System.out.printf("【%s】經(jīng)過【RSA】簽名后:%s\n", text, signature);
-
- boolean result = RsaUtil.verify(text, rsaPublicKey, signature, "MD5");
- System.out.printf("【%s】的簽名【%s】經(jīng)過【RSA】驗證后結(jié)果是:" + result, text, signature);
這里完整的demo代碼:
- import java.security.interfaces.RSAPrivateKey;
- import java.security.interfaces.RSAPublicKey;
-
- public class RsaMain {
-
- public static void main(String[] args) {
- try {
- String text = "上山打老虎";
- boolean usePKCS8 = true; // usePKCS8=true表示是否成PKCS8格式的公私秘鑰,否則乘車PKCS1格式的公私秘鑰
- String filePath = RsaUtil.class.getClassLoader().getResource("").getPath();
- System.out.printf("文件路徑:%s\n", filePath);// 存放pem,crt,pfx等文件的目錄
- byte[] publicKey, privateKey;// 公鑰和私鑰
-
- // 生成公私鑰
- Object[] rsaKey = RsaUtil.generateRsaKey(usePKCS8); // usePKCS8=true表示是否成PKCS8格式的公私秘鑰,否則乘車PKCS1格式的公私秘鑰
- publicKey = (byte[]) rsaKey[0];
- privateKey = (byte[]) rsaKey[1];
- // 從Pem文件讀取公私鑰,filePath是文件目錄
- // publicKey = RsaUtil.readFromPem(filePath + "rsa.pub");
- // privateKey = RsaUtil.readFromPem(filePath + "rsa.pem");
- // 從pfx文件讀取公私鑰,filePath是文件目錄
- // Object[] rsaKey = RsaUtil.readFromPfx(filePath + "demo.pfx",
- // "123456");
- // publicKey = (byte[]) rsaKey[0];
- // privateKey = (byte[]) rsaKey[1];
- // 從crt文件讀取公鑰(crt文件中不包含私鑰),filePath是文件目錄
- // publicKey = RsaUtil.readPublicKeyFromCrt(filePath + "demo.crt");
- // privateKey = RsaUtil.readFromPem(filePath + "demo.key");
-
- // 保存到pem文件,filePath是保存目錄
- RsaUtil.writeToPem(publicKey, false, filePath + "rsa.pub");
- RsaUtil.writeToPem(privateKey, true, filePath + "rsa.pem");
-
- // Pkcs8格式公鑰轉(zhuǎn)換為Pkcs1格式公鑰
- publicKey = RsaUtil.pkcs8ToPkcs1(false, publicKey);
- // Pkcs8格式私鑰轉(zhuǎn)換為Pkcs1格式私鑰
- privateKey = RsaUtil.pkcs8ToPkcs1(true, privateKey);
- // Pkcs1格式公鑰轉(zhuǎn)換為Pkcs8格式公鑰
- publicKey = RsaUtil.pkcs1ToPkcs8(false, publicKey);
- // Pkcs1格式私鑰轉(zhuǎn)換為Pkcs8格式私鑰
- privateKey = RsaUtil.pkcs1ToPkcs8(true, privateKey);
-
- RSAPublicKey rsaPublicKey = RsaUtil.generatePublicKey(usePKCS8, publicKey);
- RSAPrivateKey rsaPrivateKey = RsaUtil.generatePrivateKey(usePKCS8, privateKey);
-
- String encryptText = RsaUtil.rsaEncrypt(text, rsaPublicKey);
- System.out.printf("【%s】經(jīng)過【RSA】加密后:%s\n", text, encryptText);
-
- String decryptText = RsaUtil.rsaDecrypt(encryptText, rsaPrivateKey);
- System.out.printf("【%s】經(jīng)過【RSA】解密后:%s\n", encryptText, decryptText);
-
- String signature = RsaUtil.sign(text, rsaPrivateKey, "MD5");
- System.out.printf("【%s】經(jīng)過【RSA】簽名后:%s\n", text, signature);
-
- boolean result = RsaUtil.verify(text, rsaPublicKey, signature, "MD5");
- System.out.printf("【%s】的簽名【%s】經(jīng)過【RSA】驗證后結(jié)果是:" + result, text, signature);
-
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
以上就是Java 實現(xiàn)RSA非對稱加密算法的詳細(xì)內(nèi)容。