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

package com.google.crypto.tink.aead.internal;

import com.google.crypto.tink.subtle.Hex;
import javax.crypto.spec.GCMParameterSpec;
import com.google.crypto.tink.internal.Util;
import com.google.crypto.tink.subtle.Random;
import java.util.Arrays;
import com.google.crypto.tink.subtle.Validators;
import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.aead.AesGcmSivKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.GeneralSecurityException;
import com.google.crypto.tink.subtle.Bytes;
import java.security.Key;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import com.google.crypto.tink.Aead;

public final class AesGcmSiv implements Aead
{
    private static final byte[] testPlaintext;
    private static final byte[] testAad;
    private static final byte[] testKey;
    private static final byte[] testNounce;
    private static final byte[] testResult;
    private static final int IV_SIZE_IN_BYTES = 12;
    private static final int TAG_SIZE_IN_BYTES = 16;
    private final ThrowingSupplier<Cipher> cipherSupplier;
    private final SecretKey keySpec;
    private final byte[] outputPrefix;
    
    public static boolean isAesGcmSivCipher(final Cipher cipher) {
        try {
            final AlgorithmParameterSpec params = getParams(AesGcmSiv.testNounce);
            cipher.init(2, new SecretKeySpec(AesGcmSiv.testKey, "AES"), params);
            cipher.updateAAD(AesGcmSiv.testAad);
            final byte[] output = cipher.doFinal(AesGcmSiv.testResult, 0, AesGcmSiv.testResult.length);
            return Bytes.equal(output, AesGcmSiv.testPlaintext);
        }
        catch (final GeneralSecurityException ex) {
            return false;
        }
    }
    
    @AccessesPartialKey
    public static Aead create(final AesGcmSivKey key, final ThrowingSupplier<Cipher> cipherSupplier) throws GeneralSecurityException {
        if (!isAesGcmSivCipher(cipherSupplier.get())) {
            throw new IllegalStateException("Cipher does not implement AES GCM SIV.");
        }
        return new AesGcmSiv(key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()), key.getOutputPrefix().toByteArray(), cipherSupplier);
    }
    
    private AesGcmSiv(final byte[] key, final byte[] outputPrefix, final ThrowingSupplier<Cipher> cipherSupplier) throws GeneralSecurityException {
        this.outputPrefix = outputPrefix;
        Validators.validateAesKeySize(key.length);
        this.keySpec = new SecretKeySpec(key, "AES");
        this.cipherSupplier = cipherSupplier;
    }
    
    @Override
    public byte[] encrypt(final byte[] plaintext, final byte[] associatedData) throws GeneralSecurityException {
        final Cipher cipher = this.cipherSupplier.get();
        if (plaintext.length > 2147483619 - this.outputPrefix.length) {
            throw new GeneralSecurityException("plaintext too long");
        }
        final int ciphertextLen = this.outputPrefix.length + 12 + plaintext.length + 16;
        final byte[] ciphertext = Arrays.copyOf(this.outputPrefix, ciphertextLen);
        final byte[] iv = Random.randBytes(12);
        System.arraycopy(iv, 0, ciphertext, this.outputPrefix.length, 12);
        final AlgorithmParameterSpec params = getParams(iv);
        cipher.init(1, this.keySpec, params);
        if (associatedData != null && associatedData.length != 0) {
            cipher.updateAAD(associatedData);
        }
        final int written = cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, this.outputPrefix.length + 12);
        if (written != plaintext.length + 16) {
            final int actualTagSize = written - plaintext.length;
            throw new GeneralSecurityException(String.format("encryption failed; AES-GCM-SIV tag must be %s bytes, but got only %s bytes", 16, actualTagSize));
        }
        return ciphertext;
    }
    
    @Override
    public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData) throws GeneralSecurityException {
        if (ciphertext.length < this.outputPrefix.length + 12 + 16) {
            throw new GeneralSecurityException("ciphertext too short");
        }
        if (!Util.isPrefix(this.outputPrefix, ciphertext)) {
            throw new GeneralSecurityException("Decryption failed (OutputPrefix mismatch).");
        }
        final Cipher cipher = this.cipherSupplier.get();
        final AlgorithmParameterSpec params = getParams(ciphertext, this.outputPrefix.length, 12);
        cipher.init(2, this.keySpec, params);
        if (associatedData != null && associatedData.length != 0) {
            cipher.updateAAD(associatedData);
        }
        final int offset = this.outputPrefix.length + 12;
        final int len = ciphertext.length - this.outputPrefix.length - 12;
        return cipher.doFinal(ciphertext, offset, len);
    }
    
    private static AlgorithmParameterSpec getParams(final byte[] iv) {
        return getParams(iv, 0, iv.length);
    }
    
    private static AlgorithmParameterSpec getParams(final byte[] buf, final int offset, final int len) {
        return new GCMParameterSpec(128, buf, offset, len);
    }
    
    static {
        testPlaintext = Hex.decode("7a806c");
        testAad = Hex.decode("46bb91c3c5");
        testKey = Hex.decode("36864200e0eaf5284d884a0e77d31646");
        testNounce = Hex.decode("bae8e37fc83441b16034566b");
        testResult = Hex.decode("af60eb711bd85bc1e4d3e0a462e074eea428a8");
    }
    
    public interface ThrowingSupplier<T>
    {
        T get() throws GeneralSecurityException;
    }
}
