
1. 为什么要把认证方式从MD5换到scram-sha-256先说个现实问题很多PostgreSQL老库跑了好几年pg_hba.conf里还写着password_encryption md5甚至pg_authid里存着一堆以md5开头的密码哈希。平时没人管直到安全审计、等保检查、或者某次安全通告把MD5认证列为中高危项DBA才不得不面对这次“从MD5到scram-sha-256”的迁移。PostgreSQL从10版本开始就把默认的密码加密方式改成了scram-sha-256但如果你是从9.x、10.x早期继承下来的实例或者创建角色时显式指定过md5库里的老用户依然是旧哈希。今天这篇就围绕PostgreSQL密码认证的这条升级路线把原理、迁移步骤、踩坑点一次性讲透适合正在维护存量实例、又不想在切换时把业务搞挂的DBA和运维同学。很多第一次接触这个主题的人会问MD5和scram-sha-256不都是“密码加密”吗改个参数不就行了还真不是。MD5认证的本质是客户端把“MD5(密码用户名)”的哈希值发给服务端服务端和存储的哈希比对。这里面有两个致命问题一是这个哈希值在传输过程中是静态的抓到一次就能离线爆破二是MD5本身已被证明碰撞和预计算攻击都非常容易GPU跑字典的速度快到离谱。而scram-sha-256走的是RFC 5802定义的SASL流程核心特征是“盐迭代次数服务端/客户端双向验证”每次认证的挑战值都不一样即使同一条密码抓包看到的也是完全不同的交互过程。所以说这不是换个加密函数的事而是把整个认证协议换成了一套更现代的机制。1.1 MD5认证到底有什么问题MD5认证用的哈希结构是md5 MD5(password username)的十六进制结果。比如用户alice的密码是secret服务端存的就是md5后面跟一段32位哈希。问题出在几个层面第一它没有盐。同一用户同一密码在任何数据库里生成的哈希完全一致攻击者拿到pg_authid的哈希文件后可以提前跑彩虹表。更麻烦的是网卡抓包抓到的认证响应里也直接包含这个静态哈希配合抓包工具可以直接离线猜密码。第二它只是单向认证。客户端验证了服务端吗没有。在非SSL链路上中间人可以扮演服务端收集客户端发来的MD5响应后拿去服务端重放或者直接给客户端返回一个假的“认证成功”报文。虽然PG的正常流程里客户端不会发真正的密码明文但MD5认证缺少服务端证明自己身份的密码证据这就给中间人攻击留下了空间。第三MD5算法本身已经“退休”。2004年的CVE-2004-2761就是针对X.509证书里MD5签名冲突的利用虽然那是证书领域的事但反映出整个行业对MD5已经不信任。PostgreSQL官方在文档里也明确把md5标记为“仅用于兼容”而不是推荐的长期方案。更关键的是只要认证方式还是md5即使你改了password_encryption新密码依然可能被写成旧格式迁移就会变得很拧巴。1.2 scram-sha-256赢在哪里scram-sha-256不是PostgreSQL发明的它来自SASL框架全称是Salted Challenge Response Authentication MechanismSHA-256版本。核心流程可以简单理解成三步客户端先说“我要用scram-sha-256认证”服务端返回一个随机数nonce和用户对应的盐、迭代次数客户端用密码加盐做PBKDF2派生生成一系列证明消息服务端验证通过后还要反过来用自己持有的密钥材料证明“我知道这个密码”客户端也会验证服务端。这个设计带来四个直接好处抓包抓到的都是挑战、随机数、证明消息没有静态可重放的哈希每个用户有独立盐迭代次数可配置抵抗彩虹表和离线字典攻击的能力强了一个量级双向认证能有效防中间人协议是标准的ODBC、JDBC、libpq等主流驱动都支持不绑定PG私货。我经常拿门锁来打比方MD5认证就像一把“钥匙直接交给门卫看一眼”钥匙本身是固定的拍照复制就能进scram-sha-256则是“门卫给你一张随机卷子你现场算答案而且门卫也要证明他真知道门怎么开”。密码在网络里从来不直接出现每次卷子还不一样。1.3 你该不该升级场景判断当然不是所有库都必须立刻切。我见过不少还在跑PostgreSQL 9.6的项目因为兼容老应用一直用md5。我的建议是如果你已经用PG 10以上驱动也比较新那没有理由继续留在MD5越早切成本越低如果你还在PG 9.6及以下先考虑升级到受支持版本因为老版本本身就有安全和生命周期问题单独换认证方式解决不了根本如果你的老应用用的驱动只支持MD5那就得先升级驱动再做切换否则认证一改应用直接连不上如果库里有大量用户并且密码是人肉管理的建议切换的同时顺手做一次密码有效期梳理否则一堆“僵尸账号”也会在下一次审计里被拎出来。所以整篇博文的操作主线很清晰先盘点现状再改配置最后逐用户重置密码并验证。不搞“改完配置跑路”那一套而是确保每一步都可回滚、可验证。2. 升级前的准备与现状盘点我踩过最大的坑就是“以为改了pg_hba.conf就算迁移了”。实际上PostgreSQL的密码认证有两层一层是pg_hba.conf里指定的认证方式另一层是pg_authid里每个用户的密码哈希格式。前者决定“这次连接用什么协议”后者决定“这次连接能不能通过”。如果你只改pg_hba.conf为scram-sha-256但库里用户存储的还是md5开头的哈希那么所有存量用户都会认证失败因为服务端根本没有scram格式的材料可以验证。所以升级前必须把现状摸排清楚。2.1 先看当前认证配置第一件事是确认实例当前状态。连上库执行show password_encryption;这个参数决定新设置密码时生成的哈希格式默认在PG 10是scram-sha-256但老实例升级或手动改过的话可能还是md5。再看监听端口和当前认证规则cat $PGDATA/pg_hba.conf | grep -v ^# | grep -v ^$重点关注host开头的行尤其是127.0.0.1/32、::1/128以及业务网段对应的认证方法。典型配置可能是host all all 127.0.0.1/32 md5 host all all 192.168.1.0/24 md5接下来查存量用户的哈希格式SELECT rolname, substring(rolpassword from 1 for 3) AS hash_prefix, length(rolpassword) AS hash_len FROM pg_authid WHERE rolcanlogin true ORDER BY rolname;这里能看到三类md5开头老式MD5哈希SCRAM-SHA-256开头已经是scram格式NULL或空密码未设置可能是外部认证或无需登录。统计一下有多少用户需要迁移SELECT count(*) FROM pg_authid WHERE rolcanlogin true AND rolpassword LIKE md5%;这个数字就是你后续要处理的用户量。别只盯着rolname因为PostgreSQL的角色名可以包含特殊字符后面写重置脚本时最好用quote_ident做安全拼接避免SQL注入或转义错误。2.2 密码存储格式与重置策略很多人会问能不能把pg_authid里的md5哈希直接“转换”成scram哈希答案是不能。因为MD5哈希是单向的PostgreSQL存储的并不是密码明文而是MD5(passwordusername)的结果。你没法从这个结果反推出密码自然也就无法用scram算法重新对同一密码加盐生成新哈希。所以迁移的唯一正规路径是为每个用户重新设置密码。实际工作中通常有这几种办法如果业务方知道密码就让他们按新密码重新设置顺便加固如果不知道密码DBA可以临时生成强密码并通知业务方修改如果密码由配置中心或K8s Secret管理直接把Secret里的密码更新后同步执行ALTER ROLE ... PASSWORD如果应用只支持单条连接串且密码不能动那至少需要知道密码明文一次才能在服务端生成scram哈希。这里要特别提示一个策略问题有些DBA为了省事会把所有用户重置成同一个临时密码然后让应用方改。这在测试环境可以生产环境风险很高。建议给每个用户生成独立随机密码并配套密码管理流程。比如用openssl rand -base64 18生成再通过安全渠道分发。还有一种场景是使用外部认证LDAP、Kerberos或authomation插件这些用户不需要密码迁移pg_authid里可能没有密码哈希盘点时不要误伤。2.3 驱动和连接串兼容性这是最容易被忽略的一步但也是生产事故高发点。scram-sha-256要求客户端库支持SASL机制。具体来说客户端支持scram的最低版本备注libpqpsqlPostgreSQL 1010以上默认支持JDBC42.2.0之前版本默认不支持或需额外jarNpgsql (.NET)3.2.0老版本不支持psycopg2 (Python)2.82.8以前不支持node-postgres8.0早期版本不支持PGX (Go)4.0老版本不支持ODBCpsqlODBC 11.00看发行版说明如果你的业务还在用很老的驱动比如Java 8配老JDBC驱动、Python 2.7配psycopg2 2.7那么切换后大概率出现authentication method not supported或者SASL AuthenticationNotSupported之类的报错。我的建议是先在一台测试机上把驱动升到新版本或者至少在预发布环境完整跑一遍连接测试再动生产。连接串本身一般不用改因为postgresql://user:passhost/db不携带认证类型。但如果你在连接串里显式配置了sslmoderequire要注意scram配合SSL的体验更好强烈建议生产环境开SSL。虽然scram本身是安全的挑战响应机制但加上SSL可以防止登录信息在非加密链路上被中间人做降级攻击。3. 完整迁移实操从pg_hba.conf到密码重置下面进入正题。这一套操作我在多个实例上验证过流程适用于PG 10到PG 16。你不需要停库也不需要重启集群只改配置和重置密码就能平滑完成但一定要按顺序来。3.1 修改postgresql.conf与pg_hba.conf第一步先把默认密码加密方式改成scram这样后续所有新建角色和重置密码都会生成scram哈希。编辑postgresql.confpassword_encryption scram-sha-256这个参数可以用ALTER SYSTEM在线修改避免直接编辑文件出错ALTER SYSTEM SET password_encryption scram-sha-256; SELECT pg_reload_conf();pg_reload_conf()会让主进程重新加载配置不需要重启。但注意这个参数不会影响已存在用户的哈希格式只影响后续SET PASSWORD或CREATE ROLE ... PASSWORD的结果。第二步改pg_hba.conf。原则是“先测试后生效、逐网段切换”。不要把线上所有网段一次性全改成scram否则一旦有问题业务全挂回滚还得靠手速。推荐做法是先在管理网段或本地连接改成scram验证无误后再改业务网段。比如当前是这样host all all 127.0.0.1/32 md5 host all all 192.168.1.0/24 md5 host all all 10.0.0.0/8 md5先改成host all all 127.0.0.1/32 scram-sha-256 host all all 192.168.1.0/24 md5 host all all 10.0.0.0/8 md5重新加载pg_ctl reload -D $PGDATA或者SQLSELECT pg_reload_conf();然后立刻psql连本地试psql -h 127.0.0.1 -U alice -d postgres注意如果alice的密码哈希还是md5格式这一步会失败因为pg_hba.conf要求scram但库里没有scram哈希服务端无法构建挑战。所以成功的前提是先给这个用户重置密码或者至少准备一个已经重置过的测试账号。更稳妥的切换顺序是修改postgresql.conf的password_encryptionreload对测试账号重置密码确认生成SCRAM-SHA-256格式修改pg_hba.conf把某个测试来源IP改成scramreload用测试账号从该IP连接验证再批量处理所有存量用户最后把业务网段全部切到scramreload并观察日志。3.2 让现有用户切换到scram批量重置密码是这个环节的核心工作。对于所有rolpassword like md5%的用户执行ALTER ROLE username PASSWORD new_password;执行后验证SELECT rolname, substring(rolpassword from 1 for 3) FROM pg_authid WHERE rolname username;预期结果为SCR开头。实际生产里批量脚本建议写成只读查询输出SQL再由DBA审阅后执行。比如先生成重置语句到文件SELECT ALTER ROLE || quote_ident(rolname) || PASSWORD Temp || rolname || 123; FROM pg_authid WHERE rolcanlogin true AND rolpassword LIKE md5%;然后用psql执行生成的SQL。这种方式适合测试环境生产不建议使用可预测密码。更推荐的做法是写一个PL/pgSQL函数或Shell脚本动态生成随机密码并写入审计表CREATE OR REPLACE FUNCTION rotate_passwords() RETURNS void AS $$ DECLARE r record; new_pwd text; BEGIN FOR r IN SELECT rolname FROM pg_authid WHERE rolcanlogin true AND rolpassword LIKE md5% LOOP new_pwd : substr(md5(random()::text || clock_timestamp()::text), 1, 16); EXECUTE format(ALTER ROLE %I PASSWORD %L, r.rolname, new_pwd); RAISE NOTICE Rotated user: %, r.rolname; END LOOP; END; $$ LANGUAGE plpgsql;注意函数里的new_pwd可能包含纯hex字符强度一般。生产我更喜欢用pgcrypto扩展里的gen_random_bytes生成高熵随机密码但这需要先CREATE EXTENSION pgcrypto。也可以在外面用Shell处理把生成好的密码文件安全映射到DB。还有一个非常关键的细节重置密码会触发密码更新时间rolpassword变化但不会中断当前已建立的会话。也就是说你可以在业务在线的时候改密码现有连接继续用到退出为止新连接才需要新密码。这对滚动发布很友好但也要提醒业务方部署新配置后才能连上。如果用户过多想要“无感切换”其实还有一条路利用pg_authid的临时扩展比如password_check插件来批量迁移但复杂度高不如老老实实重置密码。别去直接UPDATEpg_authid的rolpassword字段那个是内部系统表强制更新可能导致缓存不一致、认证异常官方不支持。3.3 验证连接与权限迁移完成后不能只看数据库自己连得上还要模拟业务连接。建议从应用服务器发起真实连接测试至少覆盖普通账号密码登录超级用户登录使用了连接池的账号如PgBouncer、pgpool-II需要跨网段的连接SSL链路如果启用。测试命令示例psql host10.0.0.15 dbnameappdb userapp_user sslmoderequire -c select 1驱动侧测试可以用Python的psycopg2快速验证import psycopg2 conn psycopg2.connect( host10.0.0.15, dbnameappdb, userapp_user, passwordnew_password, sslmoderequire ) cur conn.cursor() cur.execute(select version()) print(cur.fetchone())我们要确认的不是“能不能跑出一条select”而是服务端日志里有没有authentication failed。看日志tail -f $PGDATA/log/postgresql-*.log | grep authentication正常情况应该看到类似connection received: host10.0.0.15 port5432 SCRAM authentication successful这里有个小技巧可以通过log_min_messages临时调到debug1来观察更多认证细节但生产环境不建议长期开启日志量太大。4. 踩坑实录与排查技巧最后这部分是我真正想分享的。MD5切scram的过程中我见过太多“看似成功实则埋雷”的操作。下面把高频坑和排查思路整理成速查表并给出几条独家经验。4.1 常见问题速查表问题现象可能原因解决方案psql连接报authentication method not supportedpsql/驱动太老不支持SASL升级libpq或客户端库到PG10以上版本所有存量用户突然无法登录只改了pg_hba.conf但没重置密码存量还是md5哈希按3.2节重置密码生成scram哈希应用日志报password authentication failed密码未更新或连接串仍用旧密码同步更新连接串/Secret确认应用配置中心已发布报SCRAM authentication requires libpq version 10应用自带的libpq版本过旧升级驱动或者临时为这个来源IP保留md5重装驱动后还是不行pg_hba.conf中来源IP匹配到了其他规则用pg_hba.conf规则匹配顺序检查pg_controldata或使用\conninfo确认修改password_encryption后新密码依然是md5没有执行reload或参数拼写错误检查SHOW password_encryption;确保为scram-sha-256PgBouncer连接池报错连接池自身版本不支持scram升级PgBouncer到1.9同时确认auth_type配置角色是replication权限用scram登录被拒pg_hba.conf中replication条目仍是md5或trust单独修改replication条目为scram并测试这里面还有一个很常见的乌龙你同时有host和hostssl两行规则或者local规则优先级更高导致你以为改的是这一条实际匹配的是另一条。PG的规则匹配是自上而下第一个匹配条目生效所以排查时一定要用\conninfo或查询pg_hba_file_rules视图确认真实生效规则SELECT * FROM pg_hba_file_rules;这个视图能看到每条规则的error字段如果有语法错误加载时reload可能不会生效需要手动修正。4.2 遇到认证失败怎么定位认证失败时不要瞎猜。按下面四个步骤来看数据库日志。POSTGRES日志里会明确写password authentication failed for user xxx后面通常会带来源IP和可能的错误类型。如果连错误类型都没有八成是pg_hba.conf里没匹配到规则会显示no pg_hba.conf entry for host ...。检查用户哈希格式。用pg_authid查询确认该用户当前存储是不是SCRAM-SHA-256开头。如果还是md5说明密码没重置或重置时password_encryption还是旧的。检查客户端实际使用的认证机制。在psql里执行\set VERBOSITY verbose或者用PGPASSWORD环境变量加psql -d ...时加上-d后的连接串观察是否有提示。多数驱动在握手失败时不会告诉你“服务端要求scram”只会笼统报password authentication failed。这时可以临时把pg_hba.conf改回md5来验证是不是哈希格式问题测完再切回来。检查连接池和中间件。如果前面有PgBouncer连接池本身可能用了auth_query方式从数据库读取密码哈希再代理认证。这时需要确保连接池能正确读取scram格式。PgBouncer 1.9之后才完整支持scram旧版本需要升级。如果用的是auth_type md5它只会把密码哈希传给后端scram协议下会失效要改成scram-sha-256。4.3 回滚方案与副本集群注意事项任何生产变更都必须有回滚方案。MD5切scram的回滚分为两种只改了pg_hba.conf还没重置密码直接改回md5并reload基本无损已经批量重置密码回滚时不仅要改回pg_hba还需要把密码恢复成旧值。如果旧密码你不知道那就没法“无感回滚”只能接受新密码继续使用或者强制联系业务重新确认旧密码。所以我的建议是在批量重置密码之前先把当前pg_authid的快照备份出来方便审计和恢复原始状态。备份方式pg_dump --schema-only --no-owner --no-privileges -t pg_authid authid_backup.sql不过pg_authid是系统表普通pg_dump不一定允许直接导出推荐用如下SQL在psql里转储\copy (SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolconnlimit, rolpassword, rolvaliduntil, rolbypassrls, rolconfig, oid FROM pg_authid) TO pg_authid_backup.csv WITH CSV HEADER;恢复时不要直接改系统表而是用CREATE ROLE或ALTER ROLE逐一重建或者用这个CSV作为审计留痕即可。正式回滚密码很难做到“无感”只能通过ALTER ROLE ... PASSWORD恢复为已知旧密码。好在多数业务方密码是可知的这种情况不常见。再说复本集群。如果你有流复制的主从架构操作顺序要格外小心。pg_authid是系统表ALTER ROLE产生的变更会通过WAL传到备库所以主库重置密码后备库的pg_authid会自动同步。但pg_hba.conf和postgresql.conf不是通过WAL同步的你得在主库和每个备库上分别修改配置文件并reload。如果只改主库不改备库发生主备切换后新主库可能还是md5规则导致认证行为不一致。更稳妥的做法是在所有节点先同步password_encryption和pg_hba.conf配置然后在主库统一重置用户密码最后逐个节点验证连接。如果使用了外部连接池或服务发现如Consul、Kubernetes Service切换完成后记得滚动重启连接池或更新配置否则旧连接的缓存哈希仍可能与新认证机制不匹配。我遇到过一起事故就是应用驱动已更新连接池也升级了但K8s里Pod还是用旧的ConfigMap导致新Pod连接失败。排查了半天最后把Secret同步一下就好了。最后再分享一个实操中很实用的小技巧用psql的\password命令重置当前用户密码时它会按照password_encryption设置自动生成哈希。但如果你在连接串里使用的是PGOPTIONS-c password_encryptionmd5那也会覆盖全局设置导致“明明改全局参了新密码却还是md5”。这属于隐蔽雷区排查时可以执行SHOW password_encryption;如果会话级设置覆盖了全局这里会显示md5。检查当前会话参数后重新连接再重置即可。我个人在实际操作中的体会是MD5到scram-sha-256的切换并不需要“停库大手术”它的核心工作量其实集中在用户密码重置和客户端驱动兼容性验证上。真正容易翻车的不是SQL而是那些散落在连接池、配置文件、环境变量和旧驱动里的“隐性依赖”。先把上面这些点一个个过一遍再动手改生产基本可以做到业务无感或者极小影响。如果你的实例里还有大量老用户和复杂连接链路不妨先用这个清单做一次自检再决定什么时候动手。好了就分享到这里。