引言
BEAM 虚拟机的进程隔离和不可变数据结构天然提供了一些安全优势——缓冲区溢出几乎不可能,共享内存攻击面极小。但应用层面的安全仍然需要主动设计:密码存储、TLS 配置、分布式节点认证、Secrets 管理、输入验证。本文覆盖 Erlang/Elixir 生态中的安全工程:从密码哈希到加密通信,从分布式信任到审计日志——构建一份 BEAM 系统的安全加固清单。
前置:/elixir-otp-supervision-tasks/(OTP 基础)、/erlang-logging-telemetry-observability/(可观测性)。
目录
- 1. 密码哈希bcryptArgon2 与 PBKDF2
- 2. 对称与非对称加密
- 3. 数字签名与证书TLS 与 SSL
- 4. 安全随机数与熵源
- 5. 分布式节点安全
- 6. 注入攻击防御
- 7. Secrets 管理环境变量与 Vault
- 8. BEAM 沙箱与资源限制
- 9. 安全审计与日志
- 10. 速查表与一句话记忆
- 延伸阅读
1. 密码哈希:bcrypt、Argon2 与 PBKDF2
1.1 选型对比
| 算法 | 特点 | 推荐场景 |
|---|---|---|
| bcrypt | 自适应 cost,抗 GPU | Web 应用密码(传统选择) |
| Argon2 | 内存困难,抗 ASIC | 新系统首选(2015 密码学竞赛冠军) |
| PBKDF2 | NIST 推荐,可调迭代 | 合规要求(FIPS) |
| scrypt | 内存困难,早期选择 | 加密货币 |
1.2 Elixir 实现
# bcrypt(comeonin + bcrypt_elixir)
hash = Bcrypt.hash_pwd_salt("password")
Bcrypt.verify_pass("password", hash) # => true
# Argon2(argon2_elixir)
hash = Argon2.hash_pwd_salt("password")
Argon2.verify_pass("password", hash) # => true
1.3 密码策略
# 最小长度:12+ 字符
# 复杂度:不强求特殊字符(NIST 建议)
# 哈希参数:bcrypt cost >= 12,Argon2 memory >= 64MB
# 盐值:自动生成,无需管理
# 禁止:MD5/SHA1/SHA256 存储密码(太快了,易暴力破解)
记忆 密码哈希选型——新系统 Argon2(内存困难)、老系统 bcrypt(自适应 cost)、合规用 PBKDF2;禁止用 SHA 系列存密码(太快易被暴力破解);bcrypt cost>=12、Argon2 memory>=64MB。
2. 对称与非对称加密
2.1 对称加密(AES)
# crypto 模块(OpenSSL 绑定)
key = :crypto.strong_rand_bytes(32) # AES-256
iv = :crypto.strong_rand_bytes(16)
plaintext = "secret data"
ciphertext = :crypto.crypto_one_time(:aes_256_cbc, key, iv, plaintext, true)
decrypted = :crypto.crypto_one_time(:aes_256_cbc, key, iv, ciphertext, false)
2.2 非对称加密(RSA/ECIES)
# 生成密钥对
{:ok, pubkey, privkey} = :crypto.generate_key(:rsa, {2048, 65537})
# 加密(公钥)
encrypted = :crypto.public_encrypt(:rsa, plaintext, pubkey, :rsa_pkcs1_oaep_padding)
# 解密(私钥)
decrypted = :crypto.private_decrypt(:rsa, encrypted, privkey, :rsa_pkcs1_oaep_padding)
2.3 密钥管理
# 对称密钥:KMS(AWS KMS/Google Cloud KMS)或 Vault 加密存储
# 非对称密钥:硬件安全模块(HSM)存储私钥
# 密钥轮换:定期更新,支持多版本解密
记忆 对称加密 AES-256 GCM(认证加密防篡改)、非对称 RSA-2048+OAEP 或 ECC(更快更小);密钥不硬编码、用 KMS/Vault 管理、定期轮换。
3. 数字签名与证书:TLS 与 SSL
3.1 TLS 配置(Phoenix)
# config/prod.exs
config :my_app, MyAppWeb.Endpoint,
https: [
port: 443,
cipher_suite: :strong, # 只启用强密码套件
certfile: System.get_env("SSL_CERT_PATH"),
keyfile: System.get_env("SSL_KEY_PATH"),
versions: [:'tlsv1.2', :'tlsv1.3'], # 禁用旧版本
honor_cipher_order: true,
eccs: [:secp256r1, :secp384r1],
transport_options: [socket_opts: [:inet6]]
]
3.2 证书管理
# Let's Encrypt:免费自动更新(用 certbot 或 legobot)
# 自签名:开发/内网用
# 商业证书:通配符/SAN 支持多域名
# 监控:证书过期告警(30/14/7 天)
3.3 证书固定
# SSL 握手时固定公钥(防中间人)
:ssl.connect('api.example.com', 443,
verify: :verify_peer,
cacertfile: '/path/to/ca.pem',
customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]
)
记忆 TLS 配置用 strong cipher_suite + tlsv1.2/1.3 + 证书路径环境变量;证书用 Let’s Encrypt 自动更新 + 过期监控;验证证书链防中间人。
4. 安全随机数与熵源
4.1 随机数生成
# 安全随机(CSPRNG)
:crypto.strong_rand_bytes(32) # 32 字节 = 256 位
# 不安全(不要用于安全场景)
:rand.uniform() # 伪随机,可预测
4.2 熵池
# Linux:/dev/urandom(非阻塞)和 /dev/random(阻塞到熵够)
# Erlang crypto:使用 OpenSSL 的 RAND_bytes
# Docker/VM:确保 virtio-rng 或 rng-tools 提供足够熵
记忆 安全随机用 crypto:strong_rand_bytes(CSPRNG);不要用 :rand.uniform 做安全相关;容器环境确保 rng-tools 提供足够熵。
5. 分布式节点安全
5.1 Cookie 认证
# Erlang 分布式默认用 cookie 认证
# ~/.erlang.cookie 或 -setcookie 启动参数
# 问题:明文传输、同一 cookie 所有节点互信
# 缓解:
# - Cookie 文件权限 400
# - 不同环境用不同 cookie
# - 生产环境不用 cookie 而用 TLS
5.2 TLS 分布式
# 启动带 TLS 的节点
erl -proto_dist inet_tls -ssl_dist_optfile dist.conf
# dist.conf
[{server,
[{certfile, "server.pem"},
{keyfile, "server.key"},
{cacertfile, "ca.pem"}]},
{client,
[{certfile, "client.pem"},
{keyfile, "client.key"},
{cacertfile, "ca.pem"}]}].
5.3 网络隔离
# 防火墙:只开放必要的 Erlang 端口(epmd 4369 + 动态端口范围)
# VPC/安全组:分布式节点在同一私有网络
# 禁止:分布式节点暴露在公网(尤其是 cookie 认证)
记忆 分布式安全——cookie 文件权限 400、不同环境不同 cookie;生产用 TLS 分布式(-proto_dist inet_tls);防火墙限制 epmd+动态端口、节点不暴露在公网。
6. 注入攻击防御
6.1 SQL 注入
# 安全:Ecto 参数绑定
Repo.all(from u in User, where: u.email == ^email)
# 危险:字符串拼接
Repo.query("SELECT * FROM users WHERE email = '#{email}'")
# 如果 email = "' OR '1'='1" → 全表暴露
6.2 XSS
# Phoenix 模板自动转义
<%= @user_input %> # 自动 HTML 实体编码
# 危险:raw/1 标记
<%= raw @user_input %> # 仅用于可信内容
# CSP 头
plug :put_secure_browser_headers, %{
"content-security-policy" => "default-src 'self'"
}
6.3 命令注入
# 危险
System.cmd("sh", ["-c", "echo #{user_input}"])
# 安全:用列表传参(无 shell 解析)
System.cmd("echo", [user_input])
# 或验证白名单
allowed = ~w(ping curl wget)
if command in allowed do
System.cmd(command, args)
end
记忆 注入防御——SQL 用 Ecto 参数绑定 ^var、XSS 用 Phoenix 自动转义 + CSP、命令注入用列表传参而非 shell 字符串;raw/1 和 fragment 要谨慎。
7. Secrets 管理:环境变量与 Vault
7.1 环境变量(开发/测试)
# config/runtime.exs
config :my_app, MyApp.Repo,
password: System.get_env("DB_PASSWORD")
config :my_app, MyAppWeb.Endpoint,
secret_key_base: System.get_env("SECRET_KEY_BASE")
7.2 Vault(生产)
# 使用 HashiCorp Vault
{:ok, %{data: data}} = Vaultex.read("secret/my_app")
config = Map.merge(config, data)
7.3 配置安全清单
1) 密码/密钥不入 Git(.gitignore .env)
2) 生产配置用 runtime.exs(编译时不包含敏感值)
3) Secret Key Base >= 64 字节随机值
4) 数据库 URL 含密码时用环境变量
5) 定期轮换密钥和证书
记忆 Secrets 管理——开发用 .env、生产用 Vault/KMS;密码密钥不入 Git、用 runtime.exs 编译时不含敏感值、Secret Key Base >= 64 字节随机、定期轮换。
8. BEAM 沙箱与资源限制
8.1 进程级限制
% 限制进程堆大小(超过触发 GC)
process_flag(max_heap_size, #{size => 1024*1024, kill => true, error_logger => true}).
% 限制消息队列(超过丢弃或杀死)
process_flag(message_queue_data, off_heap).
8.2 节点级限制
# 最大进程数:+P 参数(默认 ~262k)
erl +P 1000000
# 最大 ETS 表数
+env ERL_MAX_ETS_TABLES 50000
# 内存限制(通过 cgroup 或 OS 限制)
8.3 代码加载安全
# 禁止远程代码加载(防止劫持)
# -noshell -noinput 减少攻击面
# 签名验证:代码加载前验证签名
记忆 BEAM 资源限制——进程堆 max_heap_size、节点 +P 进程数、+env ETS 表数;禁止远程代码加载、用 noshell 减少攻击面。
9. 安全审计与日志
9.1 审计事件
# 安全相关操作必须记录
Logger.warn("Failed login attempt",
user_id: username,
ip: conn.remote_ip,
user_agent: conn.req_headers["user-agent"])
Logger.info("Password changed", user_id: user.id)
Logger.info("Role changed", user_id: user.id, old_role: old, new_role: new)
9.2 日志保护
# 日志中不输出:密码、token、信用卡号、PII
# 脱敏:email → a***@example.com
# 保留:用户 ID、IP、时间、操作结果
# 存储:集中式日志,防篡改,定期归档
记忆 安全审计——登录/登出/密码改/权限变更/失败访问全记录;日志脱敏(不写密码/token/PII)、保留用户 ID/IP/时间/结果;集中存储防篡改。
10. 速查表与一句话记忆
| 概念 | 一句话 |
|---|---|
| Argon2 | 新系统首选密码哈希 |
| bcrypt | 老系统兼容 |
| AES-256-GCM | 对称加密 |
| RSA-2048-OAEP | 非对称加密 |
| strong_rand_bytes | 安全随机 |
| TLS 1.3 | 强传输加密 |
| cookie 400 | 分布式认证 |
| Ecto ^ | SQL 参数绑定 |
| raw/1 | 谨慎使用 |
| runtime.exs | 运行时配置 |
| Vault | 生产 Secrets |
| audit log | 安全操作记录 |
一句话记忆:Erlang/Elixir 安全——密码用 Argon2(新)/bcrypt(旧)存哈希,禁止 SHA 存密码;加密 AES-256-GCM 对称、RSA-2048+OAEP 非对称;随机数用 crypto:strong_rand_bytes;TLS 配 strong cipher + 1.2/1.3、证书自动更新 Let’s Encrypt;分布式 cookie 文件权限 400、生产用 TLS 分布式(-proto_dist inet_tls)、节点不暴露公网;注入防御——SQL 用 Ecto ^ 绑定、XSS 靠 Phoenix 转义 + CSP、命令注入用列表传参;Secrets 用 runtime.exs + Vault/KMS、Secret Key Base >= 64 字节随机;BEAM 资源限制 max_heap_size +P 进程数;审计日志记录登录/权限变更,脱敏不写密码/token——「BEAM 的进程隔离是好基础,但应用安全仍需逐条加固」。
延伸阅读
- /elixir-otp-supervision-tasks/ — OTP 基础
- /erlang-logging-telemetry-observability/ — 可观测性
- Web 安全专题 — 通用 Web 安全
- 密码学专题 — 密码学原理
- OWASP Elixir
- comeonin 文档
- Phoenix Security Guide
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。