Article
Microsoft SEAL is a homomorphic encryption library that allows computations on encrypted data without decrypting it first. This guide covers how to use SEAL in Java applications, including basic operations, advanced schemes, and practical implementations.
What is Homomorphic Encryption?
- Fully Homomorphic Encryption (FHE): Perform any computation on encrypted data
- Somewhat Homomorphic Encryption (SHE): Limited computation depth
- BFV Scheme: For integer arithmetic
- CKKS Scheme: For approximate real/complex number arithmetic
Project Setup and Dependencies
Maven Dependencies:
<properties>
<seal-java.version>4.1.0</seal-java.version>
<jna.version>5.13.0</jna.version>
</properties>
<dependencies>
<!-- Microsoft SEAL Java Bindings -->
<dependency>
<groupId>com.microsoft.seal</groupId>
<artifactId>seal-java</artifactId>
<version>${seal-java.version}</version>
</dependency>
<!-- JNA for native library loading -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>${jna.version}</version>
</dependency>
<!-- JSON for serialization -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Apache Commons for utilities -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- For big integer operations -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.77</version>
</dependency>
</dependencies>
<!-- Repository for SEAL -->
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
1. Basic SEAL Setup and Configuration
SEAL Context and Key Management:
package com.example.seal.basics;
import com.microsoft.seal.*;
import java.util.Base64;
public class SEALSetup {
private EncryptionParameters encryptionParams;
private SEALContext context;
private KeyGenerator keyGenerator;
private PublicKey publicKey;
private SecretKey secretKey;
private RelinKeys relinKeys;
private GaloisKeys galoisKeys;
private Encryptor encryptor;
private Evaluator evaluator;
private Decryptor decryptor;
private BatchEncoder batchEncoder;
private CKKSEncoder ckksEncoder;
public SEALSetup(SchemeType scheme, int polyModulusDegree,
long[] coeffModulus, long plainModulus) {
// 1. Initialize encryption parameters based on scheme
this.encryptionParams = new EncryptionParameters(scheme);
// 2. Set polynomial modulus degree (power of 2, e.g., 4096, 8192, 16384)
encryptionParams.setPolyModulusDegree(polyModulusDegree);
// 3. Set coefficient modulus
switch (scheme) {
case bfv:
// For BFV scheme
encryptionParams.setCoeffModulus(CoeffModulus.BFVDefault(polyModulusDegree));
encryptionParams.setPlainModulus(PlainModulus.Batching(polyModulusDegree, 20));
break;
case ckks:
// For CKKS scheme
encryptionParams.setCoeffModulus(CoeffModulus.Create(
polyModulusDegree, new int[]{60, 40, 40, 60}));
break;
default:
throw new IllegalArgumentException("Unsupported scheme type: " + scheme);
}
// 4. Create SEAL context
this.context = new SEALContext(encryptionParams);
// 5. Initialize key generator
this.keyGenerator = new KeyGenerator(context);
// 6. Generate keys
this.publicKey = keyGenerator.createPublicKey();
this.secretKey = keyGenerator.secretKey();
// 7. Generate relinearization keys (for multiplication)
this.relinKeys = keyGenerator.createRelinKeys();
// 8. Generate Galois keys (for rotation operations)
this.galoisKeys = keyGenerator.createGaloisKeys();
// 9. Initialize encryptor, evaluator, decryptor
this.encryptor = new Encryptor(context, publicKey);
this.evaluator = new Evaluator(context);
this.decryptor = new Decryptor(context, secretKey);
// 10. Initialize encoders based on scheme
if (scheme == SchemeType.bfv) {
this.batchEncoder = new BatchEncoder(context);
} else if (scheme == SchemeType.ckks) {
this.ckksEncoder = new CKKSEncoder(context);
}
}
public static class Builder {
private SchemeType scheme = SchemeType.ckks;
private int polyModulusDegree = 8192;
private long plainModulus = 1032193;
private long[] coeffModulus;
public Builder withScheme(SchemeType scheme) {
this.scheme = scheme;
return this;
}
public Builder withPolyModulusDegree(int degree) {
if ((degree & (degree - 1)) != 0 || degree < 1024) {
throw new IllegalArgumentException("Poly modulus degree must be a power of 2 and >= 1024");
}
this.polyModulusDegree = degree;
return this;
}
public Builder withPlainModulus(long modulus) {
this.plainModulus = modulus;
return this;
}
public SEALSetup build() {
return new SEALSetup(scheme, polyModulusDegree, coeffModulus, plainModulus);
}
}
// Getters
public EncryptionParameters getEncryptionParams() { return encryptionParams; }
public SEALContext getContext() { return context; }
public PublicKey getPublicKey() { return publicKey; }
public SecretKey getSecretKey() { return secretKey; }
public RelinKeys getRelinKeys() { return relinKeys; }
public GaloisKeys getGaloisKeys() { return galoisKeys; }
public Encryptor getEncryptor() { return encryptor; }
public Evaluator getEvaluator() { return evaluator; }
public Decryptor getDecryptor() { return decryptor; }
public BatchEncoder getBatchEncoder() { return batchEncoder; }
public CKKSEncoder getCkksEncoder() { return ckksEncoder; }
// Serialization methods
public String serializePublicKey() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
publicKey.save(outputStream);
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
public PublicKey deserializePublicKey(String base64String) {
byte[] data = Base64.getDecoder().decode(base64String);
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
PublicKey key = new PublicKey();
key.load(context, inputStream);
return key;
}
// Parameter validation
public void validateParameters() {
if (!context.parametersSet()) {
throw new IllegalStateException("Encryption parameters are not set correctly");
}
if (context.firstContextData().qualifiers().usingBatching) {
System.out.println("Batching enabled");
}
if (context.firstContextData().qualifiers().usingFastPlainLift) {
System.out.println("Fast plain lift enabled");
}
// Print security level
SecurityLevel security = context.firstContextData().qualifiers().secLevel;
System.out.println("Security level: " + security);
}
}
2. BFV Scheme for Integer Operations
BFV (Brakerski/Fan-Vercauteren) Scheme Implementation:
package com.example.seal.bfv;
import com.microsoft.seal.*;
import java.util.ArrayList;
import java.util.List;
public class BFVOperations {
private final SEALSetup setup;
private final BatchEncoder batchEncoder;
private final Encryptor encryptor;
private final Evaluator evaluator;
private final Decryptor decryptor;
public BFVOperations(SEALSetup setup) {
this.setup = setup;
this.batchEncoder = setup.getBatchEncoder();
this.encryptor = setup.getEncryptor();
this.evaluator = setup.getEvaluator();
this.decryptor = setup.getDecryptor();
if (setup.getEncryptionParams().scheme() != SchemeType.bfv) {
throw new IllegalArgumentException("BFV operations require BFV scheme");
}
}
// Encrypt a vector of integers
public Ciphertext encryptVector(List<Long> values) {
// Convert list to array
long[] array = values.stream().mapToLong(Long::longValue).toArray();
// Encode the vector
Plaintext plain = new Plaintext();
batchEncoder.encode(array, plain);
// Encrypt
Ciphertext cipher = new Ciphertext();
encryptor.encrypt(plain, cipher);
return cipher;
}
// Decrypt a ciphertext to vector
public List<Long> decryptVector(Ciphertext cipher) {
// Decrypt
Plaintext plain = new Plaintext();
decryptor.decrypt(cipher, plain);
// Decode
long[] array = new long[batchEncoder.slotCount()];
batchEncoder.decode(plain, array);
// Convert to list
List<Long> result = new ArrayList<>();
for (long value : array) {
result.add(value);
}
return result;
}
// Homomorphic addition
public Ciphertext add(Ciphertext cipher1, Ciphertext cipher2) {
Ciphertext result = new Ciphertext();
evaluator.add(cipher1, cipher2, result);
return result;
}
public Ciphertext add(Ciphertext cipher, long scalar) {
// Encode scalar as plaintext
Plaintext plainScalar = new Plaintext();
batchEncoder.encode(new long[]{scalar}, plainScalar);
// Add
Ciphertext result = new Ciphertext();
evaluator.addPlain(cipher, plainScalar, result);
return result;
}
// Homomorphic subtraction
public Ciphertext subtract(Ciphertext cipher1, Ciphertext cipher2) {
Ciphertext result = new Ciphertext();
evaluator.sub(cipher1, cipher2, result);
return result;
}
// Homomorphic multiplication
public Ciphertext multiply(Ciphertext cipher1, Ciphertext cipher2) {
Ciphertext result = new Ciphertext();
evaluator.multiply(cipher1, cipher2, result);
// Relinearize to reduce ciphertext size
evaluator.relinearizeInplace(result, setup.getRelinKeys());
return result;
}
public Ciphertext multiply(Ciphertext cipher, long scalar) {
// Encode scalar as plaintext
Plaintext plainScalar = new Plaintext();
batchEncoder.encode(new long[]{scalar}, plainScalar);
// Multiply
Ciphertext result = new Ciphertext();
evaluator.multiplyPlain(cipher, plainScalar, result);
return result;
}
// Homomorphic negation
public Ciphertext negate(Ciphertext cipher) {
Ciphertext result = new Ciphertext();
evaluator.negate(cipher, result);
return result;
}
// Square
public Ciphertext square(Ciphertext cipher) {
Ciphertext result = new Ciphertext();
evaluator.square(cipher, result);
evaluator.relinearizeInplace(result, setup.getRelinKeys());
return result;
}
// Rotate vector elements
public Ciphertext rotate(Ciphertext cipher, int steps) {
Ciphertext result = new Ciphertext();
evaluator.rotateVector(cipher, steps, setup.getGaloisKeys(), result);
return result;
}
// Sum all elements in encrypted vector
public Ciphertext sumAll(Ciphertext cipher) {
int slotCount = batchEncoder.slotCount();
Ciphertext result = cipher.clone();
// Use rotation to sum all elements
for (int i = 1; i < slotCount; i *= 2) {
Ciphertext rotated = new Ciphertext();
evaluator.rotateVector(result, i, setup.getGaloisKeys(), rotated);
evaluator.addInplace(result, rotated);
}
return result;
}
// Dot product of two encrypted vectors
public Ciphertext dotProduct(Ciphertext cipher1, Ciphertext cipher2) {
// Multiply element-wise
Ciphertext multiplied = multiply(cipher1, cipher2);
// Sum all elements
return sumAll(multiplied);
}
// Compute polynomial: a*x^2 + b*x + c
public Ciphertext evaluatePolynomial(Ciphertext x, long a, long b, long c) {
// Compute x^2
Ciphertext xSquared = square(x);
// Compute a*x^2
Ciphertext ax2 = multiply(xSquared, a);
// Compute b*x
Ciphertext bx = multiply(x, b);
// Add all terms
Ciphertext result = add(ax2, bx);
result = add(result, c);
return result;
}
// Statistics operations
public Ciphertext computeMean(Ciphertext cipher) {
int slotCount = batchEncoder.slotCount();
// Sum all elements
Ciphertext sum = sumAll(cipher);
// Divide by slot count (plaintext division)
Plaintext divisor = new Plaintext();
batchEncoder.encode(new long[]{slotCount}, divisor);
Ciphertext result = new Ciphertext();
evaluator.multiplyPlain(sum, divisor, result); // Actually need to divide
return result;
}
// Example: Secure voting system
public Ciphertext countVotes(List<Ciphertext> votes) {
if (votes.isEmpty()) {
throw new IllegalArgumentException("No votes provided");
}
// Start with first vote
Ciphertext result = votes.get(0).clone();
// Sum all votes
for (int i = 1; i < votes.size(); i++) {
evaluator.addInplace(result, votes.get(i));
}
return result;
}
}
3. CKKS Scheme for Real Number Operations
CKKS (Cheon-Kim-Kim-Song) Scheme Implementation:
package com.example.seal.ckks;
import com.microsoft.seal.*;
import java.util.ArrayList;
import java.util.List;
public class CKKSOperations {
private final SEALSetup setup;
private final CKKSEncoder encoder;
private final Encryptor encryptor;
private final Evaluator evaluator;
private final Decryptor decryptor;
private final double scale;
public CKKSOperations(SEALSetup setup, double scale) {
this.setup = setup;
this.encoder = setup.getCkksEncoder();
this.encryptor = setup.getEncryptor();
this.evaluator = setup.getEvaluator();
this.decryptor = setup.getDecryptor();
this.scale = scale;
if (setup.getEncryptionParams().scheme() != SchemeType.ckks) {
throw new IllegalArgumentException("CKKS operations require CKKS scheme");
}
}
// Encrypt a vector of doubles
public Ciphertext encryptVector(List<Double> values) {
// Convert to double array
double[] array = new double[encoder.slotCount()];
for (int i = 0; i < Math.min(values.size(), array.length); i++) {
array[i] = values.get(i);
}
// Encode
Plaintext plain = new Plaintext();
encoder.encode(array, scale, plain);
// Encrypt
Ciphertext cipher = new Ciphertext();
encryptor.encrypt(plain, cipher);
return cipher;
}
// Decrypt a ciphertext to vector
public List<Double> decryptVector(Ciphertext cipher) {
// Decrypt
Plaintext plain = new Plaintext();
decryptor.decrypt(cipher, plain);
// Decode
double[] array = new double[encoder.slotCount()];
encoder.decode(plain, array);
// Convert to list
List<Double> result = new ArrayList<>();
for (double value : array) {
result.add(value);
}
return result;
}
// Basic arithmetic operations
public Ciphertext add(Ciphertext cipher1, Ciphertext cipher2) {
Ciphertext result = new Ciphertext();
evaluator.add(cipher1, cipher2, result);
return result;
}
public Ciphertext add(Ciphertext cipher, double scalar) {
Plaintext plainScalar = new Plaintext();
encoder.encode(scalar, scale, plainScalar);
Ciphertext result = new Ciphertext();
evaluator.addPlain(cipher, plainScalar, result);
return result;
}
public Ciphertext multiply(Ciphertext cipher1, Ciphertext cipher2) {
Ciphertext result = new Ciphertext();
evaluator.multiply(cipher1, cipher2, result);
// Rescale after multiplication
evaluator.rescaleToNextInplace(result);
// Relinearize
evaluator.relinearizeInplace(result, setup.getRelinKeys());
return result;
}
public Ciphertext multiply(Ciphertext cipher, double scalar) {
Plaintext plainScalar = new Plaintext();
encoder.encode(scalar, scale, plainScalar);
Ciphertext result = new Ciphertext();
evaluator.multiplyPlain(cipher, plainScalar, result);
// Rescale
evaluator.rescaleToNextInplace(result);
return result;
}
// Linear regression prediction: y = w·x + b
public Ciphertext linearRegressionPredict(Ciphertext encryptedFeatures,
List<Double> weights,
double bias) {
// Encrypt weights
Ciphertext encryptedWeights = encryptVector(weights);
// Compute dot product: w·x
Ciphertext dotProduct = multiply(encryptedFeatures, encryptedWeights);
// Add bias
return add(dotProduct, bias);
}
// Logistic regression prediction: sigmoid(w·x + b)
public Ciphertext logisticRegressionPredict(Ciphertext encryptedFeatures,
List<Double> weights,
double bias) {
// Compute linear part
Ciphertext linear = linearRegressionPredict(encryptedFeatures, weights, bias);
// Apply sigmoid approximation (using polynomial approximation)
return sigmoidApproximation(linear);
}
// Sigmoid approximation using Taylor series
private Ciphertext sigmoidApproximation(Ciphertext x) {
// 5th degree polynomial approximation:
// sigmoid(x) ≈ 0.5 + 0.197x - 0.004x^3 + 0.0001x^5
Ciphertext result;
// Compute x^2
Ciphertext x2 = square(x);
// Compute x^3 = x * x^2
Ciphertext x3 = multiply(x, x2);
// Compute x^5 = x^2 * x^3
Ciphertext x5 = multiply(x2, x3);
// Apply coefficients
Ciphertext term1 = multiply(x, 0.197);
Ciphertext term3 = multiply(x3, -0.004);
Ciphertext term5 = multiply(x5, 0.0001);
// Sum all terms
result = add(term1, term3);
result = add(result, term5);
result = add(result, 0.5);
return result;
}
// Neural network layer (dense layer with ReLU)
public Ciphertext neuralNetworkLayer(Ciphertext input,
List<List<Double>> weights,
List<Double> biases) {
List<Ciphertext> outputs = new ArrayList<>();
// For each neuron
for (int i = 0; i < weights.size(); i++) {
// Encrypt weights for this neuron
Ciphertext encryptedWeights = encryptVector(weights.get(i));
// Compute dot product
Ciphertext dotProduct = multiply(input, encryptedWeights);
// Add bias
Ciphertext preActivation = add(dotProduct, biases.get(i));
// Apply ReLU approximation (using square root approximation)
Ciphertext activated = reluApproximation(preActivation);
outputs.add(activated);
}
// TODO: Combine outputs (would need packing/rotation)
return outputs.get(0); // Simplified
}
private Ciphertext reluApproximation(Ciphertext x) {
// ReLU approximation: max(0, x) ≈ (x + sqrt(x^2 + ε)) / 2
double epsilon = 0.001;
// Compute x^2
Ciphertext x2 = square(x);
// Add epsilon
Ciphertext x2PlusEpsilon = add(x2, epsilon);
// TODO: Need square root approximation
// For now, return identity
return x.clone();
}
// Statistics operations
public Ciphertext computeMean(Ciphertext cipher) {
int slotCount = encoder.slotCount();
// Sum all elements
Ciphertext sum = sumAll(cipher);
// Divide by slot count
return multiply(sum, 1.0 / slotCount);
}
public Ciphertext computeVariance(Ciphertext cipher) {
// Compute mean
Ciphertext mean = computeMean(cipher);
// Subtract mean from all elements (need rotation/broadcasting)
// TODO: Implement proper broadcasting
// Square the differences
// Sum squared differences
// Divide by N-1
return mean; // Placeholder
}
private Ciphertext sumAll(Ciphertext cipher) {
int slotCount = encoder.slotCount();
Ciphertext result = cipher.clone();
// Use rotation to sum all elements
for (int i = 1; i < slotCount; i *= 2) {
Ciphertext rotated = new Ciphertext();
evaluator.rotateVector(result, i, setup.getGaloisKeys(), rotated);
evaluator.addInplace(result, rotated);
}
return result;
}
private Ciphertext square(Ciphertext cipher) {
Ciphertext result = new Ciphertext();
evaluator.square(cipher, result);
evaluator.relinearizeInplace(result, setup.getRelinKeys());
evaluator.rescaleToNextInplace(result);
return result;
}
}
4. Practical Use Cases and Applications
Secure Data Analytics:
package com.example.seal.applications;
import com.example.seal.bfv.BFVOperations;
import com.example.seal.ckks.CKKSOperations;
import com.microsoft.seal.*;
import java.util.*;
public class SecureDataAnalytics {
// 1. Privacy-Preserving Statistics
public static class SecureStatistics {
private final BFVOperations bfvOps;
public SecureStatistics(BFVOperations bfvOps) {
this.bfvOps = bfvOps;
}
// Compute sum of encrypted values
public Ciphertext secureSum(List<Ciphertext> encryptedValues) {
if (encryptedValues.isEmpty()) {
throw new IllegalArgumentException("No values provided");
}
Ciphertext result = encryptedValues.get(0).clone();
Evaluator evaluator = bfvOps.getEvaluator();
for (int i = 1; i < encryptedValues.size(); i++) {
evaluator.addInplace(result, encryptedValues.get(i));
}
return result;
}
// Compute average without revealing individual values
public Ciphertext secureAverage(List<Ciphertext> encryptedValues) {
Ciphertext sum = secureSum(encryptedValues);
// Multiply by 1/N (requires encoding 1/N as plaintext)
Plaintext inverseN = bfvOps.encodePlaintext(1.0 / encryptedValues.size());
Ciphertext result = new Ciphertext();
bfvOps.getEvaluator().multiplyPlain(sum, inverseN, result);
return result;
}
// Secure voting/counting
public Map<String, Long> secureVoting(List<Vote> encryptedVotes,
List<String> candidates) {
Map<String, Ciphertext> candidateVotes = new HashMap<>();
// Initialize vote counts for each candidate
for (String candidate : candidates) {
Plaintext zero = bfvOps.encodePlaintext(0L);
Ciphertext cipherZero = new Ciphertext();
bfvOps.getEncryptor().encrypt(zero, cipherZero);
candidateVotes.put(candidate, cipherZero);
}
// Count votes
for (Vote vote : encryptedVotes) {
String candidate = vote.getCandidate();
Ciphertext currentVotes = candidateVotes.get(candidate);
// Add vote (1 for this candidate)
Ciphertext one = bfvOps.encryptVector(Arrays.asList(1L));
bfvOps.getEvaluator().addInplace(currentVotes, one);
}
// Decrypt results (would be done by trusted party)
Map<String, Long> results = new HashMap<>();
// In real scenario, decryption would happen separately
return results;
}
}
// 2. Privacy-Preserving Machine Learning
public static class SecureML {
private final CKKSOperations ckksOps;
public SecureML(CKKSOperations ckksOps) {
this.ckksOps = ckksOps;
}
// Secure prediction service
public Ciphertext securePrediction(Ciphertext encryptedFeatures,
List<List<Double>> modelWeights,
List<Double> modelBiases) {
List<Ciphertext> layerOutputs = new ArrayList<>();
Ciphertext currentInput = encryptedFeatures;
// Process each layer
for (int layer = 0; layer < modelWeights.size(); layer++) {
Ciphertext layerResult = neuralNetworkLayer(
currentInput,
modelWeights.get(layer),
modelBiases.get(layer)
);
layerOutputs.add(layerResult);
currentInput = layerResult;
}
return layerOutputs.get(layerOutputs.size() - 1);
}
private Ciphertext neuralNetworkLayer(Ciphertext input,
List<Double> weights,
double bias) {
// Simplified single neuron
return ckksOps.linearRegressionPredict(input, weights, bias);
}
// Secure federated learning aggregation
public List<Double> federatedAverage(List<List<Double>> encryptedGradients) {
// This would actually work with encrypted gradients
// For demonstration, assume we can compute average
if (encryptedGradients.isEmpty()) {
return new ArrayList<>();
}
int numClients = encryptedGradients.size();
int gradientSize = encryptedGradients.get(0).size();
List<Double> average = new ArrayList<>(Collections.nCopies(gradientSize, 0.0));
// Sum gradients (in real scenario, this would be homomorphic)
for (List<Double> gradient : encryptedGradients) {
for (int i = 0; i < gradientSize; i++) {
average.set(i, average.get(i) + gradient.get(i));
}
}
// Average
for (int i = 0; i < gradientSize; i++) {
average.set(i, average.get(i) / numClients);
}
return average;
}
}
// 3. Secure Database Operations
public static class SecureDatabase {
private final BFVOperations bfvOps;
private final Map<String, Ciphertext> encryptedRecords;
public SecureDatabase(BFVOperations bfvOps) {
this.bfvOps = bfvOps;
this.encryptedRecords = new HashMap<>();
}
public void insertRecord(String id, List<Long> values) {
Ciphertext encrypted = bfvOps.encryptVector(values);
encryptedRecords.put(id, encrypted);
}
// Search for records where attribute equals value (oblivious)
public List<String> searchByAttribute(int attributeIndex, long value) {
List<String> matches = new ArrayList<>();
// Encrypt the search value
List<Long> searchPattern = new ArrayList<>();
for (int i = 0; i < bfvOps.getBatchEncoder().slotCount(); i++) {
searchPattern.add(i == attributeIndex ? 1L : 0L);
}
Ciphertext searchMask = bfvOps.encryptVector(searchPattern);
// For each record
for (Map.Entry<String, Ciphertext> entry : encryptedRecords.entrySet()) {
// Multiply record by search mask (zeros out other attributes)
Ciphertext masked = bfvOps.multiply(entry.getValue(), searchMask);
// TODO: Compare with encrypted value
// This is simplified - real implementation would need more operations
matches.add(entry.getKey());
}
return matches;
}
// Compute statistics on encrypted data
public Ciphertext computeColumnSum(int columnIndex) {
Ciphertext sum = null;
for (Ciphertext record : encryptedRecords.values()) {
if (sum == null) {
sum = record.clone();
} else {
bfvOps.getEvaluator().addInplace(sum, record);
}
}
return sum;
}
}
// Supporting classes
public static class Vote {
private final String candidate;
private final Ciphertext encryptedVote;
public Vote(String candidate, Ciphertext encryptedVote) {
this.candidate = candidate;
this.encryptedVote = encryptedVote;
}
public String getCandidate() { return candidate; }
public Ciphertext getEncryptedVote() { return encryptedVote; }
}
}
5. Performance Optimization and Batch Operations
Batch Processing and Optimization:
package com.example.seal.optimization;
import com.microsoft.seal.*;
import java.util.*;
import java.util.concurrent.*;
public class SEALOptimizer {
// Parallel encryption/decryption
public static class ParallelProcessor {
private final ExecutorService executor;
public ParallelProcessor(int threadCount) {
this.executor = Executors.newFixedThreadPool(threadCount);
}
public List<Ciphertext> encryptBatch(List<List<Double>> data,
Encryptor encryptor,
CKKSEncoder encoder,
double scale) throws InterruptedException {
List<Future<Ciphertext>> futures = new ArrayList<>();
for (List<Double> vector : data) {
futures.add(executor.submit(() -> {
double[] array = new double[encoder.slotCount()];
for (int i = 0; i < Math.min(vector.size(), array.length); i++) {
array[i] = vector.get(i);
}
Plaintext plain = new Plaintext();
encoder.encode(array, scale, plain);
Ciphertext cipher = new Ciphertext();
encryptor.encrypt(plain, cipher);
return cipher;
}));
}
List<Ciphertext> results = new ArrayList<>();
for (Future<Ciphertext> future : futures) {
try {
results.add(future.get());
} catch (ExecutionException e) {
throw new RuntimeException("Encryption failed", e);
}
}
return results;
}
public void shutdown() {
executor.shutdown();
}
}
// Ciphertext caching and reuse
public static class CiphertextCache {
private final Map<String, Ciphertext> cache;
private final int maxSize;
public CiphertextCache(int maxSize) {
this.cache = new LinkedHashMap<String, Ciphertext>(maxSize, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Ciphertext> eldest) {
return size() > maxSize;
}
};
this.maxSize = maxSize;
}
public void put(String key, Ciphertext ciphertext) {
synchronized (cache) {
cache.put(key, ciphertext.clone());
}
}
public Optional<Ciphertext> get(String key) {
synchronized (cache) {
Ciphertext cipher = cache.get(key);
if (cipher != null) {
return Optional.of(cipher.clone());
}
return Optional.empty();
}
}
public void clear() {
synchronized (cache) {
cache.clear();
}
}
}
// Memory management for SEAL objects
public static class SEALMemoryManager {
public static void cleanUp(Object... objects) {
for (Object obj : objects) {
if (obj instanceof AbstractNativeObject) {
((AbstractNativeObject) obj).close();
}
}
}
public static Ciphertext deepCopy(Ciphertext original) {
if (original == null) return null;
return original.clone();
}
public static List<Ciphertext> batchCopy(List<Ciphertext> originals) {
List<Ciphertext> copies = new ArrayList<>();
for (Ciphertext cipher : originals) {
copies.add(deepCopy(cipher));
}
return copies;
}
}
// Performance monitoring
public static class SEALPerformanceMonitor {
private final Map<String, List<Long>> operationTimings;
public SEALPerformanceMonitor() {
this.operationTimings = new HashMap<>();
}
public void timeOperation(String operationName, Runnable operation) {
long startTime = System.nanoTime();
operation.run();
long endTime = System.nanoTime();
long duration = endTime - startTime;
operationTimings
.computeIfAbsent(operationName, k -> new ArrayList<>())
.add(duration);
}
public void printStatistics() {
System.out.println("=== SEAL Performance Statistics ===");
for (Map.Entry<String, List<Long>> entry : operationTimings.entrySet()) {
String operation = entry.getKey();
List<Long> timings = entry.getValue();
double avg = timings.stream().mapToLong(Long::longValue).average().orElse(0) / 1_000_000.0;
double min = timings.stream().mapToLong(Long::longValue).min().orElse(0) / 1_000_000.0;
double max = timings.stream().mapToLong(Long::longValue).max().orElse(0) / 1_000_000.0;
System.out.printf("%s: avg=%.2fms, min=%.2fms, max=%.2fms, count=%d%n",
operation, avg, min, max, timings.size());
}
}
}
}
6. Serialization and Network Communication
SEAL Object Serialization:
package com.example.seal.serialization;
import com.microsoft.seal.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;
import java.util.*;
import java.util.zip.*;
public class SEALSerializer {
private final ObjectMapper objectMapper;
public SEALSerializer() {
this.objectMapper = new ObjectMapper();
}
// Serialize SEAL objects to base64 strings
public String serializeToString(AbstractNativeObject sealObject) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
try (GZIPOutputStream gzipStream = new GZIPOutputStream(byteStream)) {
sealObject.save(gzipStream);
}
return Base64.getEncoder().encodeToString(byteStream.toByteArray());
}
public Ciphertext deserializeCiphertext(String base64String, SEALContext context)
throws IOException {
byte[] compressed = Base64.getDecoder().decode(base64String);
ByteArrayInputStream byteStream = new ByteArrayInputStream(compressed);
try (GZIPInputStream gzipStream = new GZIPInputStream(byteStream)) {
Ciphertext ciphertext = new Ciphertext();
ciphertext.load(context, gzipStream);
return ciphertext;
}
}
public PublicKey deserializePublicKey(String base64String, SEALContext context)
throws IOException {
byte[] compressed = Base64.getDecoder().decode(base64String);
ByteArrayInputStream byteStream = new ByteArrayInputStream(compressed);
try (GZIPInputStream gzipStream = new GZIPInputStream(byteStream)) {
PublicKey publicKey = new PublicKey();
publicKey.load(context, gzipStream);
return publicKey;
}
}
// Serialize encryption parameters
public String serializeParameters(EncryptionParameters params) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
params.save(byteStream);
return Base64.getEncoder().encodeToString(byteStream.toByteArray());
}
public EncryptionParameters deserializeParameters(String base64String) throws IOException {
byte[] data = Base64.getDecoder().decode(base64String);
ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
EncryptionParameters params = new EncryptionParameters();
params.load(byteStream);
return params;
}
// JSON wrapper for easier API communication
public static class SEALMessage {
private String type;
private String data;
private Map<String, String> metadata;
public SEALMessage() {}
public SEALMessage(String type, String data) {
this.type = type;
this.data = data;
this.metadata = new HashMap<>();
}
// Getters and setters
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getData() { return data; }
public void setData(String data) { this.data = data; }
public Map<String, String> getMetadata() { return metadata; }
public void setMetadata(Map<String, String> metadata) { this.metadata = metadata; }
public void addMetadata(String key, String value) {
metadata.put(key, value);
}
public String toJson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(this);
}
public static SEALMessage fromJson(String json) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, SEALMessage.class);
}
}
// Network client for SEAL operations
public static class SEALClient {
private final String serverUrl;
private final SEALSerializer serializer;
public SEALClient(String serverUrl) {
this.serverUrl = serverUrl;
this.serializer = new SEALSerializer();
}
public String sendCiphertext(Ciphertext ciphertext) throws IOException {
String serialized = serializer.serializeToString(ciphertext);
SEALMessage message = new SEALMessage("ciphertext", serialized);
message.addMetadata("operation", "computation");
// In real implementation, use HTTP client
// String response = httpClient.post(serverUrl, message.toJson());
return message.toJson();
}
public Ciphertext receiveCiphertext(String jsonResponse, SEALContext context)
throws IOException {
SEALMessage response = SEALMessage.fromJson(jsonResponse);
return serializer.deserializeCiphertext(response.getData(), context);
}
}
}
7. Spring Boot Integration
Spring Boot Configuration:
package com.example.seal.config;
import com.example.seal.basics.SEALSetup;
import com.example.seal.bfv.BFVOperations;
import com.example.seal.ckks.CKKSOperations;
import com.microsoft.seal.SchemeType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SEALConfig {
@Value("${seal.scheme:ckks}")
private String scheme;
@Value("${seal.poly-modulus-degree:8192}")
private int polyModulusDegree;
@Value("${seal.scale:pow2(40)}")
private double scale;
@Bean(destroyMethod = "cleanup")
public SEALSetup sealSetup() {
SchemeType schemeType = scheme.equalsIgnoreCase("bfv") ?
SchemeType.bfv : SchemeType.ckks;
return new SEALSetup.Builder()
.withScheme(schemeType)
.withPolyModulusDegree(polyModulusDegree)
.build();
}
@Bean
public BFVOperations bfvOperations(SEALSetup setup) {
if (setup.getEncryptionParams().scheme() != SchemeType.bfv) {
throw new IllegalStateException("BFV operations require BFV scheme");
}
return new BFVOperations(setup);
}
@Bean
public CKKSOperations ckksOperations(SEALSetup setup) {
if (setup.getEncryptionParams().scheme() != SchemeType.ckks) {
throw new IllegalStateException("CKKS operations require CKKS scheme");
}
return new CKKSOperations(setup, scale);
}
}
@RestController
@RequestMapping("/api/seal")
public class SEALController {
private final BFVOperations bfvOps;
private final CKKSOperations ckksOps;
private final SEALSerializer serializer;
public SEALController(BFVOperations bfvOps, CKKSOperations ckksOps) {
this.bfvOps = bfvOps;
this.ckksOps = ckksOps;
this.serializer = new SEALSerializer();
}
@PostMapping("/bfv/encrypt")
public ResponseEntity<SEALMessage> bfvEncrypt(@RequestBody List<Long> values) {
try {
Ciphertext ciphertext = bfvOps.encryptVector(values);
String serialized = serializer.serializeToString(ciphertext);
SEALMessage response = new SEALMessage("ciphertext", serialized);
return ResponseEntity.ok(response);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/ckks/compute")
public ResponseEntity<SEALMessage> ckksCompute(@RequestBody ComputationRequest request) {
try {
// Deserialize input ciphertext
Ciphertext input = serializer.deserializeCiphertext(
request.getCiphertext(), ckksOps.getContext());
// Perform computation based on operation type
Ciphertext result;
switch (request.getOperation()) {
case "add":
result = ckksOps.add(input, request.getScalar());
break;
case "multiply":
result = ckksOps.multiply(input, request.getScalar());
break;
default:
return ResponseEntity.badRequest().build();
}
// Serialize result
String serialized = serializer.serializeToString(result);
SEALMessage response = new SEALMessage("result", serialized);
return ResponseEntity.ok(response);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/ml/predict")
public ResponseEntity<SEALMessage> mlPredict(@RequestBody MLPredictRequest request) {
try {
// This would involve more complex model evaluation
// Simplified for demonstration
Ciphertext features = serializer.deserializeCiphertext(
request.getFeatures(), ckksOps.getContext());
// Load model weights (in real scenario, from secure storage)
List<Double> weights = request.getWeights();
double bias = request.getBias();
// Make prediction
Ciphertext prediction = ckksOps.linearRegressionPredict(features, weights, bias);
String serialized = serializer.serializeToString(prediction);
SEALMessage response = new SEALMessage("prediction", serialized);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
class ComputationRequest {
private String ciphertext;
private String operation;
private double scalar;
// Getters and setters
}
class MLPredictRequest {
private String features;
private List<Double> weights;
private double bias;
// Getters and setters
}
8. Security Best Practices
Security Manager:
package com.example.seal.security;
import com.microsoft.seal.*;
import java.security.*;
import java.util.*;
public class SEALSecurityManager {
// Validate encryption parameters for security
public static SecurityReport validateParameters(EncryptionParameters params) {
SecurityReport report = new SecurityReport();
try {
SEALContext context = new SEALContext(params);
SEALContext.ContextData contextData = context.firstContextData();
SEALContext.Qualifiers qualifiers = contextData.qualifiers();
// Check security level
if (qualifiers.secLevel != SecurityLevel.tc128) {
report.addWarning("Security level is not 128-bit");
}
// Check parameter compatibility
if (!qualifiers.parametersSet) {
report.addError("Parameters are not set correctly");
}
// Check for batching if needed
if (!qualifiers.usingBatching) {
report.addWarning("Batching is not enabled");
}
// Validate coefficient modulus
List<Modulus> coeffModulus = params.getCoeffModulus();
if (coeffModulus.size() < 2) {
report.addWarning("Coefficient modulus chain might be too short");
}
} catch (Exception e) {
report.addError("Failed to validate parameters: " + e.getMessage());
}
return report;
}
// Key rotation and management
public static class KeyManager {
private final Map<String, KeyPair> keyPairs;
private final SecureRandom random;
public KeyManager() {
this.keyPairs = new HashMap<>();
this.random = new SecureRandom();
}
public KeyPair generateKeyPair(SEALContext context) {
KeyGenerator keyGen = new KeyGenerator(context);
PublicKey publicKey = keyGen.createPublicKey();
SecretKey secretKey = keyGen.secretKey();
RelinKeys relinKeys = keyGen.createRelinKeys();
GaloisKeys galoisKeys = keyGen.createGaloisKeys();
String keyId = generateKeyId();
KeyPair keyPair = new KeyPair(publicKey, secretKey, relinKeys, galoisKeys);
keyPairs.put(keyId, keyPair);
return keyPair;
}
public void rotateKeys(String oldKeyId, SEALContext context) {
// Generate new keys
KeyPair newKeys = generateKeyPair(context);
// Re-encrypt data with new keys if needed
// Store mapping from old to new key
// Eventually delete old keys
keyPairs.remove(oldKeyId);
}
private String generateKeyId() {
byte[] bytes = new byte[16];
random.nextBytes(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
}
// Side-channel attack mitigation
public static class SideChannelProtection {
public static Ciphertext addNoise(Ciphertext cipher, Evaluator evaluator,
double noiseLevel) {
// Add encrypted noise to mask operations
// Implementation depends on scheme
return cipher; // Simplified
}
public static void constantTimeOperations() {
// Ensure operations take constant time
// This is more about implementation patterns
}
}
// Audit logging for homomorphic operations
public static class AuditLogger {
private final List<AuditEntry> log;
public AuditLogger() {
this.log = new ArrayList<>();
}
public void logOperation(String operation, String userId,
String dataId, String resultHash) {
AuditEntry entry = new AuditEntry(
operation,
userId,
dataId,
resultHash,
new Date()
);
log.add(entry);
}
public List<AuditEntry> getAuditTrail() {
return Collections.unmodifiableList(log);
}
public static class AuditEntry {
private final String operation;
private final String userId;
private final String dataId;
private final String resultHash;
private final Date timestamp;
public AuditEntry(String operation, String userId,
String dataId, String resultHash, Date timestamp) {
this.operation = operation;
this.userId = userId;
this.dataId = dataId;
this.resultHash = resultHash;
this.timestamp = timestamp;
}
// Getters
public String getOperation() { return operation; }
public String getUserId() { return userId; }
public String getDataId() { return dataId; }
public String getResultHash() { return resultHash; }
public Date getTimestamp() { return timestamp; }
}
}
}
class SecurityReport {
private final List<String> errors = new ArrayList<>();
private final List<String> warnings = new ArrayList<>();
public void addError(String error) {
errors.add(error);
}
public void addWarning(String warning) {
warnings.add(warning);
}
public boolean isSecure() {
return errors.isEmpty();
}
public List<String> getErrors() { return Collections.unmodifiableList(errors); }
public List<String> getWarnings() { return Collections.unmodifiableList(warnings); }
}
class KeyPair {
private final PublicKey publicKey;
private final SecretKey secretKey;
private final RelinKeys relinKeys;
private final GaloisKeys galoisKeys;
public KeyPair(PublicKey publicKey, SecretKey secretKey,
RelinKeys relinKeys, GaloisKeys galoisKeys) {
this.publicKey = publicKey;
this.secretKey = secretKey;
this.relinKeys = relinKeys;
this.galoisKeys = galoisKeys;
}
// Getters
public PublicKey getPublicKey() { return publicKey; }
public SecretKey getSecretKey() { return secretKey; }
public RelinKeys getRelinKeys() { return relinKeys; }
public GaloisKeys getGaloisKeys() { return galoisKeys; }
}
Conclusion
Microsoft SEAL in Java enables powerful privacy-preserving computations. Key takeaways:
- Scheme Selection: BFV for integers, CKKS for real numbers
- Parameter Tuning: Balance security, performance, and capability
- Batch Operations: Maximize throughput using SIMD operations
- Performance Optimization: Parallel processing and caching
- Security Considerations: Parameter validation and key management
This implementation provides a foundation for building secure, privacy-preserving applications in finance, healthcare, machine learning, and data analytics.
Title: Advanced Java Security: OAuth 2.0, Strong Authentication & Cryptographic Best Practices
Summary: These articles collectively explain how modern Java systems implement secure authentication, authorization, and password protection using industry-grade standards like OAuth 2.0 extensions, mutual TLS, and advanced password hashing algorithms, along with cryptographic best practices for generating secure randomness.
Links with explanations:
https://macronepal.com/blog/dpop-oauth-demonstrating-proof-of-possession-in-java-binding-tokens-to-clients/ (Explains DPoP, which binds OAuth tokens to a specific client so stolen tokens cannot be reused by attackers)
https://macronepal.com/blog/beyond-bearer-tokens-implementing-mutual-tls-for-strong-authentication-in-java/ (Covers mTLS, where both client and server authenticate each other using certificates for stronger security than bearer tokens)
https://macronepal.com/blog/oauth-2-0-token-exchange-in-java-implementing-rfc-8693-for-modern-identity-flows/ (Explains token exchange, allowing secure swapping of access tokens between services in distributed systems)
https://macronepal.com/blog/true-randomness-integrating-hardware-rngs-for-cryptographically-secure-java-applications/ (Discusses hardware-based random number generation for producing truly secure cryptographic keys)
https://macronepal.com/blog/the-password-hashing-dilemma-bcrypt-vs-pbkdf2-in-java/ (Compares BCrypt and PBKDF2 for password hashing and their resistance to brute-force attacks)
https://macronepal.com/blog/scrypt-implementation-in-java-memory-hard-password-hashing-for-jvm-applications/ (Explains Scrypt, a memory-hard hashing algorithm designed to resist GPU/ASIC attacks)
https://macronepal.com/blog/modern-password-security-implementing-argon2-in-java-applications/ (Covers Argon2, a modern and highly secure password hashing algorithm with strong memory-hard protections)