PostgreSQL加密函数

本章将详细介绍PostgreSQL中的加密函数,包括密码哈希、数据加密、解密等,帮助您保护数据库中的敏感信息。

密码哈希函数

MD5哈希

MD5哈希函数(不推荐用于密码存储):

SELECT 
    MD5('password123') AS md5_hash,                       -- MD5哈希
    MD5('username' || 'password123') AS salted_md5;       -- 加盐MD5哈希

Crypt函数

更安全的密码哈希函数:

SELECT 
    CRYPT('password123', GEN_SALT('bf')) AS bcrypt_hash,  -- bcrypt哈希
    CRYPT('password123', GEN_SALT('md5')) AS md5_crypt_hash, -- MD5 crypt哈希
    CRYPT('password123', GEN_SALT('xdes')) AS xdes_crypt_hash; -- Extended DES哈希

密码验证

验证密码哈希:

-- 验证bcrypt哈希
SELECT 
    CRYPT('password123', '$2a$06$hashsaltvalue') = '$2a$06$hashsaltvalue' AS password_valid;

-- 验证MD5哈希
SELECT 
    CRYPT('password123', '$1$hashsaltvalue') = '$1$hashsaltvalue' AS password_valid;

数据加密函数

PGP加密

使用PGP进行数据加密:

-- PGP加密
SELECT 
    PGP_SYM_ENCRYPT('Sensitive Data', 'my_secret_key') AS encrypted_data;

-- PGP解密
SELECT 
    PGP_SYM_DECRYPT(
        PGP_SYM_ENCRYPT('Sensitive Data', 'my_secret_key'), 
        'my_secret_key'
    ) AS decrypted_data;

非对称加密

使用公钥/私钥加密:

-- 生成密钥对(需要在操作系统层面生成)
-- openssl genrsa -out private.key 2048
-- openssl rsa -in private.key -pubout -out public.key

-- PGP公钥加密
SELECT 
    PGP_PUB_ENCRYPT('Secret Message', DECODE('public_key_content', 'base64')) AS encrypted_message;

-- PGP私钥解密
SELECT 
    PGP_PUB_DECRYPT(
        PGP_PUB_ENCRYPT('Secret Message', DECODE('public_key_content', 'base64')),
        DECODE('private_key_content', 'base64')
    ) AS decrypted_message;

编码函数

Base64编码

Base64编码和解码:

SELECT 
    ENCODE('Hello World'::BYTEA, 'base64') AS base64_encoded, -- Base64编码
    DECODE(ENCODE('Hello World'::BYTEA, 'base64'), 'base64') AS base64_decoded; -- Base64解码

十六进制编码

十六进制编码和解码:

SELECT 
    ENCODE('Hello World'::BYTEA, 'hex') AS hex_encoded,       -- 十六进制编码
    DECODE(ENCODE('Hello World'::BYTEA, 'hex'), 'hex') AS hex_decoded; -- 十六进制解码

实际应用场景

用户密码存储

-- 创建用户表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入用户(使用bcrypt哈希)
INSERT INTO users (username, email, password_hash)
VALUES (
    'john_doe',
    'john@example.com',
    CRYPT('password123', GEN_SALT('bf'))
);

-- 验证用户登录
SELECT id, username 
FROM users 
WHERE username = 'john_doe' 
  AND password_hash = CRYPT('password123', password_hash);

敏感数据加密存储

-- 创建包含加密字段的表
CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    phone_encrypted BYTEA,                                -- 加密的电话号码
    ssn_encrypted BYTEA,                                  -- 加密的社会安全号码
    credit_card_encrypted BYTEA,                          -- 加密的信用卡号
    encryption_key_name VARCHAR(50) DEFAULT 'default_key'
);

-- 插入加密数据
INSERT INTO user_profiles (user_id, phone_encrypted, ssn_encrypted)
VALUES (
    1,
    PGP_SYM_ENCRYPT('13800138000', 'user_encryption_key'),
    PGP_SYM_ENCRYPT('123-45-6789', 'user_encryption_key')
);

-- 查询解密数据
SELECT 
    user_id,
    PGP_SYM_DECRYPT(phone_encrypted, 'user_encryption_key') AS phone,
    PGP_SYM_DECRYPT(ssn_encrypted, 'user_encryption_key') AS ssn
FROM user_profiles 
WHERE user_id = 1;

审计日志加密

-- 创建审计日志表
CREATE TABLE audit_logs (
    id SERIAL PRIMARY KEY,
    user_id INTEGER,
    action VARCHAR(100),
    details_encrypted BYTEA,
    ip_address INET,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入加密的审计日志
INSERT INTO audit_logs (user_id, action, details_encrypted, ip_address)
VALUES (
    1,
    'password_change',
    PGP_SYM_ENCRYPT('User changed password from 192.168.1.100', 'audit_key'),
    '192.168.1.100'::INET
);

-- 查询审计日志
SELECT 
    id,
    user_id,
    action,
    PGP_SYM_DECRYPT(details_encrypted, 'audit_key') AS details,
    ip_address,
    created_at
FROM audit_logs 
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days';

安全最佳实践

密钥管理

安全的密钥管理策略:

-- 创建密钥管理表
CREATE TABLE encryption_keys (
    id SERIAL PRIMARY KEY,
    key_name VARCHAR(100) UNIQUE NOT NULL,
    key_value TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP,
    is_active BOOLEAN DEFAULT TRUE
);

-- 插入密钥(实际应用中应从安全的密钥管理系统获取)
INSERT INTO encryption_keys (key_name, key_value, expires_at)
VALUES (
    'user_data_key',
    'super_secret_encryption_key_2023',
    CURRENT_DATE + INTERVAL '1 year'
);

-- 获取活动密钥
CREATE OR REPLACE FUNCTION get_active_encryption_key(key_name VARCHAR)
RETURNS TEXT AS $$
DECLARE
    key_value TEXT;
BEGIN
    SELECT key_value INTO key_value
    FROM encryption_keys
    WHERE key_name = key_name
      AND is_active = TRUE
      AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP);
    
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Active encryption key not found for: %', key_name;
    END IF;
    
    RETURN key_value;
END;
$$ LANGUAGE plpgsql;

密码策略

-- 创建密码强度检查函数
CREATE OR REPLACE FUNCTION check_password_strength(password TEXT)
RETURNS TABLE(
    is_strong BOOLEAN,
    score INTEGER,
    feedback TEXT
) AS $$
DECLARE
    length_score INTEGER := 0;
    uppercase_score INTEGER := 0;
    lowercase_score INTEGER := 0;
    digit_score INTEGER := 0;
    special_score INTEGER := 0;
    total_score INTEGER := 0;
BEGIN
    -- 长度检查
    IF LENGTH(password) >= 8 THEN
        length_score := 25;
    ELSIF LENGTH(password) >= 6 THEN
        length_score := 15;
    END IF;
    
    -- 大写字母检查
    IF password ~ '[A-Z]' THEN
        uppercase_score := 15;
    END IF;
    
    -- 小写字母检查
    IF password ~ '[a-z]' THEN
        lowercase_score := 15;
    END IF;
    
    -- 数字检查
    IF password ~ '[0-9]' THEN
        digit_score := 15;
    END IF;
    
    -- 特殊字符检查
    IF password ~ '[^A-Za-z0-9]' THEN
        special_score := 20;
    END IF;
    
    total_score := length_score + uppercase_score + lowercase_score + digit_score + special_score;
    
    RETURN QUERY SELECT 
        total_score >= 70 AS is_strong,
        total_score AS score,
        CASE 
            WHEN total_score >= 90 THEN 'Very Strong'
            WHEN total_score >= 70 THEN 'Strong'
            WHEN total_score >= 50 THEN 'Medium'
            WHEN total_score >= 30 THEN 'Weak'
            ELSE 'Very Weak'
        END AS feedback;
END;
$$ LANGUAGE plpgsql;

-- 使用密码强度检查
SELECT * FROM check_password_strength('MyP@ssw0rd123');

性能考虑

加密性能

加密操作的性能影响:

-- 测试不同加密方法的性能
EXPLAIN ANALYZE
SELECT 
    id,
    username,
    CRYPT(password_hash, password_hash) AS bcrypt_verify,
    MD5(password_hash) AS md5_hash
FROM users 
LIMIT 1000;

-- 为加密字段创建索引(谨慎使用)
-- 注意:加密数据通常不适合创建索引,因为加密后数据变得随机
-- 如果需要搜索,考虑使用确定性加密或搜索加密

批量加密处理

-- 批量加密用户数据
CREATE OR REPLACE FUNCTION batch_encrypt_user_data()
RETURNS INTEGER AS $$
DECLARE
    updated_count INTEGER := 0;
    user_record RECORD;
    encryption_key TEXT;
BEGIN
    -- 获取加密密钥
    encryption_key := get_active_encryption_key('user_data_key');
    
    -- 批量处理用户数据
    FOR user_record IN 
        SELECT id, phone, ssn 
        FROM users 
        WHERE phone_encrypted IS NULL OR ssn_encrypted IS NULL
    LOOP
        UPDATE users 
        SET 
            phone_encrypted = PGP_SYM_ENCRYPT(user_record.phone, encryption_key),
            ssn_encrypted = PGP_SYM_ENCRYPT(user_record.ssn, encryption_key)
        WHERE id = user_record.id;
        
        updated_count := updated_count + 1;
    END LOOP;
    
    RETURN updated_count;
END;
$$ LANGUAGE plpgsql;

扩展加密功能

安装加密扩展

-- 安装pgcrypto扩展
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- 验证扩展是否安装
SELECT * FROM pg_extension WHERE extname = 'pgcrypto';

-- 查看可用的加密函数
SELECT proname, prosrc 
FROM pg_proc 
WHERE proname ILIKE '%crypt%' 
   OR proname ILIKE '%pgp%' 
   OR proname ILIKE '%digest%'
ORDER BY proname;

高级加密功能

-- 使用不同的哈希算法
SELECT 
    DIGEST('password123', 'sha256') AS sha256_hash,
    DIGEST('password123', 'sha512') AS sha512_hash,
    DIGEST('password123', 'md5') AS md5_hash;

-- HMAC签名
SELECT 
    HMAC('message', 'secret_key', 'sha256') AS hmac_sha256,
    ENCODE(HMAC('message', 'secret_key', 'sha256'), 'hex') AS hmac_hex;

-- 随机数据生成
SELECT 
    GEN_RANDOM_BYTES(16) AS random_bytes,
    ENCODE(GEN_RANDOM_BYTES(16), 'hex') AS random_hex,
    GEN_RANDOM_UUID() AS random_uuid;

综合示例

-- 创建完整的用户管理系统
CREATE TABLE secure_users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    phone_encrypted BYTEA,
    ssn_encrypted BYTEA,
    is_active BOOLEAN DEFAULT TRUE,
    failed_login_attempts INTEGER DEFAULT 0,
    locked_until TIMESTAMP,
    last_login TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建用户注册函数
CREATE OR REPLACE FUNCTION register_user(
    p_username VARCHAR(50),
    p_email VARCHAR(255),
    p_password TEXT,
    p_phone VARCHAR(20),
    p_ssn VARCHAR(20)
)
RETURNS INTEGER AS $$
DECLARE
    user_id INTEGER;
    encryption_key TEXT;
BEGIN
    -- 检查密码强度
    IF (SELECT is_strong FROM check_password_strength(p_password)) = FALSE THEN
        RAISE EXCEPTION 'Password is not strong enough';
    END IF;
    
    -- 获取加密密钥
    encryption_key := get_active_encryption_key('user_data_key');
    
    -- 插入用户
    INSERT INTO secure_users (
        username, 
        email, 
        password_hash,
        phone_encrypted,
        ssn_encrypted
    )
    VALUES (
        p_username,
        p_email,
        CRYPT(p_password, GEN_SALT('bf')),
        PGP_SYM_ENCRYPT(p_phone, encryption_key),
        PGP_SYM_ENCRYPT(p_ssn, encryption_key)
    )
    RETURNING id INTO user_id;
    
    -- 记录审计日志
    INSERT INTO audit_logs (user_id, action, details_encrypted, ip_address)
    VALUES (
        user_id,
        'user_registration',
        PGP_SYM_ENCRYPT('User registered with email: ' || p_email, 'audit_key'),
        inet_client_addr()
    );
    
    RETURN user_id;
END;
$$ LANGUAGE plpgsql;

-- 创建用户登录函数
CREATE OR REPLACE FUNCTION login_user(
    p_username VARCHAR(50),
    p_password TEXT
)
RETURNS TABLE(
    user_id INTEGER,
    username VARCHAR(50),
    is_authenticated BOOLEAN
) AS $$
DECLARE
    user_record secure_users%ROWTYPE;
BEGIN
    -- 查找用户
    SELECT * INTO user_record 
    FROM secure_users 
    WHERE username = p_username 
      AND is_active = TRUE;
    
    -- 检查用户是否存在
    IF NOT FOUND THEN
        RETURN QUERY SELECT NULL::INTEGER, NULL::VARCHAR(50), FALSE;
        RETURN;
    END IF;
    
    -- 检查账户是否被锁定
    IF user_record.locked_until IS NOT NULL AND user_record.locked_until > CURRENT_TIMESTAMP THEN
        RAISE EXCEPTION 'Account is locked until %', user_record.locked_until;
    END IF;
    
    -- 验证密码
    IF user_record.password_hash = CRYPT(p_password, user_record.password_hash) THEN
        -- 登录成功
        UPDATE secure_users 
        SET 
            failed_login_attempts = 0,
            last_login = CURRENT_TIMESTAMP,
            updated_at = CURRENT_TIMESTAMP
        WHERE id = user_record.id;
        
        -- 记录登录日志
        INSERT INTO audit_logs (user_id, action, details_encrypted, ip_address)
        VALUES (
            user_record.id,
            'user_login',
            PGP_SYM_ENCRYPT('User logged in successfully', 'audit_key'),
            inet_client_addr()
        );
        
        RETURN QUERY SELECT user_record.id, user_record.username, TRUE;
    ELSE
        -- 登录失败
        UPDATE secure_users 
        SET 
            failed_login_attempts = failed_login_attempts + 1,
            locked_until = CASE 
                WHEN failed_login_attempts >= 4 THEN CURRENT_TIMESTAMP + INTERVAL '30 minutes'
                ELSE locked_until
            END,
            updated_at = CURRENT_TIMESTAMP
        WHERE id = user_record.id;
        
        RETURN QUERY SELECT NULL::INTEGER, NULL::VARCHAR(50), FALSE;
    END IF;
END;
$$ LANGUAGE plpgsql;

-- 使用示例
-- 注册用户
SELECT register_user('john_doe', 'john@example.com', 'MyP@ssw0rd123', '13800138000', '123-45-6789');

-- 用户登录
SELECT * FROM login_user('john_doe', 'MyP@ssw0rd123');

提示:在生产环境中使用加密功能时,务必遵循安全最佳实践。密钥管理是安全性的关键,建议使用专门的密钥管理系统。对于密码存储,始终使用强哈希算法如bcrypt,并为每个密码生成唯一的盐值。