Robert von Burg 2023-11-30 13:10:01 +01:00
parent 579cd0b0e5
commit 91a2f5651c
Signed by: eitch
GPG Key ID: 75DB9C85C74331F7
2 changed files with 197 additions and 140 deletions

View File

@ -25,6 +25,7 @@ import li.strolch.privilege.xml.CertificateStubsSaxWriter;
import li.strolch.utils.concurrent.ElementLockingHandler;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.AesCryptoHelper;
import li.strolch.utils.helper.AesCryptoHelper.SecretKeys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXParseException;
@ -135,7 +136,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
/**
* Secret key
*/
protected SecretKey secretKey;
protected SecretKeys secretKey;
/**
* flag if session refreshing is allowed

View File

@ -1,5 +1,9 @@
package li.strolch.utils.helper;
import li.strolch.utils.dbc.DBC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
@ -9,112 +13,90 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.AlgorithmParameters;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
import li.strolch.utils.dbc.DBC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Inspired by <a
* href="https://github.com/tozny/java-aes-crypto/blob/master/aes-crypto/src/main/java/com/tozny/crypto/android/AesCbcWithIntegrity.java">tozny</a>
*/
public class AesCryptoHelper {
private static final String CIPHER = "AES/CBC/PKCS5Padding";
private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String CIPHER = "AES";
private static final int AES_KEY_LENGTH_BITS = 128;
private static final int IV_LENGTH_BYTES = 16;
private static final int PBE_ITERATION_COUNT = 10000;
private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final int HMAC_KEY_LENGTH_BITS = 256;
private static final Logger logger = LoggerFactory.getLogger(AesCryptoHelper.class);
public static OutputStream wrapEncrypt(char[] password, byte[] salt, OutputStream outputStream) {
try {
// set up key
SecretKey secret = buildSecret(password, salt);
SecretKeys secret = buildSecret(password, salt);
return wrapEncrypt(secret, outputStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static OutputStream wrapEncrypt(SecretKey secret, OutputStream outputStream) {
public static OutputStream wrapEncrypt(SecretKeys secretKeys, OutputStream outputStream) {
try {
// set up cipher
Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] iv = generateIv();
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
// set up the initialization vector
AlgorithmParameters params = cipher.getParameters();
byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV();
DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length);
/*
* Now we get back the IV that will actually be used. Some runtimes
* versions do funny stuff w/ the IV, so this is to work around bugs:
*/
iv = cipher.getIV();
DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, iv.length);
// write the initialization vector, but not through the cipher output stream!
outputStream.write(initVector);
outputStream.write(iv);
outputStream.flush();
return new CipherOutputStream(outputStream, cipher);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static InputStream wrapDecrypt(char[] password, byte[] salt, InputStream inputStream) {
try {
// set up key
SecretKey secret = buildSecret(password, salt);
SecretKeys secret = buildSecret(password, salt);
return wrapDecrypt(secret, inputStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static InputStream wrapDecrypt(SecretKey secret, InputStream inputStream) {
public static InputStream wrapDecrypt(SecretKeys secretKeys, InputStream inputStream) {
try {
// read the initialization vector from the normal input stream
byte[] initVector = new byte[16];
if (inputStream.read(initVector) == -1)
if (inputStream.read(initVector) != 16)
throw new IllegalStateException("Failed to read init vector!");
// init cipher
Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector));
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(initVector));
return new CipherInputStream(inputStream, cipher);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void encrypt(char[] password, byte[] salt, String clearTextFileS, String encryptedFileS) {
try (InputStream inFile = Files.newInputStream(Paths.get(clearTextFileS));
OutputStream outFile = Files.newOutputStream(Paths.get(encryptedFileS))) {
OutputStream outFile = Files.newOutputStream(Paths.get(encryptedFileS))) {
encrypt(password, salt, inFile, outFile);
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt file " + clearTextFileS + " to " + encryptedFileS, e);
}
logger.info("Encrypted file " + clearTextFileS + " to " + encryptedFileS);
}
public static void encrypt(SecretKey secret, String clearTextFileS, String encryptedFileS) {
try (InputStream inFile = Files.newInputStream(Paths.get(clearTextFileS));
OutputStream outFile = Files.newOutputStream(Paths.get(encryptedFileS))) {
encrypt(secret, inFile, outFile);
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt file " + clearTextFileS + " to " + encryptedFileS, e);
}
@ -123,32 +105,29 @@ public class AesCryptoHelper {
}
public static void encrypt(char[] password, byte[] salt, InputStream inFile, OutputStream outFile) {
try {
// set up key
SecretKey secret = buildSecret(password, salt);
SecretKeys secret = buildSecret(password, salt);
encrypt(secret, inFile, outFile);
} catch (Exception e) {
throw new RuntimeException(e);
throw new RuntimeException("Failed to encrypt input stream to output stream!", e);
}
}
public static void encrypt(SecretKey secret, InputStream inFile, OutputStream outFile) {
public static void encrypt(SecretKeys secretKeys, InputStream inFile, OutputStream outFile) {
try {
// set up cipher
Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] iv = generateIv();
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
// set up the initialization vector
AlgorithmParameters params = cipher.getParameters();
byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV();
DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length);
outFile.write(initVector);
/*
* Now we get back the IV that will actually be used. Some runtimes
* versions do funny stuff w/ the IV, so this is to work around bugs:
*/
iv = cipher.getIV();
DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, iv.length);
outFile.write(iv);
byte[] input = new byte[64];
int bytesRead;
@ -159,24 +138,20 @@ public class AesCryptoHelper {
outFile.write(output);
}
byte[] output = cipher.doFinal();
if (output != null)
outFile.write(output);
byte[] byteCipherText = cipher.doFinal();
if (byteCipherText != null)
outFile.write(byteCipherText);
outFile.flush();
} catch (Exception e) {
throw new RuntimeException(e);
throw new RuntimeException("Failed to encrypt input stream to output stream!", e);
}
}
public static void decrypt(char[] password, byte[] salt, String encryptedFileS, String decryptedFileS) {
try (InputStream fis = Files.newInputStream(Paths.get(encryptedFileS));
OutputStream fos = Files.newOutputStream(Paths.get(decryptedFileS))) {
OutputStream fos = Files.newOutputStream(Paths.get(decryptedFileS))) {
decrypt(password, salt, fis, fos);
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt file " + decryptedFileS + " to " + decryptedFileS, e);
}
@ -185,46 +160,26 @@ public class AesCryptoHelper {
}
public static void decrypt(SecretKey secret, String encryptedFileS, String decryptedFileS) {
try (InputStream fis = Files.newInputStream(Paths.get(encryptedFileS));
OutputStream fos = Files.newOutputStream(Paths.get(decryptedFileS))) {
decrypt(secret, fis, fos);
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt file " + decryptedFileS + " to " + decryptedFileS, e);
}
logger.info("Decrypted file " + encryptedFileS + " to " + decryptedFileS);
}
public static void decrypt(char[] password, byte[] salt, InputStream fis, OutputStream fos) {
try {
// set up key
SecretKey secret = buildSecret(password, salt);
SecretKeys secret = buildSecret(password, salt);
decrypt(secret, fis, fos);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void decrypt(SecretKey secret, InputStream fis, OutputStream fos) {
public static void decrypt(SecretKeys secretKeys, InputStream fis, OutputStream fos) {
try {
// read the initialization vector
byte[] initVector = new byte[16];
if (fis.read(initVector) == -1)
byte[] iv = new byte[16];
if (fis.read(iv) == -1)
throw new IllegalStateException("Failed to read init vector!");
// init cipher
Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector));
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
byte[] in = new byte[64];
int read;
@ -239,7 +194,6 @@ public class AesCryptoHelper {
fos.write(output);
fos.flush();
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -254,7 +208,7 @@ public class AesCryptoHelper {
try {
// set up key
SecretKey secret = buildSecret(password, salt);
SecretKeys secret = buildSecret(password, salt);
return encrypt(secret, clearTextBytes);
@ -263,25 +217,20 @@ public class AesCryptoHelper {
}
}
public static byte[] encrypt(SecretKey secret, byte[] clearTextBytes) {
public static byte[] encrypt(SecretKeys secretKeys, byte[] clearTextBytes) {
try {
// set up cipher
Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, secret);
// set up the initialization vector
AlgorithmParameters params = cipher.getParameters();
byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV();
DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length);
byte[] iv = generateIv();
Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(),
new IvParameterSpec(iv));
// encrypt
byte[] encryptedBytes = cipher.doFinal(clearTextBytes);
byte[] encryptedBytes = aesCipherForEncryption.doFinal(clearTextBytes);
// create result bytes
ByteBuffer byteBuffer = ByteBuffer.allocate(initVector.length + encryptedBytes.length);
byteBuffer.put(initVector);
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encryptedBytes.length);
byteBuffer.put(iv);
byteBuffer.put(encryptedBytes);
return byteBuffer.array();
@ -292,47 +241,154 @@ public class AesCryptoHelper {
}
public static byte[] decrypt(char[] password, byte[] salt, byte[] encryptedBytes) {
try {
// set up key
SecretKey secret = buildSecret(password, salt);
SecretKeys secret = buildSecret(password, salt);
return decrypt(secret, encryptedBytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static byte[] decrypt(SecretKey secret, byte[] encryptedBytes) {
public static byte[] decrypt(SecretKeys secretKeys, byte[] encryptedBytes) {
try {
// read initialization vector
byte[] initVector = new byte[16];
System.arraycopy(encryptedBytes, 0, initVector, 0, 16);
// init cipher
Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector));
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(initVector));
return cipher.doFinal(encryptedBytes, 16, encryptedBytes.length - 16);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static SecretKey buildSecret(char[] password, byte[] salt) {
public static SecretKeys buildSecret(char[] password, byte[] salt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey secretKey = factory.generateSecret(keySpec);
// Get enough random bytes for both the AES key and the HMAC key:
KeySpec keySpec = new PBEKeySpec(password, salt, PBE_ITERATION_COUNT,
AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(PBE_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(secretKey.getEncoded(), "AES");
// Split the random bytes into two parts:
byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BITS / 8);
byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BITS / 8,
AES_KEY_LENGTH_BITS / 8 + HMAC_KEY_LENGTH_BITS / 8);
// Generate the AES key
SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
// Generate the HMAC key
SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
return new SecretKeys(confidentialityKey, integrityKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Copy the elements from the start to the end
*
* @param from the source
* @param start the start index to copy
* @param end the end index to finish
*
* @return the new buffer
*/
private static byte[] copyOfRange(byte[] from, int start, int end) {
int length = end - start;
byte[] result = new byte[length];
System.arraycopy(from, start, result, 0, length);
return result;
}
/**
* Creates a random Initialization Vector (IV) of IV_LENGTH_BYTES.
*
* @return The byte array of this IV
*/
public static byte[] generateIv() {
SecureRandom random = new SecureRandom();
byte[] b = new byte[IV_LENGTH_BYTES];
random.nextBytes(b);
return b;
}
/**
* Holder class that has both the secret AES key for encryption (confidentiality) and the secret HMAC key for
* integrity.
*/
public static class SecretKeys {
private SecretKey confidentialityKey;
private SecretKey integrityKey;
/**
* Construct the secret keys container.
*
* @param confidentialityKeyIn The AES key
* @param integrityKeyIn the HMAC key
*/
public SecretKeys(SecretKey confidentialityKeyIn, SecretKey integrityKeyIn) {
setConfidentialityKey(confidentialityKeyIn);
setIntegrityKey(integrityKeyIn);
}
public SecretKey getConfidentialityKey() {
return confidentialityKey;
}
public void setConfidentialityKey(SecretKey confidentialityKey) {
this.confidentialityKey = confidentialityKey;
}
public SecretKey getIntegrityKey() {
return integrityKey;
}
public void setIntegrityKey(SecretKey integrityKey) {
this.integrityKey = integrityKey;
}
/**
* Encodes the two keys as a string
*
* @return base64(confidentialityKey):base64(integrityKey)
*/
@Override
public String toString() {
return Base64.getEncoder().encodeToString(getConfidentialityKey().getEncoded()) + ":" + Base64
.getEncoder()
.encodeToString(getIntegrityKey().getEncoded());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + confidentialityKey.hashCode();
result = prime * result + integrityKey.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SecretKeys other = (SecretKeys) obj;
if (!integrityKey.equals(other.integrityKey))
return false;
if (!confidentialityKey.equals(other.confidentialityKey))
return false;
return true;
}
}
}