এই পর্বে যা শিখবেন
- Hash function-এর তিন ধরনের প্রতিরোধ, আর কোনটি ভাঙলে কী বিপদ
- Birthday bound হিসাব করা, ১২৮ আর ২৫৬ bit আউটপুটের পূর্ণ তুলনাসহ
- MD5 আর SHA-1 কেন অবসরে, আর এখন কী ব্যবহার করবেন
- HMAC-এর দুই ধাপ, constant-time তুলনা আর signed webhook যাচাই
- নিজের কম্পিউটারে
sha256sumদিয়ে download যাচাই, আর Merkle tree দিয়ে বদল খুঁজে বের করা
১ · Hash কী — ডেটার আঙুলের ছাপ
একটি কাল্পনিক অনলাইন বাজারের হিসাবরক্ষক প্রতি রাতে এক লাখ লাইনের একটি লেনদেনের ফাইল ব্যাংকে পাঠান। পরদিন সকালে ব্যাংক জানতে চায়, ফাইলটি কি পথে একটুও বদলেছে? পুরো ফাইল আবার মিলিয়ে দেখা অসম্ভব রকম ঝামেলার। এর বদলে যদি ফাইলটির একটি ছোট “ছাপ” থাকত, যা ফোনেও পড়ে শোনানো যায়, কাজটি অনেক সহজ হতো।
এই ছাপ বানায় hash function. ফাইল এক KB হোক বা দশ GB, SHA-256 সবসময় ২৫৬ bit, মানে ৬৪টি hex অক্ষরের একটি ছাপ দেয়। এই ছাপকে বলে hash বা digest. পর্ব ০৭-এ অখণ্ডতার (integrity) লক্ষ্যটি দেখেছেন। এই পর্বে দেখবেন সেই লক্ষ্য পূরণের আসল যন্ত্রগুলো। ধারণাটির প্রথম পরিচয় পাবেন Computer Networks কোর্সের cryptography পর্বে; এখানে আমরা রক্ষকের চোখে আরও গভীরে যাব।
জাতীয় পরিচয়পত্রের আঙুলের ছাপের কথা ভাবুন। ছাপ দেখে মানুষটিকে চেনা যায়, কিন্তু ছাপ থেকে মানুষটিকে বানানো যায় না। দুজনের ছাপ মিলে যাওয়াও প্রায় অসম্ভব। Hash ঠিক এমন: ডেটা থেকে ছাপ সহজ, ছাপ থেকে ডেটা নয়।
একটি ভালো hash function-এর গুণ
- Determinism: একই ইনপুটে সবসময়, সব কম্পিউটারে একই digest. এই গুণ না থাকলে যাচাই করাই যেত না।
- নির্দিষ্ট দৈর্ঘ্য আর গতি: যেকোনো আকারের ইনপুটে একই দৈর্ঘ্যের আউটপুট, আর হিসাব দ্রুত। (এই দ্রুততাই password রাখার জন্য সমস্যা, সেই আলোচনা পর্ব ১৩-তে।)
- Preimage resistance: শুধু digest দেওয়া থাকলে এমন কোনো ইনপুট খুঁজে পাওয়া কঠিন, যার digest এটি।
- Second preimage resistance: একটি নির্দিষ্ট ফাইল দেওয়া থাকলে একই digest-এর অন্য ফাইল বানানো কঠিন।
- Collision resistance: যেকোনো দুটি আলাদা ইনপুট, যাদের digest এক, খুঁজে পাওয়া কঠিন। এখানে আক্রমণকারী দুটি ইনপুটই নিজে বেছে নিতে পারেন।
তিন প্রতিরোধের পার্থক্য
কী দেওয়া আছে, কী খুঁজতে হবে, আর আনুমানিক খরচ (n-bit আদর্শ hash)
কোনটি ভাঙলে কী হয়? ধরুন কেউ চুক্তির দুটি সংস্করণ বানালেন, যাদের digest এক। একটিতে সই নিয়ে পরে অন্যটি দেখালেন। এটি collision-এর বিপদ, আর এর জন্য চুক্তি দুটি তাঁকেই তৈরি করতে হয়। অন্যদিকে, আগে থেকে থাকা কোনো ফাইলের নকল বানাতে হলে দরকার second preimage, যা অনেক বেশি কঠিন। তাই collision ভাঙলেও সব ব্যবহার সঙ্গে সঙ্গে ভেঙে পড়ে না। তবে পেশাদার নিয়ম হলো, collision ভাঙা শুরু হলেই সরে যাওয়া।
২ · Avalanche effect — এক অক্ষরে পুরো ছাপ বদল
পাহাড়ে একটি ছোট নুড়ি গড়িয়ে পড়লে যেমন গোটা ঢাল ধসে যেতে পারে, ভালো hash-এ তেমনি এক bit বদলালে digest-এর প্রায় অর্ধেক bit উল্টে যায়। একে বলে avalanche effect. এর ফলে “কাছাকাছি” ইনপুটের digest-ও কাছাকাছি হয় না, আর digest দেখে ইনপুট সম্পর্কে কোনো আন্দাজ করা যায় না।
নিচের কোডে দুটি কাল্পনিক নির্দেশ আছে, যাদের মধ্যে পার্থক্য একটি অক্ষর। m আর l-এর ASCII কোডে পার্থক্য ঠিক এক bit. চালিয়ে দেখুন digest-এ কতটি bit বদলায়।
import hashlib
def sha(data):
return hashlib.sha256(data).digest()
def changed_bits(x, y):
return (int.from_bytes(x) ^ int.from_bytes(y)).bit_count()
a = b"pay Rahim 500 taka"
b = b"pay Rahil 500 taka"
print("input A:", a.decode())
print("input B:", b.decode())
print("input bits changed :", changed_bits(a, b))
print("SHA-256 A:", sha(a).hex())
print("SHA-256 B:", sha(b).hex())
print("output bits changed:", changed_bits(sha(a), sha(b)), "of 256")
# 1000 more pairs: flip one input bit, count changed output bits
counts = []
for i in range(1000):
msg = bytearray(f"message number {i}".encode())
before = sha(bytes(msg))
msg[0] ^= 1 # flip exactly one bit
counts.append(changed_bits(before, sha(bytes(msg))))
print("1000 pairs: min", min(counts), "avg", sum(counts) / 1000, "max", max(counts))ইনপুটে বদলেছে input bits changed : 1, অথচ আউটপুটে output bits changed: 115 of 256. দুটি digest শুরু হয়েছে f96ecce1… আর f8f5947b… দিয়ে, কোনো মিল চোখে পড়ে না। হাজার জোড়ায় ফল এসেছে min 102 avg 127.084 max 154. আদর্শ মান ঠিক অর্ধেক, মানে ১২৮. সংখ্যাটি প্রতিবার ১২৮-এর আশপাশেই ঘোরে, এটাই avalanche.
Avalanche মানে কিন্তু “গোপন করা” নয়। Digest থেকে ইনপুট উদ্ধার করা কঠিন হলেও ইনপুটের সম্ভাব্য মান কম হলে (PIN, মোবাইল নম্বর, NID) সব সম্ভাব্য মানের hash মিলিয়ে দেখা যায়। তাই ব্যক্তিগত তথ্যের সাধারণ hash সুরক্ষা নয়। Password রাখার সঠিক পথ পর্ব ১৩-তে দেখবেন।
৩ · Birthday bound — কেন ২৫৬ bit-এ নিরাপত্তা ১২৮ bit
একটি ঘরে কতজন থাকলে দুজনের জন্মদিন এক হওয়ার সম্ভাবনা ৫০% ছাড়ায়? উত্তর মাত্র ২৩. অবাক লাগে, কারণ আমরা ভাবি “আমার সঙ্গে কারও মিল”। কিন্তু প্রশ্নটি “যেকোনো দুজনের মিল”, আর ২৩ জনে জোড়া হয় 23 × 22 / 2 = ২৫৩টি। এই একই গণিত hash-এর birthday bound: জোড়ার সংখ্যা বাড়ে k²-এর মতো, তাই মিল আসে আশার চেয়ে অনেক আগে।
সূত্রটি কোথা থেকে আসে
ধরুন digest n bit, তাই সম্ভাব্য মান N = 2nটি। k টি এলোমেলো ইনপুট নিলে জোড়া হয় প্রায় k²/2টি, আর প্রতিটি জোড়া মেলার সম্ভাবনা 1/N. একটু গুছিয়ে লিখলে:
p = ½ হলে k ≈ 1.1774 × 2n/2
1.1774 সংখ্যাটি এসেছে √(2 ln 2) থেকে। পরীক্ষায় সাধারণত এটুকু বাদ দিয়ে “প্রায় 2n/2” লিখলেই চলে। মনে রাখুন, এটি আদর্শ hash-এর সীমা। অ্যালগরিদমে দুর্বলতা থাকলে খরচ এর চেয়েও কমে যায়, যেমনটি MD5 আর SHA-1-এ হয়েছে।
পূর্ণ হিসাব: ১২৮ bit বনাম ২৫৬ bit আউটপুট
ধরে নিন একটি ধনী আক্রমণকারীর ক্ষমতা সেকেন্ডে 1012টি hash. সংখ্যাটি শুধু হিসাবের জন্য ধরে নেওয়া।
- ১২৮ bit আউটপুট: k ≈ 1.1774 × 264 ≈ 2.17 × 1019. সময় ≈ 2.17 × 107 সেকেন্ড, মানে প্রায় ২৫১ দিন। অর্থাৎ এক বছরের কম সময়ে ৫০% সম্ভাবনা।
- ২৫৬ bit আউটপুট: k ≈ 1.1774 × 2128 ≈ 4.01 × 1038. সময় ≈ 4.01 × 1026 সেকেন্ড, মানে প্রায় 1.27 × 1019 বছর। মহাবিশ্বের বয়স প্রায় 1.4 × 1010 বছর, তার প্রায় ৯০ কোটি গুণ।
আউটপুট দ্বিগুণ করায় খরচ দ্বিগুণ হয়নি, বরং 264 গুণ হয়েছে। এজন্যই collision-নিরাপত্তা ১২৮ bit চাইলে আউটপুট লাগে ২৫৬ bit. নিচের কোডে একই হিসাব টেবিলে দেখুন।
// Birthday bound: k random hashes, n-bit output
// p(collision) is about 1 - exp(-k^2 / 2^(n+1))
const pCollision = (k, n) => -Math.expm1(-(k * k) / 2 ** (n + 1));
const sci = x => x.toExponential(2).padStart(10);
let noMatch = 1; // the classroom version, exact
for (let i = 0; i < 23; i++) noMatch *= (365 - i) / 365;
console.log('23 people, 365 days: ' + ((1 - noMatch) * 100).toFixed(1) + '%');
console.log('');
console.log('bits k for 50% p(k=1e9) p(k=1e12)');
for (const n of [32, 64, 128, 160, 256]) {
const k50 = 1.1774 * 2 ** (n / 2);
console.log(String(n).padEnd(5), sci(k50), sci(pCollision(1e9, n)), sci(pCollision(1e12, n)));
}
const rate = 1e12; // assumed: 10^12 hashes per second
const year = 365.25 * 24 * 3600;
console.log('');
console.log('assumed rate: 1e12 hashes/second');
for (const n of [128, 256]) {
const seconds = 1.1774 * 2 ** (n / 2) / rate;
console.log(n + '-bit output, 50% collision: ' + (seconds / year).toExponential(2) + ' years');
}23 people, 365 days: 50.7%
bits k for 50% p(k=1e9) p(k=1e12)
32 7.72e+4 1.00e+0 1.00e+0
64 5.06e+9 2.67e-2 1.00e+0
128 2.17e+19 1.47e-21 1.47e-15
160 1.42e+24 3.42e-31 3.42e-25
256 4.01e+38 4.32e-60 4.32e-54
assumed rate: 1e12 hashes/second
128-bit output, 50% collision: 6.88e-1 years
256-bit output, 50% collision: 1.27e+19 years
৩২ bit-এর ছাপে মাত্র ৭৭ হাজারের মতো ফাইলেই ৫০% সম্ভাবনা, তাই ছোট id-কে “অনন্য” ধরে নেওয়া বিপজ্জনক। ৬৪ bit-এ এক লাখ কোটি (1012) ফাইলে মিল প্রায় নিশ্চিত। ২৫৬ bit-এ একই সংখ্যায় সম্ভাবনা 4.32e-54, যা বাস্তবে শূন্য। আর 6.88e-1 years মানে ০.৬৮৮ বছর, ওপরের ২৫১ দিনই।
৪ · MD5 আর SHA-1-এর অবসর, এখন কী ব্যবহার করবেন
Hash function চিরকালের নয়। গবেষকেরা দুর্বলতা খুঁজে পান, আর একসময় তত্ত্ব বাস্তব হয়ে যায়। তাই শুধু নাম মুখস্থ না করে প্রতিটির অবস্থা জেনে রাখুন।
| অ্যালগরিদম | আউটপুট | গঠন | অবস্থা |
|---|---|---|---|
| MD5 (১৯৯২) | ১২৮ bit | Merkle-Damgård | ভাঙা। Collision সাধারণ কম্পিউটারেই সম্ভব। নিরাপত্তা-কাজে নিষেধ |
| SHA-1 (১৯৯৫) | ১৬০ bit | Merkle-Damgård | ভাঙা। বাস্তব collision প্রকাশিত। সরে যাওয়া জরুরি |
| SHA-2 (SHA-256, SHA-512) | ২২৪–৫১২ bit | Merkle-Damgård | নিরাপদ, সবচেয়ে প্রচলিত। FIPS 180-4 |
| SHA-3 (SHA3-256) | ২২৪–৫১২ bit | Sponge | নিরাপদ, ভিন্ন নকশা। FIPS 202 |
| BLAKE2 | ৫১২ bit পর্যন্ত | HAIFA-ধর্মী | নিরাপদ বলে গণ্য, সফটওয়্যারে দ্রুত। RFC 7693 |
২০০৪-০৫ সালে Xiaoyun Wang ও সহকর্মীরা MD5-এর collision খোঁজার কার্যকর পদ্ধতি প্রকাশ করেন। ২০১২ সালে Flame ম্যালওয়্যার নিয়ে প্রকাশিত বিশ্লেষণে দেখা যায়, MD5 collision কাজে লাগিয়ে একটি জাল code-signing certificate তৈরি করা হয়েছিল। অর্থাৎ ভাঙা hash সত্যিই বাস্তব ক্ষতি করেছে। ফেব্রুয়ারি ২০১৭-তে Google আর CWI Amsterdam-এর গবেষকেরা SHAttered প্রকাশ করেন: একই SHA-1 digest-এর দুটি আলাদা PDF. প্রকাশিত হিসাবে এতে প্রায় 263টি SHA-1 গণনা লেগেছিল, যা birthday-র 280-এর চেয়ে বহুগুণ কম। ২০২০ সালে SHA-1-এ আরও শক্তিশালী chosen-prefix collision দেখানো হয়। NIST ডিসেম্বর ২০২২-এ ঘোষণা করে, ৩১ ডিসেম্বর ২০৩০-এর মধ্যে SHA-1 থেকে সরে আসার পরিকল্পনা রয়েছে।
এই বিশাল গণনা দেখে মনে হতে পারে “সাধারণ মানুষ তো পারবে না।” কিন্তু দুটি বিষয় ভাবুন। প্রথমত, গণনার খরচ প্রতি বছর কমে। দ্বিতীয়ত, আক্রমণ সবসময় উন্নত হয়, কখনো পিছিয়ে যায় না। MD5-এ যা একসময় গবেষণা ছিল, এখন তা সাধারণ যন্ত্রেই সম্ভব।
সাধারণ কাজে SHA-256. বেশি নিরাপত্তা-সীমা চাইলে SHA-512 বা SHA3-256. গতি চাইলে আর আপনার পরিবেশে অনুমোদিত হলে BLAKE2. MD5 আর SHA-1 শুধু পুরোনো সিস্টেমের সঙ্গে মেলানোর দরকারে, আর এমন জায়গায়, যেখানে নিরাপত্তা নির্ভর করে না। সেখানেও সরে যাওয়ার পরিকল্পনা রাখুন।
৫ · Merkle-Damgård আর length extension — কেন hash(key + message) ভুল
ধরুন আপনি ভাবলেন, “Hash তো যে কেউ বানাতে পারে। তাহলে বার্তার সামনে একটি গোপন key জুড়ে hash করি, হয়ে গেল প্রমাণ।” যুক্তিটি স্বাভাবিক, কিন্তু SHA-256 বা MD5-এর মতো hash-এ এই নকশা ভাঙা। কারণ লুকিয়ে আছে এদের ভেতরের গঠনে, যার নাম Merkle-Damgård. Ralph Merkle আর Ivan Damgård ১৯৮৯ সালে আলাদাভাবে এটি প্রকাশ করেন।
একটি রিলে দৌড় বা অফিসের রেজিস্টার খাতা ভাবুন। প্রতিটি পাতার শেষে আগের যোগফল নিয়ে পরের পাতায় হিসাব চলে। শেষ পাতার যোগফলই চূড়ান্ত ফল। যিনি শেষ যোগফল জানেন, তিনি খাতার আগের পাতা না দেখেই নতুন পাতা জুড়ে হিসাব চালিয়ে যেতে পারেন। Merkle-Damgård-এর সমস্যাটি ঠিক এমন।
নিচের ডায়াগ্রামে ধাপে ধাপে দেখুন digest কীভাবে তৈরি হয়। এখানে শুধু ধারণাটি দেখানো হয়েছে, padding-এর খুঁটিনাটি বাদ।
- শুরু। একটি নির্দিষ্ট প্রাথমিক মান IV আর বার্তার প্রথম block (এখানে গোপন key) ঢোকে compression function
f-এ। SHA-256-এ প্রতিটি block ৫১২ bit. - শিকল চলে।
f-এর ফল S1 (অন্তর্বর্তী অবস্থা) আর পরের block m₁ মিলে পরেরf-এ যায়। আগের সবকিছুর ছাপ S1-এর ভেতরে জমা। - শেষ অবস্থাই digest. শেষ block-এর পর যে অবস্থা থাকে, সেটিই সরাসরি digest হিসেবে বেরিয়ে আসে। অর্থাৎ SHA-256 তার পুরো ভেতরের অবস্থা বাইরে দেখিয়ে দেয়।
- সমস্যা। Digest যেহেতু পুরো অবস্থা, যাঁর হাতে digest আছে, তিনি key না জেনেও সেখান থেকে শিকল চালিয়ে যেতে পারেন। ফলে “বার্তা + নতুন অংশ”-এর বৈধ ট্যাগ বানানো সম্ভব হয়। এর নাম length extension.
ফলাফল কী? SHA-256(key + message) দিয়ে সই করা একটি API-তে আক্রমণকারী key না জেনেও বার্তার শেষে বাড়তি অংশ জুড়ে বৈধ দেখতে ট্যাগ বানাতে পারেন। এটি শুধু তত্ত্ব নয়। ২০০৯ সালে Thai Duong আর Juliano Rizzo একটি প্রকাশিত advisory-তে দেখান, Flickr-এর API signature ঠিক এই কারণে দুর্বল ছিল, যেখানে secret আর parameter জুড়ে MD5 নেওয়া হতো।
এখানে একটি জরুরি শিক্ষা আছে। SHA-256 ভাঙা নয়, ভাঙা হলো তাকে ব্যবহার করার নকশা। ভালো উপাদান দিয়েও ভুল জিনিস বানানো যায়। সমাধান নিচের জোড়ায় দেখুন। এখানে শুধু কোডের ধরন দেখানো হয়েছে, secret আর body আপনার নিজের সিস্টেমের মান ধরে নিন।
# WRONG: plain hash as MAC, and an early-exit compare
tag = hashlib.sha256(secret + body).hexdigest()
if tag == received_tag:
accept()
# RIGHT: HMAC, and a constant-time compare
tag = hmac.new(secret, body, hashlib.sha256).hexdigest()
if hmac.compare_digest(tag, received_tag):
accept()সব hash-এ এই সমস্যা নেই। SHA-3-এর sponge গঠন ভেতরের অবস্থার একটি অংশ বাইরে দেখায় না, তাই length extension কাজ করে না। SHA-512/256-এর মতো ছাঁটা (truncated) hash-ও এই বিপদ এড়ায়। তবু নিয়ম সহজ রাখুন: MAC দরকার হলে HMAC বা অনুমোদিত MAC ব্যবহার করুন, নিজে জোড়াতালি নয়।
৬ · Hash, MAC আর signature — কোনটি কী প্রমাণ করে
তিনটি জিনিসই “বদল ধরে”, কিন্তু প্রমাণ করে আলাদা জিনিস। পোস্ট অফিসের তিন রকম ব্যবস্থা ভাবুন। খামের ওজন লিখে রাখা হলো hash: যে কেউ নতুন খামে নতুন ওজন লিখতে পারেন। দুই শাখার গোপন সিলমোহর হলো MAC: দুই শাখাই সিল মারতে পারে, তাই তৃতীয় কেউ বুঝবেন না কে মেরেছে। আর ব্যক্তিগত হাতের সই হলো digital signature: সবাই মিলিয়ে দেখতে পারেন, কিন্তু সই করতে পারেন শুধু একজন।
| প্রশ্ন | Hash | MAC (HMAC) | Digital signature |
|---|---|---|---|
| Key লাগে? | না | একটি ভাগ করা গোপন key | Private key দিয়ে সই, public key দিয়ে যাচাই |
| দুর্ঘটনার বদল ধরে? | হ্যাঁ | হ্যাঁ | হ্যাঁ |
| ইচ্ছাকৃত বদল ধরে? | একা নয়; digest আলাদা বিশ্বস্ত পথে এলে তবেই | হ্যাঁ, key না থাকলে ট্যাগ বানানো যায় না | হ্যাঁ |
| প্রেরকের পরিচয় (authenticity)? | না | হ্যাঁ, তবে শুধু “key-ধারীদের কেউ” | হ্যাঁ, নির্দিষ্ট key-ধারী |
| Non-repudiation? | না | না, দুই পক্ষেরই একই key | হ্যাঁ, আইনি ও প্রক্রিয়াগত শর্তসাপেক্ষে |
| গতি | খুব দ্রুত | খুব দ্রুত | তুলনামূলক ধীর |
Digital signature-এর গভীর আলোচনা পর্ব ১১-তে। একটি কথা এখনই মনে রাখুন: signature সাধারণত পুরো ফাইলে হয় না, হয় ফাইলের hash-এ। তাই hash ভাঙলে signature-ও ঝুঁকিতে পড়ে। আর encryption-এর সঙ্গে অখণ্ডতা মেলানো, মানে AES-GCM-এর মতো authenticated encryption, পর্ব ০৮-এর বিষয়।
৭ · HMAC — দুই ধাপে বানানো নিরাপদ ট্যাগ
HMAC এসেছে Bellare, Canetti আর Krawczyk-এর ১৯৯৬ সালের গবেষণা থেকে, আর RFC 2104 (১৯৯৭) ও পরে FIPS 198-1-এ মানদণ্ড হিসেবে আছে। এর সৌন্দর্য হলো, এটি যেকোনো hash-এর ওপর চলে, hash বদলাতে হয় না। ধারণাটি সহজ: একবার ভেতরে hash, তারপর সেই ফলকে আরেকটি key-সহ আবার hash. ভেতরের পাস বার্তা চেপে ছোট করে, বাইরের পাস সেই ছাপকে গোপন key দিয়ে মুড়ে দেয়, তাই শেষ digest দেখে শিকল চালিয়ে যাওয়ার সুযোগ থাকে না।
এখানে ‖ মানে পাশাপাশি জোড়া, আর ⊕ মানে XOR. ipad হলো 0x36 আর opad হলো 0x5C, দুটিই block-এর পুরো দৈর্ঘ্য জুড়ে বারবার। নিচে একটি নকল key example-key আর বার্তা amount=500 দিয়ে আসল SHA-256 হিসাব দেখুন। মানগুলো Python-এ হিসাব করা, লম্বা মানের শুধু শুরুটুকু দেখানো হয়েছে।
- Key গুছানো
K′৬৪ byte - ভেতরের পাস
inneripad + বার্তা - বাইরের পাস
tagopad + inner
key = "example-key" (11 bytes)
K′ = 6578616d706c652d6b6579
00 00 00 ... (53 bytes of 00)
K′ = 65 78 61 6d 70 ...
ipad = 36 36 36 36 36 ...
K′ ⊕ ipad = 53 4e 57 5b 46 ...
শেষের 00 ⊕ 36 = 36
m = "amount=500"
inner = 80d66935e537a414
256ac4d883ed0e72
53d3d22e0f09fd23
41db6d07de0269a5
K′ = 65 78 61 6d 70 ...
opad = 5c 5c 5c 5c 5c ...
K′ ⊕ opad = 39 24 3d 31 2c ...
শেষের 00 ⊕ 5c = 5c
tag = 6e1bdfc43aaabd25
f678f046269cb54a
c6ecc994ae8706c2
f6310d136cb46207
hand-made : 6e1bdfc43aaabd25...
hmac.new : 6e1bdfc43aaabd25...
same 32-byte tag
- Key গুছানো। SHA-256-এর block ৬৪ byte. ১১ byte-এর key-র শেষে শূন্য বসিয়ে ৬৪ byte করা হলো। Key block-এর চেয়ে লম্বা হলে আগে সেটি hash করে নিতে হয়।
- ভেতরের key. K′-এর প্রতিটি byte-কে 0x36 দিয়ে XOR করা হলো। যেমন প্রথম byte: 0x65 ⊕ 0x36 = 0x53. শূন্যের জায়গায় 0x36-ই থাকে।
- ভেতরের পাস। ওই ৬৪ byte-এর পরে বার্তা জুড়ে SHA-256 নেওয়া হলো। ফল
80d66935…. বাইরে এই মান কখনো দেখানো হয় না। - বাইরের key. এবার একই K′-কে 0x5C দিয়ে XOR করা হলো, প্রথম byte 0x65 ⊕ 0x5C = 0x39. দুই পাসের key তাই আলাদা।
- বাইরের পাস। বাইরের key-র পরে inner digest জুড়ে আবার SHA-256. ফল
6e1bdfc4…, এটিই ট্যাগ। শেষ ধাপে নতুন একটি গোপন প্রিফিক্স বসেছে, তাই length extension-এর পথ বন্ধ। - মিলিয়ে দেখা। হাতে করা হিসাব আর Python-এর
hmac.new-এর ফল হুবহু এক। এটি শেখার জন্য দেখা। বাস্তবে HMAC নিজে লিখবেন না, library ব্যবহার করুন।
Constant-time তুলনা কেন দরকার
ট্যাগ বানানো হলো অর্ধেক কাজ। মিলিয়ে দেখার কাজটিও নিরাপদে করতে হয়। সাধারণ == বাঁ থেকে একে একে অক্ষর মেলায়, আর প্রথম অমিলেই থেমে যায়। তাই প্রথম অক্ষর ভুল হলে উত্তর সামান্য দ্রুত আসে, শেষ অক্ষর ভুল হলে সামান্য দেরিতে। এই সময়ের পার্থক্যই একটি গোপন তথ্য ফাঁস করে, যার নাম timing attack.
ব্যাংকের লকারের দারোয়ান ভাবুন, যিনি ৬ অঙ্কের কোড মেলান। প্রথম অঙ্ক ভুল হলেই সঙ্গে সঙ্গে “না” বলেন। দ্বিতীয় অঙ্ক ভুল হলে একটু ভেবে “না” বলেন। কেউ ঘড়ি ধরে বসে থাকলে তিনি বুঝে যাবেন কয়টি অঙ্ক মিলেছে। ভালো দারোয়ান সব অঙ্ক দেখার পরই উত্তর দেন, সব ক্ষেত্রে একই সময়ে।
Constant-time comparison ঠিক এই কাজ করে: অমিল পেলেও শেষ পর্যন্ত সব byte দেখে, তারপর উত্তর দেয়। Python-এ hmac.compare_digest, Node.js-এ crypto.timingSafeEqual, Go-তে subtle.ConstantTimeCompare, PHP-তে hash_equals. নিচের কোডে একটি কাল্পনিক API অনুরোধের পুরো যাচাই দেখুন। Key নকল, বার্তা নকল, ঘড়িও সিমুলেট করা, কোনো network নেই।
import hashlib, hmac
KEY = b"example-webhook-key" # fake key, for this demo only
MAX_AGE = 300 # seconds
NOW = 1767225700 # simulated clock
def sign(ts, body, key=KEY):
return hmac.new(key, f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
def verify(ts, body, tag):
if abs(NOW - ts) > MAX_AGE:
return "REJECT: too old"
if not hmac.compare_digest(sign(ts, body), tag):
return "REJECT: bad tag"
return "ACCEPT"
def early_exit_steps(x, y): # how many characters a naive compare reads
for i, (p, q) in enumerate(zip(x, y), 1):
if p != q:
return i
return len(x)
ts = 1767225600
body = b'{"order":1042,"amount":500}'
tag = sign(ts, body)
print("tag :", tag[:32] + "...")
print("original :", verify(ts, body, tag))
print("amount edit:", verify(ts, body.replace(b"500", b"900"), tag))
print("wrong key :", verify(ts, body, sign(ts, body, b"other-key")))
print("new ts, old tag:", verify(NOW, body, tag))
print("old message:", verify(ts - 3600, body, sign(ts - 3600, body)))
flip = lambda c: "1" if c == "0" else "0"
print("naive steps, 1st char wrong:", early_exit_steps(tag, flip(tag[0]) + tag[1:]))
print("naive steps, last char wrong:", early_exit_steps(tag, tag[:-1] + flip(tag[-1])))আসল বার্তা ACCEPT. পরিমাণ বদলালে বা ভুল key হলে REJECT: bad tag. শুধু সময় বদলে পুরোনো ট্যাগ চালানোর চেষ্টাও REJECT: bad tag, কারণ timestamp-ও সই করা অংশের ভেতরে আছে। এক ঘণ্টা আগের বৈধ বার্তা REJECT: too old: এই পুরোনো বার্তা আবার পাঠানোকে বলে replay attack. শেষ দুই লাইনে দেখুন, সাধারণ তুলনা প্রথম অক্ষর ভুল হলে 1টি আর শেষ অক্ষর ভুল হলে 64টি অক্ষর পড়ে। এই পার্থক্যই সময়ে ধরা পড়ার ঝুঁকি। compare_digest-এ এই পার্থক্য তথ্য ফাঁস করে না।
একটি signed webhook যাচাই, ধাপে ধাপে
পেমেন্ট গেটওয়ে যখন আপনার সার্ভারে “টাকা পেয়েছি” জানায়, সেটি একটি webhook. URL জানলে যে কেউ এই ঠিকানায় অনুরোধ পাঠাতে পারেন। তাই HMAC ট্যাগ ছাড়া বার্তা বিশ্বাস করা চলে না। ওপরের কোডের মানগুলোই নিচে ধাপে ধাপে দেখুন। ঝুঁকিটির STRIDE-চিত্র দেখেছেন পর্ব ০৪-এ।
- গেটওয়ে
signট্যাগ বানায় - আপনার সার্ভার
raw bodyঅপরিবর্তিত byte - যাচাই
age + tagসময় আর ট্যাগ - প্রক্রিয়া
order 1042একবারই
ts = 1767225600
body = {"order":1042,
"amount":500}
tag = HMAC(key, ts + "." + body)
= bb8f652c8efa40d7...
header ts : 1767225600
header tag : bb8f652c8efa40d7...
raw body : 27 bytes, unchanged
clock NOW : 1767225700
|1767225700 - 1767225600|
= 100 seconds
100 <= 300 pass
computed = HMAC(key,
"1767225600." + raw body)
= bb8f652c8efa40d7...
compare_digest(computed, tag)
bb8f652c8efa40d7...
bb8f652c8efa40d7...
result: True
order 1042 seen before? no
amount 500 mark as paid
store event id, reply 200
computed = 01775b873b06c071...
received = bb8f652c8efa40d7...
result: False
reject, log, do not process
- প্রেরক সই করে। গেটওয়ে timestamp আর body একসঙ্গে জুড়ে HMAC বানায়, ট্যাগ পাঠায় HTTP header-এ। Timestamp সই না করলে সেটি বদলানো যেত।
- Raw body ধরে রাখা। সার্ভার body-র অপরিবর্তিত byte রাখে। আগে JSON parse করে আবার string বানালে space বা ক্রম বদলে ট্যাগ মিলবে না। Header-এর ট্যাগ বৈধ বলে এখনো ধরা হয়নি।
- সময় যাচাই। পার্থক্য ১০০ সেকেন্ড, সীমা ৩০০. পাস। এই ধাপ পুরোনো বার্তা আবার পাঠানো ঠেকায়। সীমা কত হবে, তা ঘড়ির ব্যবধান দেখে ঠিক করুন।
- নিজে হিসাব। প্রেরকের পাঠানো ট্যাগ বিশ্বাস না করে সার্ভার নিজের key দিয়ে একই বিন্যাসে ট্যাগ বানায়। ফল
bb8f652c…. - Constant-time তুলনা। দুটি মান মিলল। ভুল ট্যাগের ক্ষেত্রেও উত্তরের সময় থেকে কত অক্ষর মিলেছে তা বোঝা যায় না।
- এবার প্রক্রিয়া। যাচাইয়ের পরই JSON পড়া হয়। Event id রেখে দেওয়া হয়, যাতে সীমার ভেতরে একই বার্তা দুবার এলে অর্ডার দুবার প্রক্রিয়া না হয়।
- বদলানো বার্তা। কেউ পথে amount 900 করলে হিসাব করা ট্যাগ হয়
01775b87…, যা পাঠানো ট্যাগের সঙ্গে মেলে না। বার্তা বাতিল, ঘটনা log-এ, অর্ডার অপরিবর্তিত। লক্ষ করুন, ট্যাগ বদলের জন্য key লাগত, যা আক্রমণকারীর নেই।
| কী ভুল হতে পারে | প্রতিরোধ | যেভাবে টের পাবেন | সাড়া |
|---|---|---|---|
| জাল বার্তা | HMAC যাচাই বাধ্যতামূলক | “bad tag” সংখ্যায় হঠাৎ লাফ | উৎস দেখা, rate limit, তদন্ত |
| পুরোনো বার্তা আবার পাঠানো | Timestamp আর event id | একই id বারবার, “too old” বৃদ্ধি | Log রাখা, অর্ডার যাচাই |
| Key ফাঁস | Secret vault, ন্যূনতম access | গোপন স্ক্যান, অস্বাভাবিক বৈধ বার্তা | Key বদল, পুরোনো key বাতিল, লেনদেন মিলিয়ে দেখা |
| Key বদলের সময় বিভ্রাট | অল্প সময় দুটি key গ্রহণ | বদলের পর ব্যর্থতা বাড়া | পুরোনো key নির্দিষ্ট সময়ে বন্ধ |
এখানে দেখানো ট্যাগ ও header-এর বিন্যাস একটি সাধারণ ধরন, নির্দিষ্ট কোনো সেবার নয়। আপনি যে সেবা ব্যবহার করছেন, তার নথিতে দেওয়া বিন্যাসই মেনে চলুন। আর মনে রাখুন, এই পরীক্ষাগুলো নিজের সিস্টেমে বা লিখিত অনুমতি নিয়ে করবেন। অনুমতি ছাড়া অন্যের API-তে পরীক্ষা অপরাধ হতে পারে; দেখুন পর্ব ০৬।
৮ · Download যাচাই — checksum, নিরাপত্তা আর sha256sum
আগে একটি পার্থক্য পরিষ্কার করা যাক। Checksum আর cryptographic hash এক নয়। CRC বা কার্ড নম্বরের শেষ অঙ্কের মতো checksum তৈরি হয়েছে দুর্ঘটনা ধরতে: নেটওয়ার্কের শব্দ, disk-এর নষ্ট bit, টাইপের ভুল। এগুলো দ্রুত, কিন্তু কেউ ইচ্ছা করে ফাইল বদলালে একই checksum সহজে মিলিয়ে দিতে পারেন। তাই “বৃষ্টিতে ভিজল কি না” দেখতে checksum চলে, কিন্তু “কেউ বদলাল কি না” দেখতে cryptographic hash লাগে।
নিচের কোড নিজে দুটি ফাইল বানায়, একটি digest-তালিকা তৈরি করে, তারপর একটি অক্ষর বদলে আবার যাচাই করে। সব কাজ একটি অস্থায়ী folder-এ, আপনার বা কারও আসল ফাইলে নয়।
set -eu
cd "$(mktemp -d)"
printf 'release 1.0: payroll tool\n' > app-1.0.tar
printf 'install notes\n' > notes.txt
sha256sum app-1.0.tar notes.txt > SHA256SUMS
cut -c1-16 SHA256SUMS
echo "--- check 1: files untouched"
sha256sum -c SHA256SUMS
echo "--- check 2: one character changed, same size"
wc -c < app-1.0.tar
printf 'release 1.0: paYroll tool\n' > app-1.0.tar
wc -c < app-1.0.tar
if sha256sum -c SHA256SUMS 2>&1; then
echo "all good"
else
echo "exit code $?: do not install this file"
fiপ্রথম যাচাইয়ে দুটি ফাইলই OK. দ্বিতীয়বার একটি অক্ষর বড় হাতের হলো, ফাইলের আকার আগের মতোই 26, তবু app-1.0.tar: FAILED এসেছে, সঙ্গে WARNING: 1 computed checksum did NOT match. Exit code 1 হয়, তাই script-এ এই ফল দেখে install থামানো যায়। ফাইলের আকার মিলিয়ে দেখা যে যথেষ্ট নয়, এটিই তার প্রমাণ।
নিজের কম্পিউটারে একটি আসল download যাচাই
নিজের ডাউনলোড করা ফাইলে নিচের কমান্ড চালান। ফাইলের নাম উদাহরণ মাত্র।
sha256sum app-1.0.tar
# with a published list in the same folder:
sha256sum -c SHA256SUMSGet-FileHash .\app-1.0.tar -Algorithm SHA256
# or in Command Prompt:
certutil -hashfile app-1.0.tar SHA256shasum -a 256 app-1.0.tar
shasum -a 256 -c SHA256SUMSসতর্কতা: digest আর ফাইল একই সাইট থেকে এলে যিনি ফাইল বদলাতে পারেন, তিনি digest-ও বদলাতে পারেন। তখন যাচাই শুধু দুর্ঘটনা ধরে। ভালো প্রকল্প তাই digest-তালিকায় digital signature দেয়, যা আপনি public key দিয়ে যাচাই করেন। সেই পথ পর্ব ১১-তে দেখবেন।
৯ · Content addressing আর Merkle tree
Hash শুধু যাচাইয়ের যন্ত্র নয়, সিস্টেম গড়ারও উপাদান। ধরুন প্রতিটি ফাইলের নাম হবে তার digest. একে বলে content addressing. একই বিষয়বস্তু তাহলে একবারই জমা থাকে, আর নাম দেখেই যাচাই করা যায় ফাইলটি বদলেছে কি না।
Git এভাবেই কাজ করে। hello লেখা ৬ byte-এর একটি ফাইলের জন্য Git “blob 6”, একটি শূন্য byte আর ফাইলের বিষয়বস্তু জুড়ে SHA-1 নেয়, আর ফল আসে ce013625… দিয়ে শুরু। মানটি আমরা Python-এর hashlib-এ এই সূত্রেই হিসাব করেছি। প্রতিটি commit আবার আগের commit-এর digest ধরে রাখে, তাই ইতিহাসের একটি অক্ষর বদলালে পরের সব id বদলে যায়। বিস্তারিত দেখুন Git internals পর্বে।
কিন্তু লাখ লাখ ফাইলের কোনটি বদলেছে, তা জানতে কি সব মেলাতে হবে? না। ১৯৭০-এর দশকের শেষে Ralph Merkle-এর দেওয়া Merkle tree এই সমস্যার উত্তর। পাতায় থাকে ডেটার hash, ওপরে জোড়ায় জোড়ায় hash-এর hash, আর চূড়ায় একটি Merkle root. শুধু root নিরাপদে রাখলেই পুরো তালিকা পাহারায় থাকে।
A:100 B:250 C:75 D:500 263c67 d595ae b01ab0 d532f9
410a7d a1258c / \ / \ 263c67 d595ae b01ab0 d532f9 A:100 B:250 C:75 D:500
root 901a8d
/ \
410a7d a1258c
/ \ / \
263c67 d595ae b01ab0 d532f9
A:100 B:250 C:75 D:500
root 901a8d
/ \
410a7d a1258c
/ \ / \
263c67 d595ae 5dca72 d532f9
A:100 B:250 C:95 D:500
root ed188f / \ 410a7d b4f0cd / \ / \ 263c67 d595ae 5dca72 d532f9 saved root: 901a8d mismatch
root 901a8d / ed188f differ
left 410a7d / 410a7d same, skip
right a1258c / b4f0cd differ
leaf 3 b01ab0 / 5dca72 differ: C
leaf 4 d532f9 / d532f9 same
have : A:100, saved root
given : d595ae, a1258c
H(A) = 263c67
H(263c67+d595ae)= 410a7d
H(410a7d+a1258c)= 901a8d match
- পাতা বানানো। চারটি কাল্পনিক হিসাব-লাইনের প্রতিটির SHA-256 নেওয়া হলো। এখানে প্রথম ৬ hex দেখানো হচ্ছে, আসল মান ৬৪ hex.
- জোড়ায় জোড়ায়। A আর B-র digest জুড়ে hash করে
410a7d, C আর D থেকেa1258c. - Root. দুটি মাঝের মান জুড়ে hash করে root
901a8d. এই একটি মান আলাদা নিরাপদ জায়গায় রাখা হলো, যেমন সই করা রেকর্ডে। - কেউ একটি পাতা বদলাল। C:75 হলো C:95, আর সেই পাতার hash
b01ab0থেকে হলো5dca72. ওপরের মানগুলো এখনো পুরোনো, মানে নতুন করে হিসাব হয়নি। - আবার হিসাব। নতুন করে হিসাব করলে মাঝের মান হয়
b4f0cd, root হয়ed188f. রাখা root901a8d-এর সঙ্গে মেলে না, তাই বদল ধরা পড়ল। - ওপর থেকে নিচে। বাঁ শাখা মিলে গেল, তাই A আর B দেখার দরকারই নেই। ডান শাখায় নেমে C ধরা পড়ল। চারটি পাতার গাছে দুই স্তর নামতে হলো। n পাতায় প্রায় log₂ n স্তর।
- প্রমাণ। পুরোনো গাছে A আছে কি না জানতে পুরো তালিকা লাগে না, দুটি পাশের hash যথেষ্ট। দশ লাখ পাতার গাছে লাগে মাত্র প্রায় ২০টি hash. একে বলে inclusion proof.
আরও গভীরে: Certificate Transparency (RFC 6962, ২০১৩) Merkle tree দিয়ে জনসমক্ষে certificate-এর log রাখে, আর পাতা ও ভেতরের node-এর hash-এ আলাদা প্রিফিক্স (0x00, 0x01) ব্যবহার করে, যাতে দুটি গুলিয়ে না যায়; ওপরের সরল গাছে সেটি বাদ দেওয়া হয়েছে। HMAC-এর নিরাপত্তা-প্রমাণ hash-এর collision resistance-এর ওপর নির্ভর করে না, compression function-এর pseudorandom আচরণের ওপর দাঁড়ায় (Bellare, ২০০৬ সালের প্রমাণ), তাই HMAC-SHA1 এখনো সরাসরি ভাঙা নয়। তবু নতুন নকশায় HMAC-SHA256 নিন।
পরীক্ষা ও ইন্টারভিউতে:
- Collision আর second preimage-এর পার্থক্য? Collision-এ দুটি ইনপুটই নিজে বাছা যায়, খরচ প্রায় 2n/2; second preimage-এ একটি ইনপুট আগে থেকে নির্দিষ্ট, খরচ প্রায় 2n.
- SHA-256-এর collision নিরাপত্তা কত bit? ১২৮ bit, birthday bound-এর কারণে।
hash(key + msg)কেন ভুল? Merkle-Damgård hash-এ length extension সম্ভব; তাই HMAC ব্যবহার করতে হয়।- MAC আর signature-এর মূল পার্থক্য? MAC-এ ভাগ করা key, তাই non-repudiation নেই; signature-এ private key, তাই আছে।
- ট্যাগ তুলনায়
==কেন নয়? প্রথম অমিলে থেমে সময়ের মাধ্যমে তথ্য ফাঁস করতে পারে; constant-time ফাংশন লাগে।
১০ · AI-এর যুগে hash আর MAC
AI কয়েক সেকেন্ডে webhook যাচাইয়ের কোড লিখে দিতে পারে, আর সেটি চলেও। সমস্যা হলো, ভুল crypto কোডও পরীক্ষায় দিব্যি চলে। ট্যাগ মেলে, বার্তা পাস হয়, শুধু নিরাপত্তা থাকে না। তাই নিচের ভুলগুলো চোখে রাখুন।
| AI যা করতে পারে | কেন সমস্যা | যা করবেন |
|---|---|---|
| “অখণ্ডতার জন্য MD5” বা SHA-1 সুপারিশ | পুরোনো উদাহরণের ছাপ | SHA-256 বা তার বেশি চান |
sha256(secret + body) লেখা | Length extension-এর ঝুঁকি | HMAC চান |
ট্যাগ তুলনায় == | Timing ফাঁসের ঝুঁকি | Constant-time ফাংশন বসান |
| JSON parse করে আবার string বানিয়ে যাচাই | Byte বদলে যায়, যাচাই ব্যর্থ হয় | Raw body-তে যাচাই করুন |
| Timestamp বাদ দেওয়া | পুরোনো বার্তা আবার চলে | Timestamp সই করে সীমা রাখুন |
Error ঠিক করতে যাচাই বন্ধ করা, যেমন verify=False | রক্ষাকবচটাই উঠে যায় | কারণ খুঁজুন, যাচাই বন্ধ নয় |
| বানানো RFC, CVE বা standard নম্বর দেওয়া | যাচাই না করলে ভুল ছড়ায় | মূল নথি নিজে খুলে পড়ুন |
কোড রিভিউ করাতে নিচের কাঠামো ব্যবহার করুন। আসল key বা ব্যক্তিগত তথ্য কখনো পেস্ট করবেন না; key-র জায়গায় example-token বসান।
AI-এর লেখা hash বা MAC কোড চালুর আগে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন। এগুলো নিরাপত্তাকে সিস্টেম হিসেবে দেখার অভ্যাস গড়ে।
প্র ০১SHA-1 ভাঙা বলা হচ্ছে, তাহলে Git এতদিন SHA-1 নিয়ে চলল কীভাবে? এখান থেকে ঝুঁকি বিচারের কী শেখার আছে?
Git-এর শুরুতে SHA-1-এর মূল কাজ ছিল বিষয়বস্তুর ঠিকানা তৈরি আর দুর্ঘটনাজনিত নষ্ট ধরা। নিরাপত্তার মূল ভরসা হিসেবে এটি ভাবা হয়নি। তবে বাস্তবে signed tag আর signed commit এই digest-এর ওপরই দাঁড়িয়ে থাকে, কারণ signature আসলে digest-এ হয়। তাই SHAttered প্রকাশের পর প্রশ্নটি গুরুত্ব পায়।
Git-এর সাড়া ছিল দুই ধাপে। প্রথমত, ২০১৭ সালে Git ২.১৩ থেকে collision-শনাক্তকারী SHA-1 সংস্করণ ডিফল্ট হয়। এটি SHAttered-ধরনের collision-এর চিহ্ন দেখলে আলাদা আচরণ করে, ফলে পরিচিত ধরনের হামলা ধরা পড়ে। দ্বিতীয়ত, দীর্ঘমেয়াদি সমাধান হিসেবে SHA-256 object format যোগ হয়েছে। প্রকাশিত পরিকল্পনা অনুযায়ী ভবিষ্যৎ বড় সংস্করণে একে ডিফল্ট করার কথা, যদিও বাস্তবে এই বদল ধীর, কারণ সংগ্রহস্থল, CI, হোস্টিং আর ৪০-অক্ষরের id ধরে রাখা নানা টুল একসঙ্গে বদলাতে হয়। সর্বশেষ অবস্থা Git-এর নিজের নথিতে দেখে নিন।
এখান থেকে তিনটি শিক্ষা আসে। এক, ঝুঁকি বিচার হয় ব্যবহারের প্রেক্ষিতে: collision হামলায় আক্রমণকারীকে দুটি ফাইল আগে থেকে বানিয়ে একটি সংগ্রহস্থলে ঢোকাতে হয়, যা আগে থেকে থাকা ফাইলের নকল বানানোর চেয়ে আলাদা সমস্যা। দুই, দুর্বলতা জানার পর শনাক্তকরণ দিয়ে সময় কেনা যায়, তারপর ধীরে স্থানান্তর করা যায়। তিন, hash বদলানো একটি বিরাট প্রকৌশল-কাজ, তাই নকশায় শুরু থেকে অ্যালগরিদম বদলের পথ রাখতে হয়। একে বলে crypto agility.
মূল উপলব্ধি: “ভাঙা” শব্দটি একা কিছু বলে না। প্রশ্ন করুন, কোন গুণ ভেঙেছে, আপনার ব্যবহারে সেই গুণ কতটা দরকার, আর বদলাতে কত সময় লাগবে।
প্র ০২একটি সাইট ফাইলের পাশেই SHA-256 digest দিয়ে রেখেছে। আপনি মিলিয়ে দেখলেন, মিলেছে। ফাইলটি কি তাহলে নিরাপদ?
পুরোপুরি নয়। মিলে যাওয়া প্রমাণ করে শুধু একটি কথা: আপনার হাতের ফাইল আর ওই পাতায় লেখা digest পরস্পরের সঙ্গে মেলে। ডাউনলোড পথে নষ্ট হয়নি, বা কোনো mirror ভিন্ন ফাইল দেয়নি, এটুকু ধরা পড়ে। কিন্তু digest-টি কে লিখেছেন, সেই প্রশ্নের উত্তর এখানে নেই।
যদি কেউ সাইটটিই দখল করে ফেলেন, তিনি ফাইল আর digest দুটিই বদলাতে পারেন। ২০১৬ সালে Linux Mint প্রকল্প নিজেই জানিয়েছিল, তাদের website-এ অনুপ্রবেশের পর একটি পরিবর্তিত ISO-র link দেখানো হয়েছিল। এই ধরনের ঘটনা দেখায়, একই পথে আসা ফাইল ও digest-এর ওপর একসঙ্গে ভরসা করা দুর্বল নকশা।
শক্ত সমাধান হলো বিশ্বাসের উৎস আলাদা করা। প্রকল্প তার digest-তালিকায় digital signature দেয়, আর আগে থেকে জানা public key দিয়ে আপনি সেটি যাচাই করেন। তখন সাইট দখল হলেও private key না থাকলে বৈধ সই বানানো যায় না। মনে রাখবেন, public key-টিও ভরসাযোগ্য পথে পেতে হয়, নইলে সমস্যা শুধু এক ধাপ সরে যায়। এই বিশ্বাসের শিকলের কথাই পর্ব ১১-তে আসবে।
আরও একটি সীমা আছে। Digest বলে “ফাইলটি আসল প্রকাশিত ফাইলই”, কিন্তু বলে না “ফাইলটি ভালো”। প্রকাশকের build ব্যবস্থায়ই ক্ষতিকর কোড ঢুকলে digest আর signature দুটিই ঠিক থাকবে। ২০২০ সালের SolarWinds ঘটনার প্রকাশিত প্রতিবেদনে এমন build-পর্যায়ের অনুপ্রবেশের কথাই আছে। সেই ঝুঁকির আলোচনা পর্ব ৪৮-এ, supply chain নিরাপত্তায়।
মূল উপলব্ধি: যাচাইয়ের আগে জিজ্ঞেস করুন, এই ফলাফল আমি কাকে বিশ্বাস করে পেলাম? Hash অখণ্ডতা দেখায়, উৎস নয়; signature উৎস দেখায়, সদিচ্ছা নয়।
প্র ০৩SHA-256 নিরাপদ, তবু SHA-256(key + message) ভুল কেন? “ভালো উপাদান” দিয়ে বানানো জিনিস কি নিরাপদ হয় না?
Hash function-এর প্রতিশ্রুতি হলো তিন প্রতিরোধ। “এটি একটি নিরাপদ MAC”, এমন প্রতিশ্রুতি SHA-256 কখনো দেয়নি। MAC-এর লক্ষ্য আলাদা: key না জেনে কেউ নতুন বার্তার বৈধ ট্যাগ বানাতে পারবেন না। Merkle-Damgård গঠনে digest-ই পুরো ভেতরের অবস্থা, তাই এই লক্ষ্য এই সরল নকশায় পূরণ হয় না, যদিও collision এখানে একবারও লাগে না।
২০০৯ সালের Flickr-সংক্রান্ত advisory এই ফাঁকটির বিখ্যাত উদাহরণ। Secret আর parameter জুড়ে MD5 নেওয়া হতো। লক্ষ করুন, সমস্যাটি MD5-এর collision-এর জন্য নয়, গঠনের জন্য। একই নকশা SHA-256-এও একইভাবে দুর্বল। অর্থাৎ hash বদলালে এই ভুল সারে না।
তাহলে উল্টো ক্রমে, hash(message + key), কি ঠিক? এটিও সন্তোষজনক নয়, কারণ এর নিরাপত্তা hash-এর collision resistance-এর ওপর নির্ভর করতে পারে। আর সামনে-পেছনে দুদিকে key বসানোর মতো ঘরোয়া সমাধানও প্রমাণহীন। HMAC-এর মূল্য এখানেই: এটি শুধু কাজ করে না, এর জন্য আনুষ্ঠানিক নিরাপত্তা-যুক্তি আছে, আর দুই দশকের বেশি সময় ধরে প্রকাশ্যে পরীক্ষিত। আধুনিক বিকল্পও আছে, যেমন SHA-3-ভিত্তিক KMAC (NIST SP 800-185), বা কেবল AEAD ব্যবহার করে আলাদা MAC এড়ানো।
একই ধরনের শিক্ষা পাবেন encryption-এ: AES নিরাপদ, কিন্তু ভুল mode বা nonce-এ পুনরাবৃত্তি পুরো নিরাপত্তা নষ্ট করে। Kerckhoffs-এর নীতি আর পর্ব ০৩-এর open design মিলে এখানে একটি অভ্যাস তৈরি হয়: প্রকাশ্যে পরীক্ষিত গঠন ব্যবহার করুন, শুধু প্রকাশ্যে পরীক্ষিত উপাদান নয়। পর্ব ১৪-তে এমন ভুলগুলো একসঙ্গে দেখবেন।
মূল উপলব্ধি: Cryptography-তে উপাদান ঠিক থাকলেই ফল ঠিক হয় না। প্রতিটি গঠনের নিজস্ব লক্ষ্য ও প্রমাণ লাগে। নিজে জোড়া না দিয়ে স্বীকৃত গঠন নিন।
প্র ০৪Network-এর দেরির ভেতরে কয়েক ন্যানোসেকেন্ডের পার্থক্য কি আদৌ মাপা যায়? তাহলে constant-time তুলনা নিয়ে এত কড়াকড়ি কেন?
সহজ বুদ্ধিতে মনে হয়, ইন্টারনেটের কয়েক মিলিসেকেন্ডের ওঠানামায় এত ছোট পার্থক্য হারিয়ে যাবে। কিন্তু পরিসংখ্যান এখানে আক্রমণকারীর পক্ষে কাজ করে। একই অনুরোধ অনেকবার পাঠিয়ে গড় বা নিচের দিকের মান দেখলে এলোমেলো শব্দ কমে আসে, আর স্থির পার্থক্য ভেসে ওঠে। Crosby, Wallach ও Riedi-র গবেষণা “Opportunities and Limits of Remote Timing Attacks” (ACM TISSEC, ২০০৯) দেখিয়েছে, দূর থেকে সময় মাপার ক্ষমতা অনেকের ধারণার চেয়ে ভালো হতে পারে, যদিও সীমাও আছে। পরিমাণ পরিবেশভেদে ভিন্ন।
একটি শর্ত আছে: হামলা তখনই বিপজ্জনক, যখন সিস্টেম একই গোপন ট্যাগের সঙ্গে বহু অনুমান তুলনা করতে দেয়। সেখানে এক এক অক্ষর করে সঠিক মানের কাছে যাওয়ার সুযোগ তৈরি হয়। রক্ষকের হাতে তাই একাধিক স্তর আছে: constant-time তুলনা, rate limit, আর সন্দেহজনক ব্যর্থতার সতর্কতা। এটি defense in depth-এরই একটি উদাহরণ।
তাহলে কেন কড়াকড়ি, যখন প্রতিটি পরিবেশে হামলা সফল নাও হতে পারে? কারণ খরচের হিসাব একতরফা। সঠিক ফাংশন ডাকতে এক লাইন লাগে, গতি প্রায় একই থাকে। অন্যদিকে, কোন পরিবেশে timing ধরা যাবে, তা আগে থেকে প্রমাণ করা কঠিন। একই সার্ভারে থাকা অন্য tenant বা cloud-এর ভেতর থেকে পরিমাপ অনেক সূক্ষ্ম হতে পারে। ২০০৯ সালে একটি পরিচিত crypto library-র HMAC যাচাইয়ে এমন ত্রুটির খবর প্রকাশ্যে আলোচিত হয়েছিল, যা দেখায় অভিজ্ঞ দলও ভুল করে।
নিজে constant-time ফাংশন লেখা ঝুঁকিপূর্ণ, কারণ compiler অপ্রয়োজনীয় মনে করে লুপ ছোট করে দিতে পারে, আর ভাষার runtime-ও প্রভাব ফেলে। তাই library-র পরীক্ষিত ফাংশনই ব্যবহার করুন।
মূল উপলব্ধি: যেখানে সুরক্ষা প্রায় বিনামূল্যে, আর ঝুঁকি আগে থেকে মাপা কঠিন, সেখানে তর্ক না করে সুরক্ষাটি নিয়ে নেওয়াই ভালো প্রকৌশল।
প্র ০৫MAC দ্রুত আর সরল, signature ধীর আর জটিল। তাহলে কখন কোনটি বেছে নেবেন?
সিদ্ধান্ত নির্ভর করে একটি প্রশ্নে: যাচাইকারী কি একই সঙ্গে সইকারী হতে পারবেন? MAC-এ দুই পক্ষের হাতে একই key, তাই যিনি যাচাই করতে পারেন, তিনি নতুন ট্যাগও বানাতে পারেন। দুটি পক্ষ পরস্পরকে বিশ্বাস করলে, যেমন আপনার দুটি সার্ভিস বা আপনি ও একটি নির্দিষ্ট পেমেন্ট সেবা, এটি সমস্যা নয়।
যাচাইকারী অনেকজন হলে ছবি বদলে যায়। ধরুন একটি সফটওয়্যার আপডেট লাখো ডিভাইস যাচাই করবে। MAC ব্যবহার করলে সব ডিভাইসে একই key রাখতে হবে, আর একটি ডিভাইস থেকে key বের করলেই জাল আপডেট বানানো যাবে। Signature-এ ডিভাইসে থাকে শুধু public key, যা ফাঁস হলেও ক্ষতি নেই। তাই সফটওয়্যার আপডেট, certificate আর আইনি নথিতে signature ব্যবহার হয়।
Key-র সংখ্যাও একটি বিষয়। n জন প্রত্যেকের সঙ্গে আলাদা গোপন key রাখলে লাগে n(n−1)/2টি key: ১০০০ জনে ৪,৯৯,৫০০টি। Public key পদ্ধতিতে প্রত্যেকের একটি জোড়াই যথেষ্ট। অন্যদিকে প্রতি সেকেন্ডে হাজার হাজার ছোট বার্তায় HMAC অনেক সস্তা, তাই TLS-এর মতো protocol প্রথমে public key দিয়ে পরিচয় ও key ঠিক করে, তারপর দ্রুত symmetric পদ্ধতিতে চলে।
দাবি প্রমাণের প্রশ্নও আছে। বিবাদে তৃতীয় পক্ষকে দেখাতে হলে MAC যথেষ্ট নয়, কারণ দুই পক্ষই ট্যাগ বানাতে পারতেন। তবে signature থাকলেই non-repudiation নিশ্চিত হয় না; private key ঠিকমতো রক্ষিত ছিল কি না, সেটিও প্রমাণের বিষয়। একটি বাস্তব উদাহরণ JWT: HS256 হলো HMAC, RS256 বা ES256 হলো signature. একটিমাত্র সার্ভিসের জন্য HS256 ঠিক আছে, কিন্তু অনেক সার্ভিস token যাচাই করলে key ভাগ করার ঝুঁকি বাড়ে। এই আলোচনা পর্ব ১৭-তে আবার আসবে।
মূল উপলব্ধি: প্রশ্নটি “কোনটি শক্তিশালী” নয়, প্রশ্নটি “কে কাকে বিশ্বাস করে, আর কার হাতে কী থাকা উচিত”।
অনুশীলন
কমান্ডগুলো শুধু নিজের ফাইলে চালান। আগে নিজে চেষ্টা করুন, তারপর উত্তর মিলিয়ে নিন।
-
Numerical: একটি সিস্টেম প্রতিটি ফাইলের id হিসেবে SHA-256-এর প্রথম ৪৮ bit রাখে, আর সিস্টেমে আছে 107টি ফাইল। (ক) অন্তত একটি collision-এর আনুমানিক সম্ভাবনা কত? (খ) কতটি ফাইলে সম্ভাবনা ৫০% হবে?
(ক) N = 248, তাই 2N = 249 ≈ 5.63 × 1014. k² = 1014. সূচক k²/(2N) ≈ 0.1776. p ≈ 1 − e−0.1776 ≈ ০.১৬৩, মানে প্রায় ১৬%.
(খ) k ≈ 1.1774 × 224 = 1.1774 × 16,777,216 ≈ 1.98 × 107, মানে প্রায় দুই কোটি ফাইল। শিক্ষা: digest ছেঁটে ফেলার সময় birthday হিসাব করুন। পর্যাপ্ত না হলে পুরো digest রাখুন।
-
নকশা সমালোচনা: একটি কাল্পনিক API অনুরোধে header থাকে
X-Sig: md5(secret + body). সার্ভার==দিয়ে মেলায়। Timestamp নেই। Secret লেখা আছে কোডে। এই নকশার ভুলগুলো বলুন, আর উন্নত নকশা লিখুন।- MD5: অবসরে যাওয়া hash. SHA-256 নিন।
secret + body: Length extension-এর ঝুঁকি। HMAC নিন।==: Timing ফাঁসের ঝুঁকি। Constant-time ফাংশন নিন।- Timestamp নেই: পুরোনো অনুরোধ আবার পাঠানো যায়। Timestamp ও request id সই করা অংশে রাখুন, সময়-সীমা বসান, id মনে রাখুন।
- Secret কোডে: Repository থেকে ফাঁস হতে পারে। Secret store থেকে আনুন, নিয়মিত বদলান, দুই key-র সময়কাল রাখুন।
- শনাক্তকরণ নেই: ব্যর্থ যাচাইয়ের log ও সতর্কতা যোগ করুন, তবে log-এ secret নয়।
উন্নত নকশা:
X-TimestampআরX-Sig: HMAC-SHA256(key, method + path + timestamp + raw body). Method আর path সই না করলে একই ট্যাগ অন্য endpoint-এ চালানো যেতে পারে। -
ফল অনুমান করুন: Linux-এ একটি খালি folder-এ নিচের লাইনগুলো পরপর চালানো হলো। চালানোর আগেই বলুন, দুটি
sha256sum -cকী দেখাবে, আর শেষে exit code কত হবে।printf 'ok\n' > f.txt sha256sum f.txt > S printf 'x\n' >> f.txt sha256sum -c S printf 'ok\n' > f.txt sha256sum -c Sপ্রথম যাচাইয়ে ফাইলে বাড়তি লাইন আছে, তাই আসে
f.txt: FAILEDআরWARNING: 1 computed checksum did NOT match; সেই কমান্ডের exit code 1.এরপর
>ফাইলটি আগের হুবহু বিষয়বস্তুতে ফিরিয়ে আনে। Hash শুধু বিষয়বস্তু দেখে, ফাইলের সময় দেখে না। তাই দ্বিতীয় যাচাইয়েf.txt: OK, আর শেষ exit code 0. -
নিজের কম্পিউটারে: আপনার ব্যবহৃত কোনো ওপেন-সোর্স সফটওয়্যারের অফিসিয়াল সাইট থেকে একটি ফাইল নামান। Digest হিসাব করে প্রকাশিত মানের সঙ্গে মেলান। তারপর লিখুন: digest কোন পথে পেলেন, প্রকল্পটি signature দেয় কি না, আর এই যাচাই কী প্রমাণ করল, কী করল না।
Linux-এ
sha256sum, Windows-এGet-FileHash, Mac-এshasum -a 256. দুটি মান অক্ষরে অক্ষরে মিললে প্রমাণ হলো, ফাইলটি পাতায় লেখা digest-এর সঙ্গে মেলে। ডাউনলোডে নষ্ট হয়নি।যা প্রমাণ হয়নি: digest যদি একই সাইট থেকে আসে, সাইট দখলের ক্ষেত্রে এটি কিছু বলে না। প্রকল্পটি signature দিলে সেটিও যাচাই করা ভালো। আর signature মিললেও ফাইলটি ক্ষতিকর নয়, এমন প্রমাণ হয় না। মান না মিললে ফাইল চালাবেন না, আবার অফিসিয়াল উৎস থেকে নামান, তবু না মিললে প্রকল্পকে জানান।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমSigned webhook পেলে সার্ভারের কাজগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Raw body আর header অপরিবর্তিত অবস্থায় ধরে রাখা
- Timestamp পরীক্ষা করা, সময়-সীমার ভেতরে কি না
- নিজের সংরক্ষিত key দিয়ে HMAC আবার হিসাব করা
- Constant-time ফাংশনে দুটি ট্যাগ তুলনা করা
- এবার JSON পড়ে event id দেখে অর্ডার প্রক্রিয়া করা
SHA-256-এর আউটপুট ২৫৬ bit. আদর্শ ধরে নিলে একটি collision খুঁজতে আনুমানিক কতটি hash গণনা লাগে?
- প্রায় 2256
- প্রায় 264
- প্রায় 2128
- প্রায় ৫১২
আগে থেকে থাকা একটি নির্দিষ্ট ফাইলের digest-এর সঙ্গে মেলে এমন আরেকটি ফাইল বানানো ঠেকায় hash-এর কোন গুণ?
- Preimage resistance
- Second preimage resistance
- Collision resistance
- Determinism
একটি API অনুরোধের ট্যাগ বানানো হয় SHA-256(key + message) দিয়ে। এই নকশার মূল সমস্যা কী?
- SHA-256-এর collision বের হয়ে গেছে
- ট্যাগ থেকে সরাসরি key পড়া যায়
- Hash খুব ধীর বলে সার্ভার আটকে যায়
- Length extension-এর কারণে key ছাড়াই বার্তা বাড়িয়ে বৈধ ট্যাগ বানানো যায়
একই HMAC key দুটি কোম্পানির কাছে আছে। কোম্পানি ক দাবি করল, খ একটি বার্তা পাঠিয়েছে। আদালতে এই ট্যাগ দিয়ে কেন তা প্রমাণ করা কঠিন?
- দুই পক্ষই একই ট্যাগ বানাতে পারে, তাই non-repudiation নেই
- HMAC বার্তার বদল ধরতে পারে না
- HMAC প্রতিবার ভিন্ন ট্যাগ দেয়, তাই মেলানো যায় না
- HMAC কোনো স্বীকৃত মানদণ্ড নয়
ট্যাগ যাচাইয়ে সাধারণ ==-এর বদলে constant-time ফাংশন ব্যবহার করা হয় কেন?
- এটি
==-এর চেয়ে অনেক দ্রুত - এটি ট্যাগকে encrypt করে রাখে
- উত্তরের সময় থেকে কতটি অক্ষর মিলেছে তা ফাঁস হওয়া ঠেকাতে
- এটি key নিজে থেকে বদলে দেয়
একটি সাইটে ফাইল আর তার SHA-256 digest একই সার্ভারে রাখা আছে। আপনার হিসাব করা digest মিলে গেল। এতে কী প্রমাণ হলো?
- ফাইলটিতে কোনো ক্ষতিকর কোড নেই
- ফাইলটি ওই পাতার digest-এর সঙ্গে মেলে, এর বেশি কিছু নয়
- ফাইলটি সত্যিই প্রকাশকের কাছ থেকে এসেছে, এটি নিশ্চিত
- ফাইলটি ব্যবহারের আইনি অনুমতি আছে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Hash মানে নির্দিষ্ট দৈর্ঘ্যের ছাপ। গুণ তিনটি: preimage, second preimage আর collision resistance.
- Birthday bound: n bit-এর hash-এ collision খরচ প্রায় 2n/2. SHA-256-এর collision নিরাপত্তা ১২৮ bit.
- MD5 আর SHA-1 অবসরে; SHA-256, SHA-3 বা BLAKE2 ব্যবহার করুন।
- Hash বদল দেখায়, MAC দেখায় key-ধারীর কাছ থেকে এসেছে, আর signature দেখায় নির্দিষ্ট একজন সই করেছেন।
hash(key + message)নয়, HMAC. - ট্যাগ মেলান constant-time ফাংশনে, timestamp দিয়ে replay ঠেকান, আর digest কোন পথে এসেছে, সেটিও ভাবুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১০ · Public key cryptography — RSA, elliptic curve আর key exchangeপরবর্তী পর্বগোপন key আগে ভাগ না করেই নিরাপদ যোগাযোগ।
- সাইবার নিরাপত্তা · কোর্স হোম৮০টি পর্বপুরো কোর্সের রোডম্যাপ।
- Computer Networks · Cryptography-র ভিত্তিপাশের ট্র্যাকNetwork-এর চোখে encryption, hash আর signature-এর পরিচয়।
- Git internalsগভীরে যানContent addressing কীভাবে Git-কে চালায়।
- DSA · Hash tableপাশের ট্র্যাকCryptographic নয় এমন hash কোথায় ব্যবহার হয়, আর পার্থক্য কী।