package org.bouncycastle.crypto.test;

import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.engines.DESEngine;
import org.bouncycastle.crypto.macs.ISO9797Alg3Mac;
import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.test.SimpleTest;

public class ISO9797Alg3MacTest
    extends SimpleTest
{
    static byte[] keyBytes = Hex.decode("7CA110454A1A6E570131D9619DC1376E");

    static byte[] input1 = "Hello World !!!!".getBytes();

    static byte[] output1 = Hex.decode("F09B856213BAB83B");

    public ISO9797Alg3MacTest()
    {
    }

    public void performTest()
    {
        KeyParameter key = new KeyParameter(keyBytes);
        BlockCipher cipher = new DESEngine();
        Mac mac = new ISO9797Alg3Mac(cipher);

        //
        // standard DAC - zero IV
        //
        mac.init(key);

        mac.update(input1, 0, input1.length);

        byte[] out = new byte[8];

        mac.doFinal(out, 0);

        if (!areEqual(out, output1))
        {
            fail("Failed - expected " + new String(Hex.encode(output1)) + " got " + new String(Hex.encode(out)));
        }

        //
        //  reset
        //
        mac.reset();

        mac.init(key);

        for (int i = 0; i != input1.length / 2; i++)
        {
            mac.update(input1[i]);
        }

        mac.update(input1, input1.length / 2, input1.length - (input1.length / 2));

        mac.doFinal(out, 0);

        if (!areEqual(out, output1))
        {
            fail("Reset failed - expected " + new String(Hex.encode(output1)) + " got " + new String(Hex.encode(out)));
        }

        testMacWithIv();
    }

    private void testMacWithIv()
    {
        byte[] inputData = new byte[]{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
        byte[] key = new byte[]{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
        byte[] zeroIv = new byte[8];
        byte[] nonZeroIv = new byte[]{0x5, 0x6, 0x7, 0x8, 0x1, 0x2, 0x3, 0x4};

        KeyParameter simpleParameter = new KeyParameter(key);
        ParametersWithIV zeroIvParameter = new ParametersWithIV(new KeyParameter(key), zeroIv);

        ISO9797Alg3Mac mac1 = new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding());

        // we calculate a reference MAC with a null IV
        mac1.init(simpleParameter);
        mac1.update(inputData, 0, inputData.length);
        byte[] output1 = new byte[mac1.getMacSize()];
        mac1.doFinal(output1, 0);

        // we then check that passing a vector of 0s is the same as not using any IV
        ISO9797Alg3Mac mac2 = new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding());
        mac2.init(zeroIvParameter);
        mac2.update(inputData, 0, inputData.length);
        byte[] output2 = new byte[mac2.getMacSize()];
        mac2.doFinal(output2, 0);
        if (!Arrays.areEqual(output1, output2))
        {
            fail("zero IV test failed");
        }

        // and then check that a non zero IV parameter produces a different results.
        ParametersWithIV nonZeroIvParameter = new ParametersWithIV(new KeyParameter(key), nonZeroIv);
        mac2 = new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding());
        mac2.init(nonZeroIvParameter);
        mac2.update(inputData, 0, inputData.length);
        output2 = new byte[mac2.getMacSize()];
        mac2.doFinal(output2, 0);
        if (Arrays.areEqual(output1, output2))
        {
            fail("non-zero IV test failed");
        }
    }

    public String getName()
    {
        return "ISO9797Alg3Mac";
    }

    public static void main(
        String[] args)
    {
        runTest(new ISO9797Alg3MacTest());
    }
}