OTP มันสร้างมายังไงวะ

Security 7 ส.ค. 2022

ผมเชื่อว่าหลายคนคงเคยใช้ OTP หรือ One-Time-Password ที่ถูกสร้างจากแอป Authy หรือ Google Authenticator ในการเข้าสู่ระบบในเว็บไซต์ต่างๆ สิ่งหนึ่งที่ผมเคยคิดเวลาใช้งานก็คือ เลข 6 หลักนี้ มันถูกสร้างมาได้ยังไงนะ จะบอกว่ามันถูกสร้างขึ้นมาแบบมั่วๆ ก็ไม่ใช่ เพราะ server ที่เรากรอก OTP ลงไป จะสามารถทำการเช็คได้ว่า OTP นั้นถูกต้องหรือไม่ และผมก็ยิ่งสงสัยมากขึ้นไปอีกเพราะว่าเลข OTP สามารถถูกสร้างขึ้นมาได้ ถึงแม้ว่าเราจะไม่มีการเชื่อมต่อ Internet ก็ตาม เห้ย มันจะสุดยอดเกินไปแล้ว

เมื่อผมลองได้หาข้อมูลดูก็ได้พบว่าหลักการทำงานของมันเป็นการใช้ algorithm ล้วนๆ เลย ซึ่งจะมีอยู่ 2 ตัวก็คือ HMAC-Based One-Time Password Algorithm (HOTP) และ Time-Based One-Time Password Algorithm (TOTP)

HOTP

ชื่อเต็มของมันก็คือ HMAC-Based One-Time Password Algorithm ซึ่งถ้าเราดูจากชื่อ เราก็จะรู้แล้วว่า มันจะต้องมีการทำ MAC หรือ Message Authentication Code ผ่านการทำ hashing นั้นเอง

Algorithm ของ HOTP จะต้องมีข้อมูล input 2 ตัว ที่ทั้ง server และ client จะต้องมีเหมือนกันก็คือ

  • Counter เป็นตัวเลขที่เริ่มจาก 0
  • Secret

ก่อนที่ client จะทำการสร้าง OTP ได้นั้น จะต้องมีการตกลง secret ที่จะใช้กับ server ก่อน ถ้าเราลองดูตัวอย่างของ Google Authenticator เราจะต้องทำการ scan QR Code เพื่อตั้งค่าก่อนที่เราจะสร้าง OTP ได้ ซึ่ง secret จะอยู่ในข้อมูลของ QR Code นี่แหละ และเมื่อ client ได้ข้อมูล secret จาก server ผ่าน QR Code แล้ว client ก็จะสามารถสร้าง OTP ได้จาก secret และ counter ที่เริ่มจาก 0

otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example
ตัวอย่างข้อมูลที่อยู่ใน QR Code Ref: https://stackoverflow.com/a/56737468
Ref:www.protectimus.com

เมื่อ client มี input เรียบร้อยแล้ว เราจะสามารถสร้าง OTP ได้จากการ Hash ทั้งสองค่านั้นด้วย HMAC-SHA-1 algorithm

1. Hash ค่า secret และ counter ด้วย HMAC-SHA-1 algorithm ซึ่งจะได้ผลลัพธ์ออกมา 140 bits

HS = HMAC-SHA-1( Secret, Counter )

2. เอาผลลัพธ์จากการ hash มาตัดให้เลือก 31 bits ด้วยการใช้ Dynamic Truncation

Sbits = DynamicTruncation( HS )

3. แปลงจาก 31bits ที่ถูกตัดมาให้เป็นตัวเลข จะได้ค่าอยู่ในช่วง 0 ถึง \( 2^{31}-1\) และทำการ mod ด้วย \( 10^{digit}\) โดย digit เป็นจำนวนตัวเลขที่ต้องการใน output ซึ่งในที่นี้คือ 6 ก็จะถูก mod ด้วย \( 10^{6}\) เราก็จะได้เลขออกมาในช่วง 0 ถึง 99999 ซึ่งก็คือ OTP 6 หลักที่เราต้องการนั้นเอง

Snum  = StringToNum( Sbits )
D = Snum mod \(10^{Digit}\)

4. ปรับค่า counter ให้เพิ่มไปอีก 1

counter = counter + 1

OTP Verification at server

เมื่อเราได้ OTP ที่ถูกสร้างมาจาก client แล้ว คำถามคือ ฝั่ง server จะรู้ได้อย่างไรว่า OTP นั้นถูกต้อง วิธีการก็ง่ายมากๆ ฝั่ง server ที่มีข้อมูล secret และ counter อยู่แล้วก็แค่ทำ algorithm เดียวกับ client ในการสร้าง OTP ออกมา โดยถ้า OTP ที่ server สร้างขึ้นมานั้น ตรงกับ OTP ที่ผู้ใช้งานกรอกเข้ามา ก็แสดงว่า OTP นั้นถูกต้อง และเมื่อ OTP ถูกตรวจสอบสำเร็จ server ก็จะเพิ่ม counter ของตัวเองไปอีก 1 ด้วย แต่ถ้าไม่สำเร็จ ก็จะไม่เพิ่ม counter

แล้ว Counter เอาไว้ทำอะไร

อ่านมาถึงตรงนี้หลายคนคงสงสัยว่าแล้วค่า counter มันเอาไว้ทำอะไร เรารู้ว่ามันถูกนำไป hash กับ secret และมันจะบวก 1 ทุกครั้งที่มีการสร้าง OTP แต่จุดประสงค์ของมันคืออะไรกันแน่นะ

Counter เป็นตัวแปรที่ทำให้ OTP นั้นเป็น OTP กล่าวคือมันเป็นตัวที่คอยทำให้ OTP ที่ถูกสร้างออกมาเปลี่ยนไปเรื่อยๆ เนื่องจาก Secret นั้นเป็นค่าคงที่ ถ้าเรา hash มันอย่างเดียว ผลลัพธ์ที่ออกมาก็จะเหมือนกันในทุกๆ รอบ มันก็จะไร้ซึ่งความเป็น One-Time-Password ไป

นอกจากนี้ ถ้าค่าของ counter ในฝั่ง server และ client ไม่ตรงกับ เมื่อผ่าน HOTP algorithm ค่าที่ได้ออกมาก็จะไม่ตรงกัน ทำให้การ verify OTP นั้นไม่สำเร็จ มันยังทำให้เราไม่สามารถใช้ OTP เดิมที่ถูกสร้างขึ้นมาก่อนหน้าได้ เนื่องจาก counter ที่ฝั่ง server จะไม่ตรงกับ counter ที่เคยใช้สร้าง OTP ตัวเก่านั้น

ในการใช้งานจริง อาจจะมีกรณีที่ counter ของทั้งสองฝั่งไม่ตรงกัน เช่นผู้ใช้อาจจะกดสร้าง OTP โดยไม่ได้กรอกให้ server ทำการ verify ทำให้ counter ของ client นั้นนำหน้าของ server ไป โดยในอนาคตถ้ามีการส่ง OTP มา verify ที่ server แล้วไม่ผ่าน server จะทำการ Counter Resynchronization เพื่อแก้ปัญหา counter ที่ไม่ตรงกัน

Counter Resynchronization

เนื่องจากส่วนใหญ่แล้ว counter ของฝั่ง server จะตามหลังอยู่เสมอ ในการ resync ค่า counter  จะมีตัวแปรอีกตัวก็คือ look-ahead parameter ที่เป็นตัวกำหนดว่า เราจะลองเพิ่ม counter ไปอีกเท่าไหร จนกว่าจะตอบกลับผู้ใช้ไปว่า การยืนยัน OTP นั้นล้มเหลว

ยกตัวอย่างเช่นถ้า counter ของ client อยู่ที่ 5 แต่ว่าของ server เป็น 2 โดยเรามี look-ahead parameter เป็น 5 เมื่อ server ได้รับ OTP (จาก counter = 5) และทำการเช็คแล้วว่าไม่ตรงกับ OTP ที่ตัวเองสร้างขึ้นมา (จาก counter = 2) ฝั่ง server จะลองเพิ่ม count ไปอีก 1 และสร้าง OTP ขึ้นมาใหม่จาก counter นั้น และทำการเทียบกับ OTP ที่ได้รับมาจากผู้ใช้อีกรอบ ถ้ายังไม่ตรงอีก ก็จะทำไปเรื่อยๆ ทั้งหมด 5 รอบ ถ้าหลังจากเพิ่ม counter ไป 5 รอบแล้วยังไม่ถูก ก็จะส่งกลับไปว่าการยืนยัน OTP ล้มเหลว แต่ถ้าเจอ OTP ที่ตรงกัน ฝั่ง server ก็จะขยับ counter ของตัวเองมายังเลขนั้นและทำการ + 1 เมื่อการยืนยันสำเร็จ

ไม่เห็นเหมือนกับ Google Authenticator เลย

ใช่ครับ HOTP ที่ผมอธิบายไปนั้น ไม่ได้เป็นสิ่งที่เราใช้กันในปัจจุบันสักเท่าไหร HOTP จะเหมาะกับ OTP devices ที่มีปุ่มกดเพื่อสร้าง OTP ขึ้นมา เนื่องจากว่าต้องทำการเก็บค่า counter แต่ในปัจจุบัน เราจะใช้ algorithm ที่เรียกว่า TOTP หรือ Time-Based One-Time Password Algorithm แทน

TOTP

TOTP เป็น algorithm ที่ต่อยอดมาจาก HOTP เพื่อทำให้ client สามารถสร้าง OTP แบบทิ้งขว้างได้ เนื่องจากมันไม่ได้ใช้ counter แล้ว แต่จะใช้เวลาเข้ามาแทน

การทำงานของ TOTP จะมี input ตัวเข้ามาแทน counter ก็คือ ตัวแปร T

  • X คือจำนวนวินาทีที่ OTP จะสามารถใช้ได้ก่อนจะหมดอายุ
  • T คิดได้จาก \(T = floor(currentUnixTime / X)\)
สูตรจริงๆของ T จะบอกว่า currentUnixTime คือ  \(currentUnixTime - T_{0}\) แต่เนื่องจาก \(T_{0}\) ส่วนใหญ่มีค่าเป็น 0 ผมเลยขอใช้เป็นแค่ currentUnixTime

หลังจากนี้เราก็แค่เอาค่า T กับ Secret มาเข้า algorithm เดียวกับ HOTP เราก็จะสามารถสร้าง OTP ด้วย TOTP algorithm ออกมาได้แล้ว

Verification of TOTP

Ref: Twilio

การตรวจสอบ TOTP ก็ทำได้ง่ายๆ เลยก็คือฝั่ง server ทำ algorithm เดียวกับฝั่ง client ในการสร้าง OTP ออกมา เพื่อเช็คว่าตรงกันหรือไม่

การที่เรานำเอา Unix time ณ เวลานั้นมาหารด้วย X ใน TOTP algorithm จะให้ทำค่าด้านหน้าจุดทศนิยมนั้น เปลี่ยนไปทุกๆ X วินาที

ยกตัวอย่างเช่นถ้าเรากำหนดให้ X เป็น 3 และเวลาในตอนนี้คือ June 29, 2007 09:41:01 จะมี currentUnixTime เป็น 1183110061 จะได้ตารางของค่า currentUnixTime ออกมาดังนี้

currentUnixTime currentUnixTime/3 floor(currentUnixTime/3)
1183110061 394370020.333 394370020
1183110062 394370020.667 394370020
1183110063 394370021 394370021
1183110064 394370021.333 394370021
1183110065 394370021.667 394370021
1183110066 394370022 394370022
1183110067 394370022.333 394370022
1183110068 394370022.667 394370022

จากตารางจะเห็นได้ว่าค่าที่ผ่านการ Floor แล้วจะมีค่าไม่ซ้ำกันเกิน X ครั้งเลย นั้นหมายความว่าเมื่อเวลาผ่านไป X วินาที เราจะไม่สามารถสร้าง OTP ค่านั้นออกมาได้อีกแล้ว เราจึงสามารถมั่นใจได้ว่า OTP ที่ถูกสร้างขึ้นมาน้ัน จะหมดอายุหรือ verify ไม่ผ่านเนื่องจากเวลาของฝั่ง server นั้นเดินไปเรื่อยๆ

ในการนำไปใช้งานจริงก็มีการแนะนำว่าให้ฝั่ง server ทำการเผื่อเวลาไว้สำหรับ network delay ด้วย เผื่อในกรณีที่มีความหน่วงเวลาเกิดขึ้น

That's the way

นี่แหละครับ คือ Algorithm ในการสร้าง OTP ที่เราใช้กันอยู่ทุกวันนี้ เนื่องจากการสร้างและตรวจสอบ OTP นั้นไม่ต้องใช้อะไรนอกจาก secret และนาฬิกาหรือ counter เราจึงไม่จำเป็นต้องใช้ Internet ในกระบวนการนี้เลย

ถ้าใครสนใจสามารถอ่านเรื่องนี้ได้ที่

แท็ก

Sethanant Pipatpakorn

Innovation Engineer @ KLabs, KBTG CS20 SKN36