本方案基于 Node.js + PostgreSQL 构建,重点解决档案元数据加密、传输安全及日志脱敏问题。首先需要在服务器或本地开发环境准备基础运行环境。
1. 初始化项目目录
在终端执行以下命令创建项目并初始化 package.json:
mkdir archive-security-demo
cd archive-security-demo
npm init -y
2. 安装核心依赖包
安装 Express 框架、数据库驱动、加密库及 JWT 认证工具:
npm install express pg dotenv jsonwebtoken bcryptjs body-parser
3. 创建环境配置文件
在项目根目录创建 .env 文件,填入以下配置。注意:生产环境必须更换强密码。
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_USER=archive_admin
DB_PASSWORD=SecurePass123!
DB_NAME=archive_db
JWT_SECRET=Your_Super_Secret_JWT_Key_Change_Me
ENCRYPTION_KEY=This_is_a_32_byte_long_secret_key!!
为了确保环境一致性,使用 Docker 快速启动 PostgreSQL 数据库。如果已安装数据库,可跳过此步直接建表。
1. 启动 PostgreSQL 容器
执行以下命令,映射端口并设置初始密码:
docker run --name archive-secure-db \
-e POSTGRES_PASSWORD=SecurePass123! \
-e POSTGRES_DB=archive_db \
-p 5432:5432 \
-d postgres:15-alpine
2. 初始化数据表结构
连接数据库后,执行以下 SQL 创建用户表与档案表。注意档案表中的 sensitive_data 字段设计为 TEXT 类型,用于存储加密后的 JSON 串。
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(20) DEFAULT 'viewer'
);
CREATE TABLE archives (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
owner_id INTEGER REFERENCES users(id),
-- 存储加密后的敏感信息(如身份证号、家庭住址)
sensitive_data TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
创建 utils/crypto.js 文件。这是隐私保护的核心,采用 AES-256-GCM 算法。该算法不仅提供机密性,还保证数据完整性,能有效防止密文被篡改。

const crypto = require('crypto');
const algorithm = 'aes-256-gcm';
const keyLength = 32;
const ivLength = 16;
const saltLength = 64;
const tagLength = 16;
const iterations = 100000;
// 从环境变量获取密钥,实际生产中建议使用 KMS 服务
function getKeyFromPassword(password, salt) {
return crypto.pbkdf2Sync(password, salt, iterations, keyLength, 'sha256');
}
/
加密函数
@param {string} text - 明文 JSON 字符串
@returns {string} - 格式为 salt:iv:tag:encrypted 的字符串
/
function encrypt(text) {
const iv = crypto.randomBytes(ivLength);
const salt = crypto.randomBytes(saltLength);
const key = getKeyFromPassword(process.env.ENCRYPTION_KEY, salt);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
// 组合存储:salt + iv + tag + encrypted
return `${salt.toString('hex')}:${iv.toString('hex')}:${tag.toString('hex')}:${encrypted}`;
}
/
解密函数
@param {string} encryptedData - 加密字符串
@returns {object} - 解密后的 JSON 对象
/
function decrypt(encryptedData) {
const parts = encryptedData.split(':');
if (parts.length !== 4) throw new Error('Invalid data format');
const salt = Buffer.from(parts[0], 'hex');
const iv = Buffer.from(parts[1], 'hex');
const tag = Buffer.from(parts[2], 'hex');
const encrypted = parts[3];
const key = getKeyFromPassword(process.env.ENCRYPTION_KEY, salt);
const decipher = crypto.createDecipheriv(algorithm, key, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
module.exports = { encrypt, decrypt };
创建 middleware/auth.js。隐私保护的前提是确认访问者身份。这里使用 JWT 进行无状态认证,并在中间件中拦截非法请求。
const jwt = require('jsonwebtoken');
// 模拟的中间件,生产环境请连接数据库验证 Token 黑名单
const authMiddleware = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(403).json({ error: '未提供访问令牌' });
}
// 移除 "Bearer " 前缀
const tokenString = token.startsWith('Bearer ') ? token.slice(7) : token;
jwt.verify(tokenString, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({ error: '令牌无效或已过期' });
}
// 将用户信息附加到请求对象
req.user = decoded;
next();
});
};
module.exports = { authMiddleware };
创建 server.js 主文件。本节实现档案的录入(加密存储)与查询(解密展示),并加入日志脱敏函数,防止敏感信息打印在服务器日志中。
require('dotenv').config();
const express = require('express');
const { Pool } = require('pg');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { encrypt, decrypt } = require('./utils/crypto');
const { authMiddleware } = require('./middleware/auth');
const app = express();
app.use(express.json());
// 数据库连接池配置
const pool = new Pool({
connectionString: `postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`
});
// 日志脱敏工具:替换 JSON 字符串中的敏感字段值
const maskLogData = (data) => {
const str = JSON.stringify(data);
return str
.replace(/"idCard":\s"\d{17}[\dXx]/g, '"idCard":""')
.replace(/"phone":\s"\d{11}/g, '"phone":""')
.replace(/"address":\s".+?"/g, '"address":""');
};
// 接口 1: 用户注册
app.post('/api/register', async (req, res) => {
const { username, password } = req.body;
const passwordHash = await bcrypt.hash(password, 10);
try {
const result = await pool.query(
'INSERT INTO users (username, password_hash) VALUES ($1, $2) RETURNING id',
[username, passwordHash]
);
res.json({ userId: result.rows[0].id });
} catch (err) {
res.status(500).json({ error: '注册失败' });
}
});
// 接口 2: 用户登录
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
const result = await pool.query('SELECT FROM users WHERE username = $1', [username]);
if (result.rows.length === 0) return res.status(400).json({ error: '用户不存在' });
const user = result.rows[0];
const isValid = await bcrypt.compare(password, user.password_hash);
if (isValid) {
const token = jwt.sign({ id: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
} else {
res.status(400).json({ error: '密码错误' });
}
});
// 接口 3: 创建档案(加密存储)
app.post('/api/archives', authMiddleware, async (req, res) => {
const { title, idCard, phone, address } = req.body;
// 1. 构造敏感信息对象
const sensitiveInfo = { idCard, phone, address, ownerId: req.user.id };
// 2. 加密
const encryptedData = encrypt(JSON.stringify(sensitiveInfo));
// 3. 存入数据库
try {
// 打印脱敏后的日志,确保日志不泄露隐私
console.log(`[Audit] User ${req.user.id} creating archive: ${maskLogData(sensitiveInfo)}`);
const result = await pool.query(
'INSERT INTO archives (title, owner_id, sensitive_data) VALUES ($1, $2, $3) RETURNING id',
[title, req.user.id, encryptedData]
);
res.json({ archiveId: result.rows[0].id, status: 'encrypted_stored' });
} catch (err) {
console.error(err);
res.status(500).json({ error: '档案创建失败' });
}
});
// 接口 4: 查询档案(解密展示)
app.get('/api/archives/:id', authMiddleware, async (req, res) => {
const { id } = req.params;
try {
const result = await pool.query('SELECT FROM archives WHERE id = $1', [id]);
if (result.rows.length === 0) return res.status(404).json({ error: '档案不存在' });
const archive = result.rows[0];
// 权限控制:仅允许创建者查看
if (archive.owner_id !== req.user.id) {
return res.status(403).json({ error: '无权访问该档案' });
}
// 解密数据
const decryptedData = decrypt(archive.sensitive_data);
res.json({
id: archive.id,
title: archive.title,
// 返回解密后的明文数据给前端
data: decryptedData,
created_at: archive.created_at
});
} catch (err) {
console.error(err);
res.status(500).json({ error: '解密失败或数据损坏' });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`隐私保护服务已启动,端口 ${PORT}`);
});
启动服务并进行完整流程测试,确保加密与解密逻辑无误。
1. 启动服务
node server.js
2. 注册用户
curl -X POST http://localhost:3000/api/register \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "Admin123!"}'
3. 登录获取 Token
curl -X POST http://localhost:3000/api/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "Admin123!"}'
将返回结果中的 token 值复制出来,例如 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...。
4. 创建加密档案
提交包含身份证号等敏感信息的数据。观察服务器控制台日志,应显示脱敏后的 ,而非明文。
curl -X POST http://localhost:3000/api/archives \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-d '{
"title": "机密人事档案",
"idCard": "11010519900307234X",
"phone": "13800138000",
"address": "北京市海淀区保密局大院1号"
}'
5. 查询档案验证解密
使用上一步返回的 archiveId 进行查询,应能完整还原明文信息。
curl http://localhost:3000/api/archives/1 \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
6. 数据库直接验证
进入数据库容器查看 archives 表,sensitive_data 字段内容应为乱码密文,确保静态数据安全。
docker exec -it archive-secure-db psql -U archive_admin -d archive_db -c "SELECT sensitive_data FROM archives LIMIT 1;"