SHA256WithRSA


上文中了解到SHA和RSA,工作中恰好用到擴展應用:SHA256WithRSA,本文總結下學習過程,備忘の
再提供另外一種方法,實現Java版pem密鑰和.Net版xml密鑰相互轉換的方法

密鑰轉換

准備:引入BouncyCastle.Crypto.dll

  • RSA密鑰:Pem --> XML
public static string RSAKeyPemToXml(string pemKey, bool isPrivateKey)
{
    string rsaKey = string.Empty;
    object pemObject = null;
    RSAParameters rsaPara = new RSAParameters();
    using (var sReader = new StringReader(pemKey)) {
        var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sReader);
        pemObject = pemReader.ReadObject();//(AsymmetricCipherKeyPair)
    }
            
    if (isPrivateKey)//RSA私鑰
    {
        RsaPrivateCrtKeyParameters key = (RsaPrivateCrtKeyParameters)((AsymmetricCipherKeyPair)pemObject).Private;
        rsaPara = new RSAParameters {
            Modulus = key.Modulus.ToByteArrayUnsigned(),
            Exponent = key.PublicExponent.ToByteArrayUnsigned(),
            D = key.Exponent.ToByteArrayUnsigned(),
            P = key.P.ToByteArrayUnsigned(),
            Q = key.Q.ToByteArrayUnsigned(),
            DP = key.DP.ToByteArrayUnsigned(),
            DQ = key.DQ.ToByteArrayUnsigned(),
            InverseQ = key.QInv.ToByteArrayUnsigned(),  };
    }
    else//RSA公鑰
    {
        RsaKeyParameters key = (RsaKeyParameters)pemObject;
        rsaPara = new RSAParameters {
            Modulus = key.Modulus.ToByteArrayUnsigned(),
            Exponent = key.Exponent.ToByteArrayUnsigned(),  };
    }

    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.ImportParameters(rsaPara);
    using (StringWriter sw = new StringWriter()) {
        sw.Write(rsa.ToXmlString(isPrivateKey ? true : false));
        rsaKey = sw.ToString();
    }
    return rsaKey;
}
  • RSA密鑰:XML --> Pem
public static string RSAKeyXmlToPem(string RSAKeyXml, bool isPrivateKey, bool replacefix)
{
    string pemKey = string.Empty;
    var rsa = new RSACryptoServiceProvider();
    rsa.FromXmlString(RSAKeyXml);

    RSAParameters rsaPara = new RSAParameters();
    RsaKeyParameters key = null;
    //RSA私鑰
    if (isPrivateKey)  {
        rsaPara = rsa.ExportParameters(true);
        key = new RsaPrivateCrtKeyParameters(
            new BigInteger(1, rsaPara.Modulus), new BigInteger(1, rsaPara.Exponent), new BigInteger(1, rsaPara.D),
            new BigInteger(1, rsaPara.P), new BigInteger(1, rsaPara.Q), new BigInteger(1, rsaPara.DP), new BigInteger(1, rsaPara.DQ),
            new BigInteger(1, rsaPara.InverseQ));
    }
    //RSA公鑰
    else  {
        rsaPara = rsa.ExportParameters(false);
        key = new RsaKeyParameters(false,
            new BigInteger(1, rsaPara.Modulus),  new BigInteger(1, rsaPara.Exponent));
    }

    using (TextWriter sw = new StringWriter()) {
        var pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(sw);
        pemWriter.WriteObject(key);
        pemWriter.Writer.Flush();
        pemKey = sw.ToString();
    }

    if (replacefix)  {
        //去掉證書的頭部和尾部
        pemKey = isPrivateKey ? pemKey.Replace("-----BEGIN RSA PRIVATE KEY-----", "").Replace("-----END RSA PRIVATE KEY-----", "") :
            pemKey.Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", "");
        return pemKey.Replace("\n", "").Replace("\r", "");
    }
    else { return pemKey; }
}

注意,調用RSAKeyPemToXml()方法時,pemKey必須格式正確(帶前后綴且換行),否則調用報錯。

-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

此外,調用RSAKeyXmlToPem()方法做私鑰轉換時,結果與原Pem密鑰不一致,慎用。

密鑰轉換(java)

  • 公鑰:X.509 pem,Java:X509EncodedKeySpec
  • 私鑰:PKCS#8 pem,Java:PKCS8EncodedKeySpec
/**
 * C#私鑰轉換成java私鑰
 */
public static String privateKeyFromXml(String privateKeyXml) {
	privateKeyXml = privateKeyXml.replaceAll("\r", "").replaceAll("\n", "");

	KeyFactory keyFactory;
	try {
		String modulusXml = privateKeyXml.substring(privateKeyXml.indexOf("<Modulus>") + 9, privateKeyXml.indexOf("</Modulus>"));
		BigInteger modulus = new BigInteger(1, Base64.getDecoder().decode(modulusXml));

		String publicExponentXml = privateKeyXml.substring(privateKeyXml.indexOf("<Exponent>") + 10, privateKeyXml.indexOf("</Exponent>"));
		BigInteger publicExponent = new BigInteger(1, Base64.getDecoder().decode(publicExponentXml));

		String privateExponentXml = privateKeyXml.substring(privateKeyXml.indexOf("<D>") + 3, privateKeyXml.indexOf("</D>"));
		BigInteger privateExponent = new BigInteger(1, Base64.getDecoder().decode(privateExponentXml));

		String primePXml = privateKeyXml.substring(privateKeyXml.indexOf("<P>") + 3, privateKeyXml.indexOf("</P>"));
		BigInteger primeP = new BigInteger(1, Base64.getDecoder().decode(primePXml));

		String primeQXml = privateKeyXml.substring(privateKeyXml.indexOf("<Q>") + 3, privateKeyXml.indexOf("</Q>"));
		BigInteger primeQ = new BigInteger(1, Base64.getDecoder().decode(primeQXml));

		String primeExponentPXml = privateKeyXml.substring(privateKeyXml.indexOf("<DP>") + 4, privateKeyXml.indexOf("</DP>"));
		BigInteger primeExponentP = new BigInteger(1, Base64.getDecoder().decode(primeExponentPXml));

		String primeExponentQXml = privateKeyXml.substring(privateKeyXml.indexOf("<DQ>") + 4, privateKeyXml.indexOf("</DQ>"));
		BigInteger primeExponentQ = new BigInteger(1, Base64.getDecoder().decode(primeExponentQXml));

		String crtCoefficientXml = privateKeyXml.substring(privateKeyXml.indexOf("<InverseQ>") + 10, privateKeyXml.indexOf("</InverseQ>"));
		BigInteger crtCoefficient = new BigInteger(1, Base64.getDecoder().decode(crtCoefficientXml));

		RSAPrivateCrtKeySpec rsaPriKey = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient);

		keyFactory = KeyFactory.getInstance("RSA");
		PrivateKey privateKey = keyFactory.generatePrivate(rsaPriKey);
		byte[] bytes = Base64.getEncoder().encode(privateKey.getEncoded());
		return new String(bytes, Charset.forName("utf-8"));
	} catch (Exception e) {
		System.err.println(e.toString());
	}
	return null;
}

/**
 * C#公鑰轉換成java公鑰
 */
public static String publicKeyFromXml(String publicKeyXml) {
	KeyFactory keyFactory;
	publicKeyXml = publicKeyXml.replaceAll("\r", "").replaceAll("\n", "");
	try {
		String modulusXml = publicKeyXml.substring(publicKeyXml.indexOf("<Modulus>") + 9, publicKeyXml.indexOf("</Modulus>"));
		BigInteger modulus = new BigInteger(1, Base64.getDecoder().decode(modulusXml));

		String exponentXml = publicKeyXml.substring(publicKeyXml.indexOf("<Exponent>") + 10, publicKeyXml.indexOf("</Exponent>"));
		BigInteger publicExponent = new BigInteger(1, Base64.getDecoder().decode(exponentXml));

		RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, publicExponent);
		keyFactory = KeyFactory.getInstance("RSA");
		PublicKey publicKey = keyFactory.generatePublic(rsaPubKey);
		byte[] bytes = Base64.getEncoder().encode(publicKey.getEncoded());
		return new String(bytes, Charset.forName("utf-8"));
	} catch (Exception e) {
		System.err.println(e.toString());
		return null;
	}
}

/**
 * java私鑰轉換成C#私鑰
 */
public static String privateKeyToXml(RSAPrivateCrtKey privateKey) {

	String modulusBase64 = Base64.getEncoder().encodeToString(privateKey.getModulus().toByteArray());
	String modulus = modulusBase64.replace("\r", "").replace("\n", "");

	String exponentBase64 = Base64.getEncoder().encodeToString(privateKey.getPublicExponent().toByteArray());
	String exponent = exponentBase64.replace("\r", "").replace("\n", "");

	String pBase64 = Base64.getEncoder().encodeToString(privateKey.getPrimeP().toByteArray());
	String p = pBase64.replace("\r", "").replace("\n", "");

	String qBase64 = Base64.getEncoder().encodeToString(privateKey.getPrimeQ().toByteArray());
	String q = qBase64.replace("\r", "").replace("\n", "");

	String dpBase64 = Base64.getEncoder().encodeToString(privateKey.getPrimeExponentP().toByteArray());
	String dp = dpBase64.replace("\r", "").replace("\n", "");

	String dqBase64 = Base64.getEncoder().encodeToString(privateKey.getPrimeExponentQ().toByteArray());
	String dq = dqBase64.replace("\r", "").replace("\n", "");

	String dBase64 = Base64.getEncoder().encodeToString(privateKey.getPrivateExponent().toByteArray());
	String d = dBase64.replace("\r", "").replace("\n", "");
	
	String inverseQBase64 = Base64.getEncoder().encodeToString(privateKey.getCrtCoefficient().toByteArray());
	String inverseQ = inverseQBase64.replace("\r", "").replace("\n", "");

	StringBuilder stringBuilder = new StringBuilder();
	stringBuilder.append("<RSAKeyValue>\n");
	stringBuilder.append("<Modulus>").append(modulus).append("</Modulus>\n");
	stringBuilder.append("<Exponent>").append(exponent).append("</Exponent>\n");
	stringBuilder.append("<P>").append(p).append("</P>\n");
	stringBuilder.append("<Q>").append(q).append("</Q>\n");
	stringBuilder.append("<DP>").append(dp).append("</DP>\n");
	stringBuilder.append("<DQ>").append(dq).append("</DQ>\n");
	stringBuilder.append("<InverseQ>").append(inverseQ).append("</InverseQ>\n");
	stringBuilder.append("<D>").append(d).append("</D>\n");
	stringBuilder.append("</RSAKeyValue>");
	return stringBuilder.toString();
}

/**
 * java公鑰轉換成C#公鑰
 */
public static String publicKeyToXml(RSAPublicKey publicKey) {
	String modulusBase64 = Base64.getEncoder().encodeToString(publicKey.getModulus().toByteArray());
	String modulus = modulusBase64.replace("\r", "").replace("\n", "");

	String exponentBase64 = Base64.getEncoder().encodeToString(publicKey.getPublicExponent().toByteArray());
	String exponent = exponentBase64.replace("\r", "").replace("\n", "");

	StringBuilder stringBuilder = new StringBuilder();
	stringBuilder.append("<RSAKeyValue>\n");
	stringBuilder.append("<Modulus>").append(modulus).append("</Modulus>\n");
	stringBuilder.append("<Exponent>").append(exponent).append("</Exponent>\n");
	stringBuilder.append("</RSAKeyValue>");
	return stringBuilder.toString();
}

詳見:C#-Java密鑰轉換

pfx證書

  • PFX證書:由Public Key Cryptography Standards #12,PKCS#12標准定義,包含公鑰和私鑰的二進制格式的證書形式,以pfx作為證書文件后綴名
  • CER證書:證書中沒有私鑰,DER編碼二進制格式的證書文件/BASE64編碼格式的證書文件,以cer作為證書文件后綴名

綜上所述:pfx證書文件中比cer文件中多了私鑰。
通過pfx證書實現數據簽名和驗簽

public static string Sign(string dataForSign, string priKeyFile, string keyPwd)
{
    var rsa = GetPrivateKey(priKeyFile, keyPwd);

    // Create a new RSACryptoServiceProvider
    var rsaClear = new RSACryptoServiceProvider();
    // Export RSA parameters from 'rsa' and import them into 'rsaClear'
    var paras = rsa.ExportParameters(true);
    rsaClear.ImportParameters(paras);

    using (var sha256 = new SHA256CryptoServiceProvider()) {
        var signData = rsaClear.SignData(Encoding.UTF8.GetBytes(dataForSign), sha256);
        return BytesToHex(signData);
    }
}
public bool VerifySign(string dataForSign, string signedData, string pubKeyFile)
{
    var rsa = GetPublicKey(pubKeyFile);
    using (var sha256 = new SHA256CryptoServiceProvider()) {
        return rsa.VerifyData(Encoding.UTF8.GetBytes(dataForSign), sha256, HexToBytes(signedData));
    }   
}

其中,從.pfx證書中提取公鑰和私鑰的方法

private static RSACryptoServiceProvider GetPrivateKey(string priKeyFile, string keyPwd) {
    var pc = new X509Certificate2(priKeyFile, keyPwd,
        X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);          
    return (RSACryptoServiceProvider)pc.PrivateKey; //return cert.PrivateKey.ToXmlString(true);
}

private static RSACryptoServiceProvider GetPublicKey(string pubKeyFile) {
    var pc = new X509Certificate2(pubKeyFile);
    return (RSACryptoServiceProvider)pc.PublicKey.Key; //return cert.PublicKey.Key.ToXmlString(false);
}

具體參見:.NET版SHA256WithRSA簽名驗簽,java版本參見:java版SHA256withRSA
關於如何生成數字證書,僅供參考:方法1方法2
該文C#創建數字證書並導出為pfx,並使用pfx進行非對稱加解密有時間可以研究下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM