跳转至

通知报文解密

为了保证安全性,盛付通支付在事件回调通知接口中,对关键信息进行了AES-256-GCM加密,商户收到报文后,要解密出明文

加密报文需求使用AES加密key,商户需求使用管理员账号登录b.shengpay.com进行设置AES加密key

账户设置-》秘钥管理->AES秘钥

解密算法如下

JAVA版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AesUtil {

    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;
    private final byte[] aesKey;

    public AesUtil(byte[] key) {
        if (key.length != KEY_LENGTH_BYTE) {
            throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
        }
        this.aesKey = key;
    }

    public String decryptToString(byte[] nonce, String ciphertext,byte[] associatedData)
            throws GeneralSecurityException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

PHP版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64

def decrypt(nonce, ciphertext, associated_data):
    key = "d1f20ym47uaorjqdoe9irqxhjiiwyqu4"

    key_bytes = str.encode(key)
    nonce_bytes = str.encode(nonce)
    ad_bytes = str.encode(associated_data)
    data = base64.b64decode(ciphertext)

    aesgcm = AESGCM(key_bytes)
    return aesgcm.decrypt(nonce_bytes, data, ad_bytes)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class AesGcm
{
    private static string ALGORITHM = "AES/GCM/NoPadding";
    private static int TAG_LENGTH_BIT = 128;
    private static int NONCE_LENGTH_BYTE = 12;
    private static string AES_KEY = "yourkeyhere";

    public static string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)
    {
        GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
        AeadParameters aeadParameters = new AeadParameters(
            new KeyParameter(Encoding.UTF8.GetBytes(AES_KEY)), 
            128, 
            Encoding.UTF8.GetBytes(nonce), 
            Encoding.UTF8.GetBytes(associatedData));
        gcmBlockCipher.Init(false, aeadParameters);

        byte[] data = Convert.FromBase64String(ciphertext);
        byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
        int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
        gcmBlockCipher.DoFinal(plaintext, length);
        return Encoding.UTF8.GetString(plaintext);
    }
}

PHP版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class AesUtil {

 private $aesKey;

 const KEY_LENGTH_BYTE = 32;
 const AUTH_TAG_LENGTH_BYTE = 16;


 public
 function __construct($aesKey) {
  if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
   throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
  }
  $this - > aesKey = $aesKey;
 }

 /**
  * Decrypt AEAD_AES_256_GCM ciphertext
  *
  * @param string    $associatedData     AES GCM additional authentication data
  * @param string    $nonceStr           AES GCM nonce
  * @param string    $ciphertext         AES GCM cipher text
  *
  * @return string|bool      Decrypted string on success or FALSE on failure
  */
 public
 function decryptToString($associatedData, $nonceStr, $ciphertext) {
  $ciphertext = \base64_decode($ciphertext);
  if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
   return false;
  }

  // ext-sodium (default installed on >= PHP 7.2)
  if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
   return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this - > aesKey);
  }

  // ext-libsodium (need install libsodium-php 1.x via pecl)
  if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
   return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this - > aesKey);
  }

  // openssl (PHP >= 7.1 support AEAD)
  if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
   $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
   $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

   return \openssl_decrypt($ctext, 'aes-256-gcm', $this - > aesKey, \OPENSSL_RAW_DATA, $nonceStr,
    $authTag, $associatedData);
  }

  throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
 }
}

测试报文

下面是测试数据,接入时可以参考下面测试数据验证是否解密正确

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    public static void main(String[] args) throws GeneralSecurityException, IOException {
        String aesKey = "d1f20ym47uaorjqdoe9irqxhjiiwyqu4";
        String nonce = "4vuo6n8vimq6";
        String associatedData = "";//保留字段,默认为空
        String ciphertext = "VTYYLoJvQr5vzv27Qmr6xyNMj0bYwBnn4NOfYFCkV6mklfsMmCK2+0eBG2rKGKArboohgFK1xKQHyOdDgBmLddQeuH5X3VPp4sEMoECWGDgqyG5IrnZOYIeRUAxPlrPUMYdm2S3+K++9vMovILmzOzxwCh51hfYGL0deFNdvRfzcJHrJPVJqjbw/8MWmfBxesY0f71mgB2mcD1kLPk/Ete2xo7NtBspnFw==";

        AesUtil util = new AesUtil(aesKey.getBytes());
        String a = util.decryptToString(nonce.getBytes(),ciphertext ,associatedData.getBytes());
        System.out.println(a);
        //{"expireTime":"2024-09-21 23:59:59","licenseType":"BUSINESS_LICENSE","mchId":"88084849","mchName":"上海益充电子商务有限公司","status":"WAIT"}
    }
Back to top