हमने देखा है कि पुश मैसेज को ट्रिगर करने के लिए लाइब्रेरी का इस्तेमाल कैसे किया जा सकता है, लेकिन ये लाइब्रेरी असल में क्या कर रही हैं?
वे नेटवर्क अनुरोध करते हुए यह पक्का कर रहे हैं कि इस तरह के अनुरोध सही फ़ॉर्मैट में हों. इस नेटवर्क अनुरोध के बारे में बताने वाली जानकारी वेब पुश प्रोटोकॉल है.
इस सेक्शन में बताया गया है कि सर्वर, ऐप्लिकेशन की सर्वर कुंजियों का इस्तेमाल करके अपनी पहचान कैसे कर सकता है. साथ ही, यह भी बताया गया है कि एन्क्रिप्ट (सुरक्षित) किया गया पेलोड और उससे जुड़ा डेटा कैसे भेजा जाता है.
यह वेब पुश का अच्छा हिस्सा नहीं है और मैं एन्क्रिप्शन का कोई विशेषज्ञ नहीं हूं, लेकिन आइए हर एक हिस्से पर नज़र डालते हैं, क्योंकि इससे यह जानने में मदद मिलेगी कि ये लाइब्रेरी कैसे काम कर रही हैं.
ऐप्लिकेशन सर्वर कुंजियां
जब हम किसी उपयोगकर्ता की सदस्यता लेते हैं, तो हमें applicationServerKey
मिलता है. इस कुंजी को पुश सेवा को पास कर दिया जाता है. इसका इस्तेमाल यह जांचने के लिए किया जाता है कि जिस ऐप्लिकेशन ने उपयोगकर्ता की सदस्यता ली है वह पुश मैसेज ट्रिगर करने वाला ऐप्लिकेशन है या नहीं.
जब हम किसी पुश मैसेज को ट्रिगर करते हैं, तो हेडर का एक सेट होता है. इसलिए, हम पुश सेवा को ऐप्लिकेशन की पुष्टि करने की अनुमति देते हैं. (इसे VAPID स्पेसिफ़िकेशन से बताया गया है.)
इन सबका क्या मतलब है और असल में क्या होता है? ऐप्लिकेशन सर्वर की पुष्टि करने के लिए, ये कदम उठाए जा सकते हैं:
- ऐप्लिकेशन सर्वर अपनी निजी ऐप्लिकेशन कुंजी की मदद से, कुछ JSON जानकारी पर हस्ताक्षर करता है.
- हस्ताक्षर की गई यह जानकारी पुश सेवा को पोस्ट अनुरोध में हेडर के तौर पर भेजी जाती है.
- पुश सेवा,
pushManager.subscribe()
से मिले सेव किए गए सार्वजनिक पासकोड का इस्तेमाल करती है. इसकी मदद से, यह जांच की जाती है कि मिली जानकारी पर सार्वजनिक पासकोड से जुड़े निजी पासकोड से हस्ताक्षर किया गया है या नहीं. याद रखें: सार्वजनिक कुंजी वहapplicationServerKey
होती है जिसे सदस्यता कॉल में पास किया जाता है. - अगर साइन की गई जानकारी मान्य है, तो पुश सेवा उपयोगकर्ता को पुश मैसेज भेजती है.
जानकारी के इस फ़्लो का एक उदाहरण नीचे दिया गया है. (सार्वजनिक और निजी कुंजियों को दिखाने के लिए नीचे बाईं ओर मौजूद लेजेंड को नोट करें.)
अनुरोध के हेडर में जोड़ी गई "साइन की गई जानकारी", एक JSON वेब टोकन होता है.
JSON वेब टोकन
JSON वेब टोकन (या कम शब्दों में JWT) किसी तीसरे पक्ष को मैसेज भेजने का एक तरीका है. इससे, ईमेल पाने वाला यह पुष्टि कर सकता है कि मैसेज किसने भेजा है.
जब किसी तीसरे पक्ष को मैसेज मिलता है, तो उसे भेजने वाले की सार्वजनिक कुंजी की ज़रूरत होगी और JWT के हस्ताक्षर की पुष्टि करने के लिए उसका इस्तेमाल करना होगा. अगर हस्ताक्षर मान्य है, तो JWT को मेल खाने वाली निजी कुंजी से हस्ताक्षर करना होगा इसलिए, इसे भेजने वाले व्यक्ति की ओर से होना चाहिए.
https://jwt.io/ पर ऐसी कई लाइब्रेरी मौजूद हैं जो आपके लिए साइनिंग का काम कर सकती हैं. हमारा सुझाव है कि जहां आप कर सकें वहां कई लाइब्रेरी मौजूद हैं. पूरी जानकारी के लिए, चलिए देखते हैं कि साइन किया हुआ JWT मैन्युअल तौर पर कैसे बनाया जाता है.
वेब पुश और साइन किए हुए JWT
हस्ताक्षर किया गया JWT सिर्फ़ एक स्ट्रिंग होती है, हालांकि इसे बिंदुओं की मदद से जोड़ी गई तीन स्ट्रिंग के रूप में माना जा सकता है.
पहली और दूसरी स्ट्रिंग (JWT का जानकारी और JWT डेटा), JSON के वे हिस्से हैं जिन्हें base64 कोड में बदला गया है. इसका मतलब है कि इसे सार्वजनिक तौर पर पढ़ा जा सकता है.
पहली स्ट्रिंग, JWT के बारे में जानकारी देती है, जिससे यह पता चलता है कि हस्ताक्षर बनाने के लिए किस एल्गोरिदम का इस्तेमाल किया गया था.
वेब पुश के लिए JWT की जानकारी में यह जानकारी होनी चाहिए:
{
"typ": "JWT",
"alg": "ES256"
}
दूसरी स्ट्रिंग है JWT डेटा. इससे JWT भेजने वाले के बारे में जानकारी मिलती है. इसमें, JWT किसे भेजा जाता है और यह कितने समय तक मान्य रहता है.
वेब पुश के लिए, डेटा इस फ़ॉर्मैट में होगा:
{
"aud": "https://some-push-service.org",
"exp": "1469618703",
"sub": "mailto:example@web-push-book.org"
}
aud
वैल्यू "audience" है. इसका मतलब है कि JWT किसके लिए है. वेब पुश के लिए
ऑडियंस पुश सेवा है, इसलिए हम इसे पुश सेवा की शुरुआत की जगह पर सेट करते हैं.
exp
वैल्यू, JWT की समयसीमा खत्म होने वाली है. इससे, अगर कोई जालसाज़ी को बीच में रोकता है, तो वह उस JWT का दोबारा इस्तेमाल नहीं कर पाएगा. वीडियो के खत्म होने की अवधि, सेकंड में टाइमस्टैंप होती है और यह 24 घंटे की नहीं होनी चाहिए.
Node.js में, समयसीमा खत्म होने की तारीख इनका इस्तेमाल करके सेट की गई है:
Math.floor(Date.now() / 1000) + 12 * 60 * 60;
इस प्रोसेस में 24 घंटे के बजाय 12 घंटे लगते हैं. ऐसा इसलिए किया जाता है, ताकि ईमेल भेजने वाले ऐप्लिकेशन और पुश सेवा के बीच घड़ी के अंतर की किसी भी समस्या से बचा जा सके.
आखिर में, sub
वैल्यू को यूआरएल या mailto
ईमेल पता होना चाहिए.
ऐसा इसलिए है, ताकि अगर ईमेल भेजने वाले से संपर्क करने के लिए किसी पुश सेवा की ज़रूरत पड़े, तो वह JWT से संपर्क जानकारी खोज सके. (इसलिए, वेब-पुश लाइब्रेरी को एक ईमेल पते की ज़रूरत होती है).
JWT की जानकारी की तरह, JWT डेटा को यूआरएल सेफ़ बेस64 स्ट्रिंग के तौर पर एन्कोड किया जाता है.
तीसरी स्ट्रिंग, सिग्नेचर, पहली दो स्ट्रिंग (JWT की जानकारी और JWT डेटा) को अपनाकर उन्हें एक डॉट कैरेक्टर की मदद से जोड़ते हैं. इस स्ट्रिंग को हम "अनसाइन किया गया टोकन" कहते हैं और उस पर हस्ताक्षर करते हैं.
साइनिंग प्रोसेस में, ES256 का इस्तेमाल करके "बिना हस्ताक्षर वाले टोकन" को एन्क्रिप्ट (सुरक्षित) करना ज़रूरी होता है. JWT की खास जानकारी के मुताबिक, ES256, "P-256 कर्व और SHA-256 हैश एल्गोरिदम का इस्तेमाल करने वाले ECDSA" शब्द का छोटा नाम है. वेब क्रिप्टो का इस्तेमाल करके, इस तरह से हस्ताक्षर बनाया जा सकता है:
// Utility function for UTF-8 encoding a string to an ArrayBuffer.
const utf8Encoder = new TextEncoder('utf-8');
// The unsigned token is the concatenation of the URL-safe base64 encoded
// header and body.
const unsignedToken = .....;
// Sign the |unsignedToken| using ES256 (SHA-256 over ECDSA).
const key = {
kty: 'EC',
crv: 'P-256',
x: window.uint8ArrayToBase64Url(
applicationServerKeys.publicKey.subarray(1, 33)),
y: window.uint8ArrayToBase64Url(
applicationServerKeys.publicKey.subarray(33, 65)),
d: window.uint8ArrayToBase64Url(applicationServerKeys.privateKey),
};
// Sign the |unsignedToken| with the server's private key to generate
// the signature.
return crypto.subtle.importKey('jwk', key, {
name: 'ECDSA', namedCurve: 'P-256',
}, true, ['sign'])
.then((key) => {
return crypto.subtle.sign({
name: 'ECDSA',
hash: {
name: 'SHA-256',
},
}, key, utf8Encoder.encode(unsignedToken));
})
.then((signature) => {
console.log('Signature: ', signature);
});
सिग्नेचर को डिक्रिप्ट करने के लिए पुश सेवा, सार्वजनिक ऐप्लिकेशन सर्वर कुंजी का इस्तेमाल करके JWT की पुष्टि कर सकती है. साथ ही, यह भी पक्का कर सकती है कि डिक्रिप्ट की गई स्ट्रिंग, "साइन नहीं किए गए टोकन" (जो JWT में पहली दो स्ट्रिंग) की तरह ही है.
साइन किया गया JWT (यानी कि बिंदुओं से जोड़ी गई सभी तीन स्ट्रिंग), वेब पुश सेवा को Authorization
हेडर के तौर पर भेजा जाता है. ऐसा WebPush
के साथ पहले से किया जाता है, जैसे:
Authorization: 'WebPush [JWT Info].[JWT Data].[Signature]';
वेब पुश प्रोटोकॉल में यह भी बताया गया है कि सार्वजनिक ऐप्लिकेशन सर्वर कुंजी,
Crypto-Key
हेडर में, यूआरएल के हिसाब से सुरक्षित base64 कोड वाली स्ट्रिंग के तौर पर भेजी जानी चाहिए. साथ ही, इसके पहले
p256ecdsa=
जुड़ा होना चाहिए.
Crypto-Key: p256ecdsa=[URL Safe Base64 Public Application Server Key]
पेलोड एन्क्रिप्ट (सुरक्षित) करने का तरीका
आइए, अब जानते हैं कि हम पुश मैसेज की मदद से पेलोड कैसे भेज सकते हैं, ताकि जब हमारे वेब ऐप्लिकेशन को कोई पुश मैसेज मिले, तो वह उस डेटा को ऐक्सेस कर सके.
अन्य पुश सेवाओं का इस्तेमाल करने वाले किसी भी व्यक्ति से अक्सर पूछा जाता है कि वेब पुश पेलोड को एन्क्रिप्ट (सुरक्षित) करने की ज़रूरत क्यों पड़ती है? नेटिव ऐप्लिकेशन में, पुश मैसेज की मदद से डेटा को सामान्य टेक्स्ट के तौर पर भेजा जा सकता है.
वेब पुश की खासियत यह है कि सभी पुश सेवाएं एक ही एपीआई (वेब पुश प्रोटोकॉल) का इस्तेमाल करती हैं. इसलिए, डेवलपर को इस बात की परवाह नहीं होती कि पुश सेवा कौन है. हम सही फ़ॉर्मैट में अनुरोध कर सकते हैं और उनसे एक पुश मैसेज भेजे जाने की उम्मीद कर सकते हैं. इसमें एक समस्या यह है कि डेवलपर ऐसी पुश सेवा को ईमेल भेज सकते हैं जो भरोसेमंद नहीं है. पेलोड को एन्क्रिप्ट (सुरक्षित) करने से, पुश सेवा भेजे जाने वाले डेटा को नहीं पढ़ सकती. सिर्फ़ ब्राउज़र इस जानकारी को डिक्रिप्ट कर सकता है. यह उपयोगकर्ता के डेटा की सुरक्षा करता है.
पेलोड को एन्क्रिप्ट (सुरक्षित) करने के बारे में, मैसेज के एन्क्रिप्शन की जानकारी में बताया गया है.
पुश मैसेज पेलोड को एन्क्रिप्ट (सुरक्षित) करने के खास चरणों पर ध्यान देने से पहले, हमें कुछ ऐसी तकनीकों के बारे में बताना होगा जिनका इस्तेमाल एन्क्रिप्ट (सुरक्षित) करने की प्रक्रिया के दौरान किया जाएगा. (पुश एनक्रिप्शन पर उनके इस शानदार लेख के लिए मैट स्केल्स को हैट की विशाल टिप.)
ECDH और HKDF
ईसीडीएच और एचकेडीएफ़, दोनों का इस्तेमाल एन्क्रिप्ट (सुरक्षित) करने की पूरी प्रक्रिया के दौरान किया जाता है. साथ ही, ये जानकारी को एन्क्रिप्ट करने के फ़ायदे भी देते हैं.
ECDH: एलिप्टिक कर्व डिफ़ी-हेलमैन की एक्सचेंज
मान लें कि आपके पास दो लोग हैं, जो जानकारी शेयर करना चाहते हैं, ऐलिस और बॉब. ऐलिस और बॉब, दोनों की अपनी सार्वजनिक और निजी कुंजियां हैं. ऐलिस और बॉब एक-दूसरे के साथ अपनी सार्वजनिक कुंजियां शेयर करते हैं.
ईसीडीएच की मदद से जनरेट की गई कुंजियों की प्रॉपर्टी यह होती है कि ऐलिस अपनी निजी कुंजी और बॉब की सार्वजनिक कुंजी का इस्तेमाल करके सीक्रेट वैल्यू 'X' बना सकती हैं. बॉब भी ऐसा ही कर सकता है, वह अपनी निजी कुंजी और ऐलिस की सार्वजनिक कुंजी लेकर स्वतंत्र रूप से एक ही वैल्यू 'X' बना सकता है. इससे 'X' सब शेयर हो जाता है और ऐलिस और बॉब को सिर्फ़ अपनी सार्वजनिक कुंजी शेयर करनी पड़ती थी. अब बॉब और ऐलिस अपने बीच के मैसेज को एन्क्रिप्ट और डिक्रिप्ट करने के लिए 'X' का इस्तेमाल कर सकते हैं.
ECDH में मेरी जानकारी के हिसाब से, कर्व के उन प्रॉपर्टी के बारे में बताया गया है जो शेयर किए गए सीक्रेट 'X' को बनाने की इस "सुविधा" को अनुमति देती हैं.
यह ईसीडीएच का हाई लेवल एक्सप्लेनेशन है. अगर आपको इस बारे में ज़्यादा जानकारी चाहिए, तो मेरा सुझाव है कि आप यह वीडियो देखें.
कोड की बात करें, तो ज़्यादातर भाषाओं / प्लैटफ़ॉर्म में लाइब्रेरी होती हैं, ताकि इन कुंजियों को जनरेट करना आसान हो जाए.
नोड में हम ये काम करेंगे:
const keyCurve = crypto.createECDH('prime256v1');
keyCurve.generateKeys();
const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();
HKDF: एचएमएसी आधारित कुंजी डेरिवेशन फ़ंक्शन
Wikipedia में HKDF के बारे में कम शब्दों में जानकारी दी गई है:
एचकेडीएफ़, एचएमएसी आधारित 'की' डेरिवेशन फ़ंक्शन है. यह किसी भी कमज़ोर कुंजी सामग्री को क्रिप्टोग्राफ़िक तरीके से मज़बूत कुंजी सामग्री में बदल देता है. उदाहरण के लिए, इसका इस्तेमाल डिफ़ी हेलमैन के शेयर किए गए रहस्यों को अहम कॉन्टेंट में बदलने के लिए किया जा सकता है. इस जानकारी का इस्तेमाल एन्क्रिप्शन, इंटेग्रिटी जांच या पुष्टि करने के लिए किया जा सकता है.
इसका मतलब है कि HKDF ऐसे इनपुट लेगा जो सुरक्षित नहीं है.
इस एन्क्रिप्शन को परिभाषित करने वाली खास जानकारी के लिए हमारे हैश एल्गोरिदम के तौर पर SHA-256 का इस्तेमाल करना ज़रूरी है और वेब पुश में HKDF के लिए मिलने वाली कुंजी, 256 बिट (32 बाइट) से ज़्यादा नहीं होनी चाहिए.
नोड में, इसे इस तरह लागू किया जा सकता है:
// Simplified HKDF, returning keys up to 32 bytes long
function hkdf(salt, ikm, info, length) {
// Extract
const keyHmac = crypto.createHmac('sha256', salt);
keyHmac.update(ikm);
const key = keyHmac.digest();
// Expand
const infoHmac = crypto.createHmac('sha256', key);
infoHmac.update(info);
// A one byte long buffer containing only 0x01
const ONE_BUFFER = new Buffer(1).fill(1);
infoHmac.update(ONE_BUFFER);
return infoHmac.digest().slice(0, length);
}
इस उदाहरण कोड के लिए, मैट स्केल के लेख पर हैट टिप.
इसमें ECDH और एचकेडीएफ़ शामिल नहीं हैं.
ईसीडीएच, सार्वजनिक कुंजियों को शेयर करने और शेयर किए गए सीक्रेट जनरेट करने का सुरक्षित तरीका है. HKDF, असुरक्षित सामग्री लेकर उसे सुरक्षित बनाने का एक तरीका है.
इसका इस्तेमाल, हमारे पेलोड को एन्क्रिप्ट (सुरक्षित) करने के दौरान किया जाएगा. आइए, अब देखते हैं कि हम इनपुट के रूप में क्या चुनते हैं और उसे कैसे एन्क्रिप्ट किया जाता है.
इनपुट
जब हम पेलोड वाले किसी उपयोगकर्ता को पुश मैसेज भेजना चाहते हैं, तो हमें तीन इनपुट की ज़रूरत होती है:
- खुद पेलोड.
PushSubscription
काauth
सीक्रेट.PushSubscription
से मिलीp256dh
कुंजी.
हमने देखा है कि PushSubscription
से auth
और p256dh
की वैल्यू मिलती हैं. हालांकि, आपको याद दिला दें कि सदस्यता लेने के लिए, हमें इन वैल्यू की ज़रूरत होगी:
subscription.toJSON().keys.auth;
subscription.toJSON().keys.p256dh;
subscription.getKey('auth');
subscription.getKey('p256dh');
auth
वैल्यू को सीक्रेट के तौर पर माना जाना चाहिए. इसे आपके ऐप्लिकेशन के बाहर शेयर नहीं किया जाना चाहिए.
p256dh
कुंजी एक सार्वजनिक कुंजी होती है. इसे कभी-कभी क्लाइंट की सार्वजनिक कुंजी भी कहा जाता है. यहां
हम p256dh
को सदस्यता सार्वजनिक कुंजी के तौर पर देखेंगे. सदस्यता के लिए सार्वजनिक कुंजी, ब्राउज़र से जनरेट होती है. ब्राउज़र, निजी कुंजी को किसी के साथ शेयर नहीं रखेगा. साथ ही, इसका इस्तेमाल पेलोड को डिक्रिप्ट करने के लिए किया जाएगा.
auth
, p256dh
, और payload
की ये तीन वैल्यू इनपुट के तौर पर ज़रूरी हैं. एन्क्रिप्ट (सुरक्षित) करने की प्रोसेस में, एन्क्रिप्ट (सुरक्षित) किया गया पेलोड, सॉल्ट की वैल्यू, और एक सार्वजनिक कुंजी होगी. इसका इस्तेमाल सिर्फ़ डेटा को एन्क्रिप्ट (सुरक्षित) करने के लिए किया जाएगा.
खारा
सॉल्ट, 16 बाइट का रैंडम डेटा होना चाहिए. NodeJS में, हम सॉल्ट बनाने के लिए यह काम करेंगे:
const salt = crypto.randomBytes(16);
सार्वजनिक / निजी पासकोड
सार्वजनिक और निजी कुंजियां, P-256 एलिप्टिक कर्व का इस्तेमाल करके जनरेट की जानी चाहिए. हम इन्हें नोड में इस तरह करेंगे:
const localKeysCurve = crypto.createECDH('prime256v1');
localKeysCurve.generateKeys();
const localPublicKey = localKeysCurve.getPublicKey();
const localPrivateKey = localKeysCurve.getPrivateKey();
हम इन कुंजियों को "लोकल बटन" के तौर पर कहेंगे. इनका इस्तेमाल सिर्फ़ एन्क्रिप्ट (सुरक्षित) करने के लिए किया जाता है और ऐप्लिकेशन सर्वर कुंजियों के लिए इनका कुछ नहीं होता.
पेलोड, पुष्टि करने की सीक्रेट कुंजी, और इनपुट के तौर पर सदस्यता वाली सार्वजनिक कुंजी की मदद से, जनरेट किए गए नए सॉल्ट और लोकल कुंजियों के सेट की मदद से, हम वाकई कुछ एन्क्रिप्ट (सुरक्षित) करने के लिए तैयार हैं.
शेयर किया गया सीक्रेट
इसका सबसे पहला चरण है, सदस्यता के लिए सार्वजनिक कुंजी और हमारी नई निजी कुंजी का इस्तेमाल करके, शेयर किया गया सीक्रेट बनाना. (एलिस और बॉब के साथ ईसीडीएच के बारे में दी गई जानकारी याद रखें? बस इस तरह.
const sharedSecret = localKeysCurve.computeSecret(
subscription.keys.p256dh,
'base64',
);
इसका इस्तेमाल बदली हुई रैंडम की (पीआरके) को कैलकुलेट करने के लिए, अगले चरण में किया जाता है.
बदली हुई पहचान वाली कुंजी
सूडो रैंडम की (पीआरके) पुश सदस्यता के पुष्टि करने वाले सीक्रेट और हमारे बनाए गए शेयर किए गए सीक्रेट को मिलाकर बना है.
const authEncBuff = new Buffer('Content-Encoding: auth\0', 'utf8');
const prk = hkdf(subscription.keys.auth, sharedSecret, authEncBuff, 32);
आपको लग रहा होगा कि Content-Encoding: auth\0
स्ट्रिंग किसलिए है.
कुल मिलाकर इसका कोई खास मकसद नहीं है. हालांकि, ब्राउज़र आने वाले किसी मैसेज को डिक्रिप्ट कर सकते हैं और कॉन्टेंट को कोड में बदलने की उम्मीद कर सकते हैं.
\0
, बफ़र के खत्म होने तक 0 वैल्यू वाली बाइट जोड़ता है. ऐसा तब होता है, जब ब्राउज़र उस मैसेज को डिक्रिप्ट कर देते हैं जिसे कॉन्टेंट को कोड में बदलने के लिए बहुत ज़्यादा बाइट की ज़रूरत होगी. इसके बाद, 0 बाइट वाला बाइट होगा और उसके बाद एन्क्रिप्ट किया गया डेटा आएगा.
हमारी सूडो रैंडम कुंजी बस HKDF के ज़रिए, पुष्टि करने वाला, शेयर किया गया सीक्रेट, और कोड में बदलने की जानकारी का एक हिस्सा चला रही है (यानी इसे क्रिप्टोग्राफ़िक तरीके से मज़बूत बनाना).
संदर्भ
"context" बाइट का एक सेट है, जिसका इस्तेमाल बाद में एन्क्रिप्शन ब्राउज़र में दो वैल्यू का हिसाब लगाने के लिए किया जाता है. यह बाइट का कलेक्शन है, जिसमें सदस्यता के लिए सार्वजनिक पासकोड और लोकल सार्वजनिक पासकोड शामिल हैं.
const keyLabel = new Buffer('P-256\0', 'utf8');
// Convert subscription public key into a buffer.
const subscriptionPubKey = new Buffer(subscription.keys.p256dh, 'base64');
const subscriptionPubKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = subscriptionPubKey.length;
const localPublicKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = localPublicKey.length;
const contextBuffer = Buffer.concat([
keyLabel,
subscriptionPubKeyLength.buffer,
subscriptionPubKey,
localPublicKeyLength.buffer,
localPublicKey,
]);
फ़ाइनल कॉन्टेक्स्ट बफ़र एक लेबल होता है. यह सदस्यता सार्वजनिक कुंजी में मौजूद बाइट की संख्या होती है. इसके बाद कुंजी, फिर बाइट की संख्या, और फिर लोकल सार्वजनिक कुंजी की संख्या होती है.
कॉन्टेक्स्ट वैल्यू की मदद से, हम इसका इस्तेमाल नॉन्स और कॉन्टेंट एन्क्रिप्ट (सुरक्षित) करने की कुंजी (सीईके) बनाने के लिए कर सकते हैं.
कॉन्टेंट को एन्क्रिप्ट (सुरक्षित) करने वाली कुंजी और नॉन्स
nonce एक ऐसी वैल्यू है जो रीप्ले अटैक को रोकती है, क्योंकि इसे सिर्फ़ एक बार इस्तेमाल किया जाना चाहिए.
कॉन्टेंट एन्क्रिप्ट (सुरक्षित) करने वाली कुंजी (सीईके) ऐसी कुंजी है जिसका इस्तेमाल हमारे पेलोड को एन्क्रिप्ट (सुरक्षित) करने के लिए किया जाएगा.
सबसे पहले हमें नॉन्स और सीईके के लिए डेटा की बाइट बनानी होंगी, जो सिर्फ़ कॉन्टेंट को कोड में बदलने वाली स्ट्रिंग होती है. इसके बाद, कॉन्टेक्स्ट बफ़र होता है, जिसका हमने अभी हिसाब लगाया है:
const nonceEncBuffer = new Buffer('Content-Encoding: nonce\0', 'utf8');
const nonceInfo = Buffer.concat([nonceEncBuffer, contextBuffer]);
const cekEncBuffer = new Buffer('Content-Encoding: aesgcm\0');
const cekInfo = Buffer.concat([cekEncBuffer, contextBuffer]);
यह जानकारी HKDF से मिलती है, जिसमें सॉल्ट और PRK को NonceInfo और cekInfo से मिलाया जाता है:
// The nonce should be 12 bytes long
const nonce = hkdf(salt, prk, nonceInfo, 12);
// The CEK should be 16 bytes long
const contentEncryptionKey = hkdf(salt, prk, cekInfo, 16);
इससे हमें नॉन्स और कॉन्टेंट को एन्क्रिप्ट (सुरक्षित) करने वाली कुंजी मिलती है.
एन्क्रिप्ट (सुरक्षित) करें
अब हमारे पास कॉन्टेंट एन्क्रिप्ट (सुरक्षित) करने की कुंजी है, इसलिए हम पेलोड को एन्क्रिप्ट (सुरक्षित) कर सकते हैं.
हम कुंजी के तौर पर कॉन्टेंट एन्क्रिप्शन कुंजी का इस्तेमाल करके, AES128 साइफ़र बनाते हैं और नॉन्स इनिशलाइज़ेशन वेक्टर होता है.
नोड में ऐसा इस तरह किया जाता है:
const cipher = crypto.createCipheriv(
'id-aes128-GCM',
contentEncryptionKey,
nonce,
);
अपने पेलोड को एन्क्रिप्ट (सुरक्षित) करने से पहले, हमें यह तय करना होगा कि हमें पेलोड के सामने कितनी पैडिंग (जगह) जोड़नी है. हम पैडिंग (जगह) जोड़ने की वजह यह है कि इससे पेलोड के साइज़ के हिसाब से जासूसी करने वालों को मैसेज के "टाइप" तय करने के जोखिम को कम किया जा सकता है.
किसी भी अतिरिक्त पैडिंग की लंबाई दिखाने के लिए, आपको दो बाइट की पैडिंग (जगह) जोड़नी होगी.
उदाहरण के लिए, अगर आपने कोई पैडिंग (जगह) नहीं जोड़ी है, तो आपकी वैल्यू 0 वाली दो बाइट होगी.इसका मतलब है कि कोई पैडिंग (जगह) मौजूद नहीं है. इन दो बाइट के बाद, आपको पेलोड पढ़ा जाएगा. अगर आपने 5 बाइट की पैडिंग (जगह) जोड़ी है, तो शुरुआती दो बाइट की वैल्यू पांच होगी. इससे उपभोक्ता पांच बाइट और ज़्यादा डेटा पढ़ेगा और फिर पेलोड को पढ़ना शुरू करेगा.
const padding = new Buffer(2 + paddingLength);
// The buffer must be only zeros, except the length
padding.fill(0);
padding.writeUInt16BE(paddingLength, 0);
इसके बाद, हम इस साइफ़र के ज़रिए पैडिंग और पेलोड चलाते हैं.
const result = cipher.update(Buffer.concat(padding, payload));
cipher.final();
// Append the auth tag to the result -
// https://nodejs.org/api/crypto.html#crypto_cipher_getauthtag
const encryptedPayload = Buffer.concat([result, cipher.getAuthTag()]);
अब एन्क्रिप्ट (सुरक्षित) किया गया पेलोड उपलब्ध है. हां!
अब सिर्फ़ यह तय करना बाकी है कि यह पेलोड पुश सेवा को कैसे भेजा जाए.
एन्क्रिप्ट (सुरक्षित) किए गए पेलोड हेडर और मुख्य हिस्से
एन्क्रिप्ट (सुरक्षित) किए गए इस पेलोड को पुश सेवा पर भेजने के लिए, हमें अपने पोस्ट अनुरोध में कुछ अलग-अलग हेडर तय करने होंगे.
एन्क्रिप्शन हेडर
'एन्क्रिप्ट (सुरक्षित) करने के तरीके' वाले हेडर में, पेलोड को एन्क्रिप्ट (सुरक्षित) करने के लिए इस्तेमाल किया जाने वाला साल्ट होना चाहिए.
16 बाइट वाला सॉल्ट, base64 यूआरएल सुरक्षित होना चाहिए और उसे एन्क्रिप्शन हेडर में जोड़ना चाहिए, जैसे:
Encryption: salt=[URL Safe Base64 Encoded Salt]
क्रिप्टो-कुंजी हेडर
हमने देखा कि 'ऐप्लिकेशन सर्वर कुंजियां' सेक्शन में, Crypto-Key
हेडर का इस्तेमाल सार्वजनिक ऐप्लिकेशन सर्वर कुंजी को शामिल करने के लिए किया जाता है.
इस हेडर का इस्तेमाल, पेलोड को एन्क्रिप्ट (सुरक्षित) करने के लिए इस्तेमाल होने वाली लोकल सार्वजनिक कुंजी को शेयर करने के लिए भी किया जाता है.
मिलने वाला हेडर कुछ ऐसा दिखेगा:
Crypto-Key: dh=[URL Safe Base64 Encoded Local Public Key String]; p256ecdsa=[URL Safe Base64 Encoded Public Application Server Key]
कॉन्टेंट का टाइप, लंबाई, और कोड में बदलने का हेडर
Content-Length
हेडर, एन्क्रिप्ट (सुरक्षित) किए गए पेलोड में मौजूद बाइट की संख्या है. 'कॉन्टेंट-टाइप' और 'कॉन्टेंट-कोडिंग' हेडर, तय वैल्यू होती हैं.
इस उदाहरण में दिखाया गया है.
Content-Length: [Number of Bytes in Encrypted Payload]
Content-Type: 'application/octet-stream'
Content-Encoding: 'aesgcm'
इन हेडर को सेट करने पर, हमें एन्क्रिप्ट किए गए पेलोड को अपने अनुरोध के मुख्य हिस्से के तौर पर भेजना होगा. ध्यान दें कि Content-Type
को
application/octet-stream
पर सेट किया गया है. ऐसा इसलिए, क्योंकि एन्क्रिप्ट किया गया पेलोड
बाइट स्ट्रीम के तौर पर भेजा जाना चाहिए.
NodeJS में हम ऐसा करेंगे:
const pushRequest = https.request(httpsOptions, function(pushResponse) {
pushRequest.write(encryptedPayload);
pushRequest.end();
और हेडर?
हमने JWT / ऐप्लिकेशन सर्वर कुंजियों के लिए इस्तेमाल किए जाने वाले हेडर (जैसे कि पुश सेवा से ऐप्लिकेशन की पहचान करने का तरीका) को कवर किया है. साथ ही, हमने एन्क्रिप्ट (सुरक्षित) किया गया पेलोड भेजने के लिए इस्तेमाल किए जाने वाले हेडर को भी कवर किया है.
कुछ और हेडर भी होते हैं जिनका इस्तेमाल पुश सर्विस को भेजकर, भेजे गए ईमेल के व्यवहार को बदलने के लिए किया जाता है. इनमें से कुछ हेडर ज़रूरी हैं, जबकि कुछ ज़रूरी नहीं हैं.
TTL हेडर
ज़रूरी
TTL
(या लाइव होने का समय) एक पूर्णांक है. इसमें यह बताया जाता है कि आपका पुश मैसेज डिलीवर होने से पहले, पुश सर्विस पर उसे कितने सेकंड तक दिखाना है. TTL
की समयसीमा खत्म होने पर, मैसेज को पुश सर्विस सूची से हटा दिया जाएगा और वह डिलीवर नहीं होगा.
TTL: [Time to live in seconds]
अगर TTL
को शून्य पर सेट किया जाता है, तो पुश सेवा तुरंत मैसेज डिलीवर करने की कोशिश करेगी लेकिन अगर डिवाइस ऐक्सेस नहीं किया जा सकता, तो आपका मैसेज तुरंत पुश सर्विस सूची से हटा दिया जाएगा.
तकनीकी रूप से कोई पुश सेवा, किसी पुश मैसेज के TTL
को कम कर सकती है. अगर ऐसा था, तो किसी पुश सेवा के रिस्पॉन्स में TTL
हेडर की जांच करके, यह पता लगाया जा सकता है कि ऐसा हुआ है या नहीं.
विषय
ज़रूरी नहीं
विषय ऐसी स्ट्रिंग होती हैं जिनका इस्तेमाल बाकी बचे मैसेज को नए मैसेज से बदलने के लिए किया जा सकता है. इसके लिए ज़रूरी है कि उन मैसेज के विषय के नाम मेल खाते हों.
यह तब काम आता है, जब डिवाइस के ऑफ़लाइन होने पर कई मैसेज भेजे जाते हैं और आपको यह चाहिए कि डिवाइस चालू होने पर ही उपयोगकर्ता को सबसे नया मैसेज दिखे.
अत्यावश्यकता
ज़रूरी नहीं
बहुत ज़रूरी कार्रवाई, पुश सेवा को यह बताती है कि कोई मैसेज उपयोगकर्ता के लिए कितना ज़रूरी है. पुश सेवा की मदद से, उपयोगकर्ता के डिवाइस की बैटरी लाइफ़ बचाने के लिए इसका इस्तेमाल किया जा सकता है. ऐसा, बैटरी कम होने पर ज़रूरी मैसेज के लिए जागकर किया जा सकता है.
हेडर की वैल्यू नीचे दी गई है. डिफ़ॉल्ट वैल्यू
normal
है.
Urgency: [very-low | low | normal | high]
सब कुछ एक साथ
अगर इन सभी के काम करने के तरीके के बारे में आपका कोई और सवाल है, तो आप हमेशा यह देख सकते हैं कि लाइब्रेरी किस तरह web-push-libs संगठन पर पुश मैसेज को ट्रिगर करती हैं.
एन्क्रिप्ट (सुरक्षित) किया गया पेलोड और ऊपर दिए गए हेडर मिलने पर, आपको PushSubscription
के endpoint
में बस एक पोस्ट अनुरोध करना होगा.
अब हम इस पोस्ट अनुरोध का जवाब कैसे देंगे?
पुश सेवा से मिला जवाब
किसी पुश सेवा से अनुरोध करने के बाद, आपको उसके रिस्पॉन्स का स्टेटस कोड देखना होगा. इससे, आपको पता चल पाएगा कि अनुरोध पूरा हुआ या नहीं.
स्थिति कोड | ब्यौरा |
---|---|
201 | बनाया गया. पुश मैसेज भेजने का अनुरोध मिला और उसे स्वीकार कर लिया गया है. |
429 | बहुत सारे अनुरोध. इसका मतलब है कि पुश सेवा की मदद से, आपका ऐप्लिकेशन सर्वर इससे ज़्यादा अनुरोध नहीं कर सकता. पुश सेवा में 'फिर से कोशिश करें' हेडर शामिल होना चाहिए, ताकि यह बताया जा सके कि दूसरा अनुरोध किए जाने में कितना समय लगेगा. |
400 | अनुरोध अमान्य है. इसका आम तौर पर मतलब है कि आपका कोई हेडर अमान्य है या उसका फ़ॉर्मैट गलत है. |
404 | नहीं मिला. यह इस बात का संकेत है कि सदस्यता खत्म हो गई है और इसका इस्तेमाल नहीं किया जा सकता. ऐसे में, आपको `PushSubscription` को मिटा देना चाहिए और तब तक इंतज़ार करना चाहिए, जब तक कि क्लाइंट उपयोगकर्ता की फिर से सदस्यता नहीं लेता. |
410 | गया. सदस्यता अब मान्य नहीं है और इसे ऐप्लिकेशन सर्वर से हटा दिया जाना चाहिए. `पुशसदस्यता` पर `unsubscribe()` को कॉल करके, इसे फिर से बनाया जा सकता है. |
413 | पेलोड का साइज़ बहुत बड़ा है. किसी पुश सेवा के लिए ज़रूरी है कि पेलोड कम से कम 4096 बाइट (या 4 केबी) में हो. |
इसके बाद कहां जाना है
- वेब पुश नोटिफ़िकेशन की खास जानकारी
- पुश की सुविधा कैसे काम करती है
- किसी उपयोगकर्ता की सदस्यता लेना
- अनुमति UX
- वेब पुश लाइब्रेरी का इस्तेमाल करके मैसेज भेजना
- वेब पुश प्रोटोकॉल
- पुश इवेंट को हैंडल करना
- सूचना दिखाना
- सूचना किस तरह की है
- सूचना के सामान्य पैटर्न
- पुश नोटिफ़िकेशन के बारे में अक्सर पूछे जाने वाले सवाल
- आम तौर पर होने वाली समस्याएं और गड़बड़ियों की शिकायत करना