CMAC(GBT 15852.1)和OMAC(RFC4493/NIST SP800-38B)的java实现和测试

相关标准和文档

代码下载

概述

CMAC, Cipher-based MAC, 即基于AES对称加密算法的MAC,可参考ISO 9797-1标准,其中详细定义了CMAC算法,其对应的国标为《GBT 15852.1》
最早的CMAC是基于DES的CBC模式构造的MAC,即CBC-MAC,可参考ANSI X9.9ANSI X9.19
然后 John Black 和 Phillip Rogaway 于2000年提出的避免CBC-MAC安全缺陷的XCBC模式(Extend Cipher Block Chaining Mode),作为CBC模式的扩展,用来构造MAC,即XCBC-MAC
再后来,Tetsu Iwata 和 Kaoru Kurosawa 基于XCBC-MAC提出了OMAC,即One-Key CBC-MAC,接着又精益求精第提出了OMAC1,前面的OMAC被重新命名为OMAC2,可参考其论文。
现在提到CMAC通常是指OMAC,而OMAC通常是指OMAC1,可参考NIST SP800-38B标准,也可参考RFC4493文档,其中的测试数据里使用了AES128/192/256算法和3DES算法。
基于《GBT 15852.1》的算法1和填充1可以实现《GBT 36322》中的SDF_CalculateMAC接口。

文档说明

  1. ISO 9797: CMAC算法,定义了几种不同MAC算法和填充方式。
  2. GBT 15852.1: 国家标准。2008版本对标ISO 9797-1:1999;2020版本对标ISO 9797-1:2011
  3. X9.9: DES CBC-MAC,其算法对应到“GBT 15852.1”中的“算法1,填充1”。
  4. X9.19: 3DES CBC-MAC,其算法对应到“GBT 15852.1”中的“算法3,填充1”。
  5. SP800-38B: 定义了使用AES和TDEA算法来计算OMAC。

OMAC

  1. OMAC算法特点

    • 对称算法的CBC向量IV是全0的
    • 在初始化时会根据标准中的子密钥算法生成两个子密钥key1和key2
    • 在处理最后一个分组时,如果是完整的分组,就与key1相异或,然后再加密。如果不是完整的分组,就填充比特串100000000*到完整分组,然后与key2相异或,然后再加密。
  2. 实现

  • BC库中的实现了OMAC算法,底层的实现类为org.bouncycastle.crypto.macs.CMac。对应到《GBT 15852.1-2020》的算法5。在使用时可直接通过java的 Mac 进行使用,算法名称是在相应的对称算法后面添加CMAC

CMAC:《GBT 15852.1-2020》

  1. 总结
算法密钥诱导
(可选;2种)
消息填充
(4种)
初始变换
(3种)
最终迭代
(4种)
输出变换
(3种)
截断操作
(2种)
算法1: CBC-MAC1/2/31111
算法2: EMAC1(可能)1/2/3112-key21
算法3: ANSI retail MAC1/2/3113-key21
算法4: MacDES1(可能)1/2/32-K112-key21
算法5: CMAC/OMAC12413-(K1,K2)11
算法6: LMAC1(可能)1/2/312-K211
算法7: TrCBC41112
算法8: CBCR43411

ParametersWithPadding类

ParametersWithPadding类用来设置算法的各种参数。其中定义key1表示需要输入的原始密钥;key2表示在输出变换中使用的密钥,可能是在最开始设置的,也可能是通过密钥诱导生成的。

public class ParametersWithPadding implements CipherParameters {
    int typeAlg; //算法类型:1~6
    int typePad; //填充类型:1、2、3、4
    int length; //仅 typePad=3 时有效,表示输入数据的总长度
    int transformInit; //初始变换:1、2、3
    int transformOut; //输出变换:1、2、3
    int lastIteration; //最终迭代:1、2、3、4
    int truncate; //截断操作:1、2
    int keyInduce = 1; //密钥诱导方式:0、1;仅用于算法2中。
    byte[] key1;
    byte[] key2;
    byte[] iv;

    public ParametersWithPadding(
            byte[] key1, byte[] key2,
            int typeAlg, int typePad) {
        this(key1, key2, typeAlg, typePad, 0);
    }

    public ParametersWithPadding(
            byte[] key1, byte[] key2, byte[] iv,
            int typeAlg, int typePad) {
        this(key1, key2, iv, typeAlg, typePad, 0);
    }

    public ParametersWithPadding(
            byte[] key1, byte[] key2,
            int typeAlg, int typePad, int length) {
        this(key1, key2, null, typeAlg, typePad, length);
    }

    public ParametersWithPadding(
            byte[] key1, byte[] key2, byte[] iv,
            int typeAlg, int typePad, int length) {
        this.key1 = key1;
        this.key2 = key2;
        this.iv = iv;
        this.typeAlg = typeAlg;
        this.typePad = typePad;
        this.length = length;

        transformInit = 1;
        transformOut = 1;
        lastIteration = 1;
        truncate = 1;

        switch (typeAlg) {
            case 2:
                transformOut = 2;
                break;
            case 3:
                transformOut = 3;
                //算法3的两个密钥应独立选取;当key2为null时为避免错误,这里将其设置为key1,此时算法3和算法1一致
                if (this.key2 == null)
                    this.key2 = key1;
                break;
            case 4:
                transformInit = 2;
                transformOut = 2;
                break;
            case 5:
                lastIteration = 3;
                break;
            case 6:
                lastIteration = 2;
                break;
            case 7:
                truncate = 2;
                break;
            case 8:
                transformInit = 3;
                lastIteration = 4;
                break;
        }
    }

    /**
     * 密钥诱导方式,仅用于算法2中。
     * <p>
     * 默认为1。为了兼容2008标准或验证2008标准的附录测试,需要手动设置为0.
     *
     * @param keyInduce 0表示使用2008标准中的密钥诱导;在2008标准中定义,并在附录测试中用到;<br>
     *                  1表示使用2020标准中的密钥诱导1。在2020标准中定义,但附录测试中提供了2个密钥,未用到。
     */
    public void setKeyInduce(int keyInduce) {
        this.keyInduce = keyInduce;
    }

    public CipherParameters getParameters() {
        if (iv == null)
            return new KeyParameter(key1);
        else
            return new ParametersWithIV(new KeyParameter(key1), iv);
    }

YCMac类

YCMac类中实现了标准中的8种算法。其中定义的K1,表示用在初始变换2或最终迭代3中的密钥;K2表示用在最终迭代2/3中的密钥。
2020标准中定义的MAC算法的8步操作在YCMac中的处理方法如下:

  • 第1步:密钥诱导: keyInduce
  • 第2步:消息填充: paddingTransform
  • 第3步:数据分割: 在 update 中自动处理
  • 第4步:初始变换: initTransform
  • 第5步:迭代应用分组密码: 在 update 中自动处理
  • 第6步:最终迭代: lastIteration
  • 第7步:输出变换: outTransform
  • 第8步:截断操作: truncate
public class YCMac implements Mac {
    private final byte[] mac; //mac值
    private final int macSize; //所需的Mac大小

    private final byte[] buf; //内部缓冲区
    private int bufOff; //缓冲区当前数据长度

    private byte[] K1; //用在初始变换2或最终迭代3中的密钥
    private byte[] K2; //用在最终迭代2/3中的密钥

    private final BlockCipher cipher; //底层对称算法对象
    ParametersWithPadding parameters; //算法参数

    public YCMac(BlockCipher cipher) {
        this(cipher, cipher.getBlockSize() * 8);
    }

    public YCMac(BlockCipher cipher, int macSizeInBits) {
        if ((macSizeInBits % 8) != 0) {
            throw new IllegalArgumentException("MAC size must be multiple of 8");
        }

        if (macSizeInBits > (cipher.getBlockSize() * 8)) {
            throw new IllegalArgumentException("MAC size must be less or equal to " + (cipher.getBlockSize() * 8));
        }

        this.cipher = new CBCBlockCipher(cipher);
        this.macSize = macSizeInBits / 8;

        mac = new byte[cipher.getBlockSize()];
        buf = new byte[cipher.getBlockSize()];
        bufOff = 0;
    }

    public String getAlgorithmName() {
        return cipher.getAlgorithmName();
    }

    public void init(CipherParameters params) {
        validate(params);

        cipher.init(true, parameters.getParameters());
        keyInduce(); //1.密钥诱导
        reset();

        //填充方式3:处理在开头添加的填充块
        if (parameters.typePad == 3) {
            byte[] temp = new byte[cipher.getBlockSize()];
            byte[] bLen = Pack.intToBigEndian(parameters.length * 8);
            System.arraycopy(bLen, 0, temp, temp.length - bLen.length, bLen.length);
            update(temp, 0, temp.length);
        }
    }

    void validate(CipherParameters params) {
        if (params instanceof ParametersWithPadding)
            parameters = (ParametersWithPadding) params;
        else
            throw new IllegalArgumentException("CMac mode only permits parameters type of ParametersWithPadding.");
    }

    public int getMacSize() {
        return macSize;
    }

    public void update(byte in) {
        if (bufOff == buf.length) {
            cipher.processBlock(buf, 0, mac, 0);
            bufOff = 0;
        }

        buf[bufOff++] = in;
    }

    public void update(byte[] in, int inOff, int len) {
        if (len < 0) {
            throw new IllegalArgumentException("Can't have a negative input length!");
        }

        int blockSize = cipher.getBlockSize();
        int gapLen = blockSize - bufOff;

        if (len > gapLen) {
            System.arraycopy(in, inOff, buf, bufOff, gapLen);

            initTransform(parameters.transformInit); //4.初始变换

            bufOff = 0;
            len -= gapLen;
            inOff += gapLen;

            //5.迭代应用分组密码
            while (len > blockSize) {
                cipher.processBlock(in, inOff, mac, 0);
                len -= blockSize;
                inOff += blockSize;
            }
        }

        System.arraycopy(in, inOff, buf, bufOff, len);
        bufOff += len;
    }

    public int doFinal(byte[] out, int outOff) {
        int msgLen = bufOff;

        paddingTransform(parameters.typePad); //2.消息填充。填充最后一个分组
        lastIteration(parameters.lastIteration, msgLen); //6.最终迭代
        outTransform(parameters.transformOut); //7.输出变换
        truncate(parameters.truncate, out, outOff, msgLen); //8.截断操作

        reset();
        return macSize;
    }

    /**
     * 密钥诱导。
     */
    void keyInduce() {
        KeyInduce keyInduce = new KeyInduce(cipher);

        if (parameters.typeAlg == 2) {
            //如果没有提供key2,则需要生成。
            //为了兼容2008,通过keyInduce参数决定密钥生成方式。0表示使用2008标准中的密钥诱导;1表示使用2020标准中的密钥诱导1。
            if (parameters.key2 == null) {
                if(parameters.keyInduce==0)
                    parameters.key2 = keyInduce.induce0(parameters.key1);
                else {
                    keyInduce.induce1(parameters.key1.length);
                    parameters.key1 = keyInduce.K1;
                    parameters.key2 = keyInduce.K2;
                }
            }
        } else if (parameters.typeAlg == 4) {
            //需要生成初始变换2的密钥K1:如果提供了key2,则用2008标准中的密钥诱导生成K1;
            //如果没有提供key2,则需用密钥诱导1生成K1,以及要在输出变换2中使用的密钥key2
            if (parameters.key2 != null)
                K1 = keyInduce.induce0(parameters.key2);
            else {
                keyInduce.induce1(parameters.key1.length);
                parameters.key2 = keyInduce.K1;
                K1 = keyInduce.K2;
            }
        } else if (parameters.typeAlg == 5) {
            //最终迭代3中的两个密钥用密钥诱导2生成;分别放在K1和K2中
            keyInduce.induce2();
            K1 = keyInduce.K1;
            K2 = keyInduce.K2;
        } else if (parameters.typeAlg == 6) {
            //当只提供一个密钥时,需用密钥诱导1生成所需的两个密钥。用于最终迭代2的密钥放在K2中
            if (parameters.key2 == null) {
                keyInduce.induce1(parameters.key1.length);
                parameters.key1 = keyInduce.K1;
                K2 = keyInduce.K2; //用于最终迭代2,故不设置key2
                cipher.init(true, parameters.getParameters()); //使用密钥诱导生成了key1,需要重新初始化cipher
            } else
                K2 = parameters.key2;
        }
    }

    /**
     * 填充。
     *
     * @param type 填充方式
     */
    void paddingTransform(int type) {
        int blockSize = cipher.getBlockSize();
        if (type == 1 || type == 3) {
            if (bufOff != blockSize)
                new ZeroBytePadding().addPadding(buf, bufOff);
        } else if (type == 2) {
            if (bufOff == blockSize) {
                cipher.processBlock(buf, 0, mac, 0);
                bufOff = 0;
            }
            new ISO7816d4Padding().addPadding(buf, bufOff);
        } else if (type == 4) {
            if (bufOff != blockSize)
                new ISO7816d4Padding().addPadding(buf, bufOff);
        }
    }

    /**
     * 初始变换。
     *
     * @param type 初始变换方式
     */
    void initTransform(int type) {
        cipher.processBlock(buf, 0, mac, 0);

        if (type == 2) {
            //初始变换2:使用子密钥加密后再使用原密钥进行后续加密
            reset();
            cipher.init(true, new KeyParameter(K1));
            cipher.processBlock(mac, 0, mac, 0);
            cipher.init(true, new ParametersWithIV(parameters.getParameters(), mac));
        } else if (type == 3) {
            cipher.reset();
            byte[] zeroes = new byte[cipher.getBlockSize()];
            cipher.processBlock(zeroes, 0, mac, 0);
            xor(buf, mac);
            cipher.reset();
            cipher.processBlock(buf, 0, mac, 0);
        }
    }

    /**
     * 输出变换。
     *
     * @param type 输出变换方式
     */
    void outTransform(int type) {
        if (type == 2) {
            //使用子密钥再加密一次
            reset();
            //这里需要使用全0的IV进行初始化,因为在算法4的“初始化变换”中使用了非0的IV,如果只是reset,则IV不是全0,会导致结果有误
            cipher.init(true, new ParametersWithIV(new KeyParameter(parameters.key2), new byte[cipher.getBlockSize()]));
            cipher.processBlock(mac, 0, mac, 0);
        } else if (type == 3) {
            //使用子密钥解密后再使用原密钥加密
            cipher.init(false, new KeyParameter(parameters.key2));
            cipher.processBlock(mac, 0, mac, 0);

            reset();
            cipher.init(true, parameters.getParameters());
            cipher.processBlock(mac, 0, mac, 0);
        }
    }


    /**
     * 最终迭代。
     *
     * @param type   迭代类型
     * @param msgLen 最后的消息长度
     */
    void lastIteration(int type, int msgLen) {
        if (type == 1)
            cipher.processBlock(buf, 0, mac, 0);
        if (type == 2) {
            xor(mac, buf);
            reset();
            cipher.init(true, new ParametersWithIV(new KeyParameter(K2), new byte[cipher.getBlockSize()]));
            cipher.processBlock(mac, 0, mac, 0);
        } else if (type == 3) {
            if (msgLen % cipher.getBlockSize() == 0)
                xor(buf, K1);
            else
                xor(buf, K2);
            cipher.processBlock(buf, 0, mac, 0);
        } else if (type == 4) {
            byte[] temp = new byte[cipher.getBlockSize()];
            xor(mac, buf);
            if (msgLen % cipher.getBlockSize() == 0)
                shiftRight(mac, temp);
            else
                shiftLeft(mac, temp);
            System.arraycopy(temp, 0, mac, 0, temp.length);

            reset();
            cipher.init(true, parameters.getParameters());
            cipher.processBlock(mac, 0, mac, 0);
        }
    }

    /**
     * 截断操作。
     *
     * @param type   截断类型
     * @param out    输出缓冲
     * @param outOff 输出偏移
     * @param msgLen 最后的消息长度
     */
    void truncate(int type, byte[] out, int outOff, int msgLen) {
        if (type == 2 && msgLen % cipher.getBlockSize() != 0)
            System.arraycopy(mac, mac.length - macSize, out, outOff, macSize);
        else
            System.arraycopy(mac, 0, out, outOff, macSize);
    }

    public void reset() {
        //clean the buffer.
        Arrays.fill(buf, (byte) 0);
        bufOff = 0;

        //reset the underlying cipher.
        cipher.reset();
    }


    static void xor(byte[] a, byte[] b) {
        for (int i = 0; i < a.length; i++)
            a[i] ^= b[i];
    }

    /**
     * 循环左移。
     */
    private static int shiftLeft(byte[] input, byte[] output) {
        int i = input.length;
        int bit = 0;
        while (--i >= 0) {
            int b = input[i] & 0xff;
            output[i] = (byte) ((b << 1) | bit);
            bit = (b >>> 7) & 1;
        }
        return bit;
    }

    /**
     * 循环右移。
     */
    private static int shiftRight(byte[] input, byte[] output) {
        int i = 0;
        int bit = 0;
        while (i < input.length) {
            int b = input[i] & 0xff;
            output[i] = (byte) ((b >>> 1) | bit);
            bit = (b << 7) & 0x80;
            i++;
        }

        output[0] |= bit;

        return bit;
    }

    /**
     * 密钥诱导。
     */
    static class KeyInduce {
        final BlockCipher cipher;
        byte[] K1;
        byte[] K2;

        KeyInduce(BlockCipher blockCipher) {
            this.cipher = blockCipher;
        }

        /**
         * 密钥诱导1.
         *
         * @param keyLength 分组密钥密钥长度k
         */
        void induce1(int keyLength) {
            int t = keyLength / cipher.getBlockSize();
            K1 = genKey(0, t);
            cipher.reset();
            K2 = genKey(t, 2 * t);
        }

        /**
         * 密钥诱导2.
         */
        void induce2() {
            byte[] zeroes = new byte[cipher.getBlockSize()];
            byte[] L = new byte[zeroes.length];
            cipher.processBlock(zeroes, 0, L, 0);
            K1 = multx(L);
            K2 = multx(K1);
        }

        /**
         * 《GBT 15852.1-2008》密钥诱导。
         * <p>
         * 用在算法2和算法4中。
         * <p>
         * 过程:对K从第1个4比特组开始,每隔4比特交替取补和不变
         *
         * @param key 密钥
         * @return 子密钥
         */
        public byte[] induce0(byte[] key) {
            byte[] result = key.clone();
            for (int i = 0; i < result.length; i++)
                result[i] = (byte) ((~result[i] & 0xF0) ^ (result[i] & 0x0F));
            return result;
        }

        byte[] genKey(int start, int end) {
            int blockSize = cipher.getBlockSize();
            int len = end - start;

            byte[] S = new byte[len * blockSize];
            for (int i = start; i < end; i++) {
                byte[] ct = new byte[blockSize];
                byte[] temp = Pack.intToBigEndian(i + 1);
                System.arraycopy(temp, 0, ct, ct.length - temp.length, temp.length);
                cipher.processBlock(ct, 0, S, (i - start) * blockSize);
            }

            return Arrays.copyOfRange(S, (len - 1) * blockSize, S.length);
        }

        /**
         * multx(CMac.doubleLu).
         *
         * @param in 比特串T
         * @see CMac
         */
        byte[] multx(byte[] in) {
            byte[] ret = new byte[in.length];
            int carry = shiftLeft(in, ret);

            byte[] poly = lookupPoly(cipher.getBlockSize());

            int mask = (-carry) & 0xff;
            ret[in.length - 3] ^= poly[1] & mask;
            ret[in.length - 2] ^= poly[2] & mask;
            ret[in.length - 1] ^= poly[3] & mask;

            return ret;
        }

        /**
         * lookup.
         *
         * @see CMac
         */
        static byte[] lookupPoly(int blockSizeLength) {
            int xor = 0;
            switch (blockSizeLength * 8) {
                case 64:
                    xor = 0x1B;
                    break;
                case 128:
                    xor = 0x87;
                    break;
            }

            return Pack.intToBigEndian(xor);
        }
    }
}

密钥诱导

在2008标准中定义了一个密钥诱导,在2020标准中定义了另外两个不同的密钥诱导,即密钥诱导1和密钥诱导2。密钥诱导在YCMac中的keyInduce方法中按照不同的算法进行处理。

但对于算法2有点特殊,为了兼容2008标准及其附录测试,在ParametersWithPadding中定义了keyInduce参数表示密钥诱导方式(仅用于算法2中)。

  • 0表示使用2008标准中的密钥诱导;在2008标准中定义,并在附录测试中用到;
  • 1表示使用2020标准中的密钥诱导1。在2020标准中定义,但附录测试中提供了2个密钥,未用到。
    默认为1,以符合2020标准。所以为了兼容2008标准或验证2008标准的附录测试,需要手动设置为0.

算法1/3/7/8不需要密钥诱导,其他算法的密钥诱导参看下面的代码:

void keyInduce() {
        KeyInduce keyInduce = new KeyInduce(cipher);

        if (parameters.typeAlg == 2) {
            //如果没有提供key2,则需要生成。
            //为了兼容2008,通过keyInduce参数决定密钥生成方式。0表示使用2008标准中的密钥诱导;1表示使用2020标准中的密钥诱导1。
            if (parameters.key2 == null) {
                if(parameters.keyInduce==0)
                    parameters.key2 = keyInduce.induce0(parameters.key1);
                else {
                    keyInduce.induce1(parameters.key1.length);
                    parameters.key1 = keyInduce.K1;
                    parameters.key2 = keyInduce.K2;
                }
            }
        } else if (parameters.typeAlg == 4) {
            //需要生成初始变换2的密钥K1:如果提供了key2,则用2008标准中的密钥诱导生成K1;
            //如果没有提供key2,则需用密钥诱导1生成K1,以及要在输出变换2中使用的密钥key2
            if (parameters.key2 != null)
                K1 = keyInduce.induce0(parameters.key2);
            else {
                keyInduce.induce1(parameters.key1.length);
                parameters.key2 = keyInduce.K1;
                K1 = keyInduce.K2;
            }
        } else if (parameters.typeAlg == 5) {
            //最终迭代3中的两个密钥用密钥诱导2生成;分别放在K1和K2中
            keyInduce.induce2();
            K1 = keyInduce.K1;
            K2 = keyInduce.K2;
        } else if (parameters.typeAlg == 6) {
            //当只提供一个密钥时,需用密钥诱导1生成所需的两个密钥。用于最终迭代2的密钥放在K2中
            if (parameters.key2 == null) {
                keyInduce.induce1(parameters.key1.length);
                parameters.key1 = keyInduce.K1;
                K2 = keyInduce.K2; //用于最终迭代2,故不设置key2
                cipher.init(true, parameters.getParameters()); //使用密钥诱导生成了key1,需要重新初始化cipher
            } else
                K2 = parameters.key2;
        }
    }

测试

在YMacTest的 test_GBT15852_2020() 里对《GBT 15852.1-2020 信息技术 安全技术 消息鉴别码 第1部分:采用分组密码的机制》-附录B 进行了测试和验证。

CMAC测试: 《GBT 15852.1-2020 信息技术 安全技术 消息鉴别码 第1部分:采用分组密码的机制》-附录B

消息1:This is the test message for mac
Hex: 54686973206973207468652074657374206d65737361676520666f72206d6163
填充方式1:
D1 | 54 68 69 73 20 69 73 20 74 68 65 20 74 65 73 74 
D2 | 20 6D 65 73 73 61 67 65 20 66 6F 72 20 6D 61 63 

填充方式2:
D1 | 54 68 69 73 20 69 73 20 74 68 65 20 74 65 73 74 
D2 | 20 6D 65 73 73 61 67 65 20 66 6F 72 20 6D 61 63 
D3 | 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

填充方式3:
D1 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 
D2 | 54 68 69 73 20 69 73 20 74 68 65 20 74 65 73 74 
D3 | 20 6D 65 73 73 61 67 65 20 66 6F 72 20 6D 61 63 


填充方式4:
D1 | 54 68 69 73 20 69 73 20 74 68 65 20 74 65 73 74 
D2 | 20 6D 65 73 73 61 67 65 20 66 6F 72 20 6D 61 63 


消息2:This is the test message 
hex: 54686973206973207468652074657374206d65737361676520
填充方式1:
D1 | 54 68 69 73 20 69 73 20 74 68 65 20 74 65 73 74 
D2 | 20 6D 65 73 73 61 67 65 20 00 00 00 00 00 00 00 

填充方式2:
D1 | 54 68 69 73 20 69 73 20 74 68 65 20 74 65 73 74 
D2 | 20 6D 65 73 73 61 67 65 20 80 00 00 00 00 00 00 

填充方式3:
D1 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C8 
D2 | 54 68 69 73 20 69 73 20 74 68 65 20 74 65 73 74 
D3 | 20 6D 65 73 73 61 67 65 20 00 00 00 00 00 00 00 


填充方式4:
D1 | 54 68 69 73 20 69 73 20 74 68 65 20 74 65 73 74 
D2 | 20 6D 65 73 73 61 67 65 20 80 00 00 00 00 00 00 



mac算法测试:
--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法1: 
填充方法1: 
MAC= 16 E0 29 04 EF B7 65 B7 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法1: 
填充方法2: 
MAC= 4B 65 53 AF 3C 4E 27 44 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法1: 
填充方法3: 
MAC= 71 AF 7E 45 53 40 4C BC 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法1: 
填充方法1: 
MAC= BA 89 E4 5F E8 AB F2 42 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法1: 
填充方法2: 
MAC= 42 1A D1 69 0A A1 52 E2 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法1: 
填充方法3: 
MAC= 6A 4A 86 F5 B5 E4 68 DA 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法2: 
填充方法1: 
MAC= 1E 9A 71 D3 BC 92 DF A7 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法2: 
填充方法2: 
MAC= E4 23 E3 55 99 AF D9 48 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法2: 
填充方法3: 
MAC= 40 03 BA 1B 6A DC 53 A8 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法2: 
填充方法1: 
MAC= 4E C3 C7 FA CF AA C6 07 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法2: 
填充方法2: 
MAC= F0 26 25 CE AD 00 8D 4E 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法2: 
填充方法3: 
MAC= FF D5 F1 F2 E5 ED A5 CB 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法3: 
填充方法1: 
MAC= 27 63 21 1B 2B CA F7 19 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法3: 
填充方法2: 
MAC= 51 E9 92 8C 22 38 33 0C 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法3: 
填充方法3: 
MAC= 7C D4 8C 42 42 E4 55 75 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法3: 
填充方法1: 
MAC= E3 2D 99 A6 89 C0 52 59 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法3: 
填充方法2: 
MAC= 19 72 47 22 9C E9 D7 B6 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法3: 
填充方法3: 
MAC= 3C 43 0F 1E A4 3B 54 0C 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法4: 
填充方法1: 
MAC= DD 10 52 A7 AF E8 99 9B 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法4: 
填充方法2: 
MAC= 7E 1A 9A 5E 0E F0 94 7F 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法4: 
填充方法3: 
MAC= 28 A7 0D 6B CC F7 44 22 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法4: 
填充方法1: 
MAC= AA 9D B3 D9 65 1F 86 2B 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法4: 
填充方法2: 
MAC= 94 94 76 D3 5F 17 26 1E 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法4: 
填充方法3: 
MAC= C9 D3 4E 16 C4 9A B6 43 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法5: 
填充方法4: 
MAC= 69 2C 43 71 00 F3 B5 EE 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法5: 
填充方法4: 
MAC= 47 38 A6 C7 60 B2 80 FC 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法6: 
填充方法1: 
MAC= B3 8A 96 19 5B AA 61 FC 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法6: 
填充方法2: 
MAC= A0 C4 65 EE 58 96 97 2F 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法6: 
填充方法3: 
MAC= 43 05 0D 51 C6 56 AE 60 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法6: 
填充方法1: 
MAC= 8C F6 E6 43 14 FE F4 17 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法6: 
填充方法2: 
MAC= 60 DD 95 5E D0 CA 3D 7A 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法6: 
填充方法3: 
MAC= 61 E0 00 49 E2 69 62 A3 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法7: 
填充方法4: 
MAC= 16 E0 29 04 EF B7 65 B7 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法7: 
填充方法4: 
MAC= 84 6F A2 A5 D8 34 45 A9 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520666f72206d6163
MAC算法8: 
填充方法4: 
MAC= E4 0E D7 9C 31 49 A1 C9 

--------------------------------------------------
消息: 54686973206973207468652074657374206d65737361676520
MAC算法8: 
填充方法4: 
MAC= A9 9D 13 01 3E 89 2E E2 

以下是2008标准的相关内容,现可忽略。

CMAC:《GBT 15852.1-2008》

  1. 总结
MAC算法(6种)数据填充(3种)初始变换(2种)输出变换(3种)输入密钥1输入密钥2生成诱导密钥
算法1任选11××
算法2任选12×
算法3任选13×
算法4任选22
算法5任选
算法6任选

其中,算法5是两个算法1的MAC相异或;算法6是两个算法4的MAC相异或 。

  1. 实现
  • CMac14实现了算法 1-4 ,CMac56实现了算法5~6。
  • 算法参数放在ParametersWithPadding类中,包括算法类型,填充方式,密钥,向量,初始变换,输出变换。默认CMAC的向量为全0,添加了IV以支持其他向量值。
  • YCMac类中还提供了一个基于算法1,填充1的支持update模式的CMAC方法。
    在《GBT 36322》中有一个SDF_CalculateMAC接口,支持IV的输入输出,支持多包处理,可以用此方法来实现。
    其他的MAC算法实现不了,因为它们的输出变换用了另外的密钥来进行处理,所以不支持update模式。
  1. 代码示例
/**
 * 《GBT 15852.1-2008 信息技术 安全技术 消息鉴别码 第1部分:采用分组密码的机制》算法1~4。
 *
 * @author YaoYuan
 * @since 2022/11/2
 */
public class CMac14 implements Mac {
    private byte[] mac; //mac值

    private byte[] buf; //内部缓冲区
    private int bufOff; //缓冲区当前数据长度
    private BlockCipher cipher; //底层对称算法对象

    private int macSize; //所需的Mac大小

    private byte[] subKey; //子密钥
    ParametersWithPadding parameters; //算法参数
    private boolean needTransformInit = false; //是否需要初始变换

    public CMac14(BlockCipher cipher) {
        this(cipher, cipher.getBlockSize() * 8);
    }

    public CMac14(BlockCipher cipher, int macSizeInBits) {
        if ((macSizeInBits % 8) != 0) {
            throw new IllegalArgumentException("MAC size must be multiple of 8");
        }

        if (macSizeInBits > (cipher.getBlockSize() * 8)) {
            throw new IllegalArgumentException("MAC size must be less or equal to "+ (cipher.getBlockSize() * 8));
        }

        this.cipher = new CBCBlockCipher(cipher);
        this.macSize = macSizeInBits / 8;

        mac = new byte[cipher.getBlockSize()];
        buf = new byte[cipher.getBlockSize()];
        bufOff = 0;
    }

    public String getAlgorithmName() {
        return cipher.getAlgorithmName();
    }

    public void init(CipherParameters params) {
        validate(params);

        cipher.init(true, parameters.getParameters());

        //设置子密钥
        if (parameters.typeAlg == 2)
            subKey = YCMac.genKey(parameters.key1);
        else if (parameters.typeAlg == 3)
            subKey = parameters.key2.clone();
        else if (parameters.typeAlg == 4)
            subKey = YCMac.genKey(parameters.key2);

        //只有在初始变换2中才需要额外的初始变换操作
        if (parameters.transformInit == 2)
            needTransformInit = true;

        reset();

        //填充方式3:处理在开头添加的填充块
        if (parameters.typePad == 3) {
            byte[] temp = new byte[cipher.getBlockSize()];
            byte[] bLen = Pack.intToBigEndian(parameters.length * 8);
            System.arraycopy(bLen, 0, temp, temp.length - bLen.length, bLen.length);
            update(temp, 0, temp.length);
        }
    }

    void validate(CipherParameters params) {
        if (params instanceof ParametersWithPadding)
            parameters = (ParametersWithPadding) params;
        else
            throw new IllegalArgumentException("CMac mode only permits parameters type of ParametersWithPadding.");
    }

    public int getMacSize() {
        return macSize;
    }

    public void update(byte in) {
        if (bufOff == buf.length) {
            cipher.processBlock(buf, 0, mac, 0);
            bufOff = 0;
        }

        buf[bufOff++] = in;
    }

    public void update(byte[] in, int inOff, int len) {
        if (len < 0) {
            throw new IllegalArgumentException("Can't have a negative input length!");
        }

        int blockSize = cipher.getBlockSize();
        int gapLen = blockSize - bufOff;

        if (len > gapLen) {
            System.arraycopy(in, inOff, buf, bufOff, gapLen);

            cipher.processBlock(buf, 0, mac, 0);
            //初始变换
            if (needTransformInit) {
                needTransformInit = false;
                initTransform(parameters.transformInit);
                //重设算法4的密钥
                if (parameters.typeAlg == 4)
                    subKey = parameters.key2.clone();
            }

            bufOff = 0;
            len -= gapLen;
            inOff += gapLen;

            while (len > blockSize) {
                cipher.processBlock(in, inOff, mac, 0);
                len -= blockSize;
                inOff += blockSize;
            }
        }

        System.arraycopy(in, inOff, buf, bufOff, len);
        bufOff += len;
    }

    public int doFinal(byte[] out, int outOff) {
        //最后一个分组的填充
        paddingTransform(parameters.typePad);
        cipher.processBlock(buf, 0, mac, 0);
        //输出变换
        outTransform(parameters.transformOut);

        System.arraycopy(mac, 0, out, outOff, macSize);
        reset();
        return macSize;
    }

    /**
     * 填充。
     *
     * @param type 填充方式
     */
    void paddingTransform(int type) {
        int blockSize = cipher.getBlockSize();
        if (type == 1 || type == 3) {
            if (bufOff != blockSize)
                new ZeroBytePadding().addPadding(buf, bufOff);
        } else {
            if (bufOff == blockSize) {
                cipher.processBlock(buf, 0, mac, 0);
                bufOff = 0;
            }
            new ISO7816d4Padding().addPadding(buf, bufOff);
        }
    }

    /**
     * 初始变换。
     *
     * @param type 变换方式
     */
    void initTransform(int type) {
        //只有初始变换2才需要处理:使用子密钥加密后再使用原密钥进行后续加密
        if (type == 2) {
            reset();
            cipher.init(true, new KeyParameter(subKey));
            cipher.processBlock(mac, 0, mac, 0);
            cipher.init(true, new ParametersWithIV(parameters.getParameters(), mac));
        }
    }

    /**
     * 输出变换。
     *
     * @param type 变换方式
     */
    void outTransform(int type) {
        if (type == 2) {
            //使用子密钥再加密一次
            reset();
            //这里需要使用全0的IV进行初始化,因为在算法4的“初始化变换”中使用了非0的IV,如果只是reset,则IV不是全0,会导致结果有误
            cipher.init(true, new ParametersWithIV(new KeyParameter(subKey), new byte[cipher.getBlockSize()]));
            cipher.processBlock(mac, 0, mac, 0);
        } else if (type == 3) {
            //先解密再加密
            cipher.init(false, new KeyParameter(subKey));
            cipher.processBlock(mac, 0, mac, 0);

            reset();
            cipher.init(true, parameters.getParameters());
            cipher.processBlock(mac, 0, mac, 0);
        }
    }

    public void reset() {
        //clean the buffer.
        Arrays.fill(buf, (byte) 0);
        bufOff = 0;

        //reset the underlying cipher.
        cipher.reset();
    }
}

测试

在例子中测试了:

  • 《GBT 15852.1》附录A 测试和验证
  • SP800-38B测试和验证:其中的TDEA中有两个Mac值验证失败,应该是文档本身的错误。
  • CMac-update-iv的测试和验证

IDEA中的测试输出:

  1. GBT 15852.1:
CMAC测试: 《GBT 15852.1-2008 信息技术 安全技术 消息鉴别码 第1部分:采用分组密码的机制》-附录A

消息1:Now is the time for all 
Hex: 4e6f77206973207468652074696d6520666f7220616c6c20
填充方式1:
D1 | 4E 6F 77 20 69 73 20 74 
D2 | 68 65 20 74 69 6D 65 20 
D3 | 66 6F 72 20 61 6C 6C 20 

填充方式2:
D1 | 4E 6F 77 20 69 73 20 74 
D2 | 68 65 20 74 69 6D 65 20 
D3 | 66 6F 72 20 61 6C 6C 20 
D4 | 80 00 00 00 00 00 00 00 

填充方式3:
D1 | 00 00 00 00 00 00 00 C0 
D2 | 4E 6F 77 20 69 73 20 74 
D3 | 68 65 20 74 69 6D 65 20 
D4 | 66 6F 72 20 61 6C 6C 20 


消息2:Now is the time for it
hex: 4e6f77206973207468652074696d6520666f72206974
填充方式1:
D1 | 4E 6F 77 20 69 73 20 74 
D2 | 68 65 20 74 69 6D 65 20 
D3 | 66 6F 72 20 69 74 00 00 

填充方式2:
D1 | 4E 6F 77 20 69 73 20 74 
D2 | 68 65 20 74 69 6D 65 20 
D3 | 66 6F 72 20 69 74 80 00 

填充方式3:
D1 | 00 00 00 00 00 00 00 B0 
D2 | 4E 6F 77 20 69 73 20 74 
D3 | 68 65 20 74 69 6D 65 20 
D4 | 66 6F 72 20 69 74 00 00 


mac算法测试:
--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法1: 
填充方法1: 
MAC= 70 A3 06 40 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法1: 
填充方法2: 
MAC= 10 E1 F0 F1 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法1: 
填充方法3: 
MAC= 2C 58 FB 8F 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法1: 
填充方法1: 
MAC= E4 5B 3A D2 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法1: 
填充方法2: 
MAC= A9 24 C7 21 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法1: 
填充方法3: 
MAC= B1 EC D6 FC 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法2: 
填充方法1: 
MAC= 10 F9 BC 67 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法2: 
填充方法2: 
MAC= BE 7C 2A B7 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法2: 
填充方法3: 
MAC= 8E FC 8B C7 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法2: 
填充方法1: 
MAC= 21 5E 9C E6 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法2: 
填充方法2: 
MAC= 17 36 AC 1A 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法2: 
填充方法3: 
MAC= 05 38 26 96 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法3: 
填充方法1: 
MAC= A1 C7 2E 74 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法3: 
填充方法2: 
MAC= E9 08 62 30 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法3: 
填充方法3: 
MAC= AB 05 94 63 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法3: 
填充方法1: 
MAC= 2E 2B 14 28 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法3: 
填充方法2: 
MAC= 5A 69 2C E6 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法3: 
填充方法3: 
MAC= C5 9F 7E ED 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法4: 
填充方法1: 
MAC= AD 35 02 B7 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法4: 
填充方法2: 
MAC= 61 C3 33 E3 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法4: 
填充方法3: 
MAC= 95 2A F8 38 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法4: 
填充方法1: 
MAC= 05 F1 08 4C 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法4: 
填充方法2: 
MAC= A1 BC 09 31 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法4: 
填充方法3: 
MAC= AF DE E0 F9 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法5: 
填充方法1: 
MAC= F4 E4 02 B6 B7 2C 13 17 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法5: 
填充方法2: 
MAC= 70 F0 5E C9 E4 F7 2F 99 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法5: 
填充方法3: 
MAC= D6 1F 51 F2 EA 2A 2D 63 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法5: 
填充方法1: 
MAC= 0F 24 BD A4 AC 22 0F 4F 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法5: 
填充方法2: 
MAC= E0 04 13 41 9A FC 16 0B 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法5: 
填充方法3: 
MAC= DD DF 5E D3 0F 18 EB FC 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法6: 
填充方法1: 
MAC= 57 7E F2 21 18 CE 5D BA 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法6: 
填充方法2: 
MAC= 60 74 60 B8 D8 C0 FD FA 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f7220616c6c20
MAC算法6: 
填充方法3: 
MAC= FD 3D BB 6E F1 65 07 54 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法6: 
填充方法1: 
MAC= 10 F7 47 D1 4F 72 C2 29 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法6: 
填充方法2: 
MAC= B2 9B 9A 76 DD 1C 39 12 

--------------------------------------------------
消息: 4e6f77206973207468652074696d6520666f72206974
MAC算法6: 
填充方法3: 
MAC= F6 45 FB 7D 4D 4A 42 B4 
  1. SP800-3B
com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 org.yy.mac.YMacTest,test_omac1
omac SP800-38B verify: 
====================================================================
algorithm : AES128
key: 2b7e151628aed2a6abf7158809cf4f3c
msg: 
mac: bb1d6929e95937287fa37d129b756746
====================================================================
algorithm : AES128
key: 2b7e151628aed2a6abf7158809cf4f3c
msg: 6bc1bee22e409f96e93d7e117393172a
mac: 070a16b46b4d4144f79bdd9dd04a287c
====================================================================
algorithm : AES128
key: 2b7e151628aed2a6abf7158809cf4f3c
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411
mac: dfa66747de9ae63030ca32611497c827
====================================================================
algorithm : AES128
key: 2b7e151628aed2a6abf7158809cf4f3c
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710
mac: 51f0bebf7e3b9d92fc49741779363cfe
====================================================================
algorithm : AES192
key: 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b
msg: 
mac: d17ddf46adaacde531cac483de7a9367
====================================================================
algorithm : AES192
key: 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b
msg: 6bc1bee22e409f96e93d7e117393172a
mac: 9e99a7bf31e710900662f65e617c5184
====================================================================
algorithm : AES192
key: 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411
mac: 8a1de5be2eb31aad089a82e6ee908b0e
====================================================================
algorithm : AES192
key: 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710
mac: a1d5df0eed790f794d77589659f39a11
====================================================================
algorithm : AES256
key: 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4
msg: 
mac: 028962f61b7bf89efc6b551f4667d983
====================================================================
algorithm : AES256
key: 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4
msg: 6bc1bee22e409f96e93d7e117393172a
mac: 28a7023f452e8f82bd4bf28d8c37c35c
====================================================================
algorithm : AES256
key: 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411
mac: aaf3d8f1de5640c232f5b169b9c911e6
====================================================================
algorithm : AES256
key: 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710
mac: e1992190549f6ed5696a2c056c315410
====================================================================
algorithm : DESede3
key: 8aa83bf8cbda10620bc1bf19fbb6cd58bc313d4a371ca8b5
msg: 
mac: b7a688e122ffaf95
====================================================================
algorithm : DESede3
key: 8aa83bf8cbda10620bc1bf19fbb6cd58bc313d4a371ca8b5
msg: 6bc1bee22e409f96
mac: 8e8f293136283797
====================================================================
algorithm : DESede3
key: 8aa83bf8cbda10620bc1bf19fbb6cd58bc313d4a371ca8b5
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a57
mac: 743ddbe0ce2dc2ed
====================================================================
algorithm : DESede3
key: 8aa83bf8cbda10620bc1bf19fbb6cd58bc313d4a371ca8b5
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51
mac: 33e6b1092400eae5
====================================================================
algorithm : DESede
key: 4cf15134a2850dd58a3d10ba80570d38
msg: 
mac: bd2ebf9a3ba00361
====================================================================
algorithm : DESede
key: 4cf15134a2850dd58a3d10ba80570d38
msg: 6bc1bee22e409f96
mac: 4ff2ab813c53ce83
====================================================================
algorithm : DESede
key: 4cf15134a2850dd58a3d10ba80570d38
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a57
mac: 62dd1b471902bd4e
====================================================================
algorithm : DESede
key: 4cf15134a2850dd58a3d10ba80570d38
msg: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51
mac: 31b1e431dabc4eb8
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值