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;
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";
private SQLServerAeadAes256CbcHmac256EncryptionKey columnEncryptionkey;
private byte algorithmVersion;
private boolean isDeterministic = false;
private int blockSizeInBytes = 16;
private int keySizeInBytes = SQLServerAeadAes256CbcHmac256EncryptionKey.keySize / 8;
private byte[] version = new byte[] {0x01};
private byte[] versionSize = new byte[] {1};
private int minimumCipherTextLengthInBytesNoAuthenticationTag = 1 + blockSizeInBytes + blockSizeInBytes;
private int minimumCipherTextLengthInBytesWithAuthenticationTag = minimumCipherTextLengthInBytesNoAuthenticationTag
+ keySizeInBytes;
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 {
return encryptData(plainText, true);
}
protected byte[] encryptData(byte[] plainText, boolean hasAuthenticationTag) throws SQLServerException {
aeLogger.entering(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), "encryptData", "Encrypting data.");
assert (plainText != null);
byte[] iv = new byte[blockSizeInBytes];
SecretKeySpec skeySpec = new SecretKeySpec(columnEncryptionkey.getEncryptionKey(), "AES");
if (isDeterministic) {
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;
int outputBufSize = 1 + authenticationTagLen + iv.length + (numBlocks * blockSizeInBytes);
byte[] outBuffer = new byte[outputBufSize];
outBuffer[0] = algorithmVersion;
System.arraycopy(iv, 0, outBuffer, ivStartIndex, iv.length);
try {
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;
if (numBlocks > 1) {
count = (numBlocks - 1) * blockSizeInBytes;
cipherIndex += encryptCipher.update(plainText, 0, count, outBuffer, cipherIndex);
}
byte[] buffTmp = encryptCipher.doFinal(plainText, count, plainText.length - count);
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();
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(), "encryptData", "Data encrypted.");
return outBuffer;
}
@Override
byte[] decryptData(byte[] cipherText) throws SQLServerException {
return decryptData(cipherText, true);
}
private byte[] decryptData(byte[] cipherText, boolean hasAuthenticationTag) throws SQLServerException {
assert (cipherText != null);
byte[] iv = new byte[blockSizeInBytes];
int minimumCipherTextLength = hasAuthenticationTag ? minimumCipherTextLengthInBytesWithAuthenticationTag
: minimumCipherTextLengthInBytesNoAuthenticationTag;
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);
}
int startIndex = 0;
if (cipherText[startIndex] != algorithmVersion) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidAlgorithmVersion"));
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;
if (hasAuthenticationTag) {
authenticationTagOffset = startIndex;
startIndex += keySizeInBytes;
}
System.arraycopy(cipherText, startIndex, iv, 0, iv.length);
startIndex += iv.length;
int cipherTextOffset = startIndex;
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);
}
}
return decryptData(iv, cipherText, cipherTextOffset, cipherTextCount);
}
private byte[] decryptData(byte[] iv, byte[] cipherText, int offset, int count) throws SQLServerException {
aeLogger.entering(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), "decryptData", "Decrypting data.");
assert (cipherText != null);
assert (iv != null);
byte[] plainText = null;
SecretKeySpec skeySpec = new SecretKeySpec(columnEncryptionkey.getEncryptionKey(), "AES");
IvParameterSpec ivector = new IvParameterSpec(iv);
Cipher decryptCipher;
try {
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(), "decryptData", "Data decrypted.");
return plainText;
}
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;
}
}