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

com.microsoft.sqlserver.jdbc.SQLServerAeadAes256CbcHmac256Algorithm Maven / Gradle / Ivy

Go to download

Microsoft JDBC Driver for SQL Server. The Azure Key Vault feature in Microsoft JDBC Driver for SQL Server depends on Azure SDK for JAVA and Azure Active Directory Library For Java.

There is a newer version: 12.7.0.jre11-preview
Show newest version
/*
 * Microsoft JDBC Driver for SQL Server
 * 
 * Copyright(c) Microsoft Corporation All rights reserved.
 * 
 * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
 */

package com.microsoft.sqlserver.jdbc;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.MessageFormat;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * 
 * This class implements authenticated encryption with associated data (AEAD_AES_256_CBC_HMAC_SHA256) algorithm specified at
 *  http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05
 *
 */
class SQLServerAeadAes256CbcHmac256Algorithm extends SQLServerEncryptionAlgorithm {

    static final private java.util.logging.Logger aeLogger = java.util.logging.Logger
            .getLogger("com.microsoft.sqlserver.jdbc.SQLServerAeadAes256CbcHmac256Algorithm");

    final static String algorithmName = "AEAD_AES_256_CBC_HMAC_SHA256";
    // Stores column encryption key which includes root key and derived keys
    private SQLServerAeadAes256CbcHmac256EncryptionKey columnEncryptionkey;
    private byte algorithmVersion;
    // This variable indicate whether encryption type is deterministic (if true)
    // or random (if false)
    private boolean isDeterministic = false;
    // Each block in the AES is 128 bits
    private int blockSizeInBytes = 16;
    private int keySizeInBytes = SQLServerAeadAes256CbcHmac256EncryptionKey.keySize / 8;
    private byte[] version = new byte[] {0x01};
    // Added so that java hashing algorithm is similar to c#
    private byte[] versionSize = new byte[] {1};

    /*
     * Minimum Length of cipherText without authentication tag. This value is 1 (version byte) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
     */
    private int minimumCipherTextLengthInBytesNoAuthenticationTag = 1 + blockSizeInBytes + blockSizeInBytes;

    /*
     * Minimum Length of cipherText. This value is 1 (version byte) + 32 (authentication tag) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
     */
    private int minimumCipherTextLengthInBytesWithAuthenticationTag = minimumCipherTextLengthInBytesNoAuthenticationTag + keySizeInBytes;

    /**
     * Initializes a new instance of SQLServerAeadAes256CbcHmac256Algorithm with a given key, encryption type and algorithm version
     * 
     * @param columnEncryptionkey
     *            Root encryption key from which three other keys will be derived
     * @param encryptionType
     *            Encryption Type, accepted values are Deterministic and Randomized.
     * @param algorithmVersion
     *            Algorithm version
     */
    SQLServerAeadAes256CbcHmac256Algorithm(SQLServerAeadAes256CbcHmac256EncryptionKey columnEncryptionkey,
            SQLServerEncryptionType encryptionType,
            byte algorithmVersion) {
        this.columnEncryptionkey = columnEncryptionkey;

        if (encryptionType == SQLServerEncryptionType.Deterministic) {
            this.isDeterministic = true;
        }
        this.algorithmVersion = algorithmVersion;
        version[0] = algorithmVersion;
    }

    @Override
    byte[] encryptData(byte[] plainText) throws SQLServerException {
        // hasAuthenticationTag is true for this algorithm
        return encryptData(plainText, true);
    }

    /**
     * Performs encryption of plain text
     * 
     * @param plainText
     *            text to be encrypted
     * @param hasAuthenticationTag
     *            specify if encryption needs authentication
     * @return cipher text
     * @throws SQLServerException
     */
    protected byte[] encryptData(byte[] plainText,
            boolean hasAuthenticationTag) throws SQLServerException {
        aeLogger.entering(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(),
                "Encrypting data.");
        // we will generate this initialization vector based whether
        // this encryption type is deterministic
        assert (plainText != null);
        byte[] iv = new byte[blockSizeInBytes];
        // Secret/private key to be used in AES encryption
        SecretKeySpec skeySpec = new SecretKeySpec(columnEncryptionkey.getEncryptionKey(), "AES");

        if (isDeterministic) {
            // this method makes sure this is 16 bytes key
            try {
                iv = SQLServerSecurityUtility.getHMACWithSHA256(plainText, columnEncryptionkey.getIVKey(), blockSizeInBytes);
            }
            catch (InvalidKeyException | NoSuchAlgorithmException e) {
                MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
                Object[] msgArgs = {e.getMessage()};
                throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
            }
        }
        else {
            SecureRandom random = new SecureRandom();
            random.nextBytes(iv);
        }

        int numBlocks = plainText.length / blockSizeInBytes + 1;

        int hmacStartIndex = 1;
        int authenticationTagLen = hasAuthenticationTag ? keySizeInBytes : 0;
        int ivStartIndex = hmacStartIndex + authenticationTagLen;
        int cipherStartIndex = ivStartIndex + blockSizeInBytes;

        // Output buffer size = size of VersionByte + Authentication Tag + IV + cipher Text blocks.
        int outputBufSize = 1 + authenticationTagLen + iv.length + (numBlocks * blockSizeInBytes);
        byte[] outBuffer = new byte[outputBufSize];

        // Copying the version to output buffer
        outBuffer[0] = algorithmVersion;
        // Coping IV to the output buffer
        System.arraycopy(iv, 0, outBuffer, ivStartIndex, iv.length);

        // Start the AES encryption

        try {
            // initialization vector
            IvParameterSpec ivector = new IvParameterSpec(iv);
            Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            encryptCipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivector);

            int count = 0;
            int cipherIndex = cipherStartIndex; // this is where cipherText starts

            if (numBlocks > 1) {
                count = (numBlocks - 1) * blockSizeInBytes;
                cipherIndex += encryptCipher.update(plainText, 0, count, outBuffer, cipherIndex);
            }
            // doFinal will complete the encryption
            byte[] buffTmp = encryptCipher.doFinal(plainText, count, plainText.length - count);
            // Encryption completed
            System.arraycopy(buffTmp, 0, outBuffer, cipherIndex, buffTmp.length);

            if (hasAuthenticationTag) {

                Mac hmac = Mac.getInstance("HmacSHA256");
                SecretKeySpec initkey = new SecretKeySpec(columnEncryptionkey.getMacKey(), "HmacSHA256");
                hmac.init(initkey);
                hmac.update(version, 0, version.length);
                hmac.update(iv, 0, iv.length);
                hmac.update(outBuffer, cipherStartIndex, numBlocks * blockSizeInBytes);
                hmac.update(versionSize, 0, version.length);
                byte[] hash = hmac.doFinal();
                // coping the authentication tag in the output buffer which holds cipher text
                System.arraycopy(hash, 0, outBuffer, hmacStartIndex, authenticationTagLen);

            }
        }
        catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException
                | IllegalBlockSizeException | BadPaddingException | ShortBufferException e) {

            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
            Object[] msgArgs = {e.getMessage()};
            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
        }

        aeLogger.exiting(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(),
                "Data encrypted.");
        return outBuffer;

    }

    @Override
    byte[] decryptData(byte[] cipherText) throws SQLServerException {
        return decryptData(cipherText, true);

    }

    /**
     * Decrypt the cipher text and return plain text
     * 
     * @param cipherText
     *            data to be decrypted
     * @param hasAuthenticationTag
     *            tells whether cipher text contain authentication tag
     * @return plain text
     * @throws SQLServerException
     */
    private byte[] decryptData(byte[] cipherText,
            boolean hasAuthenticationTag) throws SQLServerException {
        assert (cipherText != null);

        byte[] iv = new byte[blockSizeInBytes];

        int minimumCipherTextLength = hasAuthenticationTag ? minimumCipherTextLengthInBytesWithAuthenticationTag
                : minimumCipherTextLengthInBytesNoAuthenticationTag;

        // Here we check if length of cipher text is more than minimum value,
        // if not exception is thrown
        if (cipherText.length < minimumCipherTextLength) {
            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidCipherTextSize"));
            Object[] msgArgs = {cipherText.length, minimumCipherTextLength};
            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);

        }

        // Validate the version byte
        int startIndex = 0;
        if (cipherText[startIndex] != algorithmVersion) {
            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidAlgorithmVersion"));
            // converting byte to Hexa Decimal
            Object[] msgArgs = {String.format("%02X ", cipherText[startIndex]), String.format("%02X ", algorithmVersion)};
            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);

        }

        startIndex += 1;
        int authenticationTagOffset = 0;

        // Read authentication tag
        if (hasAuthenticationTag) {
            authenticationTagOffset = startIndex;
            // authentication tag size is keySizeInBytes
            startIndex += keySizeInBytes;
        }

        // Read IV from cipher text
        System.arraycopy(cipherText, startIndex, iv, 0, iv.length);
        startIndex += iv.length;

        // To read encrypted text from cipher
        int cipherTextOffset = startIndex;
        // All data after IV is encrypted data
        int cipherTextCount = cipherText.length - startIndex;

        if (hasAuthenticationTag) {
            byte[] authenticationTag;
            try {
                authenticationTag = prepareAuthenticationTag(iv, cipherText, cipherTextOffset, cipherTextCount);
            }
            catch (InvalidKeyException | NoSuchAlgorithmException e) {
                MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DecryptionFailed"));
                Object[] msgArgs = {e.getMessage()};
                throw new SQLServerException(this, form.format(msgArgs), null, 0, false);

            }
            if (!(SQLServerSecurityUtility.compareBytes(authenticationTag, cipherText, authenticationTagOffset, cipherTextCount))) {

                throw new SQLServerException(this, SQLServerException.getErrString("R_InvalidAuthenticationTag"), null, 0, false);

            }

        }

        // Decrypt the text and return
        return decryptData(iv, cipherText, cipherTextOffset, cipherTextCount);
    }

    /**
     * Decrypt data with specified IV
     * 
     * @param iv
     *            initialization vector
     * @param cipherText
     *            text to be decrypted
     * @param offset
     *            of cipher text
     * @param count
     *            length of cipher text
     * @return plain text
     * @throws SQLServerException
     */
    private byte[] decryptData(byte[] iv,
            byte[] cipherText,
            int offset,
            int count) throws SQLServerException {
        aeLogger.entering(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(),
                "Decrypting data.");
        assert (cipherText != null);
        assert (iv != null);
        byte[] plainText = null;
        // key to be used for decryption
        SecretKeySpec skeySpec = new SecretKeySpec(columnEncryptionkey.getEncryptionKey(), "AES");
        IvParameterSpec ivector = new IvParameterSpec(iv);
        Cipher decryptCipher;
        try {
            // AES encryption CBC mode and PKCS5 padding
            decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            decryptCipher.init(Cipher.DECRYPT_MODE, skeySpec, ivector);
            plainText = decryptCipher.doFinal(cipherText, offset, count);
        }
        catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException
                | IllegalBlockSizeException | BadPaddingException e) {

            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DecryptionFailed"));
            Object[] msgArgs = {e.getMessage()};
            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
        }

        aeLogger.exiting(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(),
                "Data decrypted.");
        return plainText;

    }

    /**
     * Prepare the authentication tag
     * 
     * @param iv
     *            initialization vector
     * @param cipherText
     * @param offset
     * @param length
     *            length of cipher text
     * @return authentication tag
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    private byte[] prepareAuthenticationTag(byte[] iv,
            byte[] cipherText,
            int offset,
            int length) throws NoSuchAlgorithmException, InvalidKeyException {
        assert (cipherText != null);
        byte[] computedHash;
        byte[] authenticationTag = new byte[keySizeInBytes];

        Mac hmac = Mac.getInstance("HmacSHA256");
        SecretKeySpec key = new SecretKeySpec(columnEncryptionkey.getMacKey(), "HmacSHA256");
        hmac.init(key);
        hmac.update(version, 0, version.length);
        hmac.update(iv, 0, iv.length);
        hmac.update(cipherText, offset, length);
        hmac.update(versionSize, 0, version.length);
        computedHash = hmac.doFinal();
        System.arraycopy(computedHash, 0, authenticationTag, 0, authenticationTag.length);

        return authenticationTag;

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy