# 敏感信息加解密

## 加密算法

敏感信息加密使用的[RSA公钥加密算法](https://zh.wikipedia.org/wiki/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95)。加密算法使用的填充方案，我们使用了相对更安全的RSAES-OAEP(Optimal Asymmetric Encryption Padding)。

RSAES-OAEP在各个编程语言中的模式值为：

* [OpenSSL](https://www.openssl.org/docs/man1.1.0/man3/RSA_public_encrypt.html)，padding设置为`RSA_PKCS1_OAEP_PADDING`
* Java，使用`Cipher.getinstance(RSA/ECB/OAEPWithSHA-1AndMGF1Padding)`
* [PHP](https://www.php.net/manual/en/function.openssl-public-encrypt.php)，padding设置为`OPENSSL_PKCS1_OAEP_PADDING`
* [.NET](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider.encrypt?view=netframework-4.5.2)，fOAEP设置为`true`
* [Node.js](https://nodejs.org/api/crypto.html#crypto_crypto_publicencrypt_key_buffer)，padding设置为`crypto.constants.RSA_PKCS1_OAEP_PADDING`
* [Go](https://golang.org/pkg/crypto/rsa/#EncryptOAEP)，使用`EncryptOAEP`

开发者应当使用**微信支付平台证书**中的公钥，对上送的敏感信息进行加密。这样只有拥有私钥的微信支付才能对密文进行解密，从而保证了信息的机密性。

另一方面，微信支付使用**商户证书**中的公钥对下行的敏感信息进行加密。开发者应使用**商户私钥**对下行的敏感信息的密文进行解密。

## 声明加密使用的平台证书

某些情况下，微信支付会更新平台证书。这时，商户有多个微信支付平台证书可以用于加密。为了保证解密顺利，商户发起请求的HTTP头部中应包括**证书序列号**，以声明加密所用的密钥对和证书。

* 商户上送敏感信息时使用**微信支付平台公钥**加密，证书序列号包含在请求HTTP头部的`Wechatpay-Serial`

## 加密示例

开发者应当使用**微信支付平台证书**中的公钥，对上送的敏感信息进行加密。

大部分编程语言支持RSA公钥加密。你可以参考示例，了解如何使用您的编程语言实现敏感信息加密。

{% tabs %}
{% tab title="Java" %}

```java
public static String rsaEncryptOAEP(String message, X509Certificate certificate)
    throws IllegalBlockSizeException, IOException {
  try {
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
    cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());

    byte[] data = message.getBytes("utf-8");
    byte[] cipherdata = cipher.doFinal(data);
    return Base64.getEncoder().encodeToString(cipherdata);
  } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
    throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
  } catch (InvalidKeyException e) {
    throw new IllegalArgumentException("无效的证书", e);
  } catch (IllegalBlockSizeException | BadPaddingException e) {
    throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
  }
```

{% endtab %}

{% tab title="Go" %}

```go
secretMessage := []byte("send reinforcements, we're going to advance")
rng := rand.Reader

cipherdata, err := EncryptOAEP(sha1.New(), rng, rsaPublicKey, secretMessage, nil)
if err != nil {
        fmt.Fprintf(os.Stderr, "Error from encryption: %s\n", err)
        return
}

ciphertext := base64.StdEncoding.EncodeToString(cipherdata)
fmt.Printf("Ciphertext: %s\n", ciphertext)
```

{% endtab %}

{% tab title="PHP" %}

```php
 private function getEncrypt($str){ 
     //$str是待加密字符串 
     $public_key_path = '平台证书路径'; 
     $public_key = file_get_contents($public_key_path); 
     $encrypted = ''; 
     if (openssl_public_encrypt($str,$encrypted,$public_key,OPENSSL_PKCS1_OAEP_PADDING)) { 
         //base64编码 
         $sign = base64_encode($encrypted);
     } else {
         throw new Exception('encrypt failed');
     }
     return $sign;
 } 
```

{% endtab %}

{% tab title=".NET" %}

```csharp
/// <summary>
/// 最终提交请求时，需对敏感信息加密，如身份证、银行卡号。
/// 加密算法是RSA，使用从接口下载到的公钥进行加密，非后台下载到的私钥。
/// 感谢ISV(https://github.com/ndma-isv)提供示例
/// 
/// </summary>
/// <param name="text">要加密的明文</param>
/// <param name="publicKey"> -----BEGIN CERTIFICATE----- 开头的string，转为bytes </param>
/// <returns></returns>
public static string RSAEncrypt(string text, byte[] publicKey)
{
    using (var x509 = new X509Certificate2(publicKey))
    {
        using (var rsa = (RSACryptoServiceProvider)x509.PublicKey.Key)
        {
            var buff = rsa.Encrypt(Encoding.UTF8.GetBytes(text), true);

            return Convert.ToBase64String(buff);
        }
    }
}
```

{% endtab %}
{% endtabs %}

## 解密示例

微信支付使用**商户证书**中的公钥对下行的敏感信息进行加密。开发者应使用**商户私钥**对下行的敏感信息的密文进行解密。

同样的，大部分编程语言支持RSA私钥解密。你可以参考示例，了解如何使用您的编程语言实现敏感信息解密。

{% tabs %}
{% tab title="Java" %}

```java
  public static String rsaDecryptOAEP(String ciphertext, PrivateKey privateKey)
      throws BadPaddingException, IOException {
    try {
      Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
      cipher.init(Cipher.DECRYPT_MODE, privateKey);

      byte[] data = Base64.getDecoder().decode(ciphertext);
      return new String(cipher.doFinal(data), "utf-8");
    } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
      throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("无效的私钥", e);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
      throw new BadPaddingException("解密失败");
    }
  }
```

{% endtab %}

{% tab title="Go" %}

```go
cipherdata, _ := base64.StdEncoding.DecodeString(ciphertext)
rng := rand.Reader

plaintext, err := DecryptOAEP(sha1.New(), rng, rsaPrivateKey, cipherdata, nil)
if err != nil {
        fmt.Fprintf(os.Stderr, "Error from decryption: %s\n", err)
        return
}

fmt.Printf("Plaintext: %s\n", string(plaintext))
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/min-gan-xin-xi-jia-mi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
