You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在go-pg中使用pgcrypto实现PostgreSQL列加密

问题根因

你当前代码无响应、逻辑不生效的核心原因有3个:

  • 直接覆盖ORM传入的b字节切片:这个切片存储了已经拼接完成的SQL前缀内容,直接赋值新切片会彻底破坏SQL结构,生成非法SQL导致数据库请求异常卡住。
  • 错误给函数套单引号:你在拼接内容前后加了单引号,导致数据库把PGP_SYM_ENCRYPT(...)识别为普通字符串值,不会执行加密函数。
  • 逻辑硬编码:写死了加密值为123456,没有使用字段实际传入的明文,读取逻辑也没有对应解密处理,即使插入成功拿到的也是无法解析的密文。
实现方案

通过go-pg自定义类型+轻量钩子实现自动加解密,核心逻辑是写入时自动拼接PGP_SYM_ENCRYPT函数表达式,查询时自动调用PGP_SYM_DECRYPT解密密文。
前置依赖:先在目标数据库执行CREATE EXTENSION IF NOT EXISTS pgcrypto;启用加密扩展,存储加密字段的列类型设置为bytea。

完整实现代码

package main

import (
	"context"
	"github.com/go-pg/pg/v10"
	"github.com/go-pg/pg/v10/orm"
	"github.com/go-pg/pg/v10/types"
)

// 从配置/环境变量读取,不要硬编码在业务代码里
const encKey = "YOUR_STORAGE_ENCRYPTION_KEY"

// EncryptedString 自动加解密的字符串类型
type EncryptedString struct {
	Plaintext string // 存储业务侧传入的明文
}

// AppendValue 写入时自动包装PGP_SYM_ENCRYPT函数
var _ types.ValueAppender = (*EncryptedString)(nil)
func (e EncryptedString) AppendValue(b []byte, flags int) ([]byte, error) {
	// 追加函数头
	b = append(b, "PGP_SYM_ENCRYPT("...)
	// 拼接明文字符串,自动处理单引号转义
	b = types.AppendString(b, e.Plaintext, 1)
	b = append(b, ", "...)
	// 拼接加密密钥
	b = types.AppendString(b, encKey, 1)
	// 闭合函数
	b = append(b, ")"...)
	return b, nil
}

// ScanValue 读取时直接接收SQL层解密后的明文
var _ types.ValueScanner = (*EncryptedString)(nil)
func (e *EncryptedString) ScanValue(rd types.Reader, n int) error {
	if n <= 0 {
		e.Plaintext = ""
		return nil
	}
	tmp, err := rd.ReadFullTemp()
	if err != nil {
		return err
	}
	e.Plaintext = string(tmp)
	return nil
}

// 业务模型
type Model struct {
	ID     uint            `pg:"id,pk"`
	Name   string          `pg:"name"`
	Crypto EncryptedString `pg:"crypto"` // 对应数据库列类型为bytea

	tableName struct{} `pg:"models"`
}

// BeforeSelect 查询钩子,自动给加密字段加解密函数
var _ orm.BeforeSelectHook = (*Model)(nil)
func (m *Model) BeforeSelect(ctx context.Context, q orm.Query) (context.Context, error) {
	// 替换crypto字段的查询逻辑,自动调用解密函数转明文
	q.ColumnExpr("id, name, PGP_SYM_DECRYPT(crypto, ?)::text AS crypto", encKey)
	return ctx, nil
}

// 插入调用示例
func insertDemo(ctx context.Context, db *pg.DB) error {
	item := &Model{
		Name:   "test_record",
		Crypto: EncryptedString{Plaintext: "test_value"}, // 直接传明文即可
	}
	_, err := db.ModelContext(ctx, item).Insert()
	return err
}

// 查询调用示例
func queryDemo(ctx context.Context, db *pg.DB) (*Model, error) {
	item := &Model{ID: 1}
	err := db.ModelContext(ctx, item).WherePK().Select()
	return item, err
}

关键说明

  • 写入时不需要手动加任何外层单引号,types.AppendString会自动处理字符串转义,生成的SQL片段格式为PGP_SYM_ENCRYPT('test_value', 'YOUR_KEY'),数据库会直接识别为函数调用执行加密。
  • 查询钩子自动给加密字段包装解密函数,业务代码不需要手动写解密逻辑,拿到的Crypto.Plaintext就是原始明文。
  • 如果多个模型都有加密字段,可以把BeforeSelect逻辑抽成公共组件,避免重复编码。
  • 不要在代码里硬编码加密密钥,建议通过环境变量、配置中心下发,密钥权限严格管控。

内容的提问来源于stack exchange,提问作者Denis Suleimanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 15:15:34