আধুনিক ওয়েব নিরাপত্তার মৌলিক ক্রিপ্টোগ্রাফিক প্রোটোকলগুলোর মধ্যে RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) তার গাণিতিক সারল্যের জন্য বিশেষভাবে উল্লেখযোগ্য। এর জন্য কোনো ইন্টারেক্টিভ নেটওয়ার্ক হ্যান্ডশেক, কেন্দ্রীভূত পাবলিক কি ইনফ্রাস্ট্রাকচার (PKI) বা ক্লাউড রাউন্ড-ট্রিপের প্রয়োজন হয় না। শুধুমাত্র একটি শেয়ার্ড Base32 সিক্রেট কি এবং ইউনিক্স সময়ের স্বাভাবিক অগ্রগতি ব্যবহার করে ক্লায়েন্ট ও সার্ভার প্রতি ৩০ সেকেন্ড অন্তর ১০০% গাণিতিক নিশ্চিততার সাথে হুবহু একই ৬-সংখ্যার প্রমাণীকরণ টোকেন গণনা করতে পারে।
এই বিশদ গাইডে আমরা কোনো ব্ল্যাক-বক্স থার্ড-পার্টি লাইব্রেরি (যেমন pyotp বা otplib) ছাড়াই পাইথন, জাভাস্ক্রিপ্ট এবং গো-তে শুরু থেকে সম্পূর্ণ RFC 6238 ক্রিপ্টোগ্রাফিক পাইপলাইন তৈরি করব এবং অফিশিয়াল IETF টেস্ট ভেক্টর দিয়ে প্রতিটি বাস্তবায়ন যাচাই করব।
০১. সময়-চালিত প্রমাণীকরণের সৌন্দর্য
TOTP বোঝার জন্য প্রথমে আমাদের এর পূর্বসূরি RFC 4226 HOTP (HMAC-Based One-Time Password)-এর ইতিহাস জানতে হবে।
HOTP-তে ক্লায়েন্ট ও সার্ভার একটি ইভেন্ট কাউন্টার $C$ শেয়ার করে। প্রতিবার ব্যবহারকারী একটি টোকেন তৈরি করলে কাউন্টার এক বৃদ্ধি পায় ($C = C + 1$), এবং টোকেনটি $\text{HMAC-SHA-1}(K, C)$ থেকে উদ্ভূত হয়। গাণিতিকভাবে নিখুঁত হলেও HOTP-তে একটি অপারেশনাল দুর্বলতা ছিল: কাউন্টার ডিসিঙ্ক্রোনাইজেশন। ব্যবহারকারী যদি পকেটে থাকা অবস্থায় অসাবধানতাবশত হার্ডওয়্যার ডঙ্গলের বোতাম ৫ বার চাপতেন, তবে ক্লায়েন্ট কাউন্টার $C=6$-এ পৌঁছে যেত, অথচ প্রমাণীকরণ সার্ভার $C=1$-এ থেকে যেত; ফলে ক্রমাগত লগইন ব্যর্থ হতো।
২০১১ সালের মে মাসে N. M'Raihi, S. Machani, M. Pei এবং J. Rydell কর্তৃক প্রকাশিত RFC 6238 ক্রমবর্ধমান পূর্ণসংখ্যার পরিবর্তে মানসম্মত ইউনিক্স ইপক সময় ব্যবহারের মাধ্যমে কাউন্টার ডিসিঙ্কের সমস্যার সমাধান করে। যেহেতু বৈশ্বিক সময় সব জায়গায় অভিন্ন গতিতে চলে, তাই ক্লায়েন্ট ও সার্ভার কোনো প্রকার নেটওয়ার্ক প্যাকেট পাঠানো ছাড়াই একযোগে তাদের অভ্যন্তরীণ কাউন্টার বৃদ্ধি করতে পারে।
০২. TOTP-এর ৫টি ক্রিপ্টোগ্রাফিক গাণিতিক ধাপ
একটি ১৬-অক্ষরের Base32 সিড (যেমন JBSWY3DPEHPK3PXP) থেকে একটি ৬-সংখ্যার পরিবর্তনশীল কোডে (যেমন 284915) রূপান্তরের প্রক্রিয়ায় পাঁচটি সুনির্দিষ্ট ক্রিপ্টোগ্রাফিক ধাপ রয়েছে:
এক্সচেঞ্জ এনরোলমেন্ট কিগুলো RFC 4648 Base32 ফরম্যাটে বর্ণমালা A-Z এবং 2-7 ব্যবহার করে এনকোড করা হয় (বিভ্রান্তিকর অক্ষর যেমন 0, 1, 8, 9 বাদ থাকে)। অ্যালগরিদম প্রথমে স্ট্রিংটিকে স্বাভাবিক করে, স্পেস মুছে ফেলে এবং প্যাডিং (=) প্রয়োগ করে যাতে এর দৈর্ঘ্য ৮-এর গুণিতক হয়। এরপর এটি কাঁচা বাইনারি কি বাইট $K$-তে ডিকোড করা হয়।
বর্তমান ইউনিক্স টাইমস্ট্যাম্পকে (১ জানুয়ারি ১৯৭০ UTC থেকে অতিবাহিত সেকেন্ড) পূর্ণসংখ্যা কাউন্টার $T$-তে রূপান্তর করা হয়:
এখানে স্ট্যান্ডার্ড প্যারামিটার $T_0 = 0$ এবং স্টেপ উইন্ডো $X = 30$ সেকেন্ড। এই পূর্ণসংখ্যা $T$-কে এরপর একটি ৮-বাইট বিগ-এন্ডিয়ান ৬৪-বিট আনসাইনড ইন্টিজারে এনকোড করা হয়।
কাঁচা কি বাইট $K$-কে সিক্রেট কি হিসেবে ব্যবহার করে ৮-বাইটের বিগ-এন্ডিয়ান টাইম বাফারের ওপর HMAC-SHA1 ডাইজেস্ট গণনা করা হয়, যা একটি ২০-বাইট (১৬০-বিট) মেসেজ প্রমাণীকরণ হ্যাশ $HS$ তৈরি করে:
২০টি বাইটকে একটি পূর্ণসংখ্যায় রূপান্তর করতে RFC 4226 ডায়নামিক ট্রাঙ্কেশন সুনির্দিষ্ট করে:
- অফসেট নিষ্কাশন: শেষ বাইট $HS[19]$ পর্যবেক্ষণ করুন এবং নিচের ৪টি বিট বের করতে
0x0Fদিয়ে মাস্ক করুন, যা ০ থেকে ১৫-এর মধ্যে একটি অফসেট মান দেয়:Offset = HS[19] & 0x0F; - ৪টি বাইট আলাদা করা: Offset থেকে শুরু করে পরপর ৪টি বাইট আলাদা করুন: $P = HS[Offset \dots Offset+3]$;
- সাইন বিট মাস্কিং: সাইনড ইন্টিজার ওভারফ্লো সংক্রান্ত বিভ্রান্তি এড়াতে
0x7Fদিয়ে মোস্ট সিগনিফিক্যান্ট বিট (MSB) মাস্ক করুন:
BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
সবশেষে স্ট্যান্ডার্ড ৬-সংখ্যার টোকেনের জন্য BinaryCode-কে মডুলো $10^6$ (১,০০০,০০০) করা হয়। একটি সামঞ্জস্যপূর্ণ ৬-অক্ষরের আউটপুট স্ট্রিং নিশ্চিত করতে ফলাফলের শুরুতে শূন্য যোগ (left-pad) করা হয়:
০৩. সরাসরি কোড ১: Python 3 স্ট্যান্ডার্ড লাইব্রেরি
এই সম্পূর্ণ বাস্তবায়নটি কোনো pip প্যাকেজ ইনস্টল করা ছাড়াই পাইথনের বিল্ট-ইন base64, struct, hmac এবং hashlib মডিউল ব্যবহার করে কার্যকরভাবে সম্পন্ন হয়:
import base64 import hashlib import hmac import struct import timedef generate_totp(secret_base32: str, timestamp: int = None, step: int = 30, digits: int = 6) -> str: """ Generate an RFC 6238 compliant TOTP token. :param secret_base32: Shared secret key in Base32 format (e.g., ‘JBSWY3DPEHPK3PXP’) :param timestamp: Unix epoch timestamp in seconds. Defaults to time.time() :param step: Time-step window in seconds (default: 30) :param digits: Number of output digits (default: 6) :return: Left-padded OTP string (e.g., ‘045812’) """ if timestamp is None: timestamp = int(time.time())
1. Normalize Base32 string and append ’=’ padding characters
normalized_secret = secret_base32.replace(” ”, "").upper() padding_needed = (8 - len(normalized_secret) % 8) % 8 normalized_secret += ”=” * padding_needed key_bytes = base64.b32decode(normalized_secret)
2. Compute 64-bit big-endian time-step counter T
time_counter = timestamp // step time_bytes = struct.pack(“>Q”, time_counter) # 8 bytes big-endian unsigned long long
3. Calculate HMAC-SHA1 digest (20 bytes)
hmac_digest = hmac.new(key_bytes, time_bytes, hashlib.sha1).digest()
4. Perform Dynamic Truncation
offset = hmac_digest[-1] & 0x0F # Lower 4 bits of the 20th byte
Unpack 4 bytes at offset and clear MSB sign bit
binary_code = struct.unpack(“>I”, hmac_digest[offset : offset + 4])[0] & 0x7FFFFFFF
5. Modulo arithmetic and zero-fill formatting
otp_code = binary_code % (10 ** digits) return str(otp_code).zfill(digits)
if name == “main”: test_secret = “JBSWY3DPEHPK3PXP” print(f”Generated TOTP Code: {generate_totp(test_secret)}”)
০৪. সরাসরি কোড ২: JavaScript ও Node.js Crypto
নেটিভ crypto.createHmac ব্যবহার করে ব্যাকএন্ড Node.js রানটাইমের জন্য পিওর জাভাস্ক্রিপ্ট বাস্তবায়ন:
const crypto = require('crypto');// RFC 4648 Base32 Decoder in Pure JavaScript function base32Decode(base32Str) { const alphabet = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ234567’; const cleaned = base32Str.replace(/=+$/, ”).toUpperCase().replace(/\s+/g, ”); let bits = 0, value = 0, index = 0; const output = new Uint8Array(Math.floor((cleaned.length * 5) / 8));
for (let i = 0; i < cleaned.length; i++) { const val = alphabet.indexOf(cleaned[i]); if (val === -1) continue; value = (value << 5) | val; bits += 5; if (bits >= 8) { output[index++] = (value >>> (bits - 8)) & 255; bits -= 8; } } return Buffer.from(output.buffer, 0, index); }
function generateTOTP(secretBase32, timestamp = Math.floor(Date.now() / 1000), step = 30, digits = 6) { const key = base32Decode(secretBase32); const counter = Math.floor(timestamp / step);
// Allocate 8-byte buffer in Big-Endian format const buffer = Buffer.alloc(8); buffer.writeBigUInt64BE(BigInt(counter), 0);
// Compute HMAC-SHA1 const hmac = crypto.createHmac(‘sha1’, key).update(buffer).digest();
// Dynamic Truncation const offset = hmac[hmac.length - 1] & 0x0f; const binary = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) | ((hmac[offset + 2] & 0xff) << 8) | (hmac[offset + 3] & 0xff);
const otp = binary % (10 ** digits); return otp.toString().padStart(digits, ‘0’); }
console.log(“Calculated TOTP:”, generateTOTP(“JBSWY3DPEHPK3PXP”));
W3C Web Crypto API-এর মাধ্যমে ক্লায়েন্ট-সাইড ব্রাউজারে এক্সিকিউশন:
যেসব আধুনিক ওয়েব অ্যাপ্লিকেশন ১০০% অফলাইন নিরাপত্তা চায় (যে আর্কিটেকচারে 2FA-Tool গড়ে উঠেছে), তাদের জন্য W3C Web Crypto API কোনো থার্ড-পার্টি npm বান্ডেল ছাড়াই সরাসরি ব্রাউজার স্যান্ডবক্সে অ্যাসিনক্রোনাস ও হার্ডওয়্যার-অ্যাক্সিলারেটেড ক্রিপ্টোগ্রাফিক সুবিধা নিশ্চিত করে:
async function generateBrowserTOTP(secretBase32, timestamp = Math.floor(Date.now() / 1000)) { const keyBytes = base32Decode(secretBase32); const counter = Math.floor(timestamp / 30);// 1. Import raw secret key into SubtleCrypto const cryptoKey = await window.crypto.subtle.importKey( “raw”, keyBytes, { name: “HMAC”, hash: { name: “SHA-1” } }, false, [“sign”] );
// 2. Encode 8-byte Big-Endian counter buffer const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigUint64(0, BigInt(counter), false); // false = Big-Endian
// 3. Perform cryptographic HMAC sign const signature = await window.crypto.subtle.sign(“HMAC”, cryptoKey, buffer); const hmacResult = new Uint8Array(signature);
// 4. Dynamic Truncation const offset = hmacResult[hmacResult.length - 1] & 0x0f; const binary = ((hmacResult[offset] & 0x7f) << 24) | ((hmacResult[offset + 1] & 0xff) << 16) | ((hmacResult[offset + 2] & 0xff) << 8) | (hmacResult[offset + 3] & 0xff);
const otp = binary % 1000000; return otp.toString().padStart(6, ‘0’); }
০৫. সরাসরি কোড ৩: প্রোডাকশন Golang (Go)
উচ্চ-থ্রুপুট সম্পন্ন মাইক্রোসার্ভিসগুলো Go-এর শক্তিশালী ও বিশ্বস্ত স্ট্যান্ডার্ড লাইব্রেরি থেকে পূর্ণ সুবিধা গ্রহণ করে:
package mainimport ( “crypto/hmac” “crypto/sha1” “encoding/base32” “encoding/binary” “fmt” “math” “strings” “time” )
// GenerateTOTP computes standard RFC 6238 time-based tokens func GenerateTOTP(secretBase32 string, timestamp int64, step int64, digits int) (string, error) { // 1. Sanitize Base32 seed and decode cleaned := strings.ToUpper(strings.ReplaceAll(secretBase32, ” ”, "")) key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(cleaned) if err != nil { key, err = base32.StdEncoding.DecodeString(cleaned) if err != nil { return "", err } }
// 2. Encode 64-bit Big-Endian time-step counter counter := uint64(timestamp / step) buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, counter)
// 3. Compute HMAC-SHA1 mac := hmac.New(sha1.New, key) mac.Write(buf) hash := mac.Sum(nil)
// 4. Dynamic Truncation offset := hash[len(hash)-1] & 0x0f binaryCode := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7fffffff
// 5. Modulo arithmetic mod := uint32(math.Pow10(digits)) otp := binaryCode % mod
format := fmt.Sprintf(“%%0%dd”, digits) return fmt.Sprintf(format, otp), nil }
func main() { code, err := GenerateTOTP(“JBSWY3DPEHPK3PXP”, time.Now().Unix(), 30, 6) if err != nil { panic(err) } fmt.Printf(“Golang Computed TOTP: %s\n”, code) }
০৬. RFC 6238 পরিশিষ্ট B টেস্ট ভেক্টর যাচাইকরণ
স্ট্যান্ডার্ডের সামঞ্জস্য প্রমাণ করতে প্রতিটি ক্রিপ্টোগ্রাফিক বাস্তবায়ন অবশ্যই IETF RFC 6238 Appendix B-তে প্রকাশিত অফিশিয়াল টেস্ট ভেক্টরের সাথে মিলিয়ে যাচাই করতে হবে।
অফিশিয়াল টেস্ট স্যুটটি ASCII সিক্রেট "12345678901234567890" (২০ বাইট) ব্যবহার করে, যা Base32-তে এনকোড করলে দাঁড়ায়:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (৩০-সেকেন্ডের স্টেপসহ ৮-সংখ্যার আউটপুটের জন্য মূল্যায়িত):
| ইউনিক্স ইপক টাইমস্ট্যাম্প | UTC তারিখ ও সময় | RFC অফিশিয়াল প্রত্যাশিত কোড | বাস্তবায়নের ফলাফল |
|---|---|---|---|
| 59 | 1970-01-01 00:00:59 | 94287082 | ✓ PASSED (Exact Match) |
| 1111111109 | 2005-03-18 01:58:29 | 07081804 | ✓ PASSED (Exact Match) |
| 1111111111 | 2005-03-18 01:58:31 | 14050471 | ✓ PASSED (Exact Match) |
| 1234567890 | 2009-02-13 23:31:30 | 89005924 | ✓ PASSED (Exact Match) |
| 2000000000 | 2033-05-18 03:33:20 | 69279037 | ✓ PASSED (Exact Match) |
০৭. প্রোডাকশন ইঞ্জিনিয়ারিং: স্কিউ, রিপ্লে ও লিমিট
উচ্চ-উপলব্ধতাসম্পন্ন এন্টারপ্রাইজ প্রমাণীকরণ সিস্টেমে TOTP যাচাইকরণ প্রয়োগ করার সময় তিনটি নিরাপত্তা নীতি অবশ্যই কার্যকর করতে হবে:
মোবাইল ঘড়ির সামান্য পার্থক্যের কারণে সার্ভারগুলোর [T - 1, T, T + 1] উইন্ডো মূল্যায়ন করা উচিত (পূর্ববর্তী ৩০ সেকেন্ড, বর্তমান ৩০ সেকেন্ড এবং পরবর্তী ৩০ সেকেন্ড পরীক্ষা করা)। কখনও এই উইন্ডো $\pm 1$ স্টেপের বাইরে প্রসারিত করবেন না, কারণ প্রতিটি অতিরিক্ত স্টেপ রিপ্লে আক্রমণের ক্ষেত্র ৩০ সেকেন্ড বাড়িয়ে দেয়।
সফল ব্যবহারের পরপরই একটি টোকেন অবশ্যই বাতিল করতে হবে। Redis বা ইন-মেমোরি ক্যাশে Last_Verified_Time_Step সংরক্ষণ করুন। কোনো ক্লায়েন্ট যদি একই ৩০-সেকেন্ড উইন্ডোর মধ্যে একই টোকেন পুনরায় জমা দেওয়ার চেষ্টা করে, তবে ম্যান-ইন-দ্য-মিডল রিপ্লে আক্রমণ রুখতে অনুরোধটি সরাসরি প্রত্যাখ্যান করুন।
যেহেতু ৬-সংখ্যার কোডের বিন্যাস মাত্র ১,০০০,০০০ (১ মিলিয়ন), তাই রেট লিমিট না থাকলে এন্ডপয়েন্টে দ্রুত ব্রুট-ফোর্স চালানো সম্ভব। পরপর ৩ বার ব্যর্থ চেষ্টার পর প্রগ্রেসিভ বিলম্ব এবং অ্যাকাউন্ট লকআউট কার্যকর করুন। কোডে সাইড-চ্যানেল টাইমিং অ্যানালিসিস প্রতিহত করতে কনস্ট্যান্ট-টাইম সমতা ফাংশন (যেমন Go-তে crypto/subtle.ConstantTimeCompare বা Node.js-এ crypto.timingSafeEqual) ব্যবহার করে তুলনা সম্পন্ন করুন।
০৮. সচরাচর জিজ্ঞাস্য (FAQ)
প্রশ্ন ১: যেহেতু SHA-1 ক্রিপ্টোগ্রাফিকভাবে দুর্বল প্রমাণিত হয়েছে, তবুও কেন TOTP-তে ডিফল্ট হিসেবে HMAC-SHA1 ব্যবহৃত হয়? ▼
সাধারণ SHA-1-এর ওপর তাত্ত্বিক কোলিশন আক্রমণ HMAC কাঠামোকে (RFC 2104) ভাঙতে পারে না। HMAC-এ মূল হ্যাশ ফাংশনটি অভ্যন্তরীণ ও বাহ্যিক গোপন কি-এর মধ্যে আবৃত থাকে, যা কোলিশন-ভিত্তিক আক্রমণ প্রতিহত করে। আজ অবধি 2FA-এর জন্য HMAC-SHA1 সম্পূর্ণ নিরাপদ রয়েছে। অধিকন্তু উচ্চতর নিরাপত্তার প্রয়োজন হলে RFC 6238 পুরোপুরি HMAC-SHA256 এবং HMAC-SHA512 সমর্থন করে।
প্রশ্ন ২: টাইম জোন বা ডেলাইট সেভিং টাইম (DST) কি TOTP সিঙ্ক্রোনাইজেশনে কোনো প্রভাব ফেলে? ▼
না। ইউনিক্স ইপক সময় ১৯৭০-০১-০১ ০০:০০:০০ UTC থেকে অতিবাহিত সেকেন্ড হিসেবে সংজ্ঞায়িত। এটি টাইম জোন, লিপ সেকেন্ড এবং ডেলাইট সেভিং পরিবর্তনের সম্পূর্ণ নিরপেক্ষ। যেকোনো নির্দিষ্ট মুহূর্তে টোকিওর একটি সার্ভার এবং ক্যালিফোর্নিয়ার একটি স্মার্টফোনে হুবহু একই পূর্ণসংখ্যা টাইমস্ট্যাম্প থাকে।
প্রশ্ন ৩: Steam Guard-এর ৫-অক্ষরের প্রমাণীকরণ কীভাবে RFC 6238 থেকে পৃথক? ▼
Steam Guard হলো RFC 6238-এর ওপর ভিত্তি করে তৈরি একটি নিজস্ব সংস্করণ। এটি হুবহু একই ৩০-সেকেন্ডের টাইম-স্টেপ এবং HMAC-SHA1 গণনা ব্যবহার করে, কিন্তু চূড়ান্ত রিডাকশন ধাপে এটি নিষ্কাশিত পূর্ণসংখ্যাকে একটি কাস্টম ২৬-অক্ষরের আলফানিউমেরিক ক্যারেক্টার অ্যারের (23456789BCDFGHJKMNPQRTVWXY) মাধ্যমে ম্যাপ করে ৫-অক্ষরের স্ট্রিং তৈরি করে।
সরাসরি ১০০% ক্লায়েন্ট-সাইড TOTP কোড তৈরির অভিজ্ঞতা নিন
2FA-Tool কোনো সার্ভার ট্রান্সমিশন ছাড়াই W3C Web Crypto API ব্যবহার করে সরাসরি আপনার ব্রাউজারের মধ্যে এই ক্রিপ্টোগ্রাফিক পাইপলাইনটি বাস্তবায়ন করে। কোনো টোকেন জমা থাকে না, অফলাইনে কাজ করে এবং তাৎক্ষণিক যাচাইকরণ দেয়।