हमने देखा कि पुश मैसेज ट्रिगर करने के लिए, लाइब्रेरी का इस्तेमाल कैसे किया जा सकता है. हालांकि, ये लाइब्रेरी असल में क्या करती हैं?
ये नेटवर्क के अनुरोध करते हैं और यह पक्का करते हैं कि ये अनुरोध सही फ़ॉर्मैट में हों. नेटवर्क के इस अनुरोध को तय करने वाली खास जानकारी, वेब पुश प्रोटोकॉल है.
इस सेक्शन में बताया गया है कि सर्वर, ऐप्लिकेशन सर्वर पासकोड की मदद से अपनी पहचान कैसे ज़ाहिर कर सकता है. साथ ही, एन्क्रिप्ट (सुरक्षित) किया गया पेलोड और उससे जुड़ा डेटा कैसे भेजा जाता है.
यह वेब पुश का कोई अच्छा पहलू नहीं है और मैं एन्क्रिप्शन का विशेषज्ञ नहीं हूं. हालांकि, आइए हर हिस्से को देखें, क्योंकि यह जानना आसान है कि ये लाइब्रेरी बैकग्राउंड में क्या कर रही हैं.
ऐप्लिकेशन सर्वर पासकोड
जब हम किसी उपयोगकर्ता को सदस्यता देते हैं, तो हम एक 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 भेजने वाले व्यक्ति, JWT पाने वाले व्यक्ति, और JWT की समयसीमा के बारे में जानकारी मिलती है.
वेब पुश के लिए, डेटा का फ़ॉर्मैट इस तरह का होगा:
{
"aud": "https://some-push-service.org",
"exp": "1469618703",
"sub": "mailto:example@web-push-book.org"
}
aud वैल्यू, "ऑडियंस" होती है. इसका मतलब है कि 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 spec के मुताबिक, 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]
पेलोड एन्क्रिप्शन
इसके बाद, आइए देखें कि पुश मैसेज के साथ पेलोड कैसे भेजा जा सकता है. इससे, जब हमारे वेब ऐप्लिकेशन को पुश मैसेज मिलेगा, तो वह उस डेटा को ऐक्सेस कर पाएगा जो उसे मिलता है.
पुश सेवा का इस्तेमाल करने वाले लोगों के मन में अक्सर यह सवाल आता है कि वेब पुश पेलोड को एन्क्रिप्ट (सुरक्षित) करने की ज़रूरत क्यों होती है? खास ऐप्लिकेशन की मदद से, पुश मैसेज में डेटा को सादे टेक्स्ट के तौर पर भेजा जा सकता है.
वेब पुश की एक अच्छी बात यह है कि सभी पुश सेवाएं एक ही एपीआई (वेब पुश प्रोटोकॉल) का इस्तेमाल करती हैं. इसलिए, डेवलपर को इस बात की चिंता करने की ज़रूरत नहीं होती कि पुश सेवा कौनसी है. हम सही फ़ॉर्मैट में अनुरोध कर सकते हैं और पुश मैसेज भेजे जाने की उम्मीद कर सकते हैं. इसका नुकसान यह है कि डेवलपर, किसी ऐसी पुश सेवा को मैसेज भेज सकते हैं जो भरोसेमंद नहीं है. पेलोड को एन्क्रिप्ट (सुरक्षित) करके, पुश सेवा भेजे गए डेटा को नहीं पढ़ सकती. सिर्फ़ ब्राउज़र ही जानकारी को डिक्रिप्ट कर सकता है. इससे उपयोगकर्ता का डेटा सुरक्षित रहता है.
पेलोड को एन्क्रिप्ट (सुरक्षित) करने के तरीके के बारे में, मैसेज को एन्क्रिप्ट करने के तरीके में बताया गया है.
पुश मैसेज के पेलोड को एन्क्रिप्ट करने के तरीके जानने से पहले, हमें एन्क्रिप्ट करने की प्रोसेस के दौरान इस्तेमाल होने वाली कुछ तकनीकों के बारे में जानना चाहिए. (पुश एन्क्रिप्शन पर बेहतरीन लेख लिखने के लिए, मैट स्केल्स को बहुत-बहुत धन्यवाद.)
ईसीडीएच और एचकेडीएफ़
एन्क्रिप्ट करने की पूरी प्रोसेस के दौरान, ईसीडीएच और एचकेडीएफ़, दोनों का इस्तेमाल किया जाता है. साथ ही, ये जानकारी को एन्क्रिप्ट करने के लिए फ़ायदेमंद होते हैं.
ईसीडीएच: एलिप्टिक कर्व डिफ़ी-हेलमैन कुंजी एक्सचेंज
मान लें कि दो लोग हैं, मयंक और ऋतिक, जो आपस में जानकारी शेयर करना चाहते हैं. मयंक और ऋतिक, दोनों के पास अपनी सार्वजनिक और निजी कुंजियां हैं. मयंक और ऋतिक, एक-दूसरे के साथ अपने सार्वजनिक पासकोड शेयर करते हैं.
ईसीडीएच से जनरेट की गई कुंजियों की खास बात यह है कि ऐलिस अपने निजी पासकोड और बॉब के सार्वजनिक पासकोड का इस्तेमाल करके, सीक्रेट वैल्यू 'X' बना सकती है. बॉब भी ऐसा कर सकता है. वह अपने निजी पासकोड और एलिस के सार्वजनिक पासकोड का इस्तेमाल करके, 'X' वैल्यू जनरेट कर सकता है. इससे 'X' एक शेयर किया गया सीक्रेट बन जाता है. साथ ही, अभिषेक और गुंजन को सिर्फ़ अपना सार्वजनिक पासकोड शेयर करना पड़ता है. अब ऋतिक और मयंक, 'X' का इस्तेमाल करके, एक-दूसरे को भेजे जाने वाले मैसेज को एन्क्रिप्ट (सुरक्षित) और डिक्रिप्ट कर सकते हैं.
मेरी जानकारी के मुताबिक, ईसीडीएच उन कर्व की प्रॉपर्टी तय करता है जो शेयर किए गए सीक्रेट 'X' को बनाने की इस "सुविधा" की अनुमति देते हैं.
यह ईसीडीएच के बारे में खास जानकारी है. अगर आपको इसके बारे में ज़्यादा जानना है, तो हमारा सुझाव है कि आप ईसीडीएच के बारे में ज़्यादा जानकारी देने वाला वीडियो देखें.
कोड के मामले में, ज़्यादातर भाषाओं / प्लैटफ़ॉर्म में लाइब्रेरी होती हैं, ताकि इन कुंजियों को आसानी से जनरेट किया जा सके.
Node में, हम ये काम करेंगे:
const keyCurve = crypto.createECDH('prime256v1');
keyCurve.generateKeys();
const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();
HKDF: एचएमएसी पर आधारित की डेरिवेशन फ़ंक्शन
Wikipedia पर 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बटन.
हमने देखा है कि 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',
);
इसका इस्तेमाल अगले चरण में, स्यूडो रैंडम की (पीआरके) का हिसाब लगाने के लिए किया जाता है.
एक प्रोसेस के बावजूद रैंडम डेटा निकलना
स्यूडो रैंडम की (पीआरके), पुश सदस्यता के ऑथराइज़ेशन सीक्रेट और शेयर किए गए सीक्रेट का कॉम्बिनेशन है. हमने इसे अभी-अभी बनाया है.
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 वैल्यू वाले दो बाइट होंगे.इसका मतलब है कि कोई पैडिंग मौजूद नहीं है. इन दो बाइट के बाद, आपको पेलोड दिखेगा. अगर आपने पांच बाइट की पैडिंग जोड़ी है, तो पहले दो बाइट की वैल्यू पांच होगी. इसलिए, उपभोक्ता पहले पांच बाइट पढ़ेगा और फिर पेलोड पढ़ना शुरू करेगा.
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 हेडर का इस्तेमाल किया गया है. इसमें सार्वजनिक ऐप्लिकेशन सर्वर पासकोड शामिल है.
इस हेडर का इस्तेमाल, पेलोड को एन्क्रिप्ट करने के लिए इस्तेमाल किए गए लोकल सार्वजनिक पासकोड को शेयर करने के लिए भी किया जाता है.
इससे मिलने वाला हेडर कुछ ऐसा दिखेगा:
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 की समयसीमा खत्म होने पर, मैसेज को पुश सेवा की सूची से हटा दिया जाएगा और उसे डिलीवर नहीं किया जाएगा.
TTL: [Time to live in seconds]
अगर आपने TTL को शून्य पर सेट किया है, तो पुश सेवा मैसेज को तुरंत डिलीवर करने की कोशिश करेगी. हालांकि, अगर डिवाइस से संपर्क नहीं हो पाता है, तो आपके मैसेज को पुश सेवा की सूची से तुरंत हटा दिया जाएगा.
तकनीकी तौर पर, पुश सेवा किसी पुश मैसेज के TTL को कम कर सकती है. पुश सेवा से मिले जवाब में TTL हेडर की जांच करके, यह पता लगाया जा सकता है कि ऐसा हुआ है या नहीं.
विषय
ज़रूरी नहीं
विषय ऐसी स्ट्रिंग होती हैं जिनका इस्तेमाल, किसी नए मैसेज के साथ, बाकी बचे मैसेज को बदलने के लिए किया जा सकता है. ऐसा तब किया जा सकता है, जब उनके विषय के नाम मेल खाते हों.
यह सुविधा उन मामलों में काम आती है जहां डिवाइस के ऑफ़लाइन होने पर कई मैसेज भेजे जाते हैं. साथ ही, आपको सिर्फ़ यह चाहिए कि डिवाइस चालू होने पर उपयोगकर्ता को सबसे नया मैसेज दिखे.
अत्यावश्यकता
ज़रूरी नहीं
ज़रूरी होने से, पुश सेवा को पता चलता है कि उपयोगकर्ता के लिए कोई मैसेज कितना ज़रूरी है. पुश सेवा इसका इस्तेमाल करके, उपयोगकर्ता के डिवाइस की बैटरी लाइफ़ बचा सकती है. इसके लिए, बैटरी कम होने पर सिर्फ़ ज़रूरी मैसेज के लिए डिवाइस को चालू किया जाता है.
हेडर की वैल्यू को यहां दिखाए गए तरीके से तय किया जाता है. डिफ़ॉल्ट वैल्यू normal है.
Urgency: [very-low | low | normal | high]
सभी सुविधाएं एक साथ
अगर आपको इस बारे में और सवाल पूछने हैं कि यह सब कैसे काम करता है, तो web-push-libs org पर जाकर, यह देखा जा सकता है कि लाइब्रेरी, पुश मैसेज को कैसे ट्रिगर करती हैं.
एन्क्रिप्ट यानी सुरक्षित किया गया पेलोड और ऊपर दिए गए हेडर मिलने के बाद, आपको PushSubscription में endpoint पर पोस्ट अनुरोध करना होगा.
तो हम इस POST अनुरोध के जवाब का क्या करें?
पुश सेवा से मिला जवाब
पुश सेवा को अनुरोध भेजने के बाद, आपको जवाब के स्टेटस कोड की जांच करनी होगी. इससे आपको पता चलेगा कि अनुरोध पूरा हुआ या नहीं.
| स्थिति कोड | ब्यौरा |
|---|---|
| 201 | बनाया गया. पुश मैसेज भेजने का अनुरोध मिला और उसे स्वीकार कर लिया गया. |
| 429 | कई अनुरोध किए गए. इसका मतलब है कि आपके ऐप्लिकेशन सर्वर ने पुश सेवा के साथ अनुरोध संख्या सीमित करने की सीमा पूरी कर ली है. पुश सेवा में 'Retry-After' हेडर शामिल होना चाहिए, ताकि यह पता चल सके कि अगला अनुरोध कब किया जा सकता है. |
| 400 | अनुरोध अमान्य है. आम तौर पर, इसका मतलब है कि आपका कोई हेडर अमान्य है या उसे गलत तरीके से फ़ॉर्मैट किया गया है. |
| 404 | नहीं मिला. इससे पता चलता है कि सदस्यता की समयसीमा खत्म हो गई है और इसका इस्तेमाल नहीं किया जा सकता. इस मामले में, आपको `PushSubscription` को मिटाना चाहिए. साथ ही, क्लाइंट के उपयोगकर्ता की सदस्यता को फिर से चालू करने का इंतज़ार करना चाहिए. |
| 410 | चला गया. सदस्यता अब मान्य नहीं है. इसलिए, इसे ऐप्लिकेशन सर्वर से हटा दिया जाना चाहिए. `PushSubscription` पर `unsubscribe()` को कॉल करके, इसे फिर से जनरेट किया जा सकता है. |
| 413 | पेलोड का साइज़ बहुत बड़ा है. पुश सेवा के लिए, पेलोड का साइज़ कम से कम 4096 बाइट (या 4 केबी) होना चाहिए. |
एचटीटीपी स्टेटस कोड के बारे में ज़्यादा जानने के लिए, वेब पुश स्टैंडर्ड (RFC8030) भी पढ़ा जा सकता है.
आगे क्या करना है
- वेब ऐप्लिकेशन से भेजे गए पुश नोटिफ़िकेशन के बारे में खास जानकारी
- पुश करने की सुविधा कैसे काम करती है
- किसी उपयोगकर्ता को सदस्यता देना
- अनुमति का यूएक्स
- वेब पुश लाइब्रेरी की मदद से मैसेज भेजना
- वेब पुश प्रोटोकॉल
- पुश इवेंट मैनेज करना
- सूचना दिखाना
- सूचनाओं के व्यवहार से जुड़ी नीति
- सूचनाओं के सामान्य पैटर्न
- पुश नोटिफ़िकेशन के बारे में अक्सर पूछे जाने वाले सवाल
- सामान्य समस्याएं और गड़बड़ियों की शिकायत करना