最近在學習golang,決定用golang搭建一個支付中心的服務,客服端與服務器通信使用非對稱rsa加簽驗簽,來保證通信的可靠安全。客服端可以請求支付中心golang生成公私鑰,支付中心提供sdk,sdk支持各主流語言。golang版本的rsa簽名和驗簽都沒問題,
在寫java版本的sdk確出現了問題,先把golang版本的sdk貼上。
package sdk import ( "crypto" "crypto/md5" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/pem" "errors" "log" ) const ( privateBlockType = "RSA PRIVATE KEY" publicBlockType = "RSA PUBLIC KEY" ) // Md5 to encode string of src, // the return string length is 32 func Md5(src string) string { defer GetTimer("md5加密")() bs := md5.Sum([]byte(src)) endCodeStr := hex.EncodeToString(bs[:]) return endCodeStr } func GenRsaKeys(bits int) (priKey, pubKey, pkcs8Key string, err error) { defer GetTimer("生成rsa密鑰對")() key, err := rsa.GenerateKey(rand.Reader, bits) if err != nil { return "", "", "", err } err = key.Validate() if err != nil { return "", "","", err } priBytes := x509.MarshalPKCS1PrivateKey(key) priKey = string(pem.EncodeToMemory(&pem.Block{Type: privateBlockType, Bytes: priBytes})) pubBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey) if err != nil { panic(err) } pubKey = string(pem.EncodeToMemory(&pem.Block{Type: publicBlockType, Bytes: pubBytes})) pkcs8Bytes, err := x509.MarshalPKCS8PrivateKey(key) if err != nil { return "", "", "", err } pkcs8Key = string(pem.EncodeToMemory(&pem.Block{Type: privateBlockType, Bytes: pkcs8Bytes})) return priKey, pubKey, pkcs8Key, err } func decodePrivateKey(privateKey string) (*rsa.PrivateKey, error) { block, _ := pem.Decode([]byte(privateKey)) if block == nil || block.Type != privateBlockType { return nil, errors.New("failed to decode PEM block containing private key") } return x509.ParsePKCS1PrivateKey(block.Bytes) } //私鑰加簽 func SignWithPrivateKey(privateKey string, src []byte) (string, error) { defer GetTimer("私鑰加簽")() key, err := decodePrivateKey(privateKey) if err != nil { return "", err } hashed := sha256.Sum256(src) log.Println("sha256:", base64.StdEncoding.EncodeToString(hashed[:])) signBytes, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, hashed[:]) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(signBytes), nil } func decodePublicKey(publicKey string) (*rsa.PublicKey, error) { block, _ := pem.Decode([]byte(publicKey)) if block == nil { return nil, errors.New("failed to decode PEM block containing private key") } pub,err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return nil, err } return pub.(*rsa.PublicKey), err } // VerifyWithPublicKey 公鑰驗簽 func VerifyWithPublicKey(signData string, srcData []byte, publicKey string) error { defer GetTimer("公鑰驗簽")() key, err := decodePublicKey(publicKey) if err != nil { return err } bytes, err := base64.StdEncoding.DecodeString(signData) if err != nil { return err } hashed := sha256.Sum256(srcData) err = rsa.VerifyPKCS1v15(key, crypto.SHA256, hashed[:], bytes) if err != nil { return err } return nil } // DecryptWithPrivateKey私鑰解密 func DecryptWithPrivateKey(privateKey string, encryptData string) ([]byte, error) { defer GetTimer("私鑰解密")() key, err := decodePrivateKey(privateKey) if err != nil { return nil, err } encryptBytes, err := base64.StdEncoding.DecodeString(encryptData) if err != nil { return nil, err } return rsa.DecryptPKCS1v15(rand.Reader, key, encryptBytes) } //EncryptWithPublicKey 公鑰加密 func EncryptWithPublicKey(publicKey string, bs []byte) (string, error) { defer GetTimer("公鑰加密")() key, err := decodePublicKey(publicKey) if err != nil { return "", err } encryptBytes, err := rsa.EncryptPKCS1v15(rand.Reader, key, bs) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(encryptBytes), nil }
GenRsaKeys(int)參數是生成私鑰字節大小,一般是1024或者2048,第一個返回值是cs1版本的私鑰,第二個參數是公鑰,第三個參數是pkcs8的私鑰,java語言只能使用
pkcs8的私鑰,cs1的會報錯,另外java使用公鑰私鑰都要去掉開頭和結尾的block type,不然會報錯。這個函數生成的公私鑰下面命令生成的等價
//生成cs1私鑰 openssl genrsa -out rsa_private_key.pem 2048 //生成公鑰 openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem //生成pkcs8私鑰 openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform pem -nocrypt -out private_pkcs8.pem
java版本的有公鑰加密,pkcs8私鑰解密,私鑰加簽,公鑰驗簽
package com.zhqn.rsa.util; import javax.crypto.Cipher; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Base64; /** * Date: 2022/1/27 18:10 * Description: * * @author zhouquan3 */ public class CryptoUtils { public static final String RSA_ALGORITHM = "rsa"; /** * 非對稱加密 * * @param src 待加密字符串 * @param publicKey 公鑰 * @return 解密后的字符串 */ public static String encryptWithPublicKey(String src, String publicKey) { try { byte[] publicKeyBytes = parseKey(publicKey); KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); PublicKey key = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes)); Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] encodeBytes = cipher.doFinal(src.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encodeBytes); } catch (Exception e) { throw new RuntimeException(e); } } /** * 去掉公鑰或者私鑰blockType * @param key 私鑰或者公鑰 * @return bytes */ private static byte[] parseKey(String key) { String newKey = Arrays.stream(key.split("\n")).filter(line -> line.length() > 0 && line.charAt(0) != '-').reduce(String::concat).orElse(""); return Base64.getDecoder().decode(newKey.getBytes()); } /** * 非對稱解密 * * @param src 待解密字符串 * @param privateKey 符合pkcs8私鑰 * @return 解密后的字符串 */ public static String decryptWithPkcs8(String src, String privateKey) { try { byte[] srcBytes = Base64.getDecoder().decode(src); byte[] privateKeyBytes = parseKey(privateKey); KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); PrivateKey key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes)); Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key); byte[] decryptBytes = cipher.doFinal(srcBytes); return new String(decryptBytes); } catch (Exception e) { throw new RuntimeException(e); } } private static PublicKey getPublicKey(String publicKey) { byte[] keyBytes = parseKey(publicKey); try { KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); return keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes)); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new RuntimeException(e); } } /** * 用公鑰驗簽 * @param sign 簽名 * @param publicKey 公鑰 * @param plain 原文 * @return true 通過 false 未通過 */ public static boolean verifySignWithPublicKey(String publicKey, String sign, String plain) { try { Signature signature = Signature.getInstance("SHA256WithRSA"); signature.initVerify(getPublicKey(publicKey)); signature.update(plain.getBytes(StandardCharsets.UTF_8)); return signature.verify(Base64.getDecoder().decode(sign)); } catch (Exception e ) { e.printStackTrace(); return false; } } /** * 用pkcs8格式的私鑰加簽名 * @param privateKey 私鑰 * @param plain 原文 * @return base64 */ public static String signWithPkcs8(String privateKey, String plain) { try { Signature signature = Signature.getInstance("SHA256WithRSA"); signature.initSign(getPrivateKey(privateKey)); signature.update(plain.getBytes()); byte[] bytes = signature.sign(); return Base64.getEncoder().encodeToString(bytes); } catch (Exception e) { throw new RuntimeException(e); } } private static PrivateKey getPrivateKey(String privateKey) { byte[] privateKeyBytes = parseKey(privateKey); KeyFactory keyFactory = null; try { keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes)); } catch (Exception e) { throw new RuntimeException(e); } } }
java加簽流程:signWithPkcs8(pkcs8私鑰,明文)得到簽名sign,golang調用VerifyWithPublicKey(簽名,原文,公鑰),驗證通過
golang加簽流程: SignWithPrivateKey(cs1版本的私鑰,明文)得到簽名sign,java調用verifySignWithPublicKey(公鑰,簽名,原文)
java加密流程: encryptWithPublicKey(公鑰,原文)得到密文,golang調用DecryptWithPrivateKey(cs1版本的私鑰,密文)得到原文
golang加密流程:EncryptWithPublicKey(公鑰,原文)得到密文, java調用decryptWithPkcs8(密文,pkcs8密鑰)得到原文
以上流程都充分測試,未發現問題,可以放心使用