हमने देखा कि पुश मैसेज ट्रिगर करने के लिए, लाइब्रेरी का इस्तेमाल कैसे किया जा सकता है. हालांकि, ये लाइब्रेरी असल में क्या करती हैं?
ये नेटवर्क अनुरोध करते हैं. साथ ही, यह पक्का करते हैं कि ऐसे अनुरोध सही फ़ॉर्मैट में हों. इस नेटवर्क अनुरोध को तय करने वाला स्पेसिफ़िकेशन, वेब पुश प्रोटोकॉल है.
इस सेक्शन में बताया गया है कि सर्वर, ऐप्लिकेशन सर्वर के पासकोड की मदद से अपनी पहचान कैसे कर सकता है. साथ ही, इसमें यह भी बताया गया है कि एन्क्रिप्ट (सुरक्षित) किया गया पेलोड और उससे जुड़ा डेटा कैसे भेजा जाता है.
यह वेब पुश का अच्छा पहलू नहीं है. साथ ही, मैं एन्क्रिप्शन का विशेषज्ञ नहीं हूं. हालांकि, आइए हम हर हिस्से को देखें, क्योंकि यह जानना ज़रूरी है कि ये लाइब्रेरी पर्दे के पीछे क्या कर रही हैं.
ऐप्लिकेशन सर्वर की कुंजियां
जब हम किसी उपयोगकर्ता को सदस्यता के लिए रजिस्टर करते हैं, तो हम applicationServerKey पास करते हैं. इस कुंजी को पुश सेवा को भेजा जाता है. इसका इस्तेमाल यह देखने के लिए किया जाता है कि जिस ऐप्लिकेशन ने उपयोगकर्ता को सदस्यता दिलाई है वही ऐप्लिकेशन पुश मैसेज ट्रिगर कर रहा है.
जब हम पुश मैसेज ट्रिगर करते हैं, तो हम हेडर का एक सेट भेजते हैं. इससे पुश सेवा को ऐप्लिकेशन की पुष्टि करने की अनुमति मिलती है. (यह VAPID स्पेसिफ़िकेशन के हिसाब से तय होता है.)
इन सबका क्या मतलब है और असल में क्या होता है? ऐप्लिकेशन सर्वर की पुष्टि करने के लिए, ये चरण अपनाए जाते हैं:
- ऐप्लिकेशन सर्वर, JSON फ़ॉर्मैट में मौजूद कुछ जानकारी पर अपने ऐप्लिकेशन के निजी पासकोड से हस्ताक्षर करता है.
- हस्ताक्षर की गई यह जानकारी, POST अनुरोध में हेडर के तौर पर पुश सेवा को भेजी जाती है.
- पुश सेवा,
pushManager.subscribe()से मिली सेव की गई सार्वजनिक कुंजी का इस्तेमाल करती है. इससे यह पता चलता है कि मिली जानकारी पर, सार्वजनिक कुंजी से जुड़ी निजी कुंजी के हस्ताक्षर हैं या नहीं. ध्यान दें: सार्वजनिक पासकोड, सदस्यता लेने के लिए किए गए कॉल में पास किया गयाapplicationServerKeyहोता है. - अगर साइन की गई जानकारी मान्य है, तो पुश सेवा उपयोगकर्ता को पुश मैसेज भेजती है.
इस तरह की जानकारी के फ़्लो का एक उदाहरण यहां दिया गया है. (सार्वजनिक और निजी कुंजियों के बारे में जानने के लिए, नीचे बाईं ओर मौजूद लेजेंड देखें.)
अनुरोध में हेडर में जोड़ी गई "हस्ताक्षर की गई जानकारी" एक JSON वेब टोकन है.
JSON वेब टोकन
JSON वेब टोकन (या छोटा नाम JWT) एक ऐसा तरीका है जिससे किसी तीसरे पक्ष को मैसेज भेजा जा सकता है. इससे मैसेज पाने वाला व्यक्ति यह पुष्टि कर सकता है कि मैसेज किसने भेजा है.
जब किसी तीसरे पक्ष को कोई मैसेज मिलता है, तो उसे मैसेज भेजने वाले की सार्वजनिक कुंजी की ज़रूरत होती है. साथ ही, उसे इस कुंजी का इस्तेमाल करके, JWT के हस्ताक्षर की पुष्टि करनी होती है. अगर हस्ताक्षर मान्य है, तो JWT पर उसी प्राइवेट कुंजी से हस्ताक्षर किया गया होगा जिससे हस्ताक्षर किया गया है. इसलिए, यह उम्मीद के मुताबिक भेजने वाले के खाते से भेजा गया होगा.
jwt.io/ पर कई लाइब्रेरी उपलब्ध हैं. ये लाइब्रेरी, आपके लिए हस्ताक्षर करने का काम कर सकती हैं. हमारा सुझाव है कि आप इनका इस्तेमाल करें. पूरी जानकारी के लिए, आइए देखते हैं कि मैन्युअल तरीके से साइन किया गया JWT कैसे बनाया जाता है.
वेब पुश और साइन किए गए JWT
हस्ताक्षर किया गया JWT सिर्फ़ एक स्ट्रिंग होता है. हालांकि, इसे बिंदुओं से जुड़ी तीन स्ट्रिंग के तौर पर देखा जा सकता है.
पहली और दूसरी स्ट्रिंग (JWT की जानकारी और JWT डेटा), JSON के हिस्से हैं. इन्हें base64 से कोड में बदला गया है. इसका मतलब है कि इन्हें सार्वजनिक तौर पर पढ़ा जा सकता है.
पहली स्ट्रिंग, JWT के बारे में जानकारी देती है. इससे पता चलता है कि हस्ताक्षर बनाने के लिए किस एल्गोरिदम का इस्तेमाल किया गया था.
वेब पुश के लिए JWT की जानकारी में यह जानकारी होनी चाहिए:
{
"typ": "JWT",
"alg": "ES256"
}
दूसरी स्ट्रिंग, 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 डेटा को यूआरएल के लिए सुरक्षित base64 स्ट्रिंग के तौर पर एन्कोड किया जाता है.
तीसरी स्ट्रिंग, हस्ताक्षर होती है. यह पहली दो स्ट्रिंग (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]
Payload Encryption
अब देखते हैं कि पुश मैसेज के साथ पेलोड कैसे भेजा जा सकता है, ताकि जब हमारा वेब ऐप्लिकेशन पुश मैसेज पाए, तो वह मिले हुए डेटा को ऐक्सेस कर सके.
पुश नोटिफ़िकेशन की अन्य सेवाओं का इस्तेमाल करने वाले लोगों के मन में यह सवाल अक्सर आता है कि वेब पुश नोटिफ़िकेशन के पेलोड को एन्क्रिप्ट (सुरक्षित) क्यों करना पड़ता है? नेटिव ऐप्लिकेशन में, पुश मैसेज के ज़रिए डेटा को सादे टेक्स्ट के तौर पर भेजा जा सकता है.
वेब पुश की एक खास बात यह है कि सभी पुश सेवाएं एक ही एपीआई (वेब पुश प्रोटोकॉल) का इस्तेमाल करती हैं. इसलिए, डेवलपर को यह जानने की ज़रूरत नहीं होती कि पुश सेवा कौनसी है. हम सही फ़ॉर्मैट में अनुरोध कर सकते हैं और पुश मैसेज भेजे जाने की उम्मीद कर सकते हैं. हालांकि, इसकी एक कमी यह है कि डेवलपर, पुश सेवा को ऐसे मैसेज भेज सकते हैं जिन पर भरोसा नहीं किया जा सकता. पुश सेवा, पेलोड को एन्क्रिप्ट (सुरक्षित) करके भेजे गए डेटा को नहीं पढ़ सकती. सिर्फ़ ब्राउज़र ही जानकारी को डिक्रिप्ट कर सकता है. इससे उपयोगकर्ता के डेटा को सुरक्षित रखा जाता है.
Message Encryption spec में, पेलोड को एन्क्रिप्ट (सुरक्षित) करने के बारे में बताया गया है.
पुश मैसेज के पेलोड को एन्क्रिप्ट (सुरक्षित) करने के तरीके जानने से पहले, हमें कुछ ऐसी तकनीकों के बारे में जानना चाहिए जिनका इस्तेमाल एन्क्रिप्शन की प्रोसेस के दौरान किया जाएगा. (पुश एन्क्रिप्शन पर शानदार लेख लिखने के लिए, मैट स्केल्स को बहुत-बहुत धन्यवाद.)
ईसीडीएच और एचकेडीएफ़
एन्क्रिप्शन की पूरी प्रोसेस में, 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: HMAC पर आधारित की डेरिवेशन फ़ंक्शन
Wikipedia पर HKDF के बारे में कम शब्दों में जानकारी दी गई है:
एचकेडीएफ़, एचएमएसी पर आधारित की डेरिवेशन फ़ंक्शन है. यह किसी भी कमज़ोर कुंजी को क्रिप्टोग्राफ़िक तौर पर मज़बूत कुंजी में बदलता है. उदाहरण के लिए, इसका इस्तेमाल Diffie Hellman के ज़रिए शेयर किए गए सीक्रेट को ऐसे मुख्य मटीरियल में बदलने के लिए किया जा सकता है जिसका इस्तेमाल एन्क्रिप्शन, इंटिग्रिटी की जांच या पुष्टि करने के लिए किया जा सकता है.
असल में, 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, सार्वजनिक कुंजियों को शेयर करने और शेयर किया जाने वाला सीक्रेट कोड जनरेट करने का एक सुरक्षित तरीका है. एचकेडीएफ़, असुरक्षित डेटा को सुरक्षित बनाने का एक तरीका है.
इसका इस्तेमाल, हमारे पेलोड को एन्क्रिप्ट (सुरक्षित) करने के दौरान किया जाएगा. अब देखते हैं कि हम किस तरह का डेटा इनपुट के तौर पर लेते हैं और उसे कैसे एन्क्रिप्ट (सुरक्षित) किया जाता है.
इनपुट
जब हमें किसी उपयोगकर्ता को पेलोड के साथ पुश मैसेज भेजना होता है, तो हमें तीन इनपुट की ज़रूरत होती है:
- पे लोड.
PushSubscriptionकाauthसीक्रेट.PushSubscriptionसेp256dhबटन.
हमने देखा है कि auth और p256dh वैल्यू, PushSubscription से वापस पाई जा रही हैं. हालांकि, आपको याद दिला दें कि सदस्यता के लिए हमें इन वैल्यू की ज़रूरत होगी:
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 एलिप्टिक कर्व का इस्तेमाल करके जनरेट की जानी चाहिए. हम Node में ऐसा इस तरह करेंगे:
const localKeysCurve = crypto.createECDH('prime256v1');
localKeysCurve.generateKeys();
const localPublicKey = localKeysCurve.getPublicKey();
const localPrivateKey = localKeysCurve.getPrivateKey();
हम इन कुंजियों को "स्थानीय कुंजियां" कहेंगे. इनका इस्तेमाल सिर्फ़ एन्क्रिप्शन के लिए किया जाता है. इनका ऐप्लिकेशन सर्वर की कुंजियों से कोई लेना-देना नहीं होता.
पेलोड, पुष्टि करने का सीक्रेट, और सदस्यता के लिए सार्वजनिक पासकोड को इनपुट के तौर पर इस्तेमाल किया जाता है. साथ ही, नए जनरेट किए गए साल्ट और स्थानीय पासकोड के सेट का इस्तेमाल किया जाता है. अब हम एन्क्रिप्ट (सुरक्षित) करने के लिए तैयार हैं.
शेयर किया गया सीक्रेट
पहला चरण, सदस्यता की सार्वजनिक कुंजी और हमारी नई निजी कुंजी का इस्तेमाल करके, शेयर किया जाने वाला सीक्रेट कोड बनाना है. क्या आपको एलिस और बॉब के साथ ईसीडीएच के बारे में बताया गया था? बस इसी तरह).
const sharedSecret = localKeysCurve.computeSecret(
subscription.keys.p256dh,
'base64',
);
इसका इस्तेमाल अगले चरण में, छद्म रैंडम कुंजी (पीआरके) का हिसाब लगाने के लिए किया जाता है.
Pseudo random key
स्यूडो रैंडम की (पीआरके), पुश सदस्यता के पुष्टि करने वाले सीक्रेट और अभी बनाए गए शेयर किए गए सीक्रेट का कॉम्बिनेशन होती है.
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 वाला बाइट और फिर एन्क्रिप्ट (सुरक्षित) किया गया डेटा होता है.
हमारा छद्म रैंडम पासकोड, एचकेडीएफ़ के ज़रिए ऑथराइज़ेशन, शेयर किया गया सीक्रेट पासवर्ड, और एन्कोडिंग की जानकारी को प्रोसेस करता है. इसका मतलब है कि यह क्रिप्टोग्राफ़िक तरीके से ज़्यादा सुरक्षित है.
कॉन्टेक्स्ट
"कॉन्टेक्स्ट" बाइट का एक सेट होता है. इसका इस्तेमाल, एन्क्रिप्शन ब्राउज़र में बाद में दो वैल्यू का हिसाब लगाने के लिए किया जाता है. यह असल में बाइट का एक ऐसा कलेक्शन होता है जिसमें सदस्यता की सार्वजनिक कुंजी और स्थानीय सार्वजनिक कुंजी शामिल होती है.
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,
]);
फ़ाइनल कॉन्टेक्स्ट बफ़र एक लेबल होता है. इसमें सदस्यता के सार्वजनिक पासकोड में मौजूद बाइट की संख्या होती है. इसके बाद, पासकोड होता है. इसके बाद, स्थानीय सार्वजनिक पासकोड में मौजूद बाइट की संख्या होती है. इसके बाद, पासकोड होता है.
इस कॉन्टेक्स्ट वैल्यू का इस्तेमाल, नॉनस और कॉन्टेंट एन्क्रिप्शन कुंजी (सीईके) बनाने के लिए किया जा सकता है.
कॉन्टेंट एन्क्रिप्शन की और नॉनस
नॉनस एक ऐसी वैल्यू होती है जो रीप्ले अटैक को रोकती है, क्योंकि इसका इस्तेमाल सिर्फ़ एक बार किया जाना चाहिए.
कॉन्टेंट एन्क्रिप्शन की (सीईके) वह कुंजी है जिसका इस्तेमाल हमारे पेलोड को एन्क्रिप्ट यानी सुरक्षित करने के लिए किया जाएगा.
सबसे पहले, हमें नॉनस और सीईके के लिए डेटा के बाइट बनाने होंगे. यह सिर्फ़ एक कॉन्टेंट एन्कोडिंग स्ट्रिंग है. इसके बाद, हमने अभी-अभी कॉन्टेक्स्ट बफ़र का हिसाब लगाया है:
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 के ज़रिए चलाया जाता है. इसमें सॉल्ट और पीआरके को 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 सिफ़र बनाते हैं. इसमें की के तौर पर कॉन्टेंट एन्क्रिप्शन की का इस्तेमाल किया जाता है और नॉनस, इनिशलाइज़ेशन वेक्टर होता है.
Node में इसे इस तरह किया जाता है:
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()]);
अब हमारे पास एन्क्रिप्ट (सुरक्षित) किया गया पेलोड है. वाह!
अब सिर्फ़ यह तय करना बाकी है कि इस पेलोड को पुश सेवा पर कैसे भेजा जाए.
एन्क्रिप्ट (सुरक्षित) किए गए पेलोड हेडर और बॉडी
इस एन्क्रिप्ट (सुरक्षित) किए गए पेलोड को पुश सेवा पर भेजने के लिए, हमें अपने POST अनुरोध में कुछ अलग हेडर तय करने होंगे.
एन्क्रिप्शन हेडर
'Encryption' हेडर में, पेलोड को एन्क्रिप्ट (सुरक्षित) करने के लिए इस्तेमाल किया गया सॉल्ट होना चाहिए.
16 बाइट के सॉल्ट को base64 यूआरएल सेफ़ एन्कोड किया जाना चाहिए. साथ ही, इसे Encryption हेडर में इस तरह जोड़ा जाना चाहिए:
Encryption: salt=[URL Safe Base64 Encoded Salt]
Crypto-Key हेडर
हमने देखा कि 'ऐप्लिकेशन सर्वर के पासकोड' सेक्शन में, 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-Type' और 'Content-Encoding' हेडर की वैल्यू तय होती हैं.
यह नीचे दिखाया गया है.
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: [Time to live in seconds]
TTL को शून्य पर सेट करने पर, पुश सेवा तुरंत मैसेज डिलीवर करने की कोशिश करेगी. हालांकि, अगर डिवाइस तक नहीं पहुंचा जा सकता, तो आपका मैसेज पुश सेवा की कतार से तुरंत हटा दिया जाएगा.
तकनीकी तौर पर, पुश नोटिफ़िकेशन की सेवा देने वाली कंपनी, पुश नोटिफ़िकेशन की TTL को कम कर सकती है. पुश सेवा से मिले जवाब में TTL हेडर की जांच करके, यह पता लगाया जा सकता है कि ऐसा हुआ है या नहीं.
विषय
ज़रूरी नहीं
विषय ऐसी स्ट्रिंग होती हैं जिनका इस्तेमाल, किसी ऐसे मैसेज को बदलने के लिए किया जा सकता है जिसे अभी तक डिलीवर नहीं किया गया है. हालांकि, ऐसा तब किया जा सकता है, जब दोनों मैसेज के विषय के नाम एक जैसे हों.
यह उन स्थितियों में काम आता है जब डिवाइस के ऑफ़लाइन होने पर कई मैसेज भेजे जाते हैं. साथ ही, आपको सिर्फ़ यह चाहिए कि डिवाइस के चालू होने पर उपयोगकर्ता को सबसे नया मैसेज दिखे.
अत्यावश्यकता
ज़रूरी नहीं
'तत्काल' सेटिंग से पुश सेवा को यह पता चलता है कि उपयोगकर्ता के लिए कोई मैसेज कितना ज़रूरी है. इस कुकी का इस्तेमाल पुश सेवा कर सकती है. इससे उपयोगकर्ता के डिवाइस की बैटरी लाइफ़ को बचाने में मदद मिलती है. ऐसा तब होता है, जब बैटरी कम होने पर सिर्फ़ ज़रूरी मैसेज के लिए डिवाइस को चालू किया जाता है.
हेडर वैल्यू को इस तरह से तय किया जाता है. डिफ़ॉल्ट वैल्यू normal है.
Urgency: [very-low | low | normal | high]
सब कुछ एक साथ
अगर आपको यह जानने के बारे में और सवाल पूछने हैं कि यह सब कैसे काम करता है, तो the web-push-libs org पर जाकर, यह देखा जा सकता है कि लाइब्रेरी, पुश मैसेज को कैसे ट्रिगर करती हैं.
आपके पास एन्क्रिप्ट (सुरक्षित) किया गया पेलोड और ऊपर दिए गए हेडर होने चाहिए. इसके बाद, आपको सिर्फ़ endpoint को PushSubscription में POST अनुरोध करना होगा.
इसलिए, हम इस POST अनुरोध के जवाब का क्या करें?
पुश सेवा से मिला जवाब
पुश सेवा के लिए अनुरोध करने के बाद, आपको जवाब के स्टेटस कोड की जांच करनी होगी. इससे आपको पता चलेगा कि अनुरोध पूरा हुआ है या नहीं.
| स्थिति कोड | ब्यौरा |
|---|---|
| 201 | बनाया गया. पुश मैसेज भेजने का अनुरोध मिला और उसे स्वीकार कर लिया गया. |
| 429 | कई अनुरोध किए गए. इसका मतलब है कि आपके ऐप्लिकेशन सर्वर ने पुश सेवा के लिए तय की गई सीमा पूरी कर ली है. पुश सेवा को 'Retry-After' हेडर शामिल करना चाहिए, ताकि यह पता चल सके कि दूसरा अनुरोध कब किया जा सकता है. |
| 400 | अनुरोध अमान्य है. आम तौर पर, इसका मतलब है कि आपका कोई हेडर अमान्य है या उसे गलत तरीके से फ़ॉर्मैट किया गया है. |
| 404 | नहीं मिला. इससे पता चलता है कि सदस्यता की समयसीमा खत्म हो गई है और इसका इस्तेमाल नहीं किया जा सकता. इस मामले में, आपको `PushSubscription` मिटाना होगा. इसके बाद, आपको क्लाइंट के उपयोगकर्ता को फिर से सदस्यता लेने के लिए इंतज़ार करना होगा. |
| 410 | हटा दिया गया है. सदस्यता अब मान्य नहीं है. इसे ऐप्लिकेशन सर्वर से हटा दिया जाना चाहिए. इसे `PushSubscription` पर `unsubscribe()` को कॉल करके फिर से बनाया जा सकता है. |
| 413 | पेलोड का साइज़ बहुत बड़ा है. पुश सेवा को कम से कम 4096 बाइट (या 4 केबी) के पेलोड का समर्थन करना चाहिए. |
एचटीटीपी स्टेटस कोड के बारे में ज़्यादा जानने के लिए, वेब पुश स्टैंडर्ड (RFC8030) भी पढ़ें.
आगे क्या करें
- वेब पुश नोटिफ़िकेशन की खास जानकारी
- पुश नोटिफ़िकेशन की सुविधा कैसे काम करती है
- किसी उपयोगकर्ता को सदस्यता देना
- अनुमति से जुड़ा यूज़र एक्सपीरियंस
- वेब पुश लाइब्रेरी की मदद से मैसेज भेजना
- वेब पुश प्रोटोकॉल
- पुश इवेंट हैंडल करना
- सूचना दिखाना
- सूचना पाने का तरीका
- सूचनाओं के सामान्य पैटर्न
- पुश नोटिफ़िकेशन के बारे में अक्सर पूछे जाने वाले सवाल
- सामान्य समस्याएं और गड़बड़ियों की रिपोर्ट करना