赞
踩
PostgreSQL里用户创建密码后,是把密码按照SCRAM-SHA-256或MD5等算法处理后的值写入数据库,等到用户登陆的时候,再把输入的密码进行相应的算法处理,把两个值进行比较来判断是否密码输入正确,具体如下:
客户端收到输入的密码后,会使用密码和用户做拼接,把用户作为salt,然后对拼接好的字符串做md5。即md5(密码+用户(salt))。然后把这个值加上md5前缀与存储在pg_authid里的rolpassword做比较,如果一致,则通过认证,如果不一致,则报密码错误。
MD5算法属于单向散列算法,无法通过反推获得原始输入数据,但是MD5不算严格意义上的加密算法,可用暴力穷举法破解。SCRAM-SHA-256生成的密文长度为256位,MD5生成的密文长度为128位。SCRAM-SHA-256算法的碰撞概率比MD5更小,因为SCRAM-SHA-256使用了更复杂的哈希算法和更长的输出长度。
//session1
postgres=# show password_encryption ;
password_encryption
---------------------
md5
(1 row)
一个session用gdb来Attach这个进程,然后在encrypt_password函数这里打上断点。
//session2
(gdb) b encrypt_password
Breakpoint 1 at 0xaaaabc4f7a40: file crypt.c, line 118.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000aaaabc4f7a40 in encrypt_password at crypt.c:118
然后原本的这个数据库连接里执行创建用户的操作
//session1
执行后处于一直卡着的状态
postgres=# create user ysla with password '1qaz!QAZ';
然后去gdb端查看堆栈
(gdb) c Continuing. Breakpoint 1, encrypt_password (target_type=PASSWORD_TYPE_MD5, role=0xaaaacccef658 "ysla", password=password@entry=0xaaaacccef670 "1qaz!QAZ") at crypt.c:118 118 { (gdb) bt #0 encrypt_password (target_type=PASSWORD_TYPE_MD5, role=0xaaaacccef658 "ysla", password=password@entry=0xaaaacccef670 "1qaz!QAZ") at crypt.c:118 #1 0x0000aaaabc48d160 in CreateRole (pstate=pstate@entry=0xaaaaccdd3038, stmt=stmt@entry=0xaaaacccef750) at user.c:446 #2 0x0000aaaabc68c420 in standard_ProcessUtility (pstmt=0xaaaacccef800, queryString=0xaaaaccceec28 "create user ysla with password '1qaz!QAZ';", readOnlyTree=<optimized out>, context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0xaaaacccf0030, qc=0xffffc7625318) at utility.c:911 #3 0x0000ffffab246270 in pgss_ProcessUtility (pstmt=0xaaaacccef800, queryString=0xaaaaccceec28 "create user ysla with password '1qaz!QAZ';", readOnlyTree=false, context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0xaaaacccf0030, qc=0xffffc7625318) at pg_stat_statements.c:1145 #4 0x0000aaaabc68a6cc in PortalRunUtility (portal=portal@entry=0xaaaaccd71f18, pstmt=pstmt@entry=0xaaaacccef800, isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0xaaaacccf0030, qc=qc@entry=0xffffc7625318) at pquery.c:1158 #5 0x0000aaaabc68a874 in PortalRunMulti (portal=portal@entry=0xaaaaccd71f18, isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0xaaaacccf0030, altdest=altdest@entry=0xaaaacccf0030, qc=qc@entry=0xffffc7625318) at pquery.c:1315 #6 0x0000aaaabc68ae00 in PortalRun (portal=portal@entry=0xaaaaccd71f18, count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true, run_once=run_once@entry=true, dest=dest@entry=0xaaaacccf0030, altdest=altdest@entry=0xaaaacccf0030, qc=qc@entry=0xffffc7625318) at pquery.c:791 #7 0x0000aaaabc686768 in exec_simple_query (query_string=query_string@entry=0xaaaaccceec28 "create user ysla with password '1qaz!QAZ';") at postgres.c:1274 #8 0x0000aaaabc687648 in PostgresMain (dbname=<optimized out>, username=<optimized out>) at postgres.c:4637 #9 0x0000aaaabc5e0514 in BackendRun (port=0xaaaaccd23cd0, port=0xaaaaccd23cd0) at postmaster.c:4464 #10 BackendStartup (port=0xaaaaccd23cd0) at postmaster.c:4192 #11 ServerLoop () at postmaster.c:1782 #12 0x0000aaaabc5e165c in PostmasterMain (argc=argc@entry=1, argv=argv@entry=0xaaaaccc56d80) at postmaster.c:1466 #13 0x0000aaaabc298464 in main (argc=1, argv=0xaaaaccc56d80) at main.c:198
可以再加一个断点
(gdb) b pg_md5_encrypt Breakpoint 2 at 0xaaaabc835a90: file md5_common.c, line 147. (gdb) c Continuing. Breakpoint 2, pg_md5_encrypt (passwd=passwd@entry=0xaaaacccef670 "1qaz!QAZ", salt=salt@entry=0xaaaacccef658 "ysla", salt_len=4, buf=buf@entry=0xaaaaccdd32a8 "", errstr=errstr@entry=0xffffc7624ad0) at md5_common.c:147 147 size_t passwd_len = strlen(passwd); (gdb) bt #0 pg_md5_encrypt (passwd=passwd@entry=0xaaaacccef670 "1qaz!QAZ", salt=salt@entry=0xaaaacccef658 "ysla", salt_len=4, buf=buf@entry=0xaaaaccdd32a8 "", errstr=errstr@entry=0xffffc7624ad0) at md5_common.c:147 #1 0x0000aaaabc4f7abc in encrypt_password (target_type=<optimized out>, role=0xaaaacccef658 "ysla", password=password@entry=0xaaaacccef670 "1qaz!QAZ") at crypt.c:137 #2 0x0000aaaabc48d160 in CreateRole (pstate=pstate@entry=0xaaaaccdd3038, stmt=stmt@entry=0xaaaacccef750) at user.c:446 #3 0x0000aaaabc68c420 in standard_ProcessUtility (pstmt=0xaaaacccef800, queryString=0xaaaaccceec28 "create user ysla with password '1qaz!QAZ';", readOnlyTree=<optimized out>, context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0xaaaacccf0030, qc=0xffffc7625318) at utility.c:911 #4 0x0000ffffab246270 in pgss_ProcessUtility (pstmt=0xaaaacccef800, queryString=0xaaaaccceec28 "create user ysla with password '1qaz!QAZ';", readOnlyTree=false, context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0xaaaacccf0030, qc=0xffffc7625318) at pg_stat_statements.c:1145 #5 0x0000aaaabc68a6cc in PortalRunUtility (portal=portal@entry=0xaaaaccd71f18, pstmt=pstmt@entry=0xaaaacccef800, isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0xaaaacccf0030, qc=qc@entry=0xffffc7625318) at pquery.c:1158 #6 0x0000aaaabc68a874 in PortalRunMulti (portal=portal@entry=0xaaaaccd71f18, isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0xaaaacccf0030, altdest=altdest@entry=0xaaaacccf0030, qc=qc@entry=0xffffc7625318) at pquery.c:1315 #7 0x0000aaaabc68ae00 in PortalRun (portal=portal@entry=0xaaaaccd71f18, count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true, run_once=run_once@entry=true, dest=dest@entry=0xaaaacccf0030, altdest=altdest@entry=0xaaaacccf0030, qc=qc@entry=0xffffc7625318) at pquery.c:791 #8 0x0000aaaabc686768 in exec_simple_query (query_string=query_string@entry=0xaaaaccceec28 "create user ysla with password '1qaz!QAZ';") at postgres.c:1274 #9 0x0000aaaabc687648 in PostgresMain (dbname=<optimized out>, username=<optimized out>) at postgres.c:4637 #10 0x0000aaaabc5e0514 in BackendRun (port=0xaaaaccd23cd0, port=0xaaaaccd23cd0) at postmaster.c:4464 #11 BackendStartup (port=0xaaaaccd23cd0) at postmaster.c:4192 #12 ServerLoop () at postmaster.c:1782 #13 0x0000aaaabc5e165c in PostmasterMain (argc=argc@entry=1, argv=argv@entry=0xaaaaccc56d80) at postmaster.c:1466 #14 0x0000aaaabc298464 in main (argc=1, argv=0xaaaaccc56d80) at main.c:198
可以往下走,可以看到这里把用户名当作salt和实际密码拼接在一起,然后根据这个做MD5的加密。
(gdb) n 150 char *crypt_buf = malloc(passwd_len + salt_len + 1); (gdb) n 153 if (!crypt_buf) (gdb) n 163 memcpy(crypt_buf, passwd, passwd_len); (gdb) n 164 memcpy(crypt_buf + passwd_len, salt, salt_len); (gdb) p crypt_buf $2 = 0xaaaaccdf21b0 "1qaz!QAZ" (gdb) p passwd_len $3 = 8 (gdb) p salt $4 = 0xaaaacccef658 "ysla" (gdb) p salt_len $5 = 4 (gdb) n 166 strcpy(buf, "md5"); (gdb) p crypt_buf $6 = 0xaaaaccdf21b0 "1qaz!QAZysla" (gdb) n 167 ret = (crypt_buf, passwd_len + salt_len, buf + 3, errstr); (gdb) p crypt_buf $7 = 0xaaaaccdf21b0 "1qaz!QAZysla" (gdb) s pg_md5_hash pg_md5_encrypt (passwd=passwd@entry=0xaaaad20f35e0 "1qaz!QAZ", salt=salt@entry=0xaaaad20f35c8 "ysla", salt_len=4, buf=buf@entry=0xaaaad21f9448 "md5f2689c3cb387c12f0183882c6c080a05", errstr=errstr@entry=0xffffd33a3b80) at md5_common.c:169 169 free(crypt_buf);
(gdb) s pg_md5_hash
pg_md5_encrypt (passwd=passwd@entry=0xaaaad20f35e0 "1qaz!QAZ", salt=salt@entry=0xaaaad20f35c8 "ysla", salt_len=4,
buf=buf@entry=0xaaaad21f9448 "md5f2689c3cb387c12f0183882c6c080a05", errstr=errstr@entry=0xffffd33a3b80) at md5_common.c:169
169 free(crypt_buf);
...
(gdb) n
CreateRole (pstate=pstate@entry=0xaaaad21f91d8, stmt=stmt@entry=0xaaaad20f36c0) at user.c:448
448 new_record[Anum_pg_authid_rolpassword - 1] =
(gdb) s
cstring_to_text (s=0xaaaad21f9448 "md5f2689c3cb387c12f0183882c6c080a05") at varlena.c:184
从“buf”输出缓冲区里查看的md5值。然后把这个值和pg_authid系统表里的记录做对比。两个值是一致的。这个pg_authid里的rolpassword字段记录的md5值就是把密码和用户名加在一起,把用户名作为salt。然后把这个值前边加上md5开头并存储在pg_authid里的rolpassword字段。
Expanded display is on.
postgres=# select * from pg_authid where rolname='ysla';
-[ RECORD 1 ]--+------------------------------------
oid | 57735
rolname | ysla
rolsuper | f
rolinherit | t
rolcreaterole | f
rolcreatedb | f
rolcanlogin | t
rolreplication | f
rolbypassrls | f
rolconnlimit | -1
rolpassword | md5f2689c3cb387c12f0183882c6c080a05
rolvaliduntil |
如果使用md5函数处理下密码+用户的字符串,并加上md5前缀,可以发现和rolpassword字段的值是一样的。
postgres=# select 'md5'||md5('1qaz!QAZysla'); ?column? ------------------------------------- md5f2689c3cb387c12f0183882c6c080a05 (1 row) postgres=# select rolpassword from pg_authid where rolname='ysla'; rolpassword ------------------------------------- md5f2689c3cb387c12f0183882c6c080a05 (1 row) postgres=# select rolpassword = 'md5'||md5('1qaz!QAZysla') from pg_authid where rolname='ysla'; ?column? ---------- t (1 row)
通过上述测试可以发现,pg_authid系统表里的rolpassword字段的MD5码 ="md5"字符串+ md5(pwd+username)),MD5在理论上是几乎无法破解的,虽然不能反向解析,但是如果获取到了这个MD5处理后的字符串,可以通过撞库方式获取MD5算法处理前的字符,用预先计算好的MD5散列值与已知的散列值进行比较,以查找匹配的明文,从而获取到用户名和密码。但是这种方法需要预先有一定的可能的明文和计算MD5散列值的能力。MD5的认证不是很安全,所以,MD5现在已经被弃用了,发生碰撞的概率是1/(2^128)。
MD5的salt是用户名字符串,密码一致根据算法生成的字符也是一致的。而SCRAM-SHA-256有随机salt的加入,同样的密码, 修改后存储内容也会变化。所以SCRAM-SHA-256更加安全,几乎很难破解,因此比较建议使用SCRAM-SHA-256而不是MD5加密。
SCRAM-SHA-256 $ 4096 : fWK6w0/oDX42HJeYaPkIWA== $ P7K5YHTAjRW4JHm/6aY4vPA549WepjUCdQzudRm/QtE= : GG/TgXZAiSuJqLBa1e7E7q2CFJJU106I9w8iHVB78Kk= //存储格式: SCRAM-SHA-256 $ <iteration count> : <salt> $ <StoredKey> : <ServerKey> postgres=# select rolname,rolpassword from pg_authid where rolname='repl'; rolname | rolpassword ---------+--------------------------------------------------------------------------------------------------------------------------------------- repl | SCRAM-SHA-256$4096:fWK6w0/oDX42HJeYaPkIWA==$P7K5YHTAjRW4JHm/6aY4vPA549WepjUCdQzudRm/QtE=:GG/TgXZAiSuJqLBa1e7E7q2CFJJU106I9w8iHVB78Kk= (1 row) postgres=# SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked FROM pg_authid where rolname='repl'; rolname | rolpassword_masked ---------+--------------------------------------------------- repl | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey> (1 row)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。