Learn how to implement and use homomorphic encryption in Java to perform computations on encrypted data without decryption.
Table of Contents
- Homomorphic Encryption Overview
- Libraries & Dependencies
- Basic Homomorphic Operations
- Microsoft SEAL Integration
- Practical Use Cases
- Performance Optimization
- Security Considerations
Homomorphic Encryption Overview
What is Homomorphic Encryption?
Homomorphic encryption allows computations to be performed on ciphertexts, generating an encrypted result that, when decrypted, matches the result of operations performed on the plaintext.
Types of Homomorphic Encryption:
- Partially Homomorphic Encryption (PHE): Supports one operation (addition OR multiplication)
- Somewhat Homomorphic Encryption (SHE): Supports limited operations
- Fully Homomorphic Encryption (FHE): Supports unlimited operations
Libraries & Dependencies
Maven Configuration
<properties>
<jna.version>5.13.0</jna.version>
<commons-math3.version>3.6.1</commons-math3.version>
<bouncycastle.version>1.75</bouncycastle.version>
</properties>
<dependencies>
<!-- Microsoft SEAL Java Bindings -->
<dependency>
<groupId>com.microsoft.seal</groupId>
<artifactId>seal-java</artifactId>
<version>4.1.1</version>
</dependency>
<!-- JNA for native integration -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>${jna.version}</version>
</dependency>
<!-- Math utilities -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>${commons-math3.version}</version>
</dependency>
<!-- Cryptography utilities -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<!-- JSON serialization -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
Gradle Configuration
dependencies {
implementation 'com.microsoft.seal:seal-java:4.1.1'
implementation 'net.java.dev.jna:jna:5.13.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.bouncycastle:bcprov-jdk18on:1.75'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
}
Basic Homomorphic Operations
1. Homomorphic Encryption Service Interface
public interface HomomorphicEncryptionService {
// Key generation
KeyPair generateKeyPair();
// Encryption
byte[] encrypt(int value);
byte[] encrypt(long value);
byte[] encrypt(double value);
// Decryption
int decryptInt(byte[] ciphertext);
long decryptLong(byte[] ciphertext);
double decryptDouble(byte[] ciphertext);
// Homomorphic operations
byte[] add(byte[] ciphertext1, byte[] ciphertext2);
byte[] add(byte[] ciphertext, int plaintext);
byte[] multiply(byte[] ciphertext1, byte[] ciphertext2);
byte[] multiply(byte[] ciphertext, int plaintext);
// Serialization
byte[] serializeCiphertext(byte[] ciphertext);
byte[] deserializeCiphertext(byte[] data);
// Key management
byte[] serializePublicKey();
byte[] serializePrivateKey();
void loadPublicKey(byte[] publicKeyData);
void loadPrivateKey(byte[] privateKeyData);
}
2. Paillier Homomorphic Encryption Implementation
@Service
public class PaillierHomomorphicEncryption implements HomomorphicEncryptionService {
private static final Logger logger = LoggerFactory.getLogger(PaillierHomomorphicEncryption.class);
private static final int DEFAULT_KEY_SIZE = 2048;
private final SecureRandom random;
private BigInteger n; // modulus n = p * q
private BigInteger nsquare; // n^2
private BigInteger g; // generator
private BigInteger lambda; // λ = lcm(p-1, q-1)
private BigInteger mu; // μ = (L(g^λ mod n^2))^{-1} mod n
private boolean keysGenerated = false;
public PaillierHomomorphicEncryption() {
this.random = new SecureRandom();
}
@Override
public KeyPair generateKeyPair() {
return generateKeyPair(DEFAULT_KEY_SIZE);
}
public KeyPair generateKeyPair(int keySize) {
try {
// Generate two large prime numbers
BigInteger p = generatePrime(keySize / 2);
BigInteger q = generatePrime(keySize / 2);
// Ensure p and q are different
while (p.equals(q)) {
q = generatePrime(keySize / 2);
}
// Calculate n = p * q
this.n = p.multiply(q);
this.nsquare = n.multiply(n);
// Calculate λ = lcm(p-1, q-1)
BigInteger pMinusOne = p.subtract(BigInteger.ONE);
BigInteger qMinusOne = q.subtract(BigInteger.ONE);
this.lambda = lcm(pMinusOne, qMinusOne);
// Choose generator g = n + 1
this.g = n.add(BigInteger.ONE);
// Calculate μ = (L(g^λ mod n^2))^{-1} mod n
BigInteger gLambda = g.modPow(lambda, nsquare);
BigInteger l = lFunction(gLambda);
this.mu = l.modInverse(n);
this.keysGenerated = true;
logger.info("Paillier key pair generated with key size: {}", keySize);
return new KeyPair(
new PublicKey(n.toByteArray(), g.toByteArray()),
new PrivateKey(lambda.toByteArray(), mu.toByteArray())
);
} catch (Exception e) {
logger.error("Failed to generate Paillier key pair", e);
throw new KeyGenerationException("Paillier key generation failed", e);
}
}
@Override
public byte[] encrypt(int value) {
return encrypt(BigInteger.valueOf(value));
}
@Override
public byte[] encrypt(long value) {
return encrypt(BigInteger.valueOf(value));
}
@Override
public byte[] encrypt(double value) {
// For floating-point numbers, use fixed-point arithmetic
long scaledValue = (long) (value * 1000); // Scale by 1000 for 3 decimal places
return encrypt(scaledValue);
}
private byte[] encrypt(BigInteger plaintext) {
validateKeys();
try {
// Ensure plaintext is in range [0, n-1]
if (plaintext.compareTo(n) >= 0) {
throw new IllegalArgumentException("Plaintext too large for encryption");
}
// Choose random r where 0 < r < n and gcd(r, n) = 1
BigInteger r;
do {
r = new BigInteger(n.bitLength(), random);
} while (r.compareTo(BigInteger.ZERO) <= 0 ||
r.compareTo(n) >= 0 ||
!r.gcd(n).equals(BigInteger.ONE));
// Encryption: c = g^m * r^n mod n^2
BigInteger gm = g.modPow(plaintext, nsquare);
BigInteger rn = r.modPow(n, nsquare);
BigInteger ciphertext = gm.multiply(rn).mod(nsquare);
return ciphertext.toByteArray();
} catch (Exception e) {
logger.error("Encryption failed", e);
throw new EncryptionException("Paillier encryption failed", e);
}
}
@Override
public int decryptInt(byte[] ciphertext) {
BigInteger plaintext = decrypt(ciphertext);
return plaintext.intValue();
}
@Override
public long decryptLong(byte[] ciphertext) {
BigInteger plaintext = decrypt(ciphertext);
return plaintext.longValue();
}
@Override
public double decryptDouble(byte[] ciphertext) {
long scaledValue = decryptLong(ciphertext);
return scaledValue / 1000.0; // Reverse scaling
}
private BigInteger decrypt(byte[] ciphertext) {
validateKeys();
try {
BigInteger c = new BigInteger(ciphertext);
// Decryption: m = L(c^λ mod n^2) * μ mod n
BigInteger cLambda = c.modPow(lambda, nsquare);
BigInteger l = lFunction(cLambda);
BigInteger plaintext = l.multiply(mu).mod(n);
return plaintext;
} catch (Exception e) {
logger.error("Decryption failed", e);
throw new DecryptionException("Paillier decryption failed", e);
}
}
@Override
public byte[] add(byte[] ciphertext1, byte[] ciphertext2) {
validateKeys();
try {
BigInteger c1 = new BigInteger(ciphertext1);
BigInteger c2 = new BigInteger(ciphertext2);
// Homomorphic addition: c1 * c2 mod n^2
BigInteger result = c1.multiply(c2).mod(nsquare);
return result.toByteArray();
} catch (Exception e) {
logger.error("Homomorphic addition failed", e);
throw new HomomorphicOperationException("Addition operation failed", e);
}
}
@Override
public byte[] add(byte[] ciphertext, int plaintext) {
validateKeys();
try {
BigInteger c = new BigInteger(ciphertext);
BigInteger m = BigInteger.valueOf(plaintext);
// Homomorphic addition with plaintext: c * g^m mod n^2
BigInteger gm = g.modPow(m, nsquare);
BigInteger result = c.multiply(gm).mod(nsquare);
return result.toByteArray();
} catch (Exception e) {
logger.error("Homomorphic addition with plaintext failed", e);
throw new HomomorphicOperationException("Addition with plaintext failed", e);
}
}
@Override
public byte[] multiply(byte[] ciphertext, int plaintext) {
validateKeys();
try {
BigInteger c = new BigInteger(ciphertext);
BigInteger k = BigInteger.valueOf(plaintext);
// Homomorphic multiplication: c^k mod n^2
BigInteger result = c.modPow(k, nsquare);
return result.toByteArray();
} catch (Exception e) {
logger.error("Homomorphic multiplication failed", e);
throw new HomomorphicOperationException("Multiplication operation failed", e);
}
}
// Paillier doesn't support ciphertext-ciphertext multiplication directly
@Override
public byte[] multiply(byte[] ciphertext1, byte[] ciphertext2) {
throw new UnsupportedOperationException(
"Paillier encryption does not support ciphertext-ciphertext multiplication");
}
// Helper methods
private BigInteger generatePrime(int bitLength) {
return BigInteger.probablePrime(bitLength, random);
}
private BigInteger lcm(BigInteger a, BigInteger b) {
return a.multiply(b).divide(a.gcd(b));
}
private BigInteger lFunction(BigInteger x) {
// L(x) = (x - 1) / n
return x.subtract(BigInteger.ONE).divide(n);
}
private void validateKeys() {
if (!keysGenerated) {
throw new IllegalStateException("Encryption keys not generated");
}
}
// Key serialization/deserialization
@Override
public byte[] serializePublicKey() {
validateKeys();
Map<String, String> keyData = new HashMap<>();
keyData.put("n", n.toString());
keyData.put("g", g.toString());
return serializeKeyData(keyData);
}
@Override
public byte[] serializePrivateKey() {
validateKeys();
Map<String, String> keyData = new HashMap<>();
keyData.put("lambda", lambda.toString());
keyData.put("mu", mu.toString());
return serializeKeyData(keyData);
}
@Override
public void loadPublicKey(byte[] publicKeyData) {
try {
Map<String, String> keyData = deserializeKeyData(publicKeyData);
this.n = new BigInteger(keyData.get("n"));
this.g = new BigInteger(keyData.get("g"));
this.nsquare = n.multiply(n);
this.keysGenerated = true;
} catch (Exception e) {
throw new KeyLoadException("Failed to load public key", e);
}
}
@Override
public void loadPrivateKey(byte[] privateKeyData) {
try {
Map<String, String> keyData = deserializeKeyData(privateKeyData);
this.lambda = new BigInteger(keyData.get("lambda"));
this.mu = new BigInteger(keyData.get("mu"));
} catch (Exception e) {
throw new KeyLoadException("Failed to load private key", e);
}
}
private byte[] serializeKeyData(Map<String, String> keyData) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsBytes(keyData);
} catch (Exception e) {
throw new SerializationException("Key serialization failed", e);
}
}
private Map<String, String> deserializeKeyData(byte[] data) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(data, new TypeReference<Map<String, String>>() {});
} catch (Exception e) {
throw new SerializationException("Key deserialization failed", e);
}
}
@Override
public byte[] serializeCiphertext(byte[] ciphertext) {
return ciphertext; // Simple implementation
}
@Override
public byte[] deserializeCiphertext(byte[] data) {
return data; // Simple implementation
}
// Key pair classes
public static class KeyPair {
private final PublicKey publicKey;
private final PrivateKey privateKey;
public KeyPair(PublicKey publicKey, PrivateKey privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public PublicKey getPublicKey() { return publicKey; }
public PrivateKey getPrivateKey() { return privateKey; }
}
public static class PublicKey {
private final byte[] n;
private final byte[] g;
public PublicKey(byte[] n, byte[] g) {
this.n = n;
this.g = g;
}
public byte[] getN() { return n; }
public byte[] getG() { return g; }
}
public static class PrivateKey {
private final byte[] lambda;
private final byte[] mu;
public PrivateKey(byte[] lambda, byte[] mu) {
this.lambda = lambda;
this.mu = mu;
}
public byte[] getLambda() { return lambda; }
public byte[] getMu() { return mu; }
}
}
Microsoft SEAL Integration
3. SEAL Homomorphic Encryption Service
@Service
@ConditionalOnClass(name = "com.microsoft.seal.SEALContext")
public class SealHomomorphicEncryption implements HomomorphicEncryptionService {
private static final Logger logger = LoggerFactory.getLogger(SealHomomorphicEncryption.class);
private SEALContext context;
private KeyGenerator keyGenerator;
private Encryptor encryptor;
private Decryptor decryptor;
private Evaluator evaluator;
private IntegerEncoder encoder;
private PublicKey publicKey;
private SecretKey secretKey;
public SealHomomorphicEncryption() {
initializeSEAL();
}
private void initializeSEAL() {
try {
// Create encryption parameters
EncryptionParameters params = new EncryptionParameters(SchemeType.BFV);
// Set polynomial modulus degree (power of 2)
int polyModulusDegree = 4096;
params.setPolyModulusDegree(polyModulusDegree);
// Set coefficient modulus
params.setCoeffModulus(CoeffModulus.BFVDefault(polyModulusDegree));
// Set plaintext modulus
params.setPlainModulus(PlainModulus.Batching(polyModulusDegree, 20));
// Create SEAL context
this.context = SEALContext.create(params);
// Create core objects
this.keyGenerator = new KeyGenerator(context);
this.encryptor = new Encryptor(context, keyGenerator.publicKey());
this.decryptor = new Decryptor(context, keyGenerator.secretKey());
this.evaluator = new Evaluator(context);
this.encoder = new IntegerEncoder(context);
// Store keys
this.publicKey = keyGenerator.publicKey();
this.secretKey = keyGenerator.secretKey();
logger.info("Microsoft SEAL initialized with polynomial modulus degree: {}", polyModulusDegree);
} catch (Exception e) {
logger.error("Failed to initialize Microsoft SEAL", e);
throw new SealInitializationException("SEAL initialization failed", e);
}
}
@Override
public KeyPair generateKeyPair() {
// Keys are generated during initialization
return new KeyPair(
new PublicKey(publicKey.save().toByteArray(), null),
new PrivateKey(secretKey.save().toByteArray(), null)
);
}
@Override
public byte[] encrypt(int value) {
try {
Plaintext plaintext = encoder.encode(value);
Ciphertext ciphertext = new Ciphertext(context);
encryptor.encrypt(plaintext, ciphertext);
return ciphertext.save().toByteArray();
} catch (Exception e) {
logger.error("SEAL encryption failed for value: {}", value, e);
throw new EncryptionException("SEAL encryption failed", e);
}
}
@Override
public byte[] encrypt(long value) {
// For large values, you might need different parameters
return encrypt((int) value); // Note: this may overflow for large longs
}
@Override
public byte[] encrypt(double value) {
try {
// Scale double to integer for encoding
long scaledValue = (long) (value * 1000); // 3 decimal places
Plaintext plaintext = encoder.encode((int) scaledValue);
Ciphertext ciphertext = new Ciphertext(context);
encryptor.encrypt(plaintext, ciphertext);
return ciphertext.save().toByteArray();
} catch (Exception e) {
logger.error("SEAL encryption failed for double value: {}", value, e);
throw new EncryptionException("SEAL encryption failed", e);
}
}
@Override
public int decryptInt(byte[] ciphertext) {
try {
Ciphertext encrypted = new Ciphertext(context);
encrypted.load(context, new ByteArrayPool(ciphertext));
Plaintext plaintext = new Plaintext();
decryptor.decrypt(encrypted, plaintext);
return encoder.decodeInt32(plaintext);
} catch (Exception e) {
logger.error("SEAL decryption failed", e);
throw new DecryptionException("SEAL decryption failed", e);
}
}
@Override
public long decryptLong(byte[] ciphertext) {
return decryptInt(ciphertext); // Note: SEAL with IntegerEncoder works with int32
}
@Override
public double decryptDouble(byte[] ciphertext) {
int scaledValue = decryptInt(ciphertext);
return scaledValue / 1000.0;
}
@Override
public byte[] add(byte[] ciphertext1, byte[] ciphertext2) {
try {
Ciphertext encrypted1 = new Ciphertext(context);
Ciphertext encrypted2 = new Ciphertext(context);
encrypted1.load(context, new ByteArrayPool(ciphertext1));
encrypted2.load(context, new ByteArrayPool(ciphertext2));
Ciphertext result = new Ciphertext(context);
evaluator.add(encrypted1, encrypted2, result);
return result.save().toByteArray();
} catch (Exception e) {
logger.error("SEAL homomorphic addition failed", e);
throw new HomomorphicOperationException("SEAL addition failed", e);
}
}
@Override
public byte[] add(byte[] ciphertext, int plaintext) {
try {
Ciphertext encrypted = new Ciphertext(context);
encrypted.load(context, new ByteArrayPool(ciphertext));
Plaintext plain = encoder.encode(plaintext);
Ciphertext result = new Ciphertext(context);
evaluator.addPlain(encrypted, plain, result);
return result.save().toByteArray();
} catch (Exception e) {
logger.error("SEAL homomorphic addition with plaintext failed", e);
throw new HomomorphicOperationException("SEAL addition with plaintext failed", e);
}
}
@Override
public byte[] multiply(byte[] ciphertext1, byte[] ciphertext2) {
try {
Ciphertext encrypted1 = new Ciphertext(context);
Ciphertext encrypted2 = new Ciphertext(context);
encrypted1.load(context, new ByteArrayPool(ciphertext1));
encrypted2.load(context, new ByteArrayPool(ciphertext2));
Ciphertext result = new Ciphertext(context);
evaluator.multiply(encrypted1, encrypted2, result);
return result.save().toByteArray();
} catch (Exception e) {
logger.error("SEAL homomorphic multiplication failed", e);
throw new HomomorphicOperationException("SEAL multiplication failed", e);
}
}
@Override
public byte[] multiply(byte[] ciphertext, int plaintext) {
try {
Ciphertext encrypted = new Ciphertext(context);
encrypted.load(context, new ByteArrayPool(ciphertext));
Plaintext plain = encoder.encode(plaintext);
Ciphertext result = new Ciphertext(context);
evaluator.multiplyPlain(encrypted, plain, result);
return result.save().toByteArray();
} catch (Exception e) {
logger.error("SEAL homomorphic multiplication with plaintext failed", e);
throw new HomomorphicOperationException("SEAL multiplication with plaintext failed", e);
}
}
// Additional SEAL-specific operations
public byte[] subtract(byte[] ciphertext1, byte[] ciphertext2) {
try {
Ciphertext encrypted1 = new Ciphertext(context);
Ciphertext encrypted2 = new Ciphertext(context);
encrypted1.load(context, new ByteArrayPool(ciphertext1));
encrypted2.load(context, new ByteArrayPool(ciphertext2));
Ciphertext result = new Ciphertext(context);
evaluator.sub(encrypted1, encrypted2, result);
return result.save().toByteArray();
} catch (Exception e) {
logger.error("SEAL homomorphic subtraction failed", e);
throw new HomomorphicOperationException("SEAL subtraction failed", e);
}
}
public byte[] square(byte[] ciphertext) {
try {
Ciphertext encrypted = new Ciphertext(context);
encrypted.load(context, new ByteArrayPool(ciphertext));
Ciphertext result = new Ciphertext(context);
evaluator.square(encrypted, result);
return result.save().toByteArray();
} catch (Exception e) {
logger.error("SEAL homomorphic square failed", e);
throw new HomomorphicOperationException("SEAL square failed", e);
}
}
// Serialization methods
@Override
public byte[] serializeCiphertext(byte[] ciphertext) {
return ciphertext; // SEAL already provides serialization
}
@Override
public byte[] deserializeCiphertext(byte[] data) {
return data; // SEAL already provides deserialization
}
@Override
public byte[] serializePublicKey() {
return publicKey.save().toByteArray();
}
@Override
public byte[] serializePrivateKey() {
return secretKey.save().toByteArray();
}
@Override
public void loadPublicKey(byte[] publicKeyData) {
try {
this.publicKey = new PublicKey(context, new ByteArrayPool(publicKeyData));
this.encryptor = new Encryptor(context, publicKey);
} catch (Exception e) {
throw new KeyLoadException("Failed to load SEAL public key", e);
}
}
@Override
public void loadPrivateKey(byte[] privateKeyData) {
try {
this.secretKey = new SecretKey(context, new ByteArrayPool(privateKeyData));
this.decryptor = new Decryptor(context, secretKey);
} catch (Exception e) {
throw new KeyLoadException("Failed to load SEAL private key", e);
}
}
@PreDestroy
public void cleanup() {
// SEAL objects need to be explicitly destroyed to free native memory
if (encoder != null) encoder.close();
if (evaluator != null) evaluator.close();
if (decryptor != null) decryptor.close();
if (encryptor != null) encryptor.close();
if (keyGenerator != null) keyGenerator.close();
if (context != null) context.close();
logger.info("SEAL resources cleaned up");
}
}
Practical Use Cases
4. Secure Voting System
@Service
public class SecureVotingService {
private static final Logger logger = LoggerFactory.getLogger(SecureVotingService.class);
private final HomomorphicEncryptionService encryptionService;
private final Map<String, byte[]> encryptedVotes = new ConcurrentHashMap<>();
public SecureVotingService(HomomorphicEncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
/**
* Cast an encrypted vote
*/
public String castVote(String voterId, int candidateId) {
try {
// Encode vote: 1 for candidate, 0 for others
byte[] encryptedVote = encryptionService.encrypt(candidateId);
encryptedVotes.put(voterId, encryptedVote);
logger.info("Vote cast by voter: {} for candidate: {}", voterId, candidateId);
return "VOTE_" + voterId + "_" + System.currentTimeMillis();
} catch (Exception e) {
logger.error("Failed to cast vote for voter: {}", voterId, e);
throw new VotingException("Vote casting failed", e);
}
}
/**
* Tally votes without decrypting individual votes
*/
public Map<Integer, Integer> tallyVotes() {
try {
if (encryptedVotes.isEmpty()) {
return Collections.emptyMap();
}
// Start with encrypted zero
byte[] total = encryptionService.encrypt(0);
// Homomorphically add all votes
for (byte[] encryptedVote : encryptedVotes.values()) {
total = encryptionService.add(total, encryptedVote);
}
// Decrypt the total
int totalVotes = encryptionService.decryptInt(total);
// In a real system, you'd have multiple candidates
// This is a simplified example with one candidate
Map<Integer, Integer> results = new HashMap<>();
results.put(1, totalVotes); // Assuming candidate ID 1
logger.info("Vote tally completed. Total votes: {}", totalVotes);
return results;
} catch (Exception e) {
logger.error("Vote tally failed", e);
throw new VotingException("Vote tally failed", e);
}
}
/**
* Verify individual vote (in real system, this would use zero-knowledge proofs)
*/
public boolean verifyVote(String voterId, int expectedCandidate) {
try {
byte[] encryptedVote = encryptedVotes.get(voterId);
if (encryptedVote == null) {
return false;
}
int actualCandidate = encryptionService.decryptInt(encryptedVote);
return actualCandidate == expectedCandidate;
} catch (Exception e) {
logger.error("Vote verification failed for voter: {}", voterId, e);
return false;
}
}
}
5. Privacy-Preserving Analytics
@Service
public class PrivacyPreservingAnalytics {
private static final Logger logger = LoggerFactory.getLogger(PrivacyPreservingAnalytics.class);
private final HomomorphicEncryptionService encryptionService;
public PrivacyPreservingAnalytics(HomomorphicEncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
/**
* Compute average of encrypted values without decrypting individual values
*/
public double computeAverage(List<byte[]> encryptedValues) {
try {
if (encryptedValues.isEmpty()) {
return 0.0;
}
// Start with encrypted zero
byte[] sum = encryptionService.encrypt(0);
// Homomorphically add all values
for (byte[] encryptedValue : encryptedValues) {
sum = encryptionService.add(sum, encryptedValue);
}
// Decrypt the sum
int total = encryptionService.decryptInt(sum);
// Compute average
double average = (double) total / encryptedValues.size();
logger.info("Computed average: {} from {} values", average, encryptedValues.size());
return average;
} catch (Exception e) {
logger.error("Average computation failed", e);
throw new AnalyticsException("Average computation failed", e);
}
}
/**
* Compute variance of encrypted values
*/
public double computeVariance(List<byte[]> encryptedValues) {
try {
if (encryptedValues.size() < 2) {
return 0.0;
}
double mean = computeAverage(encryptedValues);
// Compute sum of squares
byte[] sumSquares = encryptionService.encrypt(0);
for (byte[] encryptedValue : encryptedValues) {
// For each value: (x - mean)^2
// Since we can't subtract mean homomorphically, we'll compute differently
// This is a simplified approach
byte[] squared = encryptionService.multiply(encryptedValue, encryptionService.decryptInt(encryptedValue));
sumSquares = encryptionService.add(sumSquares, squared);
}
int totalSquares = encryptionService.decryptInt(sumSquares);
double variance = (totalSquares / (double) encryptedValues.size()) - (mean * mean);
logger.info("Computed variance: {} from {} values", variance, encryptedValues.size());
return Math.max(variance, 0.0); // Variance can't be negative
} catch (Exception e) {
logger.error("Variance computation failed", e);
throw new AnalyticsException("Variance computation failed", e);
}
}
/**
* Perform secure data aggregation from multiple sources
*/
public AggregationResult aggregateData(Map<String, List<byte[]>> encryptedDataBySource) {
try {
Map<String, Integer> sourceCounts = new HashMap<>();
Map<String, Double> sourceAverages = new HashMap<>();
byte[] globalSum = encryptionService.encrypt(0);
int totalValues = 0;
// Process each data source
for (Map.Entry<String, List<byte[]>> entry : encryptedDataBySource.entrySet()) {
String source = entry.getKey();
List<byte[]> values = entry.getValue();
// Compute source sum
byte[] sourceSum = encryptionService.encrypt(0);
for (byte[] value : values) {
sourceSum = encryptionService.add(sourceSum, value);
}
// Add to global sum
globalSum = encryptionService.add(globalSum, sourceSum);
// Store source statistics
sourceCounts.put(source, values.size());
int sourceTotal = encryptionService.decryptInt(sourceSum);
sourceAverages.put(source, (double) sourceTotal / values.size());
totalValues += values.size();
}
// Compute global statistics
int globalTotal = encryptionService.decryptInt(globalSum);
double globalAverage = (double) globalTotal / totalValues;
return AggregationResult.builder()
.sourceCounts(sourceCounts)
.sourceAverages(sourceAverages)
.globalTotal(globalTotal)
.globalAverage(globalAverage)
.totalValues(totalValues)
.build();
} catch (Exception e) {
logger.error("Data aggregation failed", e);
throw new AnalyticsException("Data aggregation failed", e);
}
}
@Data
@Builder
public static class AggregationResult {
private Map<String, Integer> sourceCounts;
private Map<String, Double> sourceAverages;
private int globalTotal;
private double globalAverage;
private int totalValues;
}
}
Performance Optimization
6. Caching and Batch Operations
@Service
public class OptimizedHomomorphicService {
private static final Logger logger = LoggerFactory.getLogger(OptimizedHomomorphicService.class);
private final HomomorphicEncryptionService encryptionService;
private final Cache<String, byte[]> encryptionCache;
private final ExecutorService batchExecutor;
public OptimizedHomomorphicService(HomomorphicEncryptionService encryptionService) {
this.encryptionService = encryptionService;
this.encryptionCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
this.batchExecutor = Executors.newFixedThreadPool(4);
}
/**
* Batch encrypt multiple values
*/
public List<byte[]> batchEncrypt(List<Integer> values) {
try {
List<Callable<byte[]>> tasks = values.stream()
.map(value -> (Callable<byte[]>) () -> encryptWithCache(value))
.collect(Collectors.toList());
List<Future<byte[]>> futures = batchExecutor.invokeAll(tasks);
List<byte[]> results = new ArrayList<>();
for (Future<byte[]> future : futures) {
results.add(future.get());
}
return results;
} catch (Exception e) {
logger.error("Batch encryption failed", e);
throw new EncryptionException("Batch encryption failed", e);
}
}
/**
* Batch homomorphic addition
*/
public byte[] batchAdd(List<byte[]> ciphertexts) {
try {
if (ciphertexts.isEmpty()) {
return encryptionService.encrypt(0);
}
byte[] result = ciphertexts.get(0);
for (int i = 1; i < ciphertexts.size(); i++) {
result = encryptionService.add(result, ciphertexts.get(i));
}
return result;
} catch (Exception e) {
logger.error("Batch addition failed", e);
throw new HomomorphicOperationException("Batch addition failed", e);
}
}
private byte[] encryptWithCache(int value) {
String cacheKey = "encrypt_" + value;
return encryptionCache.get(cacheKey, k -> encryptionService.encrypt(value));
}
@PreDestroy
public void cleanup() {
batchExecutor.shutdown();
try {
if (!batchExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
batchExecutor.shutdownNow();
}
} catch (InterruptedException e) {
batchExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
Security Considerations
7. Security Configuration and Best Practices
@Configuration
@EnableConfigurationProperties(HomomorphicEncryptionProperties.class)
public class SecurityConfig {
@Bean
@ConditionalOnProperty(name = "homomorphic.encryption.type", havingValue = "paillier")
public HomomorphicEncryptionService paillierEncryptionService() {
return new PaillierHomomorphicEncryption();
}
@Bean
@ConditionalOnProperty(name = "homomorphic.encryption.type", havingValue = "seal")
public HomomorphicEncryptionService sealEncryptionService() {
return new SealHomomorphicEncryption();
}
@Bean
public SecureRandom secureRandom() {
return new SecureRandom();
}
}
@ConfigurationProperties(prefix = "homomorphic.encryption")
@Data
public class HomomorphicEncryptionProperties {
private String type = "paillier"; // paillier, seal
private int keySize = 2048;
private boolean enableCaching = true;
private int cacheSize = 1000;
private SecurityLevel securityLevel = SecurityLevel.LEVEL_128;
public enum SecurityLevel {
LEVEL_128,
LEVEL_192,
LEVEL_256
}
}
This comprehensive implementation provides a robust foundation for homomorphic encryption in Java, covering both basic Paillier encryption and advanced Microsoft SEAL integration with practical use cases and performance optimizations.