| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
package com.sun.crypto.provider;  | 
 | 
 | 
 | 
import java.util.Arrays;  | 
 | 
import java.io.*;  | 
 | 
import java.security.*;  | 
 | 
import javax.crypto.*;  | 
 | 
import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;  | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
final class GaloisCounterMode extends FeedbackCipher { | 
 | 
 | 
 | 
    static int DEFAULT_TAG_LEN = AES_BLOCK_SIZE;  | 
 | 
    static int DEFAULT_IV_LEN = 12;   | 
 | 
 | 
 | 
    // In NIST SP 800-38D, GCM input size is limited to be no longer  | 
 | 
    // than (2^36 - 32) bytes. Otherwise, the counter will wrap  | 
 | 
    // around and lead to a leak of plaintext.  | 
 | 
    // However, given the current GCM spec requirement that recovered  | 
 | 
    // text can only be returned after successful tag verification,  | 
 | 
    // we are bound by limiting the data size to the size limit of  | 
 | 
    // java byte array, e.g. Integer.MAX_VALUE, since all data  | 
 | 
      | 
 | 
    private static final int MAX_BUF_SIZE = Integer.MAX_VALUE;  | 
 | 
 | 
 | 
    // data size when buffer is divided up to aid in intrinsics  | 
 | 
    private static final int TRIGGERLEN = 65536;    | 
 | 
 | 
 | 
      | 
 | 
    private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();  | 
 | 
    private int sizeOfAAD = 0;  | 
 | 
 | 
 | 
      | 
 | 
    private ByteArrayOutputStream ibuffer = null;  | 
 | 
 | 
 | 
      | 
 | 
    private int tagLenBytes = DEFAULT_TAG_LEN;  | 
 | 
 | 
 | 
    // these following 2 fields can only be initialized after init() is  | 
 | 
      | 
 | 
    private byte[] subkeyH = null;  | 
 | 
    private byte[] preCounterBlock = null;  | 
 | 
 | 
 | 
    private GCTR gctrPAndC = null;  | 
 | 
    private GHASH ghashAllToS = null;  | 
 | 
 | 
 | 
      | 
 | 
    private int processed = 0;  | 
 | 
 | 
 | 
      | 
 | 
    private byte[] aadBufferSave = null;  | 
 | 
    private int sizeOfAADSave = 0;  | 
 | 
    private byte[] ibufferSave = null;  | 
 | 
    private int processedSave = 0;  | 
 | 
 | 
 | 
      | 
 | 
    static void increment32(byte[] value) { | 
 | 
        if (value.length != AES_BLOCK_SIZE) { | 
 | 
              | 
 | 
            throw new ProviderException("Illegal counter block length"); | 
 | 
        }  | 
 | 
          | 
 | 
        int n = value.length - 1;  | 
 | 
        while ((n >= value.length - 4) && (++value[n] == 0)) { | 
 | 
            n--;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private static byte[] getLengthBlock(int ivLenInBytes) { | 
 | 
        long ivLen = ((long)ivLenInBytes) << 3;  | 
 | 
        byte[] out = new byte[AES_BLOCK_SIZE];  | 
 | 
        out[8] = (byte)(ivLen >>> 56);  | 
 | 
        out[9] = (byte)(ivLen >>> 48);  | 
 | 
        out[10] = (byte)(ivLen >>> 40);  | 
 | 
        out[11] = (byte)(ivLen >>> 32);  | 
 | 
        out[12] = (byte)(ivLen >>> 24);  | 
 | 
        out[13] = (byte)(ivLen >>> 16);  | 
 | 
        out[14] = (byte)(ivLen >>> 8);  | 
 | 
        out[15] = (byte)ivLen;  | 
 | 
        return out;  | 
 | 
    }  | 
 | 
 | 
 | 
    private static byte[] getLengthBlock(int aLenInBytes, int cLenInBytes) { | 
 | 
        long aLen = ((long)aLenInBytes) << 3;  | 
 | 
        long cLen = ((long)cLenInBytes) << 3;  | 
 | 
        byte[] out = new byte[AES_BLOCK_SIZE];  | 
 | 
        out[0] = (byte)(aLen >>> 56);  | 
 | 
        out[1] = (byte)(aLen >>> 48);  | 
 | 
        out[2] = (byte)(aLen >>> 40);  | 
 | 
        out[3] = (byte)(aLen >>> 32);  | 
 | 
        out[4] = (byte)(aLen >>> 24);  | 
 | 
        out[5] = (byte)(aLen >>> 16);  | 
 | 
        out[6] = (byte)(aLen >>> 8);  | 
 | 
        out[7] = (byte)aLen;  | 
 | 
        out[8] = (byte)(cLen >>> 56);  | 
 | 
        out[9] = (byte)(cLen >>> 48);  | 
 | 
        out[10] = (byte)(cLen >>> 40);  | 
 | 
        out[11] = (byte)(cLen >>> 32);  | 
 | 
        out[12] = (byte)(cLen >>> 24);  | 
 | 
        out[13] = (byte)(cLen >>> 16);  | 
 | 
        out[14] = (byte)(cLen >>> 8);  | 
 | 
        out[15] = (byte)cLen;  | 
 | 
        return out;  | 
 | 
    }  | 
 | 
 | 
 | 
    private static byte[] expandToOneBlock(byte[] in, int inOfs, int len) { | 
 | 
        if (len > AES_BLOCK_SIZE) { | 
 | 
            throw new ProviderException("input " + len + " too long"); | 
 | 
        }  | 
 | 
        if (len == AES_BLOCK_SIZE && inOfs == 0) { | 
 | 
            return in;  | 
 | 
        } else { | 
 | 
            byte[] paddedIn = new byte[AES_BLOCK_SIZE];  | 
 | 
            System.arraycopy(in, inOfs, paddedIn, 0, len);  | 
 | 
            return paddedIn;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private static byte[] getJ0(byte[] iv, byte[] subkeyH) { | 
 | 
        byte[] j0;  | 
 | 
        if (iv.length == 12) {  | 
 | 
            j0 = expandToOneBlock(iv, 0, iv.length);  | 
 | 
            j0[AES_BLOCK_SIZE - 1] = 1;  | 
 | 
        } else { | 
 | 
            GHASH g = new GHASH(subkeyH);  | 
 | 
            int lastLen = iv.length % AES_BLOCK_SIZE;  | 
 | 
            if (lastLen != 0) { | 
 | 
                g.update(iv, 0, iv.length - lastLen);  | 
 | 
                byte[] padded =  | 
 | 
                    expandToOneBlock(iv, iv.length - lastLen, lastLen);  | 
 | 
                g.update(padded);  | 
 | 
            } else { | 
 | 
                g.update(iv);  | 
 | 
            }  | 
 | 
            byte[] lengthBlock = getLengthBlock(iv.length);  | 
 | 
            g.update(lengthBlock);  | 
 | 
            j0 = g.digest();  | 
 | 
        }  | 
 | 
        return j0;  | 
 | 
    }  | 
 | 
 | 
 | 
    private static void checkDataLength(int processed, int len) { | 
 | 
        if (processed > MAX_BUF_SIZE - len) { | 
 | 
            throw new ProviderException("SunJCE provider only supports " + | 
 | 
                "input size up to " + MAX_BUF_SIZE + " bytes");  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    GaloisCounterMode(SymmetricCipher embeddedCipher) { | 
 | 
        super(embeddedCipher);  | 
 | 
        aadBuffer = new ByteArrayOutputStream();  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    String getFeedback() { | 
 | 
        return "GCM";  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    void reset() { | 
 | 
        if (aadBuffer == null) { | 
 | 
            aadBuffer = new ByteArrayOutputStream();  | 
 | 
        } else { | 
 | 
            aadBuffer.reset();  | 
 | 
        }  | 
 | 
        if (gctrPAndC != null) gctrPAndC.reset();  | 
 | 
        if (ghashAllToS != null) ghashAllToS.reset();  | 
 | 
        processed = 0;  | 
 | 
        sizeOfAAD = 0;  | 
 | 
        if (ibuffer != null) { | 
 | 
            ibuffer.reset();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    void save() { | 
 | 
        processedSave = processed;  | 
 | 
        sizeOfAADSave = sizeOfAAD;  | 
 | 
        aadBufferSave =  | 
 | 
            ((aadBuffer == null || aadBuffer.size() == 0)?  | 
 | 
             null : aadBuffer.toByteArray());  | 
 | 
        if (gctrPAndC != null) gctrPAndC.save();  | 
 | 
        if (ghashAllToS != null) ghashAllToS.save();  | 
 | 
        if (ibuffer != null) { | 
 | 
            ibufferSave = ibuffer.toByteArray();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    void restore() { | 
 | 
        processed = processedSave;  | 
 | 
        sizeOfAAD = sizeOfAADSave;  | 
 | 
        if (aadBuffer != null) { | 
 | 
            aadBuffer.reset();  | 
 | 
            if (aadBufferSave != null) { | 
 | 
                aadBuffer.write(aadBufferSave, 0, aadBufferSave.length);  | 
 | 
            }  | 
 | 
        }  | 
 | 
        if (gctrPAndC != null) gctrPAndC.restore();  | 
 | 
        if (ghashAllToS != null) ghashAllToS.restore();  | 
 | 
        if (ibuffer != null) { | 
 | 
            ibuffer.reset();  | 
 | 
            ibuffer.write(ibufferSave, 0, ibufferSave.length);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    @Override  | 
 | 
    void init(boolean decrypting, String algorithm, byte[] key, byte[] iv)  | 
 | 
            throws InvalidKeyException, InvalidAlgorithmParameterException { | 
 | 
        init(decrypting, algorithm, key, iv, DEFAULT_TAG_LEN);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    void init(boolean decrypting, String algorithm, byte[] keyValue,  | 
 | 
              byte[] ivValue, int tagLenBytes)  | 
 | 
              throws InvalidKeyException, InvalidAlgorithmParameterException { | 
 | 
        if (keyValue == null) { | 
 | 
            throw new InvalidKeyException("Internal error"); | 
 | 
        }  | 
 | 
        if (ivValue == null) { | 
 | 
            throw new InvalidAlgorithmParameterException("Internal error"); | 
 | 
        }  | 
 | 
        if (ivValue.length == 0) { | 
 | 
            throw new InvalidAlgorithmParameterException("IV is empty"); | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        this.embeddedCipher.init(false, algorithm, keyValue);  | 
 | 
        this.subkeyH = new byte[AES_BLOCK_SIZE];  | 
 | 
        this.embeddedCipher.encryptBlock(new byte[AES_BLOCK_SIZE], 0,  | 
 | 
                this.subkeyH, 0);  | 
 | 
 | 
 | 
        this.iv = ivValue.clone();  | 
 | 
        preCounterBlock = getJ0(iv, subkeyH);  | 
 | 
        byte[] j0Plus1 = preCounterBlock.clone();  | 
 | 
        increment32(j0Plus1);  | 
 | 
        gctrPAndC = new GCTR(embeddedCipher, j0Plus1);  | 
 | 
        ghashAllToS = new GHASH(subkeyH);  | 
 | 
 | 
 | 
        this.tagLenBytes = tagLenBytes;  | 
 | 
        if (aadBuffer == null) { | 
 | 
            aadBuffer = new ByteArrayOutputStream();  | 
 | 
        } else { | 
 | 
            aadBuffer.reset();  | 
 | 
        }  | 
 | 
        processed = 0;  | 
 | 
        sizeOfAAD = 0;  | 
 | 
        if (decrypting) { | 
 | 
            ibuffer = new ByteArrayOutputStream();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    void updateAAD(byte[] src, int offset, int len) { | 
 | 
        if (aadBuffer != null) { | 
 | 
            aadBuffer.write(src, offset, len);  | 
 | 
        } else { | 
 | 
              | 
 | 
            throw new IllegalStateException  | 
 | 
                ("Update has been called; no more AAD data"); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
    void processAAD() { | 
 | 
        if (aadBuffer != null) { | 
 | 
            if (aadBuffer.size() > 0) { | 
 | 
                byte[] aad = aadBuffer.toByteArray();  | 
 | 
                sizeOfAAD = aad.length;  | 
 | 
 | 
 | 
                int lastLen = aad.length % AES_BLOCK_SIZE;  | 
 | 
                if (lastLen != 0) { | 
 | 
                    ghashAllToS.update(aad, 0, aad.length - lastLen);  | 
 | 
                    byte[] padded = expandToOneBlock(aad, aad.length - lastLen,  | 
 | 
                                                     lastLen);  | 
 | 
                    ghashAllToS.update(padded);  | 
 | 
                } else { | 
 | 
                    ghashAllToS.update(aad);  | 
 | 
                }  | 
 | 
            }  | 
 | 
            aadBuffer = null;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
    void doLastBlock(byte[] in, int inOfs, int len, byte[] out, int outOfs,  | 
 | 
                     boolean isEncrypt) throws IllegalBlockSizeException { | 
 | 
        byte[] ct;  | 
 | 
        int ctOfs;  | 
 | 
        int ilen = len;    | 
 | 
 | 
 | 
        if (isEncrypt) { | 
 | 
            ct = out;  | 
 | 
            ctOfs = outOfs;  | 
 | 
        } else { | 
 | 
            ct = in;  | 
 | 
            ctOfs = inOfs;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (len > TRIGGERLEN) { | 
 | 
            int i = 0;  | 
 | 
            int tlen;    | 
 | 
            final int plen = AES_BLOCK_SIZE * 6;  | 
 | 
              | 
 | 
            final int count = len / 1024;  | 
 | 
 | 
 | 
            while (count > i) { | 
 | 
                tlen = gctrPAndC.update(in, inOfs, plen, out, outOfs);  | 
 | 
                ghashAllToS.update(ct, ctOfs, tlen);  | 
 | 
                inOfs += tlen;  | 
 | 
                outOfs += tlen;  | 
 | 
                ctOfs += tlen;  | 
 | 
                i++;  | 
 | 
            }  | 
 | 
            ilen -= count * plen;  | 
 | 
            processed += count * plen;  | 
 | 
        }  | 
 | 
 | 
 | 
        gctrPAndC.doFinal(in, inOfs, ilen, out, outOfs);  | 
 | 
        processed += ilen;  | 
 | 
 | 
 | 
        int lastLen = ilen % AES_BLOCK_SIZE;  | 
 | 
        if (lastLen != 0) { | 
 | 
            ghashAllToS.update(ct, ctOfs, ilen - lastLen);  | 
 | 
            ghashAllToS.update(  | 
 | 
                    expandToOneBlock(ct, (ctOfs + ilen - lastLen), lastLen));  | 
 | 
        } else { | 
 | 
            ghashAllToS.update(ct, ctOfs, ilen);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) { | 
 | 
        checkDataLength(processed, len);  | 
 | 
 | 
 | 
        RangeUtil.blockSizeCheck(len, blockSize);  | 
 | 
        processAAD();  | 
 | 
 | 
 | 
        if (len > 0) { | 
 | 
            RangeUtil.nullAndBoundsCheck(in, inOfs, len);  | 
 | 
            RangeUtil.nullAndBoundsCheck(out, outOfs, len);  | 
 | 
 | 
 | 
            gctrPAndC.update(in, inOfs, len, out, outOfs);  | 
 | 
            processed += len;  | 
 | 
            ghashAllToS.update(out, outOfs, len);  | 
 | 
        }  | 
 | 
 | 
 | 
        return len;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)  | 
 | 
        throws IllegalBlockSizeException, ShortBufferException { | 
 | 
        if (len > MAX_BUF_SIZE - tagLenBytes) { | 
 | 
            throw new ShortBufferException  | 
 | 
                ("Can't fit both data and tag into one buffer"); | 
 | 
        }  | 
 | 
        try { | 
 | 
            RangeUtil.nullAndBoundsCheck(out, outOfs,  | 
 | 
                (len + tagLenBytes));  | 
 | 
        } catch (ArrayIndexOutOfBoundsException aiobe) { | 
 | 
            throw new ShortBufferException("Output buffer too small"); | 
 | 
        }  | 
 | 
 | 
 | 
        checkDataLength(processed, len);  | 
 | 
 | 
 | 
        processAAD();  | 
 | 
        if (len > 0) { | 
 | 
            RangeUtil.nullAndBoundsCheck(in, inOfs, len);  | 
 | 
 | 
 | 
            doLastBlock(in, inOfs, len, out, outOfs, true);  | 
 | 
        }  | 
 | 
 | 
 | 
        byte[] lengthBlock =  | 
 | 
            getLengthBlock(sizeOfAAD, processed);  | 
 | 
        ghashAllToS.update(lengthBlock);  | 
 | 
        byte[] s = ghashAllToS.digest();  | 
 | 
        byte[] sOut = new byte[s.length];  | 
 | 
        GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);  | 
 | 
        gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);  | 
 | 
 | 
 | 
        System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes);  | 
 | 
        return (len + tagLenBytes);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) { | 
 | 
        checkDataLength(ibuffer.size(), len);  | 
 | 
 | 
 | 
        RangeUtil.blockSizeCheck(len, blockSize);  | 
 | 
        processAAD();  | 
 | 
 | 
 | 
        if (len > 0) { | 
 | 
            // store internally until decryptFinal is called because  | 
 | 
            // spec mentioned that only return recovered data after tag  | 
 | 
              | 
 | 
            RangeUtil.nullAndBoundsCheck(in, inOfs, len);  | 
 | 
            ibuffer.write(in, inOfs, len);  | 
 | 
        }  | 
 | 
        return 0;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    int decryptFinal(byte[] in, int inOfs, int len,  | 
 | 
                     byte[] out, int outOfs)  | 
 | 
        throws IllegalBlockSizeException, AEADBadTagException,  | 
 | 
        ShortBufferException { | 
 | 
        if (len < tagLenBytes) { | 
 | 
            throw new AEADBadTagException("Input too short - need tag"); | 
 | 
        }  | 
 | 
 | 
 | 
        // do this check here can also catch the potential integer overflow  | 
 | 
          | 
 | 
        checkDataLength(ibuffer.size(), (len - tagLenBytes));  | 
 | 
 | 
 | 
        try { | 
 | 
            RangeUtil.nullAndBoundsCheck(out, outOfs,  | 
 | 
                (ibuffer.size() + len) - tagLenBytes);  | 
 | 
        } catch (ArrayIndexOutOfBoundsException aiobe) { | 
 | 
            throw new ShortBufferException("Output buffer too small"); | 
 | 
        }  | 
 | 
 | 
 | 
        processAAD();  | 
 | 
 | 
 | 
        RangeUtil.nullAndBoundsCheck(in, inOfs, len);  | 
 | 
 | 
 | 
          | 
 | 
        byte[] tag = new byte[tagLenBytes];  | 
 | 
        System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes);  | 
 | 
        len -= tagLenBytes;  | 
 | 
 | 
 | 
        // If decryption is in-place or there is buffered "ibuffer" data, copy  | 
 | 
          | 
 | 
        if (in == out || ibuffer.size() > 0) { | 
 | 
            if (len > 0) { | 
 | 
                ibuffer.write(in, inOfs, len);  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            in = ibuffer.toByteArray();  | 
 | 
            inOfs = 0;  | 
 | 
            len = in.length;  | 
 | 
            ibuffer.reset();  | 
 | 
        }  | 
 | 
 | 
 | 
        if (len > 0) { | 
 | 
            doLastBlock(in, inOfs, len, out, outOfs, false);  | 
 | 
        }  | 
 | 
 | 
 | 
        byte[] lengthBlock =  | 
 | 
            getLengthBlock(sizeOfAAD, processed);  | 
 | 
        ghashAllToS.update(lengthBlock);  | 
 | 
 | 
 | 
        byte[] s = ghashAllToS.digest();  | 
 | 
        byte[] sOut = new byte[s.length];  | 
 | 
        GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);  | 
 | 
        gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);  | 
 | 
 | 
 | 
          | 
 | 
        int mismatch = 0;  | 
 | 
        for (int i = 0; i < tagLenBytes; i++) { | 
 | 
            mismatch |= tag[i] ^ sOut[i];  | 
 | 
        }  | 
 | 
 | 
 | 
        if (mismatch != 0) { | 
 | 
            throw new AEADBadTagException("Tag mismatch!"); | 
 | 
        }  | 
 | 
 | 
 | 
        return len;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
    int getTagLen() { | 
 | 
        return this.tagLenBytes;  | 
 | 
    }  | 
 | 
 | 
 | 
    int getBufferedLength() { | 
 | 
        if (ibuffer == null) { | 
 | 
            return 0;  | 
 | 
        } else { | 
 | 
            return ibuffer.size();  | 
 | 
        }  | 
 | 
    }  | 
 | 
}  |