支付API
English
English
  • Welcome 👏
  • Description
    • Access instructions
    • Safety information
    • Payment method
    • Signature generation
    • Signature verification
    • Decrypt callback
    • Data structure
  • Currency
    • Currency information
  • Order
    • Order creation
    • Order inquiry
    • Callback notification
    • Order closure
  • Payout
    • Payout inquiry
    • Create Payout
    • Callback
    • Cancel Payout
  • Wallet
    • Wallet addition
    • Wallet update
    • Wallet deletion
    • Wallet inquiry
    • Callback notification
  • Error code
    • Status code
Powered by GitBook
On this page
  • Encrypt message format
  • Decryption
  • Code example
  1. Description

Decrypt callback

PreviousSignature verificationNextData structure

Last updated 26 days ago

TIP

In order to ensure security, tokenpay payment encrypts the key information in the callback notification interface with AES-256-GCM. After the merchant receiving the message, and needs to decrypt the plaintext. The key of APP is used for decryption. This chapter describes the format of the encrypted message and how to decrypt it.

Encrypt message format

First, the merchant get the following information from the answer.

  • AES-GCM is a NIST-standardized algorithm, a mode of encryption that simultaneously guarantees the confidentiality, integrity, and authenticity of data. It is most widely used in TLS.

  • The key used for the callback message is the APP key.

  • For the encrypted data, we use a separate JSON object to represent it. In order to read conveniently, the example uses Pretty formatting and adds comments.

{
    "original_type": "transaction", // Object types before encryption
    "algorithm": "AEAD_AES_256_GCM", // Encryption algorithm

    // After encoding with Base6
    "ciphertext": "...",

    // Random string initialization vector used for encryption
    "nonce": "...",
}

Note: The encrypted random string has nothing to do with the random string used for signing. They're not the same.

Decryption

  • Most of the programming languages (newer version) both support AEAD_AES_256_GCM. Developers can refer to the following example, and learn how to implement decryption using your programming language.

Code example

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("无效的AppSecret,长度必须为32个字节");
        }
      this.aesKey = key;
    }

  public String decryptToString(byte[] nonce, String ciphertext)
  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);

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

class AesUtil {
  /**
  * AES key
  *
  * @var string
  */
  private $aesKey;

  const KEY_LENGTH_BYTE = 32;
  const AUTH_TAG_LENGTH_BYTE = 16;

 /**
  * Constructor
  */
  public
  function __construct($aesKey) {
    if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
      throw new InvalidArgumentException('无效的AppSecret,长度应为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');
  }
}

  </CodeGroupItem>

  <CodeGroupItem title="Java">

```java:no-line-numbers
OkHttpClient client = new OkHttpClient().newBuilder()
   .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "<body data here>");
Request request = new Request.Builder()
   .url("https://api.ttpay.io/v1/wallet/update")
   .method("POST", body)
   .addHeader("Authorization", "<Authorization>")
   .addHeader("User-Agent", "TTPay API (https://ttpay.io)")
   .addHeader("Content-Type", "application/json")
   .build();
Response response = client.newCall(request).execute();

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

  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
  import base64
  def decrypt(nonce, ciphertext, associated_data):

    key = "Your32Apiv3Key"

    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)

Algorithmic interface details, you can refer to .

authenticated encryption
rfc5116