Saturday, May 2, 2009

Oracle เก็บพาสเวิร์ดอย่างไร

Updated: 1/4/2007

ในการเก็บพาสเวิร์ดของ user ปกติ (ที่ไม่ใช่ user ที่ได้สิทธิ์ SYSDBA) Oracle ไม่ได้ใช้วิธีการเข้ารหัส-ถอดรหัส (Encrypted-Decrypted) ในการเก็บพาสเวิร์ดให้เป็นความลับเพราะการใช้วิธีนั้น ทำให้เกิดคำถามขึ้นมาว่า ถ้าอัลกอริทึมนั้น (อาจจะบวกกับคีย์ในการเข้ารหัส) คนที่รู้นำไปใช้ในทางที่ผิดเช่น ไปแฮ็คเอาพาสเวิร์ดของบุคคล (ที่ใช้ Oracle) ที่มีหน้าที่เกี่ยวกับการเงินในองค์กร หรือแฮ็คเอาพาสเวิร์ดของ database admin ของธนาคารเป็นต้น

จริงๆ แล้ว Oracle ไม่ได้เข้ารหัสพาสเวิร์ด แต่ใช้อัลกอริทึมที่เรียกว่า Hash แทนคือ ปกติถ้าคุณเข้ารหัส คุณจะได้ชุดของตัวอักษรที่ดูไม่รู้เรื่องออกมา พอจะใช้งานคุณก็ถอดรหัสออกดังนี้

การเข้ารหัส: 'MyPassword' ---- เข้ารหัส-----> 'AE045B73'
การถอดรหัส: 'AE045B73' -----ถอดรหัส -----> 'MyPassword'

แต่ลักษณะของ Hash อัลกอริทึมจะเป็นการเอาข้อมูลที่เป็น username และ password มาผ่าน Hash อัลกอริทึมที่จะเอาข้อมูลทั้งสองมา 'ยำ' (ขออนุญาตใช้คำนี้นะครับ) กันเพื่อแปลงให้ออกมาเป็นค่าๆ หนึ่ง (โดยปกติจะเป็นเลขฐานสิบหกจำนวนหลายๆหลักเช่น 32 หลักเป็นต้น) เวลาจะตรวจเช็คจะไม่มีการดึงข้อมูลที่เป็นค่าที่ 'ยำ' แล้วออกมา แต่จะเป็นการเอาค่าทั้งสองมา 'ยำ' กันอีกด้วยอัลกอริทึมเดิม แล้วไปเช็คกับค่าที่ยำกันไว้ก่อนหน้าว่าตรงกันหรือเปล่า

การเก็บรหัส: 'UserName' + 'Password' ---- hashed ----> 'A304Fe89604353fb'
การตรวจสอบรหัส: 'UserName' + 'Password' ---- hashed ----> 'A304Fe89604353fb'

ด้วยวิธีการนี้ค่าที่ใส่เข้าไปทั้งสองค่าจะเป็นคีย์ของมันเอง ไม่ต้องใช้คีย์อื่นใดอีก Hash อัลกอริทึมที่ใช้ก็เป็นอัลกอริทึมมาตรฐานที่ใช้กันทั่วๆไป เช่น MD5 หรือจะคิดขึ้นมาใหม่ก็ได้ ส่วนวิธีการที่ Oracle ใช้ในการเข้ารหัสพาสเวิร์ดของตัวเองเป็นดังนี้ครับ

function digest( p_username in varchar2, p_password in varchar2 )
return varchar2
is
begin
return ltrim( to_char( dbms_utility.get_hash_value( upper(p_username)||'/'||upper(p_password),1000000000, power(2,30) ),rpad( 'X',29,'X')||'X' ) );
end digest;

ฟังก์ชัน digest นี้จะนำรับเอาค่าพารามิเตอร์ username และพาสเวิร์ด เอามา Hash ให้เป็นค่าใดๆจากใน 1073741824 (หนึ่งพันเจ็ดสิบสามล้านเจ็ดแสนสี่หมื่นหนึ่งพันแปดร้อยยี่สิบสี่) ค่า บวกกับอีก 1000000000 (หนึ่งพันล้าน) เพื่อที่จะให้ค่าใหญ่ขึ้น แล้วแปลงให้เป็นเลขฐานสิบหก แล้วจึงนำไปเก็บในฟิลด์พาสเวร์ด คราวนี้ถ้า user ต้องการล็อกอินก็ใส่ username และพาสเวิร์ดเข้ามาแล้วผ่านฟังก์ชันเดียวกันนี้ ถ้าผลที่ได้ตรงกับค่าที่เก็บในฟิลด์พาสเวิร์ดก็ผ่าน ถ้าไม่ก็ไม่ผ่าน

- Hash อัลกอริทึมเป็นการแม็บกลุ่มของตัวอักษรใดๆ (String) ที่ไร้ค่าจำกัดและขอบเขตใดๆ (username+พาสเวิร์ด) กับค่าที่มีจำนวนจำกัด (เช่น 1073741824) ดังนั้นอาจจะเป็นไปได้ว่ามีค่าสองค่าใดๆซึ่งเป็น username+พาสเวิร์ด ที่หลังจากการ hash แล้วให้ค่าเดียวกัน แต่ก็มีความเป็นไปได้ยากโดยเฉพาะถ้าเรากำหนดให้ขนาดของเซ็ทของค่าที่เป็นผลของ hash มีขนาดใหญ่ขึ้น อย่างในกรณีของฟังก์ชัน Hash ข้างบนค่าที่จะเป็นไปได้มีถึง 1,073,741,824 ทีเดียว (ผมลองใช้ Hash อัลกอริทึม ที่เป็น MD5 จะได้ตัวเลขฐานสิบหกถึง 32 ตัว ซึ่งเซ็ทของค่าที่เป็นไปได้ก็คือ 16 ยกกำลัง 32 ซึ่งเป็นขนาดของเซ็ทที่มีขนาดใหญ่มาก)
- Hash ไม่มีอัลกอริทึมในการย้อนกลับ (ซึ่งก็ไม่จำเป็นที่จะต้องทำ) เหมือนกับการเข้ารหัส-ถอดรหัส สิ่งที่ต้องการคือเอาแค่ username และพาสเวิร์ดใส่เข้าไปเท่านั้น ดังนั้นจึงค่อนข้างปลอดภัย (เพราะมันไม่มีอัลกอริทึมในการย้อนกลับนั่นเอง)Hash อัลกอริทึมของ Oracle เองใน DBMS_UTILITY.GET_HASH_VALUE อาจจะมีการเปลี่ยนแปลงไปได้ในแต่ละเวอร์ชั่น ดังนั้นคุณอาจจะเลือกที่จะใช้อัลกอริทึมที่เป็นมาตรฐานอย่าง MD5 (ใน Oracle10g คือ dbms_crypto.HASH_MD5 ส่วน Oracle9i อาจจะต้องใช้ dbms_obfuscation_toolkit.MD5แทน) เป็นต้น

5 comments:

Anonymous said...

สรุปผมต้องเรียกยังไงครับ เพื่อให้เก็บ encrypted password ลงใน database ได้ครับ ลอง search google ก็หว ก็หลายทีแล้ว พยายามแล้วก็ไม่ได้ซักที่ ครับ Example ดูหน่อยซิครับ

Tanakorn Tavornsasnavong said...

ต้องขออภัยเป็นอย่างสูงครับฟังก์ชั่น Digest มีจุดที่ผิดคือตรง
upper(p_username)'/'upper(p_password) ต้องแก้เป็น upper(p_username)||'/'||upper(p_password) และตรง
rpad( 'X',29,'X')'X' ต้องแก้เป็น rpad( 'X',29,'X')||'X' ซึ่งผมได้แก้ไขในบทความแล้วครับ

เริ่มจากสร้างตาราง my_users ซึ่งเป็นตารางที่เราใช้เก็บ Username และ Password

SQL> create table my_users (username varchar2(30), password varchar2(30));

Table created.

จากนั้นก็สร้างฟังก์ชั่น digest เพื่อเอาไว้ "ยำ" Username และ Password ให้เป็นค่าประหลาด ๆ ค่าหนึ่ง

SQL> create or replace function digest( p_username in varchar2, p_password in varchar2 )
2 return varchar2
3 is
4 begin
5 return ltrim( to_char( dbms_utility.get_hash_value( upper(p_username)||'/'||upper(p_password),1000000000, power

(2,30) ),rpad( 'X',29,'X')||'X' ) );
6 end digest;
7
8 /

Function created.

คราวนี้ตอน Create User และ Password เราก็ใช้ Username และ Password ของเราเป็นพารามิเตอร์ในฟังก์ชั่น digest และเก็บค่านั้น ๆ ในฟิลด์ Password

SQL> insert into my_users values
2 ('tanakorn',digest('tanakorn','secret007'));

1 row created.

SQL> select * from my_users;

USERNAME PASSWORD
------------------------------ ------------------------------
tanakorn 49494C2E

จะเห็นได้ว่า Password เป็นค่าประหลาด ๆ แล้ว ค่าตัวที่ว่านี้เกิดจากการใช้ dbms_utility.get_hash_value ในการ "ยำ" เอาค่าที่ได้จากพารามิเตอร์ โดยมีเลขฐานเริ่มต้นเป็น 1000000000 และมีขนาดของ Hash เป็น 2 ยกกำลัง 30 ครับ ผลที่ได้คือค่า ๆ หนึ่งในค่าที่เป็นไปได้จาก
จำนวน 1073741824 ค่า บวกด้วย 1000000000 (เพื่อให้มันใหญ่) และแปลงให้มันเป็นเลยฐานสิบหก แล้วเก็บค่าที่ได้ในฐานข้อมูล

คราวนี้ถ้า User แสดง username/password เราก็จะ digest มันแล้วเปรียบเทียบกับค่าเดิมที่เก็บในฐานข้อมูล ถ้าตรงกันก็ผ่าน ถ้าไม่ก็ไม่ผ่าน

จากตัวอย่างผมใช้ decode ในการตรวจสอบดูว่าผลที่ได้จากการ digest ตรงกับค่าที่เก็บในคอลัมน์ password ของตาราง my_users หรือเปล่าครับ

SQL> select decode(digest('tanakorn','secret007'),password,'Success','Fail') result from my_users where username = 'tanakorn';

RESULT
-------
Success

SQL> select decode(digest('tanakorn','secret008'),password,'Success','Fail') result from my_users where username = 'tanakorn';

RESULT
-------
Fail
อ่านรายละเอียดเพิ่มเติมจาก
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:95412348059#tom344930500346185463

Unknown said...

ขอบคุณครับ

Anonymous said...

ท่านครับ ผมมือใหม่ อยากจะถามว่า เช็คพาสเวิร์ดใน Oracle11g อย่างไรครับ เหมือนกับว่าผมลงแล้ว ใส่พาสเวิร์ดไปหรือไม่ได้ใส่ ผมก็ลืมครับ ขอบคุณครับ

Tanakorn Tavornsasnavong said...

ขออภัยที่ตอบช้านะครับ ลองดูวิธีข้างล่างซึ่งจะช่วยให้คุณสามารถเลือกชื่อ User จากลิสต์ที่มีในฐานข้อมูล และให้คุณสามารถเปลี่ยนพาสเวิร์ดได้กรณีที่จำไม่ได้ครับ

1. บนเครื่องที่คุณลง Oracle Database ไว้ ให้ไปที่ Start Menu=> Run พิมพ์ cmd ในช่อง Open: เพื่อเปิดหน้าจอ DOS
2. บน Dos Prompt พิมพ์ sqlplus "/ as sysdba" ซึ่งจะเป็นการใช้ SQL*Plus เพื่อล็อกอินเข้าระบบฐานข้อมูล โดยใช้ User ที่มีสิทธิ์สูงสุดเช่น
C:\Documents and Settings\tanakorn>sqlplus "/ as sysdba"

SQL*Plus: Release 10.2.0.4.0 - Production on Tue Apr 26 22:52:12 2011

Copyright (c) 1982, 2007, Oracle. All Rights Reserved.


Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL>

3. ดูว่าในฐานข้อมูลมี user ใดบ้าง โดยใช้คำสั่ง select username from dba_users เช่น

SQL> select username from dba_users;

USERNAME
------------------------------
SYSMAN
DMSYS
WMSYS
PERFSTAT
ANONYMOUS
XDB
...
...

4. แก้พาสเวิร์ดของ user ที่ต้องการ โดยใช้คำสั่ง alter user ... identified by .... เช่น

SQL> alter user tanakorn identified by tanakorn;

User altered.

SQL> exit
Disconnected from Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

C:\Documents and Settings\tanakorn>sqlplus tanakorn/tanakorn

SQL*Plus: Release 10.2.0.4.0 - Production on Tue Apr 26 22:56:11 2011

Copyright (c) 1982, 2007, Oracle. All Rights Reserved.


Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> show user
USER is "TANAKORN"

Post a Comment