Skip to content

Commit

Permalink
Refactor getKey to IEncryptionKeyProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
avazirna committed Jan 12, 2024
1 parent a9eeeed commit 4caf14b
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 196 deletions.
65 changes: 62 additions & 3 deletions src/main/java/org/commcare/util/BasicEncryptionKeyProvider.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,73 @@
package org.commcare.util;

import org.commcare.core.encryption.CryptUtil;

import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.spec.SecretKeySpec;

import static org.commcare.util.EncryptionHelper.CC_KEY_ALGORITHM_AES;
import static org.commcare.util.EncryptionHelper.CC_KEY_ALGORITHM_RSA;

public class BasicEncryptionKeyProvider implements IEncryptionKeyProvider {

@Override
public EncryptionKeyAndTransformation retrieveKeyFromKeyStore(String keyAlias,
EncryptionHelper.CryptographicOperation operation) {
return null;
public EncryptionKeyAndTransformation getKey(String algorithm, String base64encodedKey,
EncryptionHelper.CryptographicOperation cryptographicOperation)
throws EncryptionException {
byte[] keyBytes;
try {
keyBytes = Base64.decode(base64encodedKey);
} catch (Base64DecoderException e) {
throw new EncryptionException("Encryption key base 64 encoding is invalid", e);
}
Key key = null;
if (algorithm.equals(getAESKeyAlgorithmRepresentation())) {
final int AES_KEY_LENGTH_BIT = 256;

if (8 * keyBytes.length != AES_KEY_LENGTH_BIT) {
throw new EncryptionException("Key should be " + AES_KEY_LENGTH_BIT +
" bits long, not " + 8 * keyBytes.length);
}
key = new SecretKeySpec(keyBytes, getAESKeyAlgorithmRepresentation());
} else if (algorithm.equals(getRSAKeyAlgorithmRepresentation())) {
// RSA is only used for Android 5.0 - 5.1.1

if (8 * keyBytes.length < CryptUtil.RSA_MIN_KEY_LENGTH_BIT) {
throw new EncryptionException("Key should be " + CryptUtil.RSA_MIN_KEY_LENGTH_BIT +
" bits long, not " + keyBytes.length);
}
KeyFactory keyFactory;
try {
keyFactory = KeyFactory.getInstance(getRSAKeyAlgorithmRepresentation());
} catch (NoSuchAlgorithmException e) {
throw new EncryptionException("There is no Provider for the RSA algorithm", e);
}

try {
if (cryptographicOperation == EncryptionHelper.CryptographicOperation.Encryption) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
key = keyFactory.generatePublic(keySpec);
} else {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
key = keyFactory.generatePrivate(keySpec);
}
} catch (InvalidKeySpecException e) {
throw new EncryptionException("Invalid key Spec: "+ e.getMessage());
}
}

if (key == null) {
throw new EncryptionException("Encryption key conversion failed");
} else {
return new EncryptionKeyAndTransformation(key,
getTransformationString(key.getAlgorithm()));
}
}

@Override
Expand Down
198 changes: 36 additions & 162 deletions src/main/java/org/commcare/util/EncryptionHelper.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
package org.commcare.util;

import org.commcare.core.encryption.CryptUtil;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class EncryptionHelper {

Expand All @@ -48,62 +37,37 @@ public void reloadEncryptionKeyProvider() {
encryptionKeyProvider = EncryptionKeyServiceProvider.getInstance().serviceImpl();
}

/**
* Encrypts a message using a key stored in the platform KeyStore. The key is retrieved using
* its alias which is established during key generation.
*
* @param message a UTF-8 encoded message to be encrypted
* @param keyAlias key alias of the Key stored in the KeyStore, depending on the algorithm,
* it can be a SecretKey (for AES) or PublicKey (for RSA) to be used to
* encrypt the message
* @return A base64 encoded payload containing the IV and AES or RSA encrypted ciphertext,
* which can be decoded by this utility's decrypt method and the same key
* @throws KeyStoreException if the keystore has not been initialized
* @throws NoSuchAlgorithmException if the appropriate data integrity algorithm could not be
* found
* @throws UnrecoverableEntryException if an entry in the keystore cannot be retrieved
*/
public String encryptWithKeyStore(String message, String keyAlias)
throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
EncryptionKeyAndTransformation keyAndTransformation = encryptionKeyProvider.retrieveKeyFromKeyStore(keyAlias, CryptographicOperation.Encryption);

try {
return encrypt(message, keyAndTransformation);
} catch (EncryptionException e) {
throw new RuntimeException("Error encountered while encrypting the data: ", e);
}
}

public String encryptWithBase64EncodedKey(String algorithm, String message, String key)
throws EncryptionException {
EncryptionKeyAndTransformation keyAndTransformation;
try {
keyAndTransformation = getKey(algorithm, key, CryptographicOperation.Encryption);
} catch (InvalidKeySpecException e) {
throw new EncryptionException("Invalid Key specifications", e);
}
return encrypt(message, keyAndTransformation);
}






/**
* Encrypts a message using the AES or RAS algorithms and produces a base64 encoded payload
* containing the ciphertext, and, when applicable, a random IV which was used to encrypt
* the input.
* containing the ciphertext, and when applicable, a random IV which was used to encrypt
* the message.
*
* @param message a UTF-8 encoded message to be encrypted
* @param keyAndTransform depending on the algorithm, a SecretKey or PublicKey, and
* cryptographic transformation to be used to encrypt the message
* @return A base64 encoded payload containing the IV and AES or RSA encrypted ciphertext,
* which can be decoded by this utility's decrypt method and the same key
* @param algorithm the algorithm to be used to encrypt/decrypt. This is only relevant when
* not using a KeyStore
* @param message a UTF-8 encoded message to be encrypted
* @param keyOrAlias depending on the IEncryptionKeyProvider implementation this can be a
* SecretKey/PublicKey or an alias of a key stored in the platform Keystore
* @return A base64 encoded payload containing the IV and AES or RSA encrypted ciphertext,
* which can be decoded by this utility's decrypt method and the same key
*/
private String encrypt(String message, EncryptionKeyAndTransformation keyAndTransform)
public String encrypt(String algorithm, String message, String keyOrAlias)
throws EncryptionException {
final int MIN_IV_LENGTH_BYTE = 1;
final int MAX_IV_LENGTH_BYTE = 255;
EncryptionKeyAndTransformation keyAndTransformation =
encryptionKeyProvider.getKey(algorithm, keyOrAlias, CryptographicOperation.Encryption);

try {
Cipher cipher = Cipher.getInstance(keyAndTransform.getTransformation());
cipher.init(Cipher.ENCRYPT_MODE, keyAndTransform.getKey());
Cipher cipher = Cipher.getInstance(keyAndTransformation.getTransformation());
cipher.init(Cipher.ENCRYPT_MODE, keyAndTransformation.getKey());
byte[] encryptedMessage = cipher.doFinal(message.getBytes(Charset.forName("UTF-8")));
byte[] iv = cipher.getIV();
int ivSize = (iv == null ? 0 : iv.length);
Expand All @@ -127,117 +91,25 @@ private String encrypt(String message, EncryptionKeyAndTransformation keyAndTran
}
}

/**
* Converts a Base64 encoded key into a SecretKey, PrivateKey or PublicKey, depending on the
* algorithm and cryptographic operation
*
* @param algorithm the algorithm to be used to encrypt/decrypt
* @param base64encodedKey key in String format
* @param cryptographicOperation Cryptographic operation where the key is to be used, relevant
* to the RSA algorithm
* @return Secret key, Public key or Private Key to be used
*/
private EncryptionKeyAndTransformation getKey(String algorithm,
String base64encodedKey,
EncryptionHelper.CryptographicOperation cryptographicOperation)
throws EncryptionException, InvalidKeySpecException {
byte[] keyBytes;
try {
keyBytes = Base64.decode(base64encodedKey);
} catch (Base64DecoderException e) {
throw new EncryptionException("Encryption key base 64 encoding is invalid", e);
}
Key key = null;
if (algorithm.equals(encryptionKeyProvider.getAESKeyAlgorithmRepresentation())) {
final int AES_KEY_LENGTH_BIT = 256;

if (8 * keyBytes.length != AES_KEY_LENGTH_BIT) {
throw new EncryptionException("Key should be " + AES_KEY_LENGTH_BIT +
" bits long, not " + 8 * keyBytes.length);
}
key = new SecretKeySpec(keyBytes, encryptionKeyProvider.getAESKeyAlgorithmRepresentation());
} else if (algorithm.equals(encryptionKeyProvider.getRSAKeyAlgorithmRepresentation())) {
// RSA is only used for Android 5.0 - 5.1.1

if (8 * keyBytes.length < CryptUtil.RSA_MIN_KEY_LENGTH_BIT) {
throw new EncryptionException("Key should be " + CryptUtil.RSA_MIN_KEY_LENGTH_BIT +
" bits long, not " + keyBytes.length);
}
KeyFactory keyFactory;
try {
keyFactory = KeyFactory.getInstance(encryptionKeyProvider.getRSAKeyAlgorithmRepresentation());
} catch (NoSuchAlgorithmException e) {
throw new EncryptionException("There is no Provider for the RSA algorithm", e);
}

if (cryptographicOperation == EncryptionHelper.CryptographicOperation.Encryption) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
key = keyFactory.generatePublic(keySpec);
} else {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
key = keyFactory.generatePrivate(keySpec);
}
}

if (key == null) {
throw new EncryptionException("Encryption key conversion failed");
} else {
return new EncryptionKeyAndTransformation(key,
encryptionKeyProvider.getTransformationString(key.getAlgorithm()));
}
}

/**
* Decrypts a base64 payload containing an IV and AES or RSA encrypted ciphertext using a key
* stored in the platform KeyStore. The key is retrieved using its alias which is established
* during key generation.
*
* @param message a UTF-8 encoded message to be decrypted
* @param keyAlias key alias of the Key stored in the KeyStore, depending on the algorithm,
* it can be a SecretKey (for AES) or PublicKey (for RSA) to be used to
* decrypt the message
* @return Decrypted message of the provided ciphertext,
* @throws KeyStoreException if the keystore has not been initialized
* @throws NoSuchAlgorithmException if the appropriate data integrity algorithm could not be
* found
* @throws UnrecoverableEntryException if an entry in the keystore cannot be retrieved
*/
public String decryptWithKeyStore(String message, String keyAlias)
throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException,
CertificateException, IOException {
EncryptionKeyAndTransformation keyAndTransformation =
encryptionKeyProvider.retrieveKeyFromKeyStore(keyAlias, CryptographicOperation.Decryption);
try {
return decrypt(message, keyAndTransformation);
} catch (EncryptionException e) {
throw new RuntimeException("Error encountered while decrypting the data ", e);
}
}

public String decryptWithBase64EncodedKey(String algorithm, String message, String key)
throws EncryptionException {
EncryptionKeyAndTransformation keyAndTransformation;
try {
keyAndTransformation = getKey(algorithm, key, CryptographicOperation.Decryption);
} catch (InvalidKeySpecException e) {
throw new EncryptionException("Invalid Key specifications", e);
}
return decrypt(message, keyAndTransformation);
}

/**
* Decrypts a base64 payload containing an IV and AES or RSA encrypted ciphertext using the
* provided key
* provided key or an alias of a key stored in the platform Keystore
*
* @param message a message to be decrypted
* @param keyAndTransform depending on the algorithm, a Secret key or Private key and its
* respective cryptographic transformation to be used for decryption
* @param message a UTF-8 encoded message to be decrypted
* @param keyOrKeyAlias depending on the IEncryptionKeyProvider implementation this can be a
* SecretKey/PublicKey or an alias of a key stored in the platform Keystore
* @return Decrypted message for the given encrypted message
* @throws EncryptionException in case the keystore has not been initialized, the appropriate
* data integrity algorithm could not be found or an entry in the
* keystore cannot be retrieved
*/
private String decrypt(String message, EncryptionKeyAndTransformation keyAndTransform)
public String decrypt(String algorithm, String message, String keyOrKeyAlias)
throws EncryptionException {
final int TAG_LENGTH_BIT = 128;

EncryptionKeyAndTransformation keyAndTransformation =
encryptionKeyProvider.getKey(algorithm, keyOrKeyAlias, CryptographicOperation.Decryption);

try {
byte[] messageBytes = Base64.decode(message);
ByteBuffer bb = ByteBuffer.wrap(messageBytes);
Expand All @@ -248,11 +120,13 @@ private String decrypt(String message, EncryptionKeyAndTransformation keyAndTran
byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);

Cipher cipher = Cipher.getInstance(keyAndTransform.getTransformation());
if (keyAndTransform.getKey().getAlgorithm().equals(encryptionKeyProvider.getAESKeyAlgorithmRepresentation())) {
cipher.init(Cipher.DECRYPT_MODE, keyAndTransform.getKey(), new GCMParameterSpec(TAG_LENGTH_BIT, iv));
Cipher cipher = Cipher.getInstance(keyAndTransformation.getTransformation());
if (keyAndTransformation.getKey().getAlgorithm().equals(
encryptionKeyProvider.getAESKeyAlgorithmRepresentation())) {
cipher.init(Cipher.DECRYPT_MODE, keyAndTransformation.getKey(),
new GCMParameterSpec(TAG_LENGTH_BIT, iv));
} else {
cipher.init(Cipher.DECRYPT_MODE, keyAndTransform.getKey());
cipher.init(Cipher.DECRYPT_MODE, keyAndTransformation.getKey());
}
byte[] plainText = cipher.doFinal(cipherText);
return new String(plainText, Charset.forName("UTF-8"));
Expand Down
22 changes: 13 additions & 9 deletions src/main/java/org/commcare/util/IEncryptionKeyProvider.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package org.commcare.util;

import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;

/**
* Service interface for Encryption Key providers for KeyStores
*
Expand All @@ -14,9 +8,19 @@

public interface IEncryptionKeyProvider {

EncryptionKeyAndTransformation retrieveKeyFromKeyStore(String keyAlias,
EncryptionHelper.CryptographicOperation operation)
throws KeyStoreException, UnrecoverableEntryException, NoSuchAlgorithmException, CertificateException, IOException;
/**
* Converts a Base64 encoded key into a SecretKey, PrivateKey or PublicKey, depending on the
* algorithm and cryptographic operation
*
* @param algorithm the algorithm to be used to encrypt/decrypt the message, only
* applicable when not using the platform keystore
* @param keyOrKeyAlias key or key alias in String format
* @param cryptographicOperation Cryptographic operation where the key is to be used, relevant
* to the RSA algorithm
* @return Secret key, Public key or Private Key to be used
*/
EncryptionKeyAndTransformation getKey(String algorithm, String keyOrKeyAlias,
EncryptionHelper.CryptographicOperation cryptographicOperation) throws EncryptionException;

void generateCryptographicKeyInKeyStore(String keyAlias);

Expand Down
Loading

0 comments on commit 4caf14b

Please sign in to comment.