最近支付公司和银联要求需要对PID上送格式做加密处理, 梳理几个重要逻辑帮助各服务商完成改造流程。

总的来说分为以下几步:

  1. 按要求整理待加密数据内容,该补位补位,注意左右,输出formatPidAndOrderStr;
  2. 形成的MAC ELEMEMENT BLOCK(MAB)按每 16 个字节做异或,如果最后不满 16 个字节,则添加 0x00
  3. 16 个字节(RESULT BLOCK)转换成 32 个 HEXDECIMAL,然后再分为两组(文档写的需要ASCII 字符, 我实际测试分组一不需要转换直接加密是正确的)
  4. 将组1进行SM4加密得到【结果】 & 【组2结果】 做异或得到 result2;
  5. result2再次SM4加密截取前8位即可。

完整代码( com.jeequan.jeepay.core.utils.JeepayPIDKit.java )

/*

 * Copyright (c) 2021-2031, 河南安付捷科技有限公司 (https://www.deveapp.cn & jeequan@126.com).

 * <p>

 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 * <p>

 * http://www.gnu.org/licenses/lgpl.html

 * <p>

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

package com.jeequan.jeepay.core.utils;



import cn.hutool.core.util.HexUtil;

import cn.hutool.crypto.SmUtil;



import javax.crypto.Cipher;

import javax.crypto.KeyGenerator;

import javax.crypto.SecretKey;

import javax.crypto.SecretKeyFactory;

import javax.crypto.spec.DESKeySpec;

import java.security.NoSuchAlgorithmException;

import java.security.SecureRandom;

import java.text.DecimalFormat;

import java.util.Arrays;



/***

* 根据pid等信息获取pidSct值

*

* @author terrfly#东南飞

* @site https://www.deveapp.cn

* @date 2025/9/17 11:20

*/

public class JeepayPIDKit {









    private final static String DES = "DES";

    private final static String CIPHER_ALGORITHM = "DES/ECB/NoPadding";





    public static void main(String[] args) {

        String pid = "C1009999";

        String payOrderId = "1220250321034502483875104";

        Long amount = 123L;

        String key = "F1774E4EDB4396B1647304EB4A6A4E3D";

        System.out.println(JeepayPIDKit.calculatePidSct(pid, payOrderId, amount, key));

    }





    /**

     * 功能描述:

     生成加密字符串

     * 在开展加密运算时,服务商 PID 代码长度为 11 位,左

     * 对齐,右补空格;服务商订单编号长度为 40 位,左对齐,

     * 右补空格;订单金额长度为 12 位,以分为单位,右对齐,

     * 左补 0

     * @param pid 服务商PID(11位)

     * @param payOrderId 订单编号(40位)

     * @param amount 订单金额(以分为单位)

     * @return 格式化后的字符串

     * @Author: terrfly#东南飞

     * @Date: 2025/9/17 11:10

     */

    public static String formatPidAndOrder(String pid, String payOrderId, Long amount) {

        // 格式化PID(11位左对齐右补空格), 若超过11位则固定11位

        String formattedPid = String.format("%-11s", pid.length() > 11 ? pid.substring(0, 11) : pid);



        // 格式化订单编号(40位左对齐右补空格)

        String formattedOrderId = String.format("%-40s", payOrderId.length() > 40 ? payOrderId.substring(0, 40) : payOrderId);



        // 格式化金额(12位右对齐左补0)

        DecimalFormat df = new DecimalFormat("000000000000");

        String formattedAmount = df.format(amount);



        return formattedPid + formattedOrderId + formattedAmount;

    }



    /**

     * 处理MAB块:按16字节分组异或,不足补0x00

     * @param data 输入数据

     * @return 处理后的16字节结果

     */

    public static byte[] processMAB(byte[] data) {

        byte[] result = new byte[16];

        Arrays.fill(result, (byte)0);



        // 计算完整16字节块的数量

        int fullBlocks = data.length / 16;

        int remaining = data.length % 16;



        // 处理完整块

        for (int i = 0; i < fullBlocks; i++) {

            int offset = i * 16;

            for (int j = 0; j < 16; j++) {

                result[j] ^= data[offset + j];

            }

        }



        // 处理剩余不足16字节的部分

        if (remaining > 0) {

            int offset = fullBlocks * 16;

            for (int j = 0; j < remaining; j++) {

                result[j] ^= data[offset + j];

            }

            // 剩余部分补0x00

            for (int j = remaining; j < 16; j++) {

                result[j] ^= 0x00;

            }

        }



        return result;

    }



    /**

     * 功能描述:

     * 将字节数组转换为十六进制字符串

     * @Author: terrfly#东南飞

     * @Date: 2025/9/16 17:11

     */

    public static String hexToAsciiConverter(String hex) {

        StringBuilder ascii = new StringBuilder();



        for (int i = 0; i < hex.length(); i++) {

            char c = hex.charAt(i);

            ascii.append(Integer.toHexString((int) c));

        }

        return ascii.toString();

    }



    /**

     * 功能描述:

     生成pidSCT

     * @param pid 服务商PID(11位)

     * @param payOrderId 订单编号(40位)

     * @param amount 订单金额(以分为单位)

     * @param key 服务商秘钥

     * @return 生成pidSCT

     * @Author: terrfly#东南飞

     * @Date: 2025/9/17 11:22

     */

    public static String calculatePidSct(String pid, String payOrderId, Long amount, String key){

        return JeepayPIDKit.calculatePidSct(JeepayPIDKit.formatPidAndOrder(pid, payOrderId, amount), key);

    }



    /**

     * 功能描述:

     生成pidSCT

     * @param formatPidAndOrderStr 格式化pid和订单信息

     * @param key 服务商秘钥

     * @return 生成pidSCT

     * @Author: terrfly#东南飞

     * @Date: 2025/9/17 11:22

     */

    public static String calculatePidSct(String formatPidAndOrderStr, String key){



        byte[] result_block_hexdecimal_array = JeepayPIDKit.processMAB(formatPidAndOrderStr.getBytes());

        String result_block_hexdecimal_str = JeepayPIDKit.bytesToHexString(result_block_hexdecimal_array);

        String resultENC = SmUtil.sm4(HexUtil.decodeHex(key)).encryptHex(result_block_hexdecimal_str.substring(0, 16)).substring(0, 32).toUpperCase();

        String resultXOR = HexUtil.encodeHexStr(bytesXOR(HexUtil.decodeHex(resultENC), HexUtil.decodeHex(hexToAsciiConverter(result_block_hexdecimal_str.substring(16))))).toUpperCase();

        String result3 = SmUtil.sm4(HexUtil.decodeHex(key)).encryptHex(HexUtil.decodeHex((resultXOR))).substring(0, 32).toUpperCase();

        return result3.substring(0, 8);

    }









    // 将字节数组转换为十六进制字符串的函数

    public static String bcd2Str(byte[] b) {

        char[] HEX_DIGITS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

        StringBuilder sb = new StringBuilder(b.length * 2);



        for (int i = 0; i < b.length; ++i) {

            sb.append(HEX_DIGITS[(b[i] & 240) >>> 4]);

            sb.append(HEX_DIGITS[b[i] & 15]);

        }



        return sb.toString();

    }



    //将一个字符串转换为BCD码(二进码十进数)的字节数组。

    //BCD码是一种用二进制编码十进制数字的编码方式,通常用4位二进制数表示一个十进制数字(0-9)

    public static byte[] str2Bcd(String asc) {

        int len = asc.length();

        int mod = len % 2;

        if (mod != 0) {

            asc = "0" + asc;

            len = asc.length();

        }



        byte[] abt = new byte[len];

        if (len >= 2) {

            len /= 2;

        }



        byte[] bbt = new byte[len];

        abt = asc.getBytes();



        for (int p = 0; p < asc.length() / 2; ++p) {

            int j;

            if (abt[2 * p] >= 97 && abt[2 * p] <= 122) {

                j = abt[2 * p] - 97 + 10;

            } else if (abt[2 * p] >= 65 && abt[2 * p] <= 90) {

                j = abt[2 * p] - 65 + 10;

            } else {

                j = abt[2 * p] - 48;

            }



            int k;

            if (abt[2 * p + 1] >= 97 && abt[2 * p + 1] <= 122) {

                k = abt[2 * p + 1] - 97 + 10;

            } else if (abt[2 * p + 1] >= 65 && abt[2 * p + 1] <= 90) {

                k = abt[2 * p + 1] - 65 + 10;

            } else {

                k = abt[2 * p + 1] - 48;

            }



            int a = (j << 4) + k;

            byte b = (byte) a;

            bbt[p] = b;

        }

        return bbt;

    }





    /**

     * 加密

     *

     * @param src 数据源

     * @param key 密钥,长度必须是8的倍数

     * @return 返回加密后的数据

     */

    public static byte[] encrypt(byte[] src, byte[] key) {

        SecureRandom sr = new SecureRandom();

        try {

            DESKeySpec dks = new DESKeySpec(key);

            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);

            SecretKey securekey = keyFactory.generateSecret(dks);

            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);

            cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);

            return cipher.doFinal(src);

        } catch (Exception e) {

        }

        return null;

    }



    /**

     * 生成密钥

     *

     * @return

     * @throws NoSuchAlgorithmException

     */

    public static byte[] initKey() throws NoSuchAlgorithmException {

        KeyGenerator kg = KeyGenerator.getInstance(DES);

        kg.init(16);

        SecretKey secretKey = kg.generateKey();

        return secretKey.getEncoded();

    }



    /**

     * 解密

     *

     * @param src 数据源

     * @param key 密钥,长度必须是8的倍数

     * @return 返回解密后的原始数据

     */

    public static byte[] decrypt(byte[] src, byte[] key) {

        SecureRandom sr = new SecureRandom();

        try {

            DESKeySpec dks = new DESKeySpec(key);

            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);

            SecretKey securekey = keyFactory.generateSecret(dks);

            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);

            cipher.init(Cipher.DECRYPT_MODE, securekey, sr);

            return cipher.doFinal(src);

        } catch (Exception e) {

        }

        return null;

    }





    /**

     * mac计算

     *

     * @param key   mac秘钥

     * @param Input 待加密数据

     *

     *使用异或和DES加密生成MAC的过程。

     * 函数的大致步骤:

     *

     * 对输入数据进行补位(填充至8的倍数)

     *

     * 将数据分成8字节的块

     *

     * 初始块与后续块依次异或,结果作为下一次异或的输入

     *

     * 将最终的8字节异或结果转换为16字节的十六进制字符串

     *

     * 取前8字节用密钥加密,然后与后8字节异或

     *

     * 对异或结果再用密钥加密,取结果的前8字节转换为十六进制,再取前8字节作为MAC

     *

     * 注意:代码中引用了JeepayPIDKit.encrypt和bytesToHexString,我们假设这些函数已正确实现。

     * @return

     */

    public static byte[] getMac(byte[] key, byte[] Input) {

        int length = Input.length;

        int x = length % 8;

        // 需要补位的长度

        int addLen = 0;

        if (x != 0) {

            addLen = 8 - length % 8;

        }

        int pos = 0;

        // 原始数据补位后的数据

        byte[] data = new byte[length + addLen];

        System.arraycopy(Input, 0, data, 0, length);

        byte[] oper1 = new byte[8];

        System.arraycopy(data, pos, oper1, 0, 8);

        pos += 8;

        // 8字节异或

        for (int i = 1; i < data.length / 8; i++) {

            byte[] oper2 = new byte[8];

            System.arraycopy(data, pos, oper2, 0, 8);

            byte[] t = bytesXOR(oper1, oper2);

            oper1 = t;

            pos += 8;

        }

        // 将异或运算后的最后8个字节(RESULT BLOCK)转换成16个HEXDECIMAL:

        byte[] resultBlock = bytesToHexString(oper1).getBytes();

        // 取前8个字节MAK加密

        byte[] front8 = new byte[8];

        System.arraycopy(resultBlock, 0, front8, 0, 8);

        byte[] behind8 = new byte[8];

        System.arraycopy(resultBlock, 8, behind8, 0, 8);

        byte[] desfront8 = JeepayPIDKit.encrypt(front8, key);

        // 将加密后的结果与后8 个字节异或:

        byte[] resultXOR = bytesXOR(desfront8, behind8);

        // 用异或的结果TEMP BLOCK 再进行一次单倍长密钥算法运算

        byte[] buff = JeepayPIDKit.encrypt(resultXOR, key);

        // 将运算后的结果(ENC BLOCK2)转换成16 个HEXDECIMAL asc

        byte[] retBuf = new byte[8];

        // 取8个长度字节就是mac值

        System.arraycopy(bytesToHexString(buff).getBytes(), 0, retBuf, 0, 8);

        return retBuf;

    }



    /**

     * 单字节异或

     *

     * @param src1

     * @param src2

     * @return

     */

    public static byte byteXOR(byte src1, byte src2) {

        return (byte) ((src1 & 0xFF) ^ (src2 & 0xFF));

    }



    /**

     * 字节数组异或

     *

     * @param src1

     * @param src2

     * @return

     */

    public static byte[] bytesXOR(byte[] src1, byte[] src2) {

        int length = src1.length;

        if (length != src2.length) {

            return null;

        }

        byte[] result = new byte[length];

        for (int i = 0; i < length; i++) {

            result[i] = byteXOR(src1[i], src2[i]);

        }

        return result;

    }



    /**

     * 字节数组转HEXDECIMAL

     *

     * @param bArray

     * @return

     */

    public static final String bytesToHexString(byte[] bArray) {

        StringBuffer sb = new StringBuffer(bArray.length);

        String sTemp;

        for (int i = 0; i < bArray.length; i++) {

            sTemp = Integer.toHexString(0xFF & bArray[i]);

            if (sTemp.length() < 2)

                sb.append(0);

            sb.append(sTemp.toUpperCase());

        }

        return sb.toString();

    }

}

文档更新时间: 2026-02 作者:LK