Problem
Homomorphic encryption is a cryptographic algorithm that lets computations be performed directly on encrypted data without needing to decrypt it. This enables secure outsourcing of computations on sensitive data while preserving privacy.
Is it possible to have homomorphic encryption in SQL Server?
Solution
Fully homomorphic encryption (FHE) lets organizations securely process, analyze, and share encrypted data in untrusted environments without ever decrypting it. Enabling true zero-trust computing, supporting AI, machine learning, and big data analytics while maintaining privacy and compliance in applications such as financial predictive modeling, healthcare research, and consumer behavior insights.
Terms and definitions
Let’s define some key terms before we dive into our first example:
Public Key Pair
Public Key Pair is a cryptographic key, paired with a private key, that can be shared openly and is used to verify digital signatures created with the corresponding private key.
Private Key Pair
Private Key Pair is a secret cryptographic key, unique to its owner, used to create digital signatures that can be verified with the corresponding public key.
Paillier Cryptosystem
Paillier Cryptosystem is a probabilistic asymmetric algorithm for public key cryptography.
True Zero-Trust Computing
True Zero-Trust Computing means that no system, user, or environment is ever inherently trusted, where every interaction must be verified, and sensitive data is never exposed in unsecure form.
Average Blood Pressure
Average blood pressure means the overall pressure in your arteries during a heartbeat cycle. Since your heart spends more time resting (diastolic) than pumping (systolic), doctors calculate the Average BP = (2 * Diastolic + Systolic) / 3.
Example
FHE enables computation on encrypted data, but is currently slower, heavier, and harder to use than traditional methods, but though research and with hardware acceleration are rapidly closing the gap.
Besides the fact that SQL Server has the bigint data type, this data type has a limitation of 64 bits. When working with encryption it is normal to work with numbers like 2^1024 which has 308 decimal digits, which is far beyond the bigint capacity.
One option is to work with Python, but due to some incompatibilities I faced, I decide to create a CLR solution for this tip.
Visual Studio
Open Visual Studio:
- Get started choosing Create a new project
- Choose the template of Class Library (.Net framework) – Visual Basic
- I will call the Project SqlMathClr and click Create
- Rename the Class1.vb file to SqlMath.vb
- Replace the content of SqlMath.vb with the code below
- Build the solution
- Record the path of the created SqlMathClr.dll, in my case “E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release”
SqlMath.vb code:
Imports System.Data.SqlTypes
Imports System.Numerics
Imports Microsoft.SqlServer.Server
Public Class SqlMath
Private Shared ReadOnly rnd As New Random()
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigAdd(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return New SqlString(BigInteger.Add(a, b).ToString())
End Function
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigDivide(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return New SqlString(BigInteger.Divide(a, b).ToString())
End Function
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigGCD(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return New SqlString(BigInteger.GreatestCommonDivisor(a, b).ToString())
End Function
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigLCM(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
If a.IsZero OrElse b.IsZero Then
Return New SqlString("0")
End If
Dim gcd As BigInteger = BigInteger.GreatestCommonDivisor(a, b)
Dim lcm As BigInteger = BigInteger.Abs(a * b / gcd)
Return New SqlString(lcm.ToString())
End Function
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigMod(num As SqlString, modNum As SqlString) As SqlString
Dim n As BigInteger = BigInteger.Parse(num.Value)
Dim m As BigInteger = BigInteger.Parse(modNum.Value)
Return New SqlString(BigInteger.Remainder(n, m).ToString())
End Function
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigModPow(baseNum As SqlString, exponent As SqlString, modNum As SqlString) As SqlString
Dim b As BigInteger = BigInteger.Parse(baseNum.Value)
Dim e As BigInteger = BigInteger.Parse(exponent.Value)
Dim m As BigInteger = BigInteger.Parse(modNum.Value)
Return New SqlString(BigInteger.ModPow(b, e, m).ToString())
End Function
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigMultiply(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return New SqlString(BigInteger.Multiply(a, b).ToString())
End Function
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigPow(baseNum As SqlString, exponent As SqlInt32) As SqlString
Dim baseBI As BigInteger = BigInteger.Parse(baseNum.Value)
Dim result As BigInteger = BigInteger.Pow(baseBI, exponent.Value)
Return New SqlString(result.ToString())
End Function
<SqlFunction(IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigSubtract(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return New SqlString(BigInteger.Subtract(a, b).ToString())
End Function
<SqlFunction(IsDeterministic:=False, IsPrecise:=True)>
Public Shared Function GenerateCoprime(num1 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim candidate As BigInteger
If a <= 1 Then
Throw New ArgumentException("Number must be greater than 1")
End If
Do
candidate = RandomBigInteger(a)
Loop While BigInteger.GreatestCommonDivisor(candidate, a) <> 1
Return New SqlString(candidate.ToString())
End Function
<SqlFunction()>
Public Shared Function HDecrypt(pkn As SqlString, pkg As SqlString, skLambda As SqlString, skmu As SqlString, num As SqlString) As SqlString
Dim n As BigInteger = BigInteger.Parse(pkn.Value)
Dim n2 As BigInteger = BigInteger.Multiply(n, n)
Dim g As BigInteger = BigInteger.Parse(pkg.Value)
Dim c As BigInteger = BigInteger.Parse(num.Value)
Dim s As BigInteger = BigInteger.Parse(skLambda.Value)
Dim m As BigInteger = BigInteger.Parse(skmu.Value)
Dim u As BigInteger = BigInteger.ModPow(c, s, n2)
Dim L As BigInteger = BigInteger.Divide((u - 1), n)
Dim x As BigInteger = (L * m) Mod n
Return x.ToString
End Function
<SqlFunction()>
Public Shared Function HEncrypt(pkn As SqlString, pkg As SqlString, num As SqlString) As SqlString
Dim n As BigInteger = BigInteger.Parse(pkn.Value)
Dim n2 As BigInteger = BigInteger.Multiply(n, n)
Dim g As BigInteger = BigInteger.Parse(pkg.Value)
Dim m As BigInteger = BigInteger.Parse(num.Value)
Dim r As BigInteger = BigInteger.Parse(GenerateCoprime(pkn))
Dim x As BigInteger = (BigInteger.ModPow(g, m, n2) * BigInteger.ModPow(r, n, n2)) Mod n2
Return x.ToString
End Function
<SqlFunction()>
Public Shared Function ModInverse(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return ModInverseInternal(a, b).ToString()
End Function
Private Shared Function ModInverseInternal(a As BigInteger, m As BigInteger) As BigInteger
Dim t As BigInteger = 0
Dim newT As BigInteger = 1
Dim r As BigInteger = m
Dim newR As BigInteger = a
While newR <> 0
Dim q As BigInteger = BigInteger.Divide(r, newR)
Dim tmpT As BigInteger = t
t = newT
newT = tmpT - q * newT
Dim tmpR As BigInteger = r
r = newR
newR = tmpR - q * newR
End While
If r > 1 Then
Throw New ArgumentException("a is not invertible modulo m")
End If
If t < 0 Then
t += m
End If
Return t
End Function
Private Shared Function RandomBigInteger(max As BigInteger) As BigInteger
Dim bytes(max.ToByteArray().Length - 1) As Byte
Dim result As BigInteger
Do
rnd.NextBytes(bytes)
bytes(bytes.Length - 1) = 0
result = New BigInteger(bytes)
Loop While result <= 1 OrElse result >= max
Return result
End Function
End ClassIf you review the code, you will see that I am returning all of the values as string due to overflow problems with the bigint data type in SQL Server.
SQL Server CLR
The first thing that you need to do it to enable CLR:
-- MSSQLTips (TSQL)
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE;
GOIt is necessary for security reasons to add the dll as trusted assembly. Change the path to your destination.
-- MSSQLTips (TSQL)
DECLARE @hash varbinary(64) =
(SELECT HASHBYTES('SHA2_512', BulkColumn)
FROM OPENROWSET(BULK 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll', SINGLE_BLOB) AS AssemblyBlob);
EXEC sp_add_trusted_assembly
@assembly_hash = @hash,
@description = N'SQL Math CLR assembly';
GONow it is necessary to register the assembly, creating it. Change the path to your destination.
-- MSSQLTips (TSQL)
CREATE ASSEMBLY [AsmMath]
FROM 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll'
WITH PERMISSION_SET = SAFE;
GOIn case of you are updating the already created AsmMath, execute it again and change the path to your destination.
-- MSSQLTips (TSQL)
ALTER ASSEMBLY [AsmMath]
FROM 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll'
WITH PERMISSION_SET = SAFE;
GOI will create a schema to group all CLR functions.
-- MSSQLTips (TSQL)
CREATE SCHEMA AsmMath;
GONow we are going to add all functions to the schema.
-- MSSQLTips (TSQL)
CREATE FUNCTION AsmMath.BigAdd(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigAdd;
GO
CREATE FUNCTION AsmMath.BigDivide(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigDivide;
GO
CREATE FUNCTION AsmMath.BigGCD(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigGCD;
GO
CREATE FUNCTION AsmMath.BigLCM(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigLCM;
GO
CREATE FUNCTION AsmMath.BigMod(@a nvarchar(MAX), @m nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigMod;
GO
CREATE FUNCTION AsmMath.BigModInverse(@a NVARCHAR(MAX), @m NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].ModInverse;
GO
CREATE FUNCTION AsmMath.BigModPow(@a nvarchar(MAX), @exp nvarchar(MAX), @m nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigModPow;
GO
CREATE FUNCTION AsmMath.BigMultiply(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigMultiply;
GO
CREATE FUNCTION AsmMath.BigPow(@a nvarchar(MAX), @exp INT)
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigPow;
GO
CREATE FUNCTION AsmMath.BigSubtract(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigSubtract;
GO
CREATE FUNCTION AsmMath.GenCoprime(@a NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].GenCoprime;
GO
CREATE FUNCTION AsmMath.HEncrypt(@pkn NVARCHAR(MAX), @pkg NVARCHAR(MAX), @num NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].HEncrypt;
GO
CREATE FUNCTION AsmMath.HDecrypt(@pkn NVARCHAR(MAX), @pkg NVARCHAR(MAX), @sklambda NVARCHAR(MAX), @skmu NVARCHAR(MAX), @num NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].HDecrypt;
GOIf you want to verify the assembler run this code:
-- MSSQLTips (TSQL)
SELECT [name]
,[clr_name]
,[create_date]
,[modify_date]
FROM sys.assemblies
WHERE [name] = 'AsmMath';
GOIn case you need to remove the CLR, run this code:
-- MSSQLTips (TSQL)
DROP FUNCTION IF EXISTS AsmMath.BigAdd;
DROP FUNCTION IF EXISTS AsmMath.BigDivide;
DROP FUNCTION IF EXISTS AsmMath.BigGCD;
DROP FUNCTION IF EXISTS AsmMath.BigLCM;
DROP FUNCTION IF EXISTS AsmMath.BigMod;
DROP FUNCTION IF EXISTS AsmMath.BigModInverse;
DROP FUNCTION IF EXISTS AsmMath.BigModPow;
DROP FUNCTION IF EXISTS AsmMath.BigMultiply;
DROP FUNCTION IF EXISTS AsmMath.BigPow;
DROP FUNCTION IF EXISTS AsmMath.BigSubtract;
DROP FUNCTION IF EXISTS AsmMath.GenCoprime;
DROP FUNCTION IF EXISTS AsmMath.HDecrypt;
DROP FUNCTION IF EXISTS AsmMath.HEncrypt;
DROP ASSEMBLY IF EXISTS AsmMath;
GOSQL Server function
The first function is to create a prime number:
-- =============================================
-- Author: SCP - MSSQLTips
-- Create date: 20250822
-- Description: Generate a large prime number
-- =============================================
CREATE OR ALTER FUNCTION [dbo].[ufnNumberPrimeNew]
(@Seed bigint)
RETURNS bigint
AS
BEGIN
DECLARE @max bigint = 999999999999999;
DECLARE @prime bigint;
SET @prime = @Seed;
WHILE @prime <= @max BEGIN
IF [dbo].[ufnNumberPrimeChk] (@prime) = 1
BREAK;
SET @prime = @prime + 1;
END
RETURN @prime;
END
GOThe second function is to check if the number is prime:
-- =============================================
-- Author: SCP - MSSQLTips
-- Create date: 20250822
-- Description: Check if a number is prime
-- =============================================
CREATE OR ALTER FUNCTION [dbo].[ufnNumberPrimeChk]
(@n bigint)
RETURNS bit
AS
BEGIN
IF @n < 2 OR @n % 2 = 0
RETURN 0;
IF @n IN (2,3)
RETURN 1;
DECLARE @i bigint = 2;
WHILE @i * @i <= @n BEGIN
IF @n % @i = 0
RETURN 0;
SET @i += 1;
END
RETURN 1;
END
GOSQL Server Table
Patients Blood Pressure
CREATE TABLE [dbo].[PatientsBloodPressure](
[Id] [int] NOT NULL,
[DummySystolic] [decimal](10, 1) NULL,
[DummyDiastolic] [decimal](10, 1) NULL,
[DummyAvg] [decimal](10, 1) NULL,
[BPEncrypted] [nvarchar](max) NULL,
CONSTRAINT [PK_PatientBloodPressure] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GOExamples
Testing the assembly’s:
-- MSSQLTips (TSQL)
SELECT AsmMath.BigPow('2', 1024) AS TwoPower1024;
SELECT AsmMath.BigAdd('12345678901234567890', '98765432109876543210') AS BigAdd;
SELECT AsmMath.BigMultiply('12345678901234567890', '98765432109876543210') AS BigProduct;
SELECT AsmMath.BigGCD('12345678901234567890', '98765432109876543210') AS GCD;
SELECT AsmMath.BigLCM('12345678901234567890', '98765432109876543210') AS LCM;
SELECT AsmMath.BigModPow('987654321', '12345', '1000000007') AS ModExp;
SELECT AsmMath.GenCoprime(1091555129684048179) AS Coprime;
GOResulting in:

Blood pressure study
In healthcare, AES is ideal for encrypting patient records stored internally, allowing trusted staff to decrypt and process data as needed. Fully homomorphic encryption (FHE), on the other hand, is used when sensitive patient data must remain confidential while enabling external parties to perform analytics or machine learning on it without ever accessing the raw data, ensuring privacy compliance while still extracting valuable insights.
A certain hospital wants to collaborate with a third-party research organization in a study to calculate the average blood pressure of its patients, but privacy laws prevent sharing raw patient information.
To ensure confidentiality, the hospital first generates a public–private key pair. The public key is shared with patients, while the private key remains securely with the hospital manager.
Each patient is provided with an IoT blood pressure device. The device records the measurement and sends the result to the hospital through an API, encrypted with the hospital’s public key.
The hospital then collects all encrypted values and, using homomorphic encryption techniques, can compute the average blood pressure across all patients without ever decrypting individual measurements. This guarantees that no patient’s personal data is exposed at any stage of the study.
Generating the public and private keys
The first step is to generate two different prime numbers and then follow the process to obtain the keys:
-- MSSQLTips (TSQL)
DECLARE @p bigint = [dbo].[ufnNumberPrimeNew] (FLOOR(RAND(CHECKSUM(NEWID())) * 2147483647));
DECLARE @q bigint = @p;
WHILE @p = @q
SET @q = [dbo].[ufnNumberPrimeNew] (FLOOR(RAND(CHECKSUM(NEWID())) * 2147483647));
DECLARE @n bigint = [AsmMath].[BigMultiply] (@p,@q);
DECLARE @n2 nvarchar(MAX) = [AsmMath].[BigMultiply] (@n,@n);
DECLARE @lambda bigint = [AsmMath].[BigLCM] (@p - 1,@q - 1);
DECLARE @g bigint = @n + 1;
DECLARE @u nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@lambda,@n2);
DECLARE @l nvarchar(MAX) = [AsmMath].[BigDivide] ([AsmMath].[BigSubtract] (@u,1), @n);
DECLARE @mu bigint = [AsmMath].[BigModInverse] (@l, @n);
SELECT 'Public Key',@n pkn, @g pkg;
SELECT 'Private Key',@lambda sklambda, @mu skmu;
GOResulting in:

For this example, we assume you are the hospital manager and will handle the private key protection. A dummy column will store unencrypted records to enable average calculations and also for us to visualize the real data. If you want you can change the values below to your previous generated ones.
-- MSSQLTips (TSQL)
-- PUBLIC KEYS ==================================
DECLARE @pkn bigint = '953070681873550127';
DECLARE @pkg bigint = '953070681873550128';
-- PRIVATE KEYS =================================
DECLARE @sklambda bigint = '476535339907528560';
DECLARE @skmu bigint = '180581948158477459';Just to explain how it works lets sum the value of two encrypted numbers m1 and m2
-- MSSQLTips (TSQL)
-- PUBLIC KEYS ==================================
DECLARE @pkn bigint = '953070681873550127';
DECLARE @pkg bigint = '953070681873550128';
-- PRIVATE KEYS =================================
DECLARE @sklambda bigint = '476535339907528560';
DECLARE @skmu bigint = '180581948158477459';
DECLARE @m1 bigint = 10;
DECLARE @m2 bigint = 20;
DECLARE @n2 nvarchar(MAX) = [AsmMath].[BigMultiply] (@pkn,@pkn);
-- Encryption of m1 and m2
DECLARE @c1 nvarchar(MAX) = [AsmMath].[HEncrypt] (@pkn,@pkg,@m1);
DECLARE @c2 nvarchar(MAX) = [AsmMath].[HEncrypt] (@pkn,@pkg,@m2);
-- Sum of the encrypted values c1 and c2
DECLARE @cSum nvarchar(MAX) = [AsmMath].[BigMod] ([AsmMath].[BigMultiply](@c1,@c2),@n2);
DECLARE @u nvarchar(MAX) = [AsmMath].[BigModPow] (@cSum,@sklambda,@n2);
DECLARE @l nvarchar(MAX)= [AsmMath].[BigDivide] ([AsmMath].[BigSubtract] (@u,1), @pkn);
SELECT [AsmMath].[HDecrypt] (@pkn,@pkg,@sklambda,@skmu,@c1) m1
,@c1 m1Encrypted
,[AsmMath].[HDecrypt] (@pkn,@pkg,@sklambda,@skmu,@c2) m2
,@c2 m2Encrypted
,[AsmMath].[BigMod] ([AsmMath].[BigMultiply] (@l,@skmu),@pkn) AS Total
,@cSum TotalEncrypted;
GOResulting in the expected sum of values encrypted where I show the decrypted values to confirm.

Back to our hospital example at the patient side, each patient has its IOT to measure the Blood Pressure and send it throughout an API to the hospital, I will simulate 10 patients that individually send us data. The patient side only need the public key to work. Since the SQL bigint data type has limitations, I will save the generated number as a string and also uses a decimal place constant to scale the number to a integer before encryption.
-- PUBLIC KEYS ==================================
DECLARE @pkn bigint = '953070681873550127';
DECLARE @pkg bigint = '953070681873550128';
DECLARE @BP
TABLE (id int IDENTITY
,DummySystolic decimal(10,1)
,DummyDiastolic decimal(10,1)
,DummyAvg int
,BPEncrypted nvarchar(MAX));
DECLARE @i int = 0
,@Patients int = 10
,@DecimalPlaces int = 1
,@Systolic decimal(10,1)
,@Diastolic decimal(10,1)
,@BPAvg decimal(10,1)
,@BPAvgInt int;
TRUNCATE TABLE [dbo].[PatientsBloodPressure];
WHILE @i < @Patients BEGIN
SET @Systolic = (RAND(CHECKSUM(NEWID())) * 26 + 111);
SET @Diastolic = (RAND(CHECKSUM(NEWID())) * 33 + 61);
SET @BPAvg = ((@Systolic + @Diastolic * 2) / 3.0);
SET @BPAvgInt = @BPAvg * POWER(10,@DecimalPlaces) + 0.5;
INSERT INTO [dbo].[PatientsBloodPressure]
VALUES (@i + 1
,@Systolic
,@Diastolic
,@BPAvg
,[AsmMath].[HEncrypt] (@pkn,@pkg,@BPAvgInt));
SET @i += 1;
END
SELECT [Id]
,[DummySystolic]
,[DummyDiastolic]
,[DummyAvg]
,[BPEncrypted]
FROM [dbo].[PatientsBloodPressure];
GOResulting in:

The only number that will be sent is the BPEncrypted, I kept the other values for demonstration purposes to illustrate how this tip works.
Now at the hospital side I want to know the average value of the encrypted average blood pressure. Now I need both keys.
-- MSSQLTips (TSQL)
-- PUBLIC KEYS ==================================
DECLARE @pkn bigint = '953070681873550127';
DECLARE @pkg bigint = '953070681873550128';
-- PRIVATE KEYS =================================
DECLARE @sklambda bigint = '476535339907528560';
DECLARE @skmu bigint = '180581948158477459';
DECLARE @DecimalPlaces int = 1
,@m1 nvarchar(MAX)
,@count float = 0
,@Sum nvarchar(MAX) = '';
DECLARE @n2 nvarchar(MAX) = [AsmMath].[BigMultiply] (@pkn,@pkn);
DECLARE crsPat CURSOR FAST_FORWARD READ_ONLY FOR
SELECT [BPEncrypted]
FROM [dbo].[PatientsBloodPressure]
OPEN crsPat
FETCH NEXT FROM crsPat INTO @m1
WHILE @@FETCH_STATUS = 0 BEGIN
IF LEN(@Sum) = 0
SET @Sum = [AsmMath].[BigMultiply](@m1,1);
ELSE
SET @Sum = [AsmMath].[BigMod] ([AsmMath].[BigMultiply](@m1,@Sum),@n2); -- multiply and rescale
SET @count += 1;
FETCH NEXT FROM crsPat INTO @m1
END
CLOSE crsPat
DEALLOCATE crsPat
DECLARE @u nvarchar(MAX) = [AsmMath].[BigModPow] (@Sum,@sklambda,@n2);
DECLARE @l nvarchar(MAX)= [AsmMath].[BigDivide] ([AsmMath].[BigSubtract] (@u,1), @pkn);
SELECT AVG(DummySystolic) AS BPSyst
,AVG(DummyDiastolic) AS BPDiast
,(AVG(DummyDiastolic) * 2 + AVG(DummySystolic)) / 3.0 AS BPCalc
,AVG(DummyAvg) AS BPAvg
,[AsmMath].[BigMod] ([AsmMath].[BigMultiply] (@l,@skmu),@pkn) / @count / POWER(10,@DecimalPlaces) AS BPAvgFromEnc
FROM [dbo].[PatientsBloodPressure];
GOResulting in:

Observe that I have the average of the measures without decrypting the original values. The dummy columns are here only to confirm this.
If we want to show the decryption of the blood pressure values, run this code:
-- MSSQLTips (TSQL)
-- PUBLIC KEYS ==================================
DECLARE @pkn bigint = '953070681873550127';
DECLARE @pkg bigint = '953070681873550128';
-- PRIVATE KEYS =================================
DECLARE @sklambda bigint = '476535339907528560';
DECLARE @skmu bigint = '180581948158477459';
DECLARE @DecimalPlaces int = 1
SELECT [Id]
,[DummySystolic]
,[DummyDiastolic]
,[DummyAvg]
,CONVERT(float,[AsmMath].[HDecrypt] (@pkn,@pkg,@sklambda,@skmu,BPEncrypted)) / POWER(10,@DecimalPlaces) AS BPDecrypted
,[BPEncrypted]
FROM [dbo].[PatientsBloodPressure];
GOResulting in:

Next Steps
- WIKIPEDIA – Homomorphic encryption
- WIKIPEDIA – Paillier cryptosystem
- Homomorphic Encryption Standardization
- IEEE – Homomorphic encryption use cases

Sebastião Pereira has over 40 years of experience in database development including T-SQL, algorithm design, machine learning and bringing innovative mathematical formulas to SQL Server. He started his career at a transnational fast-moving consumer goods (FMCG) company as an employee then later transitioning into a consultant role. He eventually founded his own company to develop software solutions for the healthcare industry. Sebastião is a respected award-winning author on MSSQLTips.com extending SQL Server capabilities beyond traditional workloads.
- MSSQLTips Awards
- Author of the Year – 2025
- Trendsetter (25+ tips) – 2025
- Rookie of the Year – 2024


