All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.jsonwebtoken.impl.security.GcmAesAeadAlgorithm Maven / Gradle / Ivy

/*
 * Copyright (C) 2021 jsonwebtoken.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.jsonwebtoken.impl.security;

import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.AeadRequest;
import io.jsonwebtoken.security.AeadResult;
import io.jsonwebtoken.security.DecryptAeadRequest;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.security.spec.AlgorithmParameterSpec;

/**
 * @since 0.12.0
 */
public class GcmAesAeadAlgorithm extends AesAlgorithm implements AeadAlgorithm {

    private static final String TRANSFORMATION_STRING = "AES/GCM/NoPadding";

    public GcmAesAeadAlgorithm(int keyLength) {
        super("A" + keyLength + "GCM", TRANSFORMATION_STRING, keyLength);
    }

    @Override
    public void encrypt(final AeadRequest req, AeadResult res) throws SecurityException {

        Assert.notNull(req, "Request cannot be null.");
        Assert.notNull(res, "Result cannot be null.");

        final SecretKey key = assertKey(req.getKey());
        final InputStream plaintext = Assert.notNull(req.getPayload(),
                "Request content (plaintext) InputStream cannot be null.");
        final OutputStream out = Assert.notNull(res.getOutputStream(),
                "Result ciphertext OutputStream cannot be null.");
        final InputStream aad = req.getAssociatedData();
        final byte[] iv = ensureInitializationVector(req);
        final AlgorithmParameterSpec ivSpec = getIvSpec(iv);

        final byte[] tag = jca(req).withCipher(new CheckedFunction() {
            @Override
            public byte[] apply(Cipher cipher) throws Exception {
                cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
                byte[] taggedCiphertext = withCipher(cipher, plaintext, aad, out);
                // When using GCM mode, the JDK appends the authentication tag to the ciphertext, so let's extract it:
                // (tag has a length of BLOCK_BYTE_SIZE):
                int ciphertextLength = Bytes.length(taggedCiphertext) - BLOCK_BYTE_SIZE;
                Streams.write(out, taggedCiphertext, 0, ciphertextLength, "Ciphertext write failure.");
                byte[] tag = new byte[BLOCK_BYTE_SIZE];
                System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, BLOCK_BYTE_SIZE);
                return tag;
            }
        });
        Streams.flush(out);
        Streams.reset(plaintext);

        res.setTag(tag).setIv(iv);
    }

    @Override
    public void decrypt(final DecryptAeadRequest req, final OutputStream out) throws SecurityException {

        Assert.notNull(req, "Request cannot be null.");
        Assert.notNull(out, "Plaintext OutputStream cannot be null.");
        final SecretKey key = assertKey(req.getKey());
        final InputStream ciphertext = Assert.notNull(req.getPayload(),
                "Decryption request content (ciphertext) InputStream cannot be null.");
        final InputStream aad = req.getAssociatedData();
        final byte[] tag = Assert.notEmpty(req.getDigest(),
                "Decryption request authentication tag cannot be null or empty.");
        final byte[] iv = assertDecryptionIv(req);
        final AlgorithmParameterSpec ivSpec = getIvSpec(iv);

        //for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array:
        final InputStream taggedCiphertext = new SequenceInputStream(ciphertext, Streams.of(tag));

        jca(req).withCipher(new CheckedFunction() {
            @Override
            public byte[] apply(Cipher cipher) throws Exception {
                cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
                byte[] last = withCipher(cipher, taggedCiphertext, aad, out);
                Streams.write(out, last, "GcmAesAeadAlgorithm#decrypt plaintext write failure.");
                return Bytes.EMPTY;
            }
        });
        Streams.flush(out);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy