Como usar o Always Encrypted com enclaves seguros e com Drivers PHP para SQL Server
Aplicável a
- Drivers 5.8.0 da Microsoft para PHP e para SQL Server
Introdução
O Always Encrypted com enclaves seguros é a segunda iteração do recurso Always Encrypted para SQL Server. O Always Encrypted com enclaves seguros permite que os usuários executem computações avançadas com base em dados criptografados criando um enclave seguro, região da memória no servidor onde os dados criptografados em um banco de dados são descriptografados para que as computações possam ser executadas. As operações compatíveis incluem comparação e correspondência de padrões com a cláusula LIKE
.
Como habilitar o Always Encrypted com enclaves seguros
O suporte para Always Encrypted com enclaves seguros está disponível nos Drivers PHP para SQL Server começando com o 5.8.0. O Always Encrypted com enclaves seguros requer o SQL Server 2019 ou posterior e a versão 17.4+ do driver ODBC. Mais detalhes sobre os requisitos gerais para o Always Encrypted com os Drivers PHP para SQL Server estão disponíveis aqui.
O Always Encrypted com enclaves seguros garante a segurança de dados criptografados atestando o enclave, ou seja, verificando-o em relação a um serviço de atestado externo. Para usar os enclaves seguros, a palavra-chave ColumnEncryption
deverá identificar o tipo de atestado e o protocolo junto com os dados de atestado associados, separados por vírgula. A versão 17.4 do driver ODBC é compatível somente com VBS (Segurança com Base em Virtualização) e com o protocolo HGS (Serviço Guardião de Host) para o tipo e protocolo do enclave. Os dados do atestado associado são a URL do servidor de atestado. Dessa forma, a configuração abaixo seria adicionada à cadeia de conexão:
ColumnEncryption=VBS-HGS,http://attestationserver.mydomain/Attestation
Caso o protocolo esteja incorreto, o driver não o reconhecerá, a conexão falhará e um erro será retornado. Caso a URL de atestado seja a única incorreta, a conexão será bem-sucedida e um erro será gerado quando houver tentativa de usar uma computação habilitada para enclave, no entanto, o comportamento será idêntico ao original do Always Encrypted. Configurar ColumnEncryption
como enabled
fornecerá uma funcionalidade regular do Always Encrypted, porém, tentar fazer uma operação habilitada para enclave retornará um erro.
Detalhes completos sobre como definir seu ambiente para ser compatível com o Always Encrypted com enclaves seguros, incluindo como configurar o Serviço Guardião de Host e criar as chaves de criptografia necessárias, podem ser encontrados aqui.
Exemplos
Os exemplos a seguir, um para SQLSRV e outro para PDO_SQLSRV, mostram como criar uma tabela com vários tipos de dados em texto não criptografado e criptografá-los, além de executar comparações e correspondência de padrões. Observe o seguintes detalhes:
- Ao criptografar uma tabela com
ALTER TABLE
, somente uma coluna poderá ser criptografada para cada chamada aALTER TABLE
, portanto, diversas chamadas são necessárias para criptografar várias colunas. - Ao passar do limite de comparação como um parâmetro para comparar os tipos char e nchar, a largura da coluna deverá ser especificada no
SQLSRV_SQLTYPE_*
correspondente ou os errosHY104
eInvalid precision value
retornarão. - Para correspondência de padrões, a ordenação deverá ser especificada como
Latin1_General_BIN2
usando a cláusulaCOLLATE
. - Ao passar a cadeia de caracteres de correspondência de padrões como um parâmetro para os tipos char e nchar correspondentes, o
SQLSRV_SQLTYPE_*
passado parasqlsrv_query
ousqlsrv_prepare
deverá especificar o comprimento da cadeia de caracteres a ser correspondida, não o tamanho da coluna. Isso porque os tipos char e nchar preencherão os espaços em branco no final da cadeia de caracteres. Por exemplo, ao corresponder a cadeia de caracteres%abc%
em relação a uma coluna char(10), especifiqueSQLSRV_SQLTYPE_CHAR(5)
. Em vez disso, caso especifiqueSQLSRV_SQLTYPE_CHAR(10)
, a consulta corresponderá a%abc%
(com cinco espaços acrescentados) e todos os dados na coluna com menos de cinco espaços não terão correspondência (portanto,abcdef
não corresponderá a%abc%
porque ele tem quatro espaços de preenchimento). Para cadeias de caracteres Unicode, use as funçõesmb_strlen
ouiconv_strlen
para obter o número de caracteres. - A interface PDO não permite especificar o comprimento de um parâmetro. Em vez disso, especifique um comprimento de 0 ou
null
emPDOStatement::bindParam
. Caso o comprimento seja explicitamente definido com outro número, o parâmetro será tratado como parâmetro de saída. - A correspondência de padrões não funciona em tipos diferentes de cadeias de caracteres no Always Encrypted.
- A verificação de erros foi excluída para fins de clareza.
Os seguintes dados são comuns para os dois exemplos:
<?php
// Data for testing - integer, datetime2, char, nchar, varchar, and nvarchar
// String data is random, showing that we can match or compare anything
$testValues = array(array(1, "2019-12-31 01:00:00", "abcd", "㬚㔈♠既", "abcd", "㬚㔈♠既"),
array(-100, "1753-01-31 14:25:25.25", "#e@?q&zy+", "ઔܛ᎓Ե⅜", "#e@?q&zy+", "ઔܛ᎓Ե⅜"),
array(100, "2112-03-15 23:40:10.1594", "zyxwv", "㶋㘚ᐋꗡ", "zyxwv", "㶋㘚ᐋꗡ"),
array(0, "8888-08-08 08:08:08.08", "7t", "㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ", "7t", "㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ"),
);
// Queries to create the table and insert data
$createTable = "DROP TABLE IF EXISTS $myTable;
CREATE TABLE $myTable (c_integer int NULL,
c_datetime2 datetime2(7) NULL,
c_char char(32) NULL,
c_nchar nchar(32) NULL,
c_varchar varchar(32) NULL,
c_nvarchar nvarchar(32) NULL);";
$insertData = "INSERT INTO $myTable (c_integer, c_datetime2, c_char, c_nchar, c_varchar, c_nvarchar) VALUES (?, ?, ?, ?, ?, ?)";
// This is the query that encrypts the table in place
$encryptQuery = " ALTER TABLE $myTable
ALTER COLUMN [c_integer] integer
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH (ONLINE = ON);
ALTER TABLE $myTable
ALTER COLUMN [c_datetime2] datetime2(7)
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH (ONLINE = ON);
ALTER TABLE $myTable
ALTER COLUMN [c_char] char(32) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH (ONLINE = ON);
ALTER TABLE $myTable
ALTER COLUMN [c_nchar] nchar(32) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH (ONLINE = ON);
ALTER TABLE $myTable
ALTER COLUMN [c_varchar] varchar(32) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH (ONLINE = ON);
ALTER TABLE $myTable
ALTER COLUMN [c_nvarchar] nvarchar(32) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH (ONLINE = ON);
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;";
?>
SQLSRV
<?php
// Specify Azure Key Vault credentials using the KeyStoreAuthentication, KeyStorePrincipalId, and KeyStoreSecret keywords
// Otherwise, the local Windows Certificate Store will be used
$options = array('database'=>$myDatabase,
'uid'=>$myUsername,
'pwd'=>$myPassword,
'CharacterSet'=>'UTF-8',
'ReturnDatesAsStrings'=>true,
'ColumnEncryption'=>"VBS-HGS,http://myattestationserver.mydomain/Attestation",
);
$conn = sqlsrv_connect($myServer, $options);
// Create the table and insert the test data
$stmt = sqlsrv_query($conn, $createTable);
foreach ($testValues as $values) {
$stmt = sqlsrv_prepare($conn, $insertData, $values);
sqlsrv_execute($stmt);
}
// Encrypt the table in place
$stmt = sqlsrv_query($conn, $encryptQuery);
// Test comparison and pattern matching on the encrypted table
echo "Test comparisons:\n";
$intThreshold = 0;
$testGreater = "SELECT c_integer FROM $myTable WHERE c_integer > ?";
$param = array($intThreshold, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_INT);
$stmt = sqlsrv_prepare($conn, $testGreater, array($param));
getResults($stmt);
// Expect:
// 1
// 100
$datetimeThreshold = "3000-01-01 00:00:00.0";
$testLess = "SELECT c_datetime2 FROM $myTable WHERE c_datetime2 < ?";
$param = array($datetimeThreshold, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_DATETIME2);
$stmt = sqlsrv_prepare($conn, $testLess, array($param));
getResults($stmt);
// Expect:
// 2019-12-31 01:00:00.0000000
// 1753-01-31 14:25:25.2500000
// 2112-03-15 23:40:10.1594000
$charThreshold = "abcd";
$ncharThreshold = "㬚㔈♠既";
$testGreaterEqual = "SELECT c_char FROM $myTable WHERE c_char >= ?";
$param = array($charThreshold, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_CHAR(32));
$stmt = sqlsrv_prepare($conn, $testGreaterEqual, array($param));
getResults($stmt);
// Expect:
// abcd
// zyxwv
$testLessEqual = "SELECT c_nchar FROM $myTable WHERE c_nchar <= ?";
$param = array($ncharThreshold, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING('UTF-8'), SQLSRV_SQLTYPE_NCHAR(32));
$stmt = sqlsrv_prepare($conn, $testLessEqual, array($param));
getResults($stmt);
// Expect:
// 㬚㔈♠既
// ઔܛ᎓Ե⅜
// 㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
$testNotGreater = "SELECT c_varchar FROM $myTable WHERE c_varchar !> ?";
$param = array($charThreshold, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_VARCHAR);
$stmt = sqlsrv_prepare($conn, $testNotGreater, array($param));
getResults($stmt);
// Expect:
// abcd
// #e@?q&zy+
// 7t
$testNotLess = "SELECT c_nvarchar FROM $myTable WHERE c_nvarchar !< ?";
$param = array($ncharThreshold, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING('UTF-8'), SQLSRV_SQLTYPE_NVARCHAR);
$stmt = sqlsrv_prepare($conn, $testNotLess, array($param));
getResults($stmt);
// Expect:
// 㬚㔈♠既
// 㶋㘚ᐋꗡ
echo "\nTest pattern matching:\n";
$charMatch = "%zy%";
$ncharMatch = "%㔈♠既%";
$param = array($charMatch, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_CHAR(strlen($charMatch)));
$testCharMatch = "SELECT c_char FROM $myTable WHERE c_char LIKE ? COLLATE Latin1_General_BIN2";
$stmt = sqlsrv_prepare($conn, $testCharMatch, array($param));
getResults($stmt);
// Expect:
// #e@?q&zy+
// zyxwv
$param = array($ncharMatch, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING("UTF-8"), SQLSRV_SQLTYPE_NCHAR(iconv_strlen($ncharMatch)));
$testNCharMatch = "SELECT c_nchar FROM $myTable WHERE c_nchar LIKE ? COLLATE Latin1_General_BIN2";
$stmt = sqlsrv_prepare($conn, $testNCharMatch, array($param));
getResults($stmt);
// Expect:
// 㬚㔈♠既
// 㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
$param = array($charMatch, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_VARCHAR(strlen($charMatch)));
$testVarcharMatch = "SELECT c_varchar FROM $myTable WHERE c_varchar LIKE ? COLLATE Latin1_General_BIN2";
$stmt = sqlsrv_prepare($conn, $testVarcharMatch, array($param));
getResults($stmt);
// Expect:
// #e@?q&zy+
// zyxwv
$param = array($ncharMatch, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING("UTF-8"), SQLSRV_SQLTYPE_NVARCHAR(iconv_strlen($ncharMatch)));
$testNVarcharMatch = "SELECT c_nvarchar FROM $myTable WHERE c_nvarchar LIKE ? COLLATE Latin1_General_BIN2";
$stmt = sqlsrv_prepare($conn, $testNVarcharMatch, array($param));
getResults($stmt);
// Expect:
// 㬚㔈♠既
// 㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
function getResults($stmt)
{
sqlsrv_execute($stmt);
while ($res = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC)) {
print_r($res[0]);
echo "\n";
}
}
?>
PDO_SQLSRV
<?php
// Specify Azure Key Vault credentials using the KeyStoreAuthentication, KeyStorePrincipalId, and KeyStoreSecret keywords
// Otherwise, the local Windows Certificate Store will be used
$options = "sqlsrv:server=$myServer;database=$myDatabase;driver={ODBC Driver 18 for SQL Server};";
$options .= "ColumnEncryption=VBS-HGS,http://myattestationserver.mydomain/Attestation",
$conn = new PDO($options, $myUsername, $myPassword);
// Create the table and insert the test data
$stmt = $conn->query($createTable);
foreach ($testValues as $values) {
$stmt = $conn->prepare($insertData);
$stmt->execute($values);
}
// Encrypt the table in place
$stmt = $conn->query($encryptQuery);
// Test comparison and pattern matching on the encrypted table
echo "Test comparisons:\n";
$intThreshold = 0;
$testGreater = "SELECT c_integer FROM $myTable WHERE c_integer > ?";
$stmt = $conn->prepare($testGreater);
$stmt->bindParam(1, $intThreshold, PDO::PARAM_INT);
getResults($stmt);
// Expect:
// 1
// 100
$datetimeThreshold = "3000-01-01 00:00:00.0";
$testLess = "SELECT c_datetime2 FROM $myTable WHERE c_datetime2 < ?";
$stmt = $conn->prepare($testLess);
$stmt->bindParam(1, $datetimeThreshold, PDO::PARAM_STR);
getResults($stmt);
// Expect:
// 2019-12-31 01:00:00.0000000
// 1753-01-31 14:25:25.2500000
// 2112-03-15 23:40:10.1594000
$charThreshold = "abcd";
$ncharThreshold = "㬚㔈♠既";
$testGreaterEqual = "SELECT c_char FROM $myTable WHERE c_char >= ?";
$stmt = $conn->prepare($testGreaterEqual);
$stmt->bindParam(1, $charThreshold, PDO::PARAM_STR);
getResults($stmt);
// Expect:
// abcd
// zyxwv
$testLessEqual = "SELECT c_nchar FROM $myTable WHERE c_nchar <= ?";
$stmt = $conn->prepare($testLessEqual);
$stmt->bindParam(1, $ncharThreshold, PDO::PARAM_STR);
getResults($stmt);
// Expect:
// 㬚㔈♠既
// ઔܛ᎓Ե⅜
// 㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
$testNotGreater = "SELECT c_varchar FROM $myTable WHERE c_varchar !> ?";
$stmt = $conn->prepare($testNotGreater);
$stmt->bindParam(1, $charThreshold, PDO::PARAM_STR);
getResults($stmt);
// Expect:
// abcd
// #e@?q&zy+
// 7t
$testNotLess = "SELECT c_nvarchar FROM $myTable WHERE c_nvarchar !< ?";
$stmt = $conn->prepare($testNotLess);
$stmt->bindParam(1, $ncharThreshold, PDO::PARAM_STR);
getResults($stmt);
// Expect:
// 㬚㔈♠既
// 㶋㘚ᐋꗡ
echo "\nTest pattern matching:\n";
$charMatch = "%zy%";
$ncharMatch = "%㔈♠既%";
$testCharMatch = "SELECT c_char FROM $myTable WHERE c_char LIKE ? COLLATE Latin1_General_BIN2";
$stmt = $conn->prepare($testCharMatch);
$stmt->bindParam(1, $charMatch, PDO::PARAM_STR);
getResults($stmt);
// Expect:
// #e@?q&zy+
// zyxwv
$testNCharMatch = "SELECT c_nchar FROM $myTable WHERE c_nchar LIKE ? COLLATE Latin1_General_BIN2";
$stmt = $conn->prepare($testNCharMatch);
$stmt->bindParam(1, $ncharMatch, PDO::PARAM_STR,null,PDO::SQLSRV_ENCODING_UTF8);
getResults($stmt);
// Expect:
// 㬚㔈♠既
// 㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
$testVarcharMatch = "SELECT c_varchar FROM $myTable WHERE c_varchar LIKE ? COLLATE Latin1_General_BIN2";
$stmt = $conn->prepare($testVarcharMatch);
$stmt->bindParam(1, $charMatch, PDO::PARAM_STR);
getResults($stmt);
// Expect:
// #e@?q&zy+
// zyxwv
$testNVarcharMatch = "SELECT c_nvarchar FROM $myTable WHERE c_nvarchar LIKE ? COLLATE Latin1_General_BIN2";
$stmt = $conn->prepare($testNVarcharMatch);
$stmt->bindParam(1, $ncharMatch, PDO::PARAM_STR,null,PDO::SQLSRV_ENCODING_UTF8);
getResults($stmt);
// Expect:
// 㬚㔈♠既
// 㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
function getResults($stmt)
{
$stmt->execute();
while($res = $stmt->fetch(PDO::FETCH_NUM)) {
print_r($res[0]);
echo "\n";
}
}
?>
Saída:
Test comparisons:
1
100
2019-12-31 01:00:00.0000000
1753-01-31 14:25:25.2500000
2112-03-15 23:40:10.1594000
abcd
zyxwv
㬚㔈♠既
ઔܛ᎓Ե⅜
㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
abcd
#e@?q&zy+
7t
㬚㔈♠既
㶋㘚ᐋꗡ
Test pattern matching:
#e@?q&zy+
zyxwv
㬚㔈♠既
㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
#e@?q&zy+
zyxwv
㬚㔈♠既
㛜ꆶ㕸㔈♠既ꁺꖁ㓫ޘ갧ᛄ
Consulte Também
Guia de programação para o driver SQL de PHP
Referência da API do driver SQLSRV
Referência da API do Driver PDO_SQLSRV
Uso do Always Encrypted com os Drivers PHP para SQL Server