1

I'm trying to pass in a parameter from my application to do a search inside a stored procedure. I pass in the parameter like this:

SqlParameter param1 = new SqlParameter(@"@FilterCustomerPO", "G06756");
param1.DbType = DbType.AnsiString;
param1.Direction = ParameterDirection.Input;
param1.Size = 50;

sqlCmd.Parameters.Add(param1);

In the stored procedure, it is defined like this:

ALTER PROCEDURE [dbo].[usp_POListing]
    @FilterCustomerPO VARCHAR (50)
AS
BEGIN
    SELECT * 
    FROM [Order]
    WHERE PONumber = @FilterCustomerPO
END

The PONumber column is encrypted using DETERMINISTIC encryption type.

When I pass in a value, I get an error:

Operand type clash: varchar is incompatible with varchar(50) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'ColumnEncryptionKey', column_encryption_key_database_name = 'DataPortal') collation_name = 'SQL_Latin1_General_CP1_CI_AS'

I've seen a lot of other people reporting on this, but none of those solutions worked for me. I'm using parameterized queries, as you can see, so not sure what I could be missing.

EDIT:

I've also tried passing in the parameter this way - same error:

sqlCmd.Parameters.Add("FilterCustomerPO", SqlDbType.VarChar, 50);
sqlCmd.Parameters["FilterCustomerPO"].Value = "G06756"

The definition of the Order Table is as follows:

CREATE TABLE [dbo].[Order]
(
    [OrderID] [INT] IDENTITY(1,1) NOT NULL,
    [CustomerID] [INT] NOT NULL,
    [OrderNumber] [INT] NOT NULL,
    [DBCOrderNumber] [VARCHAR](25) NOT NULL,
    [PONumber] [VARCHAR](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [ColumnEncryptionKey], ENCRYPTION_TYPE = Deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL,
    [BillingName] [VARCHAR](255) NOT NULL,

    CONSTRAINT [PK_Order] 
        PRIMARY KEY CLUSTERED ([OrderID] ASC)
                    WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

My client connection string also includes the Column Encryption Setting=Enabled

Please note: I am able to retrieve data just fine. It's when I send parameters into the query, and try to do a comparison/filter when I get the error.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
M.R.
  • 4,737
  • 3
  • 37
  • 81
  • Provide table `[Order]` definition and your client connection properties – Serg Aug 31 '19 at 19:24
  • `I've seen a lot of other people reporting on this, but none of those solutions worked for me.` - which ones? https://stackoverflow.com/a/41067249/11683? https://stackoverflow.com/a/56184691/11683? – GSerg Aug 31 '19 at 19:49
  • Both of them mention to change the parmeter type `DbType.AnsiStringFixedLength` - I tried, same error. – M.R. Aug 31 '19 at 19:51
  • I've also run `sys.sp_refresh_parameter_encryption`, no dice :( – M.R. Aug 31 '19 at 19:52
  • Try force parameter encryption https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlparameter.forcecolumnencryption?view=netframework-4.6 – Serg Aug 31 '19 at 20:02
  • Didn't work. based on what @david is mentioning below, this may never work. `Cannot execute statement or procedure 'usp_POListing' because ForceColumnEncryption(true) was set for SqlParameter '@FilterCustomerPO' and the database expects this parameter to be sent as plaintext. This may be due to a configuration error.` – M.R. Aug 31 '19 at 21:35

1 Answers1

2

This is AlwaysEncrypted, which only uses client-side encryption keys. It's designed to prevent SQL Server (or its administrators) from being able to decrypt the data. If you want column encryption with server-managed keys, SQL Server has that too, but it's a different feature. See Encrypt a Column of Data.

In AlwaysEncrypted data type of Order.PONumber is not really just varchar(50). It's

[varchar](50) COLLATE Latin1_General_BIN2 
    ENCRYPTED WITH (
      COLUMN_ENCRYPTION_KEY = [ColumnEncryptionKey], 
      ENCRYPTION_TYPE = Deterministic, 
      ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256')

That's all part of the column type, and so you can't use a parameter of type varchar(50). To search on this column the client has to encrypt the parameter value with the specified key and algorithm, so SQL Server can match the deterministically-encrypted column value with the encrypted parameter value. SQL Server does not have the column encryption keys, so it can't decrypt the column value, or encrypt the parameter values.

SQL Server has some ability to propagate the encrypted table parameters to a stored procedure or function. This one appears to work fine. To check the stored procedure parameter has picked up the column encryption, check sys.parameters. EG

select name, encryption_type_desc, encryption_algorithm_name
from sys.parameters
where object_id = object_id('usp_POListing')

I created a simple repro and was able to call the stored procedure like this:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp27
{
    class Program
    {
        static void Main(string[] args)
        {

            using (var con = new SqlConnection("server=localhost;database=testdb;integrated security=true;Column Encryption Setting=enabled"))
            {
                con.Open();

                var sqlCmd = new SqlCommand("usp_POListing", con);
                sqlCmd.CommandType = CommandType.StoredProcedure;

                var param1 = new SqlParameter("@FilterCustomerPO", "G06756");
                param1.SqlDbType = SqlDbType.VarChar;
                param1.Size = 50;
                param1.Direction = ParameterDirection.Input;

                sqlCmd.Parameters.Add(param1);

                var dt = new DataTable();
                using (var rdr = sqlCmd.ExecuteReader())
                {
                    dt.Load(rdr);
                }
            }
        }
    }
}

See generally Develop using Always Encrypted with .NET Framework Data Provider

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • So - maybe I have a wrong perception - a parameterized query is NOT the same as a stored procedure, even if the client is adding SqlParameters? – M.R. Aug 31 '19 at 21:24
  • In terms of this being 'Client Side' - not sure what that means - the column is encrypted using a column master key (which is stored in an Azure Key Vault), and in the client, I added an azure key vault as a provider for the encryption. The column master key is also stored in the database (inside Security/Always Encrypted Keys) – M.R. Aug 31 '19 at 21:27
  • I guess the question is - sql server (I think) has access to the keys, so why can't it encrypt? Also, I've seen instances of questions/answers here that showed that insert stored procedures work fine. Are inserts doing something differently? – M.R. Aug 31 '19 at 22:04
  • The Column Master Keys are never stored in the database. There's a _different_ scenario where SQL Server can access keys from Key Vault for Transparent Database Encryption. But for AlwaysEncrypted SQL Server never has access to the Column Master Keys. – David Browne - Microsoft Aug 31 '19 at 22:26
  • But I may be wrong about the stored procedure bit. I'll try a repro. – David Browne - Microsoft Aug 31 '19 at 22:31
  • So - what I have is very much what you tried in the repo, so what am I missing? – M.R. Aug 31 '19 at 23:12
  • Dunno, but verify your stored procedure parameter and try what I posted. – David Browne - Microsoft Aug 31 '19 at 23:17
  • 1
    So - I'm marking this as the answer, because it helped me find the issue - essentially, I had typed the parameter with a lowercase 'c' - instead of `@ FilterCustomerPO`, I had `@FiltercustomerPO`. I'm not entirely why this causes a datatype clash, but that's what it was. – M.R. Sep 05 '19 at 02:57
  • 1
    I had the same issue. Spent ours when I was passing CustomerId instead CustomerID (as written in the procedure). It is worth noting that this argument is not even the one that gets encrypted by the Always Encrypted client ADO driver. So the top tip is: check all input arguments names. The error message "Operand type clash: nvarchar is incompatible with nvarchar(1000) encrypted with (encryption_type = 'RANDOMIZED'" is useless in this case. – user3746240 Nov 17 '20 at 16:51