Erlang/Elixir 安全加固:加密、认证与分布式信任

Erlang/Elixir 安全加固:密码哈希(bcrypt/Argon2/PBKDF2)、对称与非对称加密(AES/RSA/ECIES)、数字签名与证书(x509/TLS/SSL)、安全随机数生成(crypto:strong_rand_bytes)、分布式节点安全(cookie/TLS/防火墙)、常见注入攻击防御(SQL 注入/XSS/代码注入)、Secrets 管理(runtime.exs/环境变量/HashiCorp Vault)、BEAM 沙箱与资源限制、安全审计与日志。

引言

BEAM 虚拟机的进程隔离和不可变数据结构天然提供了一些安全优势——缓冲区溢出几乎不可能,共享内存攻击面极小。但应用层面的安全仍然需要主动设计:密码存储、TLS 配置、分布式节点认证、Secrets 管理、输入验证。本文覆盖 Erlang/Elixir 生态中的安全工程:从密码哈希到加密通信,从分布式信任到审计日志——构建一份 BEAM 系统的安全加固清单。

前置:/elixir-otp-supervision-tasks/(OTP 基础)、/erlang-logging-telemetry-observability/(可观测性)。


目录


1. 密码哈希:bcrypt、Argon2 与 PBKDF2

1.1 选型对比

算法特点推荐场景
bcrypt自适应 cost,抗 GPUWeb 应用密码(传统选择)
Argon2内存困难,抗 ASIC新系统首选(2015 密码学竞赛冠军)
PBKDF2NIST 推荐,可调迭代合规要求(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. 分布式节点安全

# 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 的进程隔离是好基础,但应用安全仍需逐条加固」。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「erlang」更多文章

  1. OTP 应用设计模式:监督树结构、release 打包与热升级
  2. Ecto 高级查询与数据库工程:关联、多态与性能优化
  3. Erlang/Elixir 可观测性:日志、Telemetry 指标与分布式追踪