// 
// Decompiled by Procyon v0.6.0
// 

package com.google.crypto.tink.subtle;

import javax.crypto.AEADBadTagException;
import com.google.crypto.tink.internal.Util;
import java.security.spec.AlgorithmParameterSpec;
import java.security.Key;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import com.google.crypto.tink.mac.internal.AesUtil;
import java.util.Arrays;
import java.security.InvalidKeyException;
import com.google.crypto.tink.util.Bytes;
import com.google.crypto.tink.prf.AesCmacPrfKey;
import com.google.crypto.tink.util.SecretBytes;
import com.google.crypto.tink.prf.AesCmacPrfParameters;
import com.google.crypto.tink.AccessesPartialKey;
import java.security.GeneralSecurityException;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.daead.AesSivKey;
import javax.crypto.Cipher;
import com.google.crypto.tink.prf.Prf;
import com.google.crypto.tink.config.internal.TinkFipsUtil;
import com.google.crypto.tink.daead.subtle.DeterministicAeads;
import com.google.crypto.tink.DeterministicAead;

public final class AesSiv implements DeterministicAead, DeterministicAeads
{
    public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS;
    private static final byte[] blockZero;
    private static final byte[] blockOne;
    private static final int MAX_NUM_ASSOCIATED_DATA = 126;
    private final Prf cmacForS2V;
    private final byte[] aesCtrKey;
    private final byte[] outputPrefix;
    private static final ThreadLocal<Cipher> localAesCtrCipher;
    
    @AccessesPartialKey
    public static DeterministicAeads create(final AesSivKey key) throws GeneralSecurityException {
        return new AesSiv(key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()), key.getOutputPrefix());
    }
    
    @AccessesPartialKey
    private static Prf createCmac(final byte[] key) throws GeneralSecurityException {
        return PrfAesCmac.create(AesCmacPrfKey.create(AesCmacPrfParameters.create(key.length), SecretBytes.copyFrom(key, InsecureSecretKeyAccess.get())));
    }
    
    private AesSiv(final byte[] key, final Bytes outputPrefix) throws GeneralSecurityException {
        if (!AesSiv.FIPS.isCompatible()) {
            throw new GeneralSecurityException("Can not use AES-SIV in FIPS-mode.");
        }
        if (key.length != 32 && key.length != 64) {
            throw new InvalidKeyException("invalid key size: " + key.length + " bytes; key must have 32 or 64 bytes");
        }
        final byte[] k1 = Arrays.copyOfRange(key, 0, key.length / 2);
        this.aesCtrKey = Arrays.copyOfRange(key, key.length / 2, key.length);
        this.cmacForS2V = createCmac(k1);
        this.outputPrefix = outputPrefix.toByteArray();
    }
    
    public AesSiv(final byte[] key) throws GeneralSecurityException {
        this(key, Bytes.copyFrom(new byte[0]));
    }
    
    private byte[] s2v(final byte[]... s) throws GeneralSecurityException {
        if (s.length == 0) {
            return this.cmacForS2V.compute(AesSiv.blockOne, 16);
        }
        byte[] result = this.cmacForS2V.compute(AesSiv.blockZero, 16);
        for (int i = 0; i < s.length - 1; ++i) {
            byte[] currBlock;
            if (s[i] == null) {
                currBlock = new byte[0];
            }
            else {
                currBlock = s[i];
            }
            result = com.google.crypto.tink.subtle.Bytes.xor(AesUtil.dbl(result), this.cmacForS2V.compute(currBlock, 16));
        }
        final byte[] lastBlock = s[s.length - 1];
        if (lastBlock.length >= 16) {
            result = com.google.crypto.tink.subtle.Bytes.xorEnd(lastBlock, result);
        }
        else {
            result = com.google.crypto.tink.subtle.Bytes.xor(AesUtil.cmacPad(lastBlock), AesUtil.dbl(result));
        }
        return this.cmacForS2V.compute(result, 16);
    }
    
    private void validateAssociatedDataLength(final int associatedDataLength) throws GeneralSecurityException {
        if (associatedDataLength > 126) {
            throw new GeneralSecurityException("Too many associated datas: " + associatedDataLength + " > " + 126);
        }
    }
    
    private byte[] encryptInternal(final byte[] plaintext, final byte[]... associatedDatas) throws GeneralSecurityException {
        this.validateAssociatedDataLength(associatedDatas.length);
        if (plaintext.length > Integer.MAX_VALUE - this.outputPrefix.length - 16) {
            throw new GeneralSecurityException("plaintext too long");
        }
        final Cipher aesCtr = AesSiv.localAesCtrCipher.get();
        final byte[][] s = Arrays.copyOf(associatedDatas, associatedDatas.length + 1);
        s[associatedDatas.length] = plaintext;
        final byte[] computedIv = this.s2v(s);
        final byte[] array;
        final byte[] ivForJavaCrypto = array = computedIv.clone();
        final int n = 8;
        array[n] &= 0x7F;
        final byte[] array2 = ivForJavaCrypto;
        final int n2 = 12;
        array2[n2] &= 0x7F;
        aesCtr.init(1, new SecretKeySpec(this.aesCtrKey, "AES"), new IvParameterSpec(ivForJavaCrypto));
        final int outputSize = this.outputPrefix.length + computedIv.length + plaintext.length;
        final byte[] output = Arrays.copyOf(this.outputPrefix, outputSize);
        System.arraycopy(computedIv, 0, output, this.outputPrefix.length, computedIv.length);
        final int written = aesCtr.doFinal(plaintext, 0, plaintext.length, output, this.outputPrefix.length + computedIv.length);
        if (written != plaintext.length) {
            throw new GeneralSecurityException("not enough data written");
        }
        return output;
    }
    
    @Override
    public byte[] encryptDeterministicallyWithAssociatedDatas(final byte[] plaintext, final byte[]... associatedDatas) throws GeneralSecurityException {
        return this.encryptInternal(plaintext, associatedDatas);
    }
    
    @Override
    public byte[] encryptDeterministically(final byte[] plaintext, final byte[] associatedData) throws GeneralSecurityException {
        return this.encryptInternal(plaintext, new byte[][] { associatedData });
    }
    
    private byte[] decryptInternal(final byte[] ciphertext, final byte[]... associatedDatas) throws GeneralSecurityException {
        this.validateAssociatedDataLength(associatedDatas.length);
        if (ciphertext.length < 16 + this.outputPrefix.length) {
            throw new GeneralSecurityException("Ciphertext too short.");
        }
        if (!Util.isPrefix(this.outputPrefix, ciphertext)) {
            throw new GeneralSecurityException("Decryption failed (OutputPrefix mismatch).");
        }
        final Cipher aesCtr = AesSiv.localAesCtrCipher.get();
        final byte[] expectedIv = Arrays.copyOfRange(ciphertext, this.outputPrefix.length, 16 + this.outputPrefix.length);
        final byte[] array;
        final byte[] ivForJavaCrypto = array = expectedIv.clone();
        final int n = 8;
        array[n] &= 0x7F;
        final byte[] array2 = ivForJavaCrypto;
        final int n2 = 12;
        array2[n2] &= 0x7F;
        aesCtr.init(2, new SecretKeySpec(this.aesCtrKey, "AES"), new IvParameterSpec(ivForJavaCrypto));
        final int offset = 16 + this.outputPrefix.length;
        final int ctrCiphertextLen = ciphertext.length - offset;
        byte[] decryptedPt = aesCtr.doFinal(ciphertext, offset, ctrCiphertextLen);
        if (ctrCiphertextLen == 0 && decryptedPt == null && SubtleUtil.isAndroid()) {
            decryptedPt = new byte[0];
        }
        final byte[][] s = Arrays.copyOf(associatedDatas, associatedDatas.length + 1);
        s[associatedDatas.length] = decryptedPt;
        final byte[] computedIv = this.s2v(s);
        if (com.google.crypto.tink.subtle.Bytes.equal(expectedIv, computedIv)) {
            return decryptedPt;
        }
        throw new AEADBadTagException("Integrity check failed.");
    }
    
    @Override
    public byte[] decryptDeterministicallyWithAssociatedDatas(final byte[] ciphertext, final byte[]... associatedDatas) throws GeneralSecurityException {
        return this.decryptInternal(ciphertext, associatedDatas);
    }
    
    @Override
    public byte[] decryptDeterministically(final byte[] ciphertext, final byte[] associatedData) throws GeneralSecurityException {
        return this.decryptInternal(ciphertext, new byte[][] { associatedData });
    }
    
    static {
        FIPS = TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_NOT_FIPS;
        blockZero = new byte[16];
        blockOne = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
        localAesCtrCipher = new ThreadLocal<Cipher>() {
            @Override
            protected Cipher initialValue() {
                try {
                    return EngineFactory.CIPHER.getInstance("AES/CTR/NoPadding");
                }
                catch (final GeneralSecurityException ex) {
                    throw new IllegalStateException(ex);
                }
            }
        };
    }
}
