หากคุณวางแผนที่จะทำเฉพาะคำขอ API มาตรฐาน ซึ่งเหมาะสำหรับนักพัฒนาแอปส่วนใหญ่ คุณสามารถข้ามไปที่คำตัดสินด้านความสมบูรณ์ได้ หน้านี้อธิบายการส่งคำขอ API แบบเดิมเพื่อขอผลการตัดสินด้านความสมบูรณ์ ซึ่งรองรับใน Android 4.4 (API ระดับ 19) ขึ้นไป
ข้อควรพิจารณา
เปรียบเทียบคำขอมาตรฐานและคำขอแบบคลาสสิก
คุณสามารถสร้างคําขอมาตรฐาน คําขอแบบคลาสสิก หรือผสมผสานกันตามความต้องการด้านความปลอดภัยและการป้องกันการละเมิดของแอป คำขอมาตรฐานเหมาะสำหรับแอปและเกมทั้งหมด และใช้เพื่อตรวจสอบว่าการดำเนินการหรือการเรียกเซิร์ฟเวอร์เป็นของแท้ ในขณะที่มอบการป้องกันบางอย่างจากการเล่นซ้ำและการกรองข้อมูลออกไปยัง Google Play คำขอแบบคลาสสิกมีค่าใช้จ่ายสูงกว่า และคุณมีหน้าที่รับผิดชอบในการติดตั้งใช้งานอย่างถูกต้องเพื่อป้องกันการลักลอบนำข้อมูลออกและการโจมตีบางประเภท คำขอแบบคลาสสิกควรส่งน้อยกว่าคำขอมาตรฐาน เช่น ส่งเป็นครั้งคราวเพื่อตรวจสอบว่าการดำเนินการที่มีคุณค่าสูงหรือมีความละเอียดอ่อนนั้นเป็นของจริงหรือไม่
ตารางต่อไปนี้จะไฮไลต์ความแตกต่างที่สําคัญระหว่างคําขอทั้ง 2 ประเภท
คำขอ API มาตรฐาน | คำขอ API แบบคลาสสิก | |
---|---|---|
ข้อกำหนดเบื้องต้น | ||
เวอร์ชัน Android SDK ขั้นต่ำที่จำเป็น | Android 5.0 (API ระดับ 21) ขึ้นไป | Android 4.4 (API ระดับ 19) ขึ้นไป |
ข้อกำหนดของ Google Play | Google Play Store และบริการ Google Play | Google Play Store และบริการ Google Play |
รายละเอียดการผสานรวม | ||
ต้องวอร์มอัป API | ✔️ (ไม่กี่วินาที) | ❌ |
เวลาในการตอบสนองของคำขอโดยทั่วไป | 200-300 มิลลิวินาที | ไม่กี่วินาที |
ความถี่ในการส่งคำขอที่เป็นไปได้ | บ่อยครั้ง (ตรวจสอบตามคำขอหรือการดำเนินการ) | ไม่บ่อยนัก (ตรวจสอบครั้งเดียวสำหรับการกระทำที่มีมูลค่าสูงสุดหรือคำขอที่มีความละเอียดอ่อนมากที่สุด) |
หมดเวลา | การวอร์มอัพส่วนใหญ่ใช้เวลาไม่ถึง 10 วินาที แต่เกี่ยวข้องกับการเรียกเซิร์ฟเวอร์ จึงขอแนะนำให้ตั้งค่าการหมดเวลาที่นาน (เช่น 1 นาที) คำขอผลการตัดสินจะเกิดขึ้นที่ฝั่งไคลเอ็นต์ | คำขอส่วนใหญ่ใช้เวลาน้อยกว่า 10 วินาที แต่เกี่ยวข้องกับการเรียกเซิร์ฟเวอร์ จึงขอแนะนำให้ตั้งค่าการหมดเวลาที่นาน (เช่น 1 นาที) |
โทเค็นผลการตัดสินความสมบูรณ์ | ||
มีรายละเอียดอุปกรณ์ แอป และบัญชี | ✔️ | ✔️ |
การแคชโทเค็น | การแคชในอุปกรณ์ที่ได้รับการปกป้องโดย Google Play | ไม่แนะนำ |
ถอดรหัสและยืนยันโทเค็นผ่านเซิร์ฟเวอร์ของ Google Play | ✔️ | ✔️ |
เวลาในการตอบสนองต่อคำขอแบบเซิร์ฟเวอร์ต่อเซิร์ฟเวอร์สำหรับการถอดรหัสโดยทั่วไป | 10 มิลลิวินาทีที่มีความพร้อมใช้งาน 99.9% | 10 มิลลิวินาทีที่มีความพร้อมใช้งาน 99.9% |
ถอดรหัสและยืนยันโทเค็นในเครื่องในสภาพแวดล้อมเซิร์ฟเวอร์ที่ปลอดภัย | ❌ | ✔️ |
ถอดรหัสและยืนยันโทเค็นฝั่งไคลเอ็นต์ | ❌ | ❌ |
ความใหม่ของการตัดสินความสมบูรณ์ | Google Play จะแคชและรีเฟรชข้อมูลบางอย่างโดยอัตโนมัติ | ระบบจะคำนวณผลการตัดสินทั้งหมดใหม่ในแต่ละคำขอ |
ข้อจำกัด | ||
คำขอต่อแอปต่อวัน | 10,000 โดยค่าเริ่มต้น (ขอเพิ่มได้) | 10,000 โดยค่าเริ่มต้น (ขอเพิ่มได้) |
คำขอต่ออินสแตนซ์ของแอปต่อนาที | การวอร์มอัพ: 5 ต่อนาที โทเค็นความสมบูรณ์: ไม่มีขีดจำกัดสาธารณะ* |
โทเค็นความสมบูรณ์: 5 รายการต่อนาที |
การป้องกัน | ||
ลดความเสี่ยงจากการดัดแปลงและการโจมตีที่คล้ายกัน | ใช้ช่อง requestHash |
ใช้ฟิลด์ nonce กับการเชื่อมโยงเนื้อหาตามข้อมูลคำขอ |
ลดความเสี่ยงจากการโจมตีแบบเล่นซ้ำและการโจมตีที่คล้ายกัน | การลดผลกระทบโดยอัตโนมัติโดย Google Play | ใช้ฟิลด์ nonce กับตรรกะฝั่งเซิร์ฟเวอร์ |
* คำขอทั้งหมด รวมถึงคำขอที่ไม่มีขีดจำกัดสาธารณะ จะอยู่ภายใต้ ขีดจำกัดการป้องกันที่ไม่ใช่แบบสาธารณะที่ค่าสูง
ส่งคำขอแบบคลาสสิกเป็นครั้งคราว
การสร้างโทเค็นความสมบูรณ์จะใช้เวลา ข้อมูล และแบตเตอรี่ และแต่ละแอปจะมี จำนวนคำขอแบบคลาสสิกสูงสุดที่ส่งได้ต่อวัน ดังนั้น คุณควร ส่งคำขอแบบคลาสสิกเพื่อตรวจสอบว่าการดำเนินการที่มีมูลค่าสูงสุดหรือมีความละเอียดอ่อนมากที่สุด เป็นของจริงเท่านั้นเมื่อต้องการการรับประกันเพิ่มเติมจากคำขอมาตรฐาน คุณไม่ควรส่งคำขอแบบคลาสสิกสำหรับการกระทำที่มีความถี่สูงหรือมีมูลค่าต่ำ อย่า ส่งคำขอแบบคลาสสิกทุกครั้งที่แอปเข้าสู่เบื้องหน้าหรือทุกๆ 2-3 นาทีในเบื้องหลัง และหลีกเลี่ยงการเรียกจากอุปกรณ์จำนวนมากในเวลาเดียวกัน ระบบอาจจำกัดอัตราการเรียกใช้คำขอแบบคลาสสิกมากเกินไปของแอปเพื่อ ปกป้องผู้ใช้จากการติดตั้งที่ไม่ถูกต้อง
หลีกเลี่ยงการแคชผลการตัดสิน
การแคชผลการตัดสินจะเพิ่มความเสี่ยงของการโจมตี เช่น การกรองข้อมูลออกและการเล่นซ้ำ ซึ่งจะมีการนำผลการตัดสินที่ดีกลับมาใช้ซ้ำจากสภาพแวดล้อมที่ไม่น่าเชื่อถือ หากคุณกำลังพิจารณาที่จะส่งคำขอแบบคลาสสิกแล้วแคชไว้เพื่อใช้ในภายหลัง เราขอแนะนำให้ส่งคำขอมาตรฐานตามต้องการแทน คำขอมาตรฐาน เกี่ยวข้องกับการแคชบางอย่างในอุปกรณ์ แต่ Google Play ใช้เทคนิคการปกป้องเพิ่มเติม เพื่อลดความเสี่ยงของการโจมตีแบบรีเพลย์และการกรองข้อมูล
ใช้ช่อง Nonce เพื่อปกป้องคำขอแบบคลาสสิก
Play Integrity API มีฟิลด์ที่ชื่อ nonce
ซึ่งใช้เพื่อ
ปกป้องแอปเพิ่มเติมจากการโจมตีบางอย่าง เช่น การโจมตีแบบเล่นซ้ำและการโจมตีแบบดัดแปลง
ได้ Play Integrity API จะแสดงค่าที่คุณตั้งไว้ในฟิลด์นี้ภายใน
การตอบกลับความสมบูรณ์ที่ลงชื่อแล้ว โปรดปฏิบัติตามคำแนะนำเกี่ยวกับวิธีสร้าง Nonceอย่างละเอียดเพื่อปกป้องแอปจากการโจมตี
ลองส่งคำขอแบบคลาสสิกอีกครั้งโดยใช้ Exponential Backoff
สภาพแวดล้อม เช่น การเชื่อมต่ออินเทอร์เน็ตที่ไม่เสถียรหรืออุปกรณ์ที่ทำงานหนักเกินไป อาจทำให้การตรวจสอบความสมบูรณ์ของอุปกรณ์ล้มเหลว ซึ่งอาจส่งผลให้ ไม่มีการสร้างป้ายกำกับสำหรับอุปกรณ์ที่น่าเชื่อถือ หากต้องการลดสถานการณ์เหล่านี้ ให้ใส่ตัวเลือกการลองอีกครั้งโดยใช้ Exponential Backoff
ภาพรวม
เมื่อผู้ใช้ดำเนินการที่มีคุณค่าสูงในแอปที่คุณต้องการปกป้องด้วยการตรวจสอบความสมบูรณ์ ให้ทำตามขั้นตอนต่อไปนี้
- แบ็กเอนด์ฝั่งเซิร์ฟเวอร์ของแอปจะสร้างและส่งค่าที่ไม่ซ้ำกันไปยัง ตรรกะฝั่งไคลเอ็นต์ ขั้นตอนที่เหลือจะอ้างอิงถึงตรรกะนี้เป็น "แอป" ของคุณ
- แอปของคุณสร้าง
nonce
จากมูลค่าที่ไม่ซ้ำกันและเนื้อหาของ การกระทำที่มีมูลค่าสูง จากนั้นจะเรียกใช้ Play Integrity API โดยส่งnonce
- แอปของคุณจะได้รับการตัดสินที่ลงนามและเข้ารหัสจาก Play Integrity API
- แอปจะส่งผลการตัดสินที่ลงนามและเข้ารหัสไปยังแบ็กเอนด์ของแอป
- แบ็กเอนด์ของแอปจะส่งผลการตัดสินไปยังเซิร์ฟเวอร์ของ Google Play เซิร์ฟเวอร์ Google Play จะถอดรหัสและยืนยันผลการตัดสิน แล้วส่งผลลัพธ์กลับไปยังแบ็กเอนด์ของแอป
- แบ็กเอนด์ของแอปจะเป็นตัวกำหนดวิธีดำเนินการต่อ โดยอิงตามสัญญาณที่มีอยู่ใน เพย์โหลดของโทเค็น
- แบ็กเอนด์ของแอปจะส่งผลลัพธ์ของคำตัดสินไปยังแอป
สร้าง Nonce
เมื่อปกป้องการดำเนินการในแอปด้วย Play Integrity API คุณจะใช้ประโยชน์จากฟิลด์ nonce
เพื่อลดผลกระทบจากการโจมตีบางประเภทได้ เช่น การโจมตีแบบแทรกกลาง (PITM) และการโจมตีแบบเล่นซ้ำ Play
Integrity API จะแสดงค่าที่คุณตั้งไว้ในฟิลด์นี้ภายใน
การตอบกลับความสมบูรณ์ที่ลงชื่อ
ค่าที่ตั้งไว้ในช่อง nonce
ต้องมีรูปแบบที่ถูกต้อง
String
- ปลอดภัยสำหรับ URL
- เข้ารหัสเป็น Base64 และไม่ตัดคำ
- มีอักขระอย่างน้อย 16 ตัว
- มีอักขระได้สูงสุด 500 ตัว
ต่อไปนี้เป็นวิธีทั่วไปบางส่วนในการใช้ฟิลด์ nonce
ใน Play Integrity API หากต้องการรับการปกป้องที่รัดกุมที่สุดจาก nonce
คุณสามารถรวม
วิธีการด้านล่าง
รวมแฮชคำขอเพื่อป้องกันการดัดแปลง
คุณใช้พารามิเตอร์ nonce
ในคำขอ API แบบคลาสสิกได้ในลักษณะเดียวกับพารามิเตอร์
requestHash
ในคำขอ API แบบมาตรฐานเพื่อปกป้องเนื้อหาของคำขอ
จากการดัดแปลง
เมื่อขอการตัดสินความสมบูรณ์
- คำนวณข้อมูลสรุปของพารามิเตอร์คำขอที่สำคัญทั้งหมด (เช่น SHA256 ของการซีเรียลไลซ์คำขอที่เสถียร) จากการกระทำของผู้ใช้หรือคำขอของเซิร์ฟเวอร์ที่เกิดขึ้น
- ใช้
setNonce
เพื่อตั้งค่าฟิลด์nonce
เป็นค่าของข้อมูลสรุปที่คำนวณแล้ว
เมื่อได้รับการตัดสินความสมบูรณ์ ให้ทำดังนี้
- ถอดรหัสและยืนยันโทเค็นความสมบูรณ์ แล้วรับข้อมูลสรุปจากฟิลด์
nonce
- คำนวณข้อมูลสรุปของคำขอในลักษณะเดียวกับในแอป (เช่น SHA256 ของการซีเรียลไลซ์คำขอที่เสถียร)
- เปรียบเทียบข้อมูลสรุปฝั่งแอปและฝั่งเซิร์ฟเวอร์ หากไม่ตรงกัน แสดงว่าคำขอไม่น่าเชื่อถือ
รวมค่าที่ไม่ซ้ำกันเพื่อป้องกันการโจมตีแบบเล่นซ้ำ
หากต้องการป้องกันไม่ให้ผู้ใช้ที่เป็นอันตรายนำการตอบกลับก่อนหน้าจาก Play Integrity API มาใช้ซ้ำ คุณสามารถใช้ฟิลด์ nonce
เพื่อระบุข้อความแต่ละรายการอย่างไม่ซ้ำกันได้
เมื่อขอการตัดสินความสมบูรณ์
- รับค่าที่ไม่ซ้ำกันทั่วโลกในลักษณะที่ผู้ใช้ที่เป็นอันตรายคาดเดาไม่ได้ เช่น ค่าดังกล่าวอาจเป็นตัวเลขสุ่มที่ปลอดภัยด้วยการเข้ารหัสซึ่งสร้างขึ้นที่ฝั่งเซิร์ฟเวอร์ หรือเป็นรหัสที่มีอยู่แล้ว เช่น รหัสเซสชันหรือรหัสธุรกรรม รูปแบบที่ง่ายกว่าและมีความปลอดภัยน้อยกว่าคือการสร้าง หมายเลขแบบสุ่มในอุปกรณ์ เราขอแนะนำให้สร้างค่าที่มีขนาด 128 บิตขึ้นไป
- เรียกใช้
setNonce()
เพื่อตั้งค่าฟิลด์nonce
เป็นค่าที่ไม่ซ้ำจากขั้นตอนที่ 1
เมื่อได้รับการตัดสินความสมบูรณ์ ให้ทำดังนี้
- ถอดรหัสและยืนยันโทเค็นความสมบูรณ์ แล้วรับค่าที่ไม่ซ้ำกันจากฟิลด์
nonce
- หากค่าจากขั้นตอนที่ 1 สร้างขึ้นในเซิร์ฟเวอร์ ให้ตรวจสอบว่าค่าที่ไม่ซ้ำกันที่ได้รับเป็นค่าใดค่าหนึ่งที่สร้างขึ้น และค่าดังกล่าวใช้เป็นครั้งแรก (เซิร์ฟเวอร์จะต้องบันทึกค่าที่สร้างขึ้นไว้ในช่วงระยะเวลาที่เหมาะสม) หากค่าที่ไม่ซ้ำที่ได้รับถูกใช้ไปแล้ว หรือไม่ได้ปรากฏในบันทึก ให้ปฏิเสธคำขอ
- หรือหากสร้างค่าที่ไม่ซ้ำกันในอุปกรณ์ ให้ตรวจสอบว่าค่าที่ได้รับนั้นใช้เป็นครั้งแรก (เซิร์ฟเวอร์ของคุณต้องบันทึกค่าที่เคยเห็นแล้วเป็นระยะเวลาที่เหมาะสม) หากมีการใช้ค่าที่ไม่ซ้ำกันที่ได้รับแล้ว ให้ปฏิเสธคำขอ
รวมการป้องกันทั้ง 2 อย่างเพื่อป้องกันการดัดแปลงและการโจมตีแบบรีเพลย์ (แนะนำ)
คุณสามารถใช้ฟิลด์ nonce
เพื่อป้องกันทั้งการดัดแปลงและการโจมตีแบบรีเพลย์ได้พร้อมกัน โดยสร้างค่าที่ไม่ซ้ำกันตามที่อธิบายไว้ข้างต้น แล้วรวมค่าดังกล่าวเป็นส่วนหนึ่งของคำขอ จากนั้นคำนวณ
แฮชคำขอ โดยตรวจสอบว่าได้รวมค่าที่ไม่ซ้ำกันเป็นส่วนหนึ่งของแฮชแล้ว การใช้งานที่รวมทั้ง 2 วิธีมีดังนี้
เมื่อขอการตัดสินความสมบูรณ์
- ผู้ใช้เริ่มการกระทําที่มีมูลค่าสูง
- รับค่าที่ไม่ซ้ำกันสำหรับการดำเนินการนี้ตามที่อธิบายไว้ในส่วนรวมค่าที่ไม่ซ้ำกันเพื่อป้องกันการโจมตีแบบรีเพลย์
- เตรียมข้อความที่ต้องการปกป้อง ใส่ค่าที่ไม่ซ้ำจากขั้นตอนที่ 2 ในข้อความ
- แอปของคุณจะคำนวณค่าแฮชของข้อความที่ต้องการปกป้องตามที่อธิบายไว้ในส่วนรวมแฮชคำขอเพื่อป้องกันการปลอมแปลง เนื่องจากข้อความมีค่าที่ไม่ซ้ำกัน ค่าที่ไม่ซ้ำกันจึงเป็นส่วนหนึ่งของแฮช
- ใช้
setNonce()
เพื่อตั้งค่าฟิลด์nonce
เป็นค่าแฮชที่คำนวณจาก ขั้นตอนก่อนหน้า
เมื่อได้รับการตัดสินความสมบูรณ์ ให้ทำดังนี้
- รับค่าที่ไม่ซ้ำกันจากคำขอ
- ถอดรหัสและยืนยันโทเค็นความสมบูรณ์ แล้วรับข้อมูลสรุปจากฟิลด์
nonce
- ตามที่อธิบายไว้ในส่วนรวมแฮชคำขอเพื่อป้องกันการดัดแปลง ให้คำนวณค่าแฮชอีกครั้งในฝั่งเซิร์ฟเวอร์ และตรวจสอบว่าค่าแฮชตรงกับ ค่าแฮชที่ได้จากโทเค็นความสมบูรณ์
- ตรวจสอบความถูกต้องของค่าที่ไม่ซ้ำกันตามที่อธิบายไว้ในส่วนรวมค่าที่ไม่ซ้ำกันเพื่อป้องกันการโจมตีแบบรีเพลย์
แผนภาพลำดับต่อไปนี้แสดงขั้นตอนเหล่านี้ด้วยnonce
ฝั่งเซิร์ฟเวอร์
ขอการตัดสินความสมบูรณ์
หลังจากสร้าง nonce
แล้ว คุณสามารถขอผลการตัดสินความสมบูรณ์จาก Google
Play ได้ โดยทำตามขั้นตอนต่อไปนี้
- สร้าง
IntegrityManager
ดังที่แสดงในตัวอย่างต่อไปนี้ - สร้าง
IntegrityTokenRequest
โดยระบุnonce
ผ่านเมธอดsetNonce()
ในเครื่องมือสร้างที่เชื่อมโยง แอปที่เผยแพร่ภายนอก Google Play และ SDK เท่านั้นจะต้องระบุหมายเลขโปรเจ็กต์ Google Cloud ผ่านsetCloudProjectNumber()
ด้วย แอปใน Google Play จะลิงก์กับโปรเจ็กต์ Cloud ใน Play Console และไม่จำเป็นต้อง ตั้งค่าหมายเลขโปรเจ็กต์ Cloud ในคำขอ ใช้ตัวจัดการเพื่อเรียกใช้
requestIntegrityToken()
โดยระบุIntegrityTokenRequest
Kotlin
// Receive the nonce from the secure server. val nonce: String = ... // Create an instance of a manager. val integrityManager = IntegrityManagerFactory.create(applicationContext) // Request the integrity token by providing a nonce. val integrityTokenResponse: Task<IntegrityTokenResponse> = integrityManager.requestIntegrityToken( IntegrityTokenRequest.builder() .setNonce(nonce) .build())
Java
import com.google.android.gms.tasks.Task; ... // Receive the nonce from the secure server. String nonce = ... // Create an instance of a manager. IntegrityManager integrityManager = IntegrityManagerFactory.create(getApplicationContext()); // Request the integrity token by providing a nonce. Task<IntegrityTokenResponse> integrityTokenResponse = integrityManager .requestIntegrityToken( IntegrityTokenRequest.builder().setNonce(nonce).build());
Unity
IEnumerator RequestIntegrityTokenCoroutine() { // Receive the nonce from the secure server. var nonce = ... // Create an instance of a manager. var integrityManager = new IntegrityManager(); // Request the integrity token by providing a nonce. var tokenRequest = new IntegrityTokenRequest(nonce); var requestIntegrityTokenOperation = integrityManager.RequestIntegrityToken(tokenRequest); // Wait for PlayAsyncOperation to complete. yield return requestIntegrityTokenOperation; // Check the resulting error code. if (requestIntegrityTokenOperation.Error != IntegrityErrorCode.NoError) { AppendStatusLog("IntegrityAsyncOperation failed with error: " + requestIntegrityTokenOperation.Error); yield break; } // Get the response. var tokenResponse = requestIntegrityTokenOperation.GetResult(); }
Unreal Engine
// .h void MyClass::OnRequestIntegrityTokenCompleted( EIntegrityErrorCode ErrorCode, UIntegrityTokenResponse* Response) { // Check the resulting error code. if (ErrorCode == EIntegrityErrorCode::Integrity_NO_ERROR) { // Get the token. FString Token = Response->Token; } } // .cpp void MyClass::RequestIntegrityToken() { // Receive the nonce from the secure server. FString Nonce = ... // Create the Integrity Token Request. FIntegrityTokenRequest Request = { Nonce }; // Create a delegate to bind the callback function. FIntegrityOperationCompletedDelegate Delegate; // Bind the completion handler (OnRequestIntegrityTokenCompleted) to the delegate. Delegate.BindDynamic(this, &MyClass::OnRequestIntegrityTokenCompleted); // Initiate the integrity token request, passing the delegate to handle the result. GetGameInstance() ->GetSubsystem<UIntegrityManager>() ->RequestIntegrityToken(Request, Delegate); }
เนทีฟ
/// Create an IntegrityTokenRequest opaque object. const char* nonce = RequestNonceFromServer(); IntegrityTokenRequest* request; IntegrityTokenRequest_create(&request); IntegrityTokenRequest_setNonce(request, nonce); /// Prepare an IntegrityTokenResponse opaque type pointer and call /// IntegerityManager_requestIntegrityToken(). IntegrityTokenResponse* response; IntegrityErrorCode error_code = IntegrityManager_requestIntegrityToken(request, &response); /// ... /// Proceed to polling iff error_code == INTEGRITY_NO_ERROR if (error_code != INTEGRITY_NO_ERROR) { /// Remember to call the *_destroy() functions. return; } /// ... /// Use polling to wait for the async operation to complete. /// Note, the polling shouldn't block the thread where the IntegrityManager /// is running. IntegrityResponseStatus response_status; /// Check for error codes. IntegrityErrorCode error_code = IntegrityTokenResponse_getStatus(response, &response_status); if (error_code == INTEGRITY_NO_ERROR && response_status == INTEGRITY_RESPONSE_COMPLETED) { const char* integrity_token = IntegrityTokenResponse_getToken(response); SendTokenToServer(integrity_token); } /// ... /// Remember to free up resources. IntegrityTokenRequest_destroy(request); IntegrityTokenResponse_destroy(response); IntegrityManager_destroy();
ถอดรหัสและยืนยันการตัดสินความสมบูรณ์
เมื่อคุณขอผลการตัดสินความสมบูรณ์ Play Integrity API จะให้โทเค็นการตอบกลับที่ลงชื่อแล้ว
nonce
ที่คุณระบุในคำขอจะกลายเป็นส่วนหนึ่งของโทเค็นการตอบกลับ
รูปแบบโทเค็น
โทเค็นคือ JSON Web Token (JWT) แบบซ้อน ซึ่งเป็น JSON Web Encryption (JWE) ของ JSON Web Signature (JWS) คอมโพเนนต์ JWE และ JWS จะแสดงโดยใช้การซีเรียลไลซ์แบบกะทัดรัด
อัลกอริทึมการเข้ารหัส / การลงนามได้รับการรองรับอย่างดีในการใช้งาน JWT ต่างๆ
ถอดรหัสและยืนยันในเซิร์ฟเวอร์ของ Google (แนะนํา)
Play Integrity API ช่วยให้คุณถอดรหัสและยืนยันผลการตรวจสอบความสมบูรณ์ในเซิร์ฟเวอร์ของ Google ได้ ซึ่งจะช่วยเพิ่มความปลอดภัยของแอป โดยทำตามขั้นตอนต่อไปนี้
- สร้างบัญชีบริการ ภายในโปรเจ็กต์ Google Cloud ที่ลิงก์กับแอป
ในเซิร์ฟเวอร์ของแอป ให้ดึงโทเค็นเพื่อการเข้าถึงจากข้อมูลเข้าสู่ระบบของบัญชีบริการโดยใช้ขอบเขต
playintegrity
แล้วส่งคำขอต่อไปนี้playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \ '{ "integrity_token": "INTEGRITY_TOKEN" }'
อ่านการตอบกลับ JSON
ถอดรหัสและยืนยันในเครื่อง
หากเลือกจัดการและดาวน์โหลดคีย์การเข้ารหัสการตอบกลับ คุณจะ
ถอดรหัสและยืนยันโทเค็นที่ส่งคืนภายในสภาพแวดล้อมเซิร์ฟเวอร์ที่ปลอดภัยของคุณเองได้
คุณจะได้รับโทเค็นที่ส่งคืนโดยใช้IntegrityTokenResponse#token()
เมธอด
ตัวอย่างต่อไปนี้แสดงวิธีถอดรหัสคีย์ AES และคีย์สาธารณะ EC ที่เข้ารหัส DER สำหรับการยืนยันลายเซ็นจาก Play Console เป็นคีย์เฉพาะภาษา (ในกรณีนี้คือภาษาโปรแกรม Java) ในแบ็กเอนด์ของแอป โปรดทราบ ว่าคีย์ได้รับการเข้ารหัส Base64 โดยใช้ค่าสถานะเริ่มต้น
Kotlin
// base64OfEncodedDecryptionKey is provided through Play Console. var decryptionKeyBytes: ByteArray = Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT) // Deserialized encryption (symmetric) key. var decryptionKey: SecretKey = SecretKeySpec( decryptionKeyBytes, /* offset= */ 0, AES_KEY_SIZE_BYTES, AES_KEY_TYPE ) // base64OfEncodedVerificationKey is provided through Play Console. var encodedVerificationKey: ByteArray = Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT) // Deserialized verification (public) key. var verificationKey: PublicKey = KeyFactory.getInstance(EC_KEY_TYPE) .generatePublic(X509EncodedKeySpec(encodedVerificationKey))
Java
// base64OfEncodedDecryptionKey is provided through Play Console. byte[] decryptionKeyBytes = Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT); // Deserialized encryption (symmetric) key. SecretKey decryptionKey = new SecretKeySpec( decryptionKeyBytes, /* offset= */ 0, AES_KEY_SIZE_BYTES, AES_KEY_TYPE); // base64OfEncodedVerificationKey is provided through Play Console. byte[] encodedVerificationKey = Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT); // Deserialized verification (public) key. PublicKey verificationKey = KeyFactory.getInstance(EC_KEY_TYPE) .generatePublic(new X509EncodedKeySpec(encodedVerificationKey));
จากนั้นใช้คีย์เหล่านี้เพื่อถอดรหัสโทเค็นความสมบูรณ์ (ส่วน JWE) ก่อน แล้วจึง ยืนยันและแยกส่วน JWS ที่ซ้อนกัน
Kotlin
val jwe: JsonWebEncryption = JsonWebStructure.fromCompactSerialization(integrityToken) as JsonWebEncryption jwe.setKey(decryptionKey) // This also decrypts the JWE token. val compactJws: String = jwe.getPayload() val jws: JsonWebSignature = JsonWebStructure.fromCompactSerialization(compactJws) as JsonWebSignature jws.setKey(verificationKey) // This also verifies the signature. val payload: String = jws.getPayload()
Java
JsonWebEncryption jwe = (JsonWebEncryption)JsonWebStructure .fromCompactSerialization(integrityToken); jwe.setKey(decryptionKey); // This also decrypts the JWE token. String compactJws = jwe.getPayload(); JsonWebSignature jws = (JsonWebSignature) JsonWebStructure.fromCompactSerialization(compactJws); jws.setKey(verificationKey); // This also verifies the signature. String payload = jws.getPayload();
เพย์โหลดที่ได้คือโทเค็นข้อความธรรมดาที่มีคำตัดสินด้านความสมบูรณ์
แก้ไขปัญหาเกี่ยวกับผลการตัดสินด้วยข้อความแจ้งของ Google Play (ไม่บังคับ)
หลังจากเซิร์ฟเวอร์ได้รับคำตัดสินด้านความสมบูรณ์แล้ว เซิร์ฟเวอร์จะกำหนดวิธีดำเนินการต่อได้ หากผลการตัดสินระบุว่ามีปัญหา เช่น แอปไม่มีใบอนุญาต มีการดัดแปลง หรืออุปกรณ์ถูกบุกรุก คุณสามารถให้โอกาสผู้ใช้ ในการแก้ไขปัญหาด้วยตนเองได้
Play Integrity API มีตัวเลือกในการแสดงกล่องโต้ตอบของ Google Play ที่ แจ้งให้ผู้ใช้ดำเนินการ เช่น ดาวน์โหลดแอปเวอร์ชันทางการ จาก Google Play
ดูวิธีทริกเกอร์กล่องโต้ตอบเหล่านี้จากแอปตามการตอบกลับของเซิร์ฟเวอร์ได้ที่กล่องโต้ตอบการแก้ไข