始终用 go-mssqldb 加密

驱动支持go-mssqldb始终加密,用于客户端加密和解密敏感数据。 启用后,驱动会自动解密加密列的数据,并加密发送到加密列的参数值。

启用始终加密

columnencryption连接参数设置为true

sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&columnencryption=true

你还需要导入至少一个列主密钥(CMK)提供者包。 没有提供商,司机无法访问加密密钥。

列主密钥提供者

该驱动支持三个关键商店提供商。 以副作用导入的方式导入提供程序包,以注册它。

本地证书(PFX)

localcert提供者从本地文件系统上的PFX(PKCS #12)文件读取私钥。 CMK元数据中的提供者名称为 pfx

import (
    _ "github.com/microsoft/go-mssqldb"
    _ "github.com/microsoft/go-mssqldb/aecmk/localcert"
)

在SQL Server中配置CMK时,将密钥路径设置为PFX文件位置:

CREATE COLUMN MASTER KEY MyCMK
WITH (
    KEY_STORE_PROVIDER_NAME = 'pfx',
    KEY_PATH = '/path/to/certificate.pfx'
);

如果 PFX 文件有密码保护,可以将密码设置为环境变量或通过 pfxpassword 连接参数。

Windows 证书存储

MSSQL_CERTIFICATE_STORE提供者访问Windows证书存储中的证书。 该服务商仅适用于Windows。

import (
    _ "github.com/microsoft/go-mssqldb"
    _ "github.com/microsoft/go-mssqldb/aecmk/localcert"
)

注释

导入 localcert 还会在 Windows 上注册 Windows 证书存储提供程序。 无需单独导入。

CMK密钥路径格式为 CurrentUser/My/<thumbprint>LocalMachine/My/<thumbprint>

CREATE COLUMN MASTER KEY MyCMK
WITH (
    KEY_STORE_PROVIDER_NAME = 'MSSQL_CERTIFICATE_STORE',
    KEY_PATH = 'CurrentUser/My/<CERTIFICATE_THUMBPRINT>'
);

Azure 密钥保管库

akv提供者从Azure 密钥保管库获取列主密钥。

import (
    _ "github.com/microsoft/go-mssqldb"
    _ "github.com/microsoft/go-mssqldb/aecmk/akv"
)

CMK 密钥路径是 Azure 密钥保管库 密钥标识符 URL:

CREATE COLUMN MASTER KEY MyCMK
WITH (
    KEY_STORE_PROVIDER_NAME = 'AZURE_KEY_VAULT',
    KEY_PATH = 'https://<VAULT_NAME>.vault.azure.net/keys/<KEY_NAME>/<KEY_VERSION>'
);

Azure 密钥保管库 提供程序使用 azidentity.DefaultAzureCredential 进行身份验证。 通过环境变量、管理身份、Azure CLI 或 Azure 身份库支持的其他方法配置凭证。 有关详细信息,请参阅 Microsoft Entra ID 身份验证

查询加密列

启用始终加密并注册了服务提供者后,查询过程透明:

import (
    "context"
    "database/sql"
    "fmt"
    "log"

    _ "github.com/microsoft/go-mssqldb"
    _ "github.com/microsoft/go-mssqldb/aecmk/localcert"
)

func main() {
    db, err := sql.Open("sqlserver",
        "sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&columnencryption=true")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    ctx := context.Background()

    // Reads automatically decrypt encrypted columns
    var ssn string
    err = db.QueryRowContext(ctx,
        "SELECT SSN FROM Patients WHERE Id = @p1",
        sql.Named("p1", 1)).Scan(&ssn)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("SSN:", ssn)

    // Parameters are automatically encrypted for encrypted columns
    _, err = db.ExecContext(ctx,
        "INSERT INTO Patients (Name, SSN) VALUES (@p1, @p2)",
        sql.Named("p1", "Alice"),
        sql.Named("p2", "123-45-6789"))
    if err != nil {
        log.Fatal(err)
    }
}

精确匹配参数类型

始终加密的参数加密比普通参数绑定更严格。 驱动程序在发送值前会向 SQL Server 请求加密元数据,因此 Go 参数类型必须与 SQL Server 列类型高度匹配。

  • 一个 Go string 参数默认会以 nvarchar 形式发送。
  • 使用驱动程序特定的类型,如 mssql.NVarCharMaxmssql.DateTime1,或者mssql.DateTimeOffset当加密列使用更具体的 SQL Server 类型时。
  • 如果参数类型与加密列类型不匹配,查询在参数加密元数据发现过程中可能会因类型不匹配而失败。

例如,如果某个加密列为 nvarchar(max),那么在需要对大型字符串进行精确匹配时,应优先使用 mssql.NVarCharMax

_, err := db.ExecContext(ctx,
    "INSERT INTO Patients (Notes) VALUES (@p1)",
    sql.Named("p1", mssql.NVarCharMax("Sensitive note text")))

关于一般参数类型的指导,请参见 数据类型映射

Limitations

  • 始终加密无法用于批量复制操作。
  • azuresql 驱动程序和 Always Encrypted 可以结合使用,但你必须同时导入 azuread 包和密钥提供程序包。
  • charvarchar 的加密插入和更新目前受限。 对于必须使用“始终加密”的文本数据,优先使用加密的 nchar 列或 nvarchar 列。
  • 不支持安全飞地。