RSA加解密&RSA加验签详解

 

RSA 加密算法是目前最有影响力的 公钥加密算法,并且被普遍认为是目前 最优秀的公钥方案 之一。RSA 是第一个能同时用于 加密 和 数字签名 的算法,它能够 抵抗 到目前为止已知的 所有密码攻击,已被 ISO 推荐为公钥数据加密标准。

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 数字证书格式

一般情况,公钥证书采用的是X509的格式,私钥证书采用的是pkcs7/pkcs8/pkcs12的格式。
 
【公钥证书格式】

格式 扩展名 说明
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,就可知是pkcs#12格式的私钥证书,.cer就是公钥证书

公钥,可以对外给任何人的加密和解密密码,公开的,可以任何人访问
私钥,私钥是一定要严格保护的,通过私钥可以生成公钥,但是从公钥可以认为是永远无法推导出私钥的。 ∴ 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);
    }
}

View Code

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("
", "");
    }

    /**
     * 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);
    }
}

View Code

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;
        }
    }
}

View Code

 

测试

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

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注