เราได้เห็นวิธีใช้ไลบรารีเพื่อทริกเกอร์ข้อความพุชแล้ว แต่ ว่าไลบรารีเหล่านี้กำลังทำอะไรอยู่
นั่นคืออุปกรณ์กำลังส่งคำขอเครือข่ายไปพร้อมกับตรวจสอบว่าคำขอดังกล่าวอยู่ในรูปแบบที่ถูกต้อง ข้อกำหนดที่กำหนดคำขอเครือข่ายนี้คือ โปรโตคอลพุชบนเว็บ
ส่วนนี้จะอธิบายวิธีที่เซิร์ฟเวอร์ระบุตัวเองด้วยแอปพลิเคชัน คีย์เซิร์ฟเวอร์และวิธีส่งเพย์โหลดที่เข้ารหัสและข้อมูลที่เกี่ยวข้อง
นี่ไม่ใช่เรื่องพุชจากเว็บและไม่เชี่ยวชาญเรื่องการเข้ารหัส แต่ลองดูรายละเอียดกันดีกว่า แต่ละชิ้น เนื่องจากมีประโยชน์ในการที่จะทราบว่าไลบรารีเหล่านี้กำลังทำอะไรภายใน
คีย์เซิร์ฟเวอร์แอปพลิเคชัน
เมื่อสมัครใช้บริการผู้ใช้ เราจะส่ง applicationServerKey
ระบบจะส่งคีย์นี้ไปยังบริการ Push และใช้เพื่อตรวจสอบว่าแอปพลิเคชันที่สมัครรับข้อมูลผู้ใช้เป็นแอปพลิเคชันเดียวกันกับที่เรียกให้แสดงข้อความ Push ด้วย
เมื่อเราทริกเกอร์ข้อความ Push จะมีชุดส่วนหัวที่เราส่งไปซึ่งอนุญาตให้บริการ Push ตรวจสอบสิทธิ์แอปพลิเคชัน (ข้อกำหนด VAPID ระบุไว้)
ทั้งหมดนี้หมายความว่าอย่างไรและเกิดอะไรขึ้นบ้าง นี่คือขั้นตอนต่างๆ ที่ใช้สำหรับ การตรวจสอบสิทธิ์แอปพลิเคชันเซิร์ฟเวอร์:
- แอปพลิเคชันเซิร์ฟเวอร์จะเซ็นข้อมูล JSON บางอย่างด้วยคีย์แอปพลิเคชันส่วนตัว
- ระบบจะส่งข้อมูลที่ลงนามนี้ไปยังบริการพุชเป็นส่วนหัวในคำขอ POST
- บริการพุชใช้คีย์สาธารณะที่จัดเก็บไว้ซึ่งคุณได้รับมา
pushManager.subscribe()
สำหรับการตรวจสอบว่าข้อมูลที่ได้รับลงชื่อโดย คีย์ส่วนตัวที่เกี่ยวข้องกับคีย์สาธารณะ โปรดทราบ: คีย์สาธารณะคือapplicationServerKey
ที่ส่งไปยังการเรียก subscribe - หากข้อมูลที่มีการรับรองถูกต้อง บริการ Push จะส่งข้อความ Push ไปยังผู้ใช้
ตัวอย่างของขั้นตอนนี้คือ (โปรดดูคำอธิบายที่ด้านซ้ายล่างเพื่อระบุคีย์สาธารณะและคีย์ส่วนตัว)
"ข้อมูลที่ลงนาม" ที่เพิ่มลงในส่วนหัวในคําขอคือ JSON Web Token
โทเค็นเว็บ JSON
JSON Web Token (หรือ JWT เรียกสั้นๆ) คือวิธีส่งข้อความไปยังบุคคลที่สามเพื่อให้ผู้รับตรวจสอบได้ว่าใครเป็นผู้ส่ง
เมื่อบุคคลที่สามได้รับข้อความ บุคคลที่สามจะต้องได้รับผู้ส่ง คีย์สาธารณะและใช้เพื่อตรวจสอบลายเซ็นของ JWT หาก ลายเซ็นถูกต้อง JWT ต้องลงนามด้วยการจับคู่ที่ตรงกัน คีย์ส่วนตัว ดังนั้นต้องมาจากผู้ส่งที่คาดไว้
มีไลบรารีมากมายใน https://jwt.io/ ที่สามารถดำเนินการลงนามให้คุณได้ และเราขอแนะนำให้คุณดำเนินการดังกล่าวหากทำได้ เพื่อความครบถ้วนสมบูรณ์ ให้มาดูวิธีสร้าง JWT แบบมีเครื่องหมายด้วยตนเองกัน
พุชจากเว็บและ JWT ที่ลงชื่อ
JWT ที่มีการลงชื่อเป็นเพียงสตริง แต่ก็อาจมองว่าเป็นสตริงที่มี 3 สตริงรวมกัน ด้วยจุด
สตริงแรกและสตริงที่ 2 (ข้อมูล JWT และข้อมูล JWT) คือชิ้นส่วนของ JSON ที่เข้ารหัส Base64 ซึ่งหมายความว่าสามารถอ่านได้แบบสาธารณะ
สตริงแรกคือข้อมูลเกี่ยวกับ JWT เอง ซึ่งใช้ระบุว่าอัลกอริทึมใด ในการสร้างลายเซ็น
ข้อมูล JWT สำหรับเว็บพุชต้องมีข้อมูลต่อไปนี้
{
"typ": "JWT",
"alg": "ES256"
}
สตริงที่ 2 คือข้อมูล JWT ข้อมูลนี้ระบุข้อมูลเกี่ยวกับผู้ส่ง JWT, ผู้ที่ JWT กำหนดให้ใช้ และระยะเวลาที่ JWT ใช้งานได้
สําหรับ Web Push ข้อมูลจะมีรูปแบบดังนี้
{
"aud": "https://some-push-service.org",
"exp": "1469618703",
"sub": "mailto:example@web-push-book.org"
}
ค่า aud
คือ "กลุ่มเป้าหมาย" กล่าวคือ JWT มีไว้สำหรับใคร สําหรับ Push บนเว็บ กลุ่มเป้าหมายคือบริการ Push เราจึงตั้งค่าเป็นต้นทางของบริการ Push
ค่า exp
คือวันหมดอายุของ JWT ซึ่งจะป้องกันไม่ให้ผู้แอบดูใช้ JWT ซ้ำได้หากมีการสกัดกั้น การหมดอายุเป็นการประทับเวลาเป็นวินาทีและต้องไม่เกิน 24 ชั่วโมง
ใน Node.js การหมดอายุจะตั้งค่าโดยใช้:
Math.floor(Date.now() / 1000) + 12 * 60 * 60;
จะใช้เวลา 12 ชั่วโมงแทนที่จะเป็น 24 ชั่วโมงเพื่อหลีกเลี่ยง ปัญหาใดๆ เกี่ยวกับความแตกต่างของนาฬิการะหว่างแอปพลิเคชันที่ส่งและบริการพุช
สุดท้าย ค่า sub
ต้องเป็น URL หรืออีเมล mailto
ทั้งนี้ก็เพื่อที่หากบริการพุชจำเป็นต้องติดต่อกับผู้ส่ง บริการจะหา
ข้อมูลติดต่อจาก JWT (นี่คือเหตุผลที่ไลบรารีเว็บพุชต้องการ
อีเมล)
ข้อมูล JWT ได้รับการเข้ารหัสเป็นสตริง Base64 ที่ปลอดภัยสำหรับ URL เช่นเดียวกับข้อมูล JWT
สตริงที่ 3 ซึ่งเป็นลายเซ็นคือผลลัพธ์ที่ได้จากการนําสตริง 2 รายการแรก (ข้อมูล JWT และข้อมูล JWT) มารวมกันโดยใช้อักขระจุด ซึ่งเราจะเรียกว่า "โทเค็นที่ไม่มีลายเซ็น" และเซ็นชื่อ
กระบวนการลงนามกำหนดให้ต้องเข้ารหัส "โทเค็นที่ไม่มีการรับรอง" โดยใช้ ES256 ตามข้อกำหนด JWT นั้น ES256 ย่อมาจาก "ECDSA โดยใช้เส้นโค้ง P-256 และอัลกอริทึมแฮช SHA-256" เมื่อใช้เว็บคริปโต คุณสามารถสร้างลายเซ็นได้ ดังนี้
// 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 โดยใช้คีย์เซิร์ฟเวอร์แอปพลิเคชันสาธารณะได้ เพื่อถอดรหัสลายเซ็นและตรวจสอบว่าสตริงที่ถอดรหัสนั้นเหมือนกัน เป็น "โทเค็นที่ไม่มีการรับรอง" (นั่นคือ 2 สตริงแรกใน JWT)
ระบบจะส่ง JWT ที่ลงชื่อ (สตริงทั้ง 3 รายการที่ต่อด้วยจุด) ไปยังบริการ Push บนเว็บเป็นส่วนหัว Authorization
ที่มี WebPush
นำหน้า ดังนี้
Authorization: 'WebPush [JWT Info].[JWT Data].[Signature]';
นอกจากนี้ Web Push Protocol ยังระบุว่าคีย์เซิร์ฟเวอร์ของแอปพลิเคชันสาธารณะต้องมีลักษณะดังนี้
ที่ส่งในส่วนหัว Crypto-Key
เป็นสตริงที่เข้ารหัส base64 ที่ปลอดภัยสำหรับ URL ด้วย
p256ecdsa=
ได้แทรกข้อความไว้ข้างหน้า
Crypto-Key: p256ecdsa=[URL Safe Base64 Public Application Server Key]
การเข้ารหัสเพย์โหลด
ต่อไปมาดูวิธีส่งเพย์โหลดด้วยข้อความ Push เพื่อให้เว็บแอปเข้าถึงข้อมูลที่รับได้เมื่อได้รับข้อความ Push
คำถามทั่วไปที่เกิดขึ้นจากผู้ที่เคยใช้บริการพุชอื่นๆ คือเหตุใดเว็บจึงพุช เพย์โหลดต้องมีการเข้ารหัสไหม เมื่อใช้แอปที่มาพร้อมเครื่อง ข้อความพุชจะส่งข้อมูลในรูปแบบข้อความธรรมดาได้
หนึ่งในข้อดีของ Web Push คือนักพัฒนาแอปไม่ต้องกังวลว่าบริการ Push ใดจะเป็นผู้ส่ง เนื่องจากบริการ Push ทั้งหมดใช้ API เดียวกัน (โปรโตคอล Web Push) เราจึงส่งคำขอในรูปแบบที่ถูกต้องและคาดหวังว่าระบบจะส่งข้อความ Push ข้อเสียของวิธีนี้ก็คือนักพัฒนาซอฟต์แวร์สามารถ ส่งข้อความไปยังบริการพุชที่ไม่น่าเชื่อถือ การเข้ารหัสเพย์โหลดจะทำให้บริการ Push อ่านข้อมูลที่ส่งไม่ได้ และเฉพาะเบราว์เซอร์เท่านั้นที่สามารถถอดรหัสข้อมูลได้ ซึ่งจะช่วยปกป้อง
การเข้ารหัสของเพย์โหลดจะกำหนดไว้ในข้อกำหนดการเข้ารหัสข้อความ
ก่อนที่เราจะดูขั้นตอนเฉพาะในการเข้ารหัสเพย์โหลดข้อความพุช เราควรพูดถึงเทคนิคบางอย่างที่จะใช้ระหว่างการเข้ารหัส ขั้นตอนได้ (เคล็ดลับหมวกใหญ่จาก Mat Scales สำหรับบทความที่ยอดเยี่ยมเกี่ยวกับการพุช encryption.)
ECDH และ HKDF
ทั้ง ECDH และ HKDF จะใช้ตลอดกระบวนการเข้ารหัสและมีประโยชน์สำหรับวัตถุประสงค์ในการเข้ารหัสข้อมูล
ECDH: การแลกเปลี่ยนกุญแจ Elliptic Curve Diffie-Hellman
ลองนึกภาพว่าคุณมีคน 2 คนที่ต้องการแชร์ข้อมูลกัน คือชื่อ Alice และ Bob ทั้งขวัญใจและบัญชามีคีย์สาธารณะและคีย์ส่วนตัวของตนเอง Alice และ Bob แชร์คีย์สาธารณะให้กัน
คุณสมบัติที่มีประโยชน์ของคีย์ที่สร้างขึ้นด้วย ECDH คือ Alice สามารถใช้คีย์ส่วนตัวของเธอและคีย์สาธารณะของ Bob เพื่อสร้างค่าลับ "X" บ็อบก็ทำแบบเดียวกันได้โดยใช้คีย์ส่วนตัวและคีย์สาธารณะของอลิสเพื่อสร้างค่า "X" เดียวกัน ทำให้ "X" ข้อมูลลับที่ใช้ร่วมกัน เพราะ Alice และ Bob แค่แชร์คีย์สาธารณะ ตอนนี้บัญชาและขวัญใจจะใช้ "X" เพื่อเข้ารหัสและถอดรหัสข้อความระหว่างกันได้
ECDH ตามความรู้เท่าที่มีของฉันจะกำหนดคุณสมบัติของเส้นโค้งที่อนุญาตให้มี "ฟีเจอร์" นี้ในการสร้างคีย์ลับที่แชร์ "X"
นี่เป็นคำอธิบายระดับสูงของ ECDH หากต้องการดูข้อมูลเพิ่มเติม เราขอแนะนำให้ดูวิดีโอนี้
ในส่วนของโค้ด ภาษา/แพลตฟอร์มส่วนใหญ่จะมีไลบรารีที่ช่วยให้สร้างคีย์เหล่านี้ได้ง่าย
ในโหนด เราจะดำเนินการต่อไปนี้
const keyCurve = crypto.createECDH('prime256v1');
keyCurve.generateKeys();
const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();
HKDF: ฟังก์ชันการสร้างคีย์ตาม HMAC
Wikipedia มีคำอธิบายสั้นๆ เกี่ยวกับ HKDF ดังนี้
HKDF คือฟังก์ชันการสร้างคีย์ที่อิงตาม HMAC ซึ่งจะเปลี่ยนข้อมูลคีย์ที่อ่อนแอให้กลายเป็นข้อมูลคีย์ที่รัดกุมทางวิทยาการเข้ารหัส ตัวอย่างเช่น สามารถใช้เพื่อแปลงความลับที่แชร์ซึ่ง 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);
}
เคล็ดลับหมวกสำหรับบทความของ Mat Scale สำหรับโค้ดตัวอย่างนี้
ซึ่งครอบคลุมถึง ECDH และ HKDF อย่างคร่าวๆ
ซึ่งเป็นวิธีที่ปลอดภัยในการแชร์คีย์สาธารณะและสร้างข้อมูลลับที่ใช้ร่วมกัน HKDF เป็นวิธีหนึ่งในการ เนื้อหาที่ไม่ปลอดภัยและทำให้ไฟล์ปลอดภัย
ซึ่งจะใช้ในระหว่างการเข้ารหัสของเพย์โหลด ต่อไปมาดูสิ่งที่เราใช้เป็นอินพุตและวิธีเข้ารหัสกัน
อินพุต
เมื่อเราต้องการส่งข้อความพุชไปยังผู้ใช้ด้วยเพย์โหลด เราจะต้องป้อนข้อมูล 3 ประเภทดังนี้
- เพย์โหลด
auth
secret from thePushSubscription
- กุญแจ
p256dh
จากPushSubscription
เราเห็นว่ามีการดึงค่า auth
และ p256dh
จาก PushSubscription
แต่สำหรับ
เนื่องจากเป็นการสมัครใช้บริการ เราต้องการค่าเหล่านี้
subscription.toJSON().keys.auth;
subscription.toJSON().keys.p256dh;
subscription.getKey('auth');
subscription.getKey('p256dh');
ค่า auth
ควรถือเป็นข้อมูลลับและไม่มีการแชร์นอกแอปพลิเคชันของคุณ
คีย์ p256dh
เป็นคีย์สาธารณะ ซึ่งบางครั้งเรียกว่าคีย์สาธารณะของไคลเอ็นต์ ที่นี่
เราจะเรียก p256dh
เป็นคีย์สาธารณะของการสมัครใช้บริการ สร้างคีย์สาธารณะของการสมัครใช้บริการแล้ว
ตามเบราว์เซอร์ เบราว์เซอร์จะเก็บคีย์ส่วนตัวไว้เป็นความลับและใช้สำหรับการถอดรหัส
เพย์โหลด
ค่า 3 ค่านี้ ได้แก่ auth
, p256dh
และ payload
ต้องใช้เป็นอินพุต และผลลัพธ์ของกระบวนการเข้ารหัสจะเป็นเพย์โหลดที่เข้ารหัส ค่า Salt และคีย์สาธารณะที่ใช้เพื่อเข้ารหัสข้อมูลเท่านั้น
เกลือ
Salt ต้องเป็นข้อมูลแบบสุ่มขนาด 16 ไบต์ ใน NodeJS เราจะทำดังนี้เพื่อสร้าง Salt
const salt = crypto.randomBytes(16);
คีย์สาธารณะ / คีย์ส่วนตัว
คีย์สาธารณะและส่วนตัวควรสร้างขึ้นโดยใช้รูปไข่ P-256 ซึ่งเราจะทำใน Node ดังนี้
const localKeysCurve = crypto.createECDH('prime256v1');
localKeysCurve.generateKeys();
const localPublicKey = localKeysCurve.getPublicKey();
const localPrivateKey = localKeysCurve.getPrivateKey();
เราจะเรียกคีย์เหล่านี้ว่า "คีย์ในเครื่อง" ซึ่งจะใช้เพื่อการเข้ารหัสเท่านั้นและมี ไม่ต้องทำอะไรกับคีย์แอปพลิเคชันเซิร์ฟเวอร์
โดยมีเพย์โหลด ข้อมูลลับในการตรวจสอบสิทธิ์ และคีย์สาธารณะของการสมัครใช้บริการเป็นอินพุต และมีรหัสที่สร้างขึ้นใหม่ Salt และชุดคีย์ภายใน เราพร้อมที่จะทำการเข้ารหัส
คีย์ลับที่แชร์
ขั้นตอนแรกคือการสร้างข้อมูลลับที่ใช้ร่วมกันโดยใช้คีย์สาธารณะของการสมัคร คีย์ส่วนตัว (จำคำอธิบาย ECDH กับ Alice และ Bob ได้ไหม ง่ายๆ แค่นั้นเลย)
const sharedSecret = localKeysCurve.computeSecret(
subscription.keys.p256dh,
'base64',
);
ซึ่งจะใช้ในขั้นตอนถัดไปเพื่อคำนวณคีย์แบบสุ่ม Pseudo (PRK)
คีย์แบบสุ่มเทียม
Pseudo Random Key (PRK) คือชุดค่าผสมของการตรวจสอบสิทธิ์ของการสมัครใช้บริการพุช และข้อมูลลับที่ใช้ร่วมกันที่เราเพิ่งสร้างขึ้น
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 (กล่าวคือทำให้การเข้ารหัสมีความรัดกุมมากขึ้น)
บริบท
"บริบท" เป็นชุดไบต์ที่ใช้ในการคำนวณค่า 2 ค่าในภายหลังในการเข้ารหัส เบราว์เซอร์ โดยพื้นฐานแล้วคืออาร์เรย์ของไบต์ที่มีคีย์สาธารณะสำหรับการสมัครใช้บริการและคีย์สาธารณะในเครื่อง
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 และคีย์การเข้ารหัสเนื้อหา (CEK) ได้
คีย์การเข้ารหัสเนื้อหาและ Nonce
Nonce คือค่าที่ป้องกันการโจมตีด้วยการเล่นซ้ำ เนื่องจากควรใช้เพียงครั้งเดียว
คีย์การเข้ารหัสเนื้อหา (CEK) คือคีย์ที่จะใช้ในการเข้ารหัสเพย์โหลดของเราในท้ายที่สุด
ก่อนอื่นเราต้องสร้างไบต์ของข้อมูลสําหรับ Nonce และ CEK ซึ่งก็คือสตริงการเข้ารหัสเนื้อหาตามด้วยบัฟเฟอร์บริบทที่เราเพิ่งคํานวณ
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 ซึ่งรวม Salt และ 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);
ซึ่งจะช่วยให้เราได้รับคีย์การเข้ารหัสเนื้อหาและ Nonce
ดำเนินการเข้ารหัส
เมื่อเรามีคีย์การเข้ารหัสเนื้อหาแล้ว เราก็จะเข้ารหัสเพย์โหลดได้
เราสร้างการเข้ารหัส AES128 โดยใช้คีย์การเข้ารหัสเนื้อหาเป็นคีย์ และ Nonce จะเป็นเวกเตอร์การเริ่มต้น
ใน Node การดำเนินการนี้จะมีลักษณะดังนี้
const cipher = crypto.createCipheriv(
'id-aes128-GCM',
contentEncryptionKey,
nonce,
);
ก่อนที่จะเข้ารหัสเพย์โหลด เราต้องกำหนดจำนวนการเติมที่เราต้องการเพิ่มไว้ด้านหน้าของเพย์โหลด เหตุผลที่เราต้องการเพิ่มการเติมคือเพื่อป้องกันความเสี่ยงที่นักดักฟังจะระบุ "ประเภท" ของข้อความตามขนาดของเพย์โหลดได้
คุณต้องเพิ่มการถ่วง 2 ไบต์เพื่อระบุความยาวของการถ่วงเพิ่มเติม
ตัวอย่างเช่น หากไม่ได้เพิ่มการเติม คุณจะมีไบต์ 2 รายการที่มีค่า 0 ซึ่งหมายความว่าไม่มีการเติม หลังจากไบต์ 2 รายการนี้ คุณจะต้องอ่านเพย์โหลด หากคุณเพิ่มระยะห่างจากขอบ 5 ไบต์ 2 ไบต์แรกจะมีค่าเป็น 5 ดังนั้นผู้บริโภคจะอ่านอีก 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()]);
ตอนนี้เรามีเพย์โหลดที่เข้ารหัสแล้ว เย้!
ขั้นตอนสุดท้ายคือกำหนดวิธีส่งเพย์โหลดนี้ไปยังบริการ Push
ส่วนหัวและเนื้อหาของเพย์โหลดที่เข้ารหัส
หากต้องการส่งเพย์โหลดที่เข้ารหัสนี้ไปยังบริการ Push เราจะต้องกำหนดส่วนหัวที่แตกต่างกัน 2-3 รายการในคำขอ POST
ส่วนหัวการเข้ารหัส
ส่วนหัว "การเข้ารหัส" ต้องมีเกลือที่ใช้เข้ารหัสเพย์โหลด
เกลือ 16 ไบต์ควรเข้ารหัส Base64 URL-safe และเพิ่มลงในส่วนหัวการเข้ารหัส ดังนี้
Encryption: salt=[URL Safe Base64 Encoded Salt]
ส่วนหัว Crypto-Key
เราพบว่าส่วนหัว Crypto-Key
มีการใช้งานภายใต้ "Application Server Keys"
ที่จะมีคีย์แอปพลิเคชันเซิร์ฟเวอร์สาธารณะ
ส่วนหัวนี้ยังใช้เพื่อแชร์คีย์สาธารณะในเครื่องที่ใช้เข้ารหัสด้วย เพย์โหลด
ส่วนหัวที่ได้จะมีลักษณะดังนี้
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 / เซิร์ฟเวอร์แอปพลิเคชัน (เช่น วิธีระบุแอปพลิเคชันด้วยบริการ Push) และส่วนหัวที่ใช้ส่งเพย์โหลดที่เข้ารหัสแล้ว
นอกจากนี้ยังมีส่วนหัวเพิ่มเติมที่บริการ Push ใช้เพื่อเปลี่ยนลักษณะการทํางานของข้อความที่ส่ง โดยจำเป็นต้องระบุส่วนหัวเหล่านี้บางรายการ ในขณะที่ส่วนหัวอื่นๆ จะเป็นส่วนหัวที่ไม่บังคับ
ส่วนหัว TTL
จำเป็น
TTL
(หรือ Time To Live) คือจำนวนเต็มซึ่งระบุจำนวนวินาทีที่คุณต้องการให้ข้อความ Push แสดงอยู่ในบริการ Push ก่อนที่จะส่ง เมื่อ TTL
หมดอายุ ระบบจะนำข้อความออกจากคิวบริการ Push และจะไม่นำส่งข้อความ
TTL: [Time to live in seconds]
หากคุณตั้งค่า TTL
เป็น 0 บริการ Push จะพยายามส่งข้อความทันที แต่หากเข้าถึงอุปกรณ์ไม่ได้ ระบบจะทิ้งข้อความออกจากคิวบริการ Push ทันที
ในทางเทคนิคแล้ว บริการ Push สามารถลด TTL
ของข้อความ Push ได้หากต้องการ คุณจะทราบได้ว่าปัญหานี้เกิดขึ้นหรือไม่ โดยการตรวจสอบส่วนหัว TTL
ใน
การตอบสนองจากบริการพุช
หัวข้อ
ไม่บังคับ
หัวข้อคือสตริงที่สามารถใช้เพื่อแทนที่ข้อความที่รอดำเนินการด้วย ข้อความใหม่หากมีชื่อหัวข้อที่ตรงกัน
ซึ่งจะเป็นประโยชน์ในสถานการณ์ที่มีการส่งข้อความจำนวนมาก อุปกรณ์ออฟไลน์อยู่ และคุณต้องการให้ผู้ใช้เห็นเฉพาะเวอร์ชันล่าสุดเท่านั้น เมื่ออุปกรณ์เปิดอยู่
กรณีเร่งด่วน
ไม่บังคับ
ความเร่งด่วนจะเป็นการบอกให้บริการพุชทราบถึงความสำคัญของข้อความต่อผู้ใช้ ช่วงเวลานี้ สามารถใช้บริการพุชเพื่อช่วยรักษาอายุการใช้งานแบตเตอรี่ของอุปกรณ์ของผู้ใช้ได้โดย ตื่นมาพร้อมกับข้อความสำคัญเมื่อแบตเตอรี่เหลือน้อย
ค่าส่วนหัวได้รับการกำหนดไว้ดังที่แสดงด้านล่าง ค่าเริ่มต้น
ค่าคือ normal
Urgency: [very-low | low | normal | high]
ทุกอย่างรวมกัน
หากมีคำถามเพิ่มเติมเกี่ยวกับวิธีการทำงานของทุกอย่างนี้ คุณสามารถดูวิธีที่ไลบรารีทริกเกอร์ข้อความ Push ได้ในองค์กร web-push-libs
เมื่อคุณมีเพย์โหลดที่เข้ารหัสและส่วนหัวข้างต้นแล้ว คุณก็เพียงแค่ต้องส่งคําขอ POST ไปยัง endpoint
ใน PushSubscription
แล้วเราจะทำอย่างไรกับการตอบกลับคำขอ POST นี้
การตอบสนองจากบริการพุช
เมื่อคุณส่งคำขอไปยังบริการพุชแล้ว คุณจะต้องตรวจสอบรหัสสถานะ ในการตอบกลับ ซึ่งจะแจ้งให้ทราบว่าคำขอประสบความสำเร็จหรือไม่ หรือไม่
รหัสสถานะ | คำอธิบาย |
---|---|
201 | สร้างแล้ว เราได้รับและยอมรับคําขอส่งข้อความ Push แล้ว |
429 | มีคำขอมากเกินไป ซึ่งหมายความว่าเซิร์ฟเวอร์แอปพลิเคชันของคุณมีอัตราการส่งข้อมูลถึงขีดจำกัดด้วยบริการ Push บริการ Push ควรมีส่วนหัว "Retry-After" เพื่อระบุระยะเวลาก่อนที่จะส่งคำขออีกครั้งได้ |
400 | คำขอไม่ถูกต้อง ซึ่งโดยทั่วไปหมายความว่าส่วนหัวรายการใดรายการหนึ่งไม่ถูกต้องหรือมีรูปแบบไม่ถูกต้อง |
404 | ไม่พบ ข้อความนี้บ่งบอกว่าการสมัครใช้บริการหมดอายุแล้ว และใช้งานไม่ได้ ในกรณีนี้ คุณควรลบ "PushSubscription" และรอให้ลูกค้าสมัครอีกครั้ง |
410 | หายไปแล้ว การสมัครใช้บริการนี้ใช้ไม่ได้แล้วและควรนำออก จากแอปพลิเคชันเซิร์ฟเวอร์ ซึ่งสามารถทำให้เกิดซ้ำได้โดยการเรียก "unsubscribe()" ใน "PushSubscription" |
413 | ขนาดเพย์โหลดใหญ่เกินไป ขนาดเพย์โหลดขั้นต่ำที่บริการพุชต้องมี support คือ 4096 ไบต์ (หรือ 4 KB) |
นอกจากนี้ คุณยังอ่านมาตรฐานพุชจากเว็บ (RFC8030) เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับรหัสสถานะ HTTP ได้ด้วย
สถานที่ที่จะไปต่อ
- ภาพรวมข้อความ Push บนเว็บ
- วิธีการทำงานของพุช
- การสมัครใช้บริการผู้ใช้
- UX สิทธิ์
- การส่งข้อความด้วยไลบรารี Web Push
- โปรโตคอล Web Push
- การจัดการเหตุการณ์พุช
- การแสดงการแจ้งเตือน
- ลักษณะการทำงานของการแจ้งเตือน
- รูปแบบการแจ้งเตือนทั่วไป
- คำถามที่พบบ่อยเกี่ยวกับข้อความ Push
- ปัญหาที่พบบ่อยและการรายงานข้อบกพร่อง