หากคุณวางแผนที่จะทำเฉพาะคำขอ 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
ดูวิธีทริกเกอร์กล่องโต้ตอบเหล่านี้จากแอปตามการตอบกลับของเซิร์ฟเวอร์ได้ที่กล่องโต้ตอบการแก้ไข