package org.bouncycastle.crypto.test;

import java.security.SecureRandom;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.DSTU7624Engine;
import org.bouncycastle.crypto.engines.DSTU7624WrapEngine;
import org.bouncycastle.crypto.macs.DSTU7624Mac;
import org.bouncycastle.crypto.macs.KGMac;
import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.modes.KCCMBlockCipher;
import org.bouncycastle.crypto.modes.KCTRBlockCipher;
import org.bouncycastle.crypto.modes.KGCMBlockCipher;
import org.bouncycastle.crypto.modes.KXTSBlockCipher;
import org.bouncycastle.crypto.modes.OFBBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
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 DSTU7624Test
    extends CipherTest
{
    private static final SecureRandom RANDOM = new SecureRandom();
    
    private static byte[] randomBytes(int min, int max)
    {
        int count = min + RNGUtils.nextInt(RANDOM,max - min);
        byte[] result = new byte[count];
        RANDOM.nextBytes(result);
        return result;
    }

    static SimpleTest[] tests =
        {
            //ECB mode
            new BlockCipherVectorTest(0, new DSTU7624Engine(128), new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F")), "101112131415161718191A1B1C1D1E1F", "81BF1C7D779BAC20E1C9EA39B4D2AD06"),
            new BlockCipherVectorTest(1, new DSTU7624Engine(128), new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")), "202122232425262728292A2B2C2D2E2F", "58EC3E091000158A1148F7166F334F14"),
            new BlockCipherVectorTest(2, new DSTU7624Engine(256), new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")), "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F", "F66E3D570EC92135AEDAE323DCBD2A8CA03963EC206A0D5A88385C24617FD92C"),
            new BlockCipherVectorTest(3, new DSTU7624Engine(256), new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F")), "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", "606990E9E6B7B67A4BD6D893D72268B78E02C83C3CD7E102FD2E74A8FDFE5DD9"),
            new BlockCipherVectorTest(4, new DSTU7624Engine(512), new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F")), "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F", "4A26E31B811C356AA61DD6CA0596231A67BA8354AA47F3A13E1DEEC320EB56B895D0F417175BAB662FD6F134BB15C86CCB906A26856EFEB7C5BC6472940DD9D9"),

            //CBC mode
            new BlockCipherVectorTest(5, new CBCBlockCipher(new DSTU7624Engine(128)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F")), Hex.decode("101112131415161718191A1B1C1D1E1F")), "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F", "A73625D7BE994E85469A9FAABCEDAAB6DBC5F65DD77BB35E06BD7D1D8EAFC8624D6CB31CE189C82B8979F2936DE9BF14"),
            new BlockCipherVectorTest(6, new CBCBlockCipher(new DSTU7624Engine(128)), new ParametersWithIV(new KeyParameter(Hex.decode("0F0E0D0C0B0A09080706050403020100")), Hex.decode("1F1E1D1C1B1A19181716151413121110")), "88F2F048BA696170E3818915E0DBC0AFA6F141FEBC2F817138DA4AAB2DBF9CE490A488C9C82AC83FB0A6C0EEB64CFD22", "4F4E4D4C4B4A494847464544434241403F3E3D3C3B3A393837363534333231302F2E2D2C2B2A29282726252423222120"),
            new BlockCipherVectorTest(7, new CBCBlockCipher(new DSTU7624Engine(128)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")), Hex.decode("202122232425262728292A2B2C2D2E2F")), "303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D8000", "13EA15843AD14C50BC03ECEF1F43E398E4217752D3EB046AC393DACC5CA1D6FA0EB9FCEB229362B4F1565527EE3D8433"),
            new BlockCipherVectorTest(8, new CBCBlockCipher(new DSTU7624Engine(128)), new ParametersWithIV(new KeyParameter(Hex.decode("1F1E1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403020100")), Hex.decode("2F2E2D2C2B2A29282726252423222120")), "BC8F026FC603ECE05C24FDE87542730999B381870882AC0535D4368C4BABD81B884E96E853EE7E055262D9D204FBE212", "5F5E5D5C5B5A595857565554535251504F4E4D4C4B4A494847464544434241403F3E3D3C3B3A39383736353433323130"),
            new BlockCipherVectorTest(9, new CBCBlockCipher(new DSTU7624Engine(256)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")), Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F")), "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F", "9CDFDAA75929E7C2A5CFC1BF16B42C5AE3886D0258E8C577DC01DAF62D185FB999B9867736B87110F5F1BC7481912C593F48FF79E2AFDFAB9F704A277EC3E557B1B0A9F223DAE6ED5AF591C4F2D6FB22E48334F5E9B96B1A2EA5200F30A406CE"),
            new BlockCipherVectorTest(10, new CBCBlockCipher(new DSTU7624Engine(256)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F")), Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")), "606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF", "B8A2474578C2FEBF3F94703587BD5FDC3F4A4D2F43575B6144A1E1031FB3D1452B7FD52F5E3411461DAC506869FF8D2FAEF4FEE60379AE00B33AA3EAF911645AF8091CD8A45D141D1FB150E5A01C1F26FF3DBD26AC4225EC7577B2CE57A5B0FF"),
            new BlockCipherVectorTest(11, new CBCBlockCipher(new DSTU7624Engine(256)), new ParametersWithIV(new KeyParameter(Hex.decode("3F3E3D3C3B3A393837363534333231302F2E2D2C2B2A292827262524232221201F1E1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403020100")), Hex.decode("5F5E5D5C5B5A595857565554535251504F4E4D4C4B4A49484746454443424140")), "C69A59E10D00F087319B62288A57417C074EAD07C732A87055F0A5AD2BB288105705C45E091A9A6726E9672DC7D8C76FC45C782BCFEF7C39D94DEB84B17035BC8651255A0D34373451B6E1A2C827DB97566C9FF5506C5579F982A0EFC5BA7C28", "BFBEBDBCBBBAB9B8B7B6B5B4B3B2B1B0AFAEADACABAAA9A8A7A6A5A4A3A2A1A09F9E9D9C9B9A999897969594939291908F8E8D8C8B8A898887868584838281807F7E7D7C7B7A797877767574737271706F6E6D6C6B6A69686766656463626160"),
            new BlockCipherVectorTest(12, new CBCBlockCipher(new DSTU7624Engine(512)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F")), Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F")), "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF", "D4739B829EF901B24C1162AE4FDEF897EDA41FAC7F5770CDC90E1D1CDF124E8D7831E06B4498A4B6F6EC815DF2461DC99BB0449B0F09FCAA2C84090534BCC9329626FD74EF8F0A0BCB5765184629C3CBF53B0FB134F6D0421174B1C4E884D1CD1069A7AD19752DCEBF655842E79B7858BDE01390A760D85E88925BFE38B0FA57"),
            new BlockCipherVectorTest(13, new CBCBlockCipher(new DSTU7624Engine(512)), new ParametersWithIV(new KeyParameter(Hex.decode("3F3E3D3C3B3A393837363534333231302F2E2D2C2B2A292827262524232221201F1E1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403020100")), Hex.decode("7F7E7D7C7B7A797877767574737271706F6E6D6C6B6A696867666564636261605F5E5D5C5B5A595857565554535251504F4E4D4C4B4A49484746454443424140")), "5D5B3E3DE5BAA70E0A0684D458856CE759C6018D0B3F087FC1DAC101D380236DD934F2880B02D56A575BCA35A0CE4B0D9BA1F4A39C16CA7D80D59956630F09E54EC91E32B6830FE08323ED393F8028D150BF03CAD0629A5AFEEFF6E44257980618DB2F32B7B2B65B96E8451F1090829D2FFFC615CC1581E9221438DCEAD1FD12", "FFFEFDFCFBFAF9F8F7F6F5F4F3F2F1F0EFEEEDECEBEAE9E8E7E6E5E4E3E2E1E0DFDEDDDCDBDAD9D8D7D6D5D4D3D2D1D0CFCECDCCCBCAC9C8C7C6C5C4C3C2C1C0BFBEBDBCBBBAB9B8B7B6B5B4B3B2B1B0AFAEADACABAAA9A8A7A6A5A4A3A2A1A09F9E9D9C9B9A999897969594939291908F8E8D8C8B8A89888786858483828180"),

            //CFB mode
            new BlockCipherVectorTest(14, new CFBBlockCipher(new DSTU7624Engine(128), 128), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F")), Hex.decode("101112131415161718191A1B1C1D1E1F")), "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F", "A19E3E5E53BE8A07C9E0C01298FF83291F8EE6212110BE3FA5C72C88A082520B265570FE28680719D9B4465E169BC37A"),

            //OFB mode
            new BlockCipherVectorTest(15, new OFBBlockCipher(new DSTU7624Engine(128), 128), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F")), Hex.decode("101112131415161718191A1B1C1D1E1F")), "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F", "A19E3E5E53BE8A07C9E0C01298FF832953205C661BD85A51F3A94113BC785CAB634B36E89A8FDD16A12E4467F5CC5A26"),
            new BlockCipherVectorTest(16, new OFBBlockCipher(new DSTU7624Engine(128), 128), new ParametersWithIV(new KeyParameter(Hex.decode("0F0E0D0C0B0A09080706050403020100")), Hex.decode("1F1E1D1C1B1A19181716151413121110")), "649A1EAAE160AF20F5B3EF2F58D66C1178B82E00D26F30689C8EC22E8E86E9CBB0BD4FFEE39EB13C2311276A906DD636", "4F4E4D4C4B4A494847464544434241403F3E3D3C3B3A393837363534333231302F2E2D2C2B2A29282726252423222120"),
            new BlockCipherVectorTest(17, new OFBBlockCipher(new DSTU7624Engine(128), 128), new ParametersWithIV(new KeyParameter(Hex.decode("1F1E1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403020100")), Hex.decode("2F2E2D2C2B2A29282726252423222120")), "1A66CFBFEC00C6D52E39923E858DD64B214AB787798D3D5059A6B498AD66B34EAC48C4074BEC0D98C6", "5F5E5D5C5B5A595857565554535251504F4E4D4C4B4A494847464544434241403F3E3D3C3B3A393837"),
            new BlockCipherVectorTest(18, new OFBBlockCipher(new DSTU7624Engine(256), 256), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")), Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F")), "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F90", "B62F7F144A8C6772E693A96890F064C3F06831BF743F5B0DD061067F3D22877331AA6A99D939F05B7550E9402BD1615CC7B2D4A167E83EC0D8A894F92C72E176F3880B61C311D69CE1210C59184E818E19"),
            new BlockCipherVectorTest(19, new OFBBlockCipher(new DSTU7624Engine(256), 256), new ParametersWithIV(new KeyParameter(Hex.decode("1F1E1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403020100")), Hex.decode("3F3E3D3C3B3A393837363534333231302F2E2D2C2B2A29282726252423222120")), "7758A939DD6BD00CAF9153E5A5D5A66129105CA1EA54A97C06FA4A40960A068F55E34F9339A14436216948F92FA2FB5286D3AB1E81543FC0018A0C4E8C493475F4D35DCFB0A7A5377F6669B857CDC978E4", "9F9E9D9C9B9A999897969594939291908F8E8D8C8B8A898887868584838281807F7E7D7C7B7A797877767574737271706F6E6D6C6B6A696867666564636261605F5E5D5C5B5A595857565554535251504F"),
            new BlockCipherVectorTest(20, new OFBBlockCipher(new DSTU7624Engine(256), 256), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F")), Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")), "606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0", "0008F28A82D2D01D23BFB2F8BB4F06D8FE73BA4F48A2977585570ED3818323A668883C9DCFF610CC7E3EA5C025FBBC5CA6520F8F11CA35CEB9B07031E6DBFABE39001E9A3CC0A24BBC565939592B4DEDBD"),
            new BlockCipherVectorTest(21, new OFBBlockCipher(new DSTU7624Engine(256), 256), new ParametersWithIV(new KeyParameter(Hex.decode("3F3E3D3C3B3A393837363534333231302F2E2D2C2B2A292827262524232221201F1E1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403020100")), Hex.decode("5F5E5D5C5B5A595857565554535251504F4E4D4C4B4A49484746454443424140")), "98E122708FDABB1B1A5765C396DC79D7573221EC486ADDABD1770B147A6DD00B5FBC4F1EC68C59775B7AAA4D43C4CCE4F396D982DF64D30B03EF6C3B997BA0ED940BBC590BD30D64B5AE207147D71086B5", "BFBEBDBCBBBAB9B8B7B6B5B4B3B2B1B0AFAEADACABAAA9A8A7A6A5A4A3A2A1A09F9E9D9C9B9A999897969594939291908F8E8D8C8B8A898887868584838281807F7E7D7C7B7A797877767574737271706F"),
            new BlockCipherVectorTest(22, new OFBBlockCipher(new DSTU7624Engine(512), 512), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F")), Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F")), "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0", "CAA761980599B3ED2E945C41891BAD95F72B11C73ED26536A6847458BC76C827357156B4B3FE0DC1877F5B9F17B866C37B21D89531DB48007D05DEC928B06766C014BB9080385EDF0677E48A0A39B5E7489E28E82FFFD1F84694F17296CB701656"),
            new BlockCipherVectorTest(23, new OFBBlockCipher(new DSTU7624Engine(512), 512), new ParametersWithIV(new KeyParameter(Hex.decode("3F3E3D3C3B3A393837363534333231302F2E2D2C2B2A292827262524232221201F1E1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403020100")), Hex.decode("7F7E7D7C7B7A797877767574737271706F6E6D6C6B6A696867666564636261605F5E5D5C5B5A595857565554535251504F4E4D4C4B4A49484746454443424140")), "06C061A4A66DFC0910034B3CFBDC4206D8908241C56BF41C4103CFD6DF322210B87F57EAE9F9AD815E606A7D1E8E6BD7CB1EBFBDBCB085C2D06BF3CC1586CB2EE1D81D38437F425131321647E42F5DE309D33F25B89DE37124683E4B44824FC56D", "EFEEEDECEBEAE9E8E7E6E5E4E3E2E1E0DFDEDDDCDBDAD9D8D7D6D5D4D3D2D1D0CFCECDCCCBCAC9C8C7C6C5C4C3C2C1C0BFBEBDBCBBBAB9B8B7B6B5B4B3B2B1B0AFAEADACABAAA9A8A7A6A5A4A3A2A1A09F9E9D9C9B9A999897969594939291908F"),

            //CTR mode
            new BlockCipherVectorTest(24, new KCTRBlockCipher(new DSTU7624Engine(128)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F")), Hex.decode("101112131415161718191A1B1C1D1E1F")), "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748", "A90A6B9780ABDFDFF64D14F5439E88F266DC50EDD341528DD5E698E2F000CE21F872DAF9FE1811844A"),
            new BlockCipherVectorTest(25, new KCTRBlockCipher(new DSTU7624Engine(128)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F")), Hex.decode("101112131415161718191A1B1C1D1E1F")), "303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F", "B91A7B8790BBCFCFE65D04E5538E98E216AC209DA33122FDA596E8928070BE51"),
            new StreamCipherVectorTest(26, new KCTRBlockCipher(new DSTU7624Engine(128)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F")), Hex.decode("101112131415161718191A1B1C1D1E1F")), "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748", "A90A6B9780ABDFDFF64D14F5439E88F266DC50EDD341528DD5E698E2F000CE21F872DAF9FE1811844A"),
            new StreamCipherVectorTest(27, new KCTRBlockCipher(new DSTU7624Engine(128)), new ParametersWithIV(new KeyParameter(Hex.decode("000102030405060708090A0B0C0D0E0F")), Hex.decode("101112131415161718191A1B1C1D1E1F")), "303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F", "B91A7B8790BBCFCFE65D04E5538E98E216AC209DA33122FDA596E8928070BE51")
        };


    public DSTU7624Test()
    {
        super(tests, new DSTU7624Engine(128), new KeyParameter(new byte[16]));
    }

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

    public void performTest()
        throws Exception
    {
        super.performTest();

        MacTests();
        KeyWrapTests();
        CCMModeTests();
        XTSModeTests();
        GCMModeTests();
        testOverlapping();
        kccmKgcmNoUnverifiedPlaintextOnFailure();
        kgcmReInitClearsAssociatedText();
        kccmVariableLengthAssociatedTextAndNonce();
    }

    private void kgcmReInitClearsAssociatedText()
        throws Exception
    {
        // github PR #2349: init() applies AEADParameters.getAssociatedText() to the associatedText
        // accumulator but did not first clear it, so a re-init carried the previous operation's AAD
        // (left in the buffer by the prior doFinal's reset()) into the next tag. A re-initialised
        // cipher must behave exactly like a freshly constructed one.
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] nonce1 = Hex.decode("101112131415161718191A1B1C1D1E1F");
        byte[] nonce2 = Hex.decode("202122232425262728292A2B2C2D2E2F");
        byte[] aadA = Hex.decode("A0A1A2A3A4A5A6A7");
        byte[] aadB = Hex.decode("B0B1B2B3B4B5B6B7");
        byte[] pt = Hex.decode("303132333435363738393A3B3C3D3E3F");

        // (1) AEADParameters branch: re-init with a different AAD must match a fresh cipher.
        KGCMBlockCipher reused = new KGCMBlockCipher(new DSTU7624Engine(128));
        kgcmEncrypt(reused, new AEADParameters(new KeyParameter(key), 128, nonce1, aadA), pt);
        byte[] viaReuse = kgcmEncrypt(reused, new AEADParameters(new KeyParameter(key), 128, nonce2, aadB), pt);

        byte[] viaFresh = kgcmEncrypt(new KGCMBlockCipher(new DSTU7624Engine(128)),
            new AEADParameters(new KeyParameter(key), 128, nonce2, aadB), pt);

        if (!Arrays.areEqual(viaReuse, viaFresh))
        {
            fail("KGCM re-init did not clear stale associated text (AEADParameters)");
        }

        // (2) ParametersWithIV branch: re-init with no AAD must match a fresh no-AAD cipher.
        KGCMBlockCipher reused2 = new KGCMBlockCipher(new DSTU7624Engine(128));
        kgcmEncrypt(reused2, new AEADParameters(new KeyParameter(key), 128, nonce1, aadA), pt);
        byte[] viaReuse2 = kgcmEncrypt(reused2, new ParametersWithIV(new KeyParameter(key), nonce2), pt);

        byte[] viaFresh2 = kgcmEncrypt(new KGCMBlockCipher(new DSTU7624Engine(128)),
            new ParametersWithIV(new KeyParameter(key), nonce2), pt);

        if (!Arrays.areEqual(viaReuse2, viaFresh2))
        {
            fail("KGCM re-init did not clear stale associated text (ParametersWithIV)");
        }
    }

    private byte[] kgcmEncrypt(KGCMBlockCipher cipher, CipherParameters params, byte[] pt)
        throws Exception
    {
        cipher.init(true, params);
        byte[] out = new byte[cipher.getOutputSize(pt.length)];
        int len = cipher.processBytes(pt, 0, pt.length, out, 0);
        cipher.doFinal(out, len);
        return out;
    }

    private void kccmVariableLengthAssociatedTextAndNonce()
        throws Exception
    {
        // github PR #2350: KCCM (DSTU 7624 CCM) init now zero-extends a short nonce to the block
        // size, and processAssociatedText handles associated data whose length is not a multiple of
        // the block size (the "padding not supported" restriction was dropped) instead of rejecting
        // it. The associated-data MAC loop must still authenticate every block, including the case of
        // multi-block associated data whose length IS a multiple of the block size.
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] iv = Hex.decode("101112131415161718191A1B1C1D1E1F");
        byte[] pt = Hex.decode("303132333435363738393A3B3C3D3E3F");

        // (1) Two-block associated data at a 16-byte block size. This was already legal (32 % 16 == 0)
        // and its MAC must not change: the value below is the pre-PR result. An off-by-block error in
        // the AAD loop (only the first block authenticated) yields a different MAC here.
        byte[] aad2 = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");
        KCCMBlockCipher ccm = new KCCMBlockCipher(new DSTU7624Engine(128));
        ccm.init(true, new AEADParameters(new KeyParameter(key), 128, iv));
        ccm.processAADBytes(aad2, 0, aad2.length);
        byte[] out = new byte[ccm.getOutputSize(pt.length)];
        ccm.doFinal(out, ccm.processBytes(pt, 0, pt.length, out, 0));
        if (!Arrays.areEqual(ccm.getMac(), Hex.decode("85bf5ebb03647d30f57adf94b6904461")))
        {
            fail("KCCM multi-block associated-text MAC changed: got " + Hex.toHexString(ccm.getMac()));
        }

        // (2) Associated data whose length is not a multiple of the block size (20 bytes at bs 16)
        // must now round-trip rather than throwing "padding not supported".
        byte[] aadPartial = Hex.decode("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3");
        kccmCheckRoundtrip("partial AAD", new DSTU7624Engine(128), key, iv, aadPartial, pt);

        // (3) A nonce shorter than the block size must be accepted (zero-extended) and round-trip.
        byte[] shortNonce = Hex.decode("101112131415161718191A1B");
        kccmCheckRoundtrip("short nonce", new DSTU7624Engine(128), key, shortNonce, aadPartial, pt);
    }

    private void kccmCheckRoundtrip(String label, DSTU7624Engine engine, byte[] key, byte[] nonce, byte[] aad, byte[] pt)
        throws Exception
    {
        KCCMBlockCipher enc = new KCCMBlockCipher(engine);
        enc.init(true, new AEADParameters(new KeyParameter(key), 128, nonce));
        enc.processAADBytes(aad, 0, aad.length);
        byte[] ct = new byte[enc.getOutputSize(pt.length)];
        enc.doFinal(ct, enc.processBytes(pt, 0, pt.length, ct, 0));

        KCCMBlockCipher dec = new KCCMBlockCipher(engine);
        dec.init(false, new AEADParameters(new KeyParameter(key), 128, nonce));
        dec.processAADBytes(aad, 0, aad.length);
        byte[] recovered = new byte[dec.getOutputSize(ct.length)];
        dec.doFinal(recovered, dec.processBytes(ct, 0, ct.length, recovered, 0));

        if (!Arrays.areEqual(recovered, pt))
        {
            fail("KCCM " + label + " round-trip failed: got " + Hex.toHexString(recovered));
        }
    }

    private void kccmKgcmNoUnverifiedPlaintextOnFailure()
        throws Exception
    {
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] iv = Hex.decode("101112131415161718191A1B1C1D1E1F");
        byte[] plaintext = Hex.decode("303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F");

        // KCCM: plaintext is recovered into the output before the MAC is checked, so a tag failure
        // must not leave it there.
        {
            AEADParameters params = new AEADParameters(new KeyParameter(key), 128, iv);
            KCCMBlockCipher ccm = new KCCMBlockCipher(new DSTU7624Engine(128));
            ccm.init(true, params);
            byte[] ct = new byte[ccm.getOutputSize(plaintext.length)];
            ccm.doFinal(ct, ccm.processBytes(plaintext, 0, plaintext.length, ct, 0));

            ct[ct.length - 1] ^= 0x01;  // corrupt the (masked) MAC

            ccm.init(false, params);
            byte[] out = new byte[ct.length];
            Arrays.fill(out, (byte)0x55);
            try
            {
                ccm.doFinal(out, ccm.processBytes(ct, 0, ct.length, out, 0));
                fail("tampered KCCM ciphertext must not verify");
            }
            catch (InvalidCipherTextException e)
            {
                checkNoUnverifiedPlaintext("KCCM", out, plaintext.length);
            }
        }

        // KGCM
        {
            AEADParameters params = new AEADParameters(new KeyParameter(key), 128, iv);
            KGCMBlockCipher gcm = new KGCMBlockCipher(new DSTU7624Engine(128));
            gcm.init(true, params);
            byte[] ct = new byte[gcm.getOutputSize(plaintext.length)];
            gcm.doFinal(ct, gcm.processBytes(plaintext, 0, plaintext.length, ct, 0));

            ct[ct.length - 1] ^= 0x01;  // corrupt the tag

            gcm.init(false, params);
            byte[] out = new byte[ct.length];
            Arrays.fill(out, (byte)0x55);
            try
            {
                gcm.doFinal(out, gcm.processBytes(ct, 0, ct.length, out, 0));
                fail("tampered KGCM ciphertext must not verify");
            }
            catch (InvalidCipherTextException e)
            {
                checkNoUnverifiedPlaintext("KGCM", out, plaintext.length);
            }
        }
    }

    private void checkNoUnverifiedPlaintext(String name, byte[] out, int plaintextLen)
    {
        for (int i = 0; i != plaintextLen; i++)
        {
            if (out[i] != (byte)0x55)
            {
                fail(name + " left unverified plaintext in the output buffer on tag failure");
            }
        }
    }

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


    private void MacTests()
    {

        //test 1
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");

        byte[] authtext = Hex.decode("202122232425262728292A2B2C2D2E2F" +
            "303132333435363738393A3B3C3D3E3F" +
            "404142434445464748494A4B4C4D4E4F");

        byte[] expectedMac = Hex.decode("123B4EAB8E63ECF3E645A99C1115E241");

        byte[] mac = new byte[expectedMac.length];

        DSTU7624Mac dstu7624Mac = new DSTU7624Mac(128, 128);
        dstu7624Mac.init(new KeyParameter(key));
        dstu7624Mac.update(authtext, 0, authtext.length);
        dstu7624Mac.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed MAC test 1 - expected "
                + Hex.toHexString(expectedMac)
                + " got " + Hex.toHexString(mac));
        }


        //test 2
        key = Hex.decode("000102030405060708090A0B0C0D0E0F" +
            "101112131415161718191A1B1C1D1E1F" +
            "202122232425262728292A2B2C2D2E2F" +
            "303132333435363738393A3B3C3D3E3F");

        authtext = Hex.decode("404142434445464748494A4B4C4D4E4F" +
            "505152535455565758595A5B5C5D5E5F" +
            "606162636465666768696A6B6C6D6E6F" +
            "707172737475767778797A7B7C7D7E7F" +
            "808182838485868788898A8B8C8D8E8F" +
            "909192939495969798999A9B9C9D9E9F" +
            "A0A1A2A3A4A5A6A7A8A9AAABACADAEAF" +
            "B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF");

        expectedMac = Hex.decode("7279FA6BC8EF7525B2B35260D00A1743");

        dstu7624Mac = new DSTU7624Mac(512, 128);
        dstu7624Mac.init(new KeyParameter(key));
        dstu7624Mac.update(authtext, 0, authtext.length);
        dstu7624Mac.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed MAC test 2 - expected "
                + Hex.toHexString(expectedMac)
                + " got " + Hex.toHexString(mac));
        }

        // check that reset correctly on doFinal()
        dstu7624Mac.update(authtext, 0, authtext.length);
        dstu7624Mac.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed MAC test reset - expected "
                + Hex.toHexString(expectedMac)
                + " got " + Hex.toHexString(mac));
        }

        // check that init reset correctly
        dstu7624Mac.init(new KeyParameter(key));
        dstu7624Mac.init(new KeyParameter(key));
        dstu7624Mac.update(authtext, 0, authtext.length);
        dstu7624Mac.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed MAC test double init - expected "
                + Hex.toHexString(expectedMac)
                + " got " + Hex.toHexString(mac));
        }

        // check simple reset
        dstu7624Mac = new DSTU7624Mac(512, 128);
        dstu7624Mac.reset();
    }

    private void KeyWrapTests()
        throws Exception
    {
        //test 1
        /*
         * Initial implementation had bugs handling offset and length correctly, so for
         * this first test case we embed the input inside a larger buffer.
         */
        byte[] textA = randomBytes(1, 64);
        byte[] textB = randomBytes(1, 64);
        byte[] textToWrap = Arrays.concatenate(new byte[][]{ textA, Hex.decode("101112131415161718191A1B1C1D1E1F"), textB });

        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] expectedWrappedText = Hex.decode("1DC91DC6E52575F6DBED25ADDA95A1B6AD3E15056E489738972C199FB9EE2913");
        byte[] output = new byte[expectedWrappedText.length];

        DSTU7624WrapEngine wrapper = new DSTU7624WrapEngine(128);
        wrapper.init(true, new KeyParameter(key));
        output = wrapper.wrap(textToWrap, textA.length, textToWrap.length - textA.length - textB.length);

        if (!Arrays.areEqual(output, expectedWrappedText))
        {
            fail("Failed KW (wrapping) test 1 - expected "
                + Hex.toHexString(expectedWrappedText)
                + " got " + Hex.toHexString(output));
        }

        output = Arrays.concatenate(new byte[][]{ textB, output, textA });

        wrapper.init(false, new KeyParameter(key));
        output = wrapper.unwrap(output, textB.length, output.length - textB.length - textA.length);

        byte[] expected = Arrays.copyOfRange(textToWrap, textA.length, textToWrap.length - textB.length);
        if (!Arrays.areEqual(output, expected))
        {
            fail("Failed KW (unwrapping) test 1 - expected "
                + Hex.toHexString(expected)
                + " got " + Hex.toHexString(output));
        }

        //test 2
        key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        textToWrap = Hex.decode("101112131415161718191A1B1C1D1E1F20219000000000000000800000000000");
        expectedWrappedText = Hex.decode("0EA983D6CE48484D51462C32CC61672210FCC44196ABE635BAF878FDB83E1A63114128585D49DB355C5819FD38039169");

        output = new byte[expectedWrappedText.length];

        wrapper.init(true, new KeyParameter(key));
        output = wrapper.wrap(textToWrap, 0, textToWrap.length);


        if (!Arrays.areEqual(output, expectedWrappedText))
        {
            fail("Failed KW (wrapping) test 2 - expected "
                + Hex.toHexString(expectedWrappedText)
                + " got " + Hex.toHexString(output));
        }


        wrapper.init(false, new KeyParameter(key));

        output = wrapper.unwrap(expectedWrappedText, 0, expectedWrappedText.length);
        if (!Arrays.areEqual(output, textToWrap))
        {
            fail("Failed KW (unwrapping) test 2 - expected "
                + Hex.toHexString(textToWrap)
                + " got " + Hex.toHexString(output));
        }

        //test 3
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
        textToWrap = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F");
        expectedWrappedText = Hex.decode("2D09A7C18E6A5A0816331EC27CEA596903F77EC8D63F3BDB73299DE7FD9F4558E05992B0B24B39E02EA496368E0841CC1E3FA44556A3048C5A6E9E335717D17D");

        output = new byte[expectedWrappedText.length];

        wrapper = new DSTU7624WrapEngine(128);
        wrapper.init(true, new KeyParameter(key));
        output = wrapper.wrap(textToWrap, 0, textToWrap.length);


        if (!Arrays.areEqual(output, expectedWrappedText))
        {
            fail("Failed KW (wrapping) test 3 - expected "
                + Hex.toHexString(expectedWrappedText)
                + " got " + Hex.toHexString(output));
        }

        wrapper.init(false, new KeyParameter(key));

        output = wrapper.unwrap(expectedWrappedText, 0, expectedWrappedText.length);

        if (!Arrays.areEqual(output, textToWrap))
        {
            fail("Failed KW (unwrapping) test 3 - expected "
                + Hex.toHexString(textToWrap)
                + " got " + Hex.toHexString(output));
        }

        //test 4
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
        textToWrap = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464E8040000000000020");
        expectedWrappedText = Hex.decode("37E3EECB91150C6FA04CFD19D6FC57B7168C9FA5C5ED18601C68EE4AFD7301F8C8C51D7A0A5CD34F6FAB0D8AF11845CC1E4B16E0489FDA1D76BA4EFCFD161F76");

        output = new byte[expectedWrappedText.length];

        wrapper = new DSTU7624WrapEngine(128);
        wrapper.init(true, new KeyParameter(key));
        output = wrapper.wrap(textToWrap, 0, textToWrap.length);


        if (!Arrays.areEqual(output, expectedWrappedText))
        {
            fail("Failed KW (wrapping) test 4 - expected "
                + Hex.toHexString(expectedWrappedText)
                + " got " + Hex.toHexString(output));
        }

        wrapper.init(false, new KeyParameter(key));

        output = wrapper.unwrap(expectedWrappedText, 0, expectedWrappedText.length);

        if (!Arrays.areEqual(output, textToWrap))
        {
            fail("Failed KW (unwrapping) test 4 - expected "
                + Hex.toHexString(textToWrap)
                + " got " + Hex.toHexString(output));
        }

        //test 5
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
        textToWrap = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F");
        expectedWrappedText = Hex.decode("BE59D3C3C31B2685A8FA57CD000727F16AF303F0D87BC2D7ABD80DC2796BBC4CDBC4E0408943AF4DAF7DE9084DC81BFEF15FDCDD0DF399983DF69BF730D7AE2A199CA4F878E4723B7171DD4D1E8DF59C0F25FA0C20946BA64F9037D724BB1D50B6C2BD9788B2AF83EF6163087CD2D4488BC19F3A858D813E3A8947A529B6D65D");

        output = new byte[expectedWrappedText.length];

        wrapper = new DSTU7624WrapEngine(256);
        wrapper.init(true, new KeyParameter(key));
        output = wrapper.wrap(textToWrap, 0, textToWrap.length);


        if (!Arrays.areEqual(output, expectedWrappedText))
        {
            fail("Failed KW (wrapping) test 5 - expected "
                + Hex.toHexString(expectedWrappedText)
                + " got " + Hex.toHexString(output));
        }

        wrapper.init(false, new KeyParameter(key));

        output = wrapper.unwrap(expectedWrappedText, 0, expectedWrappedText.length);

        if (!Arrays.areEqual(output, textToWrap))
        {
            fail("Failed KW (unwrapping) test 5 - expected "
                + Hex.toHexString(textToWrap)
                + " got " + Hex.toHexString(output));
        }
    }

    private void CCMModeTests()
        throws Exception
    {
        //test 1
        byte[] key = Hex.decode("000102030405060708090a0b0c0d0e0f");
        byte[] iv = Hex.decode("101112131415161718191a1b1c1d1e1f");
        byte[] input = Hex.decode("303132333435363738393a3b3c3d3e3f");
        byte[] authText = Hex.decode("202122232425262728292a2b2c2d2e2f");

        byte[] expectedMac = Hex.decode("26a936173a4dc9160d6e3fda3a974060");
        byte[] expectedEncrypted = Hex.decode("b91a7b8790bbcfcfe65d04e5538e98e2704454c9dd39adace0b19d03f6aab07e");

        byte[] mac;
        byte[] encrypted = new byte[expectedEncrypted.length];

        // decryption no longer appends the MAC to the recovered plaintext; the output is
        // the plaintext only and the MAC is available via getMac() (standard AEAD contract).
        byte[] decrypted = new byte[input.length];
        byte[] expectedDecrypted = Arrays.clone(input);
        int len;


        AEADParameters param = new AEADParameters(new KeyParameter(key), 128, iv);

        KCCMBlockCipher dstu7624ccm = new KCCMBlockCipher(new DSTU7624Engine(128));

        dstu7624ccm.init(true, param);

        dstu7624ccm.processAADBytes(authText, 0, authText.length);

        len = dstu7624ccm.processBytes(input, 0, input.length, encrypted, 0);


        dstu7624ccm.doFinal(encrypted, len);

        mac = dstu7624ccm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed CCM mac test 1 - expected "
                + Hex.toHexString(expectedMac)
                + " got " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedEncrypted))
        {
            fail("Failed CCM encrypt test 1 - expected "
                + Hex.toHexString(expectedEncrypted)
                + " got " + Hex.toHexString(encrypted));
        }

        dstu7624ccm.init(false, param);

        dstu7624ccm.processAADBytes(authText, 0, authText.length);

        len = dstu7624ccm.processBytes(expectedEncrypted, 0, expectedEncrypted.length, decrypted, 0);

        dstu7624ccm.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, expectedDecrypted))
        {
            fail("Failed CCM decrypt/verify mac test 1 - expected "
                + Hex.toHexString(expectedDecrypted)
                + " got " + Hex.toHexString(decrypted));
        }

        //test 2
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
        iv = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");
        input = Hex.decode("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F");
        authText = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");

        expectedMac = Hex.decode("9AB831B4B0BF0FDBC36E4B4FD58F0F00");
        expectedEncrypted = Hex.decode("7EC15C54BB553CB1437BE0EFDD2E810F6058497EBCE4408A08A73FADF3F459D56B0103702D13AB73ACD2EB33A8B5E9CFFF5EB21865A6B499C10C810C4BAEBE809C48AD90A9E12A68380EF1C1B7C83EE1");

        mac = new byte[expectedMac.length];
        encrypted = new byte[expectedEncrypted.length];

        decrypted = new byte[input.length];
        expectedDecrypted = Arrays.clone(input);


        param = new AEADParameters(new KeyParameter(key), 128, iv);

        dstu7624ccm = new KCCMBlockCipher(new DSTU7624Engine(256));

        dstu7624ccm.init(true, param);

        dstu7624ccm.processAADBytes(authText, 0, authText.length);

        len = dstu7624ccm.processBytes(input, 0, input.length, encrypted, 0);

        dstu7624ccm.doFinal(encrypted, len);

        mac = dstu7624ccm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed CCM mac test 2 - expected "
                + Hex.toHexString(expectedMac)
                + " got " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedEncrypted))
        {
            fail("Failed CCM encrypt test 2 - expected "
                + Hex.toHexString(expectedEncrypted)
                + " got " + Hex.toHexString(encrypted));
        }

        dstu7624ccm.init(false, param);

        dstu7624ccm.processAADBytes(authText, 0, authText.length);

        len = dstu7624ccm.processBytes(expectedEncrypted, 0, expectedEncrypted.length, decrypted, 0);

        dstu7624ccm.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, expectedDecrypted))
        {
            fail("Failed CCM decrypt/verify mac test 2 - expected "
                + Hex.toHexString(expectedDecrypted)
                + " got " + Hex.toHexString(decrypted));
        }

        //test 3
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");
        iv = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");
        input = Hex.decode("808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF");
        authText = Hex.decode("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F");

        expectedMac = Hex.decode("924FA0326824355595C98028E84D86279CEA9135FAB35F22054AE3203E68AE46");
        expectedEncrypted = Hex.decode("3EBDB4584B5169A26FBEBA0295B4223F58D5D8A031F2950A1D7764FAB97BA058E9E2DAB90FF0C519AA88435155A71B7B53BB100F5D20AFFAC0552F5F2813DEE8DD3653491737B9615A5CCD83DB32F1E479BF227C050325BBBFF60BCA9558D7FE");

        mac = new byte[expectedMac.length];
        encrypted = new byte[expectedEncrypted.length];

        decrypted = new byte[input.length];
        expectedDecrypted = Arrays.clone(input);


        param = new AEADParameters(new KeyParameter(key), 256, iv);

        dstu7624ccm = new KCCMBlockCipher(new DSTU7624Engine(256), 6);

        dstu7624ccm.init(true, param);

        dstu7624ccm.processAADBytes(authText, 0, authText.length);

        len = dstu7624ccm.processBytes(input, 0, input.length, encrypted, 0);

        dstu7624ccm.doFinal(encrypted, len);

        mac = dstu7624ccm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed CCM mac test 3 - expected "
                + Hex.toHexString(expectedMac)
                + " got " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedEncrypted))
        {
            fail("Failed CCM encrypt test 3 - expected "
                + Hex.toHexString(expectedEncrypted)
                + " got " + Hex.toHexString(encrypted));
        }

        dstu7624ccm.init(false, param);

        dstu7624ccm.processAADBytes(authText, 0, authText.length);

        len = dstu7624ccm.processBytes(expectedEncrypted, 0, expectedEncrypted.length, decrypted, 0);

        dstu7624ccm.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, expectedDecrypted))
        {
            fail("Failed CCM decrypt/verify mac test 3 - expected "
                + Hex.toHexString(expectedDecrypted)
                + " got " + Hex.toHexString(decrypted));
        }

        //test 4
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");
        iv = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F");
        input = Hex.decode("C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF");
        authText = Hex.decode("808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF");

        expectedMac = Hex.decode("D4155EC3D888C8D32FE184AC260FD60F567705E1DF362A6F1F9C287156AA96D91BC4C56F9709E72F3D79CF0A9AC8BDC2BA836BE50E823AB50FB1B39080390923");
        expectedEncrypted = Hex.decode("220642D7277D104788CF97B10210984F506435512F7BF153C5CDABFECC10AFB4A2E2FC51F616AF80FFDD0607FAD4F542B8EF0667717CE3EAAA8FBC303CE76C99BD8F80CE149143C04FC2490272A31B029DDADA82F055FE4ABEF452A7D438B21E59C1D8B3DD4606BAD66A6F36300EF3CE0E5F3BB59F11416E80B7FC5A8E8B057A");

        mac = new byte[expectedMac.length];
        encrypted = new byte[expectedEncrypted.length];

        decrypted = new byte[input.length];
        expectedDecrypted = Arrays.clone(input);


        param = new AEADParameters(new KeyParameter(key), 512, iv);

        dstu7624ccm = new KCCMBlockCipher(new DSTU7624Engine(512), 8);

        dstu7624ccm.init(true, param);

        dstu7624ccm.processAADBytes(authText, 0, authText.length);

        len = dstu7624ccm.processBytes(input, 0, input.length, encrypted, 0);

        dstu7624ccm.doFinal(encrypted, len);

        mac = dstu7624ccm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed CCM mac test 4 - expected "
                + Hex.toHexString(expectedMac)
                + " got " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedEncrypted))
        {
            fail("Failed CCM encrypt test 4 - expected "
                + Hex.toHexString(expectedEncrypted)
                + " got " + Hex.toHexString(encrypted));
        }

        dstu7624ccm.init(false, param);

        dstu7624ccm.processAADBytes(authText, 0, authText.length);

        len = dstu7624ccm.processBytes(expectedEncrypted, 0, expectedEncrypted.length, decrypted, 0);

        dstu7624ccm.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, expectedDecrypted))
        {
            fail("Failed CCM decrypt/verify mac test 4 - expected "
                + Hex.toHexString(expectedDecrypted)
                + " got " + Hex.toHexString(decrypted));
        }

        doFinalTest(new KCCMBlockCipher(new DSTU7624Engine(512), 8), key, iv, authText, input, expectedEncrypted);

        CCMModePartialBlockTests();

        CCMModeLongMessageKeystreamTest();
        CCMModeNonceBindingTest();
        KCCMNonceReuseTests();
    }

    /*
     * Regression test for the KCCM gamma-counter carry bug: the per-block counter advance must
     * propagate carry across the whole block, otherwise only the low byte of the counter changes
     * and the keystream block E(s) repeats every 256 blocks, encrypting a long message with a
     * two-time pad. Encrypting 257 identical (zero) plaintext blocks makes ciphertext block i
     * equal block i+256 under the buggy no-carry code; with carry they differ. See github #287.
     */
    private void CCMModeLongMessageKeystreamTest()
        throws Exception
    {
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] iv = Hex.decode("101112131415161718191A1B1C1D1E1F");

        KCCMBlockCipher ccm = new KCCMBlockCipher(new DSTU7624Engine(128));
        int blockSize = ccm.getUnderlyingCipher().getBlockSize();

        // 257 blocks of zero plaintext: with zero input each ciphertext block equals its keystream
        // block, so a repeating keystream shows up directly as repeated ciphertext blocks.
        byte[] input = new byte[257 * blockSize];

        AEADParameters param = new AEADParameters(new KeyParameter(key), 128, iv);
        ccm.init(true, param);
        byte[] encrypted = new byte[ccm.getOutputSize(input.length)];
        int len = ccm.processBytes(input, 0, input.length, encrypted, 0);
        len += ccm.doFinal(encrypted, len);

        byte[] block0 = Arrays.copyOfRange(encrypted, 0, blockSize);
        byte[] block256 = Arrays.copyOfRange(encrypted, 256 * blockSize, 257 * blockSize);
        if (Arrays.areEqual(block0, block256))
        {
            fail("Failed CCM long-message keystream test - keystream block repeats every 256 blocks (two-time pad)");
        }

        // sanity: encrypt/decrypt still round-trips over the long message
        ccm.init(false, param);
        byte[] decrypted = new byte[ccm.getOutputSize(len)];
        int decLen = ccm.processBytes(encrypted, 0, len, decrypted, 0);
        decLen += ccm.doFinal(decrypted, decLen);

        if (decLen != input.length || !Arrays.areEqual(input, Arrays.copyOfRange(decrypted, 0, input.length)))
        {
            fail("Failed CCM long-message round-trip");
        }
    }

    /*
     * Regression test ensuring the G1 block is processed unconditionally; it had been previously skipped when no
     * associated data was present. The G1 block folds the nonce, data length and MAC-size flag into the MAC. If it
     * is absent, the underlying MAC is independent of the nonce, so a chosen-plaintext attacker could forge a valid
     * ciphertext+tag for an un-queried nonce by cancelling the nonce-dependent data/tag keystreams across three
     * empty-AAD encryption queries. This test assembles such a forgery and requires that the decryptor rejects it.
     * (A plain wrong-nonce decryption does NOT catch this specific bug, since the tag is masked by a nonce-
     * dependent keystream; so it fails even when the MAC ignores it.)
     */
    private void CCMModeNonceBindingTest() throws Exception
    {
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] nonce1 = Hex.decode("101112131415161718191A1B1C1D1E1F");
        byte[] nonce2 = Hex.decode("202122232425262728292A2B2C2D2E2F");
        byte[] plaintext = Hex.decode("303132333435363738393A3B3C3D3E3F");
        int blockSize = plaintext.length;

        // Three empty-AAD encryption-oracle queries used to build a cross-nonce forgery.
        byte[] z1 = CCMModeNoAADEncrypt(key, nonce1, new byte[blockSize]); // enc(0, N1)
        byte[] z2 = CCMModeNoAADEncrypt(key, nonce2, new byte[blockSize]); // enc(0, N2)
        byte[] zp = CCMModeNoAADEncrypt(key, nonce2, plaintext);           // enc(P, N2)

        // C_forge = P xor C0' (decrypts to P under N1); T_forge = TP xor T0 xor T0' cancels the
        // nonce-2 keystream and the E_K(0) terms, which only validates if the MAC ignores the nonce.
        byte[] forged = new byte[2 * blockSize];
        for (int i = 0; i < blockSize; i++)
        {
            forged[i] = (byte)(plaintext[i] ^ z1[i]);
            forged[blockSize + i] = (byte)(zp[blockSize + i] ^ z2[blockSize + i] ^ z1[blockSize + i]);
        }

        KCCMBlockCipher ccm = new KCCMBlockCipher(new DSTU7624Engine(128));
        ccm.init(false, new AEADParameters(new KeyParameter(key), 128, nonce1));
        byte[] decrypted = new byte[ccm.getOutputSize(forged.length)];
        try
        {
            int len = ccm.processBytes(forged, 0, forged.length, decrypted, 0);
            ccm.doFinal(decrypted, len);
            fail("Failed CCM nonce-binding test - cross-nonce forgery accepted (MAC not bound to nonce)");
        }
        catch (InvalidCipherTextException e)
        {
            // expected: the G1 nonce binding makes the forged tag invalid
        }
    }

    private static byte[] CCMModeNoAADEncrypt(byte[] key, byte[] nonce, byte[] plaintext) throws Exception
    {
        KCCMBlockCipher ccm = new KCCMBlockCipher(new DSTU7624Engine(128));
        ccm.init(true, new AEADParameters(new KeyParameter(key), 128, nonce));
        byte[] output = new byte[ccm.getOutputSize(plaintext.length)];
        int len = ccm.processBytes(plaintext, 0, plaintext.length, output, 0);
        len += ccm.doFinal(output, len);
        return output;
    }

    /*
     * Round-trip (self-consistency) tests for non-block-aligned KCCM input. DSTU 7624:2014 does
     * not publish a partial-block CCM test vector, so these confirm only that encrypt-then-decrypt
     * recovers the plaintext and that tampering is detected -- the produced bytes are NOT verified
     * against an independent conformant implementation. See github #287 and the interop caveat on
     * KCCMBlockCipher.
     */
    private void CCMModePartialBlockTests()
        throws Exception
    {
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] iv = Hex.decode("101112131415161718191A1B1C1D1E1F");
        byte[] authText = Hex.decode("202122232425262728292A2B2C2D2E2F");

        // 20 bytes against a 16-byte block: one full block plus a 4-byte partial block
        byte[] input = Hex.decode("303132333435363738393A3B3C3D3E3F40414243");

        AEADParameters param = new AEADParameters(new KeyParameter(key), 128, iv);
        KCCMBlockCipher ccm = new KCCMBlockCipher(new DSTU7624Engine(128));

        // encrypt
        ccm.init(true, param);
        ccm.processAADBytes(authText, 0, authText.length);
        byte[] encrypted = new byte[ccm.getOutputSize(input.length)];
        int len = ccm.processBytes(input, 0, input.length, encrypted, 0);
        len += ccm.doFinal(encrypted, len);

        if (len != input.length + ccm.getUnderlyingCipher().getBlockSize())
        {
            fail("Failed CCM partial-block ciphertext length - got " + len);
        }

        // decrypt and confirm round-trip
        ccm.init(false, param);
        ccm.processAADBytes(authText, 0, authText.length);
        byte[] decrypted = new byte[ccm.getOutputSize(len)];
        int decLen = ccm.processBytes(encrypted, 0, len, decrypted, 0);
        decLen += ccm.doFinal(decrypted, decLen);

        if (decLen != input.length || !Arrays.areEqual(input, Arrays.copyOfRange(decrypted, 0, input.length)))
        {
            fail("Failed CCM partial-block round-trip - expected "
                + Hex.toHexString(input)
                + " got " + Hex.toHexString(Arrays.copyOfRange(decrypted, 0, decLen)));
        }

        // tampering with the partial ciphertext must fail the MAC check
        encrypted[input.length - 1] ^= 0x01;
        ccm.init(false, param);
        ccm.processAADBytes(authText, 0, authText.length);
        byte[] tampered = new byte[ccm.getOutputSize(len)];
        try
        {
            int l = ccm.processBytes(encrypted, 0, len, tampered, 0);
            ccm.doFinal(tampered, l);

            fail("Failed CCM partial-block tamper detection - no exception thrown");
        }
        catch (InvalidCipherTextException e)
        {
            // expected
        }
    }

    private void XTSModeTests()
        throws Exception
    {

        //test 1
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] iv = Hex.decode("101112131415161718191A1B1C1D1E1F");
        byte[] plainText = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");

        byte[] output = new byte[plainText.length];
        byte[] expectedCipherText = Hex.decode("B3E431B3FBAF31108C302669EE7116D1CF518B6D329D30618DF5628E426BDEF1");

        byte[] decrypted = new byte[plainText.length];


        int len;

        KXTSBlockCipher dstu7624xts = new KXTSBlockCipher(new DSTU7624Engine(128));
        ParametersWithIV param = new ParametersWithIV(new KeyParameter(key), iv);

        dstu7624xts.init(true, param);
        len = dstu7624xts.processBytes(plainText, 0, plainText.length, output, 0);

        dstu7624xts.doFinal(output, len);

        if (!Arrays.areEqual(output, expectedCipherText))
        {
            fail("Failed XTS encrypt test 1 - expected "
                + Hex.toHexString(expectedCipherText)
                + " got " + Hex.toHexString(output));
        }


        dstu7624xts.init(false, param);
        len = dstu7624xts.processBytes(expectedCipherText, 0, expectedCipherText.length, decrypted, 0);
        dstu7624xts.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed XTS decrypt test 1 - expected "
                + Hex.toHexString(plainText)
                + " got " + Hex.toHexString(decrypted));
        }

        //test 2
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
        iv = Hex.decode("202122232425262728292A2B2C2D2E2F");
        plainText = Hex.decode("303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F");

        output = new byte[plainText.length];
        expectedCipherText = Hex.decode("830AC78A6F629CB4C7D5D156FD84955BD0998CA1E0BC1FF135676BF2A2598FA1");

        decrypted = new byte[plainText.length];


        dstu7624xts = new KXTSBlockCipher(new DSTU7624Engine(128));
        param = new ParametersWithIV(new KeyParameter(key), iv);

        dstu7624xts.init(true, param);
        len = dstu7624xts.processBytes(plainText, 0, plainText.length, output, 0);
        dstu7624xts.doFinal(output, len);

        if (!Arrays.areEqual(output, expectedCipherText))
        {
            fail("Failed XTS encrypt test 2 - expected "
                + Hex.toHexString(expectedCipherText)
                + " got " + Hex.toHexString(output));
        }


        dstu7624xts.init(false, param);
        len = dstu7624xts.processBytes(expectedCipherText, 0, expectedCipherText.length, decrypted, 0);
        dstu7624xts.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed XTS decrypt test 2 - expected "
                + Hex.toHexString(plainText)
                + " got " + Hex.toHexString(decrypted));
        }


        //test 3
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
        iv = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");
        plainText = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F");

        output = new byte[plainText.length];
        expectedCipherText = Hex.decode("E0E51EAEA6A3134600758EA7F87E88025D8B82897C8DB099B843054C3A51883756913571530BA8FA23003E337627E698674B807E847EC6B2292627736562F9F62B2DE9E6AAC5DF74C09A0C5CF80280174AEC9BDD4E73F7D63EDBC29A6922637A");

        decrypted = new byte[plainText.length];

        dstu7624xts = new KXTSBlockCipher(new DSTU7624Engine(256));
        param = new ParametersWithIV(new KeyParameter(key), iv);

        dstu7624xts.init(true, param);
        len = dstu7624xts.processBytes(plainText, 0, plainText.length, output, 0);
        dstu7624xts.doFinal(output, len);

        if (!Arrays.areEqual(output, expectedCipherText))
        {
            fail("Failed XTS encrypt test 3 - expected "
                + Hex.toHexString(expectedCipherText)
                + " got " + Hex.toHexString(output));
        }

        dstu7624xts.init(false, param);
        len = dstu7624xts.processBytes(expectedCipherText, 0, expectedCipherText.length, decrypted, 0);
        dstu7624xts.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed XTS decrypt test 3 - expected "
                + Hex.toHexString(plainText)
                + " got " + Hex.toHexString(decrypted));
        }

        //test 4
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");
        iv = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");
        plainText = Hex.decode("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF");

        output = new byte[plainText.length];
        expectedCipherText = Hex.decode("30663E4686574B343A1898E46973CD37DB9D775D356512EB59E723397F2A333CE2C0E96538781FF48EA1D93BDF88FFF8BB7BC4FB80A609881220C7FE21881C7374F65B232A8F94CD0E3DDC7614830C23CFCE98ADC5113496F9E106E8C8BFF3AB");

        decrypted = new byte[plainText.length];

        dstu7624xts = new KXTSBlockCipher(new DSTU7624Engine(256));
        param = new ParametersWithIV(new KeyParameter(key), iv);

        dstu7624xts.init(true, param);
        len = dstu7624xts.processBytes(plainText, 0, plainText.length, output, 0);
        dstu7624xts.doFinal(output, len);

        if (!Arrays.areEqual(output, expectedCipherText))
        {
            fail("Failed XTS encrypt test 4 - expected "
                + Hex.toHexString(expectedCipherText)
                + " got " + Hex.toHexString(output));
        }


        dstu7624xts.init(false, param);
        len = dstu7624xts.processBytes(expectedCipherText, 0, expectedCipherText.length, decrypted, 0);
        dstu7624xts.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed XTS decrypt test 4 - expected "
                + Hex.toHexString(plainText)
                + " got " + Hex.toHexString(decrypted));
        }

        //test 5
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");
        iv = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F");
        plainText = Hex.decode("808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF");

        output = new byte[plainText.length];
        expectedCipherText = Hex.decode("5C6250BD2E40AAE27E1E57512CD38E6A51D0C2B04F0D6A50E0CB43358B8C4E8BA361331436C6FFD38D77BBBBF5FEC56A234108A6CC8CB298360943E849E5BD64D26ECA2FA8AEAD070656C3777BA412BCAF3D2F08C26CF86CA8F0921043A15D709AE1112611E22D4396E582CCB661E0F778B6F38561BC338AFD5D1036ED8B322D");

        decrypted = new byte[plainText.length];

        dstu7624xts = new KXTSBlockCipher(new DSTU7624Engine(512));
        param = new ParametersWithIV(new KeyParameter(key), iv);

        dstu7624xts.init(true, param);
        len = dstu7624xts.processBytes(plainText, 0, plainText.length, output, 0);
        dstu7624xts.doFinal(output, len);

        if (!Arrays.areEqual(output, expectedCipherText))
        {
            fail("Failed XTS encrypt test 5 - expected "
                + Hex.toHexString(expectedCipherText)
                + " got " + Hex.toHexString(output));
        }


        dstu7624xts.init(false, param);
        len = dstu7624xts.processBytes(expectedCipherText, 0, expectedCipherText.length, decrypted, 0);
        dstu7624xts.doFinal(decrypted, len);

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed XTS decrypt test 5 - expected "
                + Hex.toHexString(plainText)
                + " got " + Hex.toHexString(decrypted));
        }
    }

    private void GCMModeTests()
        throws Exception
    {
        //test 1
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");

        byte[] iv = Hex.decode("101112131415161718191A1B1C1D1E1F");

        byte[] authText = Hex.decode("202122232425262728292A2B2C2D2E2F");

        byte[] plainText = Hex.decode("303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F");

        byte[] expectedEncrypted = Hex.decode("B91A7B8790BBCFCFE65D04E5538E98E216AC209DA33122FDA596E8928070BE51");

        byte[] expectedMac = Hex.decode("C8310571CD60F9584B45C1B4ECE179AF");

        byte[] expectedOutput = new byte[expectedEncrypted.length + expectedMac.length];
        System.arraycopy(expectedEncrypted, 0, expectedOutput, 0, expectedEncrypted.length);
        System.arraycopy(expectedMac, 0, expectedOutput, expectedEncrypted.length, expectedMac.length);

        byte[] mac = new byte[expectedMac.length];

        byte[] encrypted = new byte[expectedEncrypted.length + mac.length];

        byte[] decrypted = new byte[plainText.length + mac.length];

        System.arraycopy(expectedMac, 0, decrypted, plainText.length, mac.length);

        int len;

        AEADParameters parameters = new AEADParameters(new KeyParameter(key), 128, iv);

        KGCMBlockCipher dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(128));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);

        len = dstu7624gcm.processBytes(plainText, 0, plainText.length, encrypted, 0);
        dstu7624gcm.doFinal(encrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 1 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedOutput))
        {
            fail("Failed GCM/GMAC test 1 - expected encrypted: "
                + Hex.toHexString(expectedOutput)
                + " got encrypted: " + Hex.toHexString(encrypted));
        }


        dstu7624gcm.init(false, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);

        len = dstu7624gcm.processBytes(expectedOutput, 0, expectedOutput.length, decrypted, 0);
        dstu7624gcm.doFinal(decrypted, len);


        mac = dstu7624gcm.getMac();
        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 1 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //remove mac at the end of decrypted data
        byte[] tempDecrypted = new byte[plainText.length];
        System.arraycopy(decrypted, 0, tempDecrypted, 0, plainText.length);
        decrypted = tempDecrypted;


        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed GCM/GMAC test 1 - expected decrypted: "
                + Hex.toHexString(plainText)
                + " got decrypted: " + Hex.toHexString(decrypted));
        }

        //test 2
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");

        iv = Hex.decode("202122232425262728292A2B2C2D2E2F");

        authText = Hex.decode("303132333435363738393A3B3C3D3E3F");

        plainText = Hex.decode("505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F");

        expectedEncrypted = Hex.decode("FF83F27C6D4EA26101B1986235831406A297940D6C0E695596D612623E0E7CDC");

        expectedMac = Hex.decode("3C474281AFEAE4FD6D61E995258747AB");

        expectedOutput = new byte[expectedEncrypted.length + expectedMac.length];
        System.arraycopy(expectedEncrypted, 0, expectedOutput, 0, expectedEncrypted.length);
        System.arraycopy(expectedMac, 0, expectedOutput, expectedEncrypted.length, expectedMac.length);


        mac = new byte[expectedMac.length];

        encrypted = new byte[expectedEncrypted.length + mac.length];

        decrypted = new byte[plainText.length + mac.length];

        System.arraycopy(expectedMac, 0, decrypted, plainText.length, mac.length);

        parameters = new AEADParameters(new KeyParameter(key), 128, iv);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(128));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(plainText, 0, plainText.length, encrypted, 0);

        dstu7624gcm.doFinal(encrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 2 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedOutput))
        {
            fail("Failed GCM/GMAC test 2 - expected encrypted: "
                + Hex.toHexString(expectedOutput)
                + " got encrypted: " + Hex.toHexString(encrypted));
        }


        dstu7624gcm.init(false, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(expectedOutput, 0, expectedOutput.length, decrypted, 0);

        dstu7624gcm.doFinal(decrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 2 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //remove mac at the end of decrypted data
        tempDecrypted = new byte[plainText.length];
        System.arraycopy(decrypted, 0, tempDecrypted, 0, plainText.length);
        decrypted = tempDecrypted;

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed GCM/GMAC test 2 - expected decrypted: "
                + Hex.toHexString(plainText)
                + " got decrypted: " + Hex.toHexString(decrypted));
        }

        //test 3
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");

        iv = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");

        authText = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");

        plainText = Hex.decode("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F");

        expectedEncrypted = Hex.decode("7EC15C54BB553CB1437BE0EFDD2E810F6058497EBCE4408A08A73FADF3F459D56B0103702D13AB73ACD2EB33A8B5E9CFFF5EB21865A6B499C10C810C4BAEBE80");

        expectedMac = Hex.decode("1D61B0A3018F6B849CBA20AF1DDDA245");

        expectedOutput = new byte[expectedEncrypted.length + expectedMac.length];
        System.arraycopy(expectedEncrypted, 0, expectedOutput, 0, expectedEncrypted.length);
        System.arraycopy(expectedMac, 0, expectedOutput, expectedEncrypted.length, expectedMac.length);


        mac = new byte[expectedMac.length];

        encrypted = new byte[expectedEncrypted.length + mac.length];

        decrypted = new byte[plainText.length + mac.length];

        System.arraycopy(expectedMac, 0, decrypted, plainText.length, mac.length);


        parameters = new AEADParameters(new KeyParameter(key), 128, iv);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(256));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(plainText, 0, plainText.length, encrypted, 0);

        dstu7624gcm.doFinal(encrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 3 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedOutput))
        {
            fail("Failed GCM/GMAC test 3 - expected encrypted: "
                + Hex.toHexString(expectedOutput)
                + " got encrypted: " + Hex.toHexString(encrypted));
        }

        dstu7624gcm.init(false, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(expectedOutput, 0, expectedOutput.length, decrypted, 0);

        dstu7624gcm.doFinal(decrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 3 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //remove mac at the end of decrypted data
        tempDecrypted = new byte[plainText.length];
        System.arraycopy(decrypted, 0, tempDecrypted, 0, plainText.length);
        decrypted = tempDecrypted;

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed GCM/GMAC test 3 - expected decrypted: "
                + Hex.toHexString(plainText)
                + " got decrypted: " + Hex.toHexString(decrypted));
        }

        //test 4
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");

        iv = Hex.decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");

        authText = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");

        plainText = Hex.decode("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F");

        expectedEncrypted = Hex.decode("7EC15C54BB553CB1437BE0EFDD2E810F6058497EBCE4408A08A73FADF3F459D56B0103702D13AB73ACD2EB33A8B5E9CFFF5EB21865A6B499C10C810C4BAEBE80");

        expectedMac = Hex.decode("1D61B0A3018F6B849CBA20AF1DDDA245B1B296258AC0352A52D3F372E72224CE");

        expectedOutput = new byte[expectedEncrypted.length + expectedMac.length];
        System.arraycopy(expectedEncrypted, 0, expectedOutput, 0, expectedEncrypted.length);
        System.arraycopy(expectedMac, 0, expectedOutput, expectedEncrypted.length, expectedMac.length);


        mac = new byte[expectedMac.length];

        encrypted = new byte[expectedEncrypted.length + mac.length];

        decrypted = new byte[plainText.length + mac.length];

        System.arraycopy(expectedMac, 0, decrypted, plainText.length, mac.length);

        parameters = new AEADParameters(new KeyParameter(key), 256, iv);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(256));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(plainText, 0, plainText.length, encrypted, 0);

        dstu7624gcm.doFinal(encrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 4 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedOutput))
        {
            fail("Failed GCM/GMAC test 4 - expected encrypted: "
                + Hex.toHexString(expectedOutput)
                + " got encrypted: " + Hex.toHexString(encrypted));
        }


        dstu7624gcm.init(false, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(expectedOutput, 0, expectedOutput.length, decrypted, 0);

        dstu7624gcm.doFinal(decrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 4 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //remove mac at the end of decrypted data
        tempDecrypted = new byte[plainText.length];
        System.arraycopy(decrypted, 0, tempDecrypted, 0, plainText.length);
        decrypted = tempDecrypted;

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed GCM/GMAC test 4 - expected decrypted: "
                + Hex.toHexString(plainText)
                + " got decrypted: " + Hex.toHexString(decrypted));
        }

        //test 5
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");

        iv = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");

        authText = Hex.decode("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F");

        plainText = Hex.decode("808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF");

        expectedEncrypted = Hex.decode("3EBDB4584B5169A26FBEBA0295B4223F58D5D8A031F2950A1D7764FAB97BA058E9E2DAB90FF0C519AA88435155A71B7B53BB100F5D20AFFAC0552F5F2813DEE8");

        expectedMac = Hex.decode("8555FD3D9B02C2325ACA3CC9309D6B4B9AFC697D13BBBFF067198D5D86CB9820");

        expectedOutput = new byte[expectedEncrypted.length + expectedMac.length];
        System.arraycopy(expectedEncrypted, 0, expectedOutput, 0, expectedEncrypted.length);
        System.arraycopy(expectedMac, 0, expectedOutput, expectedEncrypted.length, expectedMac.length);


        mac = new byte[expectedMac.length];

        encrypted = new byte[expectedEncrypted.length + mac.length];

        decrypted = new byte[plainText.length + mac.length];

        System.arraycopy(expectedMac, 0, decrypted, plainText.length, mac.length);


        parameters = new AEADParameters(new KeyParameter(key), 256, iv);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(256));
        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(plainText, 0, plainText.length, encrypted, 0);

        dstu7624gcm.doFinal(encrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 5 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedOutput))
        {
            fail("Failed GCM/GMAC test 5 - expected encrypted: "
                + Hex.toHexString(expectedOutput)
                + " got encrypted: " + Hex.toHexString(encrypted));
        }


        dstu7624gcm.init(false, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(expectedOutput, 0, expectedOutput.length, decrypted, 0);

        dstu7624gcm.doFinal(decrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 5 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //remove mac at the end of decrypted data
        tempDecrypted = new byte[plainText.length];
        System.arraycopy(decrypted, 0, tempDecrypted, 0, plainText.length);
        decrypted = tempDecrypted;

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed GCM/GMAC test 5 - expected decrypted: "
                + Hex.toHexString(plainText)
                + " got decrypted: " + Hex.toHexString(decrypted));
        }

        //test 6
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");

        iv = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F");

        authText = Hex.decode("808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF");

        plainText = Hex.decode("C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF");

        expectedEncrypted = Hex.decode("220642D7277D104788CF97B10210984F506435512F7BF153C5CDABFECC10AFB4A2E2FC51F616AF80FFDD0607FAD4F542B8EF0667717CE3EAAA8FBC303CE76C99");

        expectedMac = Hex.decode("78A77E5948F5DC05F551486FDBB44898C9AB1BD439D7519841AE31007C09E1B312E5EA5929F952F6A3EEF5CBEAEF262B8EC1884DFCF4BAAF7B5C9291A22489E1");

        expectedOutput = new byte[expectedEncrypted.length + expectedMac.length];
        System.arraycopy(expectedEncrypted, 0, expectedOutput, 0, expectedEncrypted.length);
        System.arraycopy(expectedMac, 0, expectedOutput, expectedEncrypted.length, expectedMac.length);


        mac = new byte[expectedMac.length];

        encrypted = new byte[expectedEncrypted.length + mac.length];

        decrypted = new byte[plainText.length + mac.length];

        System.arraycopy(expectedMac, 0, decrypted, plainText.length, mac.length);

        parameters = new AEADParameters(new KeyParameter(key), 512, iv);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(512));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(plainText, 0, plainText.length, encrypted, 0);

        dstu7624gcm.doFinal(encrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 6 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        if (!Arrays.areEqual(encrypted, expectedOutput))
        {
            fail("Failed GCM/GMAC test 6 - expected encrypted: "
                + Hex.toHexString(expectedOutput)
                + " got encrypted: " + Hex.toHexString(encrypted));
        }

        dstu7624gcm.init(false, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        len = dstu7624gcm.processBytes(expectedOutput, 0, expectedOutput.length, decrypted, 0);

        dstu7624gcm.doFinal(decrypted, len);

        mac = dstu7624gcm.getMac();

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 6 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //remove mac at the end of decrypted data
        tempDecrypted = new byte[plainText.length];
        System.arraycopy(decrypted, 0, tempDecrypted, 0, plainText.length);
        decrypted = tempDecrypted;

        if (!Arrays.areEqual(decrypted, plainText))
        {
            fail("Failed GCM/GMAC test 6 - expected decrypted: "
                + Hex.toHexString(plainText)
                + " got decrypted: " + Hex.toHexString(decrypted));
        }

        /* Testing mac producing without encryption */
        //test 7
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");

        authText = Hex.decode("303132333435363738393A3B3C3D3E3F");

        expectedMac = Hex.decode("5AE309EE80B583C6523397ADCB5704C4");

        mac = new byte[expectedMac.length];

        parameters = new AEADParameters(new KeyParameter(key), 128, new byte[16]);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(128));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        dstu7624gcm.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 7 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //test 8
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");

        authText = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");

        expectedMac = Hex.decode("FF48B56F2C26CC484B8F5952D7B3E1FE");

        mac = new byte[expectedMac.length];

        parameters = new AEADParameters(new KeyParameter(key), 128, new byte[16]);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(256));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        dstu7624gcm.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 8 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //test 9
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");

        authText = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");

        expectedMac = Hex.decode("FF48B56F2C26CC484B8F5952D7B3E1FE69577701C50BE96517B33921E44634CD");

        mac = new byte[expectedMac.length];

        parameters = new AEADParameters(new KeyParameter(key), 256, new byte[32]);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(256));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        dstu7624gcm.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 9 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //test 10
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");

        authText = Hex.decode("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F");

        expectedMac = Hex.decode("96F61FA0FDE92883C5041D748F9AE91F3A0A50415BFA1466855340A5714DC01F");

        mac = new byte[expectedMac.length];

        parameters = new AEADParameters(new KeyParameter(key), 256, new byte[32]);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(256));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        dstu7624gcm.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 10 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        //test 11
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");

        authText = Hex.decode("808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF");

        expectedMac = Hex.decode("897C32E05E776FD988C5171FE70BB72949172E514E3308A871BA5BD898FB6EBD6E3897D2D55697D90D6428216C08052E3A5E7D4626F4DBBF1546CE21637357A3");

        mac = new byte[expectedMac.length];

        parameters = new AEADParameters(new KeyParameter(key), 512, new byte[32]);

        dstu7624gcm = new KGCMBlockCipher(new DSTU7624Engine(512));

        dstu7624gcm.init(true, parameters);
        dstu7624gcm.processAADBytes(authText, 0, authText.length);
        dstu7624gcm.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 11 - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        doFinalTest(new KGCMBlockCipher(new DSTU7624Engine(512)), key, new byte[32], authText, null, expectedMac);

        //test 11 - as KGMac
        key = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F");

        authText = Hex.decode("808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF");

        expectedMac = Hex.decode("897C32E05E776FD988C5171FE70BB72949172E514E3308A871BA5BD898FB6EBD6E3897D2D55697D90D6428216C08052E3A5E7D4626F4DBBF1546CE21637357A3");

        mac = new byte[expectedMac.length];

        KGMac dstuGmac = new KGMac(new KGCMBlockCipher(new DSTU7624Engine(512)));

        dstuGmac.init(new ParametersWithIV(new KeyParameter(key), new byte[32]));

        dstuGmac.update(authText, 0, authText.length);

        dstuGmac.doFinal(mac, 0);

        if (!Arrays.areEqual(mac, expectedMac))
        {
            fail("Failed GCM/GMAC test 11 (mac) - expected mac: "
                + Hex.toHexString(expectedMac)
                + " got mac: " + Hex.toHexString(mac));
        }

        KGMacPartialBlockTests();
        KGCMNonceReuseTests();
    }

    /*
     * Regression test for the KGMac (KGCM/GMAC) partial-block path. A message whose length is not a
     * multiple of the block size used to be authenticated by reading the trailing partial block out
     * of the backing buffer, whose bytes past the message length are not zeroed on reset(); the MAC
     * therefore depended on the instance's history. After zero-padding the trailing block it must be
     * deterministic regardless of what the instance processed previously. DSTU 7624:2014 publishes no
     * partial-block GMAC vector, so this is a self-consistency check only -- see github #287 and the
     * interop caveat on KGCMBlockCipher.
     */
    private void KGMacPartialBlockTests()
        throws Exception
    {
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] iv = Hex.decode("101112131415161718191A1B1C1D1E1F");
        ParametersWithIV param = new ParametersWithIV(new KeyParameter(key), iv);

        // 20 bytes against a 16-byte block: one full block plus a 4-byte partial block
        byte[] message = Hex.decode("303132333435363738393A3B3C3D3E3F40414243");

        // a fresh instance that only ever sees the partial message
        KGMac fresh = new KGMac(new KGCMBlockCipher(new DSTU7624Engine(128)));
        fresh.init(param);
        fresh.update(message, 0, message.length);
        byte[] freshMac = new byte[fresh.getMacSize()];
        fresh.doFinal(freshMac, 0);

        // a reused instance: process a longer message, reset, then the same partial message
        KGMac reused = new KGMac(new KGCMBlockCipher(new DSTU7624Engine(128)));
        reused.init(param);
        byte[] filler = new byte[48];
        Arrays.fill(filler, (byte)0xFF);
        reused.update(filler, 0, filler.length);
        byte[] discard = new byte[reused.getMacSize()];
        reused.doFinal(discard, 0);
        reused.reset();
        reused.update(message, 0, message.length);
        byte[] reusedMac = new byte[reused.getMacSize()];
        reused.doFinal(reusedMac, 0);

        if (!Arrays.areEqual(freshMac, reusedMac))
        {
            fail("Failed KGMac partial-block determinism - fresh "
                + Hex.toHexString(freshMac)
                + " but reused " + Hex.toHexString(reusedMac));
        }
    }

    /*
     * Re-initialising for encryption with the same key and nonce is rejected (a repeated key+nonce
     * is catastrophic for any GCM-family mode), matching GCMBlockCipher. reset()-based reuse,
     * a fresh nonce, and re-init for decryption are all still allowed.
     */
    private void KGCMNonceReuseTests()
        throws Exception
    {
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] nonce = Hex.decode("101112131415161718191A1B1C1D1E1F");
        byte[] nonce2 = Hex.decode("202122232425262728292A2B2C2D2E2F");

        KGCMBlockCipher c = new KGCMBlockCipher(new DSTU7624Engine(128));
        c.init(true, new AEADParameters(new KeyParameter(key), 128, nonce));
        kgcmEncryptBlock(c);

        // reset()-based reuse must still work (it does not re-init)
        c.reset();
        kgcmEncryptBlock(c);

        // re-init for encryption with the same key+nonce must be rejected
        try
        {
            c.init(true, new AEADParameters(new KeyParameter(key), 128, nonce));
            fail("KGCM nonce reuse not detected on re-init for encryption");
        }
        catch (IllegalArgumentException e)
        {
            isTrue("wrong KGCM nonce-reuse message: " + e.getMessage(),
                "cannot reuse nonce for KGCM encryption".equals(e.getMessage()));
        }

        // a different nonce is fine
        c.init(true, new AEADParameters(new KeyParameter(key), 128, nonce2));

        // re-init for decryption with the same key+nonce is allowed (the guard is encrypt-only)
        KGCMBlockCipher d = new KGCMBlockCipher(new DSTU7624Engine(128));
        d.init(true, new AEADParameters(new KeyParameter(key), 128, nonce));
        d.init(false, new AEADParameters(new KeyParameter(key), 128, nonce));
    }

    private void kgcmEncryptBlock(KGCMBlockCipher cipher)
        throws Exception
    {
        byte[] in = new byte[16];
        byte[] out = new byte[cipher.getOutputSize(in.length)];
        int len = cipher.processBytes(in, 0, in.length, out, 0);
        cipher.doFinal(out, len);
    }

    /*
     * Re-initialising for encryption with the same key and nonce is rejected (a repeated key+nonce
     * is catastrophic for the CCM construction), matching GCMBlockCipher / KGCMBlockCipher.
     * reset()-based reuse, a fresh nonce, and re-init for decryption are all still allowed.
     */
    private void KCCMNonceReuseTests()
        throws Exception
    {
        byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F");
        byte[] nonce = Hex.decode("101112131415161718191A1B1C1D1E1F");
        byte[] nonce2 = Hex.decode("202122232425262728292A2B2C2D2E2F");

        KCCMBlockCipher c = new KCCMBlockCipher(new DSTU7624Engine(128));
        c.init(true, new AEADParameters(new KeyParameter(key), 128, nonce));
        kccmEncryptBlock(c);

        // reset()-based reuse must still work (it does not re-init)
        c.reset();
        kccmEncryptBlock(c);

        // re-init for encryption with the same key+nonce must be rejected
        try
        {
            c.init(true, new AEADParameters(new KeyParameter(key), 128, nonce));
            fail("KCCM nonce reuse not detected on re-init for encryption");
        }
        catch (IllegalArgumentException e)
        {
            isTrue("wrong KCCM nonce-reuse message: " + e.getMessage(),
                "cannot reuse nonce for KCCM encryption".equals(e.getMessage()));
        }

        // a different nonce is fine
        c.init(true, new AEADParameters(new KeyParameter(key), 128, nonce2));

        // re-init for decryption with the same key+nonce is allowed (the guard is encrypt-only)
        KCCMBlockCipher d = new KCCMBlockCipher(new DSTU7624Engine(128));
        d.init(true, new AEADParameters(new KeyParameter(key), 128, nonce));
        d.init(false, new AEADParameters(new KeyParameter(key), 128, nonce));
    }

    private void kccmEncryptBlock(KCCMBlockCipher cipher)
        throws Exception
    {
        byte[] in = new byte[16];
        byte[] out = new byte[cipher.getOutputSize(in.length)];
        int len = cipher.processBytes(in, 0, in.length, out, 0);
        cipher.doFinal(out, len);
    }

    private void doFinalTest(AEADBlockCipher cipher, byte[] key, byte[] iv, byte[] authText, byte[] input, byte[] expected)
        throws Exception
    {
        byte[] output = new byte[expected.length];

        AEADParameters parameters = new AEADParameters(new KeyParameter(key), cipher.getUnderlyingCipher().getBlockSize() * 8, iv);

        cipher.init(true, parameters);
        cipher.processAADBytes(authText, 0, authText.length);

        int off = 0;
        if (input != null)
        {
            off = cipher.processBytes(input, 0, input.length, output, 0);
        }

        cipher.doFinal(output, off);

        if (!Arrays.areEqual(output, expected))
        {
            System.err.println(Hex.toHexString(output));
            System.err.println(Hex.toHexString(expected));
            fail("Failed doFinal test - init: " + cipher.getAlgorithmName());
        }

        cipher.processAADBytes(authText, 0, authText.length);

        off = 0;
        if (input != null)
        {
            off = cipher.processBytes(input, 0, input.length, output, 0);
        }

        cipher.doFinal(output, off);

        if (!Arrays.areEqual(output, expected))
        {
            fail("Failed doFinal test - after: " + cipher.getAlgorithmName());
        }
    }

    private void testOverlapping()
    {
        SecureRandom random = new SecureRandom();
        byte[] keyBytes = new byte[16];
        byte[] iv = new byte[16];
        random.nextBytes(keyBytes);
        KXTSBlockCipher bc = new KXTSBlockCipher(new DSTU7624Engine(128));
        ParametersWithIV param = new ParametersWithIV(new KeyParameter(keyBytes), iv);

        int offset = 1 + random.nextInt(bc.getBlockSize() - 1) + bc.getBlockSize();
        byte[] data = new byte[bc.getBlockSize() * 4 + offset];
        byte[] expected = new byte[bc.getOutputSize(bc.getBlockSize() * 3)];
        random.nextBytes(data);

        bc.init(true, param);
        int len = bc.processBytes(data, 0, expected.length, expected, 0);
        bc.doFinal(expected, len);
        bc.init(true, param);
        len = bc.processBytes(data, 0, expected.length, data, offset);
        bc.doFinal(data, offset + len);

        if (!areEqual(expected, Arrays.copyOfRange(data, offset, offset + expected.length)))
        {
            fail("failed for overlapping encryption");
        }

        bc.init(false, param);
        bc.processBytes(data, 0, expected.length, expected, 0);
        bc.init(false, param);
        bc.processBytes(data, 0, expected.length, data, offset);

        if (!areEqual(expected, Arrays.copyOfRange(data, offset, offset + expected.length)))
        {
            fail("failed for overlapping decryption");
        }
    }
}
